技术内容 / From a World Sheet to Country Map Packages
From a World Sheet to Country Map Packages
From a World Sheet to Country Map Packages
Travel did not begin as an attempt to build a map widget. It began as a used object: an accurate world map on aged parchment, with oxidation, folds, fibres, and worn edges; physical pushpins pressed into the sheet; text-only memories on sticky notes; and records with images or video on photo paper. A visitor should first perceive a sheet carrying time and only then the geographic data and web interface behind it.
That premise creates a precise conflict. Conventional map platforms are accurate, but they also bring Web Mercator, road tiles, and a utilitarian visual language. A generated parchment image can look convincing, but it cannot reproduce coastlines and administrative boundaries reliably. Travel therefore adopted a more involved separation of responsibilities: geography comes from real boundary data, material comes from a real paper scan, the two are composed in an offline cartographic pipeline, and the browser is responsible only for navigation and live content.
The first design: one world map with continuous zoom
The original model was a finite world sheet that behaved like a canvas. It could be dragged and zoomed, but the parchment still had torn physical edges. A continental view would show outlines and pins, country scale would reveal provinces or regions, and sticky notes and photo paper would appear only at city scale. WGS84 longitude and latitude would remain the only stored position; a second set of image-pixel coordinates would inevitably drift as assets changed.
Leaflet is an excellent way to assemble standard online tiles quickly, but Travel neither needed provider tiles nor wanted its geometry constrained by Web Mercator. OpenLayers supports a finite extent, a custom tile grid, and explicit projection transforms, while keeping pins, place labels, paper objects, and reading panels in separate layers. The first implementation therefore used OpenLayers and the Natural Earth 1 projection for the world sheet. Compared with Web Mercator, Natural Earth 1 avoids extreme polar inflation and reads more naturally as a complete piece of paper.
The source hierarchy combined Natural Earth, geoBoundaries, and, for the China prototype, GADM 4.1. The world overview used lighter coastlines and national boundaries; country views could read finer administrative data. AI was not allowed to draw the map because it cannot reproduce real coastlines, borders, or administrative identity consistently. A map API was not allowed to render it either: geocoding and cartography are separate jobs. Amap and Google Places are used only for an authenticated editor to turn a place query into candidate coordinates. Travel's own Region Catalog then performs the point-in-polygon assignment and stores a stable region_id.
Compositing real paper and administrative ink
The material base is a real parchment scan by Caleb Kimbrough. An offline renderer maps that scan into one paper coordinate system, then draws land tint, coastlines, and administrative boundaries. The world study was rendered at 8192×4096; the China and New Zealand studies at 4096×3072. The browser does not run Rough.js for this material. The renderer uses repeated Canvas strokes for a stable primary line and slight redraw offsets, a low-opacity blurred layer for ink bleed, and multiply compositing to seat the line in the fibres. Pen variation and bleed become part of a reproducible cartographic asset instead of an effect recalculated during every wheel gesture.
The intended tile renderer also had to prevent seams. Every random value was to be derived from global coordinates and a fixed seed. Paths would be drawn into buffered metatiles and cropped only after rendering. Oxidation, stains, and folds remained locked to the same UV coordinates at every scale, while finer fibres could appear with scale but could never be regenerated randomly. One early texture contained visible elliptical rings; it was removed because a procedural centre has no physical explanation on a real sheet of paper.
The 16K experiment: borrow high frequencies, not a replacement image
An 8K world sheet exposes pixels quickly at local scale, so we tested a 16K paper candidate in Colab with RealESRGAN_x2plus. The important decision was to constrain what the model was allowed to change.
The 8192×4096 paper-only image, before land tint and administrative ink, was processed in overlapping tiles to produce a 16384×8192 candidate. A Lanczos 2× resize of the original remained the colour and low-frequency baseline. Only a high-frequency residual extracted from the super-resolved candidate was added back at a tunable strength. The torn-edge alpha was resized deterministically and never reconstructed by the model. Oxidation, folds, stains, and overall colour therefore remained properties of the source scan; the model contributed only fibre and fine grain.
Validation was quantitative as well as visual. The 16K candidate was reduced back to 8K, then evaluated inside the valid paper alpha mask using MAE, PSNR, SSIM, low-frequency colour difference, and alpha error, with local comparison crops emitted for inspection. A candidate that failed to preserve the source at its original scale could not enter the map pipeline. This established high-frequency residuals as the right direction, but it did not remove the need to inspect repetition, seams, and slow dragging at the highest zoom levels.
The continuous-zoom proposal: raster, MVT, and PMTiles
Before the architectural transition, the plan for continuous world zoom had three layers. z0-z4 would retain the complete parchment raster. Between z4.3 and z4.7, it would cross-fade to “16K paper-only raster plus vector administrative ink”, rather than switching at an integer zoom. At z6-z10, three transparent detail tile sets would add medium fibres, fine fibres, and very fine grain. The old layer would remain visible until both the new paper and vector lines were ready; only then would an opacity cross-fade begin, preventing a white frame or a half-loaded map.
The administrative layer could not ask the browser to download global GeoJSON. The proposed build stage first topologised shared boundaries and assigned each edge a stable edge_id. Deterministic pen variation would be generated by normalised arc length along the highest-resolution master line, with nested levels of detail derived from that same line. Ordinary Douglas-Peucker simplification changes the vertex set at every zoom; applying fresh jitter to each simplified path would therefore make the ink jump even when the feature ID remained stable.
WGS84 geometry would be projected offline into Travel's Natural Earth 1 plane, clipped and quantised against a custom tile matrix, and encoded as MVT. PMTiles would serve only as a single-file container with Range Requests, read by a custom OpenLayers source loader. tippecanoe's normal output is tied to Web Mercator; pre-projecting with ogr2ogr alone would not solve the problem because tile addressing, clipping, and client decoding must all share the same coordinate definition.
We also considered a WebGL multi-scale virtual texture with Wang Tiles for the paper detail. It can suppress repetition more aggressively, but it requires custom shader, residency, and seam logic. The practical first step was standard OpenLayers TileLayers with seamless atlases at different periods and offsets, while acknowledging that slow movement at extreme zoom may still reveal a cycle. That is a material limit to test, not a reason to hide the problem behind a premature rendering engine.
Why the global high-zoom model failed
The first deployment did not merely have an incorrect threshold; the runtime owned the wrong scope. Starting at the world view, a few wheel steps exposed separate transitions for the complete raster, paper-only layer, administrative lines, and DOM keepsakes. Around +2, a darker line-free paper appeared; at +3, the paper colour changed again; detailed administrative lines appeared around +6; sticky notes and photo paper did not arrive until roughly +11, with the view capped at +14. The result did not feel like one sheet being enlarged. It felt like several incomplete maps taking turns.
The resource cost grew with the same mistake. Opening one Chinese or New Zealand city still prepared global high-level administrative geometry, paper detail, pin projection, and DOM overlays. During a wheel gesture, layer opacity, pin position, and paper-object size all changed together. Most county-level or equivalent local boundaries in a global package would never be visited, yet they still increased download, decode, memory, and draw costs. Moving the thresholds from +11 to +5 could change when the failure became visible, but not the upper bound of the architecture.
Several interaction defects came from the same model. Pins drifted when the base map and overlays used different coordinate transforms; transparent image bounds produced inconsistent hit areas between pin colours; an editor anchored above a pin could be pushed out of the viewport. Those issues were later addressed with one projection function, a shared hit region, and a fixed left editor drawer. The decisive performance problem, however, remained the attempt to make one world scene serve both discovery and local detail.
The final transition: a fixed world overview and country packages
The final design retires continuous zoom from the world into country detail. World mode is now a fixed, fast parchment index. It shows all real pins and lightweight outlines for available countries, but pins are not interactive there and the sheet cannot be dragged or zoomed. A wheel, double-click, or trackpad zoom attempt explicitly asks the visitor to choose a country; the country selector in the upper-left invokes the same command. China and New Zealand are currently ready, but the list comes from server data rather than a permanent front-end whitelist.
After selection, the browser loads only that country's manifest and map resources. Each country has independent bounds, base resolution, administrative hierarchy, initial view, and keepsake scale. Country mode begins at relative +0, displays sticky notes and photo paper from +5, and ends at +14. Both world and country modes treat WGS84 coordinates as the sole positional truth; screen pixels are always derived from the current projection. Country pins therefore come from the same D1 rows as world pins and never require a separately calibrated location copy.
The first country-package implementation is deliberately simple: one 4096×3072 map.webp plus a manifest, with administrative ink baked offline. Its purpose is to validate country boundaries, camera behaviour, and selective loading before increasing package complexity. The China package combines geoBoundaries ADM0 with GADM 4.1 ADM2 prefecture-level data. ADM1 is retained for Region Catalog metadata but omitted from the raster, preventing the double and misaligned borders produced by mixing sources. The New Zealand package uses geoBoundaries ADM0, ADM1, and ADM2. If high-zoom clarity later requires MVT, PMTiles, or a true raster pyramid, the country package can change internally without another migration of pin data or the browsing state machine.
How a new country enters the automated cartography pipeline
There is no artificial “Add country” button. A new country is a consequence of saving the first real pin there. Once a selected place supplies an ISO-3166 alpha-2 code, the Worker saves the pin and calls the country build logic idempotently through ctx.waitUntil. D1's countries table tracks pending, building, ready, or failed; country_build_jobs records each attempt. A partial unique index allows at most one active job per country.
The Worker sends a GitHub repository_dispatch event to build-country.yml. The workflow validates the uppercase two-letter code, serialises builds per country, installs the application and renderer dependencies, and then performs this sequence:
- Resolve country identity and extent from Natural Earth; obtain ADM0/ADM1/ADM2 from geoBoundaries, with GADM 4.1 ADM2 used by the current China prototype.
- Generate a country-specific renderer configuration and render with the same paper scan, ink parameters, and Natural Earth 1 projection.
- Produce a manifest containing SHA-256, bounds, administrative levels,
baseZoom,maxZoom, andkeepsakesFromZoom. - Upload version assets under
v3/countries/{CODE}/{version}/with a one-year immutable cache, then update the country manifest pointer with a 60-second cache. - Sign the callback body with SHA-256 HMAC. The Travel Worker verifies the signature, reads the manifest back from R2, and checks the country, version, and ready state.
- Only after all checks succeed does D1 atomically expose the country as
ready. A failure records its error and job state without exposing a partial package.
Pin writes and cartography therefore do not block each other. Content becomes real data first; a country package is a reproducible derived asset. The world page requests no high-zoom country resources, and entering China does not download New Zealand or any future country's administrative data.
What this transition gains, and what remains open
The new boundary separates problems that were always different. The world overview is responsible for atmosphere and discovery; a country map is responsible for local reading and editing. D1 owns live content, R2 owns replaceable map products, the browser renders only the selected country, and GitHub Actions performs the expensive offline work. The parchment visual baseline survives, while download, decode, and draw cost is limited to the place a visitor actually enters.
Several questions still deserve pressure testing: long-term licensing and replacement of GADM and other administrative sources; cross-zoom line continuity when a package moves from one raster to tiles or MVT; visible cycles in high-magnification paper fibres; the OpenLayers overlay and DOM budget at unusually high pin counts; and rollback behaviour across failed builds, old manifests, and real network caches.
Public implementation and source code:
https://github.com/Ysoseri1224/travel
If you have worked on offline cartography, custom Natural Earth 1 projections, topological MVT, PMTiles, multi-scale paper textures, OpenLayers, or versioned R2 assets, I would value criticism of both the discarded global model and the current country-package boundary. The repository contains the current code and public implementation; the reasoning, failures, and trade-offs are stated directly in this article.