Add MLT (MapLibre Tile) output format

Integrate the C++ MLT encoder from maplibre-tile-spec as a submodule.
Tiles are encoded by converting the existing mvt_tile to
mlt::Encoder::Layer and calling the encoder library directly.

New CLI flags:
  --output-format=mlt     Encode tiles as MLT instead of MVT
  --pretessellate         Pre-triangulate polygons (MLT only)
  --no-mlt-feature-sort   Disable within-tile Hilbert sorting (MLT only)

MLT tiles use .mlt extension in directory output and format=mlt in
mbtiles metadata. Compression and all existing flags work unchanged.
This commit is contained in:
Danila Poyarkov
2026-07-02 17:29:24 +03:00
parent 7fc82a1796
commit 53b98d76c4
15 changed files with 309 additions and 16 deletions
+44 -3
View File
@@ -57,9 +57,22 @@ H = $(wildcard *.h) $(wildcard *.hpp)
C = $(wildcard *.c) $(wildcard *.cpp)
INCLUDES = -I/usr/local/include -I. -Iclipper2/include
MLT_INCLUDES = -Imaplibre-tile-spec/cpp/include -isystem maplibre-tile-spec/cpp/vendor/fsst
MLT_LIBS = mlt-build/libmlt-cpp-encoder.a mlt-build/libfsst-lib.a
LIBS = -L/usr/local/lib
tippecanoe: geojson.o jsonpull/jsonpull.o tile.o pool.o mbtiles.o geometry.o projection.o memfile.o mvt.o serial.o main.o platform.o text.o dirtiles.o pmtiles_file.o plugin.o read_json.o write_json.o geobuf.o flatgeobuf.o evaluator.o geocsv.o csv.o geojson-loop.o json_logger.o visvalingam.o compression.o clip.o sort.o attribute.o thread.o shared_borders.o clipper2/src/clipper.engine.o
mlt-build/libmlt-cpp-encoder.a: maplibre-tile-spec/cpp/CMakeLists.txt
cmake -S maplibre-tile-spec/cpp -B mlt-build \
-DCMAKE_BUILD_TYPE=Release \
-DMLT_WITH_FASTPFOR=OFF \
-DMLT_WITH_JSON=OFF \
-DMLT_WITH_TESTS=OFF \
-DMLT_WITH_TOOLS=OFF \
-DCMAKE_CXX_STANDARD=20 \
$(if $(VERBOSE),,--log-level=WARNING) > /dev/null
cmake --build mlt-build --target mlt-cpp-encoder $(if $(VERBOSE),,-- -s) > /dev/null
tippecanoe: geojson.o jsonpull/jsonpull.o tile.o pool.o mbtiles.o geometry.o projection.o memfile.o mvt.o mlt.o serial.o main.o platform.o text.o dirtiles.o pmtiles_file.o plugin.o read_json.o write_json.o geobuf.o flatgeobuf.o evaluator.o geocsv.o csv.o geojson-loop.o json_logger.o visvalingam.o compression.o clip.o sort.o attribute.o thread.o shared_borders.o clipper2/src/clipper.engine.o $(MLT_LIBS)
$(CXX) $(PG) $(LIBS) $(FINAL_FLAGS) $(CXXFLAGS) -o $@ $^ $(LDFLAGS) -lm -lz -lsqlite3 -lpthread
tippecanoe-enumerate: enumerate.o
@@ -85,11 +98,14 @@ tippecanoe-overzoom: overzoom.o mvt.o clip.o evaluator.o jsonpull/jsonpull.o tex
%.o: %.c
$(CC) -MMD $(PG) $(INCLUDES) $(FINAL_FLAGS) $(CFLAGS) -c -o $@ $<
mlt.o: mlt.cpp mlt-build/libmlt-cpp-encoder.a
$(CXX) -MMD $(PG) $(INCLUDES) $(MLT_INCLUDES) $(FINAL_FLAGS) $(CXXFLAGS) -std=c++20 -c -o $@ $<
%.o: %.cpp
$(CXX) -MMD $(PG) $(INCLUDES) $(FINAL_FLAGS) $(CXXFLAGS) -c -o $@ $<
clean:
rm -f ./tippecanoe ./tippecanoe-* ./tile-join ./unit *.o *.d */*.o */*.d tests/**/*.mbtiles tests/**/*.check
rm -rf ./tippecanoe ./tippecanoe-* ./tile-join ./unit *.o *.d */*.o */*.d tests/**/*.mbtiles tests/**/*.check mlt-build
indent:
clang-format -i -style="{BasedOnStyle: Google, IndentWidth: 8, UseTab: Always, AllowShortIfStatementsOnASingleLine: false, ColumnLimit: 0, ContinuationIndentWidth: 8, SpaceAfterCStyleCast: true, IndentCaseLabels: false, AllowShortBlocksOnASingleLine: false, AllowShortFunctionsOnASingleLine: false, SortIncludes: false}" $(filter-out flatgeobuf.cpp,$(C)) $(H) jsonpull/*.[ch]
@@ -97,7 +113,7 @@ indent:
TESTS = $(wildcard tests/*/out/*.json)
SPACE = $(NULL) $(NULL)
test: tippecanoe tippecanoe-decode $(addsuffix .check,$(TESTS)) raw-tiles-test parallel-test pbf-test join-test enumerate-test decode-test join-filter-test unit json-tool-test allow-existing-test csv-test layer-json-test pmtiles-test decode-pmtiles-test overzoom-test
test: tippecanoe tippecanoe-decode $(addsuffix .check,$(TESTS)) raw-tiles-test parallel-test pbf-test join-test enumerate-test decode-test join-filter-test unit json-tool-test allow-existing-test csv-test layer-json-test pmtiles-test decode-pmtiles-test overzoom-test mlt-test
./unit
suffixes = json json.gz
@@ -584,6 +600,31 @@ layer-json-test: tippecanoe tippecanoe-decode
cmp tests/layer-json/out.mbtiles.json.check tests/layer-json/out.mbtiles.json
rm -f tests/layer-json/out.mbtiles.json.check tests/layer-json/out.mbtiles
mlt-test: tippecanoe
# Points: MLT output, verify tile count matches MVT
./tippecanoe -q --output-format=mlt -z5 -f -o tests/mlt/points.mbtiles tests/mlt/points.geojson
./tippecanoe -q -z5 -f -o tests/mlt/points-mvt.mbtiles tests/mlt/points.geojson
@test $$(sqlite3 tests/mlt/points.mbtiles "SELECT COUNT(*) FROM tiles") -eq $$(sqlite3 tests/mlt/points-mvt.mbtiles "SELECT COUNT(*) FROM tiles") || (echo "FAIL: MLT and MVT tile counts differ" && exit 1)
@echo "PASS: MLT tile count matches MVT"
# Verify format metadata
@test "$$(sqlite3 tests/mlt/points.mbtiles "SELECT value FROM metadata WHERE name='format'")" = "mlt" || (echo "FAIL: format metadata is not 'mlt'" && exit 1)
@echo "PASS: MLT format metadata correct"
# Verify tiles are gzip compressed
@sqlite3 tests/mlt/points.mbtiles "SELECT hex(substr(tile_data, 1, 2)) FROM tiles LIMIT 1" | grep -q "1F8B" || (echo "FAIL: MLT tiles not gzip compressed" && exit 1)
@echo "PASS: MLT tiles are gzip compressed"
# Directory output with .mlt extension
rm -rf tests/mlt/dir-out
./tippecanoe -q --output-format=mlt -z2 -f -e tests/mlt/dir-out tests/mlt/points.geojson
@test $$(find tests/mlt/dir-out -name '*.mlt' | wc -l) -gt 0 || (echo "FAIL: No .mlt files in directory output" && exit 1)
@test $$(find tests/mlt/dir-out -name '*.pbf' | wc -l) -eq 0 || (echo "FAIL: .pbf files in MLT directory output" && exit 1)
@echo "PASS: Directory output uses .mlt extension"
# Pretessellate flag
./tippecanoe -q --output-format=mlt --pretessellate -z5 -f -o tests/mlt/points-tess.mbtiles tests/mlt/points.geojson
@test $$(sqlite3 tests/mlt/points-tess.mbtiles "SELECT COUNT(*) FROM tiles") -gt 0 || (echo "FAIL: No tiles with pretessellate" && exit 1)
@echo "PASS: --pretessellate produces tiles"
rm -f tests/mlt/points.mbtiles tests/mlt/points-mvt.mbtiles tests/mlt/points-tess.mbtiles
rm -rf tests/mlt/dir-out
# Use this target to regenerate the standards that the tests are compared against
# after making a change that legitimately changes their output