mirror of
https://github.com/felt/tippecanoe.git
synced 2026-10-02 16:35:40 +02:00
* Fix variable-length-array and uninitialized-union compiler warnings Clang warns about every variable-length array in C++ (-Wvla-cxx-extension, on by default), since VLAs are a compiler extension rather than standard C++. Replace all 57 of them with std::vector, or with std::string for the mkstemp() template buffers built from tmpdir. Add -Wvla to WARNING_FLAGS so new ones don't creep back in. Separately, mvt_value's numeric_value union is 16 bytes wide (the size of string_value), but both constructors only wrote the 8 bytes of the member they were setting, leaving the rest indeterminate. The implicit copy constructor copies the union as a whole, so copying any non-string value read uninitialized bytes, which GCC reports as mvt.hpp:83:8: warning: 'v.mvt_value::numeric_value. ... .len' may be used uninitialized [-Wmaybe-uninitialized] Give string_value, the widest member, a default member initializer so the union's full width is initialized however it is later used. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01D8gsGMjK78TQiCGKTZ2PyR * Fix remaining float-conversion and format-truncation warnings Clang's -Wimplicit-const-int-float-conversion flagged two comparisons against LLONG_MAX, which is not representable as a double and rounds up to 2^63. In serial.cpp this was a real latent overflow, not just noise: the guard `extent <= LLONG_MAX` was really `extent <= 2^63`, so an extent of exactly 2^63 passed it and then hit `(long long) extent`, which is undefined for that value and yields LLONG_MIN in practice -- the opposite of the clamp the else branch intends. Make the bound exclusive so the conversion is always in range. Requires a polygon area at the very top of the double range to reach, but the clamp now behaves as written. In mbtiles.cpp the value is only a stand-in for infinity on its way into JSON, so cast explicitly; the emitted number is unchanged. Separately, g++ at -O0 warned that `char abbrev[20]` can be truncated by "%lld", which is correct: the most negative long long needs 21 bytes with the NUL. That branch is only reached when point_count < 1000, so it cannot happen today, but size the buffer to fit rather than rely on that, and replace the garbled comment about how the size was derived. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01D8gsGMjK78TQiCGKTZ2PyR * Clamp the low end of extent before converting to long long too The upper bound was fixed in the previous commit; the same overflow exists on the negative side. get_area() returns a signed shoelace area, so inner rings contribute negatively, and a polygon whose holes outweigh its rings drives extent below zero. Far enough below and `(long long) extent` is undefined again. The bounds are asymmetric, so this is not simply the mirror of the upper one: LLONG_MIN is exactly -2^63 and converts exactly, so unlike LLONG_MAX it can be an inclusive bound. Verified with -fsanitize=float-cast-overflow that the previous form traps on 2^63 and on doubles just below -2^63, and that this one is clean across both boundaries, the infinities, and NaN (which falls to LLONG_MAX, as it did before). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01D8gsGMjK78TQiCGKTZ2PyR * Add CHANGELOG entries for 2.81.0 and bump the version CHANGELOG.md was last updated for 2.80.0 (#361), and version.hpp has not moved since. Twelve PRs have landed in the meantime with no entry: #365, #368, #375, #382, #384, #385, #391, #395, #397, #399, #400, and #401. Document all of them, plus this PR, under a single 2.81.0 heading. They are not given separate version numbers because none of them was ever released under one -- version.hpp read v2.80.0 throughout -- so assigning a version per PR would invent release history. 2.81.0 is the version that will actually carry them. Where an unreleased PR was corrected by a later one (#384 by #385, #397 by #399), the pair is described as the single behavior that ships, since the intermediate behavior was never in a release. Minor rather than patch bump: the batch adds command-line options. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01D8gsGMjK78TQiCGKTZ2PyR * Review feedback: enforce the union-width assumption, describe both clamp ends The comment on mvt_value's union claimed string_value is the widest member. That is true on LP64 (16 bytes against 8) but not on ILP32, where size_t is 4 and it ties with double and long long. The default member initializer still covers the full union either way, so the fix held, but the justification did not travel. Replace the claim with a static_assert that checks it on whatever target is being built, so a platform where it stops holding is a compile error rather than silently indeterminate bytes. Verified the assert is not vacuous by widening the union in a scratch copy and watching it fail. The changelog described only the upper end of the extent clamp. Describe both: the old guard admitted everything below LLONG_MIN too. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01D8gsGMjK78TQiCGKTZ2PyR * Add 2.81.0 changelog entries for the four PRs merged from main #404, #408, #409, and #410 landed while this branch was open. None of them bumped version.hpp, so they belong under the same 2.81.0 heading as the rest of the unreleased work rather than getting versions of their own. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01D8gsGMjK78TQiCGKTZ2PyR --------- Co-authored-by: Claude <noreply@anthropic.com>
1686 lines
48 KiB
C++
1686 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 "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"
|
|
#include "usage.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 = outtile.encode();
|
|
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) {
|
|
std::vector<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);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
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;
|
|
}
|
|
}
|
|
|
|
static const struct option long_options[] = {
|
|
{"Output tileset", 0, 0, 0},
|
|
{"output", required_argument, 0, 'o'},
|
|
{"output-to-directory", required_argument, 0, 'e'},
|
|
{"force", no_argument, 0, 'f'},
|
|
|
|
{"Tileset description and attribution", 0, 0, 0},
|
|
{"name", required_argument, 0, 'n'},
|
|
{"attribution", required_argument, 0, 'A'},
|
|
{"description", required_argument, 0, 'N'},
|
|
|
|
{"Input tilesets", 0, 0, 0},
|
|
{"read-from", required_argument, 0, 'r'},
|
|
|
|
{"Zoom levels", 0, 0, 0},
|
|
{"maximum-zoom", required_argument, 0, 'z'},
|
|
{"minimum-zoom", required_argument, 0, 'Z'},
|
|
{"overzoom", no_argument, 0, 'O'},
|
|
{"buffer", required_argument, 0, 'b'},
|
|
|
|
{"Layer names", 0, 0, 0},
|
|
{"layer", required_argument, 0, 'l'},
|
|
{"exclude-layer", required_argument, 0, 'L'},
|
|
{"rename-layer", required_argument, 0, 'R'},
|
|
|
|
{"Joining with a CSV file", 0, 0, 0},
|
|
{"csv", required_argument, 0, 'c'},
|
|
{"if-matched", no_argument, 0, 'i'},
|
|
{"empty-csv-columns-are-null", no_argument, &pe, 1},
|
|
|
|
{"Filtering feature attributes", 0, 0, 0},
|
|
{"exclude", required_argument, 0, 'x'},
|
|
{"include", required_argument, 0, 'y'},
|
|
{"exclude-all", no_argument, 0, 'X'},
|
|
{"exclude-all-tile-attributes", no_argument, 0, '~'},
|
|
{"exclude-all-tile-geometries", no_argument, 0, '~'},
|
|
|
|
{"Filtering features by attributes", 0, 0, 0},
|
|
{"feature-filter-file", required_argument, 0, 'J'},
|
|
{"feature-filter", required_argument, 0, 'j'},
|
|
|
|
{"Setting or disabling tile size limits", 0, 0, 0},
|
|
{"no-tile-size-limit", no_argument, &pk, 1},
|
|
{"no-tile-compression", no_argument, &pC, 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, '~'},
|
|
|
|
{"Progress indicator", 0, 0, 0},
|
|
{"quiet", no_argument, 0, 'q'},
|
|
|
|
{"", 0, 0, 0},
|
|
{"prevent", required_argument, 0, 'p'},
|
|
{"unidecode-data", required_argument, 0, '~'},
|
|
|
|
{0, 0, 0, 0},
|
|
};
|
|
|
|
// the options above, with the usage message headings removed
|
|
static struct option real_long_options[sizeof(long_options) / sizeof(long_options[0])];
|
|
|
|
void usage(char **argv) {
|
|
static const char *const forms[] = {
|
|
"[options] source.mbtiles ...",
|
|
"[options] --read-from=inputfile.txt",
|
|
NULL,
|
|
};
|
|
static const struct usage_required_option required[] = {
|
|
{"output", "new.mbtiles", 1},
|
|
{"output-to-directory", "directory", 1},
|
|
{NULL, NULL, 0},
|
|
};
|
|
|
|
print_usage(stderr, argv[0], forms, long_options, required);
|
|
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;
|
|
|
|
strip_usage_headings(long_options, real_long_options);
|
|
std::string getopt_str = getopt_string(real_long_options);
|
|
|
|
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(), real_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 = real_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 {
|
|
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, true, 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;
|
|
}
|