Implementation of Nearby POI Detection in a Mobile Application
Finding the nearest ATM, pharmacy, or pickup point is a basic scenario for hundreds of apps. The difference between "works well" and "lags and shows outdated data" is in query architecture and proper API usage.
Two Approaches: Client-side and Server-side Search
Client-side search — load all points (or subset) into app, search nearest on device. Works for small datasets — up to several thousand points. Filter by distance via Haversine formula or via CLLocation.distance(from:) / Location.distanceTo(). Plus: works offline. Minus: can't store million points in memory.
Server-side search — PostGIS ST_DWithin, MongoDB $near, Elasticsearch geo_distance query. For large datasets only this way. App sends coordinates and radius, server returns sorted list.
Google Places Nearby Search
For POI from open data (cafes, banks, pharmacies) — Google Places API:
GET https://maps.googleapis.com/maps/api/place/nearbysearch/json?location=55.75,37.62&radius=1000&type=pharmacy&key=...
On iOS via GMSPlacesClient.findPlaceLikelihoodList or direct HTTP. On Android via Retrofit. Returns up to 20 results per request, next page — via pagetoken. Important: pagetoken activates not immediately, need 2 second delay before next page request.
For custom points (own stores, pickup points) — own backend. PostGIS query:
SELECT id, name, lat, lon,
ST_Distance(geom, ST_MakePoint(:lon, :lat)::geography) AS distance_m
FROM locations
WHERE ST_DWithin(geom, ST_MakePoint(:lon, :lat)::geography, :radius)
ORDER BY distance_m
LIMIT 50;
Display on Map and Clustering
If more than 50 points on screen — need clustering. On iOS: GMSMarkerClusterer from google-maps-ios-utils. On Android: ClusterManager from android-maps-utils. In Flutter: flutter_map + flutter_map_marker_cluster.
Clusters recalculate on every zoom change. Without debounce on onCameraMove event this causes lag — cluster calculation should happen asynchronously, not on main thread.
On cluster tap — smooth zoom via CameraUpdate.newLatLngBounds() to cluster bounds, not just zoom to center.
Update on Movement
Don't re-request POI on every location update. Logic: request on first load and when user moves more than N meters from last query center (for most cases — 300-500 m). CLLocation.distance(from: lastQueryCenter) > threshold.
Timeline: two to four days — provider choice, API integration, display with clustering, update logic.







