LWGEOM* lwgeom_buildarea(const LWGEOM *geom) { GEOSGeometry* geos_in; GEOSGeometry* geos_out; LWGEOM* geom_out; int SRID = (int)(geom->srid); int is3d = FLAGS_GET_Z(geom->flags); /* Can't build an area from an empty! */ if ( lwgeom_is_empty(geom) ) { return (LWGEOM*)lwpoly_construct_empty(SRID, is3d, 0); } LWDEBUG(3, "buildarea called"); LWDEBUGF(3, "ST_BuildArea got geom @ %p", geom); initGEOS(lwnotice, lwgeom_geos_error); geos_in = LWGEOM2GEOS(geom); if ( 0 == geos_in ) /* exception thrown at construction */ { lwerror("First argument geometry could not be converted to GEOS: %s", lwgeom_geos_errmsg); return NULL; } geos_out = LWGEOM_GEOS_buildArea(geos_in); GEOSGeom_destroy(geos_in); if ( ! geos_out ) /* exception thrown.. */ { lwerror("LWGEOM_GEOS_buildArea: %s", lwgeom_geos_errmsg); return NULL; } /* If no geometries are in result collection, return NULL */ if ( GEOSGetNumGeometries(geos_out) == 0 ) { GEOSGeom_destroy(geos_out); return NULL; } geom_out = GEOS2LWGEOM(geos_out, is3d); GEOSGeom_destroy(geos_out); #if PARANOIA_LEVEL > 0 if ( geom_out == NULL ) { lwerror("serialization error"); return NULL; } #endif return geom_out; }
// 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; }