mirror of
https://github.com/felt/tippecanoe.git
synced 2026-10-03 00:45:41 +02:00
e1795ebe3280d417baa97784af8b400a2dfdb1e7
261
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
d3a98170bf |
Look up shared nodes once per vertex instead of once per tile
With --no-simplification-of-shared-nodes, simplify_lines() used to offset every vertex of every feature to world coordinates and check it against the Bloom filter and the global sorted list of shared nodes, in every tile at every zoom level. Whether a vertex is a shared node depends only on its world coordinates, so now it is found once, after the list of shared nodes has been made and before the geometry is sorted, by a parallel pass over each reader's geometry that marks each vertex in place, in the upper bits of its serialized operation byte. Decoding puts that state into a new field of draw (which still fits in 16 bytes), and it is carried through clipping, the copies across the antimeridian at z0, and the geometry written for the next zoom level. Polygon cleaning of coalesced features restores the state of any output vertex with the same coordinates as an input vertex. Vertices whose state is still unknown, because they were created by clipping or polygon cleaning or came back from a prefilter, are still looked up in the global list when they are simplified, so the output is unchanged. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01P2nBqZisxNQfmEmon3vE9v |
||
|
|
4f2621186a |
Convert jsonpull to C++ with shared_ptr and std::vector/std::string (#388)
* Rename to jsonpull.cpp * Clear for merge * Convert jsonpull to C++ with shared_ptr and std::vector/std::string Replace the manual malloc/realloc/free memory management in jsonpull with std::shared_ptr ownership. Each json_object now owns its children through std::vector<json_object_ptr>; raw back-pointers to parent and parser remain valid by structural invariant and are cleared on json_disconnect so detached subtrees can outlive their parser. Strings become std::string, child arrays become std::vector, and the old union becomes a struct so non-trivial members can coexist while preserving the existing o->value.xxx access paths. The old jsonpull.c is replaced by jsonpull.cpp, json_stringify now returns std::string, and all callers across tippecanoe, tile-join, tippecanoe-decode, tippecanoe-json-tool, tippecanoe-overzoom and the unit tests are updated to use json_object_ptr / json_pull_ptr. Co-authored-by: Cursor <cursoragent@cursor.com> * Subclass json_object so primitives shrink from 168 to 24 bytes The previous "every member in a struct" layout cost 168 bytes per json_object, even for JSON_NULL / JSON_TRUE / JSON_FALSE nodes that have no payload. Splitting json_object into a small base class plus json_number / json_string / json_array / json_hash subclasses brings each instance down to just the size of its actual contents: json_object (base, TRUE / FALSE / NULL) 24 bytes json_number 48 bytes json_string 48 bytes json_array (empty) 48 bytes json_hash (empty) 72 bytes Other size wins along the way: * Drop enable_shared_from_this<json_object> (its embedded weak_ptr was 16 bytes per node). json_pull now keeps an explicit container_stack and the parser no longer needs to resurrect a shared_ptr from a raw `parent` walk. * Remove the unused `refcon` slot from the string variant. * No virtual destructor: shared_ptr keeps the deleter from the original std::make_shared<json_xxx> call, so destroying a shared_ptr<json_object> still runs the right subclass dtor. The base class exposes type-tagged accessors (o->string(), o->number(), o->array(), o->keys(), o->values(), o->large_signed(), o->large_unsigned()) that assert the type matches and downcast to the appropriate subclass storage. All call sites were swept from the old `o->value.X.Y` field paths to these accessors. A raw-pointer overload of json_hash_get() replaces the few external uses of shared_from_this() that survived in geojson-loop.cpp. Co-authored-by: Cursor <cursoragent@cursor.com> * Store hash key/value pairs in one ordered vector Replace the parallel std::vector<json_object_ptr> keys / values on json_hash with a single std::vector<json_entry>, where json_entry is a small {key, value} aggregate. This still preserves insertion order (the property the parallel vectors were providing) but removes the "keep two vectors in lockstep" pattern, and call sites can now use range-for with structured bindings: for (auto &[k, v] : o->entries()) { ... } Side effects: * sizeof(json_hash) drops from 72 to 48 bytes (one fewer vector header), matching json_array. * The keys() and values() accessors on json_object are replaced by a single entries() accessor returning std::vector<json_entry>&. * All call sites were swept from the old paired-index pattern (`o->keys()[i]` / `o->values()[i]`) to entry-based access. Where the original pattern relied on `nprop = 0` to short-circuit iteration on a null or non-hash `properties`, the rewrite now guards the loop explicitly with `if (o->type == JSON_HASH)` so that calling entries() doesn't trip the asserting downcast. Co-authored-by: Cursor <cursoragent@cursor.com> * Move parser-only `expect` state out of json_object `expect` was only meaningful while the parser was building a container, and only ever read or written from jsonpull.cpp itself; once parsing finished it was dead weight on every JSON_ARRAY and JSON_HASH (and present-but-unused on every primitive too). Move it into the parser's container stack, alongside the shared_ptr to the container it pertains to: struct json_pull::parse_frame { json_object_ptr container; json_type expect; }; std::vector<parse_frame> container_stack; The base class now only carries data-model state (parent, parser, type). No external caller depended on `expect`, so no sweep was needed outside jsonpull.cpp. This change does not, in itself, shrink any json_object: the 4-byte `expect` field used to live at offset 20 inside the base, where it was already being eaten by alignment padding for the 8-byte-aligned first member of every subclass (std::string, std::vector, double). The win is in the data model, not the byte count -- the 4-byte hole is still there, but it is now available for a future subclass whose first member is small enough to slot into it. Co-authored-by: Cursor <cursoragent@cursor.com> * Discriminate json_number's three numeric slots into one union json_number used to carry three parallel 8-byte fields (a double plus both a 64-bit unsigned and a 64-bit signed slot for the large-integer cases) even though at most one of the integer slots is ever the canonical value for any given number. Collapse them into a discriminated union: enum repr_t { REPR_DOUBLE, REPR_LARGE_UNSIGNED, REPR_LARGE_SIGNED }; repr_t repr; union { double d; unsigned long long u; long long s; } value; Callers keep the same read API: number() returns the appropriate double, large_unsigned() returns the ull (or 0 if not currently stored that way), large_signed() likewise. Writes go through new set_number / set_large_unsigned / set_large_signed methods that keep the discriminator and the union value in sync. This was prompted by an observation that moving json_type to the end of the object should shrink things via tail-padding reuse. Empirically the type-at-end rearrangement saves nothing on its own (every subclass payload is 8-byte aligned so it can't slot into the 4-byte tail), but the discriminated-number redesign hits the same idea from a different direction: adding the 4-byte `repr` to json_number makes the class non-standard-layout, which lets the Itanium ABI pack `repr` into the base's 4-byte tail padding at offset 20. The union value then starts at the natural offset 24, and json_number ends at offset 32 -- a 33% reduction. Per-node sizes: json_object (TRUE/FALSE/NULL) 24 bytes json_number 32 bytes (was 48) json_string 48 bytes json_array 48 bytes json_hash 48 bytes Numbers dominate real GeoJSON (every coordinate is one), so the net memory win on a typical parse is substantial. Co-authored-by: Cursor <cursoragent@cursor.com> * Fix bugs flagged in code review of jsonpull C++ port - jsontool.cpp `out()`: route JSON_NUMBER (and anything else non-string) through `json_stringify` instead of `o->string()`, which now asserts on a non-string type and would crash `--extract` on numeric attributes. - geojson.{hpp,cpp} `json_end_map`: take `json_pull_ptr` by reference so the caller's shared_ptr is released, null-guard before touching `jp->source`, and clear `jp->source` after delete to avoid a dangling pointer. - jsonpull/jsonpull.cpp: low-surrogate range check was comparing the outer-loop byte `c` instead of the parsed code unit `ch`, breaking surrogate-pair decoding for some \\uXXXX escapes. Pre-existing bug preserved across the port. - tile-join.cpp `handle_vector_layers`: require the field value to have type JSON_STRING (and the key to be non-null) before calling `string()`; the previous truthy `type` check would assert on a non-string value. Co-authored-by: Cursor <cursoragent@cursor.com> * Add jsonpull regression test for surrogate-pair decoding Covers the `c` vs `ch` bug fixed in the previous commit: parsing "\uD83D\uE000" (a valid high surrogate followed by a non-surrogate BMP code point) used to mis-classify U+E000 as a low surrogate and combine the two units into U+1F400 (F0 9F 90 80). The fixed code flushes the stale high surrogate as standalone CESU-8 (ED A0 BD) and then encodes U+E000 normally as EE 80 80. Verified the test fails under the pre-fix logic. Co-authored-by: Cursor <cursoragent@cursor.com> * Cheap perf wins in jsonpull C++ port Profiling tl_2022_us_county.json (sample(1) on Apple Silicon) showed ~38% of parse time in allocator work and ~14% in std::string::push_back during string-token construction. These changes target the low-hanging fruit from that profile: - Pre-reserve 2 slots in json_array and 4 slots in json_hash so coordinate `[x, y]` pairs and typical GeoJSON property maps avoid the 0 -> 1 -> 2 -> 4 vector-growth chain (and the shared_ptr copies it incurs). - Reuse a parser-wide std::string buffer for JSON_STRING tokens instead of constructing a fresh local std::string per token. The buffer is cleared (capacity preserved) at the start of each token and copied into the final json_string, so once it has grown to the longest string seen it stops reallocating entirely. - std::move the freshly-created container shared_ptr into the parser container stack in the `[` and `{` handlers, and move it out of the frame on the matching `]` / `}`. Each move skips one atomic inc/dec round-trip per container open and close. On a tl_2022_us_county.json benchmark (4-iter user-time mean, Apple Silicon, /usr/bin/time): - main baseline: ~8.17s - jsonpull-cpp before these changes: ~10.90s (+33%) - jsonpull-cpp with these changes: ~9.33s (+14%) So this commit recovers roughly half of the post-port regression. The remaining gap is dominated by shared_ptr atomic refcount traffic on the parse tree and per-node heap allocations, which would require the larger unique_ptr/arena reworks to address. Co-authored-by: Cursor <cursoragent@cursor.com> * Make json_free actually free the subtree In the C++ port, json_free was just `o.reset()`, which dropped the caller's reference but left the subtree alive: the parent's vector slot kept it allocated, and for line-delimited streams the parser's jp->root co-owned it until the next top-level value started parsing. That defeated the geojson-loop pattern of calling json_free on each feature after serializing it, which is supposed to release the feature so it doesn't sit in memory while subsequent ones are parsed. Restore the historical "remove this from the tree" semantics by splicing the node out of its parent (sharing splice_from_parent with json_disconnect) and clearing jp->root when the node is the parser's current top-level value, then dropping the caller's reference. Two unit tests pin this down: a pruning test parses "[[1, 2], [3, 4], [5, 6]]" element-wise and confirms that calling json_free on [3, 4] leaves the outer array with just [1, 2] and [5, 6]; a top-level test uses a weak_ptr observer to confirm that json_free on the parser's root really destroys the tree. Co-authored-by: Cursor <cursoragent@cursor.com> * Migrate jsonpull to unique_ptr ownership Replaces the shared_ptr-based json_object_ptr with a unique_ptr that has a stateless custom deleter dispatching on json_object::type before calling the right subclass destructor. Eliminates per-node atomic reference-counting and the control-block allocation that shared_ptr required for every node in the tree. API now distinguishes owning and borrowing pointers explicitly: - json_read / json_read_separators / json_hash_get return raw json_object * (borrowed from the parser-owned tree). - json_read_tree / json_disconnect return json_object_ptr (caller takes ownership; back-pointers are cleared so the subtree can outlive the parser). - json_free / json_context / json_stringify take raw pointers. - The parser's container_stack holds raw pointers; jp->root keeps unique_ptr ownership of the most recent top-level value. Internally, take_from_owner moves the unique_ptr out of whichever parent vector / hash entry / parser root owned it, which both json_free and json_disconnect rely on. In the streaming parsers (parse_feature, parse_layers, the geojson-loop callback), we are careful to free `j` only after we have processed a complete Feature: json_read returns each token as the tree is being built up, and freeing an intermediate node would splice it out of the surrounding hash and corrupt the in-progress feature. Benchmark (tl_2022_us_county.json, -z0 --extend-zooms-if-still-dropping, median of 5 runs on macOS arm64): 8.5s, vs 10.6s with shared_ptr and 8.7s on the pre-refactor C baseline. Co-authored-by: Cursor <cursoragent@cursor.com> * Fix preprocessor mistakes identified by Copilot * Make indent * Skip non-string metadata.json entries instead of reading them as strings dirmeta2tmp() warned about a metadata entry that was not a string/string pair and then read it as a string anyway. Under the new type-tagged accessors that trips the assert in json_object::string(); before them it reinterpreted the node's storage as a char pointer, which segfaulted for most values. Either way, tippecanoe-decode and tile-join could not read a directory tileset whose metadata.json had a numeric minzoom or a nested object, which is common in metadata.json files written by other tools. Add the missing continue, and cover it in raw-tiles-test. pmtilesmeta2tmp() handles the same case correctly but read the key with string() before its own JSON_STRING check, so the assert would have fired ahead of the check meant to catch a bad key. Hoist the check above the read. The parser rejects non-string hash keys, so this is unreachable in practice; the ordering is what makes the check meaningful. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017KNxyHKasyWrWcvre2yK4r * Don't redefine _GNU_SOURCE in the C++ jsonpull port The `#define _GNU_SOURCE` carried over from jsonpull.c, where it was needed to get asprintf() declared. g++ already defines _GNU_SOURCE on the command line for C++ translation units, so redefining it warns: jsonpull/jsonpull.cpp:1: warning: "_GNU_SOURCE" redefined Guard the define rather than drop it, so platforms whose C++ driver does not predefine it still get asprintf() declared. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017KNxyHKasyWrWcvre2yK4r * Add a unit test for json_disconnect json_disconnect() is documented in jsonpull.h as the supported way to splice a subtree out of the parser's tree and take ownership of it, but nothing calls it: read_filter() and parse_filter() used to, and now get the same guarantee from json_read_tree() clearing back-pointers on the way out. Cover the behavior rather than leave the primitive dead and untested. The test pins that the subtree is removed from its parent, that the parser keeps the rest of the tree, and that the detached subtree stays readable after the json_pull is destroyed. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017KNxyHKasyWrWcvre2yK4r * Correct two stale comments in the jsonpull port jsonpull.h said a json_number is 40 bytes; it is 32 (json_object is 24, and the repr discriminator fits in the base class's tail padding, so the 8-byte union lands at offset 24). plugin.cpp's parse_feature() said `j` is freed only just before returning or as jp->root at end of stream, but there is a third json_free(j) at the bottom of the loop, for a complete Feature whose geometry came out empty. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017KNxyHKasyWrWcvre2yK4r * Add a changelog entry and bump the version for the jsonpull rewrite The rewrite is meant to be behavior-preserving, but it carries four user-visible bug fixes that warrant release notes: tippecanoe-json-tool --extract on a numeric attribute, surrogate-pair decoding, tile-join reading a non-string tilejson field type, and non-string values in a directory tileset's metadata.json. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017KNxyHKasyWrWcvre2yK4r * Encode U+FFFF as three bytes instead of an overlong four The \uXXXX decoder tested `ch < 0xFFFF` before taking the three-byte UTF-8 path, so U+FFFF itself fell through to the four-byte branch and came out as F0 8F BF BF -- an overlong, and therefore invalid, encoding of a code point that fits in three bytes. check_utf8() only checks that continuation bytes look like continuation bytes, not that a sequence is the shortest form, so nothing downstream noticed: a GeoJSON attribute containing U+FFFF put invalid UTF-8 into the output tile, where a strict consumer would reject it. Since `ch` is parsed from exactly four hex digits it cannot exceed 0xFFFF on its own, so after this change the four-byte branch is reached only for a code point assembled from a surrogate pair, which is the only way to name one above the BMP. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017KNxyHKasyWrWcvre2yK4r * Keep the parent links inside a detached jsonpull subtree json_read_tree() and json_disconnect() cleared both back-pointers on every node of the subtree they handed out. Clearing `parser` throughout is necessary -- the json_pull can be destroyed while the subtree lives on, so a surviving `parser` would dangle -- but clearing `parent` throughout cost more than it bought. `parent` is a non-owning raw pointer, so keeping it cannot form a reference cycle or keep anything alive; there is nothing to leak. And within a detached subtree it refers to nodes the caller now owns as a single unit, so it stays valid for exactly as long as the subtree itself. Clearing it only made the tree unwalkable upwards, and made json_free() and json_disconnect() silently no-ops on interior nodes of a detached tree, since both find a node's owner through o->parent. So clear `parser` everywhere and clear `parent` on the detached root alone, which is the one that pointed out of the subtree at a node the parser still owns. Split the old clear_back_pointers() into clear_parser_pointers() plus a detach_subtree() wrapper that adds the root's `parent`. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017KNxyHKasyWrWcvre2yK4r * Cover the U+FFFF encoding and detached-tree parent links Each of the new assertions fails against the previous behavior, so they pin the two fixes rather than merely passing alongside them: - the U+FFFF test, plus the U+FFFE boundary below it and a surrogate pair above it, so the three-byte and four-byte paths are both held in place - json_disconnect() leaving the parent links inside the subtree intact while clearing the root's - json_free() pruning an interior node of a tree whose parser is already gone, which only works because those links survive - json_free() of a hash value leaving the key paired with a JSON_NULL placeholder, which is the documented behavior and not a removal Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017KNxyHKasyWrWcvre2yK4r * Address review notes in jsonpull itself - json_stringify walked c_str(), so it truncated at an embedded NUL even though values are std::string now and carry one through faithfully. Range over the string instead; the existing control-character branch already escapes a NUL like any other, so the output stays valid JSON. - Assert that the hash has an entry waiting before add_object() assigns to entries().back(). It always does -- JSON_VALUE is only set by a colon, which requires a pushed key -- but the derivation is not local. - Drop fabricate_object(), a pass-through to make_object() with the arguments reordered, kept only to preserve the old C name. - Inline the string_append / string_append_c wrappers over push_back and append, and note that json_print_one's JSON_HASH and JSON_ARRAY branches are unreachable, since json_print handles both itself. - json_hash_get's comment said nullptr meant "the matching value is null", which reads as JSON null. A JSON null comes back as a JSON_NULL node; nullptr means the value slot is not filled in yet. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017KNxyHKasyWrWcvre2yK4r * Tidy jsonpull call sites flagged in review - geojson.cpp and attribute.cpp passed key_pool::pool() and set_attribute_accum() a c_str() from a std::string, forcing a needless reconstruction (and truncating at an embedded NUL). Both overloads take std::string, so pass it directly. The geojson.cpp one is the hottest loop in the program. - Replace the hand-maintained counters beside range-for loops in attribute.cpp, main.cpp and tile-join.cpp with indexed loops, since the index is only wanted for error messages. - parse_json_args took json_pull_ptr by value and then copied it, costing two refcount bumps per construction. Move it. - Assert that the parser is still attached where geojson.cpp reads geometry->parser->line. Only json_read results reach it today, but json_read_tree and json_disconnect now clear every parser pointer, so a detached tree would null-deref there instead of tripping an assert. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017KNxyHKasyWrWcvre2yK4r * Pin the array-splicing fix, and close four test gaps The existing pruning test does not discriminate: json_read hands back each container as it completes, so the node it frees is always the most recently added element of its parent -- the one case the old element-count-vs-byte- count memmove got right, because it then moved zero bytes. Widening that test to more elements does not change this; the shape is what matters, not the size. Verified: the eight-element streaming variant still passes against the pre-fix code. Add a test that builds the array first and then prunes element 0 of eight, asserting the identity of every survivor rather than just the resulting count. That fails against the pre-fix code deterministically, with arr[0] == arr[1] and the last element dropped. Note the limitation on the streaming test so the next reader does not try to strengthen it in place. Also cover, all previously untested: - json_free of a hash key, and of both halves of a pair, where the entry survives with a JSON_NULL stand-in until both are gone - repeated json_read_tree over a line-delimited stream, which is what the filter loaders and -L / -E do, asserting each detached tree survives the next read and the parser's destruction - json_stringify of a partially-parsed tree, the json_context error path - json_stringify across an embedded NUL, which fails against the c_str() walk this branch replaces Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017KNxyHKasyWrWcvre2yK4r * Add changelog entries for three unadvertised fixes The array-splicing fix goes first: it is a memory-corruption fix, and it is the strongest illustration of why the ownership model is worth having, since it is exactly the failure the model makes unrepresentable. Also the uninitialized read when a filter emitted "properties": null, and the evaluator.hpp include guard that defined EVALUATOR HPP and so never guarded anything. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017KNxyHKasyWrWcvre2yK4r * Consolidate the jsonpull comments Comments were 25% of the added lines, and the ownership model was spelled out in five places. Collect it into one block at the top of jsonpull.h and point at it from the rest, cutting the ratio to 14% and the total by about 200 lines. Removed the duplicate explanations of the deleter dispatch, of what detach does to the back-pointers, and of "json_read returns intermediate containers, do not free them". Trimmed the comments that argued for a choice rather than described the code -- the reserve(2) / reserve(4) rationales, the string-buffer copy, the pmtiles check ordering -- to a line each, and shortened the test preambles, keeping the parts that say why a test is shaped the way it is. No code changes; the test suite is unchanged in both configurations. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017KNxyHKasyWrWcvre2yK4r --------- Co-authored-by: Cursor <cursoragent@cursor.com> Co-authored-by: Claude <noreply@anthropic.com> |
||
|
|
63fcac725a |
Keep variable-depth tile pyramids consistent when a zoom level has to drop features (#407)
* Only skip polygon cleaning if we are still at very high resolution * Remove collinear points and clean polygons even at high resolution * If we truncated but still have the data and need to drop, revive * Deduplicate by ID even when the duplicate is clipped away * Test that deduplication works across tile boundaries * Write out children of a tile revived after its parent truncated A tile writes the geometry for its children on pass 0 of its zoom level, and the later passes, which are only retries with new thresholds, must not write it again. But a tile whose parent truncated its pyramid is skipped on pass 0, and is only revived on a later pass, once the zoom has had to start dropping features. Gating on pass 0 meant its children were never written at all, so a revived tile was always a dead end: it appeared in the output at ordinary detail with nothing below it, even though its truncated ancestor still held the full-detail geometry. Write the children on whichever pass first tiles the tile instead. The dropping thresholds only ever increase within a zoom, so for a revived tile that is exactly the pass on which the zoom started dropping. Also collect the three thresholds into dropping_features(), since write_tile() and run_thread() have to agree about when truncation is disabled and when a skipped tile comes back. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KewFM6XCt5W9QBZkTZDjCp * Treat dropping by attribute like the other ways of dropping features --drop-by-attribute-as-needed was added after variable-depth pyramids, and minattribute never made it into the test for whether a zoom level is discarding features. A zoom that was dropping by attribute could still truncate pyramids, so some of its tiles became full-detail leaves while the rest of the zoom had features dropped out of them, and tiles skipped because an ancestor had truncated stayed missing. Unlike the other thresholds, minattribute starts at the infinity on whichever side is being kept rather than at zero, so dropping_features() now takes the direction too. On tests/tl_2022_11_tract at -Z10 -M15000, zoom 11 was dropping by attribute and truncating two pyramids at the same time; now it truncates none of them and the tile that had been skipped under zoom 10's truncation is written out. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KewFM6XCt5W9QBZkTZDjCp * Delete the merged tile that the deduplication test leaves behind overzoom-test removes merged-dedup.pbf.json.check but not the merged-dedup.pbf it was decoded from, so the file was left in the working tree after every test run. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KewFM6XCt5W9QBZkTZDjCp * Document what variable-depth pyramids now do to geometry and to dropping Truncated tiles are no longer left uncleaned: they keep every vertex that isn't collinear with its neighbors, but their polygons are cleaned so that overlapping areas are merged instead of stacked. Say so, and say that dropping features at a zoom level now suppresses truncation for the whole zoom rather than for individual tiles. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KewFM6XCt5W9QBZkTZDjCp * Start minattribute out at the infinity that excludes nothing dropping_features() reads write_tile_args::minattribute, and the in-class default of 0 decodes as a threshold that has already been chosen. Every path assigns it from zoom_minattribute before anything reads it, so this changes no behavior, but a future one that didn't would silently suppress pyramid truncation rather than fail visibly. -HUGE_VAL is the value that excludes nothing for the ascending order that drop_by_attribute_descending also defaults to, so the two members agree. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KewFM6XCt5W9QBZkTZDjCp * Regenerate the drop-by-attribute fixture through the test harness The Makefile can't be asked for a target whose name contains an =, since make reads that as a variable assignment, so this fixture was generated by hand into a scratch directory. The output path ends up in the tileset's name, description, and generator_options, and tippecanoe-decode is only passed -x generator, so all three were compared against the harness's .check.mbtiles path and could never match. make test failed on it. Regenerated with the same output path the rule uses. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KewFM6XCt5W9QBZkTZDjCp * Restore the -z14 -M25000 variable-depth fixture This configuration was dropped rather than regenerated when the -z17 -M10000 fixture was added. It still runs, so it was losing a passing regression test for no stated reason. Regenerated against current behavior. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KewFM6XCt5W9QBZkTZDjCp * Encode the = in the drop-by-attribute fixture name as %3d A test output name containing an = can't be asked for on the make command line, because make reads that argument as a variable assignment, so the fixture couldn't be regenerated through its own rule. Add %3d to the punctuation escapes that testargs decodes and use it here. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KewFM6XCt5W9QBZkTZDjCp * Regenerate the man page for the README change The variable-depth pyramid option's description changed, and man/tippecanoe.1 is generated from README.md, so the committed page no longer matched. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KewFM6XCt5W9QBZkTZDjCp --------- Co-authored-by: Claude <noreply@anthropic.com> |
||
|
|
734bba7c78 |
Fix three latent defects exposed by compiler warnings, and clear the rest (#406)
* 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> |
||
|
|
0badb242be |
Variable-depth pyramids: don't prune children while a minzoom-gated feature is still pending (#399)
Don't prune variable-depth children while a minzoom-gated feature is pending The minzoom_feature_pending flag from #397 keeps a variable-depth pyramid subdividing until explicit per-feature minzooms are satisfied, but two gaps let features still be dropped: The flag was only set when tippecanoe_minzoom > z + 1, so a feature whose minzoom is exactly z + 1 never marked the tile pending, even though a leaf at z carries only z-visible content. The early-stop commit never consulted the flag: a tile that succeeded in stopping early inserted itself into skip_children_out unconditionally, pruning the children the pending feature needed. The flag only inflated estimated_complexity_out, which the pruning ignores. Set the flag for any feature excluded below its minzoom, include it in the early-stop veto, and skip child pruning while it is set. Adds a fixture covering the minzoom == z + 1 boundary; make test passes with no diffs to existing fixtures. |
||
|
|
0dc1e00eee |
Keep variable-depth pyramids from pruning features above their minzoom (#397)
--generate-variable-depth-tile-pyramid decides a tile is a leaf once its geometry fits at full detail, then prunes the tile's entire subtree. The guard that prevents leafing while deeper content is still pending consults only feature_minzoom (the automatic dot-dropping zoom); it does not consult tippecanoe_minzoom, the explicit per-feature minzoom set via the tippecanoe.minzoom attribute. So a feature carrying an explicit minzoom deeper than where its region leafs is excluded from the leaf tile (z < minzoom) while its children are never generated. It ends up in no tile at any zoom, silently dropped. next_feature() excludes such a feature and continues without returning it, so the leaf-prevention guard in write_tile() never sees it. Carry a flag out of next_feature() when an excluded feature first appears beyond the next zoom, and feed it into the same estimated-complexity path feature_minzoom already uses, so the pyramid keeps subdividing down to the feature's minzoom. The flag only affects estimated_complexity_out, which is written solely under --generate-variable-depth-tile-pyramid, so builds without that flag are unchanged. make test passes with no fixture diffs. |
||
|
|
cb6cacef15 |
Keep features at the attribute threshold instead of dropping them (384 follow up) (#385)
Keep features at the attribute threshold instead of dropping them |
||
|
|
eb9acf9d44 | Add --drop-by-attribute-as-needed option (#384) | ||
|
|
533e000faa |
Remove undocumented command-line options (#361)
* Remove --accumulate-numeric-attributes * Remove join-sqlite, etc. * Remove --accumulate-numeric-attributes from overzoom * Remove --assign-to-bins and --bin-by-id-list * Remove --clip-polygon and --clip-bounding-box * Remove FSL expressions * Update version and changelog |
||
|
|
2d548bed06 |
Infinite loop fixes, minimizing changes to behavior (#345)
* Divide-and-conquer polygon cleaning * Catch the case where the gap can't be increased further * Catch the case where we try to keep impossibly many features * Make label points earlier in the tiling process * Another case where it could try to drop even after already limiting. * And do not coalesce on impossibly small geometries * Add missing return * Update version and changelog |
||
|
|
bfb62ee2db |
Add missing case for accumulating the mean of attributes that are inconsistently present (#329)
* Add missing case for accumulating the mean of attributes that are inconsistently present * Add more specific test |
||
|
|
9d1637f7c1 | Option to not averaging clusters of points (#326) | ||
|
|
423fea1544 |
Reduce memory consumption during tiling and tile encoding (#319)
* Improve the memory spike during tile construction * Remove `need_tilestats`. Add a bunch of debug logging * Remove debug logging * Update version and changelog |
||
|
|
583fc3744a |
Reduce attribute accumulation memory consumption (#318)
* Add a flag to use an H3 index for the feature index
* Give mvt_value and serial_val a double-with-count concept
* Switch mean over to internal accumulation state
* Get rid of the attribute accumulation map
* Change vectors of features to vectors of pointers to features
* Fix --coalesce
* Revert "Add a flag to use an H3 index for the feature index"
This reverts commit
|
||
|
|
794ae2b1eb |
Tippecanoe-decode and tippecanoe-overzoom changes to improve binning performance (#292)
* Add a tippecanoe-decode option to restrict which attributes to decode * Plumb buffer and feature limit around * Check the feature limit * Clarifying cases where output detail can be unspecified * Clip bins to the tile buffer instead of just passing them through * Add missing include * Missed some tests * Add --no-tile-compression option to tippecanoe-overzoom * Update version and changelog |
||
|
|
23667bb8eb |
Reduce memory consumption from attribute accumulation (#290)
* Progress on plumbing a string pool for full_keys through
* More plumbing for key_pool
* Don't keep features with identical locations as multiplier features
* Revert "Don't keep features with identical locations as multiplier features"
This reverts commit
|
||
|
|
28efc40e6e |
Choose the megatile features from those that will be in the next N zooms (#280)
* Choose the megatile features from those that will be in the next N zooms * Take fractional zooms into account in multiplier feature choices * Fix more tests * Add a flag to retain multiplier features by minimum distance * Limit feature expansion from multiplier density to 2x * The multiplier cap was a bad idea * Revert "The multiplier cap was a bad idea" This reverts commit |
||
|
|
c5f2f0da34 |
More work on plumbing attribute accumulation through (#263)
* Plumb bounding boxes through potential intersections
* Quick bbox reject for bins that can't possibly intersect
* Inching toward attribute accumulation in megatile handling
* Some sort of test for how all these things interact with each other.
Automatic numeric attribute accumulation does *not* apply to attributes
that have an explicit attribute accumulator set, because the order of
operations is too messy and weird
* More sketching
* More sketching
* Actually do some accumulation
* Put all that behind an --accumulate-numeric flag
* Use the same attribute accumulation logic in binning as in megatiles
* Fix backwards conditional
* Add means, but somehow I have some counts of 0
* Handle aggregated attributes with no base attribute in the feature
* Checkpoint before I break everything
* Found a flaw, now to debug
* Fix a typo that broke accumulation
* Add binning tests
* Make sure IDs make it through on the bins
* Fix count/mean accumulation
* Make the numeric accumulation prefix configurable
* Make sure the accumulate test still works with a different prefix
* Forgot to update this test
* More testing to make sure cluster sizes make it all the way through
* Fix neglected --accumulate-attribute when binning
* Mark unexercised attribute accumulation cases as "can't happen"
* Factor out numeric preservation
* Attrs with the accumulation prefix are just preserved, not accumulated
* Test behavior of prefixed attributes
* Plumbing for exclude and exclude-prefix
* Implement and test attribute prefix stripping in overzoom
* Update version and changelog
* For debugging, make an attribute list of source feature IDs
* Revert "For debugging, make an attribute list of source feature IDs"
This reverts commit
|
||
|
|
51fcf142df |
Fix bad interaction between dynamic dropping and limiting by truncation (#260)
* Fix bad interaction between dynamic dropping and limiting by truncation * Don't enforce the tile size limit here if they said not to |
||
|
|
5b18eea673 |
Work in progress on binning features in overzoom (#258)
* Factoring out tilestats management from GeoJSON file reading * Move code around so overzoom can link against parse_layers * Read the file of bins * Plumb the bins through to overzoom() * Some zip code bins to test with * (Currently non-functional) test of binning * Starting to spell out the bin matching loop * Can't flatten points, so don't flatten bins either * More fleshing out bin traversal * Bounding box of tile-relative mvt geometry * Smallest enclosing tile from bbox * Most of the bin scan * Add point in polygon check. It crashes. * Find the matching bins * GDAL-style bounding boxes have eaten my brain * Make some features to bin into * Increment a count as features are found to be within the bins * Fix longitude wraparound in overzoom bins * Fix the tests * Push off attribute copying until after bin assignment * Carry sum of numeric attributes into the bins * Also add mean, min, and max * Add --calculate-feature-index since I keep needing it for testing * Add an option to accumulate sum/mean/max/min/count of all numeric attrs * Don't bake in tippecanoe:mean, since we redo it from sum and count * Forgot to update this test fixture after removing tiled mean * Update version and changelog |
||
|
|
40bb4ff732 |
Be more careful to retry when the feature count is exceeded (#257)
* Clip before dealing with multiplier or filters in overzoom * Be more careful to retry when the feature count is exceeded * Adjust the estimated total feature count for the multiplier too * Fix the feature count estimates, I think * Pass build info into the version string * Report the actual max zoom of any tiles as the metadata maxzoom * Revert unneeded renaming to make the diff more readable * Clean up the adjustments to tile sizes and feature counts * Update version and changelog * Dropping a feature into a multiplier cluster still effectively drops it * Update changelog * Rethink the changelog description * Don't try to truncate zooms if we are still tiling at z18 |
||
|
|
bc3ef87c3f |
Add --generate-variable-depth-tile-pyramid option (#251)
* Track output position at the file level instead of within each tile * Track file position where the child tile data begins * Add option and document its intended behavior * Changing the detail loop to account for stopping early * I forgot I already added an option for this * Stop early if we can make a complete tile * Add a test of zoom truncation with limited feature count * Forgot to commit the actual code change * Make room for a vertex count in the header of each serialized tile * Estimate tile complexity; don't try truncating when unlikely to work * Be more conservative, because ever retrying a tile is a big speed hit * If stopping early, don't simplify or clean; leave that to overzoom * Add tiny polygon reduction / dust to overzoom * Don't try to stop early in the children if we dropped anything by rate * Fflush here too before pwriting * Don't stop early if we ended up dropping any features. Rework the can-the-next-zoom-stop-early logic to avoid going one zoom further than needed. * Fix warning * Fix warnings * Oops, checking for the wrong expected return value * Cleanup from adding line simplification in overzoom * Current (wrong) behavior when combining coalescing and truncating * Keep a list of parent tiles to skip rather than truncating * Now the coalesced tiles in z12 get children in z13 * Don't double-count feature dropping when the zoom level is retried * Correct README description * Remove todo about special case below basezoom, which is accounted for * Be a little more aggressive in drop-densest determination * Scale tile feature limit for megatiles in the same way as byte limit * Fully deprecate -detect-shared-borders into an alias * Track the distances found in the douglas-peucker recursion * Serialize and deserialize the distance with the vertices * Revert "Serialize and deserialize the distance with the vertices" This reverts commit |
||
|
|
47e774adc4 |
Improve the appearance of coalesce-densest-as-needed tiles (#247)
* Start to distinguish fixed cluster density setting from as-needed density
* Make consistent {drop,coalesce}-densest decisions between zooms
* Actually track the previous index instead of just intending to
* Clean up collinearities in coalesced features
* To determine densest, look at actual physical distance, not just index
* Don't actually need the previous index in serial_feature now
* Center of mass of one feature to most distant point of the next
* Add apologetic comment
* Wait, how did the tests pass before?
* Revert "Wait, how did the tests pass before?"
This reverts commit
|
||
|
|
bb4f220678 |
Reduce tiling memory (#227)
* Trying to reduce memory in tiling * Let the simplification workers go out of scope earlier * Bail out quickly once the maximum feature count is reached * Update changelog and version |
||
|
|
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 |
||
|
|
6a8f1b83d8 |
Fix some undefined behavior (#209)
* Fix some undefined behavior
* Avoid overflow in line simplification calculations
* Oops, missed a test
* Didn't mean to add that to the Makefile
* Revert "Revert "[ci] test in debug mode (#202)""
This reverts commit
|
||
|
|
312e1560a3 |
Stabilize feature order in overzoom (#210)
* Stabilize feature order in overzoom
* I want my sorts to be stable, please
* Revert "[ci] test in debug mode (#202)"
This reverts commit
|
||
|
|
a987197eed |
Don't swap attributes when reducing tiny polygon dust (#207)
* Don't swap attributes when reducing tiny polygon dust Because the dust placeholder may be misleadingly far from the feature that contributed the most area to it * Remove unused arguments; update changelog |
||
|
|
2b6630c42c |
Allow features that would be dropped dynamically to become multiplier features (#199)
* Postpone tagging features as being the first of a multiplier cluster * Upgrade some dynamically dropped features to multiplier features * Still don't let it put more features in a cluster than is allowed * Update tests * Make the current multiplier cluster size per-layer * Make sure the first non-empty-geometry in the layer is marked as primary * Improve comments * Update changelog and version * Remove commented out debugging printf * Factor out duplicated code |
||
|
|
96f126dd59 |
FSL-style expressions can use unidecode data to smash case and diacritics (#197)
* Read unidecode data, do some plumbing of it * More unidecode plumbing * Do the unidecode smashing, but it doesn't seem to be working * Ah, that's better! * Add missing header * And reorder the includes too * Shortcut when there is no unidecode data to work with * Update version and changelog * Avoid repeated unidecode smashing of the same constant string |
||
|
|
4e52cbd957 |
Drop or retain whole multiplier clusters when dropping as needed (#198)
* Prep to track conditions other than just "dropped" or "kept" * Count up instead of down * Drop or retain whole multiplier clusters based on their first feature * Calculate a global feature dropping sequence * Switch over to using the drop sequence for drop-fraction * Remove unused arguments for the old drop-fraction implementation * Fix copy-and-paste bugs, update tests * Properly incorporate feature_minzoom into the drop sequence, I hope * Rename drop_by to drop_sequence * See if sorting within clusters fixes filter stability between zooms * Remove very chatty debug print * Update changelog and version * Use named constants instead of numbers for feature dropping/keeping * Add comment to explain purpose and method of bit reversal |
||
|
|
e2a7a409c7 |
Improve tiling speed (#195)
* 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 commit |
||
|
|
6a2bce8164 |
Scale the tile size limit up with the multiplier at low zooms (#192)
* Scale the tile size limit up with the multiplier at low zooms * Add a test to demonstrate that high zoom tiles can't be extra large * Update changelog and version * Fail more cleanly when a tile can't be made small enough * Guard against a cluster where the start marker has been dropped * Look harder for a working feature interval instead of giving up * That change to the drop-smallest logic changed a test output * Update changelog * Add explanatory comment |
||
|
|
7d4d264d50 |
Speeding up tippecanoe-overzoom (#191)
* Speed up mvt_value comparison
* Converting repetitive ifs to cases
* More conversions from ifs to cases
* Optimize the always-true filter case
* Don't convert types of attributes without accumulators
* Unordered map seems to be faster than map
* Add missing header
* Fix some warnings
* Fix the warnings better
* Avoid an int->string->int conversion
* Lazily initialize layer key and values maps when actually needed
* More switches from maps to unordered_maps
* Sure, I'll take the microoptimization
* More emplacement
* Save some copies
* Emplaces and moves
* Lazy linear scan of attributes instead of building a map
* Extra printfs, missing header
* Avoid clipping if the input and output tiles are the same
* But do clip if the tile extent is being reduced
* Make sure I'm not constructing std::strings here at runtime
* More worrying about runtime string construction
* A couple more std::moves
* Const references!
* More const references
* Another std::move
* Make the string_value of mvt_value std::optional
* Reserve storage when decoding
* Provision for different mvt_values to share a string pool
* Use the string pool when decoding
* Avoid another string construction
* Try limiting the depth of the search for duplicate attributes
* Revert "Try limiting the depth of the search for duplicate attributes"
This reverts commit
|
||
|
|
cbf222754b |
Add --accumulate-attribute to tippecanoe-overzoom (#189)
* Starting to factor out attribute accumulation into its own file * Continuing to factor out attribute accumulation * Reduce duplicate code * Plumbing the accumulate-attribute option around * Call the attribute accumulator * Test that accumulation works * Add missing #includes * Don't sort within individual multiplier clusters Doing so throws off the spatial distribution of the low zooms * Docs and changelog * Add comments |
||
|
|
f957f30f90 |
Clean up internal naming related to tilestats (#190)
* Get rid of the type_and_string near-synonym for serial_val
* Rename file_keys to the more familiar tilestats
* "tas" (type_and_string) => "sv" (serial_val)
* "fk" (tile_keys) => "ts" (tilestats)
* Revert ""tas" (type_and_string) => "sv" (serial_val)"
This reverts commit
|
||
|
|
679a0d62f2 |
Make feature ordering cooperate with --retain-points-multiplier (#188)
* Make feature ordering cooperate with --retain-points-multiplier * Forgot to check in the actual code changes??? * Sort within each multiplier cluster as well as between clusters * Correct description of behavior in changelog * Drag original feature sequence along in megatiles for post-filter sort * Plumb the preserve-input-order flag through overzoom * Sort in overzoom if requested * Use within-tile input sequence numbers, not global sequence numbers * Documentation * Reverse direction of search to prevent accidental skipping * Add some comments about converting between attribute representations |
||
|
|
5d92a17193 |
Add point retention multiplier (#179)
* Add an option to retain N times as many points as usual at each zoom * Tests for point multipler with specified and guessed maxzooms * Work in progress on inverse spatial ordering * Fix inverse spatial feature order * --reorder was depending on a feature index that wasn't being preserved * Separate ordering by feature_minzoom from ordering inverse-spatially * Add a test for the inverse spatial ordering * Store the basezoom/droprate/multiplier decisions in tileset metadata * Progress on adding filters to tippecanoe-overzoom * Type promotion for comparison * Look up the attribute value for ordering * Add test of thinning and ordering features * Plumb tippecanoe_decisions metadata through pmtiles * Be careful not to put infinities in JSON * Fix accidental dropping in what is meant to preserve sparse points * Start distinguishing true, false, and null in expressions * Most of the type conversions * Add boolean conversions * Literals and conjunctions * Add filtering to tippecanoe-overzoom * Add a test of filtering in overzoom * Fix boolean conjunctions * Handle the combination of cluster size and filtering * Rework dot dropping to reconcile density threshold and multiplier * Revert "Rework dot dropping to reconcile density threshold and multiplier" This reverts commit |
||
|
|
02e3bac2c0 |
Reduce memory consumption during tiling (#177)
* Drop duplicate geometries sooner in coalescing-as-needed * Concatenate geometries before partial cleaning * Spend less time with two feature representations in memory * Further reduce in-memory duplication * Remove another copy * Don't keep duplicates in memory while coalescing * Also clear out the mvt layer once it is no longer needed * Update changelog |
||
|
|
26bf08deb2 |
Externalizing polygon shard detection (#146)
* Start of externalizing polygon shard detection * Completely untested external quicksort * Add unit test for external quicksort * Remember to clean up temporary files * Sort and scan the vertices * Bring over more vertex logic * Make nodes from vertices * Checkpoint on switching over to global shared nodes * Do the thing * Revert unintended change to coalesced linestring behavior * Take shared nodes into account in early simplification * Let it do more sorting in memory * Fix overnoding of collinear linestrings * Remove duplicate nodes, since only duplicate vertices now matter * Remember to delete temporary files * Fix out of bounds memory access below, apparently * Fix the actual undefined behavior * Still running out of memory in one case. Find out where. * Forgot the conditional * Try again to make it not run out of memory * Update version and changelog |
||
|
|
f7dc7faf31 |
Reduce memory use of polygon shard detection (#139)
* Crunch down memory required per polygon joint * *Actually* reduce the size of the structure * Update version and changelog |
||
|
|
390771f855 |
Polygon shards (#105)
* Put all of this back in geometry.cpp for conflict resolution * Stabilize line simplification to behave the same regardless of winding * Round instead of truncating when clipping lines * Restore non-Wagyu polygon clipping from prior to |
||
|
|
e6d05bc317 |
Fix tile-join crash when trying to merge empty tilesets with --overzoom (#138)
* Fix tile-join crash when trying to merge empty tilesets with --overzoom * Add an option not to reduce tiny polygons to dust at maxzoom * Add test for prevention of tiny polygon reduction at maxzoom * Change version number |
||
|
|
2ec6180003 |
Fix "strategies" accounting for 0-length linestrings and degenerate polygons (#137)
* Dropping a 0-length feature doesn't count as dropping-as-needed * Add an option not to reduce tiny polygons to dust at maxzoom * Add test for prevention of tiny polygon reduction at maxzoom * Fix accounting for tiny polygons not to include degenerate geometries * Revert "Add test for prevention of tiny polygon reduction at maxzoom" This reverts commit |
||
|
|
6778aeac52 |
Add an option to extend zooms if still dropping, but with a limit (#131)
* Add an option to extend zooms if still dropping, but with a limit * At least when to overzoom, even if not actually doing it yet * Refactor to give tile-join access to overzoom() * Didn't work, but *might* have worked * OK, it did something now * Ah, there's the bug! * Hook up pmtiles and dirtiles as overzooming sources * Add command line option to enable or disable overzooming * Add (currently broken) test of overzooming in tile-join * Slightly more abstraction for the tile-join readers * Factor out duplicated code * Move construction into a constructor * More changing accessors to methods * Reduce magic * Start tracking a list of the tiles at maxzoom * I think it worked? * Add missing #include * Fix sequence of overzoomed tiles (Y sorts backwards for TMS) * Don't spend memory on overzooming when we aren't going to use it * Diff rather than cmp, in the hope of figuring out this broken test * Keep full coordinate precision if we might extend zooms * Try a slightly different byte limit * Make drop-densest more consistent across tile boundaries * Also affects this test * Does it behave any differently if it can extend forever? * I think the discrepancy is a thread-safety problem here * Revert "Does it behave any differently if it can extend forever?" This reverts commit |
||
|
|
4318e964e3 |
Add maximum pseudocluster size option for low-zoom points (#119)
* Trying to improve cluster positions * Fix centroid calculation * Just move points to cluster centroids; add point gap threshold * Add maximum-point-gap option * Fix formatting * Rename options for clarity; add tests; update changelog * Add missing break to switch * Revert unneeded constructor cleanup that broke a test somehow * Clean up comments * Remove unused --move-points-to-cluster-centroids * Mention tile-join change in changelog |
||
|
|
c1051232ad |
Do more of line simplification with integer coordinates (#113)
* Turn off unit, which has bit-rotted
* Instrument line simplification
* Do more work in integer space
* Update tests with simplification done with more integers
* Clean up
* Update changelog
* Eradicate calls to deprecated sprintf()
* Revert "Turn off unit, which has bit-rotted"
This reverts commit
|
||
|
|
523ca55243 |
Fix bugs in --no-simplification-of-shared-nodes (#99)
* Fix bugs in --no-simplification-of-shared-nodes * Update changelog |
||
|
|
329dd86bfd |
Add --cluster-maxzoom option (#75)
* Add `--cluster-maxzoom` option * Add tests, update changelog * Update manpage * Update tests --------- Co-authored-by: Aurele Nitoref <aurele.nitoref@icloud.com> |
||
|
|
3d714e32b0 |
Add point_count_abbreviated property to clustered features (#74)
* Add `point_count_abbreviated` property to clustered features * Update tests and changelog --------- Co-authored-by: Aurele Nitoref <aurele.nitoref@icloud.com> |