Files
tippecanoe/mbtiles.cpp
Erica Fischer bd48ba8ea1 Fix accidental loss (at all zooms) of features with an explicit minzoom (#221)
* Fix accidental loss (at all zooms) of features with an explicit minzoom

* Try to stabilize centers and bounding boxes between architectures
2024-03-22 10:20:10 -07:00

940 lines
27 KiB
C++

// for vasprintf() on Linux
#ifndef _GNU_SOURCE
#define _GNU_SOURCE
#endif
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sqlite3.h>
#include <cmath>
#include <climits>
#include <vector>
#include <string>
#include <set>
#include <map>
#include <sys/stat.h>
#include "mvt.hpp"
#include "mbtiles.hpp"
#include "text.hpp"
#include "milo/dtoa_milo.h"
#include "write_json.hpp"
#include "version.hpp"
#include "errors.hpp"
size_t max_tilestats_attributes = 1000;
size_t max_tilestats_sample_values = 1000;
size_t max_tilestats_values = 100;
sqlite3 *mbtiles_open(char *dbname, char **argv, int forcetable) {
sqlite3 *outdb;
if (sqlite3_open(dbname, &outdb) != SQLITE_OK) {
fprintf(stderr, "%s: %s: %s\n", argv[0], dbname, sqlite3_errmsg(outdb));
exit(EXIT_OPEN);
}
char *err = NULL;
if (sqlite3_exec(outdb, "PRAGMA synchronous=0", NULL, NULL, &err) != SQLITE_OK) {
fprintf(stderr, "%s: async: %s\n", argv[0], err);
exit(EXIT_SQLITE);
}
if (sqlite3_exec(outdb, "PRAGMA locking_mode=EXCLUSIVE", NULL, NULL, &err) != SQLITE_OK) {
fprintf(stderr, "%s: async: %s\n", argv[0], err);
exit(EXIT_SQLITE);
}
if (sqlite3_exec(outdb, "PRAGMA journal_mode=DELETE", NULL, NULL, &err) != SQLITE_OK) {
fprintf(stderr, "%s: async: %s\n", argv[0], err);
exit(EXIT_SQLITE);
}
if (sqlite3_exec(outdb, "CREATE TABLE metadata (name text, value text);", NULL, NULL, &err) != SQLITE_OK) {
fprintf(stderr, "%s: Tileset \"%s\" already exists. You can use --force if you want to delete the old tileset.\n", argv[0], dbname);
fprintf(stderr, "%s: %s\n", argv[0], err);
if (!forcetable) {
exit(EXIT_EXISTS);
}
}
if (sqlite3_exec(outdb, "create unique index name on metadata (name);", NULL, NULL, &err) != SQLITE_OK) {
fprintf(stderr, "%s: index metadata: %s\n", argv[0], err);
if (!forcetable) {
exit(EXIT_EXISTS);
}
}
// "map" maps z/x/y coordinates to a content hash
if (sqlite3_exec(outdb, "CREATE TABLE map (zoom_level INTEGER, tile_column INTEGER, tile_row INTEGER, tile_id TEXT);", NULL, NULL, &err) != SQLITE_OK) {
fprintf(stderr, "%s: create map table: %s\n", argv[0], err);
if (!forcetable) {
exit(EXIT_EXISTS);
}
}
if (sqlite3_exec(outdb, "CREATE UNIQUE INDEX map_index ON map (zoom_level, tile_column, tile_row);", NULL, NULL, &err) != SQLITE_OK) {
fprintf(stderr, "%s: create map index: %s\n", argv[0], err);
if (!forcetable) {
exit(EXIT_EXISTS);
}
}
// "images" maps a content hash to tile contents, per zoom level
if (sqlite3_exec(outdb, "CREATE TABLE images (zoom_level integer, tile_data blob, tile_id text);", NULL, NULL, &err) != SQLITE_OK) {
fprintf(stderr, "%s: create images table: %s\n", argv[0], err);
if (!forcetable) {
exit(EXIT_EXISTS);
}
}
if (sqlite3_exec(outdb, "CREATE UNIQUE INDEX images_id ON images (zoom_level, tile_id);", NULL, NULL, &err) != SQLITE_OK) {
fprintf(stderr, "%s: create images index: %s\n", argv[0], err);
if (!forcetable) {
exit(EXIT_EXISTS);
}
}
// "tiles" is a view that retrieves content from "images"
// via the content hash looked up from "map".
if (sqlite3_exec(outdb, "CREATE VIEW tiles AS SELECT map.zoom_level AS zoom_level, map.tile_column AS tile_column, map.tile_row AS tile_row, images.tile_data AS tile_data FROM map JOIN images ON images.tile_id = map.tile_id and images.zoom_level = map.zoom_level;", NULL, NULL, &err) != SQLITE_OK) {
fprintf(stderr, "%s: create tiles view: %s\n", argv[0], err);
if (!forcetable) {
exit(EXIT_EXISTS);
}
}
return outdb;
}
void mbtiles_write_tile(sqlite3 *outdb, int z, int tx, int ty, const char *data, int size) {
std::string hash = std::to_string(fnv1a(std::string(data, size)));
// following https://github.com/mapbox/node-mbtiles/blob/master/lib/mbtiles.js
sqlite3_stmt *stmt;
const char *images = "replace into images (zoom_level, tile_id, tile_data) values (?, ?, ?)";
if (sqlite3_prepare_v2(outdb, images, -1, &stmt, NULL) != SQLITE_OK) {
fprintf(stderr, "sqlite3 images prep failed\n");
exit(EXIT_SQLITE);
}
sqlite3_bind_int(stmt, 1, z);
sqlite3_bind_text(stmt, 2, hash.c_str(), hash.size(), NULL);
sqlite3_bind_blob(stmt, 3, data, size, NULL);
if (sqlite3_step(stmt) != SQLITE_DONE) {
fprintf(stderr, "sqlite3 images insert failed: %s\n", sqlite3_errmsg(outdb));
exit(EXIT_SQLITE);
}
if (sqlite3_finalize(stmt) != SQLITE_OK) {
fprintf(stderr, "sqlite3 images finalize failed: %s\n", sqlite3_errmsg(outdb));
exit(EXIT_SQLITE);
}
const char *map = "insert into map (zoom_level, tile_column, tile_row, tile_id) values (?, ?, ?, ?)";
if (sqlite3_prepare_v2(outdb, map, -1, &stmt, NULL) != SQLITE_OK) {
fprintf(stderr, "sqlite3 map prep failed\n");
exit(EXIT_SQLITE);
}
sqlite3_bind_int(stmt, 1, z);
sqlite3_bind_int(stmt, 2, tx);
sqlite3_bind_int(stmt, 3, (1 << z) - 1 - ty);
sqlite3_bind_text(stmt, 4, hash.c_str(), hash.size(), NULL);
if (sqlite3_step(stmt) != SQLITE_DONE) {
fprintf(stderr, "sqlite3 map insert failed: %s\n", sqlite3_errmsg(outdb));
exit(EXIT_SQLITE);
}
if (sqlite3_finalize(stmt) != SQLITE_OK) {
fprintf(stderr, "sqlite3 finalize failed: %s\n", sqlite3_errmsg(outdb));
exit(EXIT_SQLITE);
}
}
void mbtiles_erase_zoom(sqlite3 *outdb, int z) {
sqlite3_stmt *stmt;
const char *query = "delete from map where zoom_level = ?";
if (sqlite3_prepare_v2(outdb, query, -1, &stmt, NULL) != SQLITE_OK) {
fprintf(stderr, "sqlite3 delete map prep failed\n");
exit(EXIT_SQLITE);
}
sqlite3_bind_int(stmt, 1, z);
if (sqlite3_step(stmt) != SQLITE_DONE) {
fprintf(stderr, "sqlite3 delete map failed: %s\n", sqlite3_errmsg(outdb));
exit(EXIT_SQLITE);
}
if (sqlite3_finalize(stmt) != SQLITE_OK) {
fprintf(stderr, "sqlite3 delete map finalize failed: %s\n", sqlite3_errmsg(outdb));
exit(EXIT_SQLITE);
}
query = "delete from images where zoom_level = ?";
if (sqlite3_prepare_v2(outdb, query, -1, &stmt, NULL) != SQLITE_OK) {
fprintf(stderr, "sqlite3 delete images prep failed\n");
exit(EXIT_SQLITE);
}
sqlite3_bind_int(stmt, 1, z);
if (sqlite3_step(stmt) != SQLITE_DONE) {
fprintf(stderr, "sqlite3 delete images failed: %s\n", sqlite3_errmsg(outdb));
exit(EXIT_SQLITE);
}
if (sqlite3_finalize(stmt) != SQLITE_OK) {
fprintf(stderr, "sqlite3 delete images finalize failed: %s\n", sqlite3_errmsg(outdb));
exit(EXIT_SQLITE);
}
}
bool serial_val::operator<(const serial_val &o) const {
if (s < o.s) {
return true;
}
if (s == o.s && type < o.type) {
return true;
}
return false;
}
bool serial_val::operator!=(const serial_val &o) const {
if (type != o.type) {
return true;
}
if (s != o.s) {
return true;
}
return false;
}
void tilestats(std::map<std::string, layermap_entry> const &layermap1, size_t elements, json_writer &state) {
// Consolidate layers/attributes whose names are truncated
std::vector<std::map<std::string, layermap_entry>> lmv;
lmv.push_back(layermap1);
std::map<std::string, layermap_entry> layermap = merge_layermaps(lmv, true);
state.nospace = true;
state.json_write_hash();
state.nospace = true;
state.json_write_string("layerCount");
state.nospace = true;
state.json_write_unsigned(layermap.size());
state.nospace = true;
state.json_write_string("layers");
state.nospace = true;
state.json_write_array();
for (auto layer : layermap) {
state.nospace = true;
state.json_write_hash();
state.nospace = true;
state.json_write_string("layer");
state.nospace = true;
state.json_write_string(layer.first);
state.nospace = true;
state.json_write_string("count");
state.nospace = true;
state.json_write_unsigned(layer.second.points + layer.second.lines + layer.second.polygons);
std::string geomtype = "Polygon";
if (layer.second.points >= layer.second.lines && layer.second.points >= layer.second.polygons) {
geomtype = "Point";
} else if (layer.second.lines >= layer.second.polygons && layer.second.lines >= layer.second.points) {
geomtype = "LineString";
}
state.nospace = true;
state.json_write_string("geometry");
state.nospace = true;
state.json_write_string(geomtype);
size_t attrib_count = layer.second.tilestats.size();
if (attrib_count > max_tilestats_attributes) {
attrib_count = max_tilestats_attributes;
}
state.nospace = true;
state.json_write_string("attributeCount");
state.nospace = true;
state.json_write_unsigned(attrib_count);
state.nospace = true;
state.json_write_string("attributes");
state.nospace = true;
state.json_write_array();
size_t attrs = 0;
for (auto attribute : layer.second.tilestats) {
if (attrs == elements) {
break;
}
attrs++;
state.nospace = true;
state.json_write_hash();
state.nospace = true;
state.json_write_string("attribute");
state.nospace = true;
state.json_write_string(attribute.first);
size_t val_count = attribute.second.sample_values.size();
if (val_count > max_tilestats_sample_values) {
val_count = max_tilestats_sample_values;
}
state.nospace = true;
state.json_write_string("count");
state.nospace = true;
state.json_write_unsigned(val_count);
int type = 0;
for (auto s : attribute.second.sample_values) {
type |= (1 << s.type);
}
std::string type_str;
// No "null" because null attributes are dropped
if (type == (1 << mvt_double)) {
type_str = "number";
} else if (type == (1 << mvt_bool)) {
type_str = "boolean";
} else if (type == (1 << mvt_string)) {
type_str = "string";
} else {
type_str = "mixed";
}
state.nospace = true;
state.json_write_string("type");
state.nospace = true;
state.json_write_string(type_str);
state.nospace = true;
state.json_write_string("values");
state.nospace = true;
state.json_write_array();
size_t vals = 0;
for (auto value : attribute.second.sample_values) {
if (vals == elements) {
break;
}
if (value.type == mvt_double || value.type == mvt_bool) {
vals++;
state.nospace = true;
state.json_write_stringified(value.s);
} else {
std::string trunc = truncate16(value.s, 256);
if (trunc.size() == value.s.size()) {
vals++;
state.nospace = true;
state.json_write_string(value.s);
}
}
}
state.nospace = true;
state.json_end_array();
if ((type & (1 << mvt_double)) != 0) {
state.nospace = true;
state.json_write_string("min");
state.nospace = true;
state.json_write_number(attribute.second.min);
state.nospace = true;
state.json_write_string("max");
state.nospace = true;
state.json_write_number(attribute.second.max);
}
state.nospace = true;
state.json_end_hash();
}
state.nospace = true;
state.json_end_array();
state.nospace = true;
state.json_end_hash();
}
state.nospace = true;
state.json_end_array();
state.nospace = true;
state.json_end_hash();
}
std::string stringify_strategies(std::vector<strategy> const &strategies) {
std::string out;
json_writer state(&out);
bool any = false;
state.nospace = true;
state.json_write_array();
for (size_t i = 0; i < strategies.size(); i++) {
state.nospace = true;
state.json_write_hash();
if (strategies[i].dropped_by_rate > 0) {
state.nospace = true;
state.json_write_string("dropped_by_rate");
state.nospace = true;
state.json_write_number(strategies[i].dropped_by_rate);
any = true;
}
if (strategies[i].dropped_by_gamma > 0) {
state.nospace = true;
state.json_write_string("dropped_by_gamma");
state.nospace = true;
state.json_write_number(strategies[i].dropped_by_gamma);
any = true;
}
if (strategies[i].dropped_as_needed > 0) {
state.nospace = true;
state.json_write_string("dropped_as_needed");
state.nospace = true;
state.json_write_number(strategies[i].dropped_as_needed);
any = true;
}
if (strategies[i].coalesced_as_needed > 0) {
state.nospace = true;
state.json_write_string("coalesced_as_needed");
state.nospace = true;
state.json_write_number(strategies[i].coalesced_as_needed);
any = true;
}
if (strategies[i].detail_reduced > 0) {
state.nospace = true;
state.json_write_string("detail_reduced");
state.nospace = true;
state.json_write_number(strategies[i].detail_reduced);
any = true;
}
if (strategies[i].tiny_polygons > 0) {
state.nospace = true;
state.json_write_string("tiny_polygons");
state.nospace = true;
state.json_write_number(strategies[i].tiny_polygons);
any = true;
}
if (strategies[i].tile_size > 0) {
state.nospace = true;
state.json_write_string("tile_size_desired");
state.nospace = true;
state.json_write_number(strategies[i].tile_size);
any = true;
}
if (strategies[i].feature_count > 0) {
state.nospace = true;
state.json_write_string("feature_count_desired");
state.nospace = true;
state.json_write_number(strategies[i].feature_count);
any = true;
}
state.nospace = true;
state.json_end_hash();
}
state.nospace = true;
state.json_end_array();
if (any) {
return out;
} else {
return "";
}
}
void mbtiles_write_metadata(sqlite3 *db, const metadata &m, bool forcetable) {
char *sql, *err;
sql = sqlite3_mprintf("INSERT INTO metadata (name, value) VALUES ('name', %Q);", m.name.c_str());
if (sqlite3_exec(db, sql, NULL, NULL, &err) != SQLITE_OK) {
fprintf(stderr, "set name in metadata: %s\n", err);
if (!forcetable) {
exit(EXIT_SQLITE);
}
}
sqlite3_free(sql);
sql = sqlite3_mprintf("INSERT INTO metadata (name, value) VALUES ('description', %Q);", m.description.c_str());
if (sqlite3_exec(db, sql, NULL, NULL, &err) != SQLITE_OK) {
fprintf(stderr, "set description in metadata: %s\n", err);
if (!forcetable) {
exit(EXIT_SQLITE);
}
}
sqlite3_free(sql);
sql = sqlite3_mprintf("INSERT INTO metadata (name, value) VALUES ('version', %d);", m.version);
if (sqlite3_exec(db, sql, NULL, NULL, &err) != SQLITE_OK) {
fprintf(stderr, "set version : %s\n", err);
if (!forcetable) {
exit(EXIT_SQLITE);
}
}
sqlite3_free(sql);
sql = sqlite3_mprintf("INSERT INTO metadata (name, value) VALUES ('minzoom', %d);", m.minzoom);
if (sqlite3_exec(db, sql, NULL, NULL, &err) != SQLITE_OK) {
fprintf(stderr, "set minzoom: %s\n", err);
if (!forcetable) {
exit(EXIT_SQLITE);
}
}
sqlite3_free(sql);
sql = sqlite3_mprintf("INSERT INTO metadata (name, value) VALUES ('maxzoom', %d);", m.maxzoom);
if (sqlite3_exec(db, sql, NULL, NULL, &err) != SQLITE_OK) {
fprintf(stderr, "set maxzoom: %s\n", err);
if (!forcetable) {
exit(EXIT_SQLITE);
}
}
sqlite3_free(sql);
sql = sqlite3_mprintf("INSERT INTO metadata (name, value) VALUES ('center', '%f,%f,%d');", m.center_lon, m.center_lat, m.center_z);
if (sqlite3_exec(db, sql, NULL, NULL, &err) != SQLITE_OK) {
fprintf(stderr, "set center: %s\n", err);
if (!forcetable) {
exit(EXIT_SQLITE);
}
}
sqlite3_free(sql);
sql = sqlite3_mprintf("INSERT INTO metadata (name, value) VALUES ('bounds', '%f,%f,%f,%f');", m.minlon, m.minlat, m.maxlon, m.maxlat);
if (sqlite3_exec(db, sql, NULL, NULL, &err) != SQLITE_OK) {
fprintf(stderr, "set bounds: %s\n", err);
if (!forcetable) {
exit(EXIT_SQLITE);
}
}
sqlite3_free(sql);
sql = sqlite3_mprintf("INSERT INTO metadata (name, value) VALUES ('antimeridian_adjusted_bounds', '%f,%f,%f,%f');", m.minlon2, m.minlat2, m.maxlon2, m.maxlat2);
if (sqlite3_exec(db, sql, NULL, NULL, &err) != SQLITE_OK) {
fprintf(stderr, "set bounds: %s\n", err);
if (!forcetable) {
exit(EXIT_SQLITE);
}
}
sqlite3_free(sql);
sql = sqlite3_mprintf("INSERT INTO metadata (name, value) VALUES ('type', %Q);", m.type.c_str());
if (sqlite3_exec(db, sql, NULL, NULL, &err) != SQLITE_OK) {
fprintf(stderr, "set type: %s\n", err);
if (!forcetable) {
exit(EXIT_SQLITE);
}
}
sqlite3_free(sql);
if (m.attribution.size() > 0) {
sql = sqlite3_mprintf("INSERT INTO metadata (name, value) VALUES ('attribution', %Q);", m.attribution.c_str());
if (sqlite3_exec(db, sql, NULL, NULL, &err) != SQLITE_OK) {
fprintf(stderr, "set attribution: %s\n", err);
if (!forcetable) {
exit(EXIT_SQLITE);
}
}
sqlite3_free(sql);
}
sql = sqlite3_mprintf("INSERT INTO metadata (name, value) VALUES ('format', %Q);", m.format.c_str());
if (sqlite3_exec(db, sql, NULL, NULL, &err) != SQLITE_OK) {
fprintf(stderr, "set format: %s\n", err);
if (!forcetable) {
exit(EXIT_SQLITE);
}
}
sqlite3_free(sql);
sql = sqlite3_mprintf("INSERT INTO metadata (name, value) VALUES ('generator', %Q);", m.generator.c_str());
if (sqlite3_exec(db, sql, NULL, NULL, &err) != SQLITE_OK) {
fprintf(stderr, "set generator: %s\n", err);
if (!forcetable) {
exit(EXIT_SQLITE);
}
}
sqlite3_free(sql);
sql = sqlite3_mprintf("INSERT INTO metadata (name, value) VALUES ('generator_options', %Q);", m.generator_options.c_str());
if (sqlite3_exec(db, sql, NULL, NULL, &err) != SQLITE_OK) {
fprintf(stderr, "set commandline: %s\n", err);
if (!forcetable) {
exit(EXIT_SQLITE);
}
}
sqlite3_free(sql);
if (m.strategies_json.size() > 0) {
sql = sqlite3_mprintf("INSERT INTO metadata (name, value) VALUES ('strategies', %Q);", m.strategies_json.c_str());
if (sqlite3_exec(db, sql, NULL, NULL, &err) != SQLITE_OK) {
fprintf(stderr, "set strategies: %s\n", err);
if (!forcetable) {
exit(EXIT_SQLITE);
}
}
sqlite3_free(sql);
}
if (m.decisions_json.size() > 0) {
sql = sqlite3_mprintf("INSERT INTO metadata (name, value) VALUES ('tippecanoe_decisions', %Q);", m.decisions_json.c_str());
if (sqlite3_exec(db, sql, NULL, NULL, &err) != SQLITE_OK) {
fprintf(stderr, "set decisions: %s\n", err);
if (!forcetable) {
exit(EXIT_SQLITE);
}
}
sqlite3_free(sql);
}
if (m.vector_layers_json.size() > 0 || m.tilestats_json.size() > 0) {
std::string json;
json_writer state(&json);
state.nospace = true;
state.json_write_hash();
if (m.vector_layers_json.size() > 0) {
state.nospace = true;
state.json_write_string("vector_layers");
state.nospace = true;
state.json_write_json(m.vector_layers_json);
if (m.tilestats_json.size() > 0) {
state.nospace = true;
state.json_write_string("tilestats");
state.nospace = true;
state.json_write_json(m.tilestats_json);
}
} else {
if (m.tilestats_json.size() > 0) {
state.nospace = true;
state.json_write_string("tilestats");
state.nospace = true;
state.json_write_json(m.tilestats_json);
}
}
state.nospace = true;
state.json_end_hash();
sql = sqlite3_mprintf("INSERT INTO metadata (name, value) VALUES ('json', %Q);", json.c_str());
if (sqlite3_exec(db, sql, NULL, NULL, &err) != SQLITE_OK) {
fprintf(stderr, "set json: %s\n", err);
if (!forcetable) {
exit(EXIT_SQLITE);
}
}
sqlite3_free(sql);
}
}
static double sixdig(double val) {
return std::round(val * 1e6) / 1e6;
}
metadata make_metadata(const char *fname, int minzoom, int maxzoom, double minlat, double minlon, double maxlat, double maxlon, double minlat2, double minlon2, double maxlat2, double maxlon2, double midlat, double midlon, const char *attribution, std::map<std::string, layermap_entry> const &layermap, bool vector, const char *description, bool do_tilestats, std::map<std::string, std::string> const &attribute_descriptions, std::string const &program, std::string const &commandline, std::vector<strategy> const &strategies, int basezoom, double droprate, int retain_points_multiplier) {
metadata m;
m.name = fname;
m.description = description != NULL ? description : fname;
m.version = 2;
m.type = "overlay";
m.format = vector ? "pbf" : "png";
m.minzoom = minzoom;
m.maxzoom = maxzoom;
m.minlat = sixdig(minlat);
m.minlon = sixdig(minlon);
m.maxlat = sixdig(maxlat);
m.maxlon = sixdig(maxlon);
m.minlat2 = sixdig(minlat2);
m.minlon2 = sixdig(minlon2);
m.maxlat2 = sixdig(maxlat2);
m.maxlon2 = sixdig(maxlon2);
m.center_lat = sixdig(midlat);
m.center_lon = sixdig(midlon);
m.center_z = maxzoom;
if (attribution != NULL) {
m.attribution = attribution;
}
m.generator = program + " " + VERSION;
m.generator_options = commandline;
m.strategies_json = stringify_strategies(strategies);
if (isinf(droprate)) {
droprate = LLONG_MAX;
}
if (basezoom != maxzoom || droprate != 2.5 || retain_points_multiplier != 1) {
m.decisions_json = std::string("{") +
"\"basezoom\":" + milo::dtoa_milo(basezoom) + "," +
"\"droprate\":" + milo::dtoa_milo(droprate) + "," +
"\"retain_points_multiplier\":" + std::to_string(retain_points_multiplier) +
std::string("}");
}
if (vector) {
{
json_writer state(&m.vector_layers_json);
state.nospace = true;
state.json_write_array();
std::vector<std::string> lnames;
for (auto ai = layermap.begin(); ai != layermap.end(); ++ai) {
lnames.push_back(ai->first);
}
for (size_t i = 0; i < lnames.size(); i++) {
auto ts = layermap.find(lnames[i]);
state.nospace = true;
state.json_write_hash();
state.nospace = true;
state.json_write_string("id");
state.nospace = true;
state.json_write_string(lnames[i]);
state.nospace = true;
state.json_write_string("description");
state.nospace = true;
state.json_write_string(ts->second.description);
state.nospace = true;
state.json_write_string("minzoom");
state.nospace = true;
state.json_write_signed(ts->second.minzoom);
state.nospace = true;
state.json_write_string("maxzoom");
state.nospace = true;
state.json_write_signed(ts->second.maxzoom);
state.nospace = true;
state.json_write_string("fields");
state.nospace = true;
state.json_write_hash();
bool first = true;
size_t attribute_count = 0;
for (auto j = ts->second.tilestats.begin(); j != ts->second.tilestats.end(); ++j) {
if (first) {
first = false;
}
state.nospace = true;
state.json_write_string(j->first);
auto f = attribute_descriptions.find(j->first);
if (f == attribute_descriptions.end()) {
int type = 0;
for (auto s : j->second.sample_values) {
type |= (1 << s.type);
}
if (type == (1 << mvt_double)) {
state.nospace = true;
state.json_write_string("Number");
} else if (type == (1 << mvt_bool)) {
state.nospace = true;
state.json_write_string("Boolean");
} else if (type == (1 << mvt_string)) {
state.nospace = true;
state.json_write_string("String");
} else {
state.nospace = true;
state.json_write_string("Mixed");
}
} else {
state.nospace = true;
state.json_write_string(f->second);
}
attribute_count++;
if (attribute_count >= max_tilestats_attributes) {
break;
}
}
state.nospace = true;
state.json_end_hash();
state.nospace = true;
state.json_end_hash();
}
state.nospace = true;
state.json_end_array();
}
{
size_t elements = max_tilestats_values;
json_writer state(&m.tilestats_json);
if (do_tilestats && elements > 0) {
tilestats(layermap, elements, state);
}
}
}
return m;
}
void mbtiles_close(sqlite3 *outdb, const char *pgm) {
char *err;
if (sqlite3_exec(outdb, "ANALYZE;", NULL, NULL, &err) != SQLITE_OK) {
fprintf(stderr, "%s: ANALYZE failed: %s\n", pgm, err);
exit(EXIT_SQLITE);
}
if (sqlite3_close(outdb) != SQLITE_OK) {
fprintf(stderr, "%s: could not close database: %s\n", pgm, sqlite3_errmsg(outdb));
exit(EXIT_CLOSE);
}
}
std::map<std::string, layermap_entry> merge_layermaps(std::vector<std::map<std::string, layermap_entry>> const &maps) {
return merge_layermaps(maps, false);
}
std::map<std::string, layermap_entry> merge_layermaps(std::vector<std::map<std::string, layermap_entry>> const &maps, bool trunc) {
std::map<std::string, layermap_entry> out;
for (size_t i = 0; i < maps.size(); i++) {
for (auto map = maps[i].begin(); map != maps[i].end(); ++map) {
if (map->second.points + map->second.lines + map->second.polygons + map->second.retain == 0) {
continue;
}
std::string layername = map->first;
if (trunc) {
layername = truncate16(layername, 256);
}
if (out.count(layername) == 0) {
out.insert(std::pair<std::string, layermap_entry>(layername, layermap_entry(out.size())));
auto out_entry = out.find(layername);
out_entry->second.minzoom = map->second.minzoom;
out_entry->second.maxzoom = map->second.maxzoom;
out_entry->second.description = map->second.description;
}
auto out_entry = out.find(layername);
if (out_entry == out.end()) {
fprintf(stderr, "Internal error merging layers\n");
exit(EXIT_IMPOSSIBLE);
}
for (auto ts = map->second.tilestats.begin(); ts != map->second.tilestats.end(); ++ts) {
std::string attribname = ts->first;
if (trunc) {
attribname = truncate16(attribname, 256);
}
auto ts2 = out_entry->second.tilestats.find(attribname);
if (ts2 == out_entry->second.tilestats.end()) {
out_entry->second.tilestats.insert(std::pair<std::string, tilestat>(attribname, ts->second));
} else {
for (auto val : ts->second.sample_values) {
auto pt = std::lower_bound(ts2->second.sample_values.begin(), ts2->second.sample_values.end(), val);
if (pt == ts2->second.sample_values.end() || *pt != val) { // not found
ts2->second.sample_values.insert(pt, val);
if (ts2->second.sample_values.size() > max_tilestats_sample_values) {
ts2->second.sample_values.pop_back();
}
}
}
ts2->second.type |= ts->second.type;
if (ts->second.min < ts2->second.min) {
ts2->second.min = ts->second.min;
}
if (ts->second.max > ts2->second.max) {
ts2->second.max = ts->second.max;
}
}
}
if (map->second.minzoom < out_entry->second.minzoom) {
out_entry->second.minzoom = map->second.minzoom;
}
if (map->second.maxzoom > out_entry->second.maxzoom) {
out_entry->second.maxzoom = map->second.maxzoom;
}
out_entry->second.points += map->second.points;
out_entry->second.lines += map->second.lines;
out_entry->second.polygons += map->second.polygons;
}
}
return out;
}
void add_to_tilestats(std::map<std::string, tilestat> &tilestats, std::string const &attrib, serial_val const &val) {
if (val.type == mvt_null) {
return;
}
auto tsa = tilestats.find(attrib);
if (tsa == tilestats.end()) {
tilestats.insert(std::pair<std::string, tilestat>(attrib, tilestat()));
tsa = tilestats.find(attrib);
}
if (tsa == tilestats.end()) {
fprintf(stderr, "Can't happen (tilestats)\n");
exit(EXIT_IMPOSSIBLE);
}
if (val.type == mvt_double) {
double d = atof(val.s.c_str());
if (d < tsa->second.min) {
tsa->second.min = d;
}
if (d > tsa->second.max) {
tsa->second.max = d;
}
}
auto pt = std::lower_bound(tsa->second.sample_values.begin(), tsa->second.sample_values.end(), val);
if (pt == tsa->second.sample_values.end() || *pt != val) { // not found
if (tsa->second.sample_values.size() >= max_tilestats_sample_values) {
if (pt == tsa->second.sample_values.end()) {
// insertion point would be at the end,
// and the list is already full, so do nothing
} else {
// bump the former last value, insert this one
tsa->second.sample_values.insert(pt, val);
tsa->second.sample_values.pop_back();
}
} else {
tsa->second.sample_values.insert(pt, val);
}
}
tsa->second.type |= (1 << val.type);
}