Skip to content

Commit

Permalink
Add --reject-null-geometry and more GeoJSON geometry validation
Browse files Browse the repository at this point in the history
  • Loading branch information
e-n-f committed Nov 21, 2023
1 parent d359461 commit 3e06333
Show file tree
Hide file tree
Showing 10 changed files with 66 additions and 1 deletion.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -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
Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
1 change: 1 addition & 0 deletions errors.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -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
4 changes: 4 additions & 0 deletions geojson.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
1 change: 1 addition & 0 deletions main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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'},
Expand Down
2 changes: 2 additions & 0 deletions man/tippecanoe.1
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions options.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -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')
Expand Down
18 changes: 18 additions & 0 deletions read_json.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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) {
Expand Down
33 changes: 33 additions & 0 deletions tile.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1609,6 +1609,34 @@ serial_feature next_feature(decompressor *geoms, std::atomic<long long> *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<long long> *geompos_in = NULL;
Expand Down Expand Up @@ -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);
}
Expand Down
2 changes: 1 addition & 1 deletion version.hpp
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
#ifndef VERSION_HPP
#define VERSION_HPP

#define VERSION "v2.37.0"
#define VERSION "v2.38.0"

#endif

0 comments on commit 3e06333

Please sign in to comment.