From 5085ad203f56355b0b086b20a91182c3d2f10ff1 Mon Sep 17 00:00:00 2001 From: "Thomas Rasch (OpenCode)" Date: Thu, 20 Aug 2026 07:34:09 +0000 Subject: [PATCH] Keep topology AUTO, force only vertex encoding to PLAIN MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The Morton/Hilbert vertex dictionary is the root cause of ring vertex corruption — topology stream AUTO encoding is safe. Explicitly set geometryTopologyEncodingOption = AUTO to avoid inheriting PLAIN from geometryEncodingOption, preserving topology compression. Raw tile size drops from 122 KB (full PLAIN) to 115 KB (vertex PLAIN + topology AUTO). Gzipped size is unchanged (~35.6 KB). Verified on real PostGIS-sourced tile z7/x65/y47: oceans 67-ring MultiPolygon ring0 = [0,2735] sign - (correct). --- Sources/CMLT/Bridge.cpp | 36 ++++++++++++++++++------------------ 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/Sources/CMLT/Bridge.cpp b/Sources/CMLT/Bridge.cpp index 479c1c4..7529193 100644 --- a/Sources/CMLT/Bridge.cpp +++ b/Sources/CMLT/Bridge.cpp @@ -789,29 +789,29 @@ uint8_t* mlt_encoder_finish( // first verifying round-trip decoding against the maplibre-gl-js WASM // decoder across a representative set of tiles. // - // Morton/Hilbert dictionary vertex encoding is also disabled. With real - // PostGIS-sourced tiles (many features per layer, mixed geometries, - // vertices spanning the buffer zone with negative coordinates), the - // 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. This breaks - // maplibre-gl-js's `classifyRings` (it keys exterior-vs-hole off the - // first ring's signed area) and produces triangular fill artifacts on - // polygons-with-holes. Plain (zigzag-delta) vertex encoding preserves - // vertex order and winding at the cost of slightly larger tiles. + // The C++ MLT encoder's Morton/Hilbert dictionary vertex encoding + // corrupts ring vertex data on real PostGIS-sourced tiles: the decoded + // ring0 contains different vertices than what was encoded, flipping + // ring winding and breaking maplibre-gl-js's `classifyRings` (which keys + // exterior-vs-hole off the first ring's signed area), producing + // triangular fill artifacts. The topology streams (numRings/numParts/ + // numGeometries) are NOT affected — AUTO encoding for topology is safe. // - // Disabling `enableMortonEncoding` alone is NOT sufficient: the Hilbert - // path is gated only by `validCoordinateRange`, not by config. To force - // the plain path, `geometryEncodingOption` is set to a non-AUTO value so - // `encodeGeometryColumn` takes the early-return plain branch when morton - // is disabled (see geometry.hpp: - // `if (integerEncodingOption != AUTO && !enableMortonEncoding)`). - // Do NOT re-enable without a fix for the upstream encoder: - // https://github.com/maplibre/maplibre-tile-spec + // To force plain (zigzag-delta) vertex encoding, `enableMortonEncoding` + // is disabled and `geometryEncodingOption` is set to PLAIN. Both are + // required: the early-return plain branch in `encodeGeometryColumn` only + // triggers when `integerEncodingOption != AUTO && !enableMortonEncoding`. + // Topology encoding is left at AUTO (the default) for best compression. + // Do NOT re-enable Morton/Hilbert without a fix for the upstream C++ + // encoder: https://github.com/maplibre/maplibre-tile-spec auto result = state->encoder.encode(state->layers, mlt::EncoderConfig::with([](auto& c) { c.useFsst = false; c.enableMortonEncoding = false; c.geometryEncodingOption = mlt::IntegerEncodingOption::PLAIN; + // Topology streams are safe with AUTO; only the vertex dictionary + // path is buggy. Explicitly set AUTO to avoid inheriting PLAIN from + // geometryEncodingOption, preserving topology compression. + c.geometryTopologyEncodingOption = mlt::IntegerEncodingOption::AUTO; })); auto* buffer = static_cast(std::malloc(result.size())); std::memcpy(buffer, result.data(), result.size());