Add --reject-null-geometry and more GeoJSON geometry validation

This commit is contained in:
Erica Fischer
2023-11-21 10:21:47 -08:00
parent d359461e61
commit 3e0633323f
10 changed files with 66 additions and 1 deletions
+4
View File
@@ -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
+1
View File
@@ -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
+1
View File
@@ -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
View File
@@ -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;
+1
View File
@@ -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'},
+2
View File
@@ -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
+1
View File
@@ -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')
+18
View File
@@ -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) {
+33
View File
@@ -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;
@@ -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);
}
+1 -1
View File
@@ -1,6 +1,6 @@
#ifndef VERSION_HPP
#define VERSION_HPP
#define VERSION "v2.37.0"
#define VERSION "v2.38.0"
#endif