A geospatial query is a database lookup that filters and sorts records by location — near a point, within a radius, or inside an area. It powers the feature request that arrives in almost every product’s second month: find nearby X — cafes, drivers, stores, other users. And it is the clearest case of a query class that ordinary B-tree indexing cannot serve: nearness is two-dimensional, and a structure sorted on one dimension cannot see it.
Key takeaways
| Question | Answer |
|---|---|
| The field type | GeoPoint — a latitude/longitude pair the database understands |
| The queries | Near (sorted by distance), within-radius, within-box/polygon |
| The index | 2dsphere-style: the globe mapped to indexable cells |
| Without it | Distance computed to every row — a full scan with trigonometry |
| The canonical feature | ”Find nearby X,” composed with ordinary filters |
The nearby query, created and felt
// JavaScript / Node.js — Back4app JS SDK
// "Find cafes within 2 km, nearest first" — the canonical geo query
const here = new Parse.GeoPoint({ latitude: 40.7484, longitude: -73.9857 });
const query = new Parse.Query('Cafe');
query.withinKilometers('location', here, 2); // radius filter, nearest-first
query.equalTo('openNow', true); // geo + ordinary filters compose
query.limit(20);
const cafes = await query.find();
cafes.forEach((c) => {
const km = here.kilometersTo(c.get('location'));
console.log(`${c.get('name')} — ${km.toFixed(2)} km away`);
}); // Flutter / Dart — Back4app Flutter SDK
// "Find cafes within 2 km, nearest first" — the canonical geo query
final here = ParseGeoPoint(latitude: 40.7484, longitude: -73.9857);
final query = QueryBuilder<ParseObject>(ParseObject('Cafe'))
..whereWithinKilometers('location', here, 2) // radius filter, nearest-first
..whereEqualTo('openNow', true) // geo + ordinary filters compose
..setLimit(20);
final response = await query.query();
for (final result in response.results ?? []) {
final cafe = result as ParseObject;
final loc = cafe.get<ParseGeoPoint>('location');
print('${cafe.get<String>('name')} at '
'${loc?.latitude}, ${loc?.longitude}');
} // iOS / Swift — Back4app Swift SDK
// "Find cafes within 2 km, nearest first" — the canonical geo query
let here = try ParseGeoPoint(latitude: 40.7484, longitude: -73.9857)
let query = Cafe.query(
withinKilometers(key: "location", geoPoint: here, distance: 2),
"openNow" == true // geo + ordinary filters compose
).limit(20)
query.find { result in
if case .success(let cafes) = result {
for cafe in cafes {
print("\(cafe.name ?? "?") — \(String(describing: cafe.location))")
}
}
} // Android / Kotlin — Back4app Android SDK
// "Find cafes within 2 km, nearest first" — the canonical geo query
val here = ParseGeoPoint(40.7484, -73.9857)
val query = ParseQuery.getQuery<ParseObject>("Cafe")
query.whereWithinKilometers("location", here, 2.0) // radius, nearest-first
query.whereEqualTo("openNow", true) // geo + filters compose
query.limit = 20
query.findInBackground { cafes, e ->
if (e == null) cafes.forEach { cafe ->
val loc = cafe.getParseGeoPoint("location")
val km = loc?.distanceInKilometersTo(here)
println("${cafe.getString("name")} — ${"%.2f".format(km)} km away")
}
} The same physics in SQL, via PostGIS — and the index that makes it viable:
-- A spatial index over the geography column:
CREATE INDEX cafe_location_gix ON cafe USING GIST (location);
-- "Cafes within 2 km, nearest first":
SELECT name, ST_Distance(location, ST_MakePoint(-73.9857, 40.7484)::geography) AS meters
FROM cafe
WHERE ST_DWithin(location, ST_MakePoint(-73.9857, 40.7484)::geography, 2000)
ORDER BY location <-> ST_MakePoint(-73.9857, 40.7484)::geography
LIMIT 20;
-- with the index: touches only cells covering the 2 km circle
-- without it: computes a distance for every row in the table
How the index sees the globe
A 2dsphere-style index makes nearness indexable by mapping the curved surface onto hierarchical cells whose sorted order preserves adjacency — points close on the globe land in nearby index entries. A radius query then reads like any index lookup: identify the few cells covering the circle, scan their candidates, verify exact distances on that short list. Spherical geometry — rather than flat-plane math — keeps results correct at real-world scales, across the antimeridian, and toward the poles.
The composition rule follows from ordinary query mechanics: geo constraints combine freely with equality, range, and pointer filters (openNow == true in the tabs above), and the selective non-geo filters in hot queries may deserve indexes of their own.
Pagination deserves a special note, because distance-sorted results break the offset habit. Skipping past page one of a near query forces the engine to re-rank everything skipped, and a point that moved between requests can shuffle the order under the user’s feet. The sturdier patterns: widen the radius progressively for “load more,” or paginate on a stable key within a fixed radius. Nearest-first is a ranking over live data — treat page boundaries as advisory, not as rows in a ledger.
Near vs. within vs. flat-plane queries
Which geo query should you use?
| Query shape | Answers | Ordering | Typical use |
|---|---|---|---|
| Near (proximity) | What is closest to here? | Nearest-first | ”Nearby cafes” lists, matching drivers |
| Within radius | What is inside r km of here? | None implied | Delivery eligibility, alerts |
| Within box | What is in this rectangle? | None | Map viewport rendering |
| Within polygon | What is inside this shape? | None | Neighborhoods, zones, geofences |
| Flat-plane (2d) | Same, on a projected plane | Varies | Game maps, floor plans — not the Earth |
The near/within distinction is product logic, not pedantry: near is a ranking (cap it with a limit and a maximum radius, or dense cities return thousands of rows), within is a predicate (pair it with the viewport or fence geometry). The flat-plane row is the honest footnote — for coordinates that are not on a sphere, spherical indexes are the wrong tool.
Common use cases
- “Find nearby X.” Cafes, gyms, ATMs, charging stations — the canonical proximity list, radius-capped and nearest-first.
- Matching and dispatch. Riders to drivers, jobs to couriers — near queries over frequently updated GeoPoints.
- Map viewports. Within-box queries feeding pins as the user pans — cheap, unordered, viewport-sized.
- Geofencing. Within-polygon checks for delivery zones, service areas, and location-triggered logic.
- Local social features. People-nearby and events-this-weekend, composed with privacy filters and pointer-based relationships.
Should you use geo queries or precompute regions? A decision matrix
| Query live with a geo index when… | Precompute region labels when… |
|---|---|
| Radii and shapes vary per request | Boundaries are fixed (stores, zones, districts) |
| Points move often (drivers, users) | Membership changes rarely |
| Nearest-first ordering matters | You only ever filter by region equality |
| Shapes are genuinely spatial | A simple region column answers everything |
| You have a spatial index available | You are grouping, not measuring |
The escape hatch matters: when every query reduces to “in zone A?”, a plain indexed string column beats spatial machinery — cheaper, simpler, and covered by ordinary B-trees. Spatial indexing earns its keep exactly when the geometry is dynamic: arbitrary centers, moving points, real distances.
Limitations and trade-offs
- Geo indexes are specialists. They serve spatial predicates only; your other filters still need their own indexes, and index maintenance costs writes — same tax, spatial rate.
- Unbounded near queries are footguns. Nearest-first over a dense city without a radius cap or limit ranks half the table. Always bound the search.
- Moving points write constantly. Live driver locations mean high-frequency GeoPoint updates, each maintaining the index — batch or throttle position updates where product allows.
- One point per row is the base model. A store with five branches is five records; shapes richer than points (routes, coverage areas) push toward fuller spatial databases.
- Precision has floors. Sphere-vs-ellipsoid error is negligible, but GPS noise is meters on a good day — design product logic (zones, thresholds) to tolerate it.
Geospatial queries on Back4app
Back4app is an open-source Backend-as-a-Service (BaaS) platform that combines a managed database, auto-generated REST and GraphQL APIs, authentication, file storage, and Cloud Code serverless functions. GeoPoint is a native column type on the managed MongoDB-backed store, and the proximity, radius, and box operators — withinKilometers, near, withinGeoBox — ship in the auto-generated APIs and every SDK, exactly as the code tabs above use them. Geo indexes are managed alongside the rest of your index strategy in the dashboard, which turns the usual “find nearby X” infrastructure project into a schema decision and three lines of query.
Frequently asked questions
What is a geospatial query?
A database lookup whose filter is spatial rather than by value: return records near a point, within a radius, or inside a box or polygon, usually sorted nearest-first. The database stores coordinates as a first-class type — a GeoPoint of latitude and longitude — and a spatial index answers the question without computing the distance to every row.
What is a GeoPoint field?
A column type holding a latitude/longitude pair, with latitude from -90 to 90 and longitude from -180 to 180. Because the database knows the field is geographic — not just two floats — it can index it spatially and expose distance operators: within kilometers or miles of here, inside this bounding box, contained in this polygon, sorted by proximity.
How does a 2dsphere-style geo index work?
It maps the curved surface of the Earth onto sorted, indexable cells — coarse cells subdivided into finer ones — so that nearness on the globe becomes adjacency in the index. A near query inspects the handful of cells covering the search area instead of every row, and spherical geometry keeps distances honest, including across the antimeridian and near the poles.
Why is a nearby search slow without a geo index?
Because without one the engine must compute the distance from your point to every row, then sort the lot — a full scan wearing trigonometry, repeated on every request. A geo index inverts the work: it walks straight to the cells covering the search radius and touches only plausible candidates. The gap widens with table size, exactly like any missing index, only costlier per row.
What is the difference between a near query and a within query?
A near query answers "what is closest?" — it sorts by distance from a point and typically caps results with a limit or maximum radius. A within query answers "what is inside?" — it filters by containment in a radius, box, or polygon and implies no ordering. Nearest-cafes lists are near queries; "stores in this map viewport" and geofence checks are within queries.
Can I combine geo filters with ordinary query filters?
Yes, and real features almost always do: within 2 km *and* open now *and* rated above four stars. The geo constraint composes with equality, range, and pointer filters in one query. Mind the interplay with indexing — the spatial index narrows by location first, and highly selective ordinary filters may deserve their own index so the combined query stays cheap.
How accurate are radius queries at large distances?
Spherical calculations model the Earth as a sphere, which differs from the true ellipsoid by up to roughly a third of a percent — meters over city distances, and usually irrelevant to product logic. What bites earlier is precision of the stored points themselves: coordinates from phone GPS carry meters of error outdoors and more indoors. For "find nearby X" both effects are noise.