3838#include < ogr_core.h>
3939#include < ogr_geometry.h>
4040
41+ #include < algorithm>
4142#include < cassert>
4243#include < cerrno>
44+ #include < cstddef>
4345#include < cstdlib>
4446#include < cstring>
4547#include < exception>
@@ -71,10 +73,166 @@ const unsigned int max_warnings = 500;
7173
7274/* ================================================== */
7375
76+ /* *
77+ * Is the point inside the ring? The envelope of the ring has to be given,
78+ * because calculating it is expensive and we need it several times.
79+ *
80+ * This uses the simple point-in-ring test instead of a GEOS operation,
81+ * because it has to work on invalid geometries, too.
82+ */
83+ [[nodiscard]] bool point_in_ring (const OGRLinearRing* ring, const OGREnvelope& envelope, const OGRPoint& point) {
84+ if (point.getX () < envelope.MinX || point.getX () > envelope.MaxX ||
85+ point.getY () < envelope.MinY || point.getY () > envelope.MaxY ) {
86+ return false ;
87+ }
88+
89+ return ring->isPointInRing (&point, false );
90+ }
91+
92+ /* *
93+ * Find the interior rings of the polygon that can not be holes of that
94+ * polygon, because they are outside the exterior ring or inside another
95+ * hole. Only the first point of each interior ring is looked at, so rings
96+ * that are only partly outside are not detected. Those cases show up as
97+ * intersections and are reported elsewhere.
98+ */
99+ [[nodiscard]] std::vector<bool > find_misplaced_holes (const OGRPolygon& polygon) {
100+ const auto num_rings = static_cast <std::size_t >(polygon.getNumInteriorRings ());
101+
102+ std::vector<OGREnvelope> envelopes (num_rings);
103+ std::vector<OGRPoint> first_points (num_rings);
104+ for (std::size_t i = 0 ; i < num_rings; ++i) {
105+ const OGRLinearRing* ring = polygon.getInteriorRing (static_cast <int >(i));
106+ assert (ring);
107+ ring->getEnvelope (&envelopes[i]);
108+ ring->getPoint (0 , &first_points[i]);
109+ }
110+
111+ const OGRLinearRing* exterior_ring = polygon.getExteriorRing ();
112+ assert (exterior_ring);
113+ OGREnvelope exterior_envelope;
114+ exterior_ring->getEnvelope (&exterior_envelope);
115+
116+ // rings outside the exterior ring ("Hole lies outside shell")
117+ std::vector<bool > outside (num_rings, false );
118+ for (std::size_t i = 0 ; i < num_rings; ++i) {
119+ outside[i] = !point_in_ring (exterior_ring, exterior_envelope, first_points[i]);
120+ }
121+
122+ // rings inside another hole ("Holes are nested"). Rings that are outside
123+ // the exterior ring are no holes at all, so they are not considered as
124+ // the containing ring here.
125+ std::vector<bool > misplaced{outside};
126+ for (std::size_t i = 0 ; i < num_rings; ++i) {
127+ if (misplaced[i]) {
128+ continue ;
129+ }
130+ for (std::size_t j = 0 ; j < num_rings; ++j) {
131+ if (i != j && !outside[j] && envelopes[j].Contains (envelopes[i]) &&
132+ point_in_ring (polygon.getInteriorRing (static_cast <int >(j)), envelopes[j], first_points[i])) {
133+ misplaced[i] = true ;
134+ break ;
135+ }
136+ }
137+ }
138+
139+ return misplaced;
140+ }
141+
142+ /* *
143+ * organizePolygons() decides which rings are holes based on their direction
144+ * and only checks the bounding box when assigning a hole to a polygon. So a
145+ * ring whose coastline was mapped the wrong way round can end up as a hole
146+ * of a polygon it isn't inside of or as a hole inside another hole. GEOS
147+ * then reports "Hole lies outside shell" or "Holes are nested" and the whole
148+ * polygon (possibly a whole continent) is invalid.
149+ *
150+ * Those rings are not holes but land mapped the wrong way round. Take them
151+ * out of the polygon, report them, and turn them around into land polygons
152+ * of their own.
153+ */
154+ [[nodiscard]] std::unique_ptr<OGRPolygon> fix_misplaced_holes (std::unique_ptr<OGRPolygon> polygon,
155+ polygon_vector_type* polygons,
156+ OutputDatabase& output,
157+ unsigned int * turned_around) {
158+ const std::vector<bool > misplaced = find_misplaced_holes (*polygon);
159+
160+ if (std::find (misplaced.cbegin (), misplaced.cend (), true ) == misplaced.cend ()) {
161+ return polygon;
162+ }
163+
164+ auto fixed_polygon = std::make_unique<OGRPolygon>();
165+ fixed_polygon->addRingDirectly (polygon->getExteriorRing ()->clone ());
166+
167+ for (int i = 0 ; i < polygon->getNumInteriorRings (); ++i) {
168+ const OGRLinearRing* interior_ring = polygon->getInteriorRing (i);
169+ assert (interior_ring);
170+
171+ if (!misplaced[static_cast <std::size_t >(i)]) {
172+ fixed_polygon->addRingDirectly (interior_ring->clone ());
173+ continue ;
174+ }
175+
176+ auto ring = std::unique_ptr<OGRLinearRing>(interior_ring->clone ());
177+ ring->reversePoints ();
178+
179+ auto ls = std::unique_ptr<OGRLineString>(OGRGeometryFactory::forceToLineString (ring->clone ())->toLineString ());
180+ output.add_error_line (std::move (ls), " direction" );
181+
182+ auto island = std::make_unique<OGRPolygon>();
183+ island->addRingDirectly (ring.release ());
184+ island->assignSpatialReference (srs.wgs84 ());
185+ polygons->push_back (std::move (island));
186+
187+ ++(*turned_around);
188+ }
189+
190+ fixed_polygon->assignSpatialReference (srs.wgs84 ());
191+ return fixed_polygon;
192+ }
193+
194+ /* *
195+ * Add the polygon to the list of polygons, trying to fix it if it isn't
196+ * valid.
197+ */
198+ void add_polygon_to (polygon_vector_type* polygons,
199+ std::unique_ptr<OGRPolygon> polygon,
200+ OutputDatabase& output,
201+ unsigned int * warnings, unsigned int * errors,
202+ unsigned int * turned_around) {
203+ if (polygon->IsValid ()) {
204+ polygons->push_back (std::move (polygon));
205+ return ;
206+ }
207+
208+ if (polygon->getNumInteriorRings () > 0 ) {
209+ polygon = fix_misplaced_holes (std::move (polygon), polygons, output, turned_around);
210+ if (polygon->IsValid ()) {
211+ polygons->push_back (std::move (polygon));
212+ return ;
213+ }
214+ }
215+
216+ auto * ring = polygon->getExteriorRing ()->clone ();
217+ auto ls = std::unique_ptr<OGRLineString>(OGRGeometryFactory::forceToLineString (ring)->toLineString ());
218+ output.add_error_line (std::move (ls), " invalid" );
219+
220+ std::unique_ptr<OGRGeometry> buf0{polygon->Buffer (0 )};
221+ if (buf0 && buf0->getGeometryType () == wkbPolygon && buf0->IsValid ()) {
222+ buf0->assignSpatialReference (srs.wgs84 ());
223+ polygons->push_back (static_cast_unique_ptr<OGRPolygon>(std::move (buf0)));
224+ (*warnings)++;
225+ } else {
226+ std::cerr << " Ignoring invalid polygon geometry.\n " ;
227+ (*errors)++;
228+ }
229+ }
230+
74231void add_polygons_in_multi_to (polygon_vector_type *polygons,
75232 std::unique_ptr<OGRGeometry> mega_geometry,
76233 OutputDatabase& output,
77- unsigned int * warnings, unsigned int * errors) {
234+ unsigned int * warnings, unsigned int * errors,
235+ unsigned int * turned_around) {
78236 // This isn't an owning pointer on purpose. We are going to "steal" parts
79237 // of the geometry a few lines below but only mark them as unowned farther
80238 // below when we are calling removeGeometry() on it. If this was an
@@ -87,22 +245,7 @@ void add_polygons_in_multi_to(polygon_vector_type *polygons,
87245 assert (geom);
88246 assert (geom->getGeometryType () == wkbPolygon);
89247 std::unique_ptr<OGRPolygon> p{static_cast <OGRPolygon*>(geom)};
90- if (p->IsValid ()) {
91- polygons->push_back (std::move (p));
92- } else {
93- auto * ring = p->getExteriorRing ()->clone ();
94- auto ls = std::unique_ptr<OGRLineString>(OGRGeometryFactory::forceToLineString (ring)->toLineString ());
95- output.add_error_line (std::move (ls), " invalid" );
96- std::unique_ptr<OGRGeometry> buf0{p->Buffer (0 )};
97- if (buf0 && buf0->getGeometryType () == wkbPolygon && buf0->IsValid ()) {
98- buf0->assignSpatialReference (srs.wgs84 ());
99- polygons->push_back (static_cast_unique_ptr<OGRPolygon>(std::move (buf0)));
100- (*warnings)++;
101- } else {
102- std::cerr << " Ignoring invalid polygon geometry.\n " ;
103- (*errors)++;
104- }
105- }
248+ add_polygon_to (polygons, std::move (p), output, warnings, errors, turned_around);
106249 }
107250
108251 mega_multipolygon->removeGeometry (-1 , FALSE );
@@ -112,7 +255,7 @@ void add_polygons_in_multi_to(polygon_vector_type *polygons,
112255/* *
113256 * This function assembles all the coastline rings into one huge multipolygon.
114257 */
115- polygon_vector_type create_polygons (CoastlineRingCollection& coastline_rings, OutputDatabase& output, unsigned int * warnings, unsigned int * errors) {
258+ polygon_vector_type create_polygons (CoastlineRingCollection& coastline_rings, OutputDatabase& output, unsigned int * warnings, unsigned int * errors, unsigned int * turned_around ) {
116259 std::vector<OGRGeometry*> all_polygons = coastline_rings.add_polygons_to_vector ();
117260
118261 if (all_polygons.empty ()) {
@@ -136,16 +279,11 @@ polygon_vector_type create_polygons(CoastlineRingCollection& coastline_rings, Ou
136279 polygon_vector_type polygons;
137280
138281 if (mega_geometry->getGeometryType () == wkbPolygon) {
139- if (mega_geometry->IsValid ()) {
140- polygons.push_back (static_cast_unique_ptr<OGRPolygon>(std::move (mega_geometry)));
141- } else {
142- std::cerr << " Ignoring invalid polygon geometry.\n " ;
143- (*errors)++;
144- }
282+ add_polygon_to (&polygons, static_cast_unique_ptr<OGRPolygon>(std::move (mega_geometry)), output, warnings, errors, turned_around);
145283 } else if (mega_geometry->getGeometryType () != wkbMultiPolygon) {
146284 throw std::runtime_error{" mega geometry isn't a (multi)polygon. Something is very wrong!" };
147285 } else {
148- add_polygons_in_multi_to (&polygons, std::move (mega_geometry), output, warnings, errors);
286+ add_polygons_in_multi_to (&polygons, std::move (mega_geometry), output, warnings, errors, turned_around );
149287 }
150288
151289 return polygons;
@@ -354,15 +492,16 @@ int main(int argc, char *argv[]) {
354492 if (options.output_polygons != output_polygon_type::none || options.output_lines ) {
355493 try {
356494 vout << " Create polygons...\n " ;
357- CoastlinePolygons coastline_polygons{create_polygons (coastline_rings, *output_database, &warnings, &errors), \
495+ unsigned int turned_around = 0 ;
496+ CoastlinePolygons coastline_polygons{create_polygons (coastline_rings, *output_database, &warnings, &errors, &turned_around), \
358497 *output_database, \
359498 options.bbox_overlap , \
360499 options.max_points_in_polygon };
361500
362501 stats.land_polygons_before_split = coastline_polygons.num_polygons ();
363502
364503 vout << " Fixing coastlines going the wrong way...\n " ;
365- stats.rings_turned_around = coastline_polygons.fix_direction ();
504+ stats.rings_turned_around = turned_around + coastline_polygons.fix_direction ();
366505 vout << " Turned " << stats.rings_turned_around << " polygons around.\n " ;
367506 warnings += stats.rings_turned_around ;
368507
0 commit comments