Files
tippecanoe/tile-join.cpp
T
Claude 13f1c3b6e4 Let tile-join and tippecanoe-overzoom write MLT tiles too
Both tools could read MapLibre Tiles but only ever wrote Mapbox Vector
Tiles, so there was no way to convert a tileset into MLT, or to keep a
tileset in MLT once it had been through either of them.

Give them the same --output-format, --pretessellate, and
--no-mlt-feature-sort options that tippecanoe has. tile-join writes the
chosen format to mbtiles files, PMTiles archives, and tile directories,
naming directory tiles and the tileset metadata format accordingly, and
tippecanoe-overzoom writes it to its output tile. Either tool will read
whichever format its sources are in regardless of what it is writing.

The output format selection and the MLT encoder options now live in
mlt.cpp, shared by all three tools rather than defined in main.cpp for
tippecanoe alone, along with encode_tile() for encoding a tile in the
selected format. overzoom() takes the format as a parameter, since
tile-join uses it internally to rescale tiles that will be re-encoded
afterward, and those intermediate tiles should stay MVT.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LxhELiLtpUFwrPSWwTYdHG
2026-08-06 17:34:02 +00:00

1671 lines
48 KiB
C++

// for vasprintf() on Linux
#ifndef _GNU_SOURCE
#define _GNU_SOURCE
#endif
#define _DEFAULT_SOURCE
#include <dirent.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sqlite3.h>
#include <limits.h>
#include <getopt.h>
#include <vector>
#include <string>
#include <map>
#include <set>
#include <zlib.h>
#include <math.h>
#include <pthread.h>
#include "mvt.hpp"
#include "mlt.hpp"
#include "projection.hpp"
#include "mbtiles.hpp"
#include "geometry.hpp"
#include "dirtiles.hpp"
#include "pmtiles_file.hpp"
#include "evaluator.hpp"
#include "csv.hpp"
#include "text.hpp"
#include "tile.hpp"
#include "tile-cache.hpp"
#include <fstream>
#include <sstream>
#include <algorithm>
#include <functional>
#include "jsonpull/jsonpull.h"
#include "milo/dtoa_milo.h"
#include "errors.hpp"
#include "geometry.hpp"
#include "thread.hpp"
#include "platform.hpp"
int pk = false;
int pC = false;
int pg = false;
int pe = false;
size_t CPUS;
int quiet = false;
int maxzoom = 32;
int minzoom = 0;
std::map<std::string, std::string> renames;
bool exclude_all = false;
bool exclude_all_tile_attributes = false;
bool exclude_all_tile_geometries = false;
std::vector<std::string> unidecode_data;
bool want_overzoom = false;
int buffer = 5;
bool progress_time() {
return false;
}
struct stats {
int minzoom = 0;
int maxzoom = 0;
double midlat = 0, midlon = 0;
double minlat = 0, minlon = 0, maxlat = 0, maxlon = 0;
double minlat2 = 0, minlon2 = 0, maxlat2 = 0, maxlon2 = 0;
std::vector<struct strategy> strategies{};
};
struct arg {
std::map<zxy, std::vector<std::string>> inputs{};
std::map<zxy, std::string> outputs{};
std::map<std::string, layermap_entry> *layermap = NULL;
std::vector<std::string> *header = NULL;
std::map<std::string, std::vector<std::string>> *mapping = NULL;
sqlite3 *db = NULL;
std::set<std::string> *exclude = NULL;
std::set<std::string> *include = NULL;
std::set<std::string> *keep_layers = NULL;
std::set<std::string> *remove_layers = NULL;
int ifmatched = 0;
json_object *filter = NULL;
struct tileset_reader *readers = NULL;
double minlat, minlon;
double maxlat, maxlon;
double minlon2, maxlon2;
};
void append_tile(std::string message, int z, unsigned x, unsigned y, std::map<std::string, layermap_entry> &layermap, std::vector<std::string> &header, std::map<std::string, std::vector<std::string>> &mapping, sqlite3 * /* db */, std::set<std::string> &exclude, std::set<std::string> &include, std::set<std::string> &keep_layers, std::set<std::string> &remove_layers, int ifmatched, mvt_tile &outtile, json_object *filter, struct arg *a) {
mvt_tile tile;
int features_added = 0;
bool was_compressed;
try {
if (!tile.decode(message, was_compressed)) {
fprintf(stderr, "Couldn't decompress tile %d/%u/%u\n", z, x, y);
exit(EXIT_MVT);
}
} catch (std::exception const &e) {
fprintf(stderr, "PBF decoding error in tile %d/%u/%u\n", z, x, y);
exit(EXIT_MVT);
}
std::shared_ptr<std::string> tile_stringpool = std::make_shared<std::string>();
for (size_t l = 0; l < tile.layers.size(); l++) {
mvt_layer &layer = tile.layers[l];
auto found = renames.find(layer.name);
if (found != renames.end()) {
layer.name = found->second;
}
if (keep_layers.size() > 0 && keep_layers.count(layer.name) == 0) {
continue;
}
if (remove_layers.count(layer.name) != 0) {
continue;
}
size_t ol;
for (ol = 0; ol < outtile.layers.size(); ol++) {
if (tile.layers[l].name == outtile.layers[ol].name) {
break;
}
}
if (ol == outtile.layers.size()) {
outtile.layers.push_back(mvt_layer());
outtile.layers[ol].name = layer.name;
outtile.layers[ol].version = layer.version;
outtile.layers[ol].extent = layer.extent;
}
mvt_layer &outlayer = outtile.layers[ol];
if (layer.extent != outlayer.extent) {
if (layer.extent > outlayer.extent) {
// this always scales up the existing layer instead of scaling down
// the layer that is being added, because the assumption is that
// scaling up is safe while scaling down requires geometry cleaning.
for (size_t i = 0; i < outlayer.features.size(); i++) {
for (size_t j = 0; j < outlayer.features[i].geometry.size(); j++) {
outlayer.features[i].geometry[j].x = outlayer.features[i].geometry[j].x * layer.extent / outlayer.extent;
outlayer.features[i].geometry[j].y = outlayer.features[i].geometry[j].y * layer.extent / outlayer.extent;
}
}
outlayer.extent = layer.extent;
}
}
auto tilestats = layermap.find(layer.name);
long long minx = LLONG_MAX;
long long miny = LLONG_MAX;
long long maxx = LLONG_MIN;
long long maxy = LLONG_MIN;
long long minx2 = LLONG_MAX;
long long maxx2 = LLONG_MIN;
bool features_added_to_layer = false;
for (size_t f = 0; f < layer.features.size(); f++) {
mvt_feature &feat = layer.features[f];
std::set<std::string> exclude_attributes;
if (filter != NULL && !evaluate(feat, layer, filter, exclude_attributes, z, unidecode_data)) {
continue;
}
struct match {
bool has_id = false;
unsigned long long id;
std::map<std::string, std::pair<mvt_value, serial_val>> attributes;
std::vector<std::string> key_order;
};
std::vector<match> matches;
bool matched = false;
// look for csv matches and start filling them out
if (!matched) {
match m;
m.id = feat.id;
m.has_id = feat.has_id;
// populate attributes and key_order as we look for matches,
// because apparently at some point i thought it was important
// to insert the joined attributes at the point in the sequence
// where the join key had been
for (size_t t = 0; t + 1 < feat.tags.size(); t += 2) {
const std::string &key = layer.keys[feat.tags[t]];
mvt_value &val = layer.values[feat.tags[t + 1]];
serial_val sv = mvt_value_to_serial_val(val);
if (val.type == mvt_null) {
continue;
}
if (!exclude_all_tile_attributes) {
if (include.count(std::string(key)) || (!exclude_all && exclude.count(std::string(key)) == 0 && exclude_attributes.count(std::string(key)) == 0)) {
m.attributes.insert(std::pair<std::string, std::pair<mvt_value, serial_val>>(key, std::pair<mvt_value, serial_val>(val, sv)));
m.key_order.push_back(key);
}
}
if (header.size() > 0 && key == header[0]) {
std::map<std::string, std::vector<std::string>>::iterator ii = mapping.find(sv.s);
if (ii != mapping.end()) {
std::vector<std::string> fields = ii->second;
matched = true;
for (size_t i = 1; i < fields.size(); i++) {
std::string joinkey = header[i];
std::string joinval = fields[i];
int attr_type = mvt_string;
if (joinval.size() > 0) {
if (joinval[0] == '"') {
joinval = csv_dequote(joinval);
} else if (is_number(joinval)) {
attr_type = mvt_double;
}
} else if (pe) {
attr_type = mvt_null;
}
const char *sjoinkey = joinkey.c_str();
if (include.count(joinkey) || (!exclude_all && exclude.count(joinkey) == 0 && exclude_attributes.count(joinkey) == 0 && attr_type != mvt_null)) {
mvt_value outval;
if (attr_type == mvt_string) {
outval.type = mvt_string;
outval.set_string_value(joinval);
} else {
outval.type = mvt_double;
outval.numeric_value.double_value = atof(joinval.c_str());
}
auto fa = m.attributes.find(sjoinkey);
if (fa != m.attributes.end()) {
m.attributes.erase(fa);
}
serial_val outsv;
outsv.type = outval.type;
outsv.s = joinval;
outval = stringified_to_mvt_value(outval.type, joinval.c_str(), tile_stringpool);
m.attributes.insert(std::pair<std::string, std::pair<mvt_value, serial_val>>(joinkey, std::pair<mvt_value, serial_val>(outval, outsv)));
m.key_order.push_back(joinkey);
}
}
}
}
}
if (matched) {
matches.push_back(m);
}
}
if (!matched && !ifmatched) {
// no matches, but they said to keep even unmatched tile features,
// so make one that is just the original feature
match m;
m.id = feat.id;
m.has_id = feat.has_id;
if (!exclude_all_tile_attributes) {
for (size_t t = 0; t + 1 < feat.tags.size(); t += 2) {
const std::string &key = layer.keys[feat.tags[t]];
mvt_value &val = layer.values[feat.tags[t + 1]];
serial_val sv = mvt_value_to_serial_val(val);
if (include.count(key) || (!exclude_all && exclude.count(key) == 0 && exclude_attributes.count(key) == 0)) {
m.attributes.insert(std::pair<std::string, std::pair<mvt_value, serial_val>>(key, std::pair<mvt_value, serial_val>(val, sv)));
m.key_order.push_back(key);
}
}
}
matches.push_back(m);
}
for (auto &m : matches) {
if (tilestats == layermap.end()) {
layermap.insert(std::pair<std::string, layermap_entry>(layer.name, layermap_entry(layermap.size())));
tilestats = layermap.find(layer.name);
tilestats->second.minzoom = z;
tilestats->second.maxzoom = z;
}
mvt_feature outfeature;
outfeature.id = m.id;
outfeature.has_id = m.has_id;
// To keep attributes in their original order instead of alphabetical
for (auto k : m.key_order) {
auto fa = m.attributes.find(k);
if (fa != m.attributes.end()) {
outlayer.tag(outfeature, k, fa->second.first);
add_to_tilestats(tilestats->second.tilestats, k, fa->second.second);
m.attributes.erase(fa);
}
}
if (exclude_all_tile_geometries) {
outfeature.type = -1;
} else {
outfeature.type = feat.type;
outfeature.geometry = feat.geometry;
if (layer.extent != outlayer.extent) {
for (size_t i = 0; i < outfeature.geometry.size(); i++) {
outfeature.geometry[i].x = outfeature.geometry[i].x * outlayer.extent / layer.extent;
outfeature.geometry[i].y = outfeature.geometry[i].y * outlayer.extent / layer.extent;
}
}
for (auto const &g : outfeature.geometry) {
if (g.op == mvt_moveto || g.op == mvt_lineto) {
// pin to the tile extent, since we don't want bounds bigger than the earth
long long gx = std::min((long long) outlayer.extent, std::max(0LL, g.x));
long long gy = std::min((long long) outlayer.extent, std::max(0LL, g.y));
// to world scale
gx = gx * (1LL << (32 - z)) / outlayer.extent;
gy = gy * (1LL << (32 - z)) / outlayer.extent;
// to world offset
gx += (1LL << (32 - z)) * x;
gy += (1LL << (32 - z)) * y;
minx = std::min(minx, gx);
miny = std::min(miny, gy);
maxx = std::max(maxx, gx);
maxy = std::max(maxy, gy);
// if in the western hemisphere, try shifting to east
if (gx < (1LL << 31)) {
gx += 1LL << 32;
}
minx2 = std::min(minx2, gx);
maxx2 = std::max(maxx2, gx);
}
}
}
features_added++;
features_added_to_layer = true;
outlayer.features.push_back(outfeature);
if (z < tilestats->second.minzoom) {
tilestats->second.minzoom = z;
}
if (z > tilestats->second.maxzoom) {
tilestats->second.maxzoom = z;
}
if (feat.type == mvt_point) {
tilestats->second.points++;
} else if (feat.type == mvt_linestring) {
tilestats->second.lines++;
} else if (feat.type == mvt_polygon) {
tilestats->second.polygons++;
}
}
}
if (features_added_to_layer) {
double lat1, lon1;
double lat2, lon2;
tile2lonlat(minx, maxy, 32, &lon1, &lat1);
tile2lonlat(maxx, miny, 32, &lon2, &lat2);
a->minlat = std::min(a->minlat, std::min(lat1, lat2));
a->minlon = std::min(a->minlon, std::min(lon1, lon2));
a->maxlat = std::max(a->maxlat, std::max(lat1, lat2));
a->maxlon = std::max(a->maxlon, std::max(lon1, lon2));
tile2lonlat(minx2, maxy, 32, &lon1, &lat1);
tile2lonlat(maxx2, miny, 32, &lon2, &lat2);
a->minlon2 = std::min(a->minlon2, std::min(lon1, lon2));
a->maxlon2 = std::max(a->maxlon2, std::max(lon1, lon2));
}
}
if (features_added == 0) {
return;
}
}
struct tilecmp {
bool operator()(std::pair<unsigned, unsigned> const &a, std::pair<unsigned, unsigned> const &b) {
// must match behavior of tileset_reader::operator<()
// except backwards, since we are pulling from the end of the list
if (b.first < a.first) {
return true;
}
if (b.first == a.first) {
// Y sorts backwards, in TMS order
if (b.second > a.second) {
return true;
}
}
return false;
}
} tilecmp;
// The `tileset_reader` is an iterator through the tiles of a tileset,
// in z/x/tms_y order.
//
// The basic idea is that it is used like this:
//
// void blah(const char *fname) {
// tileset_reader r(fname);
//
// for (; !r.all_done(); r.advance()) {
// std::pair<zxy, std::string> tile = r.current();
// whatever(tile);
// }
//
// r.close();
// }
//
// The complication is that you can actually keep calling current()
// and advance() after the tileset_reader claims to be done, in which case
// it will produce overzoomed tiles generated from the tiles in
// the maxzoom tileset. The parent tiles for those overzoomed tiles
// are retrieved internally using get_tile() rather than through the
// main iteration query.
struct tileset_reader {
// z/x/y and data of the current tile
long long zoom = 0;
long long x = 0;
long long y = 0;
std::string data = "";
bool current_tile_is_overzoomed = false;
// "done" means we have read all of the real tiles from the source.
// The iterator will continue to produce overzoomed tiles after it is "done."
bool done = false;
// for overzooming
int maxzoom_so_far = -1;
std::vector<std::pair<unsigned, unsigned>> tiles_at_maxzoom_so_far;
std::vector<std::pair<unsigned, unsigned>> overzoomed_tiles; // tiles at `zoom`
std::vector<std::pair<unsigned, unsigned>> next_overzoomed_tiles; // tiles at `zoom + 1`
bool overzoom_consumed_at_this_zoom = false;
// parent tile cache
tile_cache cache;
// for iterating mbtiles
sqlite3 *db = NULL;
sqlite3_stmt *stmt = NULL;
struct tileset_reader *next = NULL;
// for iterating dirtiles
std::vector<zxy> dirtiles;
std::string dirbase;
std::string name;
// for iterating pmtiles
char *pmtiles_map = NULL;
std::vector<pmtiles::entry_zxy> pmtiles_entries;
tileset_reader(const char *fname) {
name = fname;
struct stat st;
if (stat(fname, &st) == 0 && (st.st_mode & S_IFDIR) != 0) {
db = NULL;
stmt = NULL;
next = NULL;
dirtiles = enumerate_dirtiles(fname, minzoom, maxzoom);
dirbase = fname;
} else if (pmtiles_has_suffix(fname)) {
int pmtiles_fd = open(fname, O_RDONLY | O_CLOEXEC);
pmtiles_map = (char *) mmap(NULL, st.st_size, PROT_READ, MAP_PRIVATE, pmtiles_fd, 0);
if (pmtiles_map == MAP_FAILED) {
perror("mmap in decode");
exit(EXIT_MEMORY);
}
if (::close(pmtiles_fd) != 0) {
perror("close");
exit(EXIT_CLOSE);
}
pmtiles_entries = pmtiles_entries_tms(pmtiles_map, minzoom, maxzoom);
std::reverse(pmtiles_entries.begin(), pmtiles_entries.end());
} else {
if (sqlite3_open(fname, &db) != SQLITE_OK) {
fprintf(stderr, "%s: %s\n", fname, sqlite3_errmsg(db));
exit(EXIT_SQLITE);
}
char *err = NULL;
if (sqlite3_exec(db, "PRAGMA integrity_check;", NULL, NULL, &err) != SQLITE_OK) {
fprintf(stderr, "%s: integrity_check: %s\n", fname, err);
exit(EXIT_SQLITE);
}
const char *sql = "SELECT zoom_level, tile_column, tile_row, tile_data from tiles order by zoom_level, tile_column, tile_row;";
sqlite3_stmt *query;
if (sqlite3_prepare_v2(db, sql, -1, &query, NULL) != SQLITE_OK) {
fprintf(stderr, "%s: select failed: %s\n", fname, sqlite3_errmsg(db));
exit(EXIT_SQLITE);
}
stmt = query;
next = NULL;
}
}
// Checks the done status not only of this tileset_reader but also
// the others chained to it in the queue.
//
// Also claims not to be done if at least one overzoomed tile
// has been consumed at this zoom level, in which case they should
// all allowed to be consumed before stopping.
bool all_done() {
if (!done) {
return false;
}
if (overzoom_consumed_at_this_zoom) {
return false;
}
for (struct tileset_reader *r = next; r != NULL; r = r->next) {
if (!r->done) {
return false;
}
if (r->overzoom_consumed_at_this_zoom) {
return false;
}
}
return true;
}
std::pair<zxy, std::string> current() {
if (current_tile_is_overzoomed) {
overzoom_consumed_at_this_zoom = true;
}
return std::pair<zxy, std::string>(zxy(zoom, x, y), data);
}
void advance() {
if (done) {
if (!want_overzoom) {
fprintf(stderr, "overzoom advance called without -O\n");
exit(EXIT_IMPOSSIBLE);
}
if (overzoomed_tiles.size() == 0) {
next_overzoom();
overzoom_consumed_at_this_zoom = false;
}
if (overzoomed_tiles.size() == 0) {
// we have nothing to overzoom; give up
current_tile_is_overzoomed = false;
zoom = 32;
return;
}
auto xy = overzoomed_tiles.back();
overzoomed_tiles.erase(overzoomed_tiles.begin() + overzoomed_tiles.size() - 1);
x = xy.first;
y = xy.second;
data = retrieve_overzoom(zxy(zoom, x, y));
current_tile_is_overzoomed = true;
return;
}
current_tile_is_overzoomed = false;
if (db != NULL) {
if (sqlite3_step(stmt) == SQLITE_ROW) {
zoom = sqlite3_column_int(stmt, 0);
x = sqlite3_column_int(stmt, 1);
int tms_y = sqlite3_column_int(stmt, 2);
y = (1LL << zoom) - 1 - tms_y;
const char *s = (const char *) sqlite3_column_blob(stmt, 3);
size_t len = sqlite3_column_bytes(stmt, 3);
data = std::string(s, len);
} else {
done = true;
}
} else if (pmtiles_map != NULL) {
if (pmtiles_entries.size() == 0) {
done = true;
} else {
zoom = pmtiles_entries.back().z;
x = pmtiles_entries.back().x;
y = pmtiles_entries.back().y;
data = std::string(pmtiles_map + pmtiles_entries.back().offset, pmtiles_entries.back().length);
pmtiles_entries.pop_back();
}
} else {
if (dirtiles.size() == 0) {
done = true;
} else {
zoom = dirtiles[0].z;
x = dirtiles[0].x;
y = dirtiles[0].y;
data = dir_read_tile(dirbase, dirtiles[0]);
dirtiles.erase(dirtiles.begin());
}
}
if (done) {
if (want_overzoom) {
next_overzoom();
advance();
} else {
zoom = 32;
}
} else {
if (zoom > maxzoom_so_far) {
maxzoom_so_far = zoom;
tiles_at_maxzoom_so_far.clear();
}
if (want_overzoom) {
tiles_at_maxzoom_so_far.push_back(std::pair<unsigned, unsigned>(x, y));
}
}
}
void close() {
if (pmtiles_map) {
db = pmtilesmeta2tmp(name.c_str(), pmtiles_map);
// json, strategies
} else if (db == NULL) {
db = dirmeta2tmp(dirbase.c_str());
} else {
sqlite3_finalize(stmt);
}
}
void next_overzoom() {
zoom++;
overzoomed_tiles.clear();
// +1 because maxzoom_so_far is initially -1, an invalid shift
long long scale = (1LL << (zoom + 1)) / (1LL << (maxzoom_so_far + 1));
// If this is the first overzoomed level, we don't know yet
// which tiles will be useful, so spell out all 4 child tiles
// from each parent tile.
//
// If it is further overzoomed than that, we have a list of
// which child tiles will have features in them, so use that.
if (zoom == maxzoom_so_far + 1) {
for (auto const &xy : tiles_at_maxzoom_so_far) {
for (long long xx = 0; xx < scale; xx++) {
for (long long yy = 0; yy < scale; yy++) {
overzoomed_tiles.push_back(std::pair<unsigned, unsigned>(xy.first * scale + xx, xy.second * scale + yy));
}
}
}
} else {
overzoomed_tiles = std::move(next_overzoomed_tiles);
next_overzoomed_tiles.clear();
}
std::stable_sort(overzoomed_tiles.begin(), overzoomed_tiles.end(), tilecmp);
overzoom_consumed_at_this_zoom = false;
}
mvt_tile get_tile(zxy tile) {
std::string source;
if (db != NULL) {
const char *sql = "SELECT tile_data from tiles where zoom_level = ? and tile_column = ? and tile_row = ?;";
sqlite3_stmt *query;
if (sqlite3_prepare_v2(db, sql, -1, &query, NULL) != SQLITE_OK) {
fprintf(stderr, "%s: select failed: %s\n", name.c_str(), sqlite3_errmsg(db));
exit(EXIT_SQLITE);
}
sqlite3_bind_int(query, 1, tile.z);
sqlite3_bind_int(query, 2, tile.x);
sqlite3_bind_int(query, 3, (1LL << tile.z) - 1 - tile.y);
if (sqlite3_step(query) == SQLITE_ROW) {
const char *s = (const char *) sqlite3_column_blob(query, 0);
size_t len = sqlite3_column_bytes(query, 0);
source = std::string(s, len);
}
sqlite3_finalize(query);
} else if (pmtiles_map != NULL) {
uint64_t tile_offset;
uint32_t tile_length;
std::tie(tile_offset, tile_length) = pmtiles_get_tile(pmtiles_map, tile.z, tile.x, tile.y);
if (tile_length > 0) {
source = std::string(pmtiles_map + tile_offset, tile_length);
}
} else {
source = dir_read_tile(dirbase, tile);
}
mvt_tile content;
if (source.size() == 0) {
return content;
}
try {
bool was_compressed;
if (!content.decode(source, was_compressed)) {
fprintf(stderr, "Couldn't parse tile %lld/%lld/%lld\n", tile.z, tile.x, tile.y);
exit(EXIT_MVT);
}
} catch (std::exception const &e) {
fprintf(stderr, "PBF decoding error in tile %lld/%lld/%lld\n", tile.z, tile.x, tile.y);
exit(EXIT_PROTOBUF);
}
return content;
}
// Sort in z/x/tms_y order, because that is the order of the
// straightforward query of the mbtiles tiles table.
bool operator<(const struct tileset_reader &r) const {
// must match behavior of tilecmp
if (zoom < r.zoom) {
return true;
}
if (zoom > r.zoom) {
return false;
}
if (x < r.x) {
return true;
}
if (x > r.x) {
return false;
}
int sorty = (1LL << zoom) - 1 - y;
int r_sorty = (1LL << r.zoom) - 1 - r.y;
if (sorty < r_sorty) {
return true;
}
if (sorty > r_sorty) {
return false;
}
if (data < r.data) {
return true;
}
return false;
}
std::string retrieve_overzoom(zxy tile) {
// lock around sqlite3 access
static pthread_mutex_t retrieve_lock = PTHREAD_MUTEX_INITIALIZER;
zxy parent_tile = tile;
while (parent_tile.z > maxzoom_so_far) {
parent_tile.z--;
parent_tile.x /= 2;
parent_tile.y /= 2;
}
if (pthread_mutex_lock(&retrieve_lock) != 0) {
perror("pthread_mutex_lock");
}
std::function<mvt_tile(zxy)> getter = [&](zxy tileno) {
return get_tile(tileno);
};
mvt_tile source = cache.get(parent_tile, getter);
if (pthread_mutex_unlock(&retrieve_lock) != 0) {
perror("pthread_mutex_unlock");
}
if (source.layers.size() != 0) {
std::vector<source_tile> tv;
source_tile t;
t.tile = std::move(source);
t.z = parent_tile.z;
t.x = parent_tile.x;
t.y = parent_tile.y;
tv.push_back(std::move(t));
std::string ret = overzoom(tv, tile.z, tile.x, tile.y, -1, buffer,
std::set<std::string>(), std::set<std::string>(), std::vector<std::string>(),
false, &next_overzoomed_tiles, false, NULL, false,
std::unordered_map<std::string, attribute_op>(), unidecode_data, 0, 0,
std::vector<mvt_layer>(), "", "", SIZE_MAX,
std::vector<clipbbox>(), false);
return ret;
}
return "";
}
};
struct tileset_reader *begin_reading(char *fname) {
struct tileset_reader *r = new tileset_reader(fname);
// The reason this prefetches is so the tileset_reader queue can be
// priority-ordered, so the one with the next relevant tile
// is first in line.
r->advance();
return r;
}
void *join_worker(void *v) {
arg *a = (arg *) v;
for (auto ai = a->inputs.begin(); ai != a->inputs.end(); ++ai) {
mvt_tile tile;
for (size_t i = 0; i < ai->second.size(); i++) {
append_tile(ai->second[i], ai->first.z, ai->first.x, ai->first.y, *(a->layermap), *(a->header), *(a->mapping), a->db, *(a->exclude), *(a->include), *(a->keep_layers), *(a->remove_layers), a->ifmatched, tile, a->filter, a);
}
ai->second.clear();
bool anything = false;
mvt_tile outtile;
for (size_t i = 0; i < tile.layers.size(); i++) {
if (tile.layers[i].features.size() > 0) {
outtile.layers.push_back(tile.layers[i]);
anything = true;
}
}
if (anything) {
std::string pbf = encode_tile(outtile, output_format);
std::string compressed;
if (!pC) {
compress(pbf, compressed, true);
} else {
compressed = pbf;
}
if (!pk && compressed.size() > 500000) {
fprintf(stderr, "Tile %lld/%lld/%lld size is %lld, >500000. Skipping this tile.\n", ai->first.z, ai->first.x, ai->first.y, (long long) compressed.size());
} else {
a->outputs.insert(std::pair<zxy, std::string>(ai->first, compressed));
}
}
}
return NULL;
}
void dispatch_tasks(std::map<zxy, std::vector<std::string>> &tasks, std::vector<std::map<std::string, layermap_entry>> &layermaps, sqlite3 *outdb, const char *outdir, std::vector<std::string> &header, std::map<std::string, std::vector<std::string>> &mapping, sqlite3 *db, std::set<std::string> &exclude, std::set<std::string> &include, int ifmatched, std::set<std::string> &keep_layers, std::set<std::string> &remove_layers, json_object *filter, struct tileset_reader *readers, double *minlat, double *minlon, double *maxlat, double *maxlon, double *minlon2, double *maxlon2) {
pthread_t pthreads[CPUS];
std::vector<arg> args;
for (size_t i = 0; i < CPUS; i++) {
args.push_back(arg());
args[i].layermap = &layermaps[i];
args[i].header = &header;
args[i].mapping = &mapping;
args[i].db = db;
args[i].exclude = &exclude;
args[i].include = &include;
args[i].keep_layers = &keep_layers;
args[i].remove_layers = &remove_layers;
args[i].ifmatched = ifmatched;
args[i].filter = filter;
args[i].readers = readers;
args[i].minlat = *minlat;
args[i].minlon = *minlon;
args[i].maxlat = *maxlat;
args[i].maxlon = *maxlon;
args[i].minlon2 = *minlon2;
args[i].maxlon2 = *maxlon2;
}
size_t count = 0;
// This isn't careful about distributing tasks evenly across CPUs,
// but, from testing, it actually takes a little longer to do
// the proper allocation than is saved by perfectly balanced threads.
for (auto ai = tasks.begin(); ai != tasks.end(); ++ai) {
args[count].inputs.insert(*ai);
count = (count + 1) % CPUS;
if (ai == tasks.begin()) {
if (!quiet) {
fprintf(stderr, "%lld/%lld/%lld \r", ai->first.z, ai->first.x, ai->first.y);
fflush(stderr);
}
}
}
for (size_t i = 0; i < CPUS; i++) {
if (thread_create(&pthreads[i], NULL, join_worker, &args[i]) != 0) {
perror("pthread_create");
exit(EXIT_PTHREAD);
}
}
for (size_t i = 0; i < CPUS; i++) {
void *retval;
if (pthread_join(pthreads[i], &retval) != 0) {
perror("pthread_join");
}
*minlat = std::min(*minlat, args[i].minlat);
*minlon = std::min(*minlon, args[i].minlon);
*maxlat = std::max(*maxlat, args[i].maxlat);
*maxlon = std::max(*maxlon, args[i].maxlon);
*minlon2 = std::min(*minlon2, args[i].minlon2);
*maxlon2 = std::max(*maxlon2, args[i].maxlon2);
for (auto ai = args[i].outputs.begin(); ai != args[i].outputs.end(); ++ai) {
if (outdb != NULL) {
mbtiles_write_tile(outdb, ai->first.z, ai->first.x, ai->first.y, ai->second.data(), ai->second.size());
} else if (outdir != NULL) {
dir_write_tile(outdir, ai->first.z, ai->first.x, ai->first.y, ai->second, tile_format_extension(output_format));
}
}
}
}
void handle_strategies(const unsigned char *s, std::vector<strategy> *st) {
json_pull *jp = json_begin_string((const char *) s);
json_object *o = json_read_tree(jp);
if (o != NULL && o->type == JSON_ARRAY) {
for (size_t i = 0; i < o->value.array.length; i++) {
json_object *h = o->value.array.array[i];
if (h->type == JSON_HASH) {
for (size_t j = 0; j < h->value.object.length; j++) {
json_object *k = h->value.object.keys[j];
json_object *v = h->value.object.values[j];
if (k->type != JSON_STRING) {
fprintf(stderr, "Key %zu of %zu is not a string: %s\n", j, i, s);
} else if (v->type != JSON_NUMBER) {
fprintf(stderr, "Value %zu of %zu is not a number: %s\n", j, i, s);
} else {
if (i >= st->size()) {
st->resize(i + 1);
}
if (strcmp(k->value.string.string, "dropped_by_rate") == 0) {
(*st)[i].dropped_by_rate += v->value.number.number;
} else if (strcmp(k->value.string.string, "dropped_by_gamma") == 0) {
(*st)[i].dropped_by_gamma += v->value.number.number;
} else if (strcmp(k->value.string.string, "dropped_as_needed") == 0) {
(*st)[i].dropped_as_needed += v->value.number.number;
} else if (strcmp(k->value.string.string, "coalesced_as_needed") == 0) {
(*st)[i].coalesced_as_needed += v->value.number.number;
} else if (strcmp(k->value.string.string, "truncated_zooms") == 0) {
(*st)[i].truncated_zooms += v->value.number.number;
} else if (strcmp(k->value.string.string, "detail_reduced") == 0) {
(*st)[i].detail_reduced += v->value.number.number;
} else if (strcmp(k->value.string.string, "tiny_polygons") == 0) {
(*st)[i].tiny_polygons += v->value.number.number;
} else if (strcmp(k->value.string.string, "tile_size_desired") == 0) {
(*st)[i].tile_size += v->value.number.number;
} else if (strcmp(k->value.string.string, "feature_count_desired") == 0) {
(*st)[i].feature_count += v->value.number.number;
}
}
}
} else {
fprintf(stderr, "Element %zu is not a hash: %s\n", i, s);
}
}
json_free(o);
}
json_end(jp);
}
void handle_vector_layers(json_object *vector_layers, std::map<std::string, layermap_entry> &layermap, std::map<std::string, std::string> &attribute_descriptions) {
if (vector_layers != NULL && vector_layers->type == JSON_ARRAY) {
for (size_t i = 0; i < vector_layers->value.array.length; i++) {
if (vector_layers->value.array.array[i]->type == JSON_HASH) {
json_object *id = json_hash_get(vector_layers->value.array.array[i], "id");
json_object *desc = json_hash_get(vector_layers->value.array.array[i], "description");
if (id != NULL && desc != NULL && id->type == JSON_STRING && desc->type == JSON_STRING) {
std::string sid = id->value.string.string;
std::string sdesc = desc->value.string.string;
if (sdesc.size() != 0) {
auto f = layermap.find(sid);
if (f != layermap.end()) {
f->second.description = sdesc;
}
}
}
json_object *fields = json_hash_get(vector_layers->value.array.array[i], "fields");
if (fields != NULL && fields->type == JSON_HASH) {
for (size_t j = 0; j < fields->value.object.length; j++) {
if (fields->value.object.keys[j]->type == JSON_STRING && fields->value.object.values[j]->type) {
const char *desc2 = fields->value.object.values[j]->value.string.string;
if (strcmp(desc2, "Number") != 0 &&
strcmp(desc2, "String") != 0 &&
strcmp(desc2, "Boolean") != 0 &&
strcmp(desc2, "Mixed") != 0) {
attribute_descriptions.insert(std::pair<std::string, std::string>(fields->value.object.keys[j]->value.string.string, desc2));
}
}
}
}
}
}
}
}
void decode(struct tileset_reader *readers, std::map<std::string, layermap_entry> &layermap, sqlite3 *outdb, const char *outdir, struct stats *st, std::vector<std::string> &header, std::map<std::string, std::vector<std::string>> &mapping, sqlite3 *db, std::set<std::string> &exclude, std::set<std::string> &include, int ifmatched, std::string &attribution, std::string &description, std::set<std::string> &keep_layers, std::set<std::string> &remove_layers, std::string &name, json_object *filter, std::map<std::string, std::string> &attribute_descriptions, std::string &generator_options, std::vector<strategy> *strategies) {
std::vector<std::map<std::string, layermap_entry>> layermaps;
for (size_t i = 0; i < CPUS; i++) {
layermaps.push_back(std::map<std::string, layermap_entry>());
}
std::map<zxy, std::vector<std::string>> tasks;
double minlat = INT_MAX;
double minlon = INT_MAX;
double maxlat = INT_MIN;
double maxlon = INT_MIN;
double minlon2 = INT_MAX;
double maxlon2 = INT_MIN;
while (readers != NULL && !readers->all_done()) {
std::pair<zxy, std::string> current = readers->current();
if (current.first.z >= minzoom && current.first.z <= maxzoom) {
zxy tile = current.first;
if (tasks.count(tile) == 0) {
tasks.insert(std::pair<zxy, std::vector<std::string>>(tile, std::vector<std::string>()));
}
auto f = tasks.find(tile);
f->second.push_back(current.second);
}
// Advance the tileset_reader that we just added as a task.
// The reason this prefetches is so the tileset_reader queue can be
// priority-ordered, so the one with the next relevant tile
// is first in line.
readers->advance();
// pull the tileset_reader off the front of the queue for reordering
tileset_reader *r = readers;
readers = readers->next;
r->next = NULL;
// Is the next tileset_reader on the tileset_reader queue looking at a different tile?
// Then this tile is done and we can safely run the output queue.
if (readers == NULL || readers->zoom != current.first.z || readers->x != current.first.x || readers->y != current.first.y) {
if (tasks.size() > 100 * CPUS) {
dispatch_tasks(tasks, layermaps, outdb, outdir, header, mapping, db, exclude, include, ifmatched, keep_layers, remove_layers, filter, readers, &minlat, &minlon, &maxlat, &maxlon, &minlon2, &maxlon2);
tasks.clear();
}
}
// put the tileset_reader back onto the queue,
// in whatever sequence its next tile calls for
struct tileset_reader **rr;
for (rr = &readers; *rr != NULL; rr = &((*rr)->next)) {
if (*r < **rr) {
break;
}
}
r->next = *rr;
*rr = r;
}
dispatch_tasks(tasks, layermaps, outdb, outdir, header, mapping, db, exclude, include, ifmatched, keep_layers, remove_layers, filter, readers, &minlat, &minlon, &maxlat, &maxlon, &minlon2, &maxlon2);
layermap = merge_layermaps(layermaps);
st->minlon = std::min(minlon, st->minlon);
st->maxlon = std::max(maxlon, st->maxlon);
st->minlat = std::min(minlat, st->minlat);
st->maxlat = std::max(maxlat, st->maxlat);
st->minlon2 = std::min(minlon2, st->minlon2);
st->maxlon2 = std::max(maxlon2, st->maxlon2);
st->minlat2 = std::min(minlat, st->minlat2);
st->maxlat2 = std::max(maxlat, st->maxlat2);
struct tileset_reader *next;
for (struct tileset_reader *r = readers; r != NULL; r = next) {
next = r->next;
r->close();
sqlite3_stmt *stmt;
if (sqlite3_prepare_v2(r->db, "SELECT value from metadata where name = 'minzoom'", -1, &stmt, NULL) == SQLITE_OK) {
if (sqlite3_step(stmt) == SQLITE_ROW) {
int minz = std::max(sqlite3_column_int(stmt, 0), minzoom);
st->minzoom = std::min(st->minzoom, minz);
}
sqlite3_finalize(stmt);
}
if (sqlite3_prepare_v2(r->db, "SELECT value from metadata where name = 'maxzoom'", -1, &stmt, NULL) == SQLITE_OK) {
if (sqlite3_step(stmt) == SQLITE_ROW) {
int maxz = std::min(sqlite3_column_int(stmt, 0), maxzoom);
if (!want_overzoom) {
if (st->maxzoom >= 0 && maxz != st->maxzoom) {
fprintf(stderr, "Warning: mismatched maxzooms: %d in %s vs previous %d\n", maxz, r->name.c_str(), st->maxzoom);
}
}
st->maxzoom = std::max(st->maxzoom, maxz);
}
sqlite3_finalize(stmt);
}
if (sqlite3_prepare_v2(r->db, "SELECT value from metadata where name = 'center'", -1, &stmt, NULL) == SQLITE_OK) {
if (sqlite3_step(stmt) == SQLITE_ROW) {
const unsigned char *s = sqlite3_column_text(stmt, 0);
if (s != NULL) {
sscanf((char *) s, "%lf,%lf", &st->midlon, &st->midlat);
}
}
sqlite3_finalize(stmt);
}
if (sqlite3_prepare_v2(r->db, "SELECT value from metadata where name = 'attribution'", -1, &stmt, NULL) == SQLITE_OK) {
if (sqlite3_step(stmt) == SQLITE_ROW) {
const unsigned char *s = sqlite3_column_text(stmt, 0);
if (s != NULL) {
attribution = std::string((char *) s);
}
}
sqlite3_finalize(stmt);
}
if (sqlite3_prepare_v2(r->db, "SELECT value from metadata where name = 'description'", -1, &stmt, NULL) == SQLITE_OK) {
if (sqlite3_step(stmt) == SQLITE_ROW) {
const unsigned char *s = sqlite3_column_text(stmt, 0);
if (s != NULL) {
description = std::string((char *) s);
}
}
sqlite3_finalize(stmt);
}
if (sqlite3_prepare_v2(r->db, "SELECT value from metadata where name = 'name'", -1, &stmt, NULL) == SQLITE_OK) {
if (sqlite3_step(stmt) == SQLITE_ROW) {
const unsigned char *s = sqlite3_column_text(stmt, 0);
if (s != NULL) {
if (name.size() == 0) {
name = std::string((char *) s);
} else {
std::string proposed = name + " + " + std::string((char *) s);
if (proposed.size() < 255) {
name = proposed;
}
}
}
}
sqlite3_finalize(stmt);
}
if (sqlite3_prepare_v2(r->db, "SELECT value from metadata where name = 'json'", -1, &stmt, NULL) == SQLITE_OK) {
if (sqlite3_step(stmt) == SQLITE_ROW) {
const unsigned char *s = sqlite3_column_text(stmt, 0);
if (s != NULL) {
json_pull *jp = json_begin_string((const char *) s);
json_object *o = json_read_tree(jp);
if (o != NULL && o->type == JSON_HASH) {
json_object *vector_layers = json_hash_get(o, "vector_layers");
handle_vector_layers(vector_layers, layermap, attribute_descriptions);
json_free(o);
}
json_end(jp);
}
}
sqlite3_finalize(stmt);
}
if (sqlite3_prepare_v2(r->db, "SELECT value from metadata where name = 'generator_options'", -1, &stmt, NULL) == SQLITE_OK) {
if (sqlite3_step(stmt) == SQLITE_ROW) {
const unsigned char *s = sqlite3_column_text(stmt, 0);
if (s != NULL) {
if (generator_options.size() != 0) {
generator_options.append("; ");
generator_options.append((const char *) s);
} else {
generator_options = (const char *) s;
}
}
}
sqlite3_finalize(stmt);
}
if (sqlite3_prepare_v2(r->db, "SELECT value from metadata where name = 'strategies'", -1, &stmt, NULL) == SQLITE_OK) {
if (sqlite3_step(stmt) == SQLITE_ROW) {
const unsigned char *s = sqlite3_column_text(stmt, 0);
handle_strategies(s, strategies);
}
sqlite3_finalize(stmt);
}
// Closes either real r->db or temp mirror of metadata.json
if (sqlite3_close(r->db) != SQLITE_OK) {
fprintf(stderr, "Could not close database: %s\n", sqlite3_errmsg(r->db));
exit(EXIT_CLOSE);
}
delete r;
}
}
void usage(char **argv) {
fprintf(stderr, "Usage: %s [-f] [-i] [-pk] [-pC] [-c joins.csv] [-X] [-x exclude ...] [-y include ...] [-r inputfile.txt ] -o new.mbtiles source.mbtiles ...\n", argv[0]);
exit(EXIT_ARGS);
}
int main(int argc, char **argv) {
char *out_mbtiles = NULL;
char *out_dir = NULL;
sqlite3 *outdb = NULL;
char *csv = NULL;
int force = 0;
int ifmatched = 0;
int filearg = 0;
json_object *filter = NULL;
std::string join_sqlite_fname;
struct tileset_reader *readers = NULL;
CPUS = get_num_avail_cpus();
const char *TIPPECANOE_MAX_THREADS = getenv("TIPPECANOE_MAX_THREADS");
if (TIPPECANOE_MAX_THREADS != NULL) {
CPUS = atoi(TIPPECANOE_MAX_THREADS);
}
if (CPUS < 1) {
CPUS = 1;
}
if (sqlite3_config(SQLITE_CONFIG_SERIALIZED) != SQLITE_OK) {
fprintf(stderr, "Could not enable sqlite3 serialized multithreading\n");
exit(EXIT_SQLITE);
}
std::vector<std::string> header;
std::map<std::string, std::vector<std::string>> mapping;
sqlite3 *db = NULL;
std::set<std::string> exclude;
std::set<std::string> include;
std::set<std::string> keep_layers;
std::set<std::string> remove_layers;
std::string set_name, set_description, set_attribution;
struct option long_options[] = {
{"output", required_argument, 0, 'o'},
{"output-to-directory", required_argument, 0, 'e'},
{"force", no_argument, 0, 'f'},
{"overzoom", no_argument, 0, 'O'},
{"buffer", required_argument, 0, 'b'},
{"if-matched", no_argument, 0, 'i'},
{"attribution", required_argument, 0, 'A'},
{"name", required_argument, 0, 'n'},
{"description", required_argument, 0, 'N'},
{"prevent", required_argument, 0, 'p'},
{"csv", required_argument, 0, 'c'},
{"exclude", required_argument, 0, 'x'},
{"exclude-all", no_argument, 0, 'X'},
{"include", required_argument, 0, 'y'},
{"exclude-all-tile-attributes", no_argument, 0, '~'},
{"exclude-all-tile-geometries", no_argument, 0, '~'},
{"layer", required_argument, 0, 'l'},
{"exclude-layer", required_argument, 0, 'L'},
{"quiet", no_argument, 0, 'q'},
{"maximum-zoom", required_argument, 0, 'z'},
{"minimum-zoom", required_argument, 0, 'Z'},
{"feature-filter-file", required_argument, 0, 'J'},
{"feature-filter", required_argument, 0, 'j'},
{"rename-layer", required_argument, 0, 'R'},
{"read-from", required_argument, 0, 'r'},
{"use-attribute-for-id", required_argument, 0, '~'},
{"no-tile-size-limit", no_argument, &pk, 1},
{"no-tile-compression", no_argument, &pC, 1},
{"empty-csv-columns-are-null", no_argument, &pe, 1},
{"no-tile-stats", no_argument, &pg, 1},
{"tile-stats-attributes-limit", required_argument, 0, '~'},
{"tile-stats-sample-values-limit", required_argument, 0, '~'},
{"tile-stats-values-limit", required_argument, 0, '~'},
{"unidecode-data", required_argument, 0, '~'},
{"output-format", required_argument, 0, '~'},
{"pretessellate", no_argument, 0, '~'},
{"no-mlt-feature-sort", no_argument, 0, '~'},
{0, 0, 0, 0},
};
std::string getopt_str;
for (size_t lo = 0; long_options[lo].name != NULL; lo++) {
if (long_options[lo].val > ' ') {
getopt_str.push_back(long_options[lo].val);
if (long_options[lo].has_arg == required_argument) {
getopt_str.push_back(':');
}
}
}
extern int optind;
extern char *optarg;
int i;
std::string commandline = format_commandline(argc, argv);
int option_index = 0;
while ((i = getopt_long(argc, argv, getopt_str.c_str(), long_options, &option_index)) != -1) {
switch (i) {
case 0:
break;
case 'o':
out_mbtiles = optarg;
break;
case 'e':
out_dir = optarg;
break;
case 'f':
force = 1;
break;
case 'O':
want_overzoom = true;
break;
case 'b':
buffer = atoi(optarg);
break;
case 'i':
ifmatched = 1;
break;
case 'A':
set_attribution = optarg;
break;
case 'n':
set_name = optarg;
break;
case 'N':
set_description = optarg;
break;
case 'z':
maxzoom = atoi(optarg);
break;
case 'Z':
minzoom = atoi(optarg);
break;
case 'J':
filter = read_filter(optarg);
break;
case 'j':
filter = parse_filter(optarg);
break;
case 'p':
if (strcmp(optarg, "k") == 0) {
pk = true;
} else if (strcmp(optarg, "C") == 0) {
pC = true;
} else if (strcmp(optarg, "g") == 0) {
pg = true;
} else if (strcmp(optarg, "e") == 0) {
pe = true;
} else {
fprintf(stderr, "%s: Unknown option for -p%s\n", argv[0], optarg);
exit(EXIT_ARGS);
}
break;
case 'c':
if (csv != NULL) {
fprintf(stderr, "Only one -c for now\n");
exit(EXIT_ARGS);
}
csv = optarg;
readcsv(csv, header, mapping);
break;
case 'x':
exclude.insert(std::string(optarg));
break;
case 'X':
exclude_all = true;
break;
case 'y':
exclude_all = true;
include.insert(std::string(optarg));
break;
case 'l':
keep_layers.insert(std::string(optarg));
break;
case 'L':
remove_layers.insert(std::string(optarg));
break;
case 'R': {
char *cp = strchr(optarg, ':');
if (cp == NULL || cp == optarg) {
fprintf(stderr, "%s: -R requires old:new\n", argv[0]);
exit(EXIT_ARGS);
}
std::string before = std::string(optarg).substr(0, cp - optarg);
std::string after = std::string(cp + 1);
renames.insert(std::pair<std::string, std::string>(before, after));
break;
}
case 'r': {
std::fstream read_file;
read_file.open(std::string(optarg), std::ios::in);
if (read_file.is_open()) {
std::string sa;
filearg = 1;
while (getline(read_file, sa)) {
char *c = const_cast<char *>(sa.c_str());
tileset_reader *r = begin_reading(c);
// put the new tileset_reader in priority order
struct tileset_reader **rr;
for (rr = &readers; *rr != NULL; rr = &((*rr)->next)) {
if (*r < **rr) {
break;
}
}
r->next = *rr;
*rr = r;
}
read_file.close();
}
break;
}
case 'q':
quiet = true;
break;
case '~': {
const char *opt = long_options[option_index].name;
if (strcmp(opt, "tile-stats-attributes-limit") == 0) {
max_tilestats_attributes = atoi(optarg);
} else if (strcmp(opt, "tile-stats-sample-values-limit") == 0) {
max_tilestats_sample_values = atoi(optarg);
} else if (strcmp(opt, "tile-stats-values-limit") == 0) {
max_tilestats_values = atoi(optarg);
} else if (strcmp(opt, "unidecode-data") == 0) {
unidecode_data = read_unidecode(optarg);
} else if (strcmp(opt, "exclude-all-tile-attributes") == 0) {
exclude_all_tile_attributes = true;
} else if (strcmp(opt, "exclude-all-tile-geometries") == 0) {
exclude_all_tile_geometries = true;
} else if (strcmp(opt, "output-format") == 0) {
set_output_format(argv, optarg);
} 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);
}
break;
}
default:
usage(argv);
}
}
if ((argc - optind < 1) && (filearg == 0)) {
usage(argv);
}
if (out_mbtiles == NULL && out_dir == NULL) {
fprintf(stderr, "%s: must specify -o out.mbtiles or -e directory\n", argv[0]);
usage(argv);
}
if (out_mbtiles != NULL && out_dir != NULL) {
fprintf(stderr, "%s: Options -o and -e cannot be used together\n", argv[0]);
usage(argv);
}
if (minzoom > maxzoom) {
fprintf(stderr, "%s: Minimum zoom -Z%d cannot be greater than maxzoom -z%d\n", argv[0], minzoom, maxzoom);
exit(EXIT_ARGS);
}
if (buffer < 0) {
fprintf(stderr, "%s: buffer cannot be less than 0\n", argv[0]);
exit(EXIT_ARGS);
}
if (out_mbtiles != NULL) {
if (force) {
unlink(out_mbtiles);
} else {
if (pmtiles_has_suffix(out_mbtiles)) {
check_pmtiles(out_mbtiles, argv, false);
}
}
outdb = mbtiles_open(out_mbtiles, argv, 0);
}
if (out_dir != NULL) {
check_dir(out_dir, argv, force, false);
}
struct stats st;
st.minzoom = st.minlat = st.minlon = st.minlat2 = st.minlon2 = INT_MAX;
st.maxzoom = st.maxlat = st.maxlon = st.maxlat2 = st.maxlon2 = INT_MIN;
std::map<std::string, layermap_entry> layermap;
std::string attribution;
std::string description;
std::string name;
if (filearg == 0) {
for (i = optind; i < argc; i++) {
tileset_reader *r = begin_reading(argv[i]);
// put the new tileset_reader in priority order
struct tileset_reader **rr;
for (rr = &readers; *rr != NULL; rr = &((*rr)->next)) {
if (*r < **rr) {
break;
}
}
r->next = *rr;
*rr = r;
}
}
std::map<std::string, std::string> attribute_descriptions;
std::string generator_options;
std::vector<strategy> strategies;
decode(readers, layermap, outdb, out_dir, &st, header, mapping, db, exclude, include, ifmatched, attribution, description, keep_layers, remove_layers, name, filter, attribute_descriptions, generator_options, &strategies);
if (set_attribution.size() != 0) {
attribution = set_attribution;
}
if (set_description.size() != 0) {
description = set_description;
}
if (set_name.size() != 0) {
name = set_name;
}
if (generator_options.size() != 0) {
generator_options.append("; ");
}
generator_options.append(commandline);
// don't trust the source metadata maxzooms;
// claim the zooms that were actually written
st.maxzoom = INT_MIN;
st.minzoom = INT_MAX;
for (auto &l : layermap) {
if (l.second.minzoom < st.minzoom) {
st.minzoom = l.second.minzoom;
}
if (l.second.maxzoom > st.maxzoom) {
st.maxzoom = l.second.maxzoom;
}
}
if (st.maxlon < st.minlon) {
st.maxlon = st.minlon = st.maxlat = st.minlat = st.minlon2 = st.maxlon2 = st.minlat2 = st.maxlat2 = 0;
}
if (st.maxlon - st.minlon <= st.maxlon2 - st.minlon2) {
st.minlon2 = st.minlon;
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, tile_format_name(output_format), 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);
} else {
dir_write_metadata(out_dir, m);
}
if (outdb != NULL) {
mbtiles_close(outdb, argv[0]);
}
if (filter != NULL) {
json_free(filter);
}
if (pmtiles_has_suffix(out_mbtiles)) {
mbtiles_map_image_to_pmtiles(out_mbtiles, m, !pC, quiet, false);
}
return 0;
}