mirror of
https://github.com/felt/tippecanoe.git
synced 2026-10-03 00:45:41 +02:00
* Add a way to run tippecanoe single-threaded for profiling * Do less work when the tilestats sample values list is already full * Save a copy when retrieving the attribute key * Fewer atomic operations * Move string hashing from mbtiles to text * Only do approximate attribute deduplication when writing tiles * Feature dropping tests are sensitive to exact tile size * All tile creators now create a string pool for the tile * Features clipped away to nothing should not participate in that tile * Revert "Only do approximate attribute deduplication when writing tiles" This reverts commitc42b34b498. * Also revert the related test changes * Revert "Revert "Only do approximate attribute deduplication when writing tiles"" This reverts commit18509876c3. * Be more specific about the string hash function * Use fnv1a instead of std::hash for everything * Reduce the chance of hash collisions * Stick a hash search on the front of the tree search in addpool * Eliminate repeated hashing of the same string * Switch instead of ifs in json parsing * A few more cases to populate the hash in addpool * Store the hash in the tree instead of recalculating * Add explanatory comment for mysterious argument * Fewer copies in attribute stringification * Clean up ancient weirdness in JSON attribute stringification * More serial_val cleanup * Pass a serial_feature to rewrite instead of many broken-down arguments * Get rid of the multiple geometries within `partial` * Revert "Pass a serial_feature to rewrite instead of many broken-down arguments" This reverts commit6f4ab9b725. * Goodbye, struct coalesce * Revert "Features clipped away to nothing should not participate in that tile" This reverts commit124462fbdc. * Migrating fields from partial to serial_feature * Name reconciliation between serial_feature and partial * Replace struct partial with an augmented serial_feature * Fix some overzealous search-and-replace renaming * Don't say struct so often * Remove more of the former partial construction * Commenting and cleaning up * Trying again to avoid all these arguments to rewrite * I swear I did this same thing before and it didn't work. * More rewrite cleanup * Exile --detect-shared-borders to its own file * Add missing headers * More commenting and cleanup * More comments * Sprinkle consts around * Emplacing and std::moving * More cleanup * That shouldn't have worked after a std::move * Don't need to allocate memory to compare keys * Reduce use of the global string pool in tiling * Another avoidable mvt_value construction * Further reduction to explicit string pool passing * These reverses are no longer optimizations * These layernames can all be references * Don't drag an unused layername string around with every feature * Heed a compiler warning about potential buffer overflow * Fix my confusion about which feature's string pool is relevant * Avoid some unnecessary allocations in attribute accumulation * Maybe faster serialization? * Eliminate a comparison * Do the same here * Save a couple of allocations when parsing numbers in JSON * Immediately assign features to layers instead of subdividing later * Maintain tilestats for tippecanoe:retain_points_multiplier_sequence * Crunch out more duplicate attribute values when writing out the tile * Do tilestats for tippecanoe:retain_points_multiplier_first too * Shell filters need to be real threads, even if nothing else does * Simplify tippecanoe_minzoom/maxzoom representation * Update version and changelog
584 lines
13 KiB
C++
584 lines
13 KiB
C++
#include <stdio.h>
|
|
#include <string>
|
|
#include <limits.h>
|
|
#include <pthread.h>
|
|
#include "mvt.hpp"
|
|
#include "serial.hpp"
|
|
#include "geobuf.hpp"
|
|
#include "geojson.hpp"
|
|
#include "projection.hpp"
|
|
#include "main.hpp"
|
|
#include "protozero/varint.hpp"
|
|
#include "protozero/pbf_reader.hpp"
|
|
#include "protozero/pbf_writer.hpp"
|
|
#include "milo/dtoa_milo.h"
|
|
#include "jsonpull/jsonpull.h"
|
|
#include "text.hpp"
|
|
#include "errors.hpp"
|
|
#include "thread.hpp"
|
|
|
|
#define POINT 0
|
|
#define MULTIPOINT 1
|
|
#define LINESTRING 2
|
|
#define MULTILINESTRING 3
|
|
#define POLYGON 4
|
|
#define MULTIPOLYGON 5
|
|
|
|
struct queued_feature {
|
|
protozero::pbf_reader pbf{};
|
|
size_t dim = 0;
|
|
double e = 0;
|
|
std::vector<std::string> *keys = NULL;
|
|
std::vector<struct serialization_state> *sst = NULL;
|
|
int layer = 0;
|
|
std::string layername = "";
|
|
};
|
|
|
|
static std::vector<queued_feature> feature_queue;
|
|
|
|
void ensureDim(size_t dim) {
|
|
if (dim < 2) {
|
|
fprintf(stderr, "Geometry has fewer than 2 dimensions: %zu\n", dim);
|
|
exit(EXIT_IMPOSSIBLE);
|
|
}
|
|
}
|
|
|
|
serial_val readValue(protozero::pbf_reader &pbf) {
|
|
serial_val sv;
|
|
sv.type = mvt_null;
|
|
sv.s = "null";
|
|
|
|
while (pbf.next()) {
|
|
switch (pbf.tag()) {
|
|
case 1:
|
|
sv.type = mvt_string;
|
|
sv.s = pbf.get_string();
|
|
break;
|
|
|
|
case 2:
|
|
sv.type = mvt_double;
|
|
sv.s = milo::dtoa_milo(pbf.get_double());
|
|
break;
|
|
|
|
case 3:
|
|
sv.type = mvt_double;
|
|
sv.s = std::to_string(pbf.get_uint64());
|
|
break;
|
|
|
|
case 4:
|
|
sv.type = mvt_double;
|
|
sv.s = std::to_string(-(long long) pbf.get_uint64());
|
|
break;
|
|
|
|
case 5:
|
|
sv.type = mvt_bool;
|
|
if (pbf.get_bool()) {
|
|
sv.s = "true";
|
|
} else {
|
|
sv.s = "false";
|
|
}
|
|
break;
|
|
|
|
case 6:
|
|
sv.type = mvt_string; // stringified JSON
|
|
sv.s = pbf.get_string();
|
|
|
|
if (sv.s == "null") {
|
|
sv.type = mvt_null;
|
|
}
|
|
|
|
break;
|
|
|
|
default:
|
|
pbf.skip();
|
|
}
|
|
}
|
|
|
|
return sv;
|
|
}
|
|
|
|
drawvec readPoint(std::vector<long long> &coords, size_t dim, double e) {
|
|
ensureDim(dim);
|
|
|
|
long long x, y;
|
|
projection->project(coords[0] / e, coords[1] / e, 32, &x, &y);
|
|
drawvec dv;
|
|
dv.push_back(draw(VT_MOVETO, x, y));
|
|
return dv;
|
|
}
|
|
|
|
drawvec readLinePart(std::vector<long long> &coords, size_t dim, double e, size_t start, size_t end, bool closed) {
|
|
ensureDim(dim);
|
|
|
|
drawvec dv;
|
|
std::vector<long long> prev;
|
|
std::vector<double> p;
|
|
prev.resize(dim);
|
|
p.resize(dim);
|
|
|
|
for (size_t i = start; i + dim - 1 < end; i += dim) {
|
|
if (i + dim - 1 >= coords.size()) {
|
|
fprintf(stderr, "Internal error: line segment %zu vs %zu\n", i + dim - 1, coords.size());
|
|
exit(EXIT_IMPOSSIBLE);
|
|
}
|
|
|
|
for (size_t d = 0; d < dim; d++) {
|
|
prev[d] += coords[i + d];
|
|
p[d] = prev[d] / e;
|
|
}
|
|
|
|
long long x, y;
|
|
projection->project(p[0], p[1], 32, &x, &y);
|
|
|
|
if (i == start) {
|
|
dv.push_back(draw(VT_MOVETO, x, y));
|
|
} else {
|
|
dv.push_back(draw(VT_LINETO, x, y));
|
|
}
|
|
}
|
|
|
|
if (closed && dv.size() > 0) {
|
|
dv.push_back(draw(VT_LINETO, dv[0].x, dv[0].y));
|
|
}
|
|
|
|
return dv;
|
|
}
|
|
|
|
drawvec readLine(std::vector<long long> &coords, size_t dim, double e, bool closed) {
|
|
return readLinePart(coords, dim, e, 0, coords.size(), closed);
|
|
}
|
|
|
|
drawvec readMultiLine(std::vector<long long> &coords, std::vector<int> &lengths, size_t dim, double e, bool closed) {
|
|
if (lengths.size() == 0) {
|
|
return readLinePart(coords, dim, e, 0, coords.size(), closed);
|
|
}
|
|
|
|
drawvec dv;
|
|
size_t here = 0;
|
|
for (size_t i = 0; i < lengths.size(); i++) {
|
|
drawvec dv2 = readLinePart(coords, dim, e, here, here + lengths[i] * dim, closed);
|
|
here += lengths[i] * dim;
|
|
|
|
for (size_t j = 0; j < dv2.size(); j++) {
|
|
dv.push_back(dv2[j]);
|
|
}
|
|
}
|
|
|
|
return dv;
|
|
}
|
|
|
|
drawvec readMultiPolygon(std::vector<long long> &coords, std::vector<int> &lengths, size_t dim, double e) {
|
|
ensureDim(dim);
|
|
|
|
if (lengths.size() == 0) {
|
|
return readLinePart(coords, dim, e, 0, coords.size(), true);
|
|
}
|
|
|
|
size_t polys = lengths[0];
|
|
size_t n = 1;
|
|
size_t here = 0;
|
|
drawvec dv;
|
|
|
|
for (size_t i = 0; i < polys; i++) {
|
|
size_t rings = lengths[n++];
|
|
|
|
for (size_t j = 0; j < rings; j++) {
|
|
drawvec dv2 = readLinePart(coords, dim, e, here, here + lengths[n] * dim, true);
|
|
here += lengths[n] * dim;
|
|
n++;
|
|
|
|
for (size_t k = 0; k < dv2.size(); k++) {
|
|
dv.push_back(dv2[k]);
|
|
}
|
|
}
|
|
|
|
dv.push_back(draw(VT_CLOSEPATH, 0, 0)); // mark that the next ring is outer
|
|
}
|
|
|
|
return dv;
|
|
}
|
|
|
|
struct drawvec_type {
|
|
drawvec dv{};
|
|
int type = 0;
|
|
};
|
|
|
|
std::vector<drawvec_type> readGeometry(protozero::pbf_reader &pbf, size_t dim, double e, std::vector<std::string> &keys) {
|
|
std::vector<drawvec_type> ret;
|
|
std::vector<long long> coords;
|
|
std::vector<int> lengths;
|
|
int type = -1;
|
|
|
|
while (pbf.next()) {
|
|
switch (pbf.tag()) {
|
|
case 1:
|
|
type = pbf.get_enum();
|
|
break;
|
|
|
|
case 2: {
|
|
auto pi = pbf.get_packed_uint32();
|
|
for (auto it = pi.first; it != pi.second; ++it) {
|
|
lengths.push_back(*it);
|
|
}
|
|
break;
|
|
}
|
|
|
|
case 3: {
|
|
auto pi = pbf.get_packed_sint64();
|
|
for (auto it = pi.first; it != pi.second; ++it) {
|
|
coords.push_back(*it);
|
|
}
|
|
break;
|
|
}
|
|
|
|
case 4: {
|
|
protozero::pbf_reader geometry_reader(pbf.get_message());
|
|
std::vector<drawvec_type> dv2 = readGeometry(geometry_reader, dim, e, keys);
|
|
|
|
for (size_t i = 0; i < dv2.size(); i++) {
|
|
ret.push_back(dv2[i]);
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
default:
|
|
pbf.skip();
|
|
}
|
|
}
|
|
|
|
drawvec_type dv;
|
|
if (type == POINT) {
|
|
dv.dv = readPoint(coords, dim, e);
|
|
} else if (type == MULTIPOINT) {
|
|
dv.dv = readLine(coords, dim, e, false);
|
|
} else if (type == LINESTRING) {
|
|
dv.dv = readLine(coords, dim, e, false);
|
|
} else if (type == POLYGON) {
|
|
dv.dv = readMultiLine(coords, lengths, dim, e, true);
|
|
} else if (type == MULTILINESTRING) {
|
|
dv.dv = readMultiLine(coords, lengths, dim, e, false);
|
|
} else if (type == MULTIPOLYGON) {
|
|
dv.dv = readMultiPolygon(coords, lengths, dim, e);
|
|
} else {
|
|
// GeometryCollection
|
|
return ret;
|
|
}
|
|
|
|
dv.type = type / 2 + 1;
|
|
ret.push_back(dv);
|
|
return ret;
|
|
}
|
|
|
|
void readFeature(protozero::pbf_reader &pbf, size_t dim, double e, std::vector<std::string> &keys, struct serialization_state *sst, int layer, std::string layername) {
|
|
std::vector<drawvec_type> dv;
|
|
long long id = 0;
|
|
bool has_id = false;
|
|
std::vector<serial_val> values;
|
|
std::map<std::string, serial_val> other;
|
|
|
|
std::vector<std::string> full_keys;
|
|
std::vector<serial_val> full_values;
|
|
|
|
while (pbf.next()) {
|
|
switch (pbf.tag()) {
|
|
case 1: {
|
|
protozero::pbf_reader geometry_reader(pbf.get_message());
|
|
std::vector<drawvec_type> dv2 = readGeometry(geometry_reader, dim, e, keys);
|
|
for (size_t i = 0; i < dv2.size(); i++) {
|
|
dv.push_back(dv2[i]);
|
|
}
|
|
break;
|
|
}
|
|
|
|
case 11: {
|
|
static bool warned = false;
|
|
if (!warned) {
|
|
fprintf(stderr, "Non-numeric feature IDs not supported\n");
|
|
warned = true;
|
|
}
|
|
pbf.skip();
|
|
break;
|
|
}
|
|
|
|
case 12:
|
|
has_id = true;
|
|
id = pbf.get_sint64();
|
|
if (id < 0) {
|
|
static bool warned = false;
|
|
if (!warned) {
|
|
fprintf(stderr, "Out of range feature id %lld\n", id);
|
|
warned = true;
|
|
}
|
|
has_id = false;
|
|
}
|
|
break;
|
|
|
|
case 13: {
|
|
protozero::pbf_reader value_reader(pbf.get_message());
|
|
values.push_back(readValue(value_reader));
|
|
break;
|
|
}
|
|
|
|
case 14: {
|
|
std::vector<size_t> properties;
|
|
auto pi = pbf.get_packed_uint32();
|
|
for (auto it = pi.first; it != pi.second; ++it) {
|
|
properties.push_back(*it);
|
|
}
|
|
|
|
for (size_t i = 0; i + 1 < properties.size(); i += 2) {
|
|
if (properties[i] >= keys.size()) {
|
|
fprintf(stderr, "Out of bounds key: %zu in %zu\n", properties[i], keys.size());
|
|
exit(EXIT_IMPOSSIBLE);
|
|
}
|
|
|
|
if (properties[i + 1] >= values.size()) {
|
|
fprintf(stderr, "Out of bounds value: %zu in %zu\n", properties[i + 1], values.size());
|
|
exit(EXIT_IMPOSSIBLE);
|
|
}
|
|
|
|
full_keys.push_back(keys[properties[i]]);
|
|
full_values.push_back(values[properties[i + 1]]);
|
|
}
|
|
|
|
values.clear();
|
|
break;
|
|
}
|
|
|
|
case 15: {
|
|
std::vector<size_t> misc;
|
|
auto pi = pbf.get_packed_uint32();
|
|
for (auto it = pi.first; it != pi.second; ++it) {
|
|
misc.push_back(*it);
|
|
}
|
|
|
|
for (size_t i = 0; i + 1 < misc.size(); i += 2) {
|
|
if (misc[i] >= keys.size()) {
|
|
fprintf(stderr, "Out of bounds key: %zu in %zu\n", misc[i], keys.size());
|
|
exit(EXIT_IMPOSSIBLE);
|
|
}
|
|
|
|
if (misc[i + 1] >= values.size()) {
|
|
fprintf(stderr, "Out of bounds value: %zu in %zu\n", misc[i + 1], values.size());
|
|
exit(EXIT_IMPOSSIBLE);
|
|
}
|
|
|
|
other.insert(std::pair<std::string, serial_val>(keys[misc[i]], values[misc[i + 1]]));
|
|
}
|
|
|
|
values.clear();
|
|
break;
|
|
}
|
|
|
|
default:
|
|
pbf.skip();
|
|
}
|
|
}
|
|
|
|
for (size_t i = 0; i < dv.size(); i++) {
|
|
serial_feature sf;
|
|
|
|
sf.layer = layer;
|
|
sf.segment = sst->segment;
|
|
sf.has_id = has_id;
|
|
sf.id = id;
|
|
sf.tippecanoe_minzoom = -1;
|
|
sf.tippecanoe_maxzoom = -1;
|
|
sf.feature_minzoom = false;
|
|
sf.seq = *(sst->layer_seq);
|
|
sf.geometry = dv[i].dv;
|
|
sf.t = dv[i].type;
|
|
sf.full_keys = full_keys;
|
|
sf.full_values = full_values;
|
|
|
|
auto tip = other.find("tippecanoe");
|
|
if (tip != other.end()) {
|
|
json_pull *jp = json_begin_string(tip->second.s.c_str());
|
|
json_object *o = json_read_tree(jp);
|
|
|
|
if (o != NULL) {
|
|
json_object *min = json_hash_get(o, "minzoom");
|
|
if (min != NULL && (min->type == JSON_NUMBER)) {
|
|
sf.tippecanoe_minzoom = integer_zoom(sst->fname, milo::dtoa_milo(min->value.number.number));
|
|
}
|
|
|
|
json_object *max = json_hash_get(o, "maxzoom");
|
|
if (max != NULL && (max->type == JSON_NUMBER)) {
|
|
sf.tippecanoe_maxzoom = integer_zoom(sst->fname, milo::dtoa_milo(max->value.number.number));
|
|
}
|
|
|
|
json_object *tlayer = json_hash_get(o, "layer");
|
|
if (tlayer != NULL && (tlayer->type == JSON_STRING)) {
|
|
layername = tlayer->value.string.string;
|
|
}
|
|
}
|
|
|
|
json_free(o);
|
|
json_end(jp);
|
|
}
|
|
|
|
serialize_feature(sst, sf, layername);
|
|
}
|
|
}
|
|
|
|
struct queue_run_arg {
|
|
size_t start;
|
|
size_t end;
|
|
size_t segment;
|
|
|
|
queue_run_arg(size_t start1, size_t end1, size_t segment1)
|
|
: start(start1), end(end1), segment(segment1) {
|
|
}
|
|
};
|
|
|
|
void *run_parse_feature(void *v) {
|
|
struct queue_run_arg *qra = (struct queue_run_arg *) v;
|
|
|
|
for (size_t i = qra->start; i < qra->end; i++) {
|
|
struct queued_feature &qf = feature_queue[i];
|
|
readFeature(qf.pbf, qf.dim, qf.e, *qf.keys, &(*qf.sst)[qra->segment], qf.layer, qf.layername);
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
void runQueue() {
|
|
if (feature_queue.size() == 0) {
|
|
return;
|
|
}
|
|
|
|
std::vector<struct queue_run_arg> qra;
|
|
|
|
std::vector<pthread_t> pthreads;
|
|
pthreads.resize(CPUS);
|
|
|
|
for (size_t i = 0; i < CPUS; i++) {
|
|
*((*(feature_queue[0].sst))[i].layer_seq) = *((*(feature_queue[0].sst))[0].layer_seq) + feature_queue.size() * i / CPUS;
|
|
|
|
qra.push_back(queue_run_arg(
|
|
feature_queue.size() * i / CPUS,
|
|
feature_queue.size() * (i + 1) / CPUS,
|
|
i));
|
|
}
|
|
|
|
for (size_t i = 0; i < CPUS; i++) {
|
|
if (thread_create(&pthreads[i], NULL, run_parse_feature, &qra[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");
|
|
}
|
|
}
|
|
|
|
// Lack of atomicity is OK, since we are single-threaded again here
|
|
long long was = *((*(feature_queue[0].sst))[CPUS - 1].layer_seq);
|
|
*((*(feature_queue[0].sst))[0].layer_seq) = was;
|
|
feature_queue.clear();
|
|
}
|
|
|
|
void queueFeature(protozero::pbf_reader &pbf, size_t dim, double e, std::vector<std::string> &keys, std::vector<struct serialization_state> *sst, int layer, std::string layername) {
|
|
struct queued_feature qf;
|
|
qf.pbf = pbf;
|
|
qf.dim = dim;
|
|
qf.e = e;
|
|
qf.keys = &keys;
|
|
qf.sst = sst;
|
|
qf.layer = layer;
|
|
qf.layername = layername;
|
|
|
|
feature_queue.push_back(qf);
|
|
|
|
if (feature_queue.size() > CPUS * 500) {
|
|
runQueue();
|
|
}
|
|
}
|
|
|
|
void outBareGeometry(drawvec const &dv, int type, struct serialization_state *sst, int layer, std::string layername) {
|
|
serial_feature sf;
|
|
|
|
sf.layer = layer;
|
|
sf.segment = sst->segment;
|
|
sf.has_id = false;
|
|
sf.tippecanoe_minzoom = -1;
|
|
sf.tippecanoe_maxzoom = -1;
|
|
sf.feature_minzoom = false;
|
|
sf.seq = (*sst->layer_seq);
|
|
sf.geometry = dv;
|
|
sf.t = type;
|
|
|
|
serialize_feature(sst, sf, layername);
|
|
}
|
|
|
|
void readFeatureCollection(protozero::pbf_reader &pbf, size_t dim, double e, std::vector<std::string> &keys, std::vector<struct serialization_state> *sst, int layer, std::string layername) {
|
|
while (pbf.next()) {
|
|
switch (pbf.tag()) {
|
|
case 1: {
|
|
protozero::pbf_reader feature_reader(pbf.get_message());
|
|
queueFeature(feature_reader, dim, e, keys, sst, layer, layername);
|
|
break;
|
|
}
|
|
|
|
default:
|
|
pbf.skip();
|
|
}
|
|
}
|
|
}
|
|
|
|
void parse_geobuf(std::vector<struct serialization_state> *sst, const char *src, size_t len, int layer, std::string layername) {
|
|
protozero::pbf_reader pbf(src, len);
|
|
|
|
size_t dim = 2;
|
|
double e = 1e6;
|
|
std::vector<std::string> keys;
|
|
|
|
while (pbf.next()) {
|
|
switch (pbf.tag()) {
|
|
case 1:
|
|
keys.push_back(pbf.get_string());
|
|
break;
|
|
|
|
case 2:
|
|
dim = pbf.get_int64();
|
|
break;
|
|
|
|
case 3:
|
|
e = pow(10, pbf.get_int64());
|
|
break;
|
|
|
|
case 4: {
|
|
protozero::pbf_reader feature_collection_reader(pbf.get_message());
|
|
readFeatureCollection(feature_collection_reader, dim, e, keys, sst, layer, layername);
|
|
break;
|
|
}
|
|
|
|
case 5: {
|
|
protozero::pbf_reader feature_reader(pbf.get_message());
|
|
queueFeature(feature_reader, dim, e, keys, sst, layer, layername);
|
|
break;
|
|
}
|
|
|
|
case 6: {
|
|
protozero::pbf_reader geometry_reader(pbf.get_message());
|
|
std::vector<drawvec_type> dv = readGeometry(geometry_reader, dim, e, keys);
|
|
for (size_t i = 0; i < dv.size(); i++) {
|
|
// Always on thread 0
|
|
outBareGeometry(dv[i].dv, dv[i].type, &(*sst)[0], layer, layername);
|
|
}
|
|
break;
|
|
}
|
|
|
|
default:
|
|
pbf.skip();
|
|
}
|
|
}
|
|
|
|
runQueue();
|
|
}
|