Repository navigation
Fix MLT polygon winding artifacts in maplibre-gl-js - #54
Merged
Merged
Conversation
Two fixes for triangular fill artifacts visible in maplibre-gl-js when rendering fill polygons (e.g. oceans) from PostGIS-sourced MLT tiles: 1. Normalize polygon ring winding in tile space (MLTEncoder) maplibre-gl-js's classifyRings regroups a flat ring list into polygons by using the first ring's signed area to decide exterior-vs-hole. PostGIS ST_ClipByBox2D/ST_MakeValid can leave rings with inconsistent winding in tile space, so the first ring of a polygon-with-holes may end up with the wrong sign and be classified as a hole, splitting the polygon and producing triangular fill artifacts. normalizePolygonTilespace reverses each ring so the exterior has negative area and holes have positive area (maplibre's y-down convention), consistently across every polygon before encoding. 2. Disable Morton/Hilbert dictionary vertex encoding (Bridge.cpp) With real PostGIS-sourced tiles (many features per layer, mixed geometries, vertices spanning the buffer zone with negative coordinates), the upstream C++ MLT encoder's space-filling-curve dictionary path produces tiles whose decoded ring0 starts at a dictionary-sorted anchor rather than the encoded first vertex and whose winding is flipped, defeating the winding fix above. Disabling enableMortonEncoding alone is not sufficient (the Hilbert path is gated only by validCoordinateRange), so geometryEncodingOption is set to PLAIN to force the early-return plain zigzag-delta path in encodeGeometryColumn. Cost: slightly larger tiles. Upstream issue: https://github.com/maplibre/maplibre-tile-spec Also updates the vendored maplibre-tile-spec submodule to the latest main (87878a90) and adds a regression test (multiPolygonManyRingsWithNegativeCoordsRoundtrip).
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
maplibre-gl-js renders triangular fill artifacts on polygons-with-holes (e.g. oceans) when displaying PostGIS-sourced MLT tiles at low zooms (visible at z7).
Root cause
Two independent issues combine to produce the artifacts:
1. Inconsistent ring winding in tile space (
MLTEncoder)maplibre-gl-js's
classifyRingsregroups a flat ring list into polygons by using the first ring's signed area to decide exterior-vs-hole. PostGISST_ClipByBox2D/ST_MakeValidcan leave rings with inconsistent winding in tile space, so the first ring of a polygon-with-holes may end up with the wrong sign, gets classified as a hole, and the polygon is split — producing triangular fill artifacts.2. Morton/Hilbert dictionary vertex reordering (C++ MLT encoder)
With real PostGIS-sourced tiles (many features per layer, mixed geometries, vertices spanning the buffer zone with negative coordinates), the upstream C++ MLT encoder's space-filling-curve dictionary path produces tiles whose decoded
ring0starts at a dictionary-sorted anchor rather than the encoded first vertex, and whose winding is flipped — defeating the winding fix above.Reproduced on a real served tile (z7/x65/y47,
oceanslayer, 67-ring MultiPolygon):(0, 2735)(-405, 2730), sign+(wrong)Fix
1.
normalizePolygonTilespaceinMLTEncoder.swiftReverses each ring so the exterior has negative area and holes have positive area (maplibre's y-down convention), consistently across every polygon before encoding.
2. Disable Morton/Hilbert in
Bridge.cppForces the plain zigzag-delta vertex path that preserves vertex order and winding. Disabling
enableMortonEncodingalone is not sufficient (the Hilbert path is gated only byvalidCoordinateRange), sogeometryEncodingOptionis set toPLAINto take the early-return plain branch inencodeGeometryColumn. Cost: slightly larger tiles.Upstream issue: https://github.com/maplibre/maplibre-tile-spec
Also
maplibre-tile-specsubmodule to the latestmain(87878a90, +28 commits — none touch the encoder).multiPolygonManyRingsWithNegativeCoordsRoundtrip.Verification
oceans67-ring MultiPolygon ring0 =[0, 2735]sign-(was[-405, 2730]sign+).