Merge PR #393 (MLT output format) into main

This commit is contained in:
Claude
2026-08-06 16:44:49 +00:00
17 changed files with 374 additions and 18 deletions
+2
View File
@@ -11,6 +11,8 @@ jobs:
version: ['Release', 'Debug']
steps:
- uses: actions/checkout@v3
with:
submodules: recursive
- name: Install dependencies (Ubuntu)
if: runner.os == 'Linux'
run: sudo apt-get install libsqlite3-dev
+2
View File
@@ -43,6 +43,8 @@ tests/**/*.mbtiles
tests/**/*.check
tests/**/*.geobuf
mlt-build/
# Vim
*.swp
+3
View File
@@ -0,0 +1,3 @@
[submodule "maplibre-tile-spec"]
path = maplibre-tile-spec
url = https://github.com/maplibre/maplibre-tile-spec.git
+39 -3
View File
@@ -57,9 +57,27 @@ 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 mlt-build/fastpfor/libFastPFOR.a
MLT_BUILD_STAMP = mlt-build/.built
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_STAMP): maplibre-tile-spec/cpp/CMakeLists.txt
cmake -S maplibre-tile-spec/cpp -B mlt-build \
-DCMAKE_BUILD_TYPE=$(BUILDTYPE) \
-DMLT_WITH_FASTPFOR=ON \
-DMLT_WITH_FASTPFOR_SIMD=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
touch $@
$(MLT_LIBS): $(MLT_BUILD_STAMP)
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 +103,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 +118,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 flatgeobuf-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 flatgeobuf-test mlt-test
./unit
suffixes = json json.gz
@@ -590,6 +611,21 @@ 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
./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)
@test "$$(sqlite3 tests/mlt/points.mbtiles "SELECT value FROM metadata WHERE name='format'")" = "mlt" || (echo "FAIL: format metadata is not 'mlt'" && 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)
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)
./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
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
+6 -3
View File
@@ -44,7 +44,7 @@ $ brew install tippecanoe
On Ubuntu it will usually be easiest to build from the source repository:
```sh
$ git clone https://github.com/felt/tippecanoe.git
$ git clone --recurse-submodules https://github.com/felt/tippecanoe.git
$ cd tippecanoe
$ make -j
$ make install
@@ -552,7 +552,10 @@ the same layer, enclose them in an `all` expression so they will all be evaluate
* `--limit-tile-feature-count-at-maximum-zoom=`_features_: Abruptly limit each tile at the maximum zoom level to the specified number of _features_, after ordering them if specified.
* `-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 PBF 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)).
* `--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.
@@ -821,7 +824,7 @@ The options are:
### Setting or disabling tile size limits
* `-pk` or `--no-tile-size-limit`: Don't skip tiles larger than 500K.
* `-pC` or `--no-tile-compression`: Don't compress the PBF vector tile data.
* `-pC` or `--no-tile-compression`: Don't compress the vector tile data.
* `-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.
+3 -3
View File
@@ -25,7 +25,7 @@ std::string dir_read_tile(std::string base, struct zxy tile) {
return (contents.str());
}
void dir_write_tile(const char *outdir, int z, int tx, int ty, std::string const &pbf) {
void dir_write_tile(const char *outdir, int z, int tx, int ty, std::string const &pbf, const char *ext) {
// Don't check mkdir error returns, since most of these calls to
// mkdir will be creating directories that already exist.
mkdir(outdir, S_IRWXU | S_IRWXG | S_IRWXO);
@@ -39,7 +39,7 @@ void dir_write_tile(const char *outdir, int z, int tx, int ty, std::string const
newdir = newdir + "/" + std::to_string(tx);
mkdir(newdir.c_str(), S_IRWXU | S_IRWXG | S_IRWXO);
newdir = newdir + "/" + std::to_string(ty) + ".pbf";
newdir = newdir + "/" + std::to_string(ty) + ext;
struct stat st;
if (stat(newdir.c_str(), &st) == 0) {
@@ -81,7 +81,7 @@ static bool pbfname(const char *s) {
s++;
}
return strcmp(s, ".pbf") == 0 || strcmp(s, ".mvt") == 0;
return strcmp(s, ".pbf") == 0 || strcmp(s, ".mvt") == 0 || strcmp(s, ".mlt") == 0;
}
void check_dir(const char *dir, char **argv, bool force, bool forcetable) {
+1 -1
View File
@@ -6,7 +6,7 @@
#ifndef DIRTILES_HPP
#define DIRTILES_HPP
void dir_write_tile(const char *outdir, int z, int tx, int ty, std::string const &pbf);
void dir_write_tile(const char *outdir, int z, int tx, int ty, std::string const &pbf, const char *ext = ".pbf");
void dir_erase_zoom(const char *outdir, int z);
void dir_write_metadata(const char *outdir, const metadata &m);
+21 -1
View File
@@ -104,6 +104,9 @@ bool drop_by_attribute_descending = false;
std::vector<order_field> order_by;
bool order_reverse;
bool order_by_size = false;
int output_format = OUTPUT_MVT;
bool mlt_sort_features = true;
bool mlt_pretessellate = false;
int prevent[256];
int additional[256];
@@ -2824,7 +2827,8 @@ std::pair<int, metadata> read_input(std::vector<source> &sources, char *fname, i
ai->second.maxzoom = maxzoom;
}
metadata m = make_metadata(fname, minzoom, maxzoom, minlat, minlon, maxlat, maxlon, minlat2, minlon2, maxlat2, maxlon2, midlat, midlon, attribution, merged_lm, true, description, !prevent[P_TILE_STATS], attribute_descriptions, "tippecanoe", commandline, strategies, basezoom, droprate, retain_points_multiplier);
const char *tile_format = (output_format == OUTPUT_MLT) ? "mlt" : "pbf";
metadata m = make_metadata(fname, minzoom, maxzoom, minlat, minlon, maxlat, maxlon, minlat2, minlon2, maxlat2, maxlon2, midlat, midlon, attribution, merged_lm, tile_format, description, !prevent[P_TILE_STATS], attribute_descriptions, "tippecanoe", commandline, strategies, basezoom, droprate, retain_points_multiplier);
if (outdb != NULL) {
mbtiles_write_metadata(outdb, m, forcetable);
} else {
@@ -3160,6 +3164,9 @@ int main(int argc, char **argv) {
{"no-feature-limit", no_argument, &prevent[P_FEATURE_LIMIT], 1},
{"no-tile-size-limit", no_argument, &prevent[P_KILOBYTE_LIMIT], 1},
{"no-tile-compression", no_argument, &prevent[P_TILE_COMPRESSION], 1},
{"output-format", required_argument, 0, '~'},
{"pretessellate", no_argument, 0, '~'},
{"no-mlt-feature-sort", no_argument, 0, '~'},
{"no-tile-stats", no_argument, &prevent[P_TILE_STATS], 1},
{"tile-stats-attributes-limit", required_argument, 0, '~'},
{"tile-stats-sample-values-limit", required_argument, 0, '~'},
@@ -3327,6 +3334,19 @@ int main(int argc, char **argv) {
unidecode_data = read_unidecode(optarg);
} else if (strcmp(opt, "maximum-string-attribute-length") == 0) {
maximum_string_attribute_length = atoll_require(optarg, "Maximum string attribute length");
} else if (strcmp(opt, "output-format") == 0) {
if (strcmp(optarg, "mvt") == 0 || strcmp(optarg, "pbf") == 0) {
output_format = OUTPUT_MVT;
} else if (strcmp(optarg, "mlt") == 0) {
output_format = OUTPUT_MLT;
} else {
fprintf(stderr, "%s: --output-format must be 'mvt' or 'mlt'\n", argv[0]);
exit(EXIT_ARGS);
}
} else if (strcmp(opt, "pretessellate") == 0) {
mlt_pretessellate = true;
} else if (strcmp(opt, "no-mlt-feature-sort") == 0) {
mlt_sort_features = false;
} else {
fprintf(stderr, "%s: Unrecognized option --%s\n", argv[0], opt);
exit(EXIT_ARGS);
+7
View File
@@ -76,4 +76,11 @@ bool progress_time();
#define MAX_ZOOM 24
#define OUTPUT_MVT 0
#define OUTPUT_MLT 1
extern int output_format;
extern bool mlt_sort_features;
extern bool mlt_pretessellate;
#endif
Submodule maplibre-tile-spec added at ec6808dab4
+4 -3
View File
@@ -665,14 +665,14 @@ std::string version_str() {
return s;
}
metadata make_metadata(const char *fname, int minzoom, int maxzoom, double minlat, double minlon, double maxlat, double maxlon, double minlat2, double minlon2, double maxlat2, double maxlon2, double midlat, double midlon, const char *attribution, std::map<std::string, layermap_entry> const &layermap, bool vector, const char *description, bool do_tilestats, std::map<std::string, std::string> const &attribute_descriptions, std::string const &program, std::string const &commandline, std::vector<strategy> const &strategies, int basezoom, double droprate, int retain_points_multiplier) {
metadata make_metadata(const char *fname, int minzoom, int maxzoom, double minlat, double minlon, double maxlat, double maxlon, double minlat2, double minlon2, double maxlat2, double maxlon2, double midlat, double midlon, const char *attribution, std::map<std::string, layermap_entry> const &layermap, const char *tile_format, const char *description, bool do_tilestats, std::map<std::string, std::string> const &attribute_descriptions, std::string const &program, std::string const &commandline, std::vector<strategy> const &strategies, int basezoom, double droprate, int retain_points_multiplier) {
metadata m;
m.name = fname;
m.description = description != NULL ? description : fname;
m.version = 2;
m.type = "overlay";
m.format = vector ? "pbf" : "png";
m.format = tile_format;
m.minzoom = minzoom;
m.maxzoom = maxzoom;
@@ -711,7 +711,8 @@ metadata make_metadata(const char *fname, int minzoom, int maxzoom, double minla
std::string("}");
}
if (vector) {
bool is_vector_format = (strcmp(tile_format, "pbf") == 0 || strcmp(tile_format, "mlt") == 0);
if (is_vector_format) {
{
json_writer state(&m.vector_layers_json);
+1 -1
View File
@@ -68,7 +68,7 @@ sqlite3 *mbtiles_open(char *dbname, char **argv, int forcetable);
void mbtiles_write_tile(sqlite3 *outdb, int z, int tx, int ty, const char *data, int size);
void mbtiles_erase_zoom(sqlite3 *outdb, int z);
metadata make_metadata(const char *fname, int minzoom, int maxzoom, double minlat, double minlon, double maxlat, double maxlon, double minlat2, double minlon2, double maxlat2, double maxlon2, double midlat, double midlon, const char *attribution, std::map<std::string, layermap_entry> const &layermap, bool vector, const char *description, bool do_tilestats, std::map<std::string, std::string> const &attribute_descriptions, std::string const &program, std::string const &commandline, std::vector<strategy> const &strategies, int basezoom, double droprate, int retain_points_multiplier);
metadata make_metadata(const char *fname, int minzoom, int maxzoom, double minlat, double minlon, double maxlat, double maxlon, double minlat2, double minlon2, double maxlat2, double maxlon2, double midlat, double midlon, const char *attribution, std::map<std::string, layermap_entry> const &layermap, const char *tile_format, const char *description, bool do_tilestats, std::map<std::string, std::string> const &attribute_descriptions, std::string const &program, std::string const &commandline, std::vector<strategy> const &strategies, int basezoom, double droprate, int retain_points_multiplier);
void mbtiles_write_metadata(sqlite3 *db, const metadata &m, bool forcetable);
void mbtiles_close(sqlite3 *outdb, const char *pgm);
+260
View File
@@ -0,0 +1,260 @@
#include "mlt.hpp"
#include "jsonpull/jsonpull.h"
#include <mlt/encoder.hpp>
#include <cstdint>
#include <map>
#include <optional>
#include <string>
#include <vector>
using Vertex = mlt::Encoder::Vertex;
static bool parse_json_object_property(const std::string &s, mlt::Encoder::StructValue &out) {
if (s.empty() || s[0] != '{') {
return false;
}
json_pull *jp = json_begin_string(s.c_str());
json_object *obj = json_read_tree(jp);
if (obj == nullptr || obj->type != JSON_HASH) {
json_free(obj);
json_end(jp);
return false;
}
for (size_t i = 0; i < obj->value.object.length; i++) {
json_object *key = obj->value.object.keys[i];
json_object *val = obj->value.object.values[i];
if (key->type != JSON_STRING) {
continue;
}
std::string child_key = key->value.string.string;
std::string child_val;
switch (val->type) {
case JSON_STRING:
child_val = val->value.string.string;
break;
case JSON_NUMBER:
if (val->value.number.large_unsigned != 0) {
child_val = std::to_string(val->value.number.large_unsigned);
} else if (val->value.number.large_signed != 0) {
child_val = std::to_string(val->value.number.large_signed);
} else {
child_val = std::to_string(val->value.number.number);
}
break;
case JSON_TRUE:
child_val = "true";
break;
case JSON_FALSE:
child_val = "false";
break;
case JSON_NULL:
child_val = "null";
break;
default:
char *nested = json_stringify(val);
child_val = nested;
free(nested);
break;
}
out[child_key] = child_val;
}
json_free(obj);
json_end(jp);
return true;
}
static mlt::Encoder::PropertyValue convert_value(const mvt_value &val) {
switch (val.type) {
case mvt_bool:
return val.numeric_value.bool_value;
case mvt_int:
if (val.numeric_value.int_value >= INT32_MIN && val.numeric_value.int_value <= INT32_MAX) {
return static_cast<std::int32_t>(val.numeric_value.int_value);
}
return static_cast<std::int64_t>(val.numeric_value.int_value);
case mvt_uint:
if (val.numeric_value.uint_value <= UINT32_MAX) {
return static_cast<std::uint32_t>(val.numeric_value.uint_value);
}
return static_cast<std::uint64_t>(val.numeric_value.uint_value);
case mvt_sint:
if (val.numeric_value.sint_value >= INT32_MIN && val.numeric_value.sint_value <= INT32_MAX) {
return static_cast<std::int32_t>(val.numeric_value.sint_value);
}
return static_cast<std::int64_t>(val.numeric_value.sint_value);
case mvt_float:
return val.numeric_value.float_value;
case mvt_double:
return val.numeric_value.double_value;
case mvt_string: {
std::string s = val.get_string_value();
mlt::Encoder::StructValue struct_val;
if (parse_json_object_property(s, struct_val)) {
return struct_val;
}
return s;
}
default:
return std::string{};
}
}
static std::vector<std::vector<Vertex>> extract_rings(const mvt_feature &feature) {
std::vector<std::vector<Vertex>> rings;
for (size_t i = 0; i < feature.geometry.size(); i++) {
const auto &g = feature.geometry[i];
if (g.op == mvt_moveto) {
rings.emplace_back();
rings.back().push_back({static_cast<int32_t>(g.x), static_cast<int32_t>(g.y)});
} else if (g.op == mvt_lineto) {
rings.back().push_back({static_cast<int32_t>(g.x), static_cast<int32_t>(g.y)});
}
}
return rings;
}
static mlt::Encoder::Geometry convert_geometry(const mvt_feature &feature) {
mlt::Encoder::Geometry geom;
auto rings = extract_rings(feature);
switch (feature.type) {
case mvt_point:
if (rings.size() == 1 && rings[0].size() == 1) {
geom.type = mlt::Encoder::GeometryType::POINT;
geom.coordinates = std::move(rings[0]);
} else {
geom.type = mlt::Encoder::GeometryType::MULTIPOINT;
for (auto &ring : rings) {
for (auto &v : ring) {
geom.coordinates.push_back(v);
}
}
}
break;
case mvt_linestring:
if (rings.size() == 1) {
geom.type = mlt::Encoder::GeometryType::LINESTRING;
geom.coordinates = std::move(rings[0]);
} else {
geom.type = mlt::Encoder::GeometryType::MULTILINESTRING;
geom.parts = std::move(rings);
}
break;
case mvt_polygon: {
std::vector<std::vector<std::vector<Vertex>>> polygons;
for (auto &ring : rings) {
long long area2 = 0;
for (size_t i = 0; i < ring.size(); i++) {
size_t j = (i + 1) % ring.size();
area2 += (long long) ring[i].x * ring[j].y - (long long) ring[j].x * ring[i].y;
}
if (area2 >= 0) {
polygons.emplace_back();
}
if (!polygons.empty()) {
polygons.back().push_back(std::move(ring));
}
}
if (polygons.size() == 1) {
geom.type = mlt::Encoder::GeometryType::POLYGON;
for (auto &ring : polygons[0]) {
geom.ringSizes.push_back(static_cast<uint32_t>(ring.size()));
geom.coordinates.insert(geom.coordinates.end(), ring.begin(), ring.end());
}
} else {
geom.type = mlt::Encoder::GeometryType::MULTIPOLYGON;
for (auto &poly : polygons) {
std::vector<Vertex> part_verts;
std::vector<uint32_t> part_rings;
for (auto &ring : poly) {
part_rings.push_back(static_cast<uint32_t>(ring.size()));
part_verts.insert(part_verts.end(), ring.begin(), ring.end());
}
geom.parts.push_back(std::move(part_verts));
geom.partRingSizes.push_back(std::move(part_rings));
}
}
break;
}
}
return geom;
}
static mlt::Encoder::Layer convert_layer(const mvt_layer &layer) {
mlt::Encoder::Layer out;
out.name = layer.name;
out.extent = static_cast<uint32_t>(layer.extent);
for (const auto &feature : layer.features) {
mlt::Encoder::Feature f;
if (feature.has_id) {
f.id = feature.id;
} else {
f.id = std::nullopt;
}
f.geometry = convert_geometry(feature);
for (size_t t = 0; t + 1 < feature.tags.size(); t += 2) {
unsigned key_idx = feature.tags[t];
unsigned val_idx = feature.tags[t + 1];
if (key_idx < layer.keys.size() && val_idx < layer.values.size()) {
const auto &val = layer.values[val_idx];
if (val.type != mvt_null) {
f.properties[layer.keys[key_idx]] = convert_value(val);
}
}
}
out.features.push_back(std::move(f));
}
return out;
}
std::string encode_as_mlt(const mvt_tile &tile, bool sort_features, bool pretessellate) {
mlt::Encoder encoder;
mlt::EncoderConfig config;
config.sortFeatures = sort_features;
config.preTessellate = pretessellate;
bool any_has_id = false;
for (const auto &layer : tile.layers) {
for (const auto &feature : layer.features) {
if (feature.has_id) {
any_has_id = true;
break;
}
}
if (any_has_id) {
break;
}
}
config.includeIds = any_has_id;
std::vector<mlt::Encoder::Layer> layers;
layers.reserve(tile.layers.size());
for (const auto &layer : tile.layers) {
layers.push_back(convert_layer(layer));
}
auto bytes = encoder.encode(layers, config);
return std::string(reinterpret_cast<const char *>(bytes.data()), bytes.size());
}
+9
View File
@@ -0,0 +1,9 @@
#ifndef MLT_HPP
#define MLT_HPP
#include <string>
#include "mvt.hpp"
std::string encode_as_mlt(const mvt_tile &tile, bool sort_features, bool pretessellate);
#endif
+5
View File
@@ -0,0 +1,5 @@
{"type":"FeatureCollection","features":[
{"type":"Feature","id":1,"properties":{"name":"point1","count":100,"active":true},"geometry":{"type":"Point","coordinates":[-122.4,37.8]}},
{"type":"Feature","id":2,"properties":{"name":"point2","count":200,"active":false},"geometry":{"type":"Point","coordinates":[-122.5,37.9]}},
{"type":"Feature","id":3,"properties":{"name":"point3","count":300},"geometry":{"type":"Point","coordinates":[-122.6,38.0]}}
]}
+1 -1
View File
@@ -1636,7 +1636,7 @@ int main(int argc, char **argv) {
st.maxlon2 = st.maxlon;
}
metadata m = make_metadata(name.c_str(), st.minzoom, st.maxzoom, st.minlat, st.minlon, st.maxlat, st.maxlon, st.minlat2, st.minlon2, st.maxlat2, st.maxlon2, st.midlat, st.midlon, attribution.size() != 0 ? attribution.c_str() : NULL, layermap, true, description.c_str(), !pg, attribute_descriptions, "tile-join", generator_options, strategies, st.maxzoom, 2.5, 1);
metadata m = make_metadata(name.c_str(), st.minzoom, st.maxzoom, st.minlat, st.minlon, st.maxlat, st.maxlon, st.minlat2, st.minlon2, st.maxlat2, st.maxlon2, st.midlat, st.midlon, attribution.size() != 0 ? attribution.c_str() : NULL, layermap, "pbf", description.c_str(), !pg, attribute_descriptions, "tile-join", generator_options, strategies, st.maxzoom, 2.5, 1);
if (outdb != NULL) {
mbtiles_write_metadata(outdb, m, true);
+9 -2
View File
@@ -39,6 +39,7 @@
#include "serial.hpp"
#include "options.hpp"
#include "main.hpp"
#include "mlt.hpp"
#include "write_json.hpp"
#include "milo/dtoa_milo.h"
#include "evaluator.hpp"
@@ -2853,7 +2854,12 @@ long long write_tile(decompressor *geoms, std::atomic<long long> *geompos_in, ch
}
std::string compressed;
std::string pbf = tile.encode();
std::string pbf;
if (output_format == OUTPUT_MLT) {
pbf = encode_as_mlt(tile, mlt_sort_features, mlt_pretessellate);
} else {
pbf = tile.encode();
}
tile.layers.clear();
@@ -3028,7 +3034,8 @@ long long write_tile(decompressor *geoms, std::atomic<long long> *geompos_in, ch
if (outdb != NULL) {
mbtiles_write_tile(outdb, z, tx, ty, compressed.data(), compressed.size());
} else if (outdir != NULL) {
dir_write_tile(outdir, z, tx, ty, compressed);
const char *tile_ext = (output_format == OUTPUT_MLT) ? ".mlt" : ".pbf";
dir_write_tile(outdir, z, tx, ty, compressed, tile_ext);
}
if (pthread_mutex_unlock(&db_lock) != 0) {