Dong Nai, Binh Duong, Thanh Hoa and Quang Nam join the tree, and Long An gains Ben Luc and Can Giuoc, taking coverage from 5 provinces to 9 and the panorama index from 425k locations to 493k. A province outline is now simplified no more loosely than the leaves it is the union of. It is not only drawn: the district assignment clips each province's panoramas against it, and at the old tolerance the outline bulged past its own districts, crediting every panorama in that band to a district it does not sit in. That band held 571 panoramas across the tree, 2.53% of Binh Duong's. Unioning adjacent districts also leaves hairline sliver rings along shared borders, some of only three points, which turf.simplify refuses to clean. Those are dropped before the outline is simplified.
3.8 KiB
Project Overview
VNGeoGuessr is a GeoGuessr clone focused on Vietnamese locations. Players pick a place on a three-level region tree, view a real street panorama, and guess where it was taken. Built with Next.js 16, React 19 and Tailwind CSS 4.
Key Characteristics
- Nested regions: one country, nine provinces, 75 districts and towns. The
tree is generated into
src/data/regions/— seesrc/lib/regions.jsfor the client-safe traversal API. - Rollup scoring: a guess is credited to the district its panorama actually sits in, then rolled upward to that district's province and to Vietnam. One round writes to three score boards and three distance boards.
- Prebuilt panorama index: a Postgres table of known Mapillary panorama ids with the district each one falls in, built offline and seeded to Neon. The game draws from that table instead of searching Mapillary at request time.
- Anti-cheat security: sessions live in Redis; the exact coordinates and the resolved district are server-side secrets until the guess is submitted.
- Interactive gameplay: click-to-guess on a Leaflet map.
Game Mechanics
- Street View: Mapillary panoramas, resolved by image id from the prebuilt index
- 360° Viewer: PhotoSphere Viewer for panoramic images
- Interactive Maps: Leaflet + OpenStreetMap for guess placement
- Distance-based Scoring: 0-5 point scale based on accuracy (Turf.js)
- Session Isolation: Redis-stored sessions with 30-minute expiry
Administrative basis
Every boundary is a pre-2025-merger administrative extent, taken from
OpenStreetMap boundary/historic relations. Vietnam merged its provinces in
mid-2025; today's official Ho Chi Minh City covers 36,566 km² and reaches Vung
Tau, which is not what people mean by the city. Two consequences to remember:
- Da Lat is a district of Lam Dong, not a top-level city.
- Duc Hoa belongs to Long An, not the Tay Ninh it was merged into.
Coverage
Nine provinces — Ha Noi, Ho Chi Minh, Da Nang, Lam Dong, Long An, Dong Nai, Binh Duong, Thanh Hoa, Quang Nam — split into 75 districts and towns. Coverage is deliberately partial and will grow in future releases.
Absent coverage has three distinct causes. Only one is a defect:
Not yet added — any province outside the nine. Roadmap, not a bug. Adding one
is an entry in the REGIONS config of scripts/build-region-boundaries.mjs, a
boundary build, a panorama index build, a district assignment run, and a
database seed (npm run data:seed). No application code changes: every UI
surface reads the generated tree.
No street imagery — a district inside a covered province where Mapillary
holds no panoramas, or too few to play, typically rural. The tree lists it;
isPlayable() in src/lib/regions.js is what keeps it out of play --
resolvePlayableRegion() rejects it server-side before a session is created,
and RegionPicker renders it disabled. Expected, and it may resolve on its own: re-running the
panorama index build and reseeding picks up new Mapillary coverage. A district
that is playable but sparse is flagged thin and labelled "few streets".
Missing from the boundary — a district whose OpenStreetMap lookup did not
resolve, so it never entered the province union and its panoramas were clipped
away. Cu Chi in Ho Chi Minh City is the known case: "missingParts": 1 in
src/data/boundaries/tphcm/tphcm.json, and TPHCM-CUCHI carries no bbox in
the tree. Fixable: resolve the query, rebuild the boundary, re-run that
province's panorama index, reseed the database. It costs Mapillary tile
requests against a 50,000/day cap, which is why it is not done automatically.
Before treating an empty district as a bug, check missingParts in its
province's boundary file — that is what distinguishes the third case from the
first two.