mirror of
https://github.com/koala73/worldmonitor.git
synced 2026-04-25 17:14:57 +02:00
Adds India (IN) to country-boundary-overrides.geojson using the same Natural Earth 50m Admin 0 Countries dataset already used for Pakistan. The override system automatically replaces the base geometry on app load, providing a higher-resolution MultiPolygon boundary (1518 points). Renames the fetch script to reflect its broader purpose and updates doc references in CONTRIBUTING.md and maps-and-geocoding.mdx. Fixes koala73/worldmonitor#1721 Co-authored-by: stablegenius49 <185121704+stablegenius49@users.noreply.github.com> Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com> Co-authored-by: Elie Habib <elie.habib@gmail.com>
149 lines
7.7 KiB
Plaintext
149 lines
7.7 KiB
Plaintext
---
|
|
title: "Maps Infrastructure & Geocoding"
|
|
description: "Static map assets, country geometry, geocoding services, and boundary overrides."
|
|
---
|
|
|
|
## R2 CDN — `maps.worldmonitor.app`
|
|
|
|
All large static map files are served from Cloudflare R2, **not** from Vercel. The R2 bucket `worldmonitor-maps` is fronted by a CF-proxied custom domain:
|
|
|
|
| URL | Use |
|
|
|-----|-----|
|
|
| `https://maps.worldmonitor.app/<file>` | Production URL (CF-proxied, cached, CORS headers) |
|
|
| `https://pub-8ace9f6a86d74cb2bd5eb1de5590dd9e.r2.dev/<file>` | Raw R2 — **never use in code** (no CF caching, no CORS) |
|
|
|
|
### Why R2 instead of Vercel?
|
|
|
|
- Cloudflare bandwidth is free; Vercel charges per GB at scale
|
|
- CF Cache Rules cache `/data/`, `/assets/`, `/textures/` etc. for 30 days at edge
|
|
- Large files (GeoJSON, PMTiles) don't bloat the Vercel deployment
|
|
|
|
### Files on R2
|
|
|
|
| File | Size | Purpose |
|
|
|------|------|---------|
|
|
| `countries.geojson` | ~210 KB | Base country polygons (ISO 3166-1 Alpha-2 coded) |
|
|
| `country-boundary-overrides.geojson` | ~600 KB | Higher-resolution Natural Earth boundary overrides |
|
|
| `*.pmtiles` | ~80 GB | Self-hosted vector map tiles (when `VITE_PMTILES_URL` is set) |
|
|
|
|
### Uploading to R2
|
|
|
|
```bash
|
|
# Single file
|
|
rclone copyto <local-path> r2:worldmonitor-maps/<filename>
|
|
|
|
# rclone config note: set no_check_bucket = true (token lacks CreateBucket permission)
|
|
```
|
|
|
|
### CORS
|
|
|
|
R2 does **not** support wildcard subdomains (`https://*.example.com`). Each origin must be listed explicitly in the CORS rules. Use `r2 bucket cors set` or direct `curl -X PUT` to the R2 API (Wrangler 4.31 may fail with "not well formed").
|
|
|
|
## Country Geometry Service
|
|
|
|
**File**: `src/services/country-geometry.ts`
|
|
|
|
This service provides all country-level geocoding: point-in-polygon lookups, ISO code resolution, name matching, bounding boxes, and centroids. It loads country boundaries once on first use and indexes them for fast queries.
|
|
|
|
### Data Flow
|
|
|
|
```
|
|
countries.geojson (/data/) ──► Parse & Index (rebuildCountryIndex) ──► countryIndex Map
|
|
│
|
|
country-boundary-overrides.geojson │
|
|
(R2 CDN, 3s timeout) ──► applyCountryGeometryOverrides ──► replace matching polygons
|
|
```
|
|
|
|
1. `countries.geojson` — base polygons with ISO codes and names, served from `/data/` (Vercel)
|
|
2. `country-boundary-overrides.geojson` — optional higher-resolution polygons from [Natural Earth](https://www.naturalearthdata.com/), served from R2 CDN (`maps.worldmonitor.app`). Features matched by `ISO3166-1-Alpha-2` (or `ISO_A2`) code; matching features replace the base geometry
|
|
3. Base file loads first and the country index is built immediately (service becomes usable). Override file is fetched afterward with a **3-second timeout** — failures are silently ignored. Override lookup uses a `Map<code, Feature>` for O(1) matching
|
|
|
|
### Indexed Data Structures
|
|
|
|
| Structure | Key | Purpose |
|
|
|-----------|-----|---------|
|
|
| `countryIndex` | ISO-2 code | Full geometry + bbox for point-in-polygon |
|
|
| `iso3ToIso2` | ISO-3 code | Alpha-3 → Alpha-2 conversion |
|
|
| `nameToIso2` | lowercase name | Country name → Alpha-2 lookup |
|
|
| `codeToName` | ISO-2 code | Code → display name |
|
|
| `sortedCountryNames` | — | Regex matchers sorted by name length (longest first) for text extraction |
|
|
|
|
### Key Exports
|
|
|
|
| Function | Purpose |
|
|
|----------|---------|
|
|
| `preloadCountryGeometry()` | Trigger early loading (call at app startup) |
|
|
| `getCountryAtCoordinates(lat, lon)` | Point-in-polygon → country code + name |
|
|
| `isCoordinateInCountry(lat, lon, code)` | Check if point is inside a specific country |
|
|
| `getCountryNameByCode(code)` | ISO-2 → display name |
|
|
| `iso3ToIso2Code(iso3)` | ISO-3 → ISO-2 |
|
|
| `nameToCountryCode(text)` | Exact name match → ISO-2 |
|
|
| `matchCountryNamesInText(text)` | Extract all country names from free text |
|
|
| `getCountryBbox(code)` | Bounding box `[minLon, minLat, maxLon, maxLat]` |
|
|
| `getCountryCentroid(code)` | Bbox center, with optional fallback bounds |
|
|
| `resolveCountryFromBounds(lat, lon, bounds)` | Resolve overlapping bounding-box regions using geometry |
|
|
|
|
### Name Aliases
|
|
|
|
Common alternate names are mapped in `NAME_ALIASES`:
|
|
|
|
```
|
|
'dr congo' → CD, 'czech republic' → CZ, 'uae' → AE, 'uk' → GB, 'usa' → US, ...
|
|
```
|
|
|
|
### Political Overrides
|
|
|
|
`POLITICAL_OVERRIDES` maps sub-national codes to sovereign codes where the app treats them as separate entities (e.g., `CN-TW → TW`).
|
|
|
|
## Country Boundary Overrides
|
|
|
|
The override mechanism lets us improve individual country boundaries without replacing the entire `countries.geojson`. This is the foundation for addressing disputed borders (see [#1044](https://github.com/koala73/worldmonitor/issues/1044)).
|
|
|
|
### How It Works
|
|
|
|
1. After loading base `countries.geojson`, the app fetches `country-boundary-overrides.geojson` from R2 CDN with a 3-second timeout
|
|
2. For each feature in the override file, it matches the country in `countries.geojson` by ISO Alpha-2 code (using a `Map` for O(1) lookup)
|
|
3. The override geometry **replaces** the base geometry (both in the raw GeoJSON used for map rendering and in the indexed point-in-polygon data)
|
|
4. The override file can contain any number of countries — only matching codes are applied
|
|
|
|
### Adding a New Country Override
|
|
|
|
1. Source the boundary from [Natural Earth 50m Admin 0](https://www.naturalearthdata.com/downloads/50m-cultural-vectors/) (depicts de facto boundaries — actual territorial control — not diplomatic claims)
|
|
2. Extract the country feature by ISO code and save as GeoJSON
|
|
3. Merge into or replace `country-boundary-overrides.geojson`
|
|
4. Upload to R2:
|
|
```bash
|
|
rclone copyto public/data/country-boundary-overrides.geojson r2:worldmonitor-maps/country-boundary-overrides.geojson
|
|
```
|
|
5. No code changes needed — the app picks up the new geometry automatically
|
|
|
|
**Example script**: `scripts/fetch-country-boundary-overrides.mjs` downloads the full Natural Earth 50m dataset (~24 MB), extracts country features (currently Pakistan and India), and writes the override file.
|
|
|
|
### Geopolitical Sensitivity
|
|
|
|
- Natural Earth shows **de facto** boundaries (who actually controls the territory), not diplomatic claims
|
|
- This is the same standard used by most mapping platforms
|
|
- When adding overrides for disputed territories, document the source and rationale in the PR description
|
|
- The override system does not add or remove territory — it replaces low-resolution outlines with higher-resolution ones from the same authoritative source
|
|
|
|
## Fallback Bounds
|
|
|
|
For regions where full polygon geometry may not be loaded, `ME_STRIKE_BOUNDS` in `country-geometry.ts` provides rectangular bounding boxes for Middle Eastern countries. `resolveCountryFromBounds()` uses these as a fast first pass, falling back to precise point-in-polygon when multiple bounding boxes overlap.
|
|
|
|
## Basemap Tiles
|
|
|
|
Basemap tile configuration lives in `src/config/basemap.ts`. See [Map Engine](/map-engine) for full details on tile providers (PMTiles, OpenFreeMap, CARTO), themes, and fallback behavior.
|
|
|
|
PMTiles are also served from R2 via `maps.worldmonitor.app`, configured through `VITE_PMTILES_URL`.
|
|
|
|
## Common Mistakes
|
|
|
|
| Mistake | Fix |
|
|
|---------|-----|
|
|
| Using `pub-*.r2.dev` URLs in code | Always use `maps.worldmonitor.app` (CF-proxied) |
|
|
| Serving large GeoJSON from Vercel | Upload to R2 — Vercel bandwidth is expensive at scale |
|
|
| Fetching overrides without a timeout | Always use `AbortSignal.timeout` — override CDN may be slow or down |
|
|
| Forgetting `POLITICAL_OVERRIDES` | Check if the country code needs mapping (e.g., `CN-TW → TW`) |
|
|
| Adding aliases without checking existing | Check `NAME_ALIASES` and `nameToIso2` map first |
|
|
| Using `projection([lon, lat])` without NaN guard | d3 projections can return `[NaN, NaN]` (truthy) — always check with `Number.isFinite()` |
|