Radius search uses great-circle distance, not bounding-box approximation
Why it matters
Bounding-box radius filtering — using lat BETWEEN and lng BETWEEN — is a rectangle, not a circle. At a 5km search radius, the corners of the bounding box extend approximately 7km from the center, meaning listings up to 40% farther than the stated radius appear in results. CWE-682 (Incorrect Calculation) applies because the distance calculation is mathematically wrong. ISO 25010:2011 functional-suitability is violated because the feature does not perform as advertised. For time-sensitive searches (finding an open restaurant, locating a nearby service), returning listings that are materially farther than the stated radius causes users to navigate to incorrect distances.
Severity rationale
High because bounding-box distance calculation is mathematically incorrect and silently returns listings outside the stated radius, producing demonstrably wrong search results.
Remediation
Replace bounding-box filtering with either a PostGIS spatial query or an in-application haversine distance calculation. PostGIS is preferred for large datasets because the computation happens in the database with a spatial index.
-- PostGIS: returns listings within 5km of a point
SELECT id, name, location,
ST_Distance(location, ST_Point(:lng, :lat)::geography) AS distance_meters
FROM listings
WHERE ST_Distance(location, ST_Point(:lng, :lat)::geography) <= 5000
ORDER BY distance_meters;
For application-layer calculation when PostGIS is not available:
function haversineKm(lat1: number, lon1: number, lat2: number, lon2: number): number {
const R = 6371;
const dLat = (lat2 - lat1) * Math.PI / 180;
const dLon = (lon2 - lon1) * Math.PI / 180;
const a = Math.sin(dLat / 2) ** 2 +
Math.cos(lat1 * Math.PI / 180) * Math.cos(lat2 * Math.PI / 180) * Math.sin(dLon / 2) ** 2;
return R * 2 * Math.asin(Math.sqrt(a));
}
const inRadius = listings.filter(l => haversineKm(searchLat, searchLng, l.lat, l.lng) <= radiusKm);
See also directory-map-location.location-search.radius-in-url — radius should be reflected in the URL for shareability.
Detection
-
ID:
radius-distance-calculation -
Severity:
high -
What to look for: Examine the search radius logic. Check whether distance is calculated using great-circle distance (haversine formula) or a spatial index query. Look for bounding-box approximations that might return incorrect results. Test a search with radius and verify that results are correctly ordered by distance.
-
Pass criteria: Radius search uses great-circle distance (haversine formula) or a spatial database query (PostGIS, MongoDB geospatial). Enumerate all distance calculation functions and confirm at least 1 uses haversine or spatial index. Results are accurately filtered by distance from the search point.
-
Fail criteria: Bounding-box approximation is used as the sole distance filter. Results include listings outside the radius, or exclude listings inside the radius. A simple lat/lng BETWEEN clause without haversine refinement does not count as pass.
-
Skip (N/A) when: No radius-based search feature exists.
-
Cross-reference: Compare with
directory-map-location.location-search.radius-in-url— search radius should be both accurately calculated (this check) and reflected in the URL for shareability. -
Detail on fail:
"Search radius uses bounding-box (lat/lng BETWEEN), returning listings 15km away from a 5km radius search"or"No distance calculation found; search appears to use arbitrary result ordering" -
Remediation: Use a spatial query with PostGIS or implement haversine distance:
SELECT id, name, location FROM listings WHERE ST_Distance(location, ST_Point(search_lng, search_lat)::geography) <= 5000 ORDER BY ST_Distance(location, ST_Point(search_lng, search_lat)::geography);Or in application code:
function haversineDistance(lat1, lon1, lat2, lon2) { const R = 6371; const dLat = (lat2 - lat1) * Math.PI / 180; const dLon = (lon2 - lon1) * Math.PI / 180; const a = Math.sin(dLat / 2) ** 2 + Math.cos(lat1 * Math.PI / 180) * Math.cos(lat2 * Math.PI / 180) * Math.sin(dLon / 2) ** 2; const c = 2 * Math.asin(Math.sqrt(a)); return R * c; } const resultsInRadius = listings.filter(l => { const distance = haversineDistance(searchLat, searchLng, l.lat, l.lng); return distance <= radiusKm; });
External references
- cwe · CWE-682 — Incorrect Calculation — bounding-box approximation returns incorrect radius-search results
- iso-25010:2011 · functional-suitability — Functional Correctness — haversine or spatial-index distance required for accurate radius filtering
Taxons
History
- 2026-04-18·v1.0.0·Initial import from directory-map-location·automated