diff --git a/CHANGELOG.md b/CHANGELOG.md index 8ccf0a70..8c23b037 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +# 2.38.0 + +* Add --reject-null-geometry and more checking of GeoJSON polygon and linestring validity + # 2.37.0 * Speed up tile-join overzooming and make it use less memory, by not including empty child tiles in the enumeration diff --git a/README.md b/README.md index 6a1c8557..9bcc0961 100644 --- a/README.md +++ b/README.md @@ -533,6 +533,7 @@ the same layer, enclose them in an `all` expression so they will all be evaluate * `-pW` or `--reverse-source-polygon-winding`: Instead of respecting GeoJSON polygon ring order, use the opposite of the original polygon winding in the source data to distinguish inner (counterclockwise) and outer (clockwise) polygon rings. * `--clip-bounding-box=`*minlon*`,`*minlat*`,`*maxlon*`,`*maxlat*: Clip all features to the specified bounding box. * `-aP` or `--convert-polygons-to-label-points`: Replace polygon geometries with a label point or points for the polygon in each tile it intersects. + * `--reject-null-geometry`: Exit with an error when a feature has a `null` geometry instead of just giving a warning. ### Setting or disabling tile size limits diff --git a/errors.hpp b/errors.hpp index 904c4ce2..b8e2f69a 100644 --- a/errors.hpp +++ b/errors.hpp @@ -19,5 +19,6 @@ #define EXIT_UNLINK 118 #define EXIT_UTF8 119 #define EXIT_WRITE 120 +#define EXIT_INCORRECT_GEOMETRY 121 // avoid 124, 125, 126, 127, 137, which are used by GNU timeout diff --git a/geojson.cpp b/geojson.cpp index 16ef9457..24c07f19 100644 --- a/geojson.cpp +++ b/geojson.cpp @@ -49,6 +49,10 @@ int serialize_geojson_feature(struct serialization_state *sst, json_object *geom fprintf(stderr, "%s:%d: null geometry (additional not reported): ", sst->fname, sst->line); json_context(feature); warned = 1; + + if (additional[A_REJECT_NULL_GEOMETRY]) { + exit(EXIT_INCORRECT_GEOMETRY); + } } return 0; diff --git a/main.cpp b/main.cpp index 2c303a99..d9888d9a 100644 --- a/main.cpp +++ b/main.cpp @@ -3214,6 +3214,7 @@ int main(int argc, char **argv) { {"reverse-source-polygon-winding", no_argument, &prevent[P_REVERSE_SOURCE_POLYGON_WINDING], 1}, {"clip-bounding-box", required_argument, 0, '~'}, {"convert-polygons-to-label-points", no_argument, &additional[A_GENERATE_POLYGON_LABEL_POINTS], 1}, + {"reject-null-geometry", no_argument, &additional[A_REJECT_NULL_GEOMETRY], 1}, {"Filtering tile contents", 0, 0, 0}, {"prefilter", required_argument, 0, 'C'}, diff --git a/man/tippecanoe.1 b/man/tippecanoe.1 index a1abaaf2..47482b79 100644 --- a/man/tippecanoe.1 +++ b/man/tippecanoe.1 @@ -695,6 +695,8 @@ the line or polygon within one tile unit of its proper location. You can probabl \fB\fC\-\-clip\-bounding\-box=\fR\fIminlon\fP\fB\fC,\fR\fIminlat\fP\fB\fC,\fR\fImaxlon\fP\fB\fC,\fR\fImaxlat\fP: Clip all features to the specified bounding box. .IP \(bu 2 \fB\fC\-aP\fR or \fB\fC\-\-convert\-polygons\-to\-label\-points\fR: Replace polygon geometries with a label point or points for the polygon in each tile it intersects. +.IP \(bu 2 +\fB\fC\-\-reject\-null\-geometry\fR: Exit with an error when a feature has a \fB\fCnull\fR geometry instead of just giving a warning. .RE .SS Setting or disabling tile size limits .RS diff --git a/options.hpp b/options.hpp index c56c5fcd..d627076f 100644 --- a/options.hpp +++ b/options.hpp @@ -26,6 +26,7 @@ #define A_HILBERT ((int) 'h') #define A_VISVALINGAM ((int) 'v') #define A_GENERATE_POLYGON_LABEL_POINTS ((int) 'P') +#define A_REJECT_NULL_GEOMETRY ((int) '0') #define P_SIMPLIFY ((int) 's') #define P_SIMPLIFY_LOW ((int) 'S') diff --git a/read_json.cpp b/read_json.cpp index 166df57d..d440e9dc 100644 --- a/read_json.cpp +++ b/read_json.cpp @@ -103,6 +103,14 @@ void parse_geometry(int t, json_object *j, drawvec &out, int op, const char *fna } if (t == GEOM_POLYGON) { + if (out.size() < 3) { + fprintf(stderr, "%s:%d: polygon must contain at least three points: ", fname, line); + json_context(j); + fprintf(stderr, "%s:%d: polygon must contain at least three points: ", fname, line); + json_context(feature); + exit(EXIT_INCORRECT_GEOMETRY); + } + // Note that this is not using the correct meaning of closepath. // // We are using it here to close an entire Polygon, to distinguish @@ -114,6 +122,16 @@ void parse_geometry(int t, json_object *j, drawvec &out, int op, const char *fna out.push_back(draw(VT_CLOSEPATH, 0, 0)); } + + if (t == GEOM_LINESTRING) { + if (out.size() < 2) { + fprintf(stderr, "%s:%d: linestring must contain at least two points: ", fname, line); + json_context(j); + fprintf(stderr, "%s:%d: linestring must contain at least two points: ", fname, line); + json_context(feature); + exit(EXIT_INCORRECT_GEOMETRY); + } + } } void stringify_value(json_object *value, int &type, std::string &stringified, const char *reading, int line, json_object *feature) { diff --git a/tile.cpp b/tile.cpp index d94b1493..f8b380f3 100644 --- a/tile.cpp +++ b/tile.cpp @@ -1609,6 +1609,34 @@ serial_feature next_feature(decompressor *geoms, std::atomic *geompos } } +bool legit_geometry(int t, const drawvec &g) { + bool legit = false; + + for (size_t i = 0; i < g.size(); i++) { + if (g[i].op == VT_MOVETO) { + size_t j; + + for (j = i + 1; j < g.size(); j++) { + if (g[j].op != VT_LINETO) { + break; + } + } + + if (t == VT_LINE && j < i + 2) { + return false; + } + if (t == VT_POLYGON && j < i + 3) { + return false; + } + + legit = true; // must contain at least one ring + i = j - 1; + } + } + + return legit; +} + struct run_prefilter_args { decompressor *geoms = NULL; std::atomic *geompos_in = NULL; @@ -1657,6 +1685,11 @@ void *run_prefilter(void *v) { tmp_layer.extent = 1LL << 32; tmp_layer.name = (*(rpa->layer_unmaps))[sf.segment][sf.layer]; + // make sure the geometry is actually legal geojson + if (!legit_geometry(sf.t, sf.geometry)) { + continue; + } + if (sf.t == VT_POLYGON) { sf.geometry = close_poly(sf.geometry); } diff --git a/version.hpp b/version.hpp index 5cd56cb7..709cd9a7 100644 --- a/version.hpp +++ b/version.hpp @@ -1,6 +1,6 @@ #ifndef VERSION_HPP #define VERSION_HPP -#define VERSION "v2.37.0" +#define VERSION "v2.38.0" #endif