mirror of
https://github.com/felt/tippecanoe.git
synced 2026-10-02 16:35:40 +02:00
The anchor point that `--convert-polygons-to-label-points` computes was stored in `serial_feature` as a single `unsigned long long`, the Morton index of two *unsigned 32-bit* coordinates. That representation cost two things: * `SHIFT_LEFT` can legitimately put an anchor outside the [0, 2^32) plane -- that is exactly what happens for a feature reaching into a tile's buffer -- but `encode_index` only takes 32-bit unsigned values, so the anchor was masked with `& ((1LL << 32) - 1)` on the way in. An anchor just west or north of the plane wrapped around to the far edge of the world rather than staying where it was. This is most visible in the buffer of the z0 tile, whose own bounds are the whole plane, so every buffer anchor is out of range. * Zero was overloaded as "no anchor," but zero is also the index of the legitimate world coordinate (0, 0). Store the anchor as a pair of signed `long long` coordinates with an explicit `has_label_point` flag instead. The serialized form grows from one varint to two, both zigzag-encoded so negative coordinates round-trip; this is the internal geometry temp file, not tile output, so nothing external depends on the layout. `write_tile` now checks the flag rather than relying on the sentinel, and drops the feature when there is no anchor instead of quietly tiling one at (0, 0). `checkerboard_anchors` gains the tile buffer as a parameter and short-circuits: if the real anchor lies within the tile plus its buffer, emit it directly rather than searching the repeating checkerboard for a substitute. Computing `ty2` is what makes that test possible -- it had been commented out because only `tx2` was needed for the label spacing. The one place this changes test output is tests/ne_110m_admin_0_countries/out/-z2_--convert-polygons-to-label-points.json, where Antarctica's anchors move. That fixture is regenerated here. CHANGELOG and version follow this repo's conventions: an entry under a new 2.81.1 heading, patch rather than minor since this only fixes behavior. Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NcSXLjtokxioaBCCwGTEv1
964 lines
28 KiB
C++
964 lines
28 KiB
C++
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <errno.h>
|
|
#include <string>
|
|
#include <vector>
|
|
#include <sqlite3.h>
|
|
#include <set>
|
|
#include <map>
|
|
#include <algorithm>
|
|
#include <limits.h>
|
|
#include <zlib.h>
|
|
#include "protozero/varint.hpp"
|
|
#include "geometry.hpp"
|
|
#include "mbtiles.hpp"
|
|
#include "mvt.hpp"
|
|
#include "tile.hpp"
|
|
#include "serial.hpp"
|
|
#include "options.hpp"
|
|
#include "main.hpp"
|
|
#include "pool.hpp"
|
|
#include "projection.hpp"
|
|
#include "evaluator.hpp"
|
|
#include "milo/dtoa_milo.h"
|
|
#include "errors.hpp"
|
|
#include "text.hpp"
|
|
|
|
// Offset coordinates to keep them positive
|
|
#define COORD_OFFSET (4LL << 32)
|
|
#define SHIFT_RIGHT(a) ((long long) std::round((double) (a) / (1LL << geometry_scale)))
|
|
#define SHIFT_LEFT(a) ((((a) + (COORD_OFFSET >> geometry_scale)) << geometry_scale) - COORD_OFFSET)
|
|
|
|
// write to file
|
|
|
|
size_t fwrite_check(const void *ptr, size_t size, size_t nitems, FILE *stream, std::atomic<long long> *fpos, const char *fname) {
|
|
size_t w = fwrite(ptr, size, nitems, stream);
|
|
if (w != nitems) {
|
|
fprintf(stderr, "%s: Write to temporary file failed: %s\n", fname, strerror(errno));
|
|
exit(EXIT_WRITE);
|
|
}
|
|
*fpos += size * nitems;
|
|
return w;
|
|
}
|
|
|
|
void serialize_int(FILE *out, int n, std::atomic<long long> *fpos, const char *fname) {
|
|
serialize_long_long(out, n, fpos, fname);
|
|
}
|
|
|
|
void serialize_long_long(FILE *out, long long n, std::atomic<long long> *fpos, const char *fname) {
|
|
unsigned long long zigzag = protozero::encode_zigzag64(n);
|
|
|
|
serialize_ulong_long(out, zigzag, fpos, fname);
|
|
}
|
|
|
|
void serialize_ulong_long(FILE *out, unsigned long long zigzag, std::atomic<long long> *fpos, const char *fname) {
|
|
char buf[10]; // ceil(64 / 7)
|
|
char *s = buf;
|
|
|
|
while (zigzag >= 0x80) {
|
|
*s++ = (zigzag & 0x7F) | 0x80;
|
|
zigzag >>= 7;
|
|
}
|
|
|
|
*s++ = zigzag;
|
|
fwrite_check(buf, sizeof(char), s - buf, out, fpos, fname);
|
|
}
|
|
|
|
void serialize_byte(FILE *out, signed char n, std::atomic<long long> *fpos, const char *fname) {
|
|
fwrite_check(&n, sizeof(signed char), 1, out, fpos, fname);
|
|
}
|
|
|
|
void serialize_uint(FILE *out, unsigned n, std::atomic<long long> *fpos, const char *fname) {
|
|
serialize_ulong_long(out, n, fpos, fname);
|
|
}
|
|
|
|
// write to memory
|
|
|
|
size_t fwrite_check(const void *ptr, size_t size, size_t nitems, std::string &stream) {
|
|
stream += std::string((char *) ptr, size * nitems);
|
|
return nitems;
|
|
}
|
|
|
|
void serialize_ulong_long(std::string &out, unsigned long long zigzag) {
|
|
char buf[10]; // ceil(64 / 7)
|
|
char *s = buf;
|
|
|
|
while (zigzag >= 0x80) {
|
|
*s++ = (zigzag & 0x7F) | 0x80;
|
|
zigzag >>= 7;
|
|
}
|
|
|
|
*s++ = zigzag;
|
|
out.append(buf, s - buf);
|
|
}
|
|
|
|
void serialize_long_long(std::string &out, long long n) {
|
|
unsigned long long zigzag = protozero::encode_zigzag64(n);
|
|
|
|
serialize_ulong_long(out, zigzag);
|
|
}
|
|
|
|
void serialize_int(std::string &out, int n) {
|
|
serialize_long_long(out, n);
|
|
}
|
|
|
|
void serialize_byte(std::string &out, signed char n) {
|
|
out += n;
|
|
}
|
|
|
|
void serialize_uint(std::string &out, unsigned n) {
|
|
serialize_ulong_long(out, n);
|
|
}
|
|
|
|
// read from memory
|
|
|
|
void deserialize_int(const char **f, int *n) {
|
|
long long ll;
|
|
deserialize_long_long(f, &ll);
|
|
*n = ll;
|
|
}
|
|
|
|
void deserialize_long_long(const char **f, long long *n) {
|
|
unsigned long long zigzag = 0;
|
|
deserialize_ulong_long(f, &zigzag);
|
|
*n = protozero::decode_zigzag64(zigzag);
|
|
}
|
|
|
|
void deserialize_ulong_long(const char **f, unsigned long long *zigzag) {
|
|
*zigzag = 0;
|
|
int shift = 0;
|
|
|
|
while (1) {
|
|
if ((**f & 0x80) == 0) {
|
|
*zigzag |= ((unsigned long long) **f) << shift;
|
|
*f += 1;
|
|
shift += 7;
|
|
break;
|
|
} else {
|
|
*zigzag |= ((unsigned long long) (**f & 0x7F)) << shift;
|
|
*f += 1;
|
|
shift += 7;
|
|
}
|
|
}
|
|
}
|
|
|
|
void deserialize_uint(const char **f, unsigned *n) {
|
|
unsigned long long v;
|
|
deserialize_ulong_long(f, &v);
|
|
*n = v;
|
|
}
|
|
|
|
void deserialize_byte(const char **f, signed char *n) {
|
|
memcpy(n, *f, sizeof(signed char));
|
|
*f += sizeof(signed char);
|
|
}
|
|
|
|
static void write_geometry(drawvec const &dv, std::string &out, long long wx, long long wy) {
|
|
for (size_t i = 0; i < dv.size(); i++) {
|
|
if (dv[i].op == VT_MOVETO || dv[i].op == VT_LINETO) {
|
|
serialize_byte(out, dv[i].op);
|
|
serialize_long_long(out, dv[i].x - wx);
|
|
serialize_long_long(out, dv[i].y - wy);
|
|
wx = dv[i].x;
|
|
wy = dv[i].y;
|
|
} else {
|
|
serialize_byte(out, dv[i].op);
|
|
}
|
|
}
|
|
serialize_byte(out, VT_END);
|
|
}
|
|
|
|
// called from generating the next zoom level
|
|
std::string serialize_feature(serial_feature *sf, long long wx, long long wy) {
|
|
std::string s;
|
|
|
|
serialize_byte(s, sf->t);
|
|
|
|
#define FLAG_LAYER 7
|
|
|
|
#define FLAG_LABEL_POINT 6
|
|
#define FLAG_INDEX 4
|
|
#define FLAG_EXTENT 3
|
|
#define FLAG_ID 2
|
|
#define FLAG_MINZOOM 1
|
|
#define FLAG_MAXZOOM 0
|
|
|
|
long long layer = 0;
|
|
layer |= sf->layer << FLAG_LAYER;
|
|
layer |= (sf->has_label_point) << FLAG_LABEL_POINT;
|
|
layer |= (sf->index != 0) << FLAG_INDEX;
|
|
layer |= (sf->extent != 0) << FLAG_EXTENT;
|
|
layer |= sf->has_id << FLAG_ID;
|
|
layer |= (sf->tippecanoe_minzoom != -1) << FLAG_MINZOOM;
|
|
layer |= (sf->tippecanoe_maxzoom != -1) << FLAG_MAXZOOM;
|
|
|
|
serialize_long_long(s, layer);
|
|
serialize_long_long(s, sf->seq);
|
|
if (sf->tippecanoe_minzoom != -1) {
|
|
serialize_int(s, sf->tippecanoe_minzoom);
|
|
}
|
|
if (sf->tippecanoe_maxzoom != -1) {
|
|
serialize_int(s, sf->tippecanoe_maxzoom);
|
|
}
|
|
if (sf->has_id) {
|
|
serialize_ulong_long(s, sf->id);
|
|
}
|
|
|
|
serialize_int(s, sf->segment);
|
|
|
|
write_geometry(sf->geometry, s, wx, wy);
|
|
|
|
if (sf->index != 0) {
|
|
serialize_ulong_long(s, sf->index);
|
|
serialize_ulong_long(s, sf->gap);
|
|
}
|
|
if (sf->has_label_point) {
|
|
serialize_long_long(s, sf->label_point_x);
|
|
serialize_long_long(s, sf->label_point_y);
|
|
}
|
|
if (sf->extent != 0) {
|
|
serialize_long_long(s, sf->extent);
|
|
}
|
|
|
|
serialize_long_long(s, sf->keys.size());
|
|
|
|
for (size_t i = 0; i < sf->keys.size(); i++) {
|
|
serialize_long_long(s, sf->keys[i]);
|
|
serialize_long_long(s, sf->values[i]);
|
|
}
|
|
|
|
// MAGIC: This knows that the feature minzoom is the last byte of the feature,
|
|
serialize_byte(s, sf->feature_minzoom);
|
|
return s;
|
|
}
|
|
|
|
serial_feature deserialize_feature(std::string const &geoms, unsigned z, unsigned tx, unsigned ty, unsigned *initial_x, unsigned *initial_y) {
|
|
serial_feature sf;
|
|
const char *cp = geoms.c_str();
|
|
|
|
deserialize_byte(&cp, &sf.t);
|
|
deserialize_long_long(&cp, &sf.layer);
|
|
|
|
sf.seq = 0;
|
|
deserialize_long_long(&cp, &sf.seq);
|
|
|
|
sf.tippecanoe_minzoom = -1;
|
|
sf.tippecanoe_maxzoom = -1;
|
|
sf.id = 0;
|
|
sf.has_id = false;
|
|
if (sf.layer & (1 << FLAG_MINZOOM)) {
|
|
deserialize_int(&cp, &sf.tippecanoe_minzoom);
|
|
}
|
|
if (sf.layer & (1 << FLAG_MAXZOOM)) {
|
|
deserialize_int(&cp, &sf.tippecanoe_maxzoom);
|
|
}
|
|
if (sf.layer & (1 << FLAG_ID)) {
|
|
sf.has_id = true;
|
|
deserialize_ulong_long(&cp, &sf.id);
|
|
}
|
|
|
|
deserialize_int(&cp, &sf.segment);
|
|
|
|
sf.index = 0;
|
|
sf.gap = 0;
|
|
sf.label_point_x = 0;
|
|
sf.label_point_y = 0;
|
|
sf.has_label_point = false;
|
|
sf.extent = 0;
|
|
|
|
sf.geometry = decode_geometry(&cp, z, tx, ty, sf.bbox, initial_x[sf.segment], initial_y[sf.segment]);
|
|
|
|
if (sf.layer & (1 << FLAG_INDEX)) {
|
|
deserialize_ulong_long(&cp, &sf.index);
|
|
deserialize_ulong_long(&cp, &sf.gap);
|
|
}
|
|
if (sf.layer & (1 << FLAG_LABEL_POINT)) {
|
|
sf.has_label_point = true;
|
|
deserialize_long_long(&cp, &sf.label_point_x);
|
|
deserialize_long_long(&cp, &sf.label_point_y);
|
|
}
|
|
if (sf.layer & (1 << FLAG_EXTENT)) {
|
|
deserialize_long_long(&cp, &sf.extent);
|
|
}
|
|
|
|
sf.layer >>= FLAG_LAYER;
|
|
|
|
long long count;
|
|
deserialize_long_long(&cp, &count);
|
|
|
|
for (long long i = 0; i < count; i++) {
|
|
long long k, v;
|
|
deserialize_long_long(&cp, &k);
|
|
deserialize_long_long(&cp, &v);
|
|
sf.keys.push_back(k);
|
|
sf.values.push_back(v);
|
|
}
|
|
|
|
// MAGIC: This knows that the feature minzoom is the last byte of the feature.
|
|
deserialize_byte(&cp, &sf.feature_minzoom);
|
|
|
|
if (cp != geoms.c_str() + geoms.size()) {
|
|
fprintf(stderr, "wrong length decoding feature: used %zd, len is %zu\n", cp - geoms.c_str(), geoms.size());
|
|
exit(EXIT_IMPOSSIBLE);
|
|
}
|
|
|
|
return sf;
|
|
}
|
|
|
|
static long long scale_geometry(struct serialization_state *sst, long long *bbox, drawvec &geom) {
|
|
long long offset = 0;
|
|
long long prev = 0;
|
|
bool has_prev = false;
|
|
double scale = 1.0 / (1 << geometry_scale);
|
|
|
|
for (size_t i = 0; i < geom.size(); i++) {
|
|
if (geom[i].op == VT_MOVETO || geom[i].op == VT_LINETO) {
|
|
long long x = geom[i].x;
|
|
long long y = geom[i].y;
|
|
|
|
if (additional[A_DETECT_WRAPAROUND]) {
|
|
if (geom[i].op == VT_LINETO) {
|
|
x += offset;
|
|
if (has_prev) {
|
|
// jumps at least 180° but not exactly 360°,
|
|
// which in some data sets is an intentional
|
|
// line across the world
|
|
if (x - prev > (1LL << 31) && x - prev != (1LL << 32)) {
|
|
offset -= 1LL << 32;
|
|
x -= 1LL << 32;
|
|
} else if (prev - x > (1LL << 31) && prev - x != (1LL << 32)) {
|
|
offset += 1LL << 32;
|
|
x += 1LL << 32;
|
|
}
|
|
}
|
|
|
|
has_prev = true;
|
|
prev = x;
|
|
} else {
|
|
offset = 0;
|
|
prev = x;
|
|
}
|
|
}
|
|
|
|
if (x < bbox[0]) {
|
|
bbox[0] = x;
|
|
}
|
|
if (y < bbox[1]) {
|
|
bbox[1] = y;
|
|
}
|
|
if (x > bbox[2]) {
|
|
bbox[2] = x;
|
|
}
|
|
if (y > bbox[3]) {
|
|
bbox[3] = y;
|
|
}
|
|
|
|
if (!*(sst->initialized)) {
|
|
if (x < 0 || x >= (1LL << 32) || y < 0 || y >= (1LL << 32)) {
|
|
*(sst->initial_x) = 1LL << 31;
|
|
*(sst->initial_y) = 1LL << 31;
|
|
} else {
|
|
*(sst->initial_x) = SHIFT_LEFT(SHIFT_RIGHT(x));
|
|
*(sst->initial_y) = SHIFT_LEFT(SHIFT_RIGHT(y));
|
|
}
|
|
|
|
*(sst->initialized) = 1;
|
|
}
|
|
|
|
if (additional[A_GRID_LOW_ZOOMS]) {
|
|
// If we are gridding, snap to the maxzoom grid in case the incoming data
|
|
// is already supposed to be aligned to tile boundaries (but is not, exactly,
|
|
// because of rounding error during projection).
|
|
|
|
geom[i].x = std::round(x * scale);
|
|
geom[i].y = std::round(y * scale);
|
|
} else {
|
|
geom[i].x = SHIFT_RIGHT(x);
|
|
geom[i].y = SHIFT_RIGHT(y);
|
|
}
|
|
}
|
|
}
|
|
|
|
return geom.size();
|
|
}
|
|
|
|
static std::string strip_zeroes(std::string s) {
|
|
// Doesn't do anything special with '-' followed by leading zeros
|
|
// since integer IDs must be positive
|
|
|
|
while (s.size() > 0 && s[0] == '0') {
|
|
s.erase(s.begin());
|
|
}
|
|
|
|
return s;
|
|
}
|
|
|
|
int nodecmp(const void *void1, const void *void2) {
|
|
node *n1 = (node *) void1;
|
|
node *n2 = (node *) void2;
|
|
|
|
if (n1->index < n2->index) {
|
|
return -1;
|
|
} else if (n1->index > n2->index) {
|
|
return 1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void add_scaled_node(struct reader *r, serialization_state *sst, draw g) {
|
|
long long x = SHIFT_LEFT(g.x);
|
|
long long y = SHIFT_LEFT(g.y);
|
|
|
|
struct node n;
|
|
n.index = encode_vertex((unsigned) x, (unsigned) y);
|
|
|
|
fwrite_check((char *) &n, sizeof(struct node), 1, r->nodefile, &r->nodepos, sst->fname);
|
|
}
|
|
|
|
// called from frontends
|
|
int serialize_feature(struct serialization_state *sst, serial_feature &sf, std::string const &layername) {
|
|
struct reader *r = &(*sst->readers)[sst->segment];
|
|
key_pool key_pool;
|
|
|
|
sf.bbox[0] = LLONG_MAX;
|
|
sf.bbox[1] = LLONG_MAX;
|
|
sf.bbox[2] = LLONG_MIN;
|
|
sf.bbox[3] = LLONG_MIN;
|
|
|
|
for (size_t i = 0; i < sf.geometry.size(); i++) {
|
|
if (sf.geometry[i].op == VT_MOVETO || sf.geometry[i].op == VT_LINETO) {
|
|
// standard -180 to 180 world plane
|
|
//
|
|
// mask X, since it wraps around
|
|
// pin Y, since it doesn't
|
|
|
|
long long x = sf.geometry[i].x & 0xFFFFFFFF;
|
|
long long y = std::max(std::min(sf.geometry[i].y, 0xFFFFFFFFLL), 0LL);
|
|
|
|
{
|
|
r->file_bbox1[0] = std::min(r->file_bbox1[0], x);
|
|
r->file_bbox1[1] = std::min(r->file_bbox1[1], y);
|
|
r->file_bbox1[2] = std::max(r->file_bbox1[2], x);
|
|
r->file_bbox1[3] = std::max(r->file_bbox1[3], y);
|
|
|
|
// printf("%llx,%llx %llx,%llx %llx,%llx ", x, y, r->file_bbox1[0], r->file_bbox1[1], r->file_bbox1[2], r->file_bbox1[3]);
|
|
|
|
// shift the western hemisphere 360 degrees to the east
|
|
if (x < 0x80000000) { // prime meridian
|
|
x += 0x100000000;
|
|
}
|
|
|
|
r->file_bbox2[0] = std::min(r->file_bbox2[0], x);
|
|
r->file_bbox2[1] = std::min(r->file_bbox2[1], y);
|
|
r->file_bbox2[2] = std::max(r->file_bbox2[2], x);
|
|
r->file_bbox2[3] = std::max(r->file_bbox2[3], y);
|
|
}
|
|
}
|
|
}
|
|
|
|
// try to remind myself that the geometry in this function is in SCALED COORDINATES
|
|
drawvec scaled_geometry = sf.geometry;
|
|
sf.geometry.clear();
|
|
scale_geometry(sst, sf.bbox, scaled_geometry);
|
|
|
|
// This has to happen after scaling so that the wraparound detection has happened first.
|
|
// Otherwise the inner/outer calculation will be confused by bad geometries.
|
|
if (sf.t == VT_POLYGON) {
|
|
scaled_geometry = fix_polygon(scaled_geometry, prevent[P_USE_SOURCE_POLYGON_WINDING], prevent[P_REVERSE_SOURCE_POLYGON_WINDING]);
|
|
}
|
|
|
|
for (auto &c : clipbboxes) {
|
|
if (sf.t == VT_POLYGON) {
|
|
scaled_geometry = simple_clip_poly(scaled_geometry, SHIFT_RIGHT(c.minx), SHIFT_RIGHT(c.miny), SHIFT_RIGHT(c.maxx), SHIFT_RIGHT(c.maxy), prevent[P_SIMPLIFY_SHARED_NODES]);
|
|
} else if (sf.t == VT_LINE) {
|
|
scaled_geometry = clip_lines(scaled_geometry, SHIFT_RIGHT(c.minx), SHIFT_RIGHT(c.miny), SHIFT_RIGHT(c.maxx), SHIFT_RIGHT(c.maxy));
|
|
} else if (sf.t == VT_POINT) {
|
|
scaled_geometry = clip_point(scaled_geometry, SHIFT_RIGHT(c.minx), SHIFT_RIGHT(c.miny), SHIFT_RIGHT(c.maxx), SHIFT_RIGHT(c.maxy));
|
|
}
|
|
|
|
scaled_geometry = remove_noop(scaled_geometry, sf.t, 0);
|
|
|
|
sf.bbox[0] = LLONG_MAX;
|
|
sf.bbox[1] = LLONG_MAX;
|
|
sf.bbox[2] = LLONG_MIN;
|
|
sf.bbox[3] = LLONG_MIN;
|
|
|
|
for (auto &g : scaled_geometry) {
|
|
long long x = SHIFT_LEFT(g.x);
|
|
long long y = SHIFT_LEFT(g.y);
|
|
|
|
if (x < sf.bbox[0]) {
|
|
sf.bbox[0] = x;
|
|
}
|
|
if (y < sf.bbox[1]) {
|
|
sf.bbox[1] = y;
|
|
}
|
|
if (x > sf.bbox[2]) {
|
|
sf.bbox[2] = x;
|
|
}
|
|
if (y > sf.bbox[3]) {
|
|
sf.bbox[3] = y;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (scaled_geometry.size() == 0) {
|
|
// Feature was clipped away
|
|
return 1;
|
|
}
|
|
|
|
if (prevent[P_SIMPLIFY_SHARED_NODES]) {
|
|
scaled_geometry = remove_noop(scaled_geometry, sf.t, 0);
|
|
|
|
if (sf.t == VT_POLYGON || sf.t == VT_LINE) {
|
|
for (size_t i = 0; i < scaled_geometry.size(); i++) {
|
|
if (scaled_geometry[i].op == VT_MOVETO) {
|
|
size_t j;
|
|
|
|
for (j = i + 1; j < scaled_geometry.size(); j++) {
|
|
if (scaled_geometry[j].op != VT_LINETO) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (sf.t == VT_POLYGON && j - i >= 4) {
|
|
for (size_t k = i; k < j - 1; k++) {
|
|
// % (j - i - 1) because we don't want the duplicate last point
|
|
|
|
struct vertex v(
|
|
scaled_geometry[(k - i + 0) % (j - i - 1) + i],
|
|
scaled_geometry[(k - i + 1) % (j - i - 1) + i],
|
|
scaled_geometry[(k - i + 2) % (j - i - 1) + i]);
|
|
|
|
fwrite_check((char *) &v, sizeof(struct vertex), 1, r->vertexfile, &r->vertexpos, sst->fname);
|
|
}
|
|
} else if (sf.t == VT_LINE && j - i >= 2) {
|
|
for (size_t k = i; k + 2 < j; k++) {
|
|
struct vertex v(
|
|
scaled_geometry[k + 0],
|
|
scaled_geometry[k + 1],
|
|
scaled_geometry[k + 2]);
|
|
|
|
fwrite_check((char *) &v, sizeof(struct vertex), 1, r->vertexfile, &r->vertexpos, sst->fname);
|
|
}
|
|
}
|
|
|
|
// since the starting point is never simplified away,
|
|
// don't let it be simplified away in any other polygons either.
|
|
// Needs to appear twice here so that the check below will see
|
|
// it as appearing in multiple features.
|
|
add_scaled_node(r, sst, scaled_geometry[i]);
|
|
|
|
if (sf.t == VT_LINE && j - i >= 2) {
|
|
// linestrings also need to preserve the last point
|
|
|
|
add_scaled_node(r, sst, scaled_geometry[j - 1]);
|
|
} else if (sf.t == VT_POLYGON && j - i >= 4) {
|
|
// To avoid letting polygons get simplified away to nothing,
|
|
// also keep the furthest-away point from the initial point
|
|
// (which Douglas-Peucker simplification would keep anyway,
|
|
// if its search weren't being split up by polygon side).
|
|
|
|
double far = 0;
|
|
size_t which = i;
|
|
for (size_t k = i + 1; k < j - 1; k++) {
|
|
double xd = scaled_geometry[k].x - scaled_geometry[i].x;
|
|
double yd = scaled_geometry[k].y - scaled_geometry[i].y;
|
|
double d = xd * xd + yd * yd;
|
|
if (d > far ||
|
|
((d == far) && (scaled_geometry[k] < scaled_geometry[which]))) {
|
|
far = d;
|
|
which = k;
|
|
}
|
|
}
|
|
|
|
add_scaled_node(r, sst, scaled_geometry[which]);
|
|
|
|
// And, likewise, the point most distant from those two points,
|
|
// which probably would also be the one that Douglas-Peucker
|
|
// would keep next.
|
|
|
|
far = 0;
|
|
size_t which2 = i;
|
|
|
|
for (size_t k = i + 1; k < j - 1; k++) {
|
|
double d = distance_from_line(scaled_geometry[k].x, scaled_geometry[k].y,
|
|
scaled_geometry[i].x, scaled_geometry[i].y,
|
|
scaled_geometry[which].x, scaled_geometry[which].y);
|
|
if ((d > far) ||
|
|
((d == far) && (scaled_geometry[k] < scaled_geometry[which2]))) {
|
|
far = d;
|
|
which2 = k;
|
|
}
|
|
}
|
|
|
|
add_scaled_node(r, sst, scaled_geometry[which2]);
|
|
}
|
|
|
|
i = j - 1;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!sf.has_id) {
|
|
if (additional[A_GENERATE_IDS]) {
|
|
sf.has_id = true;
|
|
sf.id = sf.seq + 1;
|
|
}
|
|
}
|
|
|
|
if (sst->want_dist) {
|
|
std::vector<unsigned long long> locs;
|
|
for (size_t i = 0; i < scaled_geometry.size(); i++) {
|
|
if (scaled_geometry[i].op == VT_MOVETO || scaled_geometry[i].op == VT_LINETO) {
|
|
locs.push_back(encode_index(SHIFT_LEFT(scaled_geometry[i].x), SHIFT_LEFT(scaled_geometry[i].y)));
|
|
}
|
|
}
|
|
std::stable_sort(locs.begin(), locs.end());
|
|
size_t n = 0;
|
|
double sum = 0;
|
|
for (size_t i = 1; i < locs.size(); i++) {
|
|
if (locs[i - 1] != locs[i]) {
|
|
sum += log(locs[i] - locs[i - 1]);
|
|
n++;
|
|
}
|
|
}
|
|
if (n > 0) {
|
|
double avg = exp(sum / n);
|
|
// Convert approximately from tile units to feet
|
|
// See comment about empirical data in main.cpp
|
|
double dist_ft = sqrt(avg) / 33;
|
|
|
|
*(sst->dist_sum) += log(dist_ft) * n;
|
|
*(sst->dist_count) += n;
|
|
}
|
|
locs.clear();
|
|
}
|
|
|
|
double extent = 0;
|
|
if (additional[A_DROP_SMALLEST_AS_NEEDED] || additional[A_COALESCE_SMALLEST_AS_NEEDED] || order_by_size || sst->want_dist) {
|
|
if (sf.t == VT_POLYGON) {
|
|
for (size_t i = 0; i < scaled_geometry.size(); i++) {
|
|
if (scaled_geometry[i].op == VT_MOVETO) {
|
|
size_t j;
|
|
for (j = i + 1; j < scaled_geometry.size(); j++) {
|
|
if (scaled_geometry[j].op != VT_LINETO) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
extent += SHIFT_LEFT(SHIFT_LEFT(1LL)) * get_area(scaled_geometry, i, j);
|
|
i = j - 1;
|
|
}
|
|
}
|
|
} else if (sf.t == VT_LINE) {
|
|
double dist = 0;
|
|
for (size_t i = 1; i < scaled_geometry.size(); i++) {
|
|
if (scaled_geometry[i].op == VT_LINETO) {
|
|
double xd = SHIFT_LEFT(scaled_geometry[i].x - scaled_geometry[i - 1].x);
|
|
double yd = SHIFT_LEFT(scaled_geometry[i].y - scaled_geometry[i - 1].y);
|
|
dist += sqrt(xd * xd + yd * yd);
|
|
}
|
|
}
|
|
// treat lines as having the area of a circle with the line as diameter
|
|
extent = M_PI * (dist / 2) * (dist / 2);
|
|
}
|
|
|
|
// VT_POINT extent will be calculated in write_tile from the distance between adjacent features.
|
|
}
|
|
|
|
// Clamp before converting, since converting a double that is out of range
|
|
// for a long long is undefined. The bounds are asymmetric: LLONG_MAX is not
|
|
// representable as a double and rounds up to 2^63, so the upper bound has to
|
|
// be exclusive, while LLONG_MIN is exactly -2^63 and so can be included.
|
|
// Areas are signed, so holes that outweigh their rings can make this
|
|
// negative.
|
|
if (extent >= (double) LLONG_MIN && extent < (double) LLONG_MAX) {
|
|
sf.extent = (long long) extent;
|
|
} else if (extent < 0) {
|
|
sf.extent = LLONG_MIN;
|
|
} else {
|
|
sf.extent = LLONG_MAX; // also the NaN case
|
|
}
|
|
|
|
if (sst->want_dist && sf.t == VT_POLYGON) {
|
|
*(sst->area_sum) += extent;
|
|
}
|
|
|
|
unsigned long long bbox_index;
|
|
long long midx, midy;
|
|
|
|
if (sf.t == VT_POINT) {
|
|
// keep old behavior, which loses one bit of precision at the bottom
|
|
midx = (sf.bbox[0] / 2 + sf.bbox[2] / 2) & ((1LL << 32) - 1);
|
|
midy = (sf.bbox[1] / 2 + sf.bbox[3] / 2) & ((1LL << 32) - 1);
|
|
} else if ((additional[A_DROP_DENSEST_AS_NEEDED] || additional[A_COALESCE_DENSEST_AS_NEEDED]) && sf.t == VT_POLYGON) {
|
|
// This probably should really apply to all polygons,
|
|
// but I hate to change the feature sequence in all the
|
|
// test fixtures again
|
|
draw scaled_center = center_of_mass_mp(scaled_geometry);
|
|
midx = SHIFT_LEFT(scaled_center.x) & ((1LL << 32) - 1);
|
|
midy = SHIFT_LEFT(scaled_center.y) & ((1LL << 32) - 1);
|
|
} else {
|
|
// To reduce the chances of giving multiple polygons or linestrings
|
|
// the same index, use an arbitrary but predictable point from the
|
|
// geometry as the index point rather than the bounding box center
|
|
// as was previously used. The index point chosen comes from a hash
|
|
// of the overall geometry, so features with the same geometry will
|
|
// still have the same index. Specifically this avoids guessing
|
|
// too high a maxzoom for a data source that has a large number of
|
|
// LineStrings that map essentially the same route but with slight
|
|
// jitter between them, even though the geometries themselves are
|
|
// not very detailed.
|
|
size_t ix = 0;
|
|
for (size_t i = 0; i < scaled_geometry.size(); i++) {
|
|
ix += scaled_geometry[i].x + scaled_geometry[i].y;
|
|
}
|
|
if (scaled_geometry.size() != 0) {
|
|
ix = ix % scaled_geometry.size();
|
|
}
|
|
|
|
// If off the edge of the plane, mask to bring it back into the addressable area
|
|
midx = SHIFT_LEFT(scaled_geometry[ix].x) & ((1LL << 32) - 1);
|
|
midy = SHIFT_LEFT(scaled_geometry[ix].y) & ((1LL << 32) - 1);
|
|
}
|
|
|
|
bbox_index = encode_index(midx, midy);
|
|
if (additional[A_CALCULATE_INDEX]) {
|
|
sf.full_keys.push_back(key_pool.pool("tippecanoe:index"));
|
|
|
|
serial_val sv;
|
|
sv.type = mvt_double;
|
|
sv.s = std::to_string(bbox_index);
|
|
sf.full_values.push_back(sv);
|
|
}
|
|
|
|
if (sf.t == VT_POLYGON && additional[A_GENERATE_POLYGON_LABEL_POINTS]) {
|
|
drawvec dv = polygon_to_anchor(scaled_geometry);
|
|
if (dv.size() > 0) {
|
|
sf.label_point_x = SHIFT_LEFT(dv[0].x);
|
|
sf.label_point_y = SHIFT_LEFT(dv[0].y);
|
|
sf.has_label_point = true;
|
|
}
|
|
}
|
|
|
|
if (additional[A_DROP_DENSEST_AS_NEEDED] ||
|
|
additional[A_COALESCE_DENSEST_AS_NEEDED] ||
|
|
additional[A_CLUSTER_DENSEST_AS_NEEDED] ||
|
|
additional[A_CALCULATE_FEATURE_DENSITY] ||
|
|
additional[A_DROP_SMALLEST_AS_NEEDED] ||
|
|
additional[A_COALESCE_SMALLEST_AS_NEEDED] ||
|
|
additional[A_DROP_FRACTION_AS_NEEDED] ||
|
|
additional[A_COALESCE_FRACTION_AS_NEEDED] ||
|
|
prevent[P_DYNAMIC_DROP] ||
|
|
additional[A_INCREASE_GAMMA_AS_NEEDED] ||
|
|
additional[A_GENERATE_POLYGON_LABEL_POINTS] ||
|
|
sst->uses_gamma ||
|
|
retain_points_multiplier > 1 ||
|
|
preserve_multiplier_density_threshold > 0 ||
|
|
cluster_distance != 0) {
|
|
sf.index = bbox_index;
|
|
} else {
|
|
sf.index = 0;
|
|
}
|
|
|
|
if (sst->layermap->count(layername) == 0) {
|
|
sst->layermap->emplace(layername, layermap_entry(sst->layermap->size()));
|
|
}
|
|
|
|
auto ai = sst->layermap->find(layername);
|
|
if (ai != sst->layermap->end()) {
|
|
sf.layer = ai->second.id;
|
|
|
|
if (!sst->filters) {
|
|
if (sf.t == VT_POINT) {
|
|
ai->second.points++;
|
|
} else if (sf.t == VT_LINE) {
|
|
ai->second.lines++;
|
|
} else if (sf.t == VT_POLYGON) {
|
|
ai->second.polygons++;
|
|
}
|
|
}
|
|
} else {
|
|
fprintf(stderr, "Internal error: can't find layer name %s\n", layername.c_str());
|
|
exit(EXIT_IMPOSSIBLE);
|
|
}
|
|
|
|
for (auto &kv : set_attributes) {
|
|
bool found = false;
|
|
for (size_t i = 0; i < sf.full_keys.size(); i++) {
|
|
if (*sf.full_keys[i] == kv.first) {
|
|
sf.full_values[i] = kv.second;
|
|
found = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!found) {
|
|
sf.full_keys.push_back(key_pool.pool(kv.first));
|
|
sf.full_values.push_back(kv.second);
|
|
}
|
|
}
|
|
|
|
for (ssize_t i = (ssize_t) sf.full_keys.size() - 1; i >= 0; i--) {
|
|
coerce_value(*sf.full_keys[i], sf.full_values[i].type, sf.full_values[i].s, sst->attribute_types);
|
|
|
|
if (prevent[P_SINGLE_PRECISION]) {
|
|
if (sf.full_values[i].type == mvt_double) {
|
|
// don't coerce integers to floats, since that is counterproductive
|
|
if (sf.full_values[i].s.find('.') != std::string::npos) {
|
|
sf.full_values[i].s = milo::dtoa_milo((float) atof(sf.full_values[i].s.c_str()));
|
|
}
|
|
}
|
|
}
|
|
|
|
if (*sf.full_keys[i] == attribute_for_id) {
|
|
if (sf.full_values[i].type != mvt_double && !additional[A_CONVERT_NUMERIC_IDS]) {
|
|
static bool warned = false;
|
|
|
|
if (!warned) {
|
|
fprintf(stderr, "Warning: Attribute \"%s\"=\"%s\" as feature ID is not a number\n", sf.full_keys[i]->c_str(), sf.full_values[i].s.c_str());
|
|
warned = true;
|
|
}
|
|
} else {
|
|
char *err;
|
|
long long id_value = strtoull(sf.full_values[i].s.c_str(), &err, 10);
|
|
|
|
if (err != NULL && *err != '\0') {
|
|
static bool warned_frac = false;
|
|
|
|
if (!warned_frac) {
|
|
fprintf(stderr, "Warning: Can't represent non-integer feature ID %s\n", sf.full_values[i].s.c_str());
|
|
warned_frac = true;
|
|
}
|
|
} else if (std::to_string(id_value) != strip_zeroes(sf.full_values[i].s)) {
|
|
static bool warned = false;
|
|
|
|
if (!warned) {
|
|
fprintf(stderr, "Warning: Can't represent too-large feature ID %s\n", sf.full_values[i].s.c_str());
|
|
warned = true;
|
|
}
|
|
} else {
|
|
sf.id = id_value;
|
|
sf.has_id = true;
|
|
|
|
sf.full_keys.erase(sf.full_keys.begin() + i);
|
|
sf.full_values.erase(sf.full_values.begin() + i);
|
|
continue;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (sst->exclude_all) {
|
|
if (sst->include->count(*sf.full_keys[i]) == 0) {
|
|
sf.full_keys.erase(sf.full_keys.begin() + i);
|
|
sf.full_values.erase(sf.full_values.begin() + i);
|
|
continue;
|
|
}
|
|
} else if (sst->exclude->count(*sf.full_keys[i]) != 0) {
|
|
sf.full_keys.erase(sf.full_keys.begin() + i);
|
|
sf.full_values.erase(sf.full_values.begin() + i);
|
|
continue;
|
|
}
|
|
}
|
|
|
|
if (!sst->filters) {
|
|
for (size_t i = 0; i < sf.full_keys.size(); i++) {
|
|
auto ts = sst->layermap->find(layername);
|
|
add_to_tilestats(ts->second.tilestats, *sf.full_keys[i], sf.full_values[i]);
|
|
}
|
|
}
|
|
|
|
if (maximum_string_attribute_length > 0) {
|
|
for (size_t i = 0; i < sf.full_keys.size(); i++) {
|
|
if (sf.full_values[i].type == mvt_string && sf.full_values[i].s.size() > maximum_string_attribute_length) {
|
|
sf.full_values[i].s = truncate_string(sf.full_values[i].s, maximum_string_attribute_length);
|
|
}
|
|
}
|
|
}
|
|
|
|
for (size_t i = 0; i < sf.full_keys.size(); i++) {
|
|
sf.keys.push_back(addpool(r->poolfile, r->treefile, sf.full_keys[i]->c_str(), mvt_string, r->key_dedup));
|
|
sf.values.push_back(addpool(r->poolfile, r->treefile, sf.full_values[i].s.c_str(), sf.full_values[i].type, r->value_dedup));
|
|
}
|
|
|
|
long long geomstart = r->geompos;
|
|
sf.geometry = scaled_geometry;
|
|
|
|
std::string feature = serialize_feature(&sf, SHIFT_RIGHT(*(sst->initial_x)), SHIFT_RIGHT(*(sst->initial_y)));
|
|
serialize_long_long(r->geomfile, feature.size(), &r->geompos, sst->fname);
|
|
fwrite_check(feature.c_str(), sizeof(char), feature.size(), r->geomfile, &r->geompos, sst->fname);
|
|
|
|
struct index index;
|
|
index.start = geomstart;
|
|
index.end = r->geompos;
|
|
index.segment = sst->segment;
|
|
index.seq = *(sst->layer_seq);
|
|
index.t = sf.t;
|
|
index.ix = bbox_index;
|
|
|
|
fwrite_check(&index, sizeof(struct index), 1, r->indexfile, &r->indexpos, sst->fname);
|
|
|
|
for (size_t i = 0; i < 2; i++) {
|
|
if (sf.bbox[i] < r->file_bbox[i]) {
|
|
r->file_bbox[i] = sf.bbox[i];
|
|
}
|
|
}
|
|
for (size_t i = 2; i < 4; i++) {
|
|
if (sf.bbox[i] > r->file_bbox[i]) {
|
|
r->file_bbox[i] = sf.bbox[i];
|
|
}
|
|
}
|
|
|
|
if (*(sst->progress_seq) % 10000 == 0) {
|
|
checkdisk(sst->readers);
|
|
if (!quiet && !quiet_progress && progress_time()) {
|
|
fprintf(stderr, "Read %.2f million features\r", *sst->progress_seq / 1000000.0);
|
|
fflush(stderr);
|
|
}
|
|
}
|
|
(*(sst->progress_seq))++;
|
|
(*(sst->layer_seq))++;
|
|
|
|
return 1;
|
|
}
|
|
|
|
void coerce_value(std::string const &key, int &vt, std::string &val, std::unordered_map<std::string, int> const *attribute_types) {
|
|
auto a = (*attribute_types).find(key);
|
|
if (a != attribute_types->end()) {
|
|
if (a->second == mvt_string) {
|
|
vt = mvt_string;
|
|
} else if (a->second == mvt_float) {
|
|
vt = mvt_double;
|
|
val = milo::dtoa_milo(atof(val.c_str()));
|
|
} else if (a->second == mvt_int) {
|
|
vt = mvt_double;
|
|
if (val.size() == 0) {
|
|
val = "0";
|
|
}
|
|
|
|
for (size_t ii = 0; ii < val.size(); ii++) {
|
|
char c = val[ii];
|
|
if (c < '0' || c > '9') {
|
|
val = std::to_string(round(atof(val.c_str())));
|
|
break;
|
|
}
|
|
}
|
|
} else if (a->second == mvt_bool) {
|
|
if (val == "false" || val == "0" || val == "null" || val.size() == 0 || (vt == mvt_double && atof(val.c_str()) == 0)) {
|
|
vt = mvt_bool;
|
|
val = "false";
|
|
} else {
|
|
vt = mvt_bool;
|
|
val = "true";
|
|
}
|
|
} else {
|
|
fprintf(stderr, "Can't happen: attribute type %d\n", a->second);
|
|
exit(EXIT_IMPOSSIBLE);
|
|
}
|
|
}
|
|
}
|