]> granicus.if.org Git - postgis/commitdiff
Implement ST_AddIsoNode in C
authorSandro Santilli <strk@keybit.net>
Mon, 29 Jun 2015 07:49:24 +0000 (07:49 +0000)
committerSandro Santilli <strk@keybit.net>
Mon, 29 Jun 2015 07:49:24 +0000 (07:49 +0000)
Funded by Tuscany Region (Italy) - SITA (CIG: 60351023B8)

git-svn-id: http://svn.osgeo.org/postgis/trunk@13735 b70326c6-7e19-0410-871a-916f4a2858ee

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

index a40556a900ba43a64b161fb493545e916b9d8323..f09225274a00c8d72f17e7961a37d1e8f12a1886 100644 (file)
@@ -365,12 +365,9 @@ typedef struct LWT_BE_CALLBACKS_T {
    *
    * @param topo the topology to act upon
    * @param pt the query point
-   * @param numelems input/output parameter, pass number of edge identifiers
-   *                 in the input array, gets number of node in output array.
-   * @param fields fields to be filled in the returned structure, see
-   *               LWT_COL_FACE_* macros
    *
-   * @return a face identifier, -1 if point is on a topology edge
+   * @return a face identifier, -1 if no face contains the point
+   *         (could be in universe face or on an edge)
    *         or -2 on error (@see lastErrorMessage)
    */
   LWT_ELEMID (*getFaceContainingPoint) (
index 2b7d5a46fd1b262b3162a2b8acd10d7b2d32aa8b..e54c15899b2117743d7517e99abcc4b7a8c2f86e 100644 (file)
@@ -250,7 +250,7 @@ LWT_ELEMID lwt_AddIsoNode(LWT_TOPOLOGY* topo, LWT_ELEMID face, LWPOINT* pt,
     }
     if ( lwt_be_ExistsEdgeIntersectingPoint(topo, pt) ) /*x*/
     {
-      lwerror("SQL/MM Spatial exception - edge crosses node");
+      lwerror("SQL/MM Spatial exception - edge crosses node.");
       return -1;
     }
   }
@@ -262,18 +262,19 @@ LWT_ELEMID lwt_AddIsoNode(LWT_TOPOLOGY* topo, LWT_ELEMID face, LWPOINT* pt,
       lwerror("Backend error: %s", lwt_be_lastErrorMessage(topo->be_iface));
       return -1;
     }
-    if ( foundInFace == -1 ) {
-      lwerror("SQL/MM Spatial exception - edge crosses node");
-      return -1;
-    }
+    if ( foundInFace == -1 ) foundInFace = 0;
   }
 
   if ( face == -1 ) {
     face = foundInFace;
   }
   else if ( ! skipISOChecks && foundInFace != face ) {
-    lwerror("SQL/MM Spatial exception - within face % (not %)",
+#if 0
+    lwerror("SQL/MM Spatial exception - within face %d (not %d)",
             foundInFace, face);
+#else
+    lwerror("SQL/MM Spatial exception - not within face");
+#endif
     return -1;
   }
 
index 007757d90e45141aa360810ef88ace40d760e6c3..70476ca07b30659877d2f84f0035e71362b8c816 100644 (file)
@@ -473,6 +473,82 @@ cb_getEdgeById(const LWT_BE_TOPOLOGY* topo,
   return edges;
 }
 
+static LWT_ISO_EDGE*
+cb_getEdgeWithinDistance2D(const LWT_BE_TOPOLOGY* topo,
+      const LWPOINT* pt, double dist, int* numelems,
+      int fields, int limit)
+{
+  LWT_ISO_EDGE *edges;
+       int spi_result;
+  int elems_requested = limit;
+  size_t hexewkb_size;
+  char *hexewkb;
+
+  StringInfoData sqldata;
+  StringInfo sql = &sqldata;
+  int i;
+
+  initStringInfo(sql);
+  if ( elems_requested == -1 ) {
+    appendStringInfoString(sql, "SELECT EXISTS ( SELECT 1");
+  } else {
+    appendStringInfoString(sql, "SELECT ");
+    addEdgeFields(sql, fields, 0);
+  }
+  appendStringInfo(sql, " FROM \"%s\".edge_data", topo->name);
+  // TODO: use binary cursor here ?
+  hexewkb = lwgeom_to_hexwkb(lwpoint_as_lwgeom(pt), WKB_EXTENDED, &hexewkb_size);
+  if ( dist ) {
+    appendStringInfo(sql, " WHERE ST_DWithin('%s'::geometry, geom, %g)", hexewkb, dist);
+  } else {
+    appendStringInfo(sql, " WHERE ST_Within('%s'::geometry, geom)", hexewkb);
+  }
+  lwfree(hexewkb);
+  if ( elems_requested == -1 ) {
+    appendStringInfoString(sql, ")");
+  } else if ( elems_requested > 0 ) {
+    appendStringInfo(sql, " LIMIT %d", elems_requested);
+  }
+  lwpgnotice("cb_getEdgeWithinDistance2D: query is: %s", sql->data);
+  spi_result = SPI_execute(sql->data, true, limit >= 0 ? limit : 0);
+  if ( spi_result != SPI_OK_SELECT ) {
+               cberror(topo->be_data, "unexpected return (%d) from query execution: %s", spi_result, sql->data);
+         *numelems = -1; return NULL;
+  }
+  pfree(sqldata.data);
+
+  lwpgnotice("cb_getEdgeWithinDistance2D: edge query "
+             "(limited by %d) returned %d rows",
+             elems_requested, SPI_processed);
+  *numelems = SPI_processed;
+  if ( ! SPI_processed ) {
+    return NULL;
+  }
+
+  if ( elems_requested == -1 )
+  {
+    /* This was an EXISTS query */
+    {
+      Datum dat;
+      bool isnull, exists;
+      dat = SPI_getbinval(SPI_tuptable->vals[0], SPI_tuptable->tupdesc, 1, &isnull);
+      exists = DatumGetBool(dat);
+      *numelems = exists ? 1 : 0;
+      lwpgnotice("cb_getEdgeWithinDistance2D: exists ? %d", *numelems);
+    }
+    return NULL;
+  }
+
+  edges = palloc( sizeof(LWT_ISO_EDGE) * SPI_processed );
+  for ( i=0; i<SPI_processed; ++i )
+  {
+    HeapTuple row = SPI_tuptable->vals[i];
+    fillEdgeFields(&edges[i], row, SPI_tuptable->tupdesc, fields);
+  }
+
+  return edges;
+}
+
 static LWT_ISO_NODE*
 cb_getNodeWithinDistance2D(const LWT_BE_TOPOLOGY* topo,
       const LWPOINT* pt, double dist, int* numelems,
@@ -828,6 +904,51 @@ cb_updateTopoGeomEdgeSplit ( const LWT_BE_TOPOLOGY* topo,
   return 1;
 }
 
+static LWT_ELEMID
+cb_getFaceContainingPoint( const LWT_BE_TOPOLOGY* topo, const LWPOINT* pt )
+{
+       int spi_result;
+  StringInfoData sqldata;
+  StringInfo sql = &sqldata;
+  bool isnull;
+  Datum dat;
+  LWT_ELEMID face_id;
+  size_t hexewkb_size;
+  char *hexewkb;
+
+  initStringInfo(sql);
+
+  hexewkb = lwgeom_to_hexwkb(lwpoint_as_lwgeom(pt), WKB_EXTENDED, &hexewkb_size);
+  /* TODO: call GetFaceGeometry internally, avoiding the round-trip to sql */
+  appendStringInfo(sql, "SELECT face_id FROM \"%s\".face "
+                        "WHERE mbr && '%s'::geometry AND ST_Contains("
+     "topology.ST_GetFaceGeometry('%s', face_id), "
+     "'%s'::geometry) LIMIT 1",
+      topo->name, hexewkb, topo->name, hexewkb);
+  lwfree(hexewkb);
+
+  spi_result = SPI_execute(sql->data, true, 1);
+  if ( spi_result != SPI_OK_SELECT ) {
+               cberror(topo->be_data, "unexpected return (%d) from query execution: %s",
+            spi_result, sql->data);
+         return -2;
+  }
+  pfree(sqldata.data);
+
+  if ( SPI_processed != 1 ) {
+         return -1; /* none found */
+  }
+
+  dat = SPI_getbinval( SPI_tuptable->vals[0],
+                       SPI_tuptable->tupdesc, 1, &isnull );
+  if ( isnull ) {
+               cberror(topo->be_data, "corrupted topology: face with NULL face_id");
+         return -2;
+  }
+  face_id = DatumGetInt32(dat);
+  return face_id;
+}
+
 LWT_BE_CALLBACKS be_callbacks = {
     cb_lastErrorMessage,
     NULL, /* createTopology */
@@ -836,13 +957,13 @@ LWT_BE_CALLBACKS be_callbacks = {
     NULL, /* getNodeById */
     cb_getNodeWithinDistance2D,
     cb_insertNodes,
-    cb_getEdgeById, /* getEdgeById */
-    NULL, /* getEdgeWithinDistance2D */
+    cb_getEdgeById,
+    cb_getEdgeWithinDistance2D,
     cb_getNextEdgeId,
     cb_insertEdges,
     cb_updateEdges,
     NULL, /* getFacesById */
-    NULL, /* getFaceContainingPoint */
+    cb_getFaceContainingPoint,
     cb_updateTopoGeomEdgeSplit
 };
 
@@ -952,3 +1073,79 @@ Datum ST_ModEdgeSplit(PG_FUNCTION_ARGS)
   SPI_finish();
   PG_RETURN_INT32(node_id);
 }
+
+/*  ST_AddIsoNode(atopology, aface, apoint) */
+Datum ST_AddIsoNode(PG_FUNCTION_ARGS);
+PG_FUNCTION_INFO_V1(ST_AddIsoNode);
+Datum ST_AddIsoNode(PG_FUNCTION_ARGS)
+{
+  text* toponame_text;
+  char* toponame;
+  LWT_ELEMID containing_face;
+  LWT_ELEMID node_id;
+  GSERIALIZED *geom;
+  LWGEOM *lwgeom;
+  LWPOINT *pt;
+  LWT_TOPOLOGY *topo;
+
+  if ( PG_ARGISNULL(0) || PG_ARGISNULL(2) ) {
+    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);
+
+  if ( PG_ARGISNULL(1) ) containing_face = -1;
+  else {
+    containing_face = PG_GETARG_INT32(1);
+    if ( containing_face < 0 ) {
+      lwpgerror("SQL/MM Spatial exception - not within face");
+      PG_RETURN_NULL();
+    }
+  }
+
+  geom = PG_GETARG_GSERIALIZED_P(2);
+  lwgeom = lwgeom_from_gserialized(geom);
+  pt = lwgeom_as_lwpoint(lwgeom);
+  if ( ! pt ) {
+    lwgeom_free(lwgeom);
+         PG_FREE_IF_COPY(geom, 2);
+#if 0
+    lwpgerror("ST_AddIsoNode third argument must be a point geometry");
+#else
+    lwpgerror("SQL/MM Spatial exception - invalid point");
+#endif
+    PG_RETURN_NULL();
+  }
+
+  if ( SPI_OK_CONNECT != SPI_connect() ) {
+    lwpgerror("Could not connect to SPI");
+    PG_RETURN_NULL();
+  }
+
+  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_AddIsoNode");
+  node_id = lwt_AddIsoNode(topo, containing_face, pt, 0);
+  POSTGIS_DEBUG(1, "lwt_AddIsoNode returned");
+  lwgeom_free(lwgeom);
+  PG_FREE_IF_COPY(geom, 3);
+  lwt_FreeTopology(topo);
+
+  if ( node_id == -1 ) {
+    /* should never reach this point, as lwerror would raise an exception */
+    SPI_finish();
+    PG_RETURN_NULL();
+  }
+
+  SPI_finish();
+  PG_RETURN_INT32(node_id);
+}
index 22a1019cd4e4398eb51121cc6401a6c6b585b4a5..564085ed7e61872d410c6c1295e195b7ffb88181 100644 (file)
@@ -1475,129 +1475,8 @@ LANGUAGE 'plpgsql' STABLE;
 --
 CREATE OR REPLACE FUNCTION topology.ST_AddIsoNode(atopology varchar, aface integer, apoint geometry)
   RETURNS INTEGER AS
-$$
-DECLARE
-  rec RECORD;
-  nodeid integer;
-  sql text;
-  containingface integer;
-BEGIN
-
-  --
-  -- Atopology and apoint are required
-  -- 
-  IF atopology IS NULL OR apoint IS NULL THEN
-    RAISE EXCEPTION
-     'SQL/MM Spatial exception - null argument';
-  END IF;
-
-  --
-  -- Atopology must  be registered
-  -- 
-  IF NOT EXISTS(SELECT name FROM topology.topology WHERE topology.name = atopology) THEN
-    RAISE EXCEPTION
-     'SQL/MM Spatial exception - invalid topology name';
-  END IF;
-  --
-  -- Apoint must be a point
-  --
-  IF substring(geometrytype(apoint), 1, 5) != 'POINT'
-  THEN
-    RAISE EXCEPTION
-     'SQL/MM Spatial exception - invalid point';
-  END IF;
-
-  --
-  -- Check if a coincident node already exists
-  -- 
-  -- We use index AND x/y equality
-  --
-  FOR rec IN EXECUTE 'SELECT node_id FROM '
-    || quote_ident(atopology)
-    || '.node WHERE ST_Equals(geom, $1)'
-    USING apoint
-  LOOP
-    RAISE EXCEPTION
-     'SQL/MM Spatial exception - coincident node';
-  END LOOP;
-
-  --
-  -- Check if any edge crosses (intersects) this node
-  -- I used _intersects_ here to include boundaries (endpoints)
-  --
-  FOR rec IN EXECUTE 'SELECT edge_id FROM '
-    || quote_ident(atopology) || '.edge ' 
-    || 'WHERE ST_Intersects(geom, $1)'
-    USING apoint
-  LOOP
-    RAISE EXCEPTION
-    'SQL/MM Spatial exception - edge crosses node.';
-  END LOOP;
-
-  -- retrieve the face that contains (eventually) the point
-  
-  --
-  -- first test is to check if there is inside an mbr (more fast)
-  --
-  sql := 'SELECT f.face_id FROM ' 
-        || quote_ident(atopology) 
-        || '.face f WHERE f.face_id > 0 AND f.mbr && $1'
-        || ' AND ST_Contains(topology.ST_GetFaceGeometry('
-        || quote_literal(atopology) 
-        || ', f.face_id), $1)'
-        ;
-  IF aface IS NOT NULL AND aface != 0 THEN
-    sql := sql || ' AND f.face_id = ' || aface;
-  END IF;
-
-#ifdef POSTGIS_TOPOLOGY_DEBUG
-  RAISE DEBUG '%', sql;
-#endif
-  EXECUTE sql INTO containingface USING apoint;
-
-  -- If aface was specified, check that it was correct
-  IF aface IS NOT NULL THEN -- {
-    IF aface = 0 THEN -- {
-      IF containingface IS NOT NULL THEN -- {
-        RAISE EXCEPTION
-          'SQL/MM Spatial exception - within face % (not universe)',
-          containingface;
-      ELSE -- }{
-        containingface := 0;
-      END IF; -- }
-    ELSE -- }{ -- aface != 0
-      IF containingface IS NULL OR containingface != aface THEN -- {
-        RAISE EXCEPTION 'SQL/MM Spatial exception - not within face';
-      END IF; -- }
-    END IF; -- }
-  ELSE -- }{ -- aface is null
-    containingface := COALESCE(containingface, 0);
-  END IF; -- }
-
-  --
-  -- Insert the new row
-  --
-  sql := 'INSERT INTO '
-      || quote_ident(atopology)
-      || '.node(node_id, geom, containing_face) SELECT nextval('
-      || quote_literal( quote_ident(atopology) || '.node_node_id_seq' )
-      || '), $1, '
-      || containingface
-      || ' RETURNING node_id';
-#ifdef POSTGIS_TOPOLOGY_DEBUG
-  RAISE DEBUG '%', sql;
-#endif
-
-  EXECUTE sql INTO nodeid USING apoint;
-
-  RETURN nodeid;
-EXCEPTION
-  -- TODO: avoid the EXCEPTION handling here ?
-  WHEN INVALID_SCHEMA_NAME THEN
-    RAISE EXCEPTION 'SQL/MM Spatial exception - invalid topology name';
-END
-$$
-LANGUAGE 'plpgsql' VOLATILE;
+       'MODULE_PATHNAME','ST_AddIsoNode'
+  LANGUAGE 'c' VOLATILE;
 --} ST_AddIsoNode
 
 --{