]> granicus.if.org Git - postgis/commitdiff
Implement ST_GetFaceGeometry in C
authorSandro Santilli <strk@keybit.net>
Fri, 31 Jul 2015 16:34:10 +0000 (16:34 +0000)
committerSandro Santilli <strk@keybit.net>
Fri, 31 Jul 2015 16:34:10 +0000 (16:34 +0000)
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

liblwgeom/TODO
liblwgeom/liblwgeom_topo.h
liblwgeom/liblwgeom_topo_internal.h
liblwgeom/lwgeom_topo.c
topology/postgis_topology.c
topology/sql/sqlmm.sql.in
topology/topology.sql.in

index 78309c67d4c4612d3d0e87a1b64d7e7f4706ad14..06f069963a2d737e039be7c39ff08310fdf8d08a 100644 (file)
@@ -7,8 +7,8 @@ lwt_ModEdgeSplit      X
 lwt_NewEdgesSplit     X
 
 lwt_AddIsoEdge        X
+lwt_GetFaceGeometry   X
 lwt_GetFaceEdges
-lwt_GetFaceGeometry
 
 lwt_ChangeEdgeGeom
 lwt_MoveIsoNode
index 7368c2ebef45a9f263b5221ad4558c6a83b4ed98..6dade5c0093d4ed98e51ce951f71671e18d04353 100644 (file)
@@ -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);
 
index 3c2176d7c22ce0af991ac4ce106d969fa72ddf34..49a9067973446d6443596b103ceb858310a6075f 100644 (file)
@@ -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 */
index bcc799d083e3d6cdbb8db37a58357b48cf5fd085..44911c806d263e21a2a6373a3d313af6c4a318cc 100644 (file)
@@ -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; i<numfaceedges; ++i )
+  {
+    /* NOTE: skipping edges with same face on both sides, although
+     *       correct, results in a failure to build faces from
+     *       invalid topologies as expected by legacy tests.
+     * TODO: update legacy tests expectances/unleash this skipping ?
+     */
+    /* if ( edges[i].face_left == edges[i].face_right ) continue; */
+    geoms[validedges++] = lwline_as_lwgeom(edges[i].geom);
+  }
+  if ( ! validedges )
+  {
+    lwfree(geoms);
+    /* Face has no valid 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);
+  }
+  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;
+}
index fbc55a15246720049b647923538de77c3830258a..f721eaf5879759196229c12797974f26ad5fc3d0 100644 (file)
@@ -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);
+}
index fbf9a95b84647038f62038c48b679b8b418992a3..c426f931a5c984b9e798c8511aebc91dfdd7d9ae 100644 (file)
@@ -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
 
 
index df0417fb0ec84a105d6ba1d2fcf00608ec08f8d4..a0979abbb47997a1892fff8eaad8aae0b1d95493 100644 (file)
@@ -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';