Tighten MLT wording

This commit is contained in:
Danila Poyarkov
2026-07-04 19:56:35 +03:00
parent 0e536bcec4
commit 776a7b3b84
5 changed files with 5 additions and 21 deletions
-1
View File
@@ -43,7 +43,6 @@ tests/**/*.mbtiles
tests/**/*.check tests/**/*.check
tests/**/*.geobuf tests/**/*.geobuf
# MLT build directory
mlt-build/ mlt-build/
# Vim # Vim
-1
View File
@@ -1,4 +1,3 @@
[submodule "maplibre-tile-spec"] [submodule "maplibre-tile-spec"]
path = maplibre-tile-spec path = maplibre-tile-spec
url = https://github.com/maplibre/maplibre-tile-spec.git url = https://github.com/maplibre/maplibre-tile-spec.git
branch = main
-5
View File
@@ -606,20 +606,15 @@ layer-json-test: tippecanoe tippecanoe-decode
rm -f tests/layer-json/out.mbtiles.json.check tests/layer-json/out.mbtiles rm -f tests/layer-json/out.mbtiles.json.check tests/layer-json/out.mbtiles
mlt-test: tippecanoe 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 --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 ./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) @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)
# 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) @test "$$(sqlite3 tests/mlt/points.mbtiles "SELECT value FROM metadata WHERE name='format'")" = "mlt" || (echo "FAIL: format metadata is not 'mlt'" && exit 1)
# 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) @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)
# Directory output with .mlt extension
rm -rf tests/mlt/dir-out rm -rf tests/mlt/dir-out
./tippecanoe -q --output-format=mlt -z2 -f -e tests/mlt/dir-out tests/mlt/points.geojson ./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 '*.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) @test $$(find tests/mlt/dir-out -name '*.pbf' | wc -l) -eq 0 || (echo "FAIL: .pbf files in MLT directory output" && exit 1)
# Pretessellate flag
./tippecanoe -q --output-format=mlt --pretessellate -z5 -f -o tests/mlt/points-tess.mbtiles tests/mlt/points.geojson ./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) @test $$(sqlite3 tests/mlt/points-tess.mbtiles "SELECT COUNT(*) FROM tiles") -gt 0 || (echo "FAIL: No tiles with pretessellate" && exit 1)
rm -f tests/mlt/points.mbtiles tests/mlt/points-mvt.mbtiles tests/mlt/points-tess.mbtiles rm -f tests/mlt/points.mbtiles tests/mlt/points-mvt.mbtiles tests/mlt/points-tess.mbtiles
+3 -3
View File
@@ -553,9 +553,9 @@ the same layer, enclose them in an `all` expression so they will all be evaluate
* `-pf` or `--no-feature-limit`: Don't limit tiles to 200,000 features * `-pf` or `--no-feature-limit`: Don't limit tiles to 200,000 features
* `-pk` or `--no-tile-size-limit`: Don't limit tiles to 500K bytes * `-pk` or `--no-tile-size-limit`: Don't limit tiles to 500K bytes
* `-pC` or `--no-tile-compression`: Don't compress the vector tile data. If you are getting "Unimplemented type 3" error messages from a renderer, it is probably because it expects uncompressed tiles using this option rather than the normal gzip-compressed tiles. * `-pC` or `--no-tile-compression`: Don't compress the vector tile data. If you are getting "Unimplemented type 3" error messages from a renderer, it is probably because it expects uncompressed tiles using this option rather than the normal gzip-compressed tiles.
* `--output-format=`*format*: Set the tile encoding format. Supported values: `mvt` (default, Mapbox Vector Tiles) or `mlt` ([MapLibre Tiles](https://github.com/maplibre/maplibre-tile-spec)). MLT is a columnar format that typically produces smaller tiles than MVT. * `--output-format=`*format*: Set the tile encoding format. Supported values: `mvt` (default, Mapbox Vector Tiles) or `mlt` ([MapLibre Tiles](https://github.com/maplibre/maplibre-tile-spec)).
* `--pretessellate`: When using `--output-format=mlt`, pre-triangulate polygon geometries for faster rendering. Only applies to layers where all features are polygons. * `--pretessellate`: When using `--output-format=mlt`, pre-triangulate polygon geometries. Only applies to layers where all features are polygons.
* `--no-mlt-feature-sort`: When using `--output-format=mlt`, disable within-tile spatial sorting of features by Hilbert curve index. Sorting is on by default and improves compression. * `--no-mlt-feature-sort`: When using `--output-format=mlt`, disable within-tile spatial sorting of features by Hilbert curve index. Sorting is on by default.
* `-pg` or `--no-tile-stats`: Don't generate the `tilestats` row in the tileset metadata. Uploads without [tilestats](https://github.com/mapbox/mapbox-geostats) will take longer to process. * `-pg` or `--no-tile-stats`: Don't generate the `tilestats` row in the tileset metadata. Uploads without [tilestats](https://github.com/mapbox/mapbox-geostats) will take longer to process.
* `--tile-stats-attributes-limit=`*count*: Include `tilestats` information about at most *count* attributes instead of the default 1000. * `--tile-stats-attributes-limit=`*count*: Include `tilestats` information about at most *count* attributes instead of the default 1000.
* `--tile-stats-sample-values-limit=`*count*: Calculate `tilestats` attribute statistics based on *count* values instead of the default 1000. * `--tile-stats-sample-values-limit=`*count*: Calculate `tilestats` attribute statistics based on *count* values instead of the default 1000.
+2 -11
View File
@@ -11,8 +11,7 @@
using Vertex = mlt::Encoder::Vertex; using Vertex = mlt::Encoder::Vertex;
// Try to parse a JSON object string into MLT STRUCT (flat string children only) static bool parse_json_object_property(const std::string &s, mlt::Encoder::StructValue &out) {
static bool try_parse_json_object(const std::string &s, mlt::Encoder::StructValue &out) {
if (s.empty() || s[0] != '{') { if (s.empty() || s[0] != '{') {
return false; return false;
} }
@@ -60,7 +59,6 @@ static bool try_parse_json_object(const std::string &s, mlt::Encoder::StructValu
child_val = "null"; child_val = "null";
break; break;
default: default:
// Nested object/array - stringify back
char *nested = json_stringify(val); char *nested = json_stringify(val);
child_val = nested; child_val = nested;
free(nested); free(nested);
@@ -101,7 +99,7 @@ static mlt::Encoder::PropertyValue convert_value(const mvt_value &val) {
case mvt_string: { case mvt_string: {
std::string s = val.get_string_value(); std::string s = val.get_string_value();
mlt::Encoder::StructValue struct_val; mlt::Encoder::StructValue struct_val;
if (try_parse_json_object(s, struct_val)) { if (parse_json_object_property(s, struct_val)) {
return struct_val; return struct_val;
} }
return s; return s;
@@ -111,8 +109,6 @@ static mlt::Encoder::PropertyValue convert_value(const mvt_value &val) {
} }
} }
// Split MVT command stream into coordinate rings (sequences between moveto commands).
// Each ring is a vector of vertices. For polygons, closepath is implicit (MLT strips closing points).
static std::vector<std::vector<Vertex>> extract_rings(const mvt_feature &feature) { static std::vector<std::vector<Vertex>> extract_rings(const mvt_feature &feature) {
std::vector<std::vector<Vertex>> rings; std::vector<std::vector<Vertex>> rings;
@@ -124,7 +120,6 @@ static std::vector<std::vector<Vertex>> extract_rings(const mvt_feature &feature
} else if (g.op == mvt_lineto) { } else if (g.op == mvt_lineto) {
rings.back().push_back({static_cast<int32_t>(g.x), static_cast<int32_t>(g.y)}); rings.back().push_back({static_cast<int32_t>(g.x), static_cast<int32_t>(g.y)});
} }
// mvt_closepath: polygon ring close — MLT stores without closing point
} }
return rings; return rings;
} }
@@ -160,12 +155,9 @@ static mlt::Encoder::Geometry convert_geometry(const mvt_feature &feature) {
break; break;
case mvt_polygon: { case mvt_polygon: {
// Outer rings are clockwise (positive area), holes are counter-clockwise.
// Group into polygons: each outer ring starts a new polygon.
std::vector<std::vector<std::vector<Vertex>>> polygons; std::vector<std::vector<std::vector<Vertex>>> polygons;
for (auto &ring : rings) { for (auto &ring : rings) {
// Signed area to detect winding: positive = clockwise = outer ring (in MVT screen coords)
long long area2 = 0; long long area2 = 0;
for (size_t i = 0; i < ring.size(); i++) { for (size_t i = 0; i < ring.size(); i++) {
size_t j = (i + 1) % ring.size(); size_t j = (i + 1) % ring.size();
@@ -173,7 +165,6 @@ static mlt::Encoder::Geometry convert_geometry(const mvt_feature &feature) {
} }
if (area2 >= 0) { if (area2 >= 0) {
// Outer ring — start new polygon
polygons.emplace_back(); polygons.emplace_back();
} }
if (!polygons.empty()) { if (!polygons.empty()) {