static VALUE method_geometry_difference(VALUE self, VALUE rhs) { VALUE result = Qnil; RGeo_GeometryData* self_data = RGEO_GEOMETRY_DATA_PTR(self); const GEOSGeometry* self_geom = self_data->geom; if (self_geom) { VALUE factory = self_data->factory; const GEOSGeometry* rhs_geom = rgeo_convert_to_geos_geometry(factory, rhs, Qnil); if (rhs_geom) { result = rgeo_wrap_geos_geometry(factory, GEOSDifference_r(self_data->geos_context, self_geom, rhs_geom), Qnil); } } return result; }
// Will return NULL on error (expect error handler being called by then) Q_NOWARN_UNREACHABLE_PUSH static GEOSGeometry *LWGEOM_GEOS_makeValidPolygon( const GEOSGeometry *gin, QString &errorMessage ) { GEOSContextHandle_t handle = QgsGeos::getGEOSHandler(); GEOSGeom gout; GEOSGeom geos_bound; GEOSGeom geos_cut_edges, geos_area, collapse_points; GEOSGeometry *vgeoms[3]; // One for area, one for cut-edges unsigned int nvgeoms = 0; Q_ASSERT( GEOSGeomTypeId_r( handle, gin ) == GEOS_POLYGON || GEOSGeomTypeId_r( handle, gin ) == GEOS_MULTIPOLYGON ); geos_bound = GEOSBoundary_r( handle, gin ); if ( !geos_bound ) return nullptr; // Use noded boundaries as initial "cut" edges geos_cut_edges = LWGEOM_GEOS_nodeLines( geos_bound ); if ( !geos_cut_edges ) { GEOSGeom_destroy_r( handle, geos_bound ); errorMessage = QStringLiteral( "LWGEOM_GEOS_nodeLines() failed" ); return nullptr; } // NOTE: the noding process may drop lines collapsing to points. // We want to retrieve any of those { GEOSGeometry *pi = nullptr; GEOSGeometry *po = nullptr; try { pi = GEOSGeom_extractUniquePoints_r( handle, geos_bound ); } catch ( GEOSException &e ) { GEOSGeom_destroy_r( handle, geos_bound ); errorMessage = QStringLiteral( "GEOSGeom_extractUniquePoints(): %1" ).arg( e.what() ); return nullptr; } try { po = GEOSGeom_extractUniquePoints_r( handle, geos_cut_edges ); } catch ( GEOSException &e ) { GEOSGeom_destroy_r( handle, geos_bound ); GEOSGeom_destroy_r( handle, pi ); errorMessage = QStringLiteral( "GEOSGeom_extractUniquePoints(): %1" ).arg( e.what() ); return nullptr; } try { collapse_points = GEOSDifference_r( handle, pi, po ); } catch ( GEOSException &e ) { GEOSGeom_destroy_r( handle, geos_bound ); GEOSGeom_destroy_r( handle, pi ); GEOSGeom_destroy_r( handle, po ); errorMessage = QStringLiteral( "GEOSDifference(): %1" ).arg( e.what() ); return nullptr; } GEOSGeom_destroy_r( handle, pi ); GEOSGeom_destroy_r( handle, po ); } GEOSGeom_destroy_r( handle, geos_bound ); // And use an empty geometry as initial "area" try { geos_area = GEOSGeom_createEmptyPolygon_r( handle ); } catch ( GEOSException &e ) { errorMessage = QStringLiteral( "GEOSGeom_createEmptyPolygon(): %1" ).arg( e.what() ); GEOSGeom_destroy_r( handle, geos_cut_edges ); return nullptr; } // See if an area can be build with the remaining edges // and if it can, symdifference with the original area. // Iterate this until no more polygons can be created // with left-over edges. while ( GEOSGetNumGeometries_r( handle, geos_cut_edges ) ) { GEOSGeometry *new_area = nullptr; GEOSGeometry *new_area_bound = nullptr; GEOSGeometry *symdif = nullptr; GEOSGeometry *new_cut_edges = nullptr; // ASSUMPTION: cut_edges should already be fully noded try { new_area = LWGEOM_GEOS_buildArea( geos_cut_edges, errorMessage ); } catch ( GEOSException &e ) { GEOSGeom_destroy_r( handle, geos_cut_edges ); GEOSGeom_destroy_r( handle, geos_area ); errorMessage = QStringLiteral( "LWGEOM_GEOS_buildArea() threw an error: %1" ).arg( e.what() ); return nullptr; } if ( GEOSisEmpty_r( handle, new_area ) ) { // no more rings can be build with thes edges GEOSGeom_destroy_r( handle, new_area ); break; } // We succeeded in building a ring ! // Save the new ring boundaries first (to compute // further cut edges later) try { new_area_bound = GEOSBoundary_r( handle, new_area ); } catch ( GEOSException &e ) { // We did check for empty area already so // this must be some other error errorMessage = QStringLiteral( "GEOSBoundary() threw an error: %1" ).arg( e.what() ); GEOSGeom_destroy_r( handle, new_area ); GEOSGeom_destroy_r( handle, geos_area ); return nullptr; } // Now symdiff new and old area try { symdif = GEOSSymDifference_r( handle, geos_area, new_area ); } catch ( GEOSException &e ) { GEOSGeom_destroy_r( handle, geos_cut_edges ); GEOSGeom_destroy_r( handle, new_area ); GEOSGeom_destroy_r( handle, new_area_bound ); GEOSGeom_destroy_r( handle, geos_area ); errorMessage = QStringLiteral( "GEOSSymDifference() threw an error: %1" ).arg( e.what() ); return nullptr; } GEOSGeom_destroy_r( handle, geos_area ); GEOSGeom_destroy_r( handle, new_area ); geos_area = symdif; symdif = nullptr; // Now let's re-set geos_cut_edges with what's left // from the original boundary. // ASSUMPTION: only the previous cut-edges can be // left, so we don't need to reconsider // the whole original boundaries // // NOTE: this is an expensive operation. try { new_cut_edges = GEOSDifference_r( handle, geos_cut_edges, new_area_bound ); } catch ( GEOSException &e ) { GEOSGeom_destroy_r( handle, geos_cut_edges ); GEOSGeom_destroy_r( handle, new_area_bound ); GEOSGeom_destroy_r( handle, geos_area ); errorMessage = QStringLiteral( "GEOSDifference() threw an error: %1" ).arg( e.what() ); return nullptr; } GEOSGeom_destroy_r( handle, geos_cut_edges ); GEOSGeom_destroy_r( handle, new_area_bound ); geos_cut_edges = new_cut_edges; } if ( !GEOSisEmpty_r( handle, geos_area ) ) { vgeoms[nvgeoms++] = geos_area; } else { GEOSGeom_destroy_r( handle, geos_area ); } if ( !GEOSisEmpty_r( handle, geos_cut_edges ) ) { vgeoms[nvgeoms++] = geos_cut_edges; } else { GEOSGeom_destroy_r( handle, geos_cut_edges ); } if ( !GEOSisEmpty_r( handle, collapse_points ) ) { vgeoms[nvgeoms++] = collapse_points; } else { GEOSGeom_destroy_r( handle, collapse_points ); } if ( 1 == nvgeoms ) { // Return cut edges gout = vgeoms[0]; } else { // Collect areas and lines (if any line) try { gout = GEOSGeom_createCollection_r( handle, GEOS_GEOMETRYCOLLECTION, vgeoms, nvgeoms ); } catch ( GEOSException &e ) { errorMessage = QStringLiteral( "GEOSGeom_createCollection() threw an error: %1" ).arg( e.what() ); // TODO: cleanup! return nullptr; } } return gout; }