Skip to content

refactor(math): redefine world coordinates to zoom-16 pre-scaled space#306

Merged
bhousel merged 1 commit intomainfrom
world-coord-z16
May 4, 2026
Merged

refactor(math): redefine world coordinates to zoom-16 pre-scaled space#306
bhousel merged 1 commit intomainfrom
world-coord-z16

Conversation

@bhousel
Copy link
Copy Markdown
Collaborator

@bhousel bhousel commented May 4, 2026

This PR redefines the "world" coordinate system that we added #297 to pre-scale the coordinate up to z16.

Background

Previously, wgs84ToWorld() produced coordinates in the 0..256 range (zoom 0). Converting world β†’ screen then required:

scale = 2^z
screenX = (worldX - 128) * scale + translateX

This means every cached world coordinate must be multiplied by 2^z on every frame, at every zoom level.

What this changes

World coordinates are now pre-scaled to zoom 16, giving a range of 0..16,777,216 (= 256 Γ— 2^16). The transform becomes:

scale = 2^(z - 16)
screenX = (worldX - 8_388_608) * scale + translateX

Key property: at z16 the scale is exactly 1.0 β€” world coordinates are pixel-perfect screen coordinates with no transform needed.

Motivation

Editors like Rapid project GeoJSON geometry once and cache the result as world coordinates, then render that cache every frame. With z0 world coords, this cached geometry must still be reprojected into screen coordinates for rendering.

By pre-scaling to z16 (much closer to the zooms that we actually the geometry at),

  • World -> screen can simply be performed as a multiply or GPU transform
  • Preserves a useful amount of floating point precision (i.e. details specified in pixels like dash lines or line widths are not infinitesimally small).
  • Can avoid recomputing and storing another bunch of coordinate arrays and other supporting data on every change in zoom.
  • This is especially helpful for Pixi.js pipelines where geometry is uploaded to the GPU once.

API compatibility

project() / unproject() compose wgs84ToWorld + worldToScreen (and inverses). These methods remain numerically invariant β€” all callers of project/unproject continue to work unchanged.

Tile.tileExtent values are now in z16 world space (0..16,777,216) instead of z0 (0..256).

Previously, wgs84ToWorld() produced coordinates in 0..256 range (z0).
worldToScreen() then needed scale = 2^z to convert world β†’ screen at
every zoom level.

This change pre-scales world coordinates to zoom 16, so the range is
now 0..16,777,216 (= 256 Γ— 2^16).  worldToScreen() now uses
scale = 2^(z-16):
  - at z16, scale = 1.0 β€” world coords ARE screen coords
  - at other zooms, a single scalar multiply is all that is needed

Motivation: Rapid (and similar editors) project GeoJSON geometry once
and cache the result as 'world coordinates'.  With the old z0 range,
every cached world point had to be multiplied by 2^z on every frame
and at every zoom change.  With z16 pre-scaling, geometry cached at
world-z16 can be rendered directly at z16 with no recalculation.
At other zoom levels only one scalar multiply separates cache from
screen β€” no per-vertex loop.

Follows on from #297.

API notes:
- project() / unproject() compose wgs84ToWorld + worldToScreen (and
  inverses) so those methods remain numerically invariant β€” no callers
  of project/unproject need to change.
- Tile.tileExtent values are now in z16 world space (0..16,777,216)
  instead of z0 (0..256).
@bhousel bhousel merged commit 0a63544 into main May 4, 2026
1 check passed
@bhousel bhousel deleted the world-coord-z16 branch May 4, 2026 21:42
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant