Location data powers a surprising range of business applications. Delivery routing. Territory management. Asset tracking. Store locators. Service area definition. Field workforce coordination. Anywhere geography matters to operations, location-aware applications provide value. These features integrate naturally with web applications and mobile apps, adding spatial awareness to core business workflows.
The naive implementation is straightforward: store latitude and longitude as decimals, call Google Maps when you need a map, and use the Haversine formula for distance calculations. This works for a proof of concept. It breaks in production when you need to query "find all customers within 15km of this warehouse" against 50,000 records, or when your geocoding bill arrives, or when users in rural areas complain that addresses aren't resolving correctly.
Building location features that perform at scale, cost predictably, and handle edge cases requires understanding the full stack: coordinate systems, geocoding strategies, spatial databases, map rendering, and the privacy implications of location data. This page covers the patterns we use.
Coordinate Systems and Precision
Coordinates look simple. Latitude and longitude, two numbers. The complexity hides in the details.
WGS84: The Standard
WGS84 (World Geodetic System 1984) is the coordinate reference system used by GPS and most mapping services. Coordinates are expressed as latitude (north/south, -90 to +90) and longitude (east/west, -180 to +180). Store coordinates in this format unless you have a specific reason not to.
Some legacy systems use other coordinate systems. British National Grid (EPSG:27700) is common in UK government data. UTM (Universal Transverse Mercator) appears in surveying data. PostGIS handles conversions between systems, but WGS84 should be your storage standard.
Precision Matters
Coordinate precision directly affects accuracy. Each decimal place represents roughly:
| Decimal Places | Approximate Precision | Use Case |
|---|---|---|
| 1 | ~11 km | Country-level only |
| 2 | ~1.1 km | Town identification |
| 3 | ~110 m | Street-level |
| 4 | ~11 m | Building identification |
| 5 | ~1.1 m | Door-level accuracy |
| 6 | ~10 cm | Survey-grade positioning |
For most business applications, 6 decimal places is sufficient. Storing more precision wastes space without adding useful accuracy (GPS itself is typically accurate to 3-5 metres under normal conditions).
Storage format: Store coordinates as DECIMAL(9,6) for latitude and DECIMAL(10,6) for longitude if using standard SQL columns. This accommodates the full range of valid coordinates with appropriate precision. Better still, use PostGIS geometry types.
The Earth Is Not Flat
The Haversine formula calculates great-circle distance between two points on a sphere. It's computationally cheap and accurate enough for most purposes. For distances under 20km, the error is negligible.
For longer distances or when accuracy matters (surveying, precise routing), use the Vincenty formula or PostGIS's geography type, which accounts for the Earth's ellipsoidal shape. The difference between spherical and ellipsoidal calculations can be up to 0.5% over long distances.
Geocoding: Addresses to Coordinates
Converting addresses to coordinates (forward geocoding) and coordinates to addresses (reverse geocoding) is fundamental. It's also where naive implementations become expensive.
The Naive Approach
Call a geocoding API every time you need coordinates. Google Maps Geocoding API, Mapbox, or HERE. Pass the address string, get back latitude and longitude.
This works until:
- You process a bulk import of 10,000 customer addresses and the API bill arrives
- The API rate-limits you mid-import and the job fails
- Users experience latency spikes during address entry
- The same addresses get geocoded repeatedly across different features
The Robust Pattern: Geocode Once, Cache Forever
Geocode addresses on first encounter, store the result, and never geocode the same address twice. This requires:
Address Normalisation
Before geocoding, normalise addresses to a canonical form. "10 Downing Street, London" and "10 Downing St, London, UK" should resolve to the same cache key. Strip whitespace, standardise abbreviations, uppercase for comparison. This prevents duplicate geocoding of equivalent addresses.
Geocode Results Table
Store geocoded results with the normalised address as key, coordinates, provider used, confidence score, and timestamp. Index on the normalised address for fast lookups. Include the raw API response for debugging.
Batch Processing
Queue geocoding jobs for bulk imports rather than processing synchronously. Respect API rate limits. Retry failed geocodes with exponential backoff. Alert on systematic failures (which often indicate address data quality issues). For robust queue handling patterns, see our approach to background jobs.
Provider Selection
Geocoding providers vary significantly in accuracy, coverage, and cost.
| Provider | Strengths | Considerations |
|---|---|---|
| Google Maps | Excellent global coverage, high accuracy, address autocomplete | Most expensive, strict terms (results must display on Google Maps) |
| Mapbox | Good accuracy, generous free tier, flexible terms | Weaker in some regions, less detailed POI data |
| HERE | Strong European coverage, good batch processing, enterprise support | Complex pricing, heavier API |
| Nominatim (OSM) | Free, no terms restrictions, self-hostable | Variable accuracy, limited commercial support, rate limits on public servers |
| Postcodes.io (UK) | Free UK postcode lookups, fast, no rate limits | UK only, postcode centroid (not door-level) |
For UK-focused applications, a hybrid approach works well: use Postcodes.io for initial postcode-based lookups (free, fast), then fall back to Google or Mapbox for full address resolution when door-level precision is needed.
Address Autocomplete
Address autocomplete provides suggestions as users type, reducing errors and improving data quality. Google Places Autocomplete is the standard, but charges per session (a session covers all keystrokes until selection).
Implementation pattern: debounce input (wait 300ms after typing stops before querying), restrict to relevant countries, bias results toward user location, and validate the final selected address with a geocode call.
Terms of service: Google's terms require displaying autocomplete results on a Google Map. If you're using Mapbox or Leaflet for display, you need a different autocomplete provider. Mapbox and Algolia Places offer alternatives with more flexible terms.
Spatial Databases: PostGIS
Storing coordinates as decimal columns works for simple cases. Once you need to answer questions like "find all locations within 10km" or "which delivery zone contains this address", you need a spatial database.
The Naive Approach
Store latitude and longitude as decimals. Calculate distances in application code. For "find nearest", pull all records and sort by calculated distance.
This fails at scale. Calculating distance for 50,000 records takes seconds. The database can't use indexes for distance queries because the calculation happens after retrieval. Memory usage spikes when loading all records for in-application filtering.
The Robust Pattern: PostGIS
PostGIS extends PostgreSQL with spatial types and functions. Coordinates become geometry or geography objects. Spatial indexes enable fast queries against millions of records.
Geometry vs Geography Types
PostGIS offers two spatial types with different calculation methods:
geometry
Planar calculations (flat Earth). Faster. Suitable for small areas where curvature is negligible. Units depend on coordinate system (degrees for WGS84).
Use for: building floor plans, city-scale analysis, when performance matters more than precision.
geography
Spheroidal calculations (curved Earth). Slower but accurate over long distances. Results in metres. Automatically handles coordinate wrapping.
Use for: country-wide queries, international applications, when accuracy matters.
For most business applications, geography is the better default. The performance difference is measurable but rarely significant with proper indexing.
Essential Spatial Queries
PostGIS provides functions for common spatial operations:
ST_DWithin: Find Within Distance
SELECT * FROM locations WHERE ST_DWithin(location, ST_MakePoint(-0.1276, 51.5074)::geography, 10000)
Returns all locations within 10km of the given point. Uses spatial indexes. Fast even against millions of records.
ST_Distance: Calculate Distance
SELECT name, ST_Distance(location, ST_MakePoint(-0.1276, 51.5074)::geography) as distance FROM locations ORDER BY distance LIMIT 10
Returns the 10 nearest locations with exact distances in metres. Combine with ST_DWithin for performance (filter first, then calculate exact distances).
ST_Contains: Point in Polygon
SELECT zone.name FROM delivery_zones zone WHERE ST_Contains(zone.boundary, ST_MakePoint(-0.1276, 51.5074)::geometry)
Returns which delivery zone contains the given point. Essential for territory assignment and service area checks.
ST_Intersects: Overlap Detection
SELECT a.name, b.name FROM territories a, territories b WHERE a.id != b.id AND ST_Intersects(a.boundary, b.boundary)
Finds overlapping territories. Useful for validation and conflict detection.
Spatial Indexing
Spatial indexes (GiST or SP-GiST) are essential for performance. Without them, every spatial query scans the entire table. With them, PostGIS can eliminate most records before calculation.
CREATE INDEX idx_locations_geom ON locations USING GIST (location);
Index creation is automatic for geography columns in recent PostGIS versions, but verify with EXPLAIN ANALYZE that your queries are using the index.
Performance benchmark: A "find within 10km" query against 100,000 points completes in under 10ms with a spatial index. Without the index, the same query takes 2-3 seconds. Index your spatial columns.
Map Display and Rendering
Map display seems straightforward: embed a map, add markers. The complexity emerges with scale (thousands of markers), customisation requirements, and mobile performance.
Provider Options
| Provider | Type | Best For |
|---|---|---|
| Google Maps | Proprietary | Consumer-facing apps where users expect Google. Street View. Places data. |
| Mapbox GL JS | Vector tiles | Custom styling, high performance, data visualisation. Developer-friendly. |
| Leaflet | Library (tile-agnostic) | Lightweight, works with any tile provider. Good for simple maps. |
| OpenLayers | Library (tile-agnostic) | Complex GIS applications. Steeper learning curve, more capable. |
| Maplibre GL JS | Vector tiles (open source) | Mapbox GL fork, no vendor lock-in, self-hostable. |
For internal business applications, Mapbox GL JS or Maplibre typically offer the best balance of capability and cost. For consumer applications where users expect familiar UI, Google Maps may be worth the premium.
Raster vs Vector Tiles
Traditional maps use raster tiles: pre-rendered images at each zoom level. Vector tiles send raw geometry data and render on the client.
Vector tiles are the modern standard. Use raster only when supporting legacy devices or when map styling isn't needed.
Handling Many Markers
Displaying thousands of markers causes performance problems. The browser struggles to render them all, and the map becomes unusable visually.
Marker Clustering
Group nearby markers into clusters that show a count. Expand on zoom or click. Leaflet.markercluster and Mapbox's cluster sources handle this automatically. For 10,000+ points, server-side clustering (PostGIS ST_ClusterKMeans) before sending data to the client improves initial load.
Viewport Loading
Only load markers visible in the current viewport. As the user pans or zooms, fetch new data. Requires a spatial query backend (PostGIS) that can efficiently return "points within this bounding box". Include a buffer around the viewport to prevent flickering during pan.
Vector Tile Data Layers
For very large datasets (100,000+ points), generate vector tiles from your data. Tippecanoe (from Mapbox) converts GeoJSON to vector tiles. Serve as static files or generate dynamically. The map library handles rendering efficiently using WebGL.
Performance target: Map interactions (pan, zoom) should feel instant, under 100ms. If your map stutters, you're sending too much data to the client. Move filtering and aggregation to the server.
Routing and Optimisation
Simple directions (A to B) are a solved problem. Multi-stop route optimisation with constraints is computationally hard and remains an active area of development.
Directions APIs
All major providers offer directions APIs: Google Directions, Mapbox Directions, HERE Routing, OSRM (open source). They return distance, duration, and turn-by-turn instructions for a given origin and destination.
Considerations when choosing:
- Traffic data: Google and HERE have the best real-time traffic. Mapbox and OSRM use historical patterns.
- Vehicle profiles: Truck routing needs height, weight, and hazmat restrictions. Not all providers support this.
- Waypoints: Most APIs support intermediate stops but limit the number (Google: 25 waypoints).
- Terms: Google requires displaying routes on Google Maps. Others are more flexible.
The Route Optimisation Problem
Given a set of stops, find the order that minimises total distance or time. This is the Travelling Salesman Problem (TSP), which is NP-hard. Exact solutions are computationally infeasible beyond about 20 stops.
Practical Approaches
Nearest Neighbour Heuristic
Start at the depot, always go to the nearest unvisited stop. Fast (O(n^2)), easy to implement, produces routes 20-25% longer than optimal. Good enough for informal route suggestions.
Google OR-Tools
Open-source optimisation library from Google. OR-Tools handles TSP, Vehicle Routing Problem (VRP), and variants with time windows, capacity constraints, and multiple vehicles. Python and C++ interfaces. Production-grade for medium-scale problems (hundreds of stops).
Commercial Optimisation APIs
Google Route Optimization API, Mapbox Optimization API, HERE Tour Planning. Handle large-scale problems, include traffic, return turn-by-turn directions. Cost scales with stops and complexity.
Constraints in Real Routing
Real delivery and service routing involves constraints that basic TSP ignores:
- Time windows: Customer available 9am-12pm only. Delivery before 6pm.
- Vehicle capacity: Van holds 50 packages or 500kg.
- Driver hours: Maximum 8 hours driving. Mandatory breaks.
- Priority stops: Some stops must happen first or last.
- Skills: Some deliveries require specific equipment or certification.
- Return to depot: Or end at a different location.
Each constraint narrows the solution space but complicates the optimisation. Commercial APIs and OR-Tools support these constraints; naive implementations don't.
Real-world result: A logistics client reduced daily route distance by 18% after implementing constraint-aware optimisation. For a fleet of 12 vehicles, this translated to measurable fuel savings and an extra 2-3 deliveries per vehicle per day.
Offline Maps and Caching
Field applications often operate where connectivity is poor or absent. Offline capability requires caching both map tiles and application data.
Map Tile Caching
Map tiles can be cached locally for offline use. The approach depends on the platform:
Mobile Native
Mapbox Mobile SDKs support offline map packs. Define a region and zoom range, download in advance. Tiles stored in SQLite. Automatic management of cache size and expiry.
Progressive Web Apps
Service workers can cache tile requests. Limited storage (varies by browser, typically 50-100MB usable). Manual cache management needed. Consider caching only frequently-accessed areas.
Desktop/Embedded
Download tile sets directly. MBTiles format (SQLite) is standard. Leaflet and OpenLayers support local tile sources. Storage is less constrained; pre-cache entire regions.
Terms of Service
Tile caching terms vary significantly by provider:
- Google Maps: No caching permitted beyond browser/OS cache. Offline requires specific licensing.
- Mapbox: Offline supported in mobile SDKs with active subscription. Pre-rendering for distribution requires custom agreement.
- OpenStreetMap: Free to cache and distribute. Attribution required.
For applications requiring offline maps, OSM-based tiles (self-hosted or via providers like Mapbox with appropriate licensing) are often the most practical choice.
Offline Data Synchronisation
Caching map tiles is only half the problem. Application data (customer locations, job details, route information) also needs offline access with eventual synchronisation.
Patterns for offline data:
- Download before departure: Sync the day's jobs before leaving. Read-only offline access.
- Queue modifications: Store changes locally, sync when connectivity returns. Handle conflicts (same record modified by different users).
- Optimistic UI: Show success immediately, sync in background. Roll back if sync fails.
Implementation note: For React Native and Expo applications, WatermelonDB provides a robust local database with sync capabilities. For web PWAs, IndexedDB with a sync layer handles most cases. The complexity is in conflict resolution, not storage.
Geofencing
Geofencing triggers actions when a device enters or exits a defined geographic area. Common uses: delivery arrival notifications, time tracking for field workers, asset security alerts.
Implementation Approaches
Client-Side (Mobile OS)
iOS and Android provide native geofencing APIs. Define regions, receive callbacks on entry/exit. Battery-efficient (uses cell towers and WiFi, not continuous GPS). Limited to ~100 regions per app on iOS.
Best for: user's own device, where you control the app.
Server-Side
Device reports location periodically. Server checks position against fence definitions using PostGIS ST_Contains. More flexible (unlimited fences, complex shapes), but requires position reporting infrastructure.
Best for: fleet tracking, asset monitoring, where position is already being reported.
Fence Definition
Circular fences (centre point plus radius) are simplest. Polygonal fences allow arbitrary shapes (property boundaries, irregular service areas). PostGIS handles both.
For client-side geofencing, circular regions are more battery-efficient. For server-side, polygons add no cost.
Edge Cases
- GPS jitter: Position updates can bounce in and out of a fence boundary. Implement hysteresis (require position to be clearly inside/outside before triggering) or debounce events.
- Tunnel/building entry: GPS signal loss can look like an exit. Don't trigger alerts on signal loss alone.
- Small fences: GPS accuracy (3-5m in good conditions, 10-30m in urban canyons) limits useful minimum fence radius. A 10m fence will generate false triggers.
Privacy and Compliance
Location data is personal data under GDPR and similar regulations. Collection, storage, and processing require appropriate legal basis and security measures.
Legal Basis
For employee location tracking, legitimate interest may apply for delivery tracking (customers expect to know where their package is) but consent is typically required for broader monitoring. For consumer applications, consent is almost always required.
The legal basis affects what you can do with the data. Location collected for delivery tracking shouldn't be used for productivity monitoring without separate consent.
Data Minimisation
Collect only what's needed. Store only what's needed. Delete when no longer needed.
Transparency
Users should know when location is being collected and why. Mobile apps should use location permission prompts that explain the purpose. Employees should have clear policies on what's tracked and how it's used.
Consider providing users access to their own location history. This builds trust and is often required under data subject access rights.
Security
Location data reveals patterns: where people live, work, visit regularly. Protect accordingly:
- Encryption: TLS for transmission, encryption at rest for storage.
- Access control: Limit who can view location data. Log access for audit.
- Retention: Automatic deletion after retention period. Don't keep data "just in case".
- Anonymisation: For analytics, consider k-anonymity or differential privacy techniques.
Breach risk: A location data breach is particularly sensitive. Historical location data can reveal home addresses, workplace, daily routines, and personal associations. The reputational and legal exposure from a location data breach exceeds that of many other data types.
Common Patterns
Recurring implementation patterns we use across location-aware applications:
Store Locator
User enters postcode or grants location permission. Use address autocomplete or Geolocation API.
Geocode the input (if postcode/address). Cache the result.
Query PostGIS for stores within radius, ordered by distance. ST_DWithin then ST_Distance.
Display results as list and map markers. Cluster if many results. Link to directions.
Delivery Zone Check
Admin defines delivery zones as polygons in a map editor. Store as PostGIS geometry.
Customer enters delivery address during checkout. Geocode to coordinates.
Query PostGIS: SELECT zone FROM delivery_zones WHERE ST_Contains(boundary, point).
Return zone-specific pricing, delivery windows, or "outside delivery area" message.
Field Worker Dispatch
Workers report location periodically via mobile app. Store in time-series table.
New job arrives. Geocode job address if not already done.
Find available workers within reasonable distance. Factor in current job queue and skills.
Assign job. Send notification with navigation link. Update dispatch board in real-time.
Technology Stack
Our standard stack for location-aware applications:
Database
- PostgreSQL with PostGIS extension
- Geography type for coordinate storage
- GiST indexes on spatial columns
- TimescaleDB for high-volume tracking data
Geocoding
- Google Maps or Mapbox as primary provider
- Postcodes.io for UK postcode lookups
- Results cached in database
- Queued processing for bulk operations
Map Display
- Mapbox GL JS or Maplibre for web
- React Native Maps or Mapbox SDK for mobile
- Vector tiles for custom styling
- Server-side clustering for large datasets
Routing
- Mapbox Directions or OSRM for simple routing
- Google OR-Tools for optimisation
- Commercial APIs for complex constraints
- Cached distance matrices where appropriate
What You Get
-
Accurate geocoding Addresses converted to coordinates reliably, with caching to control costs.
-
Fast spatial queries "Find nearest" and "within area" queries that perform at scale using PostGIS.
-
Interactive maps Maps that render smoothly with thousands of points and work offline when needed.
-
Efficient routing Optimised routes that account for real constraints, not just shortest path.
-
Reliable geofencing Entry/exit detection that handles GPS noise and edge cases.
-
Privacy compliance Location data handled with appropriate consent, minimisation, and security.
Location capabilities built on solid foundations: PostGIS for spatial queries, proper geocoding strategies, maps that perform, and privacy handled correctly from the start.
Build Location-Aware Features
Store locators, delivery routing, territory management, asset tracking, field workforce coordination. Location features that perform at scale, cost predictably, and handle the edge cases that naive implementations miss.
Discuss your location requirements →