Files
vngeoguessr/docs/project-overview.md
T
tiennm99 d1332cd689 feat(regions): open four more provinces for play
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.
2026-09-04 15:20:13 +07:00

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/ — see src/lib/regions.js for 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.