diff --git a/.gitignore b/.gitignore index 8f53e676..fc77c550 100644 --- a/.gitignore +++ b/.gitignore @@ -43,7 +43,6 @@ tests/**/*.mbtiles tests/**/*.check tests/**/*.geobuf -# MLT build directory mlt-build/ # Vim diff --git a/.gitmodules b/.gitmodules index cb7e0f58..5dd2ba49 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,4 +1,3 @@ [submodule "maplibre-tile-spec"] path = maplibre-tile-spec url = https://github.com/maplibre/maplibre-tile-spec.git - branch = main diff --git a/Makefile b/Makefile index f56d6b42..df4d7d9b 100644 --- a/Makefile +++ b/Makefile @@ -606,20 +606,15 @@ layer-json-test: tippecanoe tippecanoe-decode 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) - # 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) - # 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) - # 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) - # 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) rm -f tests/mlt/points.mbtiles tests/mlt/points-mvt.mbtiles tests/mlt/points-tess.mbtiles diff --git a/README.md b/README.md index 24e229b4..a3a0e322 100644 --- a/README.md +++ b/README.md @@ -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 * `-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. - * `--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. - * `--pretessellate`: When using `--output-format=mlt`, pre-triangulate polygon geometries for faster rendering. 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. + * `--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. 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. * `-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-sample-values-limit=`*count*: Calculate `tilestats` attribute statistics based on *count* values instead of the default 1000. diff --git a/mlt.cpp b/mlt.cpp index 69bf9b91..bfae0cb4 100644 --- a/mlt.cpp +++ b/mlt.cpp @@ -11,8 +11,7 @@ using Vertex = mlt::Encoder::Vertex; -// Try to parse a JSON object string into MLT STRUCT (flat string children only) -static bool try_parse_json_object(const std::string &s, mlt::Encoder::StructValue &out) { +static bool parse_json_object_property(const std::string &s, mlt::Encoder::StructValue &out) { if (s.empty() || s[0] != '{') { return false; } @@ -60,7 +59,6 @@ static bool try_parse_json_object(const std::string &s, mlt::Encoder::StructValu child_val = "null"; break; default: - // Nested object/array - stringify back char *nested = json_stringify(val); child_val = nested; free(nested); @@ -101,7 +99,7 @@ static mlt::Encoder::PropertyValue convert_value(const mvt_value &val) { case mvt_string: { std::string s = val.get_string_value(); 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 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> extract_rings(const mvt_feature &feature) { std::vector> rings; @@ -124,7 +120,6 @@ static std::vector> extract_rings(const mvt_feature &feature } else if (g.op == mvt_lineto) { rings.back().push_back({static_cast(g.x), static_cast(g.y)}); } - // mvt_closepath: polygon ring close — MLT stores without closing point } return rings; } @@ -160,12 +155,9 @@ static mlt::Encoder::Geometry convert_geometry(const mvt_feature &feature) { break; 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>> polygons; for (auto &ring : rings) { - // Signed area to detect winding: positive = clockwise = outer ring (in MVT screen coords) long long area2 = 0; for (size_t i = 0; i < ring.size(); i++) { size_t j = (i + 1) % ring.size(); @@ -173,7 +165,6 @@ static mlt::Encoder::Geometry convert_geometry(const mvt_feature &feature) { } if (area2 >= 0) { - // Outer ring — start new polygon polygons.emplace_back(); } if (!polygons.empty()) {