Skip to content

Add anti-meridian support by rendering multiple world copies in WebGL2#388

Open
ZwaarContrast wants to merge 1 commit intoallmaps:developfrom
ZwaarContrast:feature/antimeridian-world-wrapping
Open

Add anti-meridian support by rendering multiple world copies in WebGL2#388
ZwaarContrast wants to merge 1 commit intoallmaps:developfrom
ZwaarContrast:feature/antimeridian-world-wrapping

Conversation

@ZwaarContrast
Copy link

Summary

  • When panning across the anti-meridian (±180° longitude), warped maps would disappear because the renderer only drew a single world copy
  • The WebGL2Renderer now renders multiple world copies, matching how OpenLayers handles world wrapping internally
  • The Viewport computes which world copies are visible (startWorld/endWorld) based on the viewport extent and the projection's world width
  • For each visible world, a translated projection transform is composed and passed to the shader, so the same map geometry is drawn at the correct offset
  • Coordinate wrapping ensures geoRectangle and getProjectedGeoBufferedRectangle always return values within the primary world, so tile fetching and proj4 inverse projections produce valid results
  • World wrapping is only enabled for Web Mercator (+proj=merc); non-wrapping projections default to a single world copy, keeping behavior safe for the viewport projection support in viewport projections #361

Files changed

  • packages/render/src/shared/web-mercator.ts (new) — RADIUS and HALF_SIZE constants, computed from the WGS84 sphere radius like OpenLayers
  • packages/render/src/viewport/Viewport.ts — startWorld, endWorld, worldWidth properties; wrapProjectedGeoPoint and projectedGeoPointToGeoPoint helpers
  • packages/render/src/renderers/WebGL2Renderer.ts — getWorldClipTransforms() pre-computes one transform per world copy; render methods loop over worlds
  • packages/render/test/viewport.test.ts (new) — 14 tests covering world offset calculation, coordinate wrapping, and non-Mercator safety
  • packages/render/package.json — added vitest dev dependency and test script

@ZwaarContrast ZwaarContrast force-pushed the feature/antimeridian-world-wrapping branch from fd8044f to da290f1 Compare March 17, 2026 16:21
@mclaeysb
Copy link
Member

Thank you for this very complete PR, @ZwaarContrast !

Firs off, and for historic reference: we were doing something similar than the wrapping you are proposing here before fixing the rendering over the anti-meridian, see bindPointWebMercatorProjection: not going as far as to render multiple copies, but fixing anti-meridian rendering specifically for the webmercator case by 'wrapping' coordinares.

And related info: the anti-meridian rendering seems to work now for MapLibre but not for OpenLayers. I'll be looking at that in the coming days first. Maybe we switch away from using geo coordinates in the process altogether, which could impact this PR.

But this PR is not a fix for anti-meridian rendering but rather extending functionality to rendering multiple copies. Let's first consider what is the desirable behavior here. I'd say:

Hard requirements:

  • If a map of say New-Zealand, with all GCPs on one side of the anti-meridian, has a mask that reaches over the anti-meridian, we want it to render well.
    • This is attempted now but needs some fixing as I mentioned.
  • If a map of the Bering strait has GCPs on both sides of the anti-meridian, we want it to render well.
    • As far as I know this doesn't work now and should be fixed. This is a complex task and would be ideally be included in a sponsored project. It requires, at least, that the GCPs know they are wrapped around (e.g. +175° and +195°), which is not following the GeoJSON spec, and a fix in Allmaps Editor to produce coordinates like this, plus work on the renderer.

Open questions:

  • If a map of Africa is rendered on a webmercator map and we zoom out enough or pan to the right enough to see multiple copied of Africa, should it render multiple times?
    • This is what you propose here if I understand well. I see that the user could expect this, but I can also think of situations where this is not desirable: imagine a webmercator map of the entire world, stretching wider then 360°. Should it overlap with itself on the edges, or only be rendered once? If we implement this, lets make this optional using a setting per map, similar to the wrapX setting on OpenLayer sources.

Maybe you have other cases you think of, or this behaviour is prescribed in one of the plugins. Please share!

We must also take into consideration that the plugins behave differently, with their own settings on allowing panning across the anti-meridian for 'global' projections (this makes no sence on say a 'RD new' projection), and whether features should be copied then.

And finally, we are considering to (also) build a tiled warpedmaplayer, i.e. render a webgl image per tile, and not just one for the entire viewport. This approach would make rendering multiple copies very easy, and could maybe be the preferred approach for this problem?

@ZwaarContrast
Copy link
Author

Thanks for the thorough review Manuel, really helpful context!

I want to clarify what this PR fixes, because I think it's a different problem from the Bering Strait case.

What this PR fixes: When panning past the antimeridian in OpenLayers, maps disappear. OL's viewState.center grows unbounded past ±20037508 by design, so maps at primary world coordinates end up off-screen.

What this PR does NOT fix: Maps whose GCPs span both sides of the antimeridian. As you mentioned, that would need changes at the annotation/editor level, which is a different problem from what this PR tackles.

I think the OL antimeridian issue you're planning to look at might actually be this same problem. Your +over fix in 2a21ede works for MapLibre (verified locally), but not for OL. The multi-world rendering loop in this PR follows the same pattern OL's own vector/tile layers use internally for wrapX. I couldn't find a simpler OL-side fix since the View intentionally leaves the center unbounded.

Note: the multi-world rendering currently only activates for Mercator projections. For other projections it falls back to a single render pass, so there's no risk of breaking non-Mercator use cases as far as I can see.

I agree multi-world rendering should be optional per map (like wrapX), and I'd be interested to hear more about the tiled renderer approach. Happy to rework the PR once the direction is clearer.

Also spotted a small bug while I was going through the code: getLonLatDefinition and getWebMercatorDefinition are both missing a return on the over: false branch. Currently dormant since everything defaults to over: true, but opened a fix: #400.

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.

2 participants