Throw instead of exiting from the tile clipping and overzooming code

The code reachable from overzooming a tile no longer calls exit(). It
reports errors by throwing tippecanoe_error, and owns its FILE * and
json_pull * handles through RAII wrappers so that nothing leaks when an
error unwinds the stack.

New files:

* errors.hpp/errors.cpp: the tippecanoe_error exception class, plus
  throw_tippecanoe_error() and throw_perror() helpers.
* raii.hpp: unique_file and unique_json_pull, both std::unique_ptr
  aliases, for FILE * and json_pull *.

exit() becomes throw in clip.cpp, mvt.cpp, mvt.hpp, read_json.cpp,
evaluator.cpp, text.cpp, geometry.cpp, attribute.cpp, and
write_json.hpp. Each tool's main() is now a try/catch around an
inner_main(), so the command-line tools exit with the status they
always did.

Along the way this fixes a z_stream leaked when compress() failed, an
exit() that could run from json_writer's destructor during unwinding,
and an exit() in mvt_value's hash function.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DuCSVGssWNUCpVEE8Yp5HT
This commit is contained in:
Claude
2026-08-07 18:46:09 +00:00
parent 734bba7c78
commit 85547150c2
19 changed files with 272 additions and 170 deletions
+8
View File
@@ -1,5 +1,13 @@
# 2.81.0 # 2.81.0
* Report errors from the tile clipping and overzooming code by throwing a
`tippecanoe_error` rather than by calling `exit()`, and give that code
RAII ownership of its `FILE *` and `json_pull *` handles so that nothing
leaks when an error unwinds the stack. Each command-line tool catches the
exception in `main()` and exits with the same status it would have
before. Along the way this fixes a leaked `z_stream` when `compress()`
failed and two `exit()` calls that could run from a destructor or from
`mvt_value`'s hash function.
* Add `--drop-by-attribute-as-needed=`*attribute* to drop the features with * Add `--drop-by-attribute-as-needed=`*attribute* to drop the features with
the lowest values of a numeric attribute from oversized tiles, and the lowest values of a numeric attribute from oversized tiles, and
`--drop-by-attribute-order=desc` to drop the highest values instead. `--drop-by-attribute-order=desc` to drop the highest values instead.
+7 -7
View File
@@ -95,25 +95,25 @@ C = $(wildcard *.c) $(wildcard *.cpp)
INCLUDES = -I/usr/local/include -I. -Iclipper2/include INCLUDES = -I/usr/local/include -I. -Iclipper2/include
LIBS = -L/usr/local/lib LIBS = -L/usr/local/lib
tippecanoe: geojson.o jsonpull/jsonpull.o tile.o pool.o mbtiles.o geometry.o projection.o memfile.o mvt.o serial.o main.o platform.o text.o dirtiles.o pmtiles_file.o plugin.o read_json.o write_json.o geobuf.o flatgeobuf.o evaluator.o geocsv.o csv.o geojson-loop.o json_logger.o visvalingam.o compression.o clip.o sort.o attribute.o thread.o shared_borders.o usage.o clipper2/src/clipper.engine.o tippecanoe: geojson.o jsonpull/jsonpull.o tile.o pool.o mbtiles.o geometry.o projection.o memfile.o mvt.o serial.o main.o platform.o text.o dirtiles.o pmtiles_file.o plugin.o read_json.o write_json.o geobuf.o flatgeobuf.o evaluator.o geocsv.o csv.o geojson-loop.o json_logger.o visvalingam.o compression.o clip.o sort.o attribute.o thread.o shared_borders.o usage.o clipper2/src/clipper.engine.o errors.o
$(CXX) $(PG) $(LIBS) $(FINAL_FLAGS) $(CXXFLAGS) -o $@ $^ $(LDFLAGS) -lm -lz -lsqlite3 -lpthread $(CXX) $(PG) $(LIBS) $(FINAL_FLAGS) $(CXXFLAGS) -o $@ $^ $(LDFLAGS) -lm -lz -lsqlite3 -lpthread
tippecanoe-enumerate: enumerate.o usage.o tippecanoe-enumerate: enumerate.o usage.o
$(CXX) $(PG) $(LIBS) $(FINAL_FLAGS) $(CXXFLAGS) -o $@ $^ $(LDFLAGS) -lsqlite3 $(CXX) $(PG) $(LIBS) $(FINAL_FLAGS) $(CXXFLAGS) -o $@ $^ $(LDFLAGS) -lsqlite3
tippecanoe-decode: decode.o projection.o mvt.o write_json.o text.o jsonpull/jsonpull.o dirtiles.o pmtiles_file.o usage.o tippecanoe-decode: decode.o projection.o mvt.o write_json.o text.o jsonpull/jsonpull.o dirtiles.o pmtiles_file.o usage.o errors.o
$(CXX) $(PG) $(LIBS) $(FINAL_FLAGS) $(CXXFLAGS) -o $@ $^ $(LDFLAGS) -lm -lz -lsqlite3 $(CXX) $(PG) $(LIBS) $(FINAL_FLAGS) $(CXXFLAGS) -o $@ $^ $(LDFLAGS) -lm -lz -lsqlite3
tile-join: tile-join.o platform.o projection.o mbtiles.o mvt.o memfile.o dirtiles.o jsonpull/jsonpull.o text.o evaluator.o csv.o write_json.o pmtiles_file.o clip.o attribute.o thread.o read_json.o usage.o clipper2/src/clipper.engine.o tile-join: tile-join.o platform.o projection.o mbtiles.o mvt.o memfile.o dirtiles.o jsonpull/jsonpull.o text.o evaluator.o csv.o write_json.o pmtiles_file.o clip.o attribute.o thread.o read_json.o usage.o clipper2/src/clipper.engine.o errors.o
$(CXX) $(PG) $(LIBS) $(FINAL_FLAGS) $(CXXFLAGS) -o $@ $^ $(LDFLAGS) -lm -lz -lsqlite3 -lpthread $(CXX) $(PG) $(LIBS) $(FINAL_FLAGS) $(CXXFLAGS) -o $@ $^ $(LDFLAGS) -lm -lz -lsqlite3 -lpthread
tippecanoe-json-tool: jsontool.o jsonpull/jsonpull.o csv.o text.o geojson-loop.o usage.o tippecanoe-json-tool: jsontool.o jsonpull/jsonpull.o csv.o text.o geojson-loop.o usage.o errors.o
$(CXX) $(PG) $(LIBS) $(FINAL_FLAGS) $(CXXFLAGS) -o $@ $^ $(LDFLAGS) -lm -lz -lsqlite3 -lpthread $(CXX) $(PG) $(LIBS) $(FINAL_FLAGS) $(CXXFLAGS) -o $@ $^ $(LDFLAGS) -lm -lz -lsqlite3 -lpthread
unit: unit.o text.o sort.o mvt.o projection.o clip.o attribute.o jsonpull/jsonpull.o evaluator.o read_json.o clipper2/src/clipper.engine.o unit: unit.o text.o sort.o mvt.o projection.o clip.o attribute.o jsonpull/jsonpull.o evaluator.o read_json.o clipper2/src/clipper.engine.o errors.o
$(CXX) $(PG) $(LIBS) $(FINAL_FLAGS) $(CXXFLAGS) -o $@ $^ $(LDFLAGS) -lm -lz -lsqlite3 -lpthread $(CXX) $(PG) $(LIBS) $(FINAL_FLAGS) $(CXXFLAGS) -o $@ $^ $(LDFLAGS) -lm -lz -lsqlite3 -lpthread
tippecanoe-overzoom: overzoom.o mvt.o clip.o evaluator.o jsonpull/jsonpull.o text.o attribute.o read_json.o projection.o read_json.o usage.o clipper2/src/clipper.engine.o tippecanoe-overzoom: overzoom.o mvt.o clip.o evaluator.o jsonpull/jsonpull.o text.o attribute.o read_json.o projection.o read_json.o usage.o clipper2/src/clipper.engine.o errors.o
$(CXX) $(PG) $(LIBS) $(FINAL_FLAGS) $(CXXFLAGS) -o $@ $^ $(LDFLAGS) -lm -lz -lsqlite3 -lpthread $(CXX) $(PG) $(LIBS) $(FINAL_FLAGS) $(CXXFLAGS) -o $@ $^ $(LDFLAGS) -lm -lz -lsqlite3 -lpthread
-include $(wildcard *.d) -include $(wildcard *.d)
@@ -128,7 +128,7 @@ clean:
rm -f ./tippecanoe ./tippecanoe-* ./tile-join ./unit *.o *.d */*.o */*.d tests/**/*.mbtiles tests/**/*.check rm -f ./tippecanoe ./tippecanoe-* ./tile-join ./unit *.o *.d */*.o */*.d tests/**/*.mbtiles tests/**/*.check
indent: indent:
clang-format -i -style="{BasedOnStyle: Google, IndentWidth: 8, UseTab: Always, AllowShortIfStatementsOnASingleLine: false, ColumnLimit: 0, ContinuationIndentWidth: 8, SpaceAfterCStyleCast: true, IndentCaseLabels: false, AllowShortBlocksOnASingleLine: false, AllowShortFunctionsOnASingleLine: false, SortIncludes: false}" $(filter-out flatgeobuf.cpp,$(C)) $(H) jsonpull/*.[ch] clang-format -i -style="{BasedOnStyle: Google, IndentWidth: 8, UseTab: Always, AllowShortIfStatementsOnASingleLine: false, ColumnLimit: 0, ContinuationIndentWidth: 8, SpaceAfterCStyleCast: true, IndentCaseLabels: false, AllowShortBlocksOnASingleLine: false, AllowShortFunctionsOnASingleLine: false, SortIncludes: false, PointerAlignment: Right, DerivePointerAlignment: false}" $(filter-out flatgeobuf.cpp,$(C)) $(H) jsonpull/*.[ch]
TESTS = $(wildcard tests/*/out/*.json) TESTS = $(wildcard tests/*/out/*.json)
SPACE = $(NULL) $(NULL) SPACE = $(NULL) $(NULL)
+10 -17
View File
@@ -5,6 +5,7 @@
#include "serial.hpp" #include "serial.hpp"
#include "jsonpull/jsonpull.h" #include "jsonpull/jsonpull.h"
#include "milo/dtoa_milo.h" #include "milo/dtoa_milo.h"
#include "raii.hpp"
std::map<std::string, attribute_op> numeric_operations = { std::map<std::string, attribute_op> numeric_operations = {
{"sum", op_sum}, {"sum", op_sum},
@@ -33,8 +34,7 @@ void set_attribute_accum(std::unordered_map<std::string, attribute_op> &attribut
} else if (type == "count") { } else if (type == "count") {
t = op_count; t = op_count;
} else { } else {
fprintf(stderr, "Attribute method (%s) must be sum, product, mean, max, min, concat, comma, or count\n", type.c_str()); throw_tippecanoe_error(EXIT_ARGS, "Attribute method (%s) must be sum, product, mean, max, min, concat, comma, or count", type.c_str());
exit(EXIT_ARGS);
} }
attribute_accum.insert(std::pair<std::string, attribute_op>(name, t)); attribute_accum.insert(std::pair<std::string, attribute_op>(name, t));
@@ -42,17 +42,15 @@ void set_attribute_accum(std::unordered_map<std::string, attribute_op> &attribut
void set_attribute_accum(std::unordered_map<std::string, attribute_op> &attribute_accum, const char *arg, char **argv) { void set_attribute_accum(std::unordered_map<std::string, attribute_op> &attribute_accum, const char *arg, char **argv) {
if (*arg == '{') { if (*arg == '{') {
json_pull *jp = json_begin_string(arg); unique_json_pull jp(json_begin_string(arg));
json_object *o = json_read_tree(jp); json_object *o = json_read_tree(jp.get());
if (o == NULL) { if (o == NULL) {
fprintf(stderr, "%s: -E%s: %s\n", *argv, arg, jp->error); throw_tippecanoe_error(EXIT_JSON, "%s: -E%s: %s", *argv, arg, jp->error);
exit(EXIT_JSON);
} }
if (o->type != JSON_HASH) { if (o->type != JSON_HASH) {
fprintf(stderr, "%s: -E%s: not a JSON object\n", *argv, arg); throw_tippecanoe_error(EXIT_JSON, "%s: -E%s: not a JSON object", *argv, arg);
exit(EXIT_JSON);
} }
for (size_t i = 0; i < o->value.object.length; i++) { for (size_t i = 0; i < o->value.object.length; i++) {
@@ -60,26 +58,22 @@ void set_attribute_accum(std::unordered_map<std::string, attribute_op> &attribut
json_object *v = o->value.object.values[i]; json_object *v = o->value.object.values[i];
if (k->type != JSON_STRING) { if (k->type != JSON_STRING) {
fprintf(stderr, "%s: -E%s: key %zu not a string\n", *argv, arg, i); throw_tippecanoe_error(EXIT_JSON, "%s: -E%s: key %zu not a string", *argv, arg, i);
exit(EXIT_JSON);
} }
if (v->type != JSON_STRING) { if (v->type != JSON_STRING) {
fprintf(stderr, "%s: -E%s: value %zu not a string\n", *argv, arg, i); throw_tippecanoe_error(EXIT_JSON, "%s: -E%s: value %zu not a string", *argv, arg, i);
exit(EXIT_JSON);
} }
set_attribute_accum(attribute_accum, k->value.string.string, v->value.string.string); set_attribute_accum(attribute_accum, k->value.string.string, v->value.string.string);
} }
json_free(o); json_free(o);
json_end(jp);
return; return;
} }
const char *s = strchr(arg, ':'); const char *s = strchr(arg, ':');
if (s == NULL) { if (s == NULL) {
fprintf(stderr, "-E%s option must be in the form -Ename:method\n", arg); throw_tippecanoe_error(EXIT_ARGS, "-E%s option must be in the form -Ename:method", arg);
exit(EXIT_ARGS);
} }
std::string name = std::string(arg, s - arg); std::string name = std::string(arg, s - arg);
@@ -171,8 +165,7 @@ static void preserve_attribute1(attribute_op const &op, std::string const &key,
break; break;
default: default:
fprintf(stderr, "can't happen: operation that isn't used by --accumulate-numeric-attributes\n"); throw_tippecanoe_error(EXIT_IMPOSSIBLE, "can't happen: operation that isn't used by --accumulate-numeric-attributes");
exit(EXIT_IMPOSSIBLE);
} }
full_keys.push_back(key_pool.pool(key)); full_keys.push_back(key_pool.pool(key));
+12 -24
View File
@@ -46,8 +46,7 @@ drawvec simple_clip_poly(drawvec &geom, long long minx, long long miny, long lon
tmp = clip_poly1(tmp, minx, miny, maxx, maxy, ax, ay, bx, by, edge_nodes, prevent_simplify_shared_nodes); tmp = clip_poly1(tmp, minx, miny, maxx, maxy, ax, ay, bx, by, edge_nodes, prevent_simplify_shared_nodes);
if (tmp.size() > 0) { if (tmp.size() > 0) {
if (tmp[0].first != tmp[tmp.size() - 1].first || tmp[0].second != tmp[tmp.size() - 1].second) { if (tmp[0].first != tmp[tmp.size() - 1].first || tmp[0].second != tmp[tmp.size() - 1].second) {
fprintf(stderr, "Internal error: Polygon ring not closed\n"); throw_tippecanoe_error(EXIT_FAILURE, "Internal error: Polygon ring not closed");
exit(EXIT_FAILURE);
} }
} }
for (size_t k = 0; k < tmp.size(); k++) { for (size_t k = 0; k < tmp.size(); k++) {
@@ -60,8 +59,7 @@ drawvec simple_clip_poly(drawvec &geom, long long minx, long long miny, long lon
i = j - 1; i = j - 1;
} else { } else {
fprintf(stderr, "Unexpected operation in polygon %d\n", (int) geom[i].op); throw_tippecanoe_error(EXIT_IMPOSSIBLE, "Unexpected operation in polygon %d", (int) geom[i].op);
exit(EXIT_IMPOSSIBLE);
} }
} }
@@ -246,8 +244,7 @@ static void decode_clipped(mapbox::geometry::multi_polygon<long long> &t, drawve
double area = get_area(ring, 0, ring.size()); double area = get_area(ring, 0, ring.size());
if ((j == 0 && area < 0) || (j != 0 && area > 0)) { if ((j == 0 && area < 0) || (j != 0 && area > 0)) {
fprintf(stderr, "Ring area has wrong sign: %f for %zu\n", area, j); throw_tippecanoe_error(EXIT_IMPOSSIBLE, "Ring area has wrong sign: %f for %zu", area, j);
exit(EXIT_IMPOSSIBLE);
} }
for (size_t k = 0; k < ring.size(); k++) { for (size_t k = 0; k < ring.size(); k++) {
@@ -362,8 +359,7 @@ drawvec clean_or_clip_poly(drawvec &geom, int z, int buffer, bool clip, bool try
fprintf(f, "\n\n\n\n\n"); fprintf(f, "\n\n\n\n\n");
fclose(f); fclose(f);
fprintf(stderr, "Internal error: Polygon cleaning failed. Log in /tmp/wagyu.log\n"); throw_tippecanoe_error(EXIT_IMPOSSIBLE, "Internal error: Polygon cleaning failed. Log in /tmp/wagyu.log");
exit(EXIT_IMPOSSIBLE);
} }
if (scale != 1) { if (scale != 1) {
@@ -439,8 +435,7 @@ drawvec clip_poly_poly(drawvec const &geom, drawvec const &bounds) {
result.clear(); result.clear();
wagyu.execute(mapbox::geometry::wagyu::clip_type_intersection, result, mapbox::geometry::wagyu::fill_type_positive, mapbox::geometry::wagyu::fill_type_positive); wagyu.execute(mapbox::geometry::wagyu::clip_type_intersection, result, mapbox::geometry::wagyu::fill_type_positive, mapbox::geometry::wagyu::fill_type_positive);
} catch (std::runtime_error &e) { } catch (std::runtime_error &e) {
fprintf(stderr, "Internal error: Polygon clipping failed\n"); throw_tippecanoe_error(EXIT_IMPOSSIBLE, "Internal error: Polygon clipping failed");
exit(EXIT_IMPOSSIBLE);
} }
} }
@@ -635,8 +630,7 @@ double get_area_scaled(const drawvec &geom, size_t i, size_t j) {
} }
} }
fprintf(stderr, "get_area_scaled: can't happen\n"); throw_tippecanoe_error(EXIT_IMPOSSIBLE, "get_area_scaled: can't happen");
exit(EXIT_IMPOSSIBLE);
} }
double get_area(const drawvec &geom, size_t i, size_t j) { double get_area(const drawvec &geom, size_t i, size_t j) {
@@ -754,8 +748,7 @@ static bool inside(std::pair<double, double> d, int edge, long long minx, long l
return d.first > minx; return d.first > minx;
} }
fprintf(stderr, "internal error inside\n"); throw_tippecanoe_error(EXIT_FAILURE, "internal error inside");
exit(EXIT_FAILURE);
} }
static std::pair<double, double> intersect(std::pair<double, double> a, std::pair<double, double> b, int edge, long long minx, long long miny, long long maxx, long long maxy) { static std::pair<double, double> intersect(std::pair<double, double> a, std::pair<double, double> b, int edge, long long minx, long long miny, long long maxx, long long maxy) {
@@ -773,8 +766,7 @@ static std::pair<double, double> intersect(std::pair<double, double> a, std::pai
return std::pair<double, double>(minx, (a.second + (double) (b.second - a.second) * (minx - a.first) / (b.first - a.first))); return std::pair<double, double>(minx, (a.second + (double) (b.second - a.second) * (minx - a.first) / (b.first - a.first)));
} }
fprintf(stderr, "internal error intersecting\n"); throw_tippecanoe_error(EXIT_FAILURE, "internal error intersecting");
exit(EXIT_FAILURE);
} }
// http://en.wikipedia.org/wiki/Sutherland%E2%80%93Hodgman_algorithm // http://en.wikipedia.org/wiki/Sutherland%E2%80%93Hodgman_algorithm
@@ -910,8 +902,7 @@ void douglas_peucker(drawvec &geom, int start, int n, double e, size_t kept, siz
std::stack<int> recursion_stack; std::stack<int> recursion_stack;
if (!geom[start + 0].necessary || !geom[start + n - 1].necessary) { if (!geom[start + 0].necessary || !geom[start + n - 1].necessary) {
fprintf(stderr, "endpoints not marked necessary\n"); throw_tippecanoe_error(EXIT_IMPOSSIBLE, "endpoints not marked necessary");
exit(EXIT_IMPOSSIBLE);
} }
int prev = 0; int prev = 0;
@@ -1237,12 +1228,10 @@ std::string overzoom(std::vector<input_tile> const &tiles, int nz, int nx, int n
try { try {
bool was_compressed; bool was_compressed;
if (!tile.decode(t.tile, was_compressed)) { if (!tile.decode(t.tile, was_compressed)) {
fprintf(stderr, "Couldn't parse tile %d/%u/%u\n", t.z, t.x, t.y); throw_tippecanoe_error(EXIT_MVT, "Couldn't parse tile %d/%u/%u", t.z, t.x, t.y);
exit(EXIT_MVT);
} }
} catch (std::exception const &e) { } catch (std::exception const &e) {
fprintf(stderr, "PBF decoding error in tile %d/%u/%u\n", t.z, t.x, t.y); throw_tippecanoe_error(EXIT_PROTOBUF, "PBF decoding error in tile %d/%u/%u", t.z, t.x, t.y);
exit(EXIT_PROTOBUF);
} }
source_tile out; source_tile out;
@@ -1892,8 +1881,7 @@ drawvec fix_polygon(const drawvec &geom, bool use_winding, bool reverse_winding)
i = j - 1; i = j - 1;
outer = 0; outer = 0;
} else { } else {
fprintf(stderr, "Internal error: polygon ring begins with %d, not moveto\n", geom[i].op); throw_tippecanoe_error(EXIT_IMPOSSIBLE, "Internal error: polygon ring begins with %d, not moveto", geom[i].op);
exit(EXIT_IMPOSSIBLE);
} }
} }
+13 -1
View File
@@ -589,7 +589,7 @@ void usage(char **argv) {
exit(EXIT_ARGS); exit(EXIT_ARGS);
} }
int main(int argc, char **argv) { int inner_main(int argc, char **argv) {
extern int optind; extern int optind;
extern char *optarg; extern char *optarg;
int i; int i;
@@ -666,3 +666,15 @@ int main(int argc, char **argv) {
return 0; return 0;
} }
int main(int argc, char **argv) {
try {
return inner_main(argc, argv);
} catch (tippecanoe_error &e) {
fprintf(stderr, "%s\n", e.what());
return e.exit_code;
} catch (std::exception &e) {
fprintf(stderr, "Error: %s\n", e.what());
return EXIT_FAILURE;
}
}
+31
View File
@@ -0,0 +1,31 @@
#include <stdio.h>
#include <stdarg.h>
#include <string.h>
#include <errno.h>
#include "errors.hpp"
[[noreturn]] void throw_tippecanoe_error(int code, const char *fmt, ...) {
va_list ap;
va_start(ap, fmt);
char *msg = NULL;
if (vasprintf(&msg, fmt, ap) < 0) {
va_end(ap);
throw tippecanoe_error(code, "error (could not format message)");
}
va_end(ap);
std::string s(msg);
free(msg);
// Remove trailing newline if present (stderr messages often have one)
while (s.size() > 0 && s.back() == '\n') {
s.pop_back();
}
throw tippecanoe_error(code, s);
}
[[noreturn]] void throw_perror(int code, const char *msg) {
throw tippecanoe_error(code, std::string(msg) + ": " + strerror(errno));
}
+36
View File
@@ -1,3 +1,6 @@
#ifndef ERRORS_HPP
#define ERRORS_HPP
#define EXIT_INCOMPLETE 100 #define EXIT_INCOMPLETE 100
#define EXIT_ARGS 101 #define EXIT_ARGS 101
#define EXIT_CLOSE 102 #define EXIT_CLOSE 102
@@ -21,3 +24,36 @@
#define EXIT_WRITE 120 #define EXIT_WRITE 120
// avoid 124, 125, 126, 127, 137, which are used by GNU timeout // avoid 124, 125, 126, 127, 137, which are used by GNU timeout
#include <stdexcept>
#include <string>
#include <exception>
class tippecanoe_error : public std::runtime_error {
public:
int exit_code;
tippecanoe_error(int code, const std::string &message)
: std::runtime_error(message), exit_code(code) {
}
};
// Format and throw a tippecanoe_error. Use in place of fprintf(stderr,...)+exit().
[[noreturn]] void throw_tippecanoe_error(int code, const char *fmt, ...)
__attribute__((format(printf, 2, 3)));
// Throw from errno-based errors (replaces perror()+exit() pattern)
[[noreturn]] void throw_perror(int code, const char *msg);
// Check the void* return value from pthread_join. If a thread caught an
// exception, the return value is a heap-allocated std::exception_ptr.
// This re-throws it on the joining thread, or does nothing if retval is NULL.
inline void rethrow_if_thread_failed(void *retval) {
if (retval != NULL) {
std::exception_ptr *ep = (std::exception_ptr *) retval;
std::exception_ptr copy = *ep;
delete ep;
std::rethrow_exception(copy);
}
}
#endif
+27 -50
View File
@@ -6,6 +6,7 @@
#include "mvt.hpp" #include "mvt.hpp"
#include "evaluator.hpp" #include "evaluator.hpp"
#include "errors.hpp" #include "errors.hpp"
#include "raii.hpp"
#include "milo/dtoa_milo.h" #include "milo/dtoa_milo.h"
#include "text.hpp" #include "text.hpp"
@@ -48,8 +49,7 @@ int compare(mvt_value const &one, json_object *two, bool &fail) {
break; break;
case mvt_no_such_key: case mvt_no_such_key:
default: default:
fprintf(stderr, "Internal error: bad mvt type %d\n", one.type); throw_tippecanoe_error(EXIT_IMPOSSIBLE, "Internal error: bad mvt type %d", one.type);
exit(EXIT_IMPOSSIBLE);
} }
if (v < two->value.number.number) { if (v < two->value.number.number) {
@@ -84,8 +84,7 @@ int compare(mvt_value const &one, json_object *two, bool &fail) {
break; break;
} }
fprintf(stderr, "Internal error: bad mvt type %d\n", one.type); throw_tippecanoe_error(EXIT_IMPOSSIBLE, "Internal error: bad mvt type %d", one.type);
exit(EXIT_IMPOSSIBLE);
} }
// 0: false // 0: false
@@ -119,39 +118,33 @@ static int eval(std::function<mvt_value(std::string const &)> feature, json_obje
} }
if (f == NULL || f->type != JSON_ARRAY) { if (f == NULL || f->type != JSON_ARRAY) {
fprintf(stderr, "Filter is not an array: %s\n", json_stringify(f)); throw_tippecanoe_error(EXIT_FILTER, "Filter is not an array: %s", json_stringify(f));
exit(EXIT_FILTER);
} }
if (f->value.array.length < 1) { if (f->value.array.length < 1) {
fprintf(stderr, "Array too small in filter: %s\n", json_stringify(f)); throw_tippecanoe_error(EXIT_FILTER, "Array too small in filter: %s", json_stringify(f));
exit(EXIT_FILTER);
} }
if (f->value.array.array[0]->type != JSON_STRING) { if (f->value.array.array[0]->type != JSON_STRING) {
fprintf(stderr, "Filter operation is not a string: %s\n", json_stringify(f)); throw_tippecanoe_error(EXIT_FILTER, "Filter operation is not a string: %s", json_stringify(f));
exit(EXIT_FILTER);
} }
if (strcmp(f->value.array.array[0]->value.string.string, "has") == 0 || if (strcmp(f->value.array.array[0]->value.string.string, "has") == 0 ||
strcmp(f->value.array.array[0]->value.string.string, "!has") == 0) { strcmp(f->value.array.array[0]->value.string.string, "!has") == 0) {
if (f->value.array.length != 2) { if (f->value.array.length != 2) {
fprintf(stderr, "Wrong number of array elements in filter: %s\n", json_stringify(f)); throw_tippecanoe_error(EXIT_FILTER, "Wrong number of array elements in filter: %s", json_stringify(f));
exit(EXIT_FILTER);
} }
if (strcmp(f->value.array.array[0]->value.string.string, "has") == 0) { if (strcmp(f->value.array.array[0]->value.string.string, "has") == 0) {
if (f->value.array.array[1]->type != JSON_STRING) { if (f->value.array.array[1]->type != JSON_STRING) {
fprintf(stderr, "\"has\" key is not a string: %s\n", json_stringify(f)); throw_tippecanoe_error(EXIT_FILTER, "\"has\" key is not a string: %s", json_stringify(f));
exit(EXIT_FILTER);
} }
return feature(std::string(f->value.array.array[1]->value.string.string)).type != mvt_no_such_key; return feature(std::string(f->value.array.array[1]->value.string.string)).type != mvt_no_such_key;
} }
if (strcmp(f->value.array.array[0]->value.string.string, "!has") == 0) { if (strcmp(f->value.array.array[0]->value.string.string, "!has") == 0) {
if (f->value.array.array[1]->type != JSON_STRING) { if (f->value.array.array[1]->type != JSON_STRING) {
fprintf(stderr, "\"!has\" key is not a string: %s\n", json_stringify(f)); throw_tippecanoe_error(EXIT_FILTER, "\"!has\" key is not a string: %s", json_stringify(f));
exit(EXIT_FILTER);
} }
return feature(std::string(f->value.array.array[1]->value.string.string)).type == mvt_no_such_key; return feature(std::string(f->value.array.array[1]->value.string.string)).type == mvt_no_such_key;
} }
@@ -164,12 +157,10 @@ static int eval(std::function<mvt_value(std::string const &)> feature, json_obje
strcmp(f->value.array.array[0]->value.string.string, "<") == 0 || strcmp(f->value.array.array[0]->value.string.string, "<") == 0 ||
strcmp(f->value.array.array[0]->value.string.string, "<=") == 0) { strcmp(f->value.array.array[0]->value.string.string, "<=") == 0) {
if (f->value.array.length != 3) { if (f->value.array.length != 3) {
fprintf(stderr, "Wrong number of array elements in filter: %s\n", json_stringify(f)); throw_tippecanoe_error(EXIT_FILTER, "Wrong number of array elements in filter: %s", json_stringify(f));
exit(EXIT_FILTER);
} }
if (f->value.array.array[1]->type != JSON_STRING) { if (f->value.array.array[1]->type != JSON_STRING) {
fprintf(stderr, "comparison key is not a string: %s\n", json_stringify(f)); throw_tippecanoe_error(EXIT_FILTER, "comparison key is not a string: %s", json_stringify(f));
exit(EXIT_FILTER);
} }
mvt_value ff = feature(std::string(f->value.array.array[1]->value.string.string)); mvt_value ff = feature(std::string(f->value.array.array[1]->value.string.string));
@@ -223,8 +214,7 @@ static int eval(std::function<mvt_value(std::string const &)> feature, json_obje
return cmp <= 0; return cmp <= 0;
} }
fprintf(stderr, "Internal error: can't happen: %s\n", json_stringify(f)); throw_tippecanoe_error(EXIT_IMPOSSIBLE, "Internal error: can't happen: %s", json_stringify(f));
exit(EXIT_IMPOSSIBLE);
} }
if (strcmp(f->value.array.array[0]->value.string.string, "all") == 0 || if (strcmp(f->value.array.array[0]->value.string.string, "all") == 0 ||
@@ -266,13 +256,11 @@ static int eval(std::function<mvt_value(std::string const &)> feature, json_obje
if (strcmp(f->value.array.array[0]->value.string.string, "in") == 0 || if (strcmp(f->value.array.array[0]->value.string.string, "in") == 0 ||
strcmp(f->value.array.array[0]->value.string.string, "!in") == 0) { strcmp(f->value.array.array[0]->value.string.string, "!in") == 0) {
if (f->value.array.length < 2) { if (f->value.array.length < 2) {
fprintf(stderr, "Array too small in filter: %s\n", json_stringify(f)); throw_tippecanoe_error(EXIT_FILTER, "Array too small in filter: %s", json_stringify(f));
exit(EXIT_FILTER);
} }
if (f->value.array.array[1]->type != JSON_STRING) { if (f->value.array.array[1]->type != JSON_STRING) {
fprintf(stderr, "\"!in\" key is not a string: %s\n", json_stringify(f)); throw_tippecanoe_error(EXIT_FILTER, "\"!in\" key is not a string: %s", json_stringify(f));
exit(EXIT_FILTER);
} }
mvt_value ff = feature(std::string(f->value.array.array[1]->value.string.string)); mvt_value ff = feature(std::string(f->value.array.array[1]->value.string.string));
@@ -321,13 +309,11 @@ static int eval(std::function<mvt_value(std::string const &)> feature, json_obje
if (strcmp(f->value.array.array[0]->value.string.string, "attribute-filter") == 0) { if (strcmp(f->value.array.array[0]->value.string.string, "attribute-filter") == 0) {
if (f->value.array.length != 3) { if (f->value.array.length != 3) {
fprintf(stderr, "Wrong number of array elements in filter: %s\n", json_stringify(f)); throw_tippecanoe_error(EXIT_FILTER, "Wrong number of array elements in filter: %s", json_stringify(f));
exit(EXIT_FILTER);
} }
if (f->value.array.array[1]->type != JSON_STRING) { if (f->value.array.array[1]->type != JSON_STRING) {
fprintf(stderr, "\"attribute-filter\" key is not a string: %s\n", json_stringify(f)); throw_tippecanoe_error(EXIT_FILTER, "\"attribute-filter\" key is not a string: %s", json_stringify(f));
exit(EXIT_FILTER);
} }
bool ok = eval(feature, f->value.array.array[2], exclude_attributes, unidecode_data) > 0; bool ok = eval(feature, f->value.array.array[2], exclude_attributes, unidecode_data) > 0;
@@ -338,14 +324,12 @@ static int eval(std::function<mvt_value(std::string const &)> feature, json_obje
return true; return true;
} }
fprintf(stderr, "Unknown filter %s\n", json_stringify(f)); throw_tippecanoe_error(EXIT_FILTER, "Unknown filter %s", json_stringify(f));
exit(EXIT_FILTER);
} }
bool evaluate(std::function<mvt_value(std::string const &)> feature, std::string const &layer, json_object *filter, std::set<std::string> &exclude_attributes, std::vector<std::string> const &unidecode_data) { bool evaluate(std::function<mvt_value(std::string const &)> feature, std::string const &layer, json_object *filter, std::set<std::string> &exclude_attributes, std::vector<std::string> const &unidecode_data) {
if (filter == NULL || filter->type != JSON_HASH) { if (filter == NULL || filter->type != JSON_HASH) {
fprintf(stderr, "Error: filter is not a hash: %s\n", json_stringify(filter)); throw_tippecanoe_error(EXIT_JSON, "Error: filter is not a hash: %s", json_stringify(filter));
exit(EXIT_JSON);
} }
bool ok = true; bool ok = true;
@@ -365,34 +349,27 @@ bool evaluate(std::function<mvt_value(std::string const &)> feature, std::string
} }
json_object *read_filter(const char *fname) { json_object *read_filter(const char *fname) {
FILE *fp = fopen(fname, "r"); unique_file fp(fopen(fname, "r"));
if (fp == NULL) { if (fp.get() == NULL) {
perror(fname); throw_perror(EXIT_OPEN, fname);
exit(EXIT_OPEN);
} }
json_pull *jp = json_begin_file(fp); unique_json_pull jp(json_begin_file(fp.get()));
json_object *filter = json_read_tree(jp); json_object *filter = json_read_tree(jp.get());
if (filter == NULL) { if (filter == NULL) {
fprintf(stderr, "%s: %s\n", fname, jp->error); throw_tippecanoe_error(EXIT_JSON, "%s: %s", fname, jp.get()->error);
exit(EXIT_JSON);
} }
json_disconnect(filter); json_disconnect(filter);
json_end(jp);
fclose(fp);
return filter; return filter;
} }
json_object *parse_filter(const char *s) { json_object *parse_filter(const char *s) {
json_pull *jp = json_begin_string(s); unique_json_pull jp(json_begin_string(s));
json_object *filter = json_read_tree(jp); json_object *filter = json_read_tree(jp.get());
if (filter == NULL) { if (filter == NULL) {
fprintf(stderr, "Could not parse filter %s\n", s); throw_tippecanoe_error(EXIT_JSON, "Could not parse filter %s\n%s", s, jp.get()->error);
fprintf(stderr, "%s\n", jp->error);
exit(EXIT_JSON);
} }
json_disconnect(filter); json_disconnect(filter);
json_end(jp);
return filter; return filter;
} }
+1 -2
View File
@@ -508,8 +508,7 @@ drawvec stairstep(drawvec &geom, int z, int detail) {
// out.push_back(draw(VT_LINETO, xx, yy)); // out.push_back(draw(VT_LINETO, xx, yy));
} else { } else {
fprintf(stderr, "Can't happen: stairstepping lineto with no moveto\n"); throw_tippecanoe_error(EXIT_IMPOSSIBLE, "Can't happen: stairstepping lineto with no moveto");
exit(EXIT_IMPOSSIBLE);
} }
} }
+14 -2
View File
@@ -365,7 +365,7 @@ void join_csv(json_object *j) {
vo->value.number.large_unsigned = 0; vo->value.number.large_unsigned = 0;
vo->value.number.large_signed = 0; vo->value.number.large_signed = 0;
} else { } else {
abort(); throw_tippecanoe_error(EXIT_IMPOSSIBLE, "unexpected JSON attribute type %d", attr_type);
} }
properties->value.object.keys[properties->value.object.length] = ko; properties->value.object.keys[properties->value.object.length] = ko;
@@ -437,7 +437,7 @@ void usage(char **argv) {
exit(EXIT_ARGS); exit(EXIT_ARGS);
} }
int main(int argc, char **argv) { int inner_main(int argc, char **argv) {
const char *csv = NULL; const char *csv = NULL;
strip_usage_headings(long_options, real_long_options); strip_usage_headings(long_options, real_long_options);
@@ -520,3 +520,15 @@ int main(int argc, char **argv) {
return fail; return fail;
} }
int main(int argc, char **argv) {
try {
return inner_main(argc, argv);
} catch (tippecanoe_error &e) {
fprintf(stderr, "%s\n", e.what());
return e.exit_code;
} catch (std::exception &e) {
fprintf(stderr, "Error: %s\n", e.what());
return EXIT_FAILURE;
}
}
+13 -1
View File
@@ -3165,7 +3165,7 @@ void usage(char **argv, int status) {
exit(status); exit(status);
} }
int main(int argc, char **argv) { int inner_main(int argc, char **argv) {
#ifdef MTRACE #ifdef MTRACE
mtrace(); mtrace();
#endif #endif
@@ -3870,6 +3870,18 @@ int main(int argc, char **argv) {
return ret; return ret;
} }
int main(int argc, char **argv) {
try {
return inner_main(argc, argv);
} catch (tippecanoe_error &e) {
fprintf(stderr, "%s\n", e.what());
return e.exit_code;
} catch (std::exception &e) {
fprintf(stderr, "Error: %s\n", e.what());
return EXIT_FAILURE;
}
}
int mkstemp_cloexec(char *name) { int mkstemp_cloexec(char *name) {
int fd = mkstemp(name); int fd = mkstemp(name);
if (fd >= 0) { if (fd >= 0) {
+11 -19
View File
@@ -102,6 +102,7 @@ int compress(std::string const &input, std::string &output, bool gz) {
deflate_s.next_out = (Bytef *) (output.data() + length); deflate_s.next_out = (Bytef *) (output.data() + length);
int ret = deflate(&deflate_s, Z_FINISH); int ret = deflate(&deflate_s, Z_FINISH);
if (ret != Z_STREAM_END && ret != Z_OK && ret != Z_BUF_ERROR) { if (ret != Z_STREAM_END && ret != Z_OK && ret != Z_BUF_ERROR) {
deflateEnd(&deflate_s);
return -1; return -1;
} }
length += (increase - deflate_s.avail_out); length += (increase - deflate_s.avail_out);
@@ -118,7 +119,7 @@ bool mvt_tile::decode(const std::string &message, bool &was_compressed) {
if (is_compressed(message)) { if (is_compressed(message)) {
std::string uncompressed; std::string uncompressed;
if (decompress(message, uncompressed) == 0) { if (decompress(message, uncompressed) == 0) {
exit(EXIT_MVT); throw_tippecanoe_error(EXIT_MVT, "Tile decompression failed");
} }
src = uncompressed; src = uncompressed;
was_compressed = true; was_compressed = true;
@@ -368,11 +369,9 @@ std::string mvt_tile::encode() {
value_writer.add_bool(7, pbv.numeric_value.bool_value); value_writer.add_bool(7, pbv.numeric_value.bool_value);
break; break;
case mvt_null: case mvt_null:
fprintf(stderr, "Internal error: trying to write null attribute to tile\n"); throw_tippecanoe_error(EXIT_IMPOSSIBLE, "Internal error: trying to write null attribute to tile");
exit(EXIT_IMPOSSIBLE);
default: default:
fprintf(stderr, "Internal error: trying to write undefined attribute type to tile\n"); throw_tippecanoe_error(EXIT_IMPOSSIBLE, "Internal error: trying to write undefined attribute type to tile");
exit(EXIT_IMPOSSIBLE);
} }
std::shared_ptr<sorted_value> sv = std::make_shared<sorted_value>(); std::shared_ptr<sorted_value> sv = std::make_shared<sorted_value>();
@@ -451,8 +450,7 @@ std::string mvt_tile::encode() {
long long dy = wwy - py; long long dy = wwy - py;
if (dx < INT_MIN || dx > INT_MAX || dy < INT_MIN || dy > INT_MAX) { if (dx < INT_MIN || dx > INT_MAX || dy < INT_MIN || dy > INT_MAX) {
fprintf(stderr, "Internal error: Geometry delta is too big: %lld,%lld\n", dx, dy); throw_tippecanoe_error(EXIT_IMPOSSIBLE, "Internal error: Geometry delta is too big: %lld,%lld", dx, dy);
exit(EXIT_IMPOSSIBLE);
} }
geometry.push_back(protozero::encode_zigzag32(dx)); geometry.push_back(protozero::encode_zigzag32(dx));
@@ -464,8 +462,7 @@ std::string mvt_tile::encode() {
} else if (op == mvt_closepath) { } else if (op == mvt_closepath) {
length++; length++;
} else { } else {
fprintf(stderr, "\nInternal error: corrupted geometry\n"); throw_tippecanoe_error(EXIT_IMPOSSIBLE, "Internal error: corrupted geometry");
exit(EXIT_IMPOSSIBLE);
} }
} }
@@ -514,8 +511,7 @@ bool mvt_value::operator<(const mvt_value &o) const {
return numeric_value.null_value < o.numeric_value.null_value; return numeric_value.null_value < o.numeric_value.null_value;
default: default:
fprintf(stderr, "mvt_value::operator<<: can't happen\n"); throw_tippecanoe_error(EXIT_IMPOSSIBLE, "mvt_value::operator<<: can't happen");
exit(EXIT_IMPOSSIBLE);
} }
} }
@@ -550,8 +546,7 @@ bool mvt_value::operator==(const mvt_value &o) const {
return numeric_value.null_value == o.numeric_value.null_value; return numeric_value.null_value == o.numeric_value.null_value;
default: default:
fprintf(stderr, "mvt_value::operator==: can't happen\n"); throw_tippecanoe_error(EXIT_IMPOSSIBLE, "mvt_value::operator==: can't happen");
exit(EXIT_IMPOSSIBLE);
} }
} }
@@ -813,8 +808,7 @@ serial_val mvt_value_to_serial_val(mvt_value const &v) {
sv.s = "null"; sv.s = "null";
break; break;
default: default:
fprintf(stderr, "unhandled mvt_type %d\n", v.type); throw_tippecanoe_error(EXIT_IMPOSSIBLE, "unhandled mvt_type %d", v.type);
exit(EXIT_IMPOSSIBLE);
} }
return sv; return sv;
@@ -840,8 +834,7 @@ long long mvt_value_to_long_long(mvt_value const &v) {
case mvt_null: case mvt_null:
return 0; return 0;
default: default:
fprintf(stderr, "unhandled mvt_type %d\n", v.type); throw_tippecanoe_error(EXIT_IMPOSSIBLE, "unhandled mvt_type %d", v.type);
exit(EXIT_IMPOSSIBLE);
} }
} }
@@ -865,8 +858,7 @@ double mvt_value_to_double(mvt_value const &v) {
case mvt_null: case mvt_null:
return 0; return 0;
default: default:
fprintf(stderr, "unhandled mvt_type %d\n", v.type); throw_tippecanoe_error(EXIT_IMPOSSIBLE, "unhandled mvt_type %d", v.type);
exit(EXIT_IMPOSSIBLE);
} }
} }
+1 -2
View File
@@ -204,8 +204,7 @@ struct std::hash<mvt_value> {
return fnv1a(sizeof(int), (void *) &k.numeric_value.null_value); return fnv1a(sizeof(int), (void *) &k.numeric_value.null_value);
default: default:
fprintf(stderr, "mvt_value hash can't happen\n"); throw tippecanoe_error(EXIT_IMPOSSIBLE, "mvt_value hash: unexpected type");
exit(EXIT_IMPOSSIBLE);
} }
} }
}; };
+26 -20
View File
@@ -12,6 +12,7 @@
#include "read_json.hpp" #include "read_json.hpp"
#include "projection.hpp" #include "projection.hpp"
#include "usage.hpp" #include "usage.hpp"
#include "raii.hpp"
extern char *optarg; extern char *optarg;
extern int optind; extern int optind;
@@ -91,24 +92,21 @@ void usage(char **argv) {
std::string read_json_file(const char *fname) { std::string read_json_file(const char *fname) {
std::string out; std::string out;
FILE *f = fopen(fname, "r"); unique_file f(fopen(fname, "r"));
if (f == NULL) { if (!f) {
perror(optarg); throw_perror(EXIT_OPEN, fname);
exit(EXIT_OPEN);
} }
char buf[2000]; char buf[2000];
size_t nread; size_t nread;
while ((nread = fread(buf, sizeof(char), 2000, f)) != 0) { while ((nread = fread(buf, sizeof(char), 2000, f.get())) != 0) {
out += std::string(buf, nread); out += std::string(buf, nread);
} }
fclose(f);
return out; return out;
} }
int main(int argc, char **argv) { int inner_main(int argc, char **argv) {
int i; int i;
const char *outtile = NULL; const char *outtile = NULL;
const char *outfile = NULL; const char *outfile = NULL;
@@ -273,16 +271,14 @@ int main(int argc, char **argv) {
char buf[1000]; char buf[1000];
int len; int len;
FILE *f = fopen(s.tile.c_str(), "rb"); unique_file f(fopen(s.tile.c_str(), "rb"));
if (f == NULL) { if (!f) {
perror(s.tile.c_str()); throw_perror(EXIT_OPEN, s.tile.c_str());
exit(EXIT_FAILURE);
} }
while ((len = fread(buf, sizeof(char), 1000, f)) > 0) { while ((len = fread(buf, sizeof(char), 1000, f.get())) > 0) {
tile.append(std::string(buf, len)); tile.append(std::string(buf, len));
} }
fclose(f);
input_tile t = s; input_tile t = s;
t.tile = std::move(tile); t.tile = std::move(tile);
@@ -292,14 +288,24 @@ int main(int argc, char **argv) {
out = overzoom(its, nz, nx, ny, detail, buffer, keep, exclude, exclude_prefix, do_compress, NULL, demultiply, json_filter, preserve_input_order, attribute_accum, unidecode_data, simplification, tiny_polygon_size, std::vector<mvt_layer>(), "", "", SIZE_MAX, std::vector<clipbbox>(), deduplicate_by_id); out = overzoom(its, nz, nx, ny, detail, buffer, keep, exclude, exclude_prefix, do_compress, NULL, demultiply, json_filter, preserve_input_order, attribute_accum, unidecode_data, simplification, tiny_polygon_size, std::vector<mvt_layer>(), "", "", SIZE_MAX, std::vector<clipbbox>(), deduplicate_by_id);
} }
FILE *f = fopen(outfile, "wb"); unique_file f(fopen(outfile, "wb"));
if (f == NULL) { if (!f) {
perror(outfile); throw_perror(EXIT_OPEN, outfile);
exit(EXIT_FAILURE);
} }
fwrite(out.c_str(), sizeof(char), out.size(), f); fwrite(out.c_str(), sizeof(char), out.size(), f.get());
fclose(f);
return 0; return 0;
} }
int main(int argc, char **argv) {
try {
return inner_main(argc, argv);
} catch (tippecanoe_error &e) {
fprintf(stderr, "%s\n", e.what());
return e.exit_code;
} catch (std::exception &e) {
fprintf(stderr, "Error: %s\n", e.what());
return EXIT_FAILURE;
}
}
+28
View File
@@ -0,0 +1,28 @@
#ifndef RAII_HPP
#define RAII_HPP
#include <memory>
#include <stdio.h>
#include "jsonpull/jsonpull.h"
// FILE* → unique_file
struct file_closer {
void operator()(FILE *f) {
if (f) {
fclose(f);
}
}
};
using unique_file = std::unique_ptr<FILE, file_closer>;
// json_pull* → unique_json_pull
struct json_pull_closer {
void operator()(json_pull *jp) {
if (jp) {
json_end(jp);
}
}
};
using unique_json_pull = std::unique_ptr<json_pull, json_pull_closer>;
#endif
+13 -14
View File
@@ -14,6 +14,7 @@
#include "milo/dtoa_milo.h" #include "milo/dtoa_milo.h"
#include "errors.hpp" #include "errors.hpp"
#include "serial.hpp" #include "serial.hpp"
#include "raii.hpp"
const char *geometry_names[GEOM_TYPES] = { const char *geometry_names[GEOM_TYPES] = {
"Point", "Point",
@@ -99,7 +100,7 @@ void parse_coordinates(int t, json_object *j, drawvec &out, int op, const char *
json_context(j); json_context(j);
fprintf(stderr, "%s:%d: malformed point: ", fname, line); fprintf(stderr, "%s:%d: malformed point: ", fname, line);
json_context(feature); json_context(feature);
exit(EXIT_JSON); throw_tippecanoe_error(EXIT_JSON, "%s:%d: malformed point", fname, line);
} }
} }
@@ -135,7 +136,7 @@ serial_val stringify_value(json_object *value, const char *reading, int line, js
if (err.size() > 0) { if (err.size() > 0) {
fprintf(stderr, "%s:%d: %s: ", reading, line, err.c_str()); fprintf(stderr, "%s:%d: %s: ", reading, line, err.c_str());
json_context(feature); json_context(feature);
exit(EXIT_UTF8); throw_tippecanoe_error(EXIT_UTF8, "%s:%d: %s", reading, line, err.c_str());
} }
} else if (vt == JSON_NUMBER) { } else if (vt == JSON_NUMBER) {
sv.type = mvt_double; sv.type = mvt_double;
@@ -184,20 +185,20 @@ std::pair<int, drawvec> parse_geometry(json_object *geometry, json_pull *jp, jso
if (geometry_type == NULL) { if (geometry_type == NULL) {
fprintf(stderr, "Filter output:%d: null geometry (additional not reported): ", jp->line); fprintf(stderr, "Filter output:%d: null geometry (additional not reported): ", jp->line);
json_context(j); json_context(j);
exit(EXIT_JSON); throw_tippecanoe_error(EXIT_JSON, "Filter output:%d: null geometry (additional not reported)", jp->line);
} }
if (geometry_type->type != JSON_STRING) { if (geometry_type->type != JSON_STRING) {
fprintf(stderr, "Filter output:%d: geometry type is not a string: ", jp->line); fprintf(stderr, "Filter output:%d: geometry type is not a string: ", jp->line);
json_context(j); json_context(j);
exit(EXIT_JSON); throw_tippecanoe_error(EXIT_JSON, "Filter output:%d: geometry type is not a string", jp->line);
} }
json_object *coordinates = json_hash_get(geometry, "coordinates"); json_object *coordinates = json_hash_get(geometry, "coordinates");
if (coordinates == NULL || coordinates->type != JSON_ARRAY) { if (coordinates == NULL || coordinates->type != JSON_ARRAY) {
fprintf(stderr, "Filter output:%d: geometry without coordinates array: ", jp->line); fprintf(stderr, "Filter output:%d: geometry without coordinates array: ", jp->line);
json_context(j); json_context(j);
exit(EXIT_JSON); throw_tippecanoe_error(EXIT_JSON, "Filter output:%d: geometry without coordinates array", jp->line);
} }
int t; int t;
@@ -209,7 +210,7 @@ std::pair<int, drawvec> parse_geometry(json_object *geometry, json_pull *jp, jso
if (t >= GEOM_TYPES) { if (t >= GEOM_TYPES) {
fprintf(stderr, "Filter output:%d: Can't handle geometry type %s: ", jp->line, geometry_type->value.string.string); fprintf(stderr, "Filter output:%d: Can't handle geometry type %s: ", jp->line, geometry_type->value.string.string);
json_context(j); json_context(j);
exit(EXIT_JSON); throw_tippecanoe_error(EXIT_JSON, "Filter output:%d: Can't handle geometry type %s", jp->line, geometry_type->value.string.string);
} }
drawvec dv; drawvec dv;
@@ -305,9 +306,9 @@ std::vector<mvt_layer> parse_layers(FILE *fp, int z, unsigned x, unsigned y, int
std::map<std::string, mvt_layer> ret; std::map<std::string, mvt_layer> ret;
std::shared_ptr<std::string> tile_stringpool = std::make_shared<std::string>(); std::shared_ptr<std::string> tile_stringpool = std::make_shared<std::string>();
json_pull *jp = json_begin_file(fp); unique_json_pull jp(json_begin_file(fp));
while (1) { while (1) {
json_object *j = json_read(jp); json_object *j = json_read(jp.get());
if (j == NULL) { if (j == NULL) {
if (jp->error != NULL) { if (jp->error != NULL) {
fprintf(stderr, "Filter output:%d: %s: ", jp->line, jp->error); fprintf(stderr, "Filter output:%d: %s: ", jp->line, jp->error);
@@ -316,7 +317,7 @@ std::vector<mvt_layer> parse_layers(FILE *fp, int z, unsigned x, unsigned y, int
} else { } else {
fprintf(stderr, "\n"); fprintf(stderr, "\n");
} }
exit(EXIT_JSON); throw_tippecanoe_error(EXIT_JSON, "Filter output:%d: %s", jp->line, jp->error);
} }
json_free(jp->root); json_free(jp->root);
@@ -336,7 +337,7 @@ std::vector<mvt_layer> parse_layers(FILE *fp, int z, unsigned x, unsigned y, int
fprintf(stderr, "Filter output:%d: feature without properties hash: ", jp->line); fprintf(stderr, "Filter output:%d: feature without properties hash: ", jp->line);
json_context(j); json_context(j);
json_free(j); json_free(j);
exit(EXIT_JSON); throw_tippecanoe_error(EXIT_JSON, "Filter output:%d: feature without properties hash", jp->line);
} }
std::string layername = "unknown"; std::string layername = "unknown";
@@ -364,10 +365,10 @@ std::vector<mvt_layer> parse_layers(FILE *fp, int z, unsigned x, unsigned y, int
fprintf(stderr, "Filter output:%d: filtered feature with no geometry: ", jp->line); fprintf(stderr, "Filter output:%d: filtered feature with no geometry: ", jp->line);
json_context(j); json_context(j);
json_free(j); json_free(j);
exit(EXIT_JSON); throw_tippecanoe_error(EXIT_JSON, "Filter output:%d: filtered feature with no geometry", jp->line);
} }
std::pair<int, drawvec> parsed_geometry = parse_geometry(geometry, jp, j, z, x, y, extent, fix_longitudes, true); std::pair<int, drawvec> parsed_geometry = parse_geometry(geometry, jp.get(), j, z, x, y, extent, fix_longitudes, true);
int t = parsed_geometry.first; int t = parsed_geometry.first;
drawvec &dv = parsed_geometry.second; drawvec &dv = parsed_geometry.second;
@@ -404,8 +405,6 @@ std::vector<mvt_layer> parse_layers(FILE *fp, int z, unsigned x, unsigned y, int
json_free(j); json_free(j);
} }
json_end(jp);
std::vector<mvt_layer> final; std::vector<mvt_layer> final;
for (auto a : ret) { for (auto a : ret) {
final.push_back(a.second); final.push_back(a.second);
+6 -9
View File
@@ -8,6 +8,7 @@
#include "milo/dtoa_milo.h" #include "milo/dtoa_milo.h"
#include "milo/milo.h" #include "milo/milo.h"
#include "errors.hpp" #include "errors.hpp"
#include "raii.hpp"
/** /**
* Returns an empty string if `s` is valid utf8; * Returns an empty string if `s` is valid utf8;
@@ -135,7 +136,7 @@ int integer_zoom(std::string where, std::string text) {
double d = atof(text.c_str()); double d = atof(text.c_str());
if (!std::isfinite(d) || d != floor(d) || d < 0 || d > 32) { if (!std::isfinite(d) || d != floor(d) || d < 0 || d > 32) {
fprintf(stderr, "%s: Expected integer zoom level in \"tippecanoe\" GeoJSON extension, not %s\n", where.c_str(), text.c_str()); fprintf(stderr, "%s: Expected integer zoom level in \"tippecanoe\" GeoJSON extension, not %s\n", where.c_str(), text.c_str());
exit(EXIT_JSON); throw_tippecanoe_error(EXIT_JSON, "%s: Expected integer zoom level in \"tippecanoe\" GeoJSON extension, not %s", where.c_str(), text.c_str());
} }
return d; return d;
} }
@@ -181,8 +182,7 @@ char *dtoa_milo(double val) {
std::string s = milo::dtoa_milo(val); std::string s = milo::dtoa_milo(val);
char *dup = strdup(s.c_str()); char *dup = strdup(s.c_str());
if (dup == NULL) { if (dup == NULL) {
perror("strdup"); throw_perror(EXIT_MEMORY, "strdup");
exit(EXIT_MEMORY);
} }
return dup; return dup;
} }
@@ -191,25 +191,22 @@ char *dtoa_milo(double val) {
std::vector<std::string> read_unidecode(const char *fname) { std::vector<std::string> read_unidecode(const char *fname) {
std::string data; std::string data;
FILE *f = fopen(fname, "rb"); unique_file f(fopen(fname, "rb"));
if (f == NULL) { if (f == NULL) {
perror(fname); throw_perror(EXIT_OPEN, fname);
exit(EXIT_OPEN);
} }
std::string buf; std::string buf;
buf.resize(2000); buf.resize(2000);
while (true) { while (true) {
size_t nread = fread((void *) buf.c_str(), sizeof(char), buf.size(), f); size_t nread = fread((void *) buf.c_str(), sizeof(char), buf.size(), f.get());
if (nread == 0) { if (nread == 0) {
break; break;
} }
data.append(buf.c_str(), nread); data.append(buf.c_str(), nread);
} }
fclose(f);
std::vector<std::string> out; std::vector<std::string> out;
out.emplace_back(); // because the data file is 1-indexed out.emplace_back(); // because the data file is 1-indexed
out.emplace_back(); // ascii 001 out.emplace_back(); // ascii 001
+13 -1
View File
@@ -1332,7 +1332,7 @@ void usage(char **argv) {
exit(EXIT_ARGS); exit(EXIT_ARGS);
} }
int main(int argc, char **argv) { int inner_main(int argc, char **argv) {
char *out_mbtiles = NULL; char *out_mbtiles = NULL;
char *out_dir = NULL; char *out_dir = NULL;
sqlite3 *outdb = NULL; sqlite3 *outdb = NULL;
@@ -1683,3 +1683,15 @@ int main(int argc, char **argv) {
return 0; return 0;
} }
int main(int argc, char **argv) {
try {
return inner_main(argc, argv);
} catch (tippecanoe_error &e) {
fprintf(stderr, "%s\n", e.what());
return e.exit_code;
} catch (std::exception &e) {
fprintf(stderr, "Error: %s\n", e.what());
return EXIT_FAILURE;
}
}
+2 -1
View File
@@ -25,8 +25,9 @@ struct json_writer {
~json_writer() { ~json_writer() {
if (state.size() > 0) { if (state.size() > 0) {
if (state.size() != 1 || state[0] != JSON_WRITE_TOP) { if (state.size() != 1 || state[0] != JSON_WRITE_TOP) {
// Log but don't throw — throwing from a destructor
// during stack unwinding calls std::terminate().
fprintf(stderr, "JSON not closed at end\n"); fprintf(stderr, "JSON not closed at end\n");
exit(EXIT_FAILURE);
} }
} }
} }