跪拜 Guibai
← All articles
Frontend · Cesium · Vue.js

Cesium Basemap Migration: When Changing Tile URLs Moves Every Marker 400 Meters

By 小鹿小陆 ·
Read original on juejin.cn ↗ Google Translate ↗ Alt translation

Any Cesium deployment that serves users in China will hit this coordinate split, and the cost of getting it wrong is a map where every asset lands in the wrong block. The article's patterns—override the tile scheme, not the data; load-once-and-toggle layers; treat preferences as live state—apply directly to any multi-provider basemap architecture, including Western apps that switch between WGS84-compliant providers but need to support GCJ-02 data for Chinese users.

Summary

A real-world migration from Tianditu to AMap in a Cesium application starts with a simple URL change but quickly exposes a deeper problem: every icon and 3D model on the map shifts southeast by hundreds of meters. The cause is the coordinate datum difference—Tianditu uses WGS84, AMap uses GCJ-02, a non-linear offset applied to public coordinates in China—and no constant correction fixes it.

The fix is to leave business data in WGS84 and override the Cesium TilingScheme's two key methods: converting lat/lng to tile numbers and tile numbers back to lat/lng rectangles, inserting a WGS84↔GCJ-02 conversion at that boundary. The forward transform has an analytic expression; the inverse does not, so the author uses four iterations of residual-feedback to get sub-millimeter accuracy. The second problem is that switching basemaps by rebuilding the imagery provider causes a white flash and camera reset; the solution is to load all four layers (two imagery + two label layers) once and just toggle `.show`. The third problem is state: the preference must persist, propagate to live instances (via `WeakMap` and `WeakRef`), and be re-read from storage on every entry point, avoiding the module-level snapshot trap.

Takeaways
— Tianditu serves tiles in WGS84, AMap in GCJ-02; GCJ-02 offsets coordinates non-linearly with latitude and longitude.
— Do not fit a constant offset to fix the shift; measure three distant points to confirm the offset varies, which indicates a coordinate system mismatch.
— Keep business coordinates in WGS84; do the correction only at the tile mapping layer by overriding the TilingScheme's tileAt and rectAt methods.
— The forward GCJ-02 transform has an analytic expression; the inverse has no closed form, so use iterative inversion (4 rounds) to reach sub-millimeter precision.
— Do not flip the Y axis in the tile scheme; mainstream XYZ tiles already match the engine's internal north-to-south numbering.
— Load both basemaps (each with imagery and label layers) at initialization and switch by toggling `.show` to avoid provider-rebuild flashes and camera resets.
— When toggling labels, the condition must be `active && withLabels` to prevent mismatched label/provider combinations.
— Store per-instance state in a WeakMap and track live instances with WeakRef and FinalizationRegistry to prevent memory leaks and cross-instance interference.
— The storage event only fires in other documents; same-tab writes need an active pull, and module-level snapshots are stale after log in/log out cycles.
— Before launch, verify pixel alignment, seams across zoom levels, max-level out-of-range behavior, label toggles, multi-instance consistency, state persistence across refresh/tabs/re-login, and memory cleanup.
— Both Tianditu and AMap offer free developer keys; verify current terms for quotas and commercial use from the official platforms.
Conclusions

The author's decision to correct at the tile scheme rather than the data is the correct architectural call: it confines the coordinate-system difference to one layer, making future provider additions a matter of adding another parameter table entry instead of a data migration.

The iterative inverse is a pragmatic engineering tradeoff; since GCJ-02 has no closed-form inverse, accepting four rounds of feedback gets the error below tile seams, which is far cheaper than trying to derive a perfect transform.

The symptom list—offset points, zoom-time seams, and the 'refresh fixes it' bug—is a reusable diagnostic guide for any map app: coordinate datum mismatches show up as non-constant offsets, and stale module state always persists until a manual reload.

The article's pre-launch checklist is a reminder that the real cost of this migration is testing: most failures only appear under specific zoom levels or specific operation sequences, so a static screenshot check is never enough.

Concepts & terms
GCJ-02
The coordinate system used for public map data in China, applying a non-linear offset to WGS84 coordinates. Because the offset varies with latitude and longitude, there is no constant correction factor.
TilingScheme
In Cesium, the object that defines how a map is divided into tiles, responsible for converting between geographic coordinates and tile coordinates/volume. Overriding its tileAt and rectAt methods lets you insert custom coordinate conversions at the tile boundary.
WeakMap / WeakRef / FinalizationRegistry
JavaScript features for managing object references without preventing garbage collection. WeakMap holds keys weakly, WeakRef holds a reference that can be collected, and FinalizationRegistry lets you run cleanup after an object is collected; together they allow a global registry of live map instances that doesn't leak memory.
From the discussion
Featured comments
卷土的土

Coordinate transformation should be decoupled from business data.

See top comments, translated →
Source: juejin.cn ↗ Google Translate ↗ Backup ↗