/ Docs / Geolocation v1 Β· 2026
Geo Β· Location

Geolocation

How RideChain turns a fuzzy rural address into a deliverable point, streams live partner location battery-cheaply, fences every handover, and keeps that location data honest and private.

1. Overview β€” geolocation is the spine

Almost every interesting thing RideChain does leans on knowing where something is. Geolocation underpins matching (which partners are near this pickup?), routing (how do we get there cheapest?), live tracking (where is my parcel right now?), geofenced handovers (one of the four custody checks), and fraud detection (is this GPS even real?). It is less a feature and more the coordinate system the rest of the platform is drawn on.

Two stores carry the geo load, with a deliberate split of duties:

πŸ—„οΈ

PostgreSQL + PostGIS

The durable, authoritative store. Holds Points, Block Hubs, saved addresses, geofence polygons, partner service areas and resolved booking coordinates as geography columns. Answers nearest-eligible and containment queries (KNN, ST_DWithin) with GiST indexes. The source of truth that survives a restart.

⚑

Redis geo sets

The live, hot layer. Online partner positions are written to GEOADD sorted sets and queried with GEOSEARCH for sub-millisecond radius lookups, plus cached ETAs and last-known fixes. Volatile by design β€” a partner who goes offline simply ages out; it never has to be the system of record.

Rule of thumb: if it must still be true after a reboot (a Point's location, a geofence, a delivered booking's POD coordinates) it lives in PostGIS; if it is only true "right now" (a moving partner, a cached ETA) it lives in Redis. The Go geo module owns both and keeps them coherent. See the broader picture in System Architecture.
flowchart TB
  subgraph Apps["Edge apps (Flutter / PWA)"]
    PT["πŸ›΅ Partner app
streams location"] BK["πŸ“± Book app
live track + ETA"] end PT -->|"WebSocket fixes"| GEO["Go geo module"] GEO -->|"GEOADD / GEOSEARCH"| RDS[("Redis geo sets
live positions Β· ETAs")] GEO -->|"durable writes / KNN"| PG[("PostgreSQL + PostGIS
Points Β· geofences Β· addresses")] GEO -->|"nearest online partners"| MATCH["🎯 Matching"] GEO -->|"snapped path"| ROUTE["🧭 Routing (OSRM)"] GEO -->|"fence + timestamp events"| CUST["πŸ”’ Custody / handover"] GEO -->|"plausibility signals"| FRAUD["🚨 Fraud module"] RDS -->|"pushed fixes + ETA"| BK classDef hot fill:#fff3e0,stroke:#f4920b,color:#8a5200; classDef dur fill:#e8f0fb,stroke:#1f5fae,color:#143d6e; class RDS hot; class PG dur;
The Go geo module sits between the apps and the two stores, fanning location out to matching, routing, custody and fraud. Redis serves the live read path; PostGIS holds the durable truth.

2. The rural addressing problem (and how we resolve it)

The hardest geo problem in the Indian interior is not GPS accuracy β€” it is that most homes have no usable postal address. "Ramu's house behind the temple, near the big neem tree" is a real, deliverable address to a local and a useless one to a database. RideChain cannot demand a pin-perfect lat/long from a first-time rural sender, so it accepts fuzzy input and resolves it down to something a partner can actually reach.

πŸ“

Map pin

Drag a pin on a map tile. Works when there is data and the user is map-literate; we snap and store the resulting geography(Point).

⭐

Saved addresses

Once resolved, an address is saved (home, shop, in-laws' village) so the fuzzy work is done only once per place.

πŸ”Š

Voice address

The user speaks the address in their language; it is transcribed and parsed into village + landmark candidates for confirmation.

πŸ›•

"Near landmark X"

Free-text landmark ("behind Hanuman mandir") matched against a curated landmark gazetteer for that village.

πŸ”€

3-word / grid code

A what3words-style three-word or grid landmark code resolves to a ~3 m square β€” easy to read aloud over a phone, no spelling needed.

πŸͺ

RideChain Point anchor

The strongest signal: a known RideChain Point (kirana / CSC / panchayat / chai stall) is a surveyed, fixed coordinate. "Deliver to the Point near X" sidesteps door ambiguity entirely.

The resolver tries these in order of confidence, falling back gracefully, and always offers the Point as a known anchor when door-level resolution is too uncertain. Crucially, much of this works offline: a packaged village + landmark gazetteer lets the app suggest a deliverable area code even with no data, deferring the precise pin until sync.

flowchart TB
  IN["Fuzzy address input"] --> Q{"Have a map pin?"}
  Q -- "yes" --> SNAP["Snap pin to road / building"]
  Q -- "no" --> SAVED{"Matches a saved address?"}
  SAVED -- "yes" --> USE["Reuse stored coordinate"]
  SAVED -- "no" --> CODE{"3-word / grid code given?"}
  CODE -- "yes" --> DECODE["Decode to ~3 m square"]
  CODE -- "no" --> LAND{"Landmark in gazetteer?"}
  LAND -- "online" --> GEOCODE["Geocode village + landmark"]
  LAND -- "offline" --> OFFLINE["Offline fallback:
village centroid + landmark note"] LAND -- "ambiguous" --> CLARIFY["Operator / voice clarifies"] SNAP --> SCORE["Confidence score"] USE --> SCORE DECODE --> SCORE GEOCODE --> SCORE OFFLINE --> SCORE CLARIFY --> SCORE SCORE --> LOW{"Confidence below threshold?"} LOW -- "yes" --> ANCHOR["Offer nearest RideChain Point as anchor"] LOW -- "no" --> SAVE["Persist geography(Point) + save address"] ANCHOR --> SAVE
Resolving a fuzzy address to a deliverable location. Each path produces a confidence score; anything too uncertain falls back to a surveyed RideChain Point anchor rather than guessing a door.
Why the Point anchor matters: a Point is the only coordinate we have physically surveyed and verified. When everything else is fuzzy, routing to a Point is always safe β€” and it doubles as the fallback custody venue described in Last-Mile Delivery.

3. Live tracking architecture

Live tracking has to feel real-time to the booker while costing the partner almost no battery or data β€” non-negotiable on a budget Android phone in a low-signal village. The Partner app streams fixes over a WebSocket to the Go geo module, which writes the live position into the Redis geo index and pushes derived position + ETA on to the Book app.

sequenceDiagram
  autonumber
  participant PT as "Partner app"
  participant GEO as "Go geo module"
  participant RDS as "Redis geo index"
  participant ETA as "ETA / routing"
  participant BK as "Book app"
  PT->>GEO: "WS connect (job-scoped, authed)"
  loop "Adaptive sampling"
    PT->>GEO: "Location fix (lat, lng, accuracy, speed, ts)"
    GEO->>RDS: "GEOADD live position"
    GEO->>ETA: "Recompute ETA if moved enough"
    ETA-->>GEO: "ETA + remaining distance"
    GEO-->>BK: "Push position + ETA"
  end
  Note over PT,GEO: "Near pickup/drop β†’ sample faster; idle β†’ slower"
  PT-->>GEO: "Offline: buffer fixes locally"
  PT->>GEO: "On reconnect: replay buffered fixes (deduped by ts)"
        
Battery-aware location streaming. Fixes flow Partner app β†’ WebSocket β†’ Go geo module β†’ Redis, then a derived position + ETA is pushed to the Book app. Offline fixes are buffered and replayed on reconnect.

Adaptive sampling is the battery trick: the app raises the GPS sampling rate when the partner is close to a pickup or drop (where precision matters and the booker is watching), and drops it right down when the partner is idle, far away, or stationary. The geo module can also push the desired cadence to the device based on job state.

Partner stateSampling cadenceWhy
Idle / no active jobVery slow (or off)No one is tracking; save battery and data.
Assigned, en route, far awayLowCoarse position is enough for a far-off ETA.
Approaching pickup / drop geofenceHighBooker is watching; geofence entry must be precise.
Stationary > N secondsThrottle downPosition is not changing β€” stop spending power.
Low battery (see edge cases)Reduced everywhereProtect the partner's ability to finish the job.
Offline is the norm, not the exception. When the WebSocket drops, fixes are buffered to a local outbox with their original timestamps and replayed on reconnect β€” deduplicated by timestamp so a reconnect storm never corrupts the track. The Book app meanwhile shows "last known" with a clear staleness indicator rather than a frozen-but-fresh-looking dot.

4. Geofencing & the custody check

A geofence is a region β€” a circle (centre + radius) or a polygon β€” drawn around a place that matters: a RideChain Point, a pickup door, a drop door, or a Block Hub. Entering the right geofence, together with a trusted server timestamp, is one of the four custody checks a handover must pass β€” alongside the assignment lock, the unique parcel-ID/QR scan, and the two-sided handover OTP (see Last-Mile Delivery).

flowchart LR
  FIX["Partner location fix"] --> CHK{"Inside the handover
geofence?"} CHK -- "no" --> WAIT["Hold: handover blocked,
guide partner closer"] CHK -- "yes" --> ACC{"Accuracy within
threshold?"} ACC -- "no" --> WAIT ACC -- "yes" --> STAMP["Server stamps entry time"] STAMP --> GATE["Geofence custody check PASSED"] GATE --> NEXT["Proceed to QR scan + OTP"]
A geofence event. The device reports the fix, but the server decides containment and stamps the time β€” the partner's clock is never trusted for custody. Only then do the QR and OTP gates open.

Containment is evaluated server-side with PostGIS: ST_DWithin(point, fence_centre, radius) for circular fences and ST_Contains(polygon, point) for polygons, both backed by GiST indexes. Radii are tunable per Point because a tight 30 m fence that works in a town is hopeless beside a sprawling rural Point with poor GPS β€” see the edge-case note on fence sizing.

The timestamp is the server's, not the phone's. Geofence entry is only valid when the server records it, because a device clock can be wrong or deliberately skewed. This is what lets the geofence + timestamp pair stand up as a custody control rather than a soft hint.

5. Nearest queries β€” who is closest?

The question matching keeps asking is: "which eligible, online partners are nearest to this pickup, right now?" Geolocation answers it from both stores. Redis gives the fast first cut over live positions; PostGIS refines with durable eligibility and exact geo predicates.

  1. Redis GEOSEARCH β€” pull all online partners within an expanding radius of the pickup from the live geo set, ordered by distance. Sub-millisecond, no disk.
  2. PostGIS KNN refine β€” for the candidate set, apply durable eligibility (vehicle type, service area, rating, capacity) and exact distance with the <-> KNN operator over a GiST index.
  3. Hand to matching β€” the ranked, eligible shortlist feeds the dispatch cascade in Nearest-Partner Matching.
flowchart TB
  PICK["Pickup location"] --> RDS[("Redis GEOSEARCH
radius, online only")] RDS --> CAND["Candidate partners
(by distance)"] CAND --> PGQ[("PostGIS KNN refine
eligibility + exact distance")] PGQ --> SHORT["Ranked eligible shortlist"] SHORT --> MATCH["🎯 Matching cascade"] SHARD["Geo-sharding by region / block"] -.->|"scopes the search space"| RDS SHARD -.-> PGQ
Nearest-partner resolution: Redis radius search for speed, PostGIS KNN for exact, eligible ranking, then matching. Geo-sharding by region/block scopes both queries so they stay fast as the network grows.
Geo-sharding for scale: live positions and queries are partitioned by region / block, so a pickup search never scans the whole country's partners β€” only the relevant shard. This keeps nearest-queries cheap as the partner base grows; the sharding strategy is detailed in Scale & Low-Cost.

6. Coordinate handling & accuracy

A GPS fix is never a point β€” it is a point plus an accuracy radius, and in rural / forest / low-signal terrain that radius can balloon. RideChain treats accuracy as a first-class field on every fix and refuses to pretend a Β±200 m fix is a doorstep.

ConditionTypical accuracyHow we handle it
Open rural road, clear sky~5–15 mTrust the fix; snap to road network for ETA.
Dense forest / canopy~30–100 mWiden tolerances; lean on the Point anchor near handover.
Deep valley / hill shadow~50–200 mHold last-known + ETA; do not auto-pass geofence.
2G-only / weak signal areaCoarse, intermittentBuffer fixes; use cell-tower / Wi-Fi assist where available.
Tunnel / building interiorNo fixFreeze to last-known with a staleness flag; resume on reacquire.

Two corrections run on the stream. Snapping projects a raw fix onto the nearest plausible road segment (via the OSRM road network used in Fastest-Route Finding) so a track looks like a road journey, not a drunkard's walk across fields. Jitter / drift smoothing rejects physically impossible jumps and lightly filters scatter while a device is stationary, so a parked partner does not appear to wander.

Accuracy gates custody. A geofence check (Β§4) requires the fix accuracy to be within threshold before it can pass. A Β±150 m fix cannot prove you are inside a 50 m fence β€” so the handover holds and the operator/anchor fallback kicks in rather than a false "arrived".

7. Anti-spoofing & fraud

Because geofence + timestamp is a money-releasing custody check, a faked location is an attack on the ledger. The geo module feeds the fraud module a set of plausibility signals and flags fixes that cannot be trusted; a flagged fix can block escrow release until a human confirms.

flowchart TB
  FIX["Incoming location fix"] --> MOCK{"Android mock-location
flag set?"} MOCK -- "yes" --> FLAG["Flag + block release"] MOCK -- "no" --> SENSOR{"Sensors agree?
(accelerometer Β· cell tower)"} SENSOR -- "implausible" --> FLAG SENSOR -- "ok" --> SPEED{"Speed plausible?
(no teleport jump)"} SPEED -- "impossible" --> FLAG SPEED -- "ok" --> ROUTEP{"On a plausible route?"} ROUTEP -- "no" --> SOFT["Soft-flag: extra review"] ROUTEP -- "yes" --> TRUST["Accept fix"] FLAG --> FRAUD["🚨 Fraud module:
require manual confirm"] SOFT --> FRAUD
Spoof detection layers cheap, strong checks first (the OS mock-location flag) then progressively softer plausibility checks. Anything hard-flagged blocks release and escalates to the fraud module.
Hard flag = blocked release. A mock-location or impossible-speed flag at a handover blocks the geofence custody check from passing, so escrow cannot release on a spoofed "arrival". Recovery routes through operator/manual confirmation; the full catalogue lives in Edge-Case Catalog.

8. Location privacy

Live location is sensitive personal data, and RideChain handles it under India's DPDP Act with explicit consent and tight scoping. The guiding principle: a partner's whereabouts are shared only as much as a live job requires, only while it is live.

πŸ«₯

Masked sharing

The booker sees an approximate live position and ETA, not a raw high-precision stream of the partner's exact GPS log.

⏱️

Active-job only

The partner's location is exposed to a booker only during that booker's active job. Job done, the live link closes β€” a partner is never trackable between jobs.

πŸ—‘οΈ

Retention limits

Live position trails are retained only as long as needed for tracking, disputes and audit, then aged out. Redis live fixes are inherently short-lived.

βœ…

DPDP consent

Location collection is consented at onboarding/booking with a clear purpose; consent and retention follow DPDP obligations.

πŸ†˜

SOS live-share

A safety override: triggering SOS broadcasts a precise live-location share to the safety/ops team for the duration of the incident.

Not exposed beyond the job: a booker can see their assigned partner approach while the job is live, and nothing afterward. Partner location is decoupled from booker identity once the leg settles.

9. Edge cases & failure modes

Geolocation in the interior is unreliable by default, so every failure mode below has a defined behaviour that degrades gracefully rather than blocking a delivery. The full cross-component catalogue lives in the Edge-Case Catalog.

ScenarioMitigation
GPS denied / location permission revokedDegrade gracefully: block GPS-based auto-confirm and fall back to operator manual confirm at a RideChain Point; prompt to re-grant.
No signal / 2G-only areaBuffer fixes in the local outbox and replay on reconnect; SMS-fallback status updates so the booker still gets progress.
GPS drift / low accuracy fixEnforce an accuracy threshold before any geofence pass; anchor to the surveyed Point coordinate instead of a wide, drifting fix.
Mock-location / GPS spoofDetect via the OS mock flag + sensor/speed plausibility, flag the fix, and block escrow release until manual confirm.
Battery low on partner deviceReduce sampling cadence everywhere to protect the partner's ability to finish the job; warn ops if it risks the SLA.
Device clock skewUse the trusted server timestamp for all custody and ordering β€” the device clock is never authoritative.
Geofence too small at a rural PointRadii are tunable per Point; widen the fence where GPS is poor so legitimate arrivals are not held forever.
Landmark address ambiguousOperator- or voice-assisted clarification narrows it; otherwise route to the nearest Point anchor as the deliverable point.
Offline during handoverQueue the geofence/OTP/POD events locally and reconcile on reconnect, deduped by timestamp; custody holds until confirmed.
Tunnel / forest dead zoneFreeze to last-known position with a staleness flag and hold the ETA rather than showing a stale dot as live; resume on reacquire.
Partner leaves the geofence before OTPBlock the handover and require re-confirmation inside the fence β€” the geofence custody check must hold at the moment of the OTP.
Spoofed "arrival" to release money earlyGeofence + server timestamp + accuracy gate must all pass; a fraud-flagged fix cannot satisfy the custody check, so escrow stays held.