From: Sandro Santilli Date: Fri, 31 Jul 2015 16:34:10 +0000 (+0000) Subject: Implement ST_GetFaceGeometry in C X-Git-Tag: 2.2.0rc1~202 X-Git-Url: https://granicus.if.org/sourcecode?a=commitdiff_plain;h=05385c200a4d860e0886a6da49497ef02d5141b4;p=postgis Implement ST_GetFaceGeometry in C Adds callbacks to return SRID, precision and Z flag for backend topology object. Returns EMPTY polygon for faces with no boundaries, closing #3221. Funded by Tuscany Region (Italy) - SITA (CIG: 60351023B8) git-svn-id: http://svn.osgeo.org/postgis/trunk@13861 b70326c6-7e19-0410-871a-916f4a2858ee --- diff --git a/liblwgeom/TODO b/liblwgeom/TODO index 78309c67d..06f069963 100644 --- a/liblwgeom/TODO +++ b/liblwgeom/TODO @@ -7,8 +7,8 @@ lwt_ModEdgeSplit X lwt_NewEdgesSplit X lwt_AddIsoEdge X +lwt_GetFaceGeometry X lwt_GetFaceEdges -lwt_GetFaceGeometry lwt_ChangeEdgeGeom lwt_MoveIsoNode diff --git a/liblwgeom/liblwgeom_topo.h b/liblwgeom/liblwgeom_topo.h index 7368c2ebe..6dade5c00 100644 --- a/liblwgeom/liblwgeom_topo.h +++ b/liblwgeom/liblwgeom_topo.h @@ -690,6 +690,30 @@ typedef struct LWT_BE_CALLBACKS_T { int numelems ); + /** + * Get topology SRID + * @return 0 for unknown + */ + int (*topoGetSRID) ( + const LWT_BE_TOPOLOGY* topo + ); + + /** + * Get topology precision + */ + double (*topoGetPrecision) ( + const LWT_BE_TOPOLOGY* topo + ); + + /** + * Get topology Z flag + * @return 1 if topology elements do have Z value, 0 otherwise + */ + int (*topoHasZ) ( + const LWT_BE_TOPOLOGY* topo + ); + + } LWT_BE_CALLBACKS; @@ -1116,7 +1140,8 @@ LWT_ELEMID lwt_NewEdgeHeal(LWT_TOPOLOGY* topo, LWT_ELEMID e1, LWT_ELEMID e2); * @param face identifier of the face * @param edges will be set to an array of signed edge identifiers, will * need to be released with lwfree - * @return the number of edges in the edges array + * @return the number of edges in the edges array, or -1 on error + * (liblwgeom error handler will be invoked with error message) * */ int lwt_GetFaceEdges(LWT_TOPOLOGY* topo, LWT_ELEMID face, LWT_ELEMID **edges); @@ -1129,7 +1154,8 @@ int lwt_GetFaceEdges(LWT_TOPOLOGY* topo, LWT_ELEMID face, LWT_ELEMID **edges); * @param topo the topology to operate on * @param face identifier of the face * @return a polygon geometry representing the face, ownership to caller, - * to be released with lwgeom_release + * to be released with lwgeom_release, or NULL on error + * (liblwgeom error handler will be invoked with error message) */ LWGEOM* lwt_GetFaceGeometry(LWT_TOPOLOGY* topo, LWT_ELEMID face); diff --git a/liblwgeom/liblwgeom_topo_internal.h b/liblwgeom/liblwgeom_topo_internal.h index 3c2176d7c..49a906797 100644 --- a/liblwgeom/liblwgeom_topo_internal.h +++ b/liblwgeom/liblwgeom_topo_internal.h @@ -78,8 +78,9 @@ struct LWT_TOPOLOGY_T { const LWT_BE_IFACE *be_iface; LWT_BE_TOPOLOGY *be_topo; - char *name; - char *table_prefix; + int srid; + double precision; + int hasZ; }; #endif /* LIBLWGEOM_TOPO_INTERNAL_H */ diff --git a/liblwgeom/lwgeom_topo.c b/liblwgeom/lwgeom_topo.c index bcc799d08..44911c806 100644 --- a/liblwgeom/lwgeom_topo.c +++ b/liblwgeom/lwgeom_topo.c @@ -113,6 +113,24 @@ lwt_be_loadTopologyByName(LWT_BE_IFACE *be, const char *name) CB1(be, loadTopologyByName, name); } +static int +lwt_be_topoGetSRID(LWT_TOPOLOGY *topo) +{ + CBT0(topo, topoGetSRID); +} + +static double +lwt_be_topoGetPrecision(LWT_TOPOLOGY *topo) +{ + CBT0(topo, topoGetPrecision); +} + +static int +lwt_be_topoHasZ(LWT_TOPOLOGY *topo) +{ + CBT0(topo, topoHasZ); +} + int lwt_be_freeTopology(LWT_TOPOLOGY *topo) { @@ -379,8 +397,9 @@ lwt_LoadTopology( LWT_BE_IFACE *iface, const char *name ) topo = lwalloc(sizeof(LWT_TOPOLOGY)); topo->be_iface = iface; topo->be_topo = be_topo; - topo->name = NULL; /* don't want to think about it now.. */ - topo->table_prefix = NULL; /* don't want to think about it now */ + topo->srid = lwt_be_topoGetSRID(topo); + topo->hasZ = lwt_be_topoHasZ(topo); + topo->precision = lwt_be_topoGetPrecision(topo); return topo; } @@ -469,6 +488,8 @@ _lwt_CheckEdgeCrossing( LWT_TOPOLOGY* topo, GEOSGeometry *edgegg; const GEOSPreparedGeometry* prepared_edge; + initGEOS(lwnotice, lwgeom_geos_error); + edgegg = LWGEOM2GEOS( lwline_as_lwgeom(geom), 0); if ( ! edgegg ) { lwerror("Could not convert edge geometry to GEOS: %s", lwgeom_geos_errmsg); @@ -1977,8 +1998,6 @@ _lwt_AddEdge( LWT_TOPOLOGY* topo, LWT_ISO_EDGE seledge; LWT_ISO_EDGE updedge; - initGEOS(lwnotice, lwgeom_geos_error); - if ( ! skipChecks ) { /* curve must be simple */ @@ -2343,8 +2362,6 @@ _lwt_AddEdge( LWT_TOPOLOGY* topo, } } - lwnotice("XXXX end of adding an edge, newedge.face_left is %d, newface is %d, newface1 is %d", newedge.face_left, newface, newface1); - /* * Update topogeometries, if needed */ @@ -2386,3 +2403,105 @@ lwt_AddEdgeNewFaces( LWT_TOPOLOGY* topo, { return _lwt_AddEdge( topo, start_node, end_node, geom, skipChecks, 0 ); } + +LWGEOM* +lwt_GetFaceGeometry(LWT_TOPOLOGY* topo, LWT_ELEMID faceid) +{ + int numfaceedges; + LWT_ISO_EDGE *edges; + LWT_ISO_FACE *face; + LWPOLY *out; + LWGEOM *outg; + int i; + int fields; + + if ( faceid == 0 ) + { + lwerror("SQL/MM Spatial exception - universal face has no geometry"); + return NULL; + } + + /* Construct the face geometry */ + numfaceedges = 1; + fields = LWT_COL_EDGE_GEOM | + LWT_COL_EDGE_FACE_LEFT | + LWT_COL_EDGE_FACE_RIGHT + ; + edges = lwt_be_getEdgeByFace( topo, &faceid, &numfaceedges, fields ); + if ( numfaceedges == -1 ) { + lwerror("Backend error: %s", lwt_be_lastErrorMessage(topo->be_iface)); + return NULL; + } + + if ( numfaceedges == 0 ) + { + i = 1; + face = lwt_be_getFaceById(topo, &faceid, &i, LWT_COL_FACE_FACE_ID); + if ( i == -1 ) { + lwerror("Backend error: %s", lwt_be_lastErrorMessage(topo->be_iface)); + return NULL; + } + if ( i == 0 ) { + lwerror("SQL/MM Spatial exception - non-existent face."); + return NULL; + } + lwfree( face ); + if ( i > 1 ) { + lwerror("Corrupted topology: multiple face records have face_id=%" + PRId64, faceid); + return NULL; + } + /* Face has no boundary edges, we'll return EMPTY, see + * https://trac.osgeo.org/postgis/ticket/3221 */ + out = lwpoly_construct_empty(topo->srid, topo->hasZ, 0); + return lwpoly_as_lwgeom(out); + } + + /* Linemerge polygon boundaries */ + + LWGEOM **geoms = lwalloc( sizeof(LWGEOM*) * numfaceedges ); + int validedges = 0; + for ( i=0; isrid, topo->hasZ, 0); + return lwpoly_as_lwgeom(out); + } + LWCOLLECTION *bounds = lwcollection_construct(MULTILINETYPE, + topo->srid, + NULL, /* gbox */ + validedges, + geoms); +#if 0 /* debugging (a leaking one) */ + lwnotice("Collected face bounds: %s", lwgeom_to_wkt((LWGEOM*)bounds, + WKT_ISO, 2, NULL)); +#endif + + outg = lwgeom_buildarea( lwcollection_as_lwgeom(bounds) ); + + _lwt_release_edges(edges, numfaceedges); + lwcollection_release(bounds); + + return outg; +} + +int +lwt_GetFaceEdges(LWT_TOPOLOGY* topo, LWT_ELEMID face, LWT_ELEMID **edges) +{ + /* TODO: Construct the face geometry */ + /* TODO: Then for each ring of the face polygon... */ + lwerror("lwt_GetFaceEdges not implemented yet"); + return -1; +} diff --git a/topology/postgis_topology.c b/topology/postgis_topology.c index fbc55a152..f721eaf58 100644 --- a/topology/postgis_topology.c +++ b/topology/postgis_topology.c @@ -70,6 +70,7 @@ struct LWT_BE_TOPOLOGY_T { int id; int srid; int precision; + int hasZ; }; /* utility funx */ @@ -158,6 +159,24 @@ cb_loadTopologyByName(const LWT_BE_DATA* be, const char *name) return topo; } +static int +cb_topoGetSRID(const LWT_BE_TOPOLOGY* topo) +{ + return topo->srid; +} + +static int +cb_topoHasZ(const LWT_BE_TOPOLOGY* topo) +{ + return topo->hasZ; +} + +static double +cb_topoGetPrecision(const LWT_BE_TOPOLOGY* topo) +{ + return topo->precision; +} + static int cb_freeTopology(LWT_BE_TOPOLOGY* topo) { @@ -2183,7 +2202,10 @@ static LWT_BE_CALLBACKS be_callbacks = { cb_getEdgeByFace, cb_getNodeByFace, cb_updateNodesById, - cb_deleteFacesById + cb_deleteFacesById, + cb_topoGetSRID, + cb_topoGetPrecision, + cb_topoHasZ }; @@ -2644,3 +2666,63 @@ Datum ST_AddEdgeNewFaces(PG_FUNCTION_ARGS) SPI_finish(); PG_RETURN_INT32(edge_id); } + +/* ST_GetFaceGeometry(atopology, aface) */ +Datum ST_GetFaceGeometry(PG_FUNCTION_ARGS); +PG_FUNCTION_INFO_V1(ST_GetFaceGeometry); +Datum ST_GetFaceGeometry(PG_FUNCTION_ARGS) +{ + text* toponame_text; + char* toponame; + LWT_ELEMID face_id; + LWGEOM *lwgeom; + LWT_TOPOLOGY *topo; + GSERIALIZED *geom; + MemoryContext old_context; + + if ( PG_ARGISNULL(0) || PG_ARGISNULL(1) ) { + lwpgerror("SQL/MM Spatial exception - null argument"); + PG_RETURN_NULL(); + } + + toponame_text = PG_GETARG_TEXT_P(0); + toponame = text2cstring(toponame_text); + PG_FREE_IF_COPY(toponame_text, 0); + + face_id = PG_GETARG_INT32(1) ; + + if ( SPI_OK_CONNECT != SPI_connect() ) { + lwpgerror("Could not connect to SPI"); + PG_RETURN_NULL(); + } + be_data.data_changed = false; + + topo = lwt_LoadTopology(be_iface, toponame); + pfree(toponame); + if ( ! topo ) { + /* should never reach this point, as lwerror would raise an exception */ + SPI_finish(); + PG_RETURN_NULL(); + } + + POSTGIS_DEBUG(1, "Calling lwt_GetFaceGeometry"); + lwgeom = lwt_GetFaceGeometry(topo, face_id); + POSTGIS_DEBUG(1, "lwt_GetFaceGeometry returned"); + lwt_FreeTopology(topo); + + if ( lwgeom == NULL ) { + /* should never reach this point, as lwerror would raise an exception */ + SPI_finish(); + PG_RETURN_NULL(); + } + + /* Serialize in upper memory context (outside of SPI) */ + /* TODO: use a narrower context to switch to */ + old_context = MemoryContextSwitchTo( TopMemoryContext ); + geom = geometry_serialize(lwgeom); + MemoryContextSwitchTo(old_context); + + SPI_finish(); + + PG_RETURN_POINTER(geom); +} diff --git a/topology/sql/sqlmm.sql.in b/topology/sql/sqlmm.sql.in index fbf9a95b8..c426f931a 100644 --- a/topology/sql/sqlmm.sql.in +++ b/topology/sql/sqlmm.sql.in @@ -1407,63 +1407,8 @@ LANGUAGE 'plpgsql' VOLATILE; -- CREATE OR REPLACE FUNCTION topology.ST_GetFaceGeometry(toponame varchar, aface integer) RETURNS GEOMETRY AS -$$ -DECLARE - rec RECORD; - sql TEXT; -BEGIN - - -- - -- toponame and aface are required - -- - IF toponame IS NULL OR aface IS NULL THEN - RAISE EXCEPTION 'SQL/MM Spatial exception - null argument'; - END IF; - - IF NOT EXISTS(SELECT name FROM topology.topology WHERE name = toponame) THEN - RAISE EXCEPTION 'SQL/MM Spatial exception - invalid topology name'; - END IF; - - IF aface = 0 THEN - RAISE EXCEPTION - 'SQL/MM Spatial exception - universal face has no geometry'; - END IF; - - BEGIN - - -- No such face - sql := 'SELECT NOT EXISTS (SELECT * from ' || quote_ident(toponame) - || '.face WHERE face_id = ' || aface - || ') as none'; - EXECUTE sql INTO rec; - IF rec.none THEN - RAISE EXCEPTION 'SQL/MM Spatial exception - non-existent face.'; - END IF; - - -- - -- Construct face - -- - sql := - 'SELECT ST_BuildArea(ST_Collect(geom)) as geom FROM ' - || quote_ident(toponame) - || '.edge_data WHERE left_face = ' || aface - || ' OR right_face = ' || aface; - FOR rec IN EXECUTE sql - LOOP - RETURN rec.geom; - END LOOP; - - EXCEPTION - WHEN INVALID_SCHEMA_NAME THEN - RAISE EXCEPTION 'SQL/MM Spatial exception - invalid topology name'; - WHEN UNDEFINED_TABLE THEN - RAISE EXCEPTION 'corrupted topology "%"', toponame; - END; - - RETURN NULL; -END -$$ -LANGUAGE 'plpgsql' STABLE; + 'MODULE_PATHNAME', 'ST_GetFaceGeometry' + LANGUAGE 'c' STABLE; --} ST_GetFaceGeometry diff --git a/topology/topology.sql.in b/topology/topology.sql.in index df0417fb0..a0979abbb 100644 --- a/topology/topology.sql.in +++ b/topology/topology.sql.in @@ -1562,7 +1562,7 @@ BEGIN -- Scan the table looking for NULL geometries FOR rec IN EXECUTE 'SELECT f1.face_id FROM ' - 'face_check f1 WHERE f1.geom IS NULL' + 'face_check f1 WHERE f1.geom IS NULL OR ST_IsEmpty(f1.geom)' LOOP -- Face missing ! retrec.error := 'face has no rings';