Files
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>
2026-08-13 21:27:07 -07:00

41 KiB

2.82.0

  • Fix corruption of a JSON array when a non-final element was removed from it. The old json_free / json_disconnect passed an element count to memmove where a byte count was required, so pruning element 0 of an 8-element array left the first two slots pointing at the same node -- a double free at teardown -- and silently dropped the last element. Only reachable through a whole-document tree, because removing the most recently added element made the bad memmove a zero-length no-op. (#388)
  • Rewrite jsonpull in C++ with unique_ptr ownership, std::vector for arrays and hash entries, and std::string for string values, replacing the hand-rolled malloc/realloc/free memory management. Value payloads now live in type-tagged subclasses reached through asserting accessors, so reading a hash as a string fails immediately instead of silently returning garbage. (#388)
  • Fix tippecanoe-json-tool --extract crashing on a numeric attribute, which read the number's storage as a string pointer. (#388)
  • Fix decoding of a \u escape sequence in which a high surrogate is followed by a non-surrogate BMP code point, which combined the two into a single wrong code point. (#388)
  • Fix a \uFFFF escape being decoded to the overlong, invalid four-byte UTF-8 sequence F0 8F BF BF instead of EF BF BF. (#388)
  • Fix tile-join reading a non-string field type out of a tileset's tilejson. (#388)
  • Fix tippecanoe-decode and tile-join crashing on a directory tileset whose metadata.json holds a non-string value, such as a numeric minzoom or a nested object. Those entries are now reported and skipped. (#388)
  • Fix an uninitialized read when a prefilter or postfilter emitted a feature with "properties": null. Both filter readers accepted a null properties and then read its length as though it were a hash, which was never initialized for a non-container node. (#388)
  • Fix the include guard in evaluator.hpp, which defined EVALUATOR HPP instead of EVALUATOR_HPP and so never guarded anything. (#388)

2.81.0

  • Add --drop-by-attribute-as-needed=attribute to drop the features with the lowest values of a numeric attribute from oversized tiles, and --drop-by-attribute-order=desc to drop the highest values instead. Features exactly at the threshold are kept rather than dropped. (#384, #385)
  • Add --exclude-all-tile-geometries to tile-join, to produce tiles that carry only attributes. (#382)
  • Generate each tool's usage message from the same option table that getopt_long() reads, so the hand-written lists in tile-join, tippecanoe-overzoom, tippecanoe-json-tool, tippecanoe-decode, and tippecanoe-enumerate can no longer fall behind the options actually accepted. Options previously reachable only by their short names are now listed. tippecanoe-overzoom reports a missing -o instead of passing a null pointer to fopen(), and tippecanoe prints its usage when run with no arguments. (#409)
  • Fix the radix sort used by --prefer-radix-sort. A bucket written out directly rather than through the merge was written one byte longer than its length prefix claimed, desynchronizing everything read from the geometry after it. Subdividing could also recurse forever once it ran out of files to split with, shifting by the full width of the index and writing past the end of the arrays of buckets. Radix-sorted output is now checked against the in-memory sort rather than against a stored copy. (#404)
  • Read FlatGeobuf integer and float properties as numbers. They were tagged with types that the rest of tippecanoe does not treat as numeric, so they were reported in tilestats as "mixed", with quoted values and no min or max, and warned when used as a feature ID. ULong properties are now also read as unsigned rather than signed. (#395)
  • Respect the -t temporary directory option in sorting operations, which previously always used the system temporary directory. (#368)
  • Keep --generate-variable-depth-tile-pyramid from silently dropping features whose explicit per-feature minzoom is deeper than the zoom at which their region becomes a leaf. Such a feature was excluded from the leaf tile while its children were never generated, so it appeared at no zoom at all. (#397, #399)
  • Drop a polygon hole that no remaining ring can parent, instead of failing the whole run. Degenerate input could abort tiling over a single unrepresentable sliver. (#401)
  • Clamp the feature extent to the long long range before converting it, at both ends. The previous extent <= LLONG_MAX guard was doubly wrong: LLONG_MAX is not representable as a double and rounds up, so an extent at the very top of the range overflowed the conversion and came out as the most negative value rather than the largest, and the guard admitted everything below LLONG_MIN as well, which overflowed the other way. Areas are signed, so holes that outweigh their rings can reach the low end. (#406)
  • Initialize the full width of the mvt_value numeric union, which left the bytes of the wider unused member indeterminate even though the implicit copy constructor copies the union as a whole. (#406)
  • Replace all variable-length arrays with std::vector and std::string, and build with -Wvla. VLAs are a compiler extension rather than standard C++, and clang warns about every one of them by default. (#406)
  • Remove the unused Dockerfiles, Travis configuration, and lambda directory. (#365)
  • Correct README statements that disagreed with the code. Among them, -aD and -aS were documented the wrong way round, --limit-base-zoom-to-maximum-zoom was given as -Pb rather than -pb, and the dot-dropping description had both the fraction and the zoom direction backwards: tippecanoe keeps 1/2.5 of the dots at zooms below the base zoom, rather than dropping that share above it. (#410)
  • Generate man/tippecanoe.1 with go-md2man rather than md2man-roff, which is packaged only as a Ruby gem and so had let the page drift out of date. The page now has a proper header and a NAME section, so man -k and whatis can find it, and no longer silently drops or mangles text the old converter mishandled. CI checks it against README.md. (#408)
  • Documentation fixes: correct three misspellings in the README and man page, repair the dead All Streets link, and tag more README code blocks with their language. (#375, #391, #400)

2.80.0

  • Remove undocumented command-line options

2.79.0

  • When deduplicating features by ID in tippecanoe-overzoom, be careful to track even features that have been clipped away.

2.78.0

  • Fix potential infinite loops in as-needed dropping and coalescing. When the threshold cannot be increased, it is now an error, rather than falling back to trying to lower the detail.
  • Cleaning of complex polygon geometries now happens in stages to avoid performance problems when there are very large numbers of vertices.
  • Label point generation happens earlier in tiling, to avoid doing slow operations on polygons that will not be retained anyway.

2.77.0

  • Add --deduplicate-by-id option to tippecanoe-overzoom

2.76.0

  • Add missing case for accumulating the mean of attributes that are inconsistently present
  • Add --keep-point-cluster-position (#326)

2.75.1

  • Further reduce memory consumption in attribute sorting and tilestats tracking

2.75.0

  • Reduce memory consumption in attribute accumulation and feature sorting

2.74.0

  • Add the option to join attributes from a sqlite database in tile-join
  • Improve tile-join's tileset bounding box calculations

2.73.0

  • Correctly clip features down to nothing when the clip region doesn't intersect the tile at all

2.72.0

  • Add --clip-polygon-file and --feature-filter-file options to tippecanoe-overzoom

2.71.0

  • Add --clip-bounding-box and --clip-polygon options to tippecanoe-overzoom

2.70.1

  • Raise tippecanoe-decode limit on the size of individual tiles

2.70.0

  • Performance improvements to tippecanoe-overzoom with attribute exclusion

2.69.0

  • Fix crash when the first bin gets clipped away

2.68.0

  • Adds --no-tile-compression option to tippecanoe-overzoom
  • Make tippecanoe-overzoom clip the output tile to the tile buffer after assigning points to bins
  • Adds --include/-y option to tippecanoe-decode to decode only specified attributes
  • Cleans up some inconsistent handling of variable tile extents in overzooming code
  • Speeds up overzooming slightly in tile-join by doing less preflighting to discover which child tiles contain features

2.67.0

  • Reduce memory consumption of duplicate attribute names in serial_feature
  • The maxzoom guess calculation now takes into account the number of duplicate feature locations

2.66.0

  • Only bin by ID, not by geometry, if --bin-by-id-list is specified
  • Do attribute accumulation in overzoom in mvt_value instead of converting to serial_val
  • Fix bool values read from flatgeobuf sources (#289)

2.65.0

  • Improve spatial distribution of --retain-points-multiplier features
  • Add --preserve-multiplier-density-threshold option to maintain minimum density of multiplier features

2.64.0

  • Add --bin-by-id to overzoom

2.63.0

  • Top-level null filter now evaluates to true

2.62.6

  • Remove buggy optimization to avoid reclipping in tippecanoe-overzoom

2.62.5

  • More aggressive binning when points fail the point-in-polygon test

2.62.4

  • Fix accumulation of count and mean in overzoom

2.62.3

  • Summary statistics with --accumulate-numeric-attributes make it from tiling through to binning
  • Prefix can be specified for --accumulate-numeric-attributes
  • Added --exclude and --exclude-prefix to tippecanoe-overzoom

2.62.2

  • Pass feature ID through with bins

2.62.1

  • More work in progress on binning point features in overzoom

2.62.0

  • Fix another bad interaction, this time between dropping-as-needed and --limit-tile-feature-count

2.61.0

  • Added --calculate-feature-index option
  • Added "count" accumulation type to --accumulate-attribute
  • Work in progress on binning of point features in overzoom. Not ready for use yet.

2.60.0

  • Fix bad interaction between --retain-points-multiplier and stopping early when the tile feature limit is reached
  • Fix another bad interaction between --retain-points-multiplier, dropping-as-needed, and variable depth tile pyramids
  • Add optional BUILD_INFO string to version
  • Reorder overzoom logic to clip before dealing with multiplier and filters
  • When --generate-variable-depth-tile-pyramid is in use, report the actual highest zoom generated as tileset maxzoom

2.59.0

  • Correct antimeridian_adjusted_bounds latitude calculation when vertices extend beyond the edge of the Mercator plane

2.58.0

  • Add --generate-variable-depth-tile-pyramid option
  • Add --line-simplification and --tiny-polygon-size options to tippecanoe-overzoom
  • Adjust tile feature limit for --retain-points-multiplier
  • Tune convergence rate for --coalesce-densest and --drop-densest
  • Fix overreported drop and coalesce counts in strategies

2.57.0

  • Add multi-tile input to tippecanoe-overzoom

2.56.0

  • Rework --coalesce-densest-as-needed and --drop-densest-as-needed to look better
  • Add --maximum-string-attribute-length option

2.55.0

  • Fix hash collisions in the string pool

2.53.0

  • Stop trying to add features to the tile after the feature limit is reached

2.52.0

  • Fix accidental loss (at all zooms) of features that specify an explicit minzoom

2.51.0

  • Fix null behavior in "in" expressions
  • But they are back to not using unidecode

2.50.0

  • FSL-style "in" expressions use unidecode again

2.49.0

  • FSL-style "in" expressions now allow numeric comparisons, but they no longer use unidecode to remove diacritics.

2.48.0

  • Fix some undefined behavior bugs, one of which results in slight changes to line simplification choices

2.47.0

  • Stabilize feature order in tippecanoe-overzoom when --preserve-feature-order is specified but the sequence attribute is not present

2.46.0

  • Polygon dust returns to having the attributes of the contributing feature nearest the placeholder instead of the contributing feature with the largest area.

2.45.0

  • Adjust tile size limit with --retain-points-multiplier dynamically within each tile, to allow multiplier features at high zooms if other features are being dropped as-needed

2.44.0

  • Add --unidecode-data option to allow case-insensitive filter comparisons of transliterated strings

2.43.0

  • Change -fraction-as-needed feature dropping to be consistent across tiles and zoom levels, and to follow the same pattern as point dropping by zoom level
  • With -as-needed feature dropping, drop or retain entire multiplier clusters instead of individual features
  • Sort the features within each multiplier cluster by its retention priority, for more consistency between zoom levels in filtered feature choice

2.42.0

  • Improve tiling speed
  • Generate tilestats for the --retain-points-multiplier attributes

2.41.3

  • Performance optimizations to tile reading, writing, and overzooming
  • Automatically vary the tile size limit by zoom level to match the intended retain-points-multiplier multiplication
  • Fix decompression error when bailing out because a tile can't be made small enough
  • Search harder for a feature size threshold to make the tile small enough before giving up

2.41.2

  • Add --accumulate-attribute to tippecanoe-overzoom
  • Go back to ordering features within each multiplier cluster spatially, not in the order specified for tile feature order

2.41.1

  • Make --preserve-input-order, --order-by, --order-descending-by, --order-smallest-first, and --order-largest-first cooperate with --retain-points-multiplier. The clusters will be ordered by their lead feature in the specified sequence. The other features in each cluster will continue to be physically near the lead feature, but ordered as specified within the cluster.

2.41.0

  • Add Felt-style expression support for -j feature filters
  • Add --retain-points-multiplier option
  • Add tippecanoe_decisions metadata field to record basezoom, drop rate, and multiplier
  • Add multiplier thinning (-m) and feature filters (-j) to tippecanoe-overzoom

2.40.0

  • Slightly reduce compression aggressiveness to improve as-needed dropping speed

2.39.0

  • Reduce memory usage during tiling

2.38.0

  • Tolerate polygon rings with insuffiently many points in input

2.37.1

  • Reduce maximum memory used for vertex sorting

2.37.0

  • Speed up tile-join overzooming and make it use less memory, by not including empty child tiles in the enumeration

2.36.0

  • Make tile-join distrust the source tilesets' metadata maxzoom and minzoom
  • Add a special case in --detect-longitude-wraparound not to wrap around jumps of exactly 360°

2.35.0

  • Fix a bug in --detect-longitude-wraparound when there are multiple rings

2.34.1

  • Further improvements to tile-join speed

2.34.0

  • Improve speed of overzooming in tile-join

2.33.0

  • Further reduce memory usage of --no-simplification-of-shared-nodes by calculating the list of shared nodes globally using temporary files rather than in memory for each individual tile
  • Make --no-simplification-of-shared-nodes behave for LineStrings as it does for Polygons, preventing simplification only at crossings, convergences, and divergences, not at every point along collinear segments

2.32.1

  • Reduce memory usage of --no-simplification-of-shared-nodes for polygons

2.32.0

  • Extend --no-simplification-of-shared-nodes to also simplify shared polygon borders consistently

2.31.0

  • Fix tile-join crash when trying to join empty tilesets
  • Add --no-tiny-polygon-reduction-at-maximum-zoom option

2.30.1

  • Fix spurious reports of tiny polygons and 0-length LineStrings in "strategies"

2.30.0

  • Add --extend-zooms-if-still-dropping-maximum option
  • Add --overzoom option to tile-join

2.29.0

  • Add tippecanoe-overzoom tool

2.28.1

  • Allow --set-attribute to override an existing attribute value

2.28.0

  • Add --preserve-point-density-threshold option to reduce blank areas of the map at low zooms
  • Fix tile-join bug where use of --read-from would also accidentally enable --quiet

2.27.0

  • Do more of line simplification in integer coordinates, to make behavior consistent across platforms
  • Reduce excessive logging during pmtiles conversion
  • Add --set-attribute option
  • Accept JSON form of --accumulate-attribute

2.26.1

  • Avoid crashing if there is a polygon ring with only one point

2.26.0

  • Fix bugs in --no-simplification-of-shared-nodes
  • Updated dockerfile (jtmiclat)
  • Set build options to use C++-17 (james2432)
  • Use std::fpclassify instead of plain fpclassify (james)
  • Fix pmtiles warnings (bdon)

2.25.0

  • Add --include/-y option to tile-join

2.24.0

  • Add --cluster-maxzoom option to limit zoom levels that receive clustering
  • Add point_count_abbreviated attribute to clustered features, for consistency with supercluster
  • Makefile changes to support FreeBSD
  • Add -r option to tile-join to provide a file containing a list of input files
  • Add antimeridian_adjusted_bounds field to tileset metadata

2.23.0

  • Remove the concept of "separate metadata." Features now always directly reference their keys and values rather than going through a second level of indirection.
  • Limit the size of the string pool to 1/5 the size of memory, to prevent thrashing during feature ingestion.
  • Avoid using writeable memory maps. Instead, explicitly copy data in and out of memory.
  • Compress streams of features in the temporary files, to reduce disk usage and I/O latency

2.22.0

  • Speed up feature dropping by removing unnecessary search for other small features

2.21.0

  • Improve label placement to avoid placing labels in polygon holes

2.20.0

  • Round coordinates instead of truncating them, for better precision when overzooming

2.19.0

  • Don't guess an excessively large maxzoom when there is only one feature
  • Set the base zoom for -Bg as part of the --smallest-maximum-zoom-guess logic

2.18.0

  • Fix crash when using tile-join to join an empty pmtiles tileset

2.17.0

  • Add pmtiles output format

2.16.0

  • During tiling, limit the size of the statistics that are kept for -as-needed calculations, because they can get quite large for sources with hundreds of millions of features.

2.15.2

  • Change tile hash function to fnv1a
  • Report JSON object context on the same line as the error message

2.15.1

  • Correct mbtiles inserts to use text instead of blob
  • Add an internal data structure to represent tileset metadata

2.15.0

  • Generate label points in a more straightforward checkerboard, and fewer of them at high zoom levels.

2.14.0

  • Don't preflight each zoom level if one of the as-needed options is specified. Instead, go ahead and write out the tiles, and then clear out the zoom level for another try if necessary.

2.13.1

  • Simplify geometry earlier when the in-memory representation of a tile gets large, to reduce peak memory usage

2.13.0

  • Add --limit-tile-feature-count and --limit-tile-feature-count-at-maximum-zoom
  • Coalesce small features only onto other small features with --coalesce-smallest-as-needed, never to large features
  • Clean coalesced-as-needed features before simplifying them, to improve simplification quality

2.12.0

  • Add --drop-denser option to drop points in dense clusters in preference to those in sparse areas.

2.11.0

  • Change sqlite3 schema to deduplicate identical tiles
  • Limit guessed maxzoom to avoid spending too many tiles on polygon fill

2.10.0

  • Upgrade flatbuffers version

2.9.1

  • Do label generation after simplification, not before.

2.9.0

  • Add an option to generate label points in place of polygons
  • Add --order-smallest-first and --order-largest-first options
  • When tiny polygons are being aggregated into dust, keep the attributes of the largest.

2.8.1

  • Improve precision of polygon ring area calculations

2.8.0

  • Add the option to use a different simplification level at maxzoom

2.7.0

  • Add the option to use the Visvalingam simplification algorithm

2.6.4

  • Update tests that should have been updated in 2.6.2

2.6.3

  • Fix crash in tile-join caused by wrong-way comparison

2.6.2

  • Stop adding features to a tile if it can't possibly work, to limit memory use
  • Add --integer and --fraction options to tippecanoe-decode
  • Carry strategies field from tileset metadata through tile-join

2.6.1

  • Upgrade protozero to version 1.7.1

2.6.0

  • Add another drop rate guessing options, from the same metrics -zg uses
  • Reduce maxzooms being guessed a little:
    • Use 1.5 standard deviations, not 2, as the minimum distinguishable
    • Give overlapping polygons and linestrings more distinct indices

2.5.0

  • Add an option to add extra detail at maxzoom that does not factor into guessing
  • Restore the intended behavior that tiny polygons don't get further simplified
  • Add an option to use single-precision floating point in tiles
  • Improve polygon simplification by choosing a better start/end point
  • Sort attribute values in tiles to make them compress a little better
  • Fix dropping of "largest" points when there are duplicate points
  • Add an option to prevent guessing a basezoom higher than the maxzoom
  • Add --order-by and --order-descending options

2.4.1

  • Accept tilestats limiting options in tile-join, not just tippecanoe

2.4.0

  • Change maxzoom guessing to take into account the standard deviation of the distances between features, so data with tight clusters will choose a higher maxzoom

2.3.2

  • Add --smallest-maximum-zoom-guess to guess maxzoom starting at some minimum

2.3.1

  • Track the desired tile size (the maximum size if no features were dropped) in each zoom level too.

2.3.0

  • Drop and coalesce points too as part of smallest-as-needed dropping and coalescing
  • Keep statistics in the tileset metadata of what tile size reduction strategies were used at each zoom level

2.2.0

  • Reduce memory consumption when parsing large JSON objects
  • Don't exit with an error if free disk space can't be determined

2.1.0

  • Add barebones support for FlatGeobuf input files

2.0.0

1.36.0

  • Update Wagyu to version 0.5.0

1.35.0

  • Fix calculation of mean when accumulating attributes in clusters

1.34.6

  • Fix crash when there are null entries in the metadata table

1.34.5

  • Fix line numbers in GeoJSON feature parsing error messages

1.34.4

  • Be careful to avoid undefined behavior from shifting negative numbers

1.34.3

  • Add an option to keep intersection nodes from being simplified away

1.34.2

  • Be more consistent about when longitudes beyond 180 are allowed. Now if the entire feature is beyond 180, it will still appear.

1.34.1

  • Don't run shell filters if the current zoom is below the minzoom
  • Fix -Z and -z for tile directories in tile-join and tippecanoe-decode
  • Return a successful error status for --help and --version

1.34.0

  • Record the command line options in the tileset metadata

1.33.0

  • MultiLineStrings were previously ignored in Geobuf input

1.32.12

  • Accept .mvt as well as .pbf in directories of tiles
  • Allow tippecanoe-decode and tile-join of directories with no metadata

1.32.11

  • Don't let attribute exclusion apply to the attribute that has been specified to become the feature ID

1.32.10

  • Fix a bug that disallowed a per-feature minzoom of 0

1.32.9

  • Limit tile detail to 30 and buffer size to 127 to prevent coordinate delta overflow in vector tiles.

1.32.8

  • Better error message if the output tileset already exists

1.32.7

  • Point features may now be coalesced into MultiPoint features with --coalesce.
  • Add --hilbert option to put features in Hilbert Curve sequence

1.32.6

  • Make it an error, not a warning, to have missing coordinates for a point

1.32.5

  • Use less memory on lines and polygons that are too small for the tile
  • Fix coordinate rounding problem that was causing --grid-low-zooms grids to be lost at low zooms if the original polygons were not aligned to tile boundaries

1.32.4

  • Ignore leading zeroes when converting string attributes to feature IDs

1.32.3

  • Add an option to convert stringified number feature IDs to numbers
  • Add an option to use a specified feature attribute as the feature ID

1.32.2

  • Warn in tile-join if tilesets being joined have inconsistent maxzooms

1.32.1

  • Fix null pointer crash when reading filter output that does not tag features with their extent
  • Add --clip-bounding-box option to clip input geometry

1.32.0

  • Fix a bug that allowed coalescing of features with mismatched attributes if they had been passed through a shell prefilter

1.31.7

  • Create the output tile directory even if there are no valid features

1.31.6

  • Issue an error message in tile-join if minzoom is greater than maxzoom

1.31.5

  • Add options to change the tilestats limits

1.31.4

  • Keep tile-join from generating a tileset name longer than 255 characters

1.31.3

  • Fix the missing filename in JSON parsing warning messages

1.31.2

  • Don't accept anything inside another JSON object's properties as a feature or geometry of its own.

1.31.1

  • Add --exclude-all to tile-join

1.31.0

  • Upgrade Wagyu to version 0.4.3

1.30.6

  • Take cluster distance into account when guessing a maxzoom

1.30.4

  • Features within the z0 tile buffer of the antimeridian (not only those that cross it) are duplicated on both sides.

1.30.3

  • Add an option to automatically assign ids to features

1.30.2

  • Don't guess a higher maxzoom than is allowed for manual selection

1.30.1

  • Ensure that per-feature minzoom and maxzoom are integers
  • Report compression errors in tippecanoe-decode
  • Add the ability to specify the file format with -L{"format":"…"}
  • Add an option to treat empty CSV columns as nulls, not empty strings

1.30.0

  • Add a filter extension to allow filtering individual attributes

1.29.3

  • Include a generator field in tileset metadata with the Tippecanoe version

1.29.2

  • Be careful to remove null attributes from prefilter/postfilter output

1.29.1

  • Add --use-source-polygon-winding and --reverse-source-polygon-winding

1.29.0

  • Add the option to specify layer file, name, and description as JSON
  • Add the option to specify the description for attributes in the tileset metadata
  • In CSV input, a trailing comma now counts as a trailing empty field
  • In tippecanoe-json-tool, an empty CSV field is now an empty string, not null (for consistency with tile-join)

1.28.1

  • Explicitly check for infinite and not-a-number input coordinates

1.28.0

  • Directly support gzipped GeoJSON as input files

1.27.16

  • Fix thread safety issues related to the out-of-disk-space checker

1.27.15

  • --extend-zooms-if-still-dropping now also extends zooms if features are dropped by --force-feature-limit

1.27.14

  • Use an exit status of 100 if some zoom levels were successfully written but not all zoom levels could be tiled.

1.27.13

  • Allow filtering features by zoom level in conditional expressions
  • Lines in CSV input with empty geometry columns will be ignored

1.27.12

  • Check integrity of sqlite3 file before decoding or tile-joining

1.27.11

  • Always include tile and layer in tippecanoe-decode, fixing corrupt JSON.
  • Clean up writing of JSON in general.

1.27.10

  • Add --progress-interval setting to reduce progress indicator frequency

1.27.9

  • Make clusters look better by averaging locations of clustered points

1.27.8

  • Add --accumulate-attribute to keep attributes of dropped, coalesced, or clustered features
  • Make sure numeric command line arguments are actually numbers
  • Don't coalesce features whose non-string-pool attributes don't match

1.27.7

  • Add an option to produce only a single tile
  • Retain non-ASCII characters in layernames generated from filenames
  • Remember to close input files after reading them
  • Add --coalesce-fraction-as-needed and --coalesce-densest-as-needed
  • Report distances in both feet and meters

1.27.6

  • Fix opportunities for integer overflow and out-of-bounds references

1.27.5

  • Add --cluster-densest-as-needed to cluster features
  • Add --maximum-tile-features to set the maximum number of features in a tile

1.27.4

  • Support CSV point input
  • Don't coalesce features that have different IDs but are otherwise identical
  • Remove the 700-point limit on coalesced features, since polygon merging is no longer a performance problem

1.27.3

  • Clean up duplicated code for reading tiles from a directory

1.27.2

  • Tippecanoe-decode can decode directories of tiles, not just mbtiles
  • The --allow-existing option works on directories of tiles
  • Trim .geojson, not just .json, when making layer names from filenames

1.27.1

  • Fix a potential null pointer when parsing GeoJSON with bare geometries
  • Fix a bug that could cause the wrong features to be coalesced when input was parsed in parallel

1.27.0

  • Add tippecanoe-json-tool for sorting and joining GeoJSON files
  • Fix problem where --detect-shared-borders could simplify polygons away
  • Attach --coalesce-smallest-as-needed leftovers to the last feature, not the first
  • Fix overflow when iterating through 0-length lists backwards

1.26.7

  • Add an option to quiet the progress indicator but not warnings
  • Enable more compiler warnings and fix related problems

1.26.6

  • Be more careful about checking for overflow when parsing numbers

1.26.5

  • Support UTF-16 surrogate pairs in JSON strings
  • Support arbitrarily long lines in CSV files.
  • Treat CSV fields as numbers only if they follow JSON number syntax

1.26.4

  • Array bounds bug fix in binary to decimal conversion library

1.26.3

  • Guard against impossible coordinates when decoding tilesets

1.26.2

  • Make sure to encode tile-joined integers as ints, not doubles

1.26.1

  • Add tile-join option to rename layers

1.26.0

Fix error when parsing attributes with empty-string keys

1.25.0

  • Add --coalesce-smallest-as-needed strategy for reducing tile sizes
  • Add --stats option to tipppecanoe-decode

1.24.1

  • Limit the size and depth of the string pool for better performance

1.24.0

  • Add feature filters using the Mapbox GL Style Specification filter syntax

1.23.0

  • Add input support for Geobuf file format

1.22.2

  • Add better diagnostics for NaN or Infinity in input JSON

1.22.1

  • Fix tilestats generation when long string attribute values are elided
  • Add option not to produce tilestats
  • Add tile-join options to select zoom levels to copy

1.22.0

  • Add options to filter each tile's contents through a shell pipeline

1.21.0

  • Generate layer, feature, and attribute statistics as part of tileset metadata

1.20.1

  • Close mbtiles file properly when there are no valid features in the input

1.20.0

  • Add long options to tippecanoe-decode and tile-join. Add --quiet to tile-join.

1.19.3

  • Upgrade protozero to version 1.5.2

1.19.2

  • Ignore UTF-8 byte order mark if present

1.19.1

  • Add an option to increase maxzoom if features are still being dropped

1.19.0

  • Tile-join can merge and create directories, not only mbtiles
  • Maxzoom guessing (-zg) takes into account resolution within each feature

1.18.2

  • Fix crash with very long (>128K) attribute values

1.18.1

  • Only warn once about invalid polygons in tippecanoe-decode

1.18.0

  • Fix compression of tiles in tile-join
  • Calculate the tileset bounding box in tile-join from the tile boundaries

1.17.7

  • Enforce polygon winding and closure rules in tippecanoe-decode

1.17.6

  • Add tile-join options to set name, attribution, description

1.17.5

  • Preserve the tileset names from the source mbtiles in tile-join

1.17.4

  • Fix RFC 8142 support: Don't try to split all memory mapped files

1.17.3

  • Support RFC 8142 GeoJSON text sequences

1.17.2

  • Organize usage output the same way as in the README

1.17.1

  • Add -T option to coerce the types of feature attributes

1.17.0

  • Add -zg option to guess an appropriate maxzoom

1.16.17

  • Clean up JSON parsing at the end of each FeatureCollection to avoid running out of memory

1.16.16

  • Add tile-join options to include or exclude specific layers

1.16.15

  • Add --output-to-directory and --no-tile-compression options

1.16.14

  • Add --description option for mbtiles metadata
  • Clean up some utility functions

1.16.13

  • Add --detect-longitude-wraparound option

1.16.12

  • Stop processing higher zooms when a feature reaches its explicit maxzoom tag

1.16.11

  • Remove polygon splitting, since polygon cleaning is now fast enough

1.16.10

  • Add a tippecanoe-decode option to specify layer names

1.16.9

  • Clean up layer name handling to fix layer merging crash

1.16.8

  • Fix some code that could sometimes try to divide by zero
  • Add check for $TIPPECANOE_MAX_THREADS environmental variable

1.16.7

  • Fix area of placeholders for degenerate multipolygons

1.16.6

  • Upgrade Wagyu to 0.3.0; downgrade C++ requirement to C++ 11

1.16.5

  • Add -z and -Z options to tippecanoe-decode

1.16.4

  • Use Wagyu's quick_lr_clip() instead of a separate implementation

1.16.3

  • Upgrade Wagyu to bfbf2893

1.16.2

  • Associate attributes with the right layer when explicitly tagged

1.16.1

  • Choose a deeper starting tile than 0/0/0 if there is one that contains all the features

1.16.0

  • Switch from Clipper to Wagyu for polygon topology correction

1.15.4

  • Dot-dropping with -r/-B doesn't apply if there is a per-feature minzoom tag

1.15.3

  • Round coordinates in low-zoom grid math instead of truncating

1.15.2

  • Add --grid-low-zooms option to snap low-zoom features to the tile grid

1.15.1

  • Stop --drop-smallest-as-needed from always dropping all points

1.15.0

  • New strategies for making tiles smaller, with uniform behavior across the whole zoom level: --increase-gamma-as-needed, --drop-densest-as-needed, --drop-fraction-as-needed, --drop-smallest-as-needed.
  • Option to specify the maximum tile size in bytes
  • Option to turn off tiny polygon reduction
  • Better error checking in JSON parsing

1.14.4

  • Make -B/-r feature-dropping consistent between tiles and zoom levels

1.14.3

  • Add --detect-shared-borders option for better polygon simplification

1.14.2

  • Enforce that string feature attributes must be encoded as UTF-8

1.14.1

  • Whitespace after commas in tile-join .csv input is no longer significant

1.14.0

  • Tile-join is multithreaded and can merge multiple vector mbtiles files together

1.13.0

  • Add the ability to specify layer names within the GeoJSON input

1.12.11

  • Don't try to revive a placeholder for a degenerate polygon that had negative area

1.12.10

  • Pass feature IDs through in tile-join

1.12.9

  • Clean up parsing and serialization. Provide some context with parsing errors.

1.12.8

  • Fix the spelling of the --preserve-input-order option

1.12.7

  • Support the "id" field of GeoJSON objects and vector tile features

1.12.6

  • Fix error reports when reading from an empty file with parallel input

1.12.5

  • Add an option to vary the level of line and polygon simplification
  • Be careful not to produce an empty tile if there was a feature with empty geometry.

1.12.4

  • Be even more careful not to produce features with empty geometry

1.12.3

  • Fix double-counted progress in the progress indicator

1.12.2

  • Add ability to specify a projection to tippecanoe-decode

1.12.1

  • Fix incorrect tile layer version numbers in tile-join output

1.12.0

  • Fix a tile-join bug that would retain fields that were supposed to be excluded

1.11.9

  • Add minimal support for alternate input projections (EPSG:3857).

1.11.8

  • Add an option to calculate the density of features as a feature attribute

1.11.7

  • Keep metadata together with geometry for features that don't span many tiles, to avoid extra memory load from indexing into a separate metadata file

1.11.6

  • Reduce the size of critical data structures to reduce dynamic memory use

1.11.5

  • Let zoom level 0 have just as much extent and buffer as any other zoom
  • Fix tippecanoe-decode bug that would sometimes show outer rings as inner

1.11.4

  • Don't let polygons with nonzero area disappear during cleaning

1.11.3

  • Internal code cleanup

1.11.2

  • Update Clipper to fix potential crash

1.11.1

  • Make better use of C++ standard libraries

1.11.0

  • Convert C source files to C++

1.10.0

  • Upgrade Clipper to fix potential crashes and improve polygon topology

1.9.16

  • Switch to protozero as the library for reading and writing protocol buffers

1.9.15

  • Add option not to clip features

1.9.14

  • Clean up polygons after coalescing, if necessary

1.9.13

  • Don't trust the OS so much about how many files can be open

1.9.12

  • Limit the size of the parallel parsing streaming input buffer
  • Add an option to set the tileset's attribution

1.9.11

  • Fix a line simplification crash when a segment degenerates to a single point

1.9.10

  • Warn if temporary disk space starts to run low

1.9.9

  • Add --drop-polygons to drop a fraction of polygons by zoom level
  • Only complain once about failing to clean polygons

1.9.8

  • Use an on-disk radix sort for the index to control virtual memory thrashing when the geometry and index are too large to fit in memory

1.9.7

  • Fix build problem (wrong spelling of long long max/min constants)

1.9.6

  • Add an option to give specific layer names to specific input files

1.9.5

  • Remove temporary files that were accidentally left behind
  • Be more careful about checking memory allocations and array bounds
  • Add GNU-style long options

1.9.4

  • Tippecanoe-decode can decode .pbf files that aren't in an .mbtiles container

1.9.3

  • Don't get stuck in a loop trying to split up very small, very complicated polygons

1.9.2

  • Increase maximum tile size for tippecanoe-decode

1.9.1

  • Incorporate Mapnik's Clipper upgrades for consistent results between Mac and Linux

1.9.0

  • Claim vector tile version 2 in mbtiles
  • Split too-complex polygons into multiple features

1.8.1

  • Bug fixes to maxzoom, and more tests

1.8.0

  • There are tests that can be run with "make test".

1.7.2

  • Feature properties that are arrays or hashes get stringified rather than being left out with a warning.

1.7.1

  • Make clipping behavior with no buffer consistent with Mapnik. Features that are exactly on a tile boundary appear in both tiles.

1.7.0

  • Parallel processing of input with -P works with streamed input too
  • Error handling if unsupported options given to -p or -a

1.6.4

  • Fix crashing bug when layers are being merged with -l

1.6.3

  • Add an option to do line simplification only at zooms below maxzoom

1.6.2

  • Make sure line simplification matches on opposite sides of a tile boundary

1.6.1

  • Use multiple threads for line simplification and polygon cleaning

1.6.0

  • Add option of parallelized input when reading from a line-delimited file

1.5.1

  • Fix internal error when number of CPUs is not a power of 2
  • Add missing #include

1.5.0

  • Base zoom for dot-dropping can be specified independently of maxzoom for tiling.
  • Tippecanoe can calculate a base zoom and drop rate for you.

1.4.3

  • Encode numeric attributes as integers instead of floating point if possible

1.4.2

  • Bug fix for problem that would occasionally produce empty point geometries
  • More bug fixes for polygon generation

1.4.1

  • Features that cross the antimeridian are split into two parts instead of being partially lost off the edge

1.4.0

  • More polygon correctness
  • Query the system for the number of available CPUs instead of guessing
  • Merge input files into one layer if a layer name is specified
  • Document and install tippecanoe-enumerate and tippecanoe-decode

1.3.0

  • Tile generation is multithreaded to take advantage of multiple CPUs
  • More compact data representation reduces memory usage and improves speed
  • Polygon clipping uses Clipper and makes sure interior and exterior rings are distinguished by winding order
  • Individual GeoJSON features can specify their own minzoom and maxzoom
  • New tile-join utility can add new properties from a CSV file to an existing tileset
  • Feature coalescing, line-reversing, and reordering by attribute are now options, not defaults
  • Output of decode utility is now in GeoJSON format
  • Tile generation with a minzoom spends less time on unused lower zoom levels
  • Bare geometries without a Feature wrapper are accepted
  • Default tile resolution is 4096 units at all zooms since renderers assume it

1.2.0

  • Switched to top-down rendering, yielding performance improvements
  • Add a dot-density gamma feature to thin out especially dense clusters
  • Add support for multiple layers, making it possible to include more than one GeoJSON featurecollection in a map. #29
  • Added flags that let you optionally avoid simplifying lines, restricting maximum tile sizes, and coalescing features #30
  • Added check that minimum zoom level is less than maximum zoom level
  • Added -v flag to check tippecanoe's version