Back to Explore
GuideVibeFix 编辑部Updated Oct 17, 2026

How 'Coffee Near Me' Works: Location & Maps for Vibe Projects

From 'coffee near me' to live courier tracking: store WGS84, convert for GCJ-02/BD-09 at render, query nearby in two phases (geohash coarse + precise distance), pick a map SDK by compliance first, cache reverse-geocoding by geohash, and treat precise location as sensitive personal data from collection time.

Aerial photo of a dense city with streets and high-rise buildings in warm evening light, evoking urban location services

Scope first: what this guide does NOT cover

Location features are easy to confuse with three published guides. The division of labor:

  • "Site Search in Vibe Projects" covers text and vector retrieval — tokenization, inverted indexes, vector recall. This guide covers geospatial retrieval: how coordinates are stored, how "nearby" is queried, how distance ranking works. Different index structures, different ranking logic, different bottlenecks.
  • "Calendar Booking & Timezones in Vibe Projects" covers the time dimension — RRULE, the three timezone rules. This guide covers the space dimension. They meet only in combined filters like "open now + near me".
  • "Data Subject Requests (DSR)" covers what to do when users exercise deletion rights. This guide covers minimization at collection: location is sensitive personal data — collect less, blur it, delete on schedule. Complementary, not overlapping.

This guide answers one question: from "coffee shops near me" to live courier tracking, how a solo team ships location features without stepping on mines. Datums, nearby search, map SDK choices, geocoding, privacy compliance — all actionable today.

Lesson one: datums — the mandatory exam for China-market apps

Lat/lng is not one standard. Get this wrong and everything downstream is wrong:

  • WGS84: raw GPS coordinates, the international standard. Your database should store exactly this and nothing else.
  • GCJ-02 ("Mars coordinates"): what Chinese map SDKs (Amap, Tencent) use inside mainland China. A systematic offset of hundreds of meters from WGS84 — plot raw GPS on an Amap and the pin lands across the street.
  • BD-09: Baidu adds another offset layer on top of GCJ-02. Only relevant when using Baidu's SDK.

Iron rule: store WGS84 in the database, convert at render time for whichever SDK you use. The conversion algorithms are public (a few dozen lines) — don't store two coordinate sets and double your trouble. Non-China projects skip this, but still confirm which datum your SDK expects.

How to store precision: in PostgreSQL use geography(Point, 4326), not two naked floats. geography computes on the sphere (meters); geometry on a plane — visibly wrong across cities. MySQL users: POINT + SPATIAL index, same idea.

The core feature: nearby search in two phases

Two-phase nearby search diagram: coarse phase (user coordinate, geohash encoding, prefix match over 9 grid cells) then fine phase (candidate set, precise ST_Distance/Haversine, distance-cursor pagination), with a geohash length vs grid precision reference table

"Coffee shops within 3km of me" sounds trivial until you have a million rows and the naive query kills the database. The correct shape is two phases:

  • Phase 1 · coarse (index layer): encode the user coordinate as a geohash (e.g. wx4g0e), take a 5–6 character prefix, and prefix-match with a plain B-tree — cutting candidates from millions to hundreds in one shot. Query the 9-grid (own cell + 8 neighbors): a user standing on a cell boundary would otherwise miss the shop across the street.
  • Phase 2 · fine (compute layer): compute exact spherical distance for the few hundred candidates (ST_DWithin / ST_Distance, or Haversine in app code) and sort by distance.
  • Why two phases: exact distance math is CPU-hungry; running it row-by-row over millions is suicide. Prefix-index first, precise math second — the universal shape of every LBS system.
-- PostGIS nearby search in one statement (GIST index is the precondition)
SELECT id, name,
       ST_Distance(location, ST_MakePoint(116.4, 39.9)::geography) AS dist_m
FROM shops
WHERE ST_DWithin(location, ST_MakePoint(116.4, 39.9)::geography, 3000)
ORDER BY dist_m
LIMIT 20;
-- GIST index: CREATE INDEX ON shops USING GIST (location);
-- Without it, the query above is a full table scan
  • Paginate with a cursor, not offset: the sort key is distance (continuously varying); use WHERE dist_m > :last_dist. Offset re-computes all previous pages' distances — slower the deeper you go.
  • Geohash vs. PostGIS: on PostgreSQL, just use PostGIS (least fuss). On MongoDB / MySQL or serverless DBs without spatial indexes, a geohash prefix column is the zero-dependency alternative. Know both and stack switches don't scare you.

Map SDK selection: compliance first, price second

OptionFitsWatch out
Amap / Tencent LBSChina-majority usersGCJ-02 datum; daily-quota billing, free tier is genuinely enough for small projects
MapboxOverseas users, heavy style customizationPer map-load billing; most styling freedom
Google MapsOverseas, best POI dataPriciest; unavailable in mainland China
Leaflet + OSM tilesZero budget, can self-host tilesFree, but POI/geocoding is on you
  • The solo-dev default: Amap for China (Chinese docs, honest free tier), Mapbox overseas (good-looking, matches the vibe-project aesthetic). Google Maps is a "revisit when you're big" option.
  • Tiles can be self-hosted: base maps are just image tiles — OSM data + TileServer-GL, and a $5 VPS handles small traffic. The self-hosting guide's mindset, reused for maps.
  • Never ship an unrestricted SDK key in the frontend: restrict map keys by HTTP referer / bundle ID. Unrestricted keys get scraped and the bill is yours. A server-side proxy with caching is the cleaner architecture (next section).

Geocoding: two-way translation between addresses and coordinates — caching is where the money is

A user types an address; you need coordinates for nearby search. You get GPS coordinates; you need "Chaoyang District, Beijing" to display. Both directions call the map vendor's API, and both are billed per call.

  • Forward geocoding (address → coordinates): do it once when a merchant onboards or a listing is published, store the result. Addresses don't move — this is a one-time cost.
  • Reverse geocoding (coordinates → address): expensive at frequency. A courier reporting every 5 seconds is hundreds of thousands of calls a day.
  • Cache it: geohash-6 precision is ~1.2km × 0.6km — cache reverse-geocode results keyed by 6-char geohash for 7 days and every request on the same street hits. Hit rates of 80%+ are realistic, cutting the bill by three quarters. One Redis layer server-side; never let every request punch through to the vendor.
  • Degrade gracefully: if the geocoding API is down, nearby search must not go down with it. Fall back to "show the distance number, skip the address text" — the core path survives.

Privacy & compliance: location is sensitive personal data — restrain at collection

What sets location apart: both PIPL and GDPR classify precise location as sensitive personal data, which means:

  • Separate consent + data minimization. Location permission can't hide in a bundled agreement; "coffee near me" needs city-level precision, not continuous GPS. Write iOS / Android permission copy like a human: "we need your location to find nearby shops", not "location permission required". Refusal rates track your honesty.
  • Blurred storage: everything user-facing, logged, or analyzed uses geohash-5 (~5km) or coarser. Full-precision coordinates live in server memory for distance math and are discarded, never persisted. That's your strongest answer when regulators ask.
  • Retention windows: raw real-time points (courier tracks) aggregate into statistics after 7–30 days; track export goes to the user only. Per the DSR guide, location data must be purged in the same batch as deletion requests.
  • Background location is a red line: unless you're a navigation or running app, don't request it. Store review is brutal on background location — a solo team can't afford to burn a review cycle there.

Advanced: real-time location (delivery / ride-hailing)

"Watch the courier on the map" is a level up in complexity:

  • Throttle uploads: every 5 seconds from the client is plenty; every second wastes battery and bandwidth. Skip uploads when stationary (GPS drift < 20m); the server shows "updated X min ago" from the last point + timestamp.
  • Aggregate on delivery: 500 couriers can't be 500 pins on the user's map. Server-side grid aggregation (quadkey / geohash-prefix group-by) per viewport, shipping only cluster pins + counts. Re-aggregate on zoom change.
  • Channel choice: SSE is enough (server → client one-way). Websockets add connection management for zero incremental value here — same conclusion as the realtime-features guide: choose for need, not for show.
  • Disconnect semantics: courier app killed → no heartbeat for 30s → pin goes grey. "Courier online 2 min ago" beats a frozen fake pin.

The cost math: three map-API billing dimensions, how to save on each

  • Map loads (base tiles): billed per thousand loads. Save: no embedded map on list pages (static thumbnail instead); full map only on detail pages; CDN-cache tiles.
  • Geocoding / reverse: per call. Save: the geohash cache above + encode-once at ingestion.
  • Route planning: the priciest, per call at a high unit price. Save: sort lists by straight-line distance (free, compute it yourself); call route planning only when the user taps "navigate". "Within 3km" filters work fine on straight-line distance.

One line: separate "compute it yourself for free" (straight-line distance, geohash) from "pay per API call" (reverse geocode, routing) — and never pay for what you can compute. That's the entire cost playbook for location features.

Testing: three boundary cases you must cover

  1. The 180° meridian: geohash prefixes differ completely on either side; your 9-grid logic must handle it. Your app works for Pacific island users too — don't only test in Beijing.
  2. High latitudes: geohash cells distort near the poles (meridians converge); use spherical math (ST_Distance on geography), never planar.
  3. Mock coordinates: dev environments get a fixed coordinate matrix (one point each in Beijing / Shanghai / New York / London + edge cases) instead of walking outside with a real phone. Both iOS and Android simulators support mock location — one config line.

Pre-launch checklist: 8 items

  1. Coordinates stored as WGS84; GCJ-02/BD-09 conversion at the render layer per SDK
  2. PostGIS GIST index built; EXPLAIN confirms index use on million-row nearby queries
  3. 9-grid neighbor logic covers cell-boundary cases
  4. Pagination by distance cursor, never offset
  5. Reverse-geocode Redis cache (geohash-6 keys) with observable punch-through rate
  6. Permission copy written like a human; graceful fallback (manual city pick) on denial
  7. Blurred coordinates everywhere user-facing/logged; full precision never persisted
  8. Map keys restricted by referer/bundle ID, or proxied server-side

Closing: location features = spatial indexing + cost awareness + privacy respect

Technically it's spatial indexing (PostGIS/geohash) plus two-phase queries. On cost: never pay for what you can compute. On compliance: location is sensitive personal data — restrain at collection. Get all three right and location is a feature, not a future incident.

Related guides: "Site Search in Vibe Projects" (text/vector retrieval) and this guide (spatial retrieval) are the two wings of search; "Calendar Booking & Timezones" (time) plus this guide (space) complete the coordinate system for O2O products.

Views 0Comments 0

Comments (0)

ME
0/1000
Loading comments...
Browse projectsPublish your project

Related articles

Close-up photo of a video editing timeline on a monitor, with clips, color wheels and timecode visible
Guide
One 4K Upload Away from Meltdown: Video Pipelines for Vibe Projects

Uploaded video is just the start: it needs transcoding, multi-bitrate renditions, HLS packaging and an async queue before smooth playback. This guide gives you the full pipeline diagram, FFmpeg recipes, the HLS sweet spots, a BullMQ state machine, thumbnail/subtitle/moderation branches, and the three cost multipliers to calculate before launch.

DeploymentPerformanceBackend Engineering
Aerial night photo of a glowing highway interchange, a metaphor for database connections being routed and pooled
Guide
FATAL: Too Many Clients Already: Connection Pooling for Vibe Projects

FATAL: too many clients already is not a slow query or the wrong database — it's connection exhaustion. This guide covers the pooling math, PgBouncer's three modes, Supabase/Neon pooler setup, the Prisma prepared-statement gotcha, and a troubleshooting table.

PostgreSQLPerformanceBackend Engineering
Real photo: rows of server racks inside a data center, illustrating database archiving
Guide
Before the Table Hits 100M Rows: Data Archiving & Hot-Cold Separation for Vibe Projects

When append-only tables outgrow RAM, backups and queries decay together. A field guide to archiving for vibe projects: four signals that it's time, three strategies compared (native partitioning + pg_partman vs archive tables vs Parquet cold export), an idempotent archive job state machine, query routing, thaw procedures, and how archiving relates to backups and compliance retention.

Data WorkflowsDatabaseBackend Engineering