From: Sandro Santilli Date: Mon, 29 Jun 2015 07:49:24 +0000 (+0000) Subject: Implement ST_AddIsoNode in C X-Git-Tag: 2.2.0rc1~303 X-Git-Url: https://granicus.if.org/sourcecode?a=commitdiff_plain;h=07d4167439ad55c12b2d6214d5b891cfe911ca5e;p=postgis Implement ST_AddIsoNode in C Funded by Tuscany Region (Italy) - SITA (CIG: 60351023B8) git-svn-id: http://svn.osgeo.org/postgis/trunk@13735 b70326c6-7e19-0410-871a-916f4a2858ee --- diff --git a/liblwgeom/liblwgeom_topo.h b/liblwgeom/liblwgeom_topo.h index a40556a90..f09225274 100644 --- a/liblwgeom/liblwgeom_topo.h +++ b/liblwgeom/liblwgeom_topo.h @@ -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) ( diff --git a/liblwgeom/lwgeom_topo.c b/liblwgeom/lwgeom_topo.c index 2b7d5a46f..e54c15899 100644 --- a/liblwgeom/lwgeom_topo.c +++ b/liblwgeom/lwgeom_topo.c @@ -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; } diff --git a/topology/postgis_topology.c b/topology/postgis_topology.c index 007757d90..70476ca07 100644 --- a/topology/postgis_topology.c +++ b/topology/postgis_topology.c @@ -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; ivals[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); +} diff --git a/topology/sql/sqlmm.sql.in b/topology/sql/sqlmm.sql.in index 22a1019cd..564085ed7 100644 --- a/topology/sql/sqlmm.sql.in +++ b/topology/sql/sqlmm.sql.in @@ -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 --{