• Skip to primary navigation
  • Skip to main content
  • Engineering Blog
  • Open Source Contributions
  • See Job Openings

13 min read

People are not Birds: How We Replaced Radius Circles with Real Travel Shapes

By John Williams · May 15, 2026

When someone searches for a dog sitter or walker on Rover, they have a reasonable expectation: the results will be people who can actually get to them. That expectation sounds simple. For years, the underlying model made it harder to fulfill than it should have been.

Rover’s original service area feature drew a circle around a provider’s address. You set a radius (say, five miles) and the platform drew a perfect circle around your location. If an owner fell inside the circle, you were a candidate match. Clean, simple, and almost completely disconnected from how people actually move through cities. A provider in West Seattle might be shown to pet owners across Puget Sound. A provider in a dense urban grid might “reach” a destination three miles away that takes 45 minutes to drive to, while missing neighborhoods that are genuinely close by road. We called the project “People are not Birds,” because a bird flies in a straight line — people don’t.

This is the story of how we replaced those circles with something that actually reflects reality.

The Problem with Circles

A travel radius is seductive in its simplicity. It fits in a single integer column. It’s easy to reason about. The math is trivial. But a circle drawn from a fixed point has no concept of a body of water, a highway interchange, a mountain range, or rush hour. It treats all directions from a point as equally accessible, which is rarely true in practice.

For providers, this meant appearing in search results for places they’d never realistically serve. A busy professional who drives and is willing to travel 20 minutes through Seattle traffic might technically “reach” a neighborhood within their radius that actually takes 45 minutes to drive to, while missing another neighborhood that’s genuinely close by road but falls just outside the circle.

For owners, it meant seeing results that looked geographically plausible but weren’t. The provider was in range on a map, but not in range in the real world.

The traveling service area problem isn’t just a UX issue — it’s a data modeling issue. Storing a single integer radius says nothing about how a provider actually travels. It doesn’t capture mode of transport. It doesn’t capture road networks or traffic patterns. It flattens a complex, geography-dependent relationship into a number that, at best, approximates the truth.

Enter Isochrones

An isochrone is a shape on a map representing all the points reachable from a given origin within a set amount of time, or within a set distance, traveling by a specific mode. Think of it like a ripple spreading out from a point, except instead of spreading uniformly in all directions, it follows roads and paths and stops where geography stops it. Drive five miles north and you might cross a bridge and reach a neighborhood easily. Drive five miles west and you might hit a dead end or a body of water and get nowhere.

This is exactly the right model for Rover’s use case. A provider who drives and is willing to travel 30 minutes can now be represented by a polygon that follows the actual road network from their location. A provider who cycles has a different shape: smaller, shaped by bike paths and terrain rather than highways. A provider who only walks has a smaller shape still. The shape reflects actual reachability, not a theoretical radius.

To generate these polygons, we integrated with an external isochrone API. We send a provider’s location, their preferred travel mode, and their contour preference, either a travel time limit or a distance limit, and the API returns a GeoJSON polygon representing their actual service area. On the search side, we replaced the old radius query with a polygon intersection query in Elasticsearch: a pet owner’s location is tested against these polygons to determine which providers can genuinely reach them.

How We Built It

Inheriting the Prototype

The project started at Rover’s internal hackathon, Maker Days, in August 2023. A cross-functional team built a working proof of concept in three days. That’s genuinely impressive. The Maker Days proposal was optimistic, as these things often are, describing a path where everything would fall into place from there.

Four months later, when the project was formally picked back up, the picture was less tidy. The branch was stale. The codebase had moved on. A concurrent migration had removed Haystack (a Django search abstraction layer) and migrated to a new Elasticsearch interface in the interim, which broke several things in the POC. The prototype also had multiple failing tests that had been left in place under hackathon conditions, and we spent real time just getting it to a state where we could understand what it was doing. We also expanded scope: the Maker Days prototype assumed one polygon per provider. The production design needed one polygon per service type, because a provider might offer dog walking, drop-in visits, or house sitting, each with different travel preferences.

The formal Implementation Plan, written in March 2024, essentially started from first principles rather than building on the prototype. The retrospective was candid about it: the team wished we’d started from scratch. That’s not a criticism of the people who built the POC. Three days is three days, and the original work proved the concept was viable. In hindsight, though, the effort spent rehabilitating the branch probably cost more time than rewriting from scratch would have — a useful lesson about when to inherit a prototype and when to start fresh.

The Polygon Size Problem

Once we had the architecture working in development, we discovered that the data was dramatically larger than expected. A single GeoJSON polygon for a service area can run up to around 350 kilobytes. The original expectation was somewhere in the 12–20 kilobyte range based on raw API response sizes. The discrepancy (roughly 20x larger than expected) came down to how the polygon geometry was being stored before any optimization.

The scale implications were stark. Tens of thousands of active providers, multiple services each, all storing multi-hundred-kilobyte GeoJSON blobs in a relational database table — that’s not a viable architecture. During an early backfill attempt in August 2024, the team saw exactly how non-viable it was: the attempt triggered database replica lag severe enough that the retrospective described it as “nearly took down the DB.”

The solution had two parts. First, we tuned the generalization parameter in the isochrone API request. The API supports Douglas-Peucker generalization, a standard algorithm for reducing the number of coordinates in a polygon with minimal loss in visual accuracy. Without any generalization, an isochrone polygon might contain over 1,600 coordinates. With the parameter set to a 50-meter tolerance, that same polygon comes down to around 183 coordinates. The shape looks essentially identical to any human looking at a map. The data is dramatically smaller.

Second, we moved GeoJSON blobs out of the relational database into object storage, with the database holding only a reference. This is the right call on size and cost grounds, but it comes with a real tradeoff: fetching from S3 adds latency to any operation that needs to read the polygon data, including reindexing into Elasticsearch. We added metrics to track that impact and monitor it over time.

Elasticsearch and Geoshapes

Swapping a radius query for a polygon intersection query in Elasticsearch isn’t a drop-in change. The legacy model used a simple distance filter on a geo_point field. The new model requires a geoshape field and a shape intersection query, and geoshape is significantly more expensive to index than geo_point.

We ran into this in two ways. First, there was a version dependency. The performance optimizations that make geoshape indexing tractable at scale, specifically BKD tree-based indexing, a spatial index structure introduced in Elasticsearch 7.x that makes geoshape queries significantly faster at scale, weren’t available in the version of Elasticsearch Rover was running when we started geo-search testing. That version dependency pushed back the start of meaningful testing while the ES upgrade was worked through.

Second, when we did begin bulk reindexing with the new geoshape field type during an August 2024 backfill, the Elasticsearch cluster saturated. The bulk indexing thread pool queued up faster than it could drain, producing rejected execution errors and spiking indexing latency more than threefold. The investigation traced the issue to indexing throughput limits at the current cluster scale. The immediate fix was scaling up the cluster instance count. The longer-term fix was completing the Elasticsearch upgrade that unlocked the better indexing path.

There was one more ES planning item worth noting. Polygon coordinate data at the scale of all active providers adds up to a large amount of JSON. When we ran the numbers for full rollout, the polygon data alone would push the Elasticsearch cluster beyond its existing storage capacity. We planned for the cluster upgrade required to accommodate that and executed it before the global launch.

The Polygon Validity Problem

Not every polygon the external API returns is immediately usable. Elasticsearch’s geoshape type requires what the GeoJSON specification calls a valid Linear Ring: a closed polygon whose edges don’t cross themselves. The API doesn’t always return geometries that satisfy this requirement.

We discovered this during integration testing and had to build handling for it. The approach was iterative validation and correction: for each returned polygon, we check whether it forms a valid Linear Ring, attempt corrections if it doesn’t, and move on if a correctable version is available. There’s also an edge case, rare but real, where the geometry for a provider’s location produces no valid, distinct polygon at all. For those providers, we needed a graceful fallback path rather than a silent indexing failure.

In practice, these cases were uncommon. A partial backfill run in April 2025 processed tens of thousands of providers and found only a small handful that hit the no-valid-polygon case. But search infrastructure doesn’t get to decide which edge cases are too rare to matter. If a bad polygon reaches the indexing pipeline and silently fails, that provider’s service area doesn’t exist in search results. We handled it, and the launch rollout plan explicitly accounted for re-running the backfill with appropriate offsets to catch any providers that had initially fallen into this path.

The Backfill Calibration Problem

When it came time to backfill existing providers into the new model, we faced a question we hadn’t fully thought through: what contour value should we use? Providers already had a radius preference, so the obvious answer was to use that as their polygon contour distance. You had a 5-mile radius before; you get a 5-mile polygon now.

The problem is geometric. A circle with a 5-mile radius covers a specific area, and a polygon with a 5-mile maximum travel distance covers a smaller one. That’s always true by definition: the circle extends uniformly in every direction, while the polygon is constrained to where roads and paths actually go. We knew the shapes would differ; we didn’t know by how much.

To find out, we sampled around 180 services across markets in the US, UK, and Spain and compared the radius-based circle area against the backfilled polygon area for each. The mean reduction was around 50%. That varied significantly by geography: markets with no major water saw reductions of around 43%, markets with some water around 47%, and coastal or lake-heavy markets around 61%. In one coastal market, the polygon covered less than a quarter of what the circle had, because the original circle was mostly over ocean, and a polygon that follows roads cannot cover water. The more precisely you model a provider’s actual reach, the more honest you have to be about what that reach excludes.

This analysis shaped our backfill strategy. Rather than silently handing every provider a smaller service area than the one they’d configured, we could account for the reduction deliberately: either adjusting the contour value upward, or choosing not to backfill certain providers at all and letting them set their preferences explicitly. The investigation gave us the data to have that conversation with product and make a considered choice, rather than discovering the impact after the fact.

Precision Exposes Imprecision

More accurate geographic polygons surfaced a second-order problem we hadn’t anticipated: the polygons were precise enough to reveal how imprecise our upstream location data was.

When a user searches on Rover without being authenticated, we don’t have their exact location. We approximate it using their zip code centroid, the geographic center of their zip. That’s a reasonable approximation when your service areas are large circles: a centroid for a dense urban zip is probably somewhere inside a multi-mile radius. But a carefully constructed polygon that follows road networks is a different shape. The centroid might land inside the polygon while the user’s actual address was outside it entirely.

When this happened, the search pipeline would initially show the provider as available, centroid in range, but then issue a soft block notification after the user created an account and their real address turned out to be outside the provider’s actual service area. We saw an increase in these notifications after the experiment rolled out, which created friction for users who had reasonably expected to find providers near them, and affected provider behavior in turn.

The immediate fix was removing the soft block mechanism. It was treating a symptom of the data quality problem rather than the cause, and removing it eliminated the confusing experience. The real fix, and the direction we want to go, is collecting a more precise user location before search happens rather than after. That produces better matches from the start and removes the centroid approximation problem at its source. It’s a natural next iteration for this work.

The Result

The feature launched globally in October 2025, covering all active providers on the platform. The backfill took several days and required manual coordination: GitHub Actions imposes a job timeout shorter than the time the full backfill required, so the team restarted the job in stages across the launch window.

Providers can now set their service area based on how they actually travel. If you drive, your service area follows roads. If you cycle, it follows bike-accessible routes. If you walk, it reflects walking distance. Owners searching for a provider see results based on real reachability, not a circle drawn around an address. The geographic mismatch that had been silently affecting match quality for years is no longer baked into the data model.

The Shape of the Project

There’s an arc to this project that’s worth sitting with. It started as a three-day hackathon prototype in August 2023 and shipped as a production-ready global feature in October 2025. That’s two years from first commit to fully backfilled launch.

The gap between “this works in a demo” and “this works for every provider, reliably, at scale, without taking down the database or the search cluster” is where almost all of the real engineering lives. It’s where the architecture decisions get made, where the incidents happen, where the edge cases accumulate, and where the team has to be honest with itself about what it actually built versus what it thought it built. That gap is the job. We don’t know yet what the next version of this looks like, but the problem space keeps getting richer the deeper you go into it.

Learn More
  • Read Our Blog
  • Rover Q&A Community
  • Rover Store
  • Rover Guarantee
  • Safety
About Rover
  • About Us
  • Contact Us
  • Accessibility
  • Get the App
  • Press
  • Careers
  • Leadership Team
  • Privacy Statement
  • Cookie Policy
  • CA - Do Not Sell My Info
  • Terms of Service
Need Help?
  • Help Center
Follow Rover on FacebookFollow Rover on InstagramFollow Rover on LinkedInSubscribe to Rover's YouTube ChannelFollow Rover on TikTok
Your privacy choices
© 2026 A Place for Rover, Inc. All Rights Reserved.