123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472 |
- /*!
- \file lib/vector/Vlib/build_pg.c
- \brief Vector library - Building topology for PostGIS layers
- Higher level functions for reading/writing/manipulating vectors.
- Line offset is
- - centroids : FID
- - other types : index of the first record (which is FID) in offset array.
- (C) 2012 by the GRASS Development Team
- This program is free software under the GNU General Public License
- (>=v2). Read the file COPYING that comes with GRASS for details.
- \author Martin Landa <landa.martin gmail.com>
- */
- #include <grass/vector.h>
- #include <grass/glocale.h>
- #ifdef HAVE_POSTGRES
- #include "pg_local_proto.h"
- static int build_topo(struct Map_info *, int);
- static int build_topogeom_stmt(const struct Format_info_pg *, int, int, char *);
- static int save_map_bbox(const struct Format_info_pg *, const struct bound_box*);
- static int create_topo_grass(const struct Format_info_pg *);
- static int has_topo_grass(const struct Format_info_pg *);
- #endif
- /*!
- \brief Build topology for PostGIS layer
- Build levels:
- - GV_BUILD_NONE
- - GV_BUILD_BASE
- - GV_BUILD_ATTACH_ISLES
- - GV_BUILD_CENTROIDS
- - GV_BUILD_ALL
- \param Map pointer to Map_info structure
- \param build build level
- \return 1 on success
- \return 0 on error
- */
- int Vect_build_pg(struct Map_info *Map, int build)
- {
- #ifdef HAVE_POSTGRES
- struct Plus_head *plus;
- struct Format_info_pg *pg_info;
- plus = &(Map->plus);
- pg_info = &(Map->fInfo.pg);
- G_debug(1, "Vect_build_pg(): db='%s' table='%s', build=%d",
- pg_info->db_name, pg_info->table_name, build);
- if (build == plus->built)
- return 1; /* do nothing */
- /* TODO move this init to better place (Vect_open_ ?), because in
- theory build may be reused on level2 */
- if (build >= plus->built && build > GV_BUILD_BASE) {
- G_free((void *)pg_info->offset.array);
- G_zero(&(pg_info->offset), sizeof(struct Format_info_offset));
- }
- if (!pg_info->conn) {
- G_warning(_("No DB connection"));
- return 0;
- }
- if (!pg_info->fid_column && !pg_info->toposchema_name) {
- G_warning(_("Feature table <%s> has no primary key defined"),
- pg_info->table_name);
- G_warning(_("Random read is not supported for this layer. "
- "Unable to build topology."));
- return 0;
- }
- /* commit transaction block (update mode only) */
- if (pg_info->inTransaction && Vect__execute_pg(pg_info->conn, "COMMIT") == -1)
- return 0;
- pg_info->inTransaction = FALSE;
-
- if (build > GV_BUILD_NONE) {
- G_message(_("Using external data format '%s' (feature type '%s')"),
- Vect_get_finfo_format_info(Map),
- Vect_get_finfo_geometry_type(Map));
- if (!pg_info->toposchema_name)
- G_message(_("Building pseudo-topology over simple features..."));
- else
- G_message(_("Building topology from PostGIS topology schema <%s>..."),
- pg_info->toposchema_name);
- }
- if (!pg_info->toposchema_name) /* pseudo-topology for simple features */
- return Vect__build_sfa(Map, build);
-
- /* PostGIS Topology */
- return build_topo(Map, build);
- #else
- G_fatal_error(_("GRASS is not compiled with PostgreSQL support"));
- return 0;
- #endif
- }
- #ifdef HAVE_POSTGRES
- /*!
- \brief Build from PostGIS topology schema
- \todo Attach isles
- \param Map pointer to Map_info struct
- \param build build level
- \return 1 on success
- \return 0 on error
- */
- int build_topo(struct Map_info *Map, int build)
- {
- int line, type, topo_id, s;
- int area, nareas, isle, nisles;
- int face[2];
- char stmt[DB_SQL_MAX];
-
- struct Plus_head *plus;
- struct Format_info_pg *pg_info;
- struct P_line *Line;
- struct P_area *Area;
- struct P_topo_c *topo_c;
- struct P_topo_b *topo_b;
- struct P_isle *Isle;
-
- plus = &(Map->plus);
- pg_info = &(Map->fInfo.pg);
-
- /* check if upgrade or downgrade */
- if (build < plus->built) {
- /* -> downgrade */
- Vect__build_downgrade(Map, build);
- return 1;
- }
- /* -> upgrade */
-
- if (build < GV_BUILD_BASE)
- return 1; /* nothing to print */
-
- /* update TopoGeometry based on GRASS-like topology */
- Vect_build_nat(Map, build);
-
- /* store map boundig box in DB */
- save_map_bbox(pg_info, &(plus->box));
-
- /* begin transaction */
- if (Vect__execute_pg(pg_info->conn, "BEGIN"))
- return 0;
-
- /* update faces from GRASS Topology */
- if (build >= GV_BUILD_AREAS) {
- /* reset centroids to '0' (universal face) */
- sprintf(stmt, "UPDATE \"%s\".node SET containing_face = 0 WHERE "
- "containing_face IS NOT NULL", pg_info->toposchema_name);
- G_debug(2, "SQL: %s", stmt);
- if(Vect__execute_pg(pg_info->conn, stmt) == -1) {
- Vect__execute_pg(pg_info->conn, "ROLLBACK");
- return 0;
- }
- /* reset left|right edges */
- sprintf(stmt, "UPDATE \"%s\".edge_data SET left_face = 0, right_face = 0",
- pg_info->toposchema_name);
- G_debug(2, "SQL: %s", stmt);
- if(Vect__execute_pg(pg_info->conn, stmt) == -1) {
- Vect__execute_pg(pg_info->conn, "ROLLBACK");
- return 0;
- }
- /* delete faces */
- sprintf(stmt, "DELETE FROM \"%s\".face WHERE "
- "face_id != 0", pg_info->toposchema_name);
- G_debug(2, "SQL: %s", stmt);
- if(Vect__execute_pg(pg_info->conn, stmt) == -1) {
- Vect__execute_pg(pg_info->conn, "ROLLBACK");
- return 0;
- }
-
- /* insert face from GRASS topology */
- nareas = Vect_get_num_areas(Map);
- for (area = 1; area <= nareas; area++) {
- if (0 == Vect__insert_face_pg(Map, area)) {
- Vect__execute_pg(pg_info->conn, "ROLLBACK");
- return 0;
- }
-
- if (build < GV_BUILD_CENTROIDS)
- continue;
-
- /* update centroids */
- Area = plus->Area[area];
- if (Area->centroid < 1) {
- G_warning(_("Area %d without centroid"), area);
- continue;
- }
-
- Line = plus->Line[Area->centroid];
- sprintf(stmt, "UPDATE \"%s\".node SET "
- "containing_face = %d WHERE node_id = %lu",
- pg_info->toposchema_name, area, Line->offset);
- G_debug(2, "SQL: %s", stmt);
-
- if(Vect__execute_pg(pg_info->conn, stmt) == -1) {
- Vect__execute_pg(pg_info->conn, "ROLLBACK");
- return 0;
- }
- }
- }
-
- if (build >= GV_BUILD_ATTACH_ISLES) {
- /* insert isles as faces with negative face_id */
- nisles = Vect_get_num_islands(Map);
- for(isle = 1; isle <= nisles; isle++) {
- Isle = plus->Isle[isle];
- Vect__insert_face_pg(Map, -isle);
- }
- }
- G_message(_("Updating TopoGeometry data..."));
- for (line = 1; line <= plus->n_lines; line++) {
- type = Vect_read_line(Map, NULL, NULL, line);
- G_percent(line, plus->n_lines, 3);
-
- Line = Map->plus.Line[line];
- if (!Line) {
- G_warning(_("Inconsistency in topology detected. "
- "Dead line found."));
- return 0;
- }
-
- if (build >= GV_BUILD_AREAS && type == GV_BOUNDARY) {
- topo_b = (struct P_topo_b *) Line->topo;
- for (s = 0; s < 2; s++) { /* for both sides */
- face[s] = s == 0 ? topo_b->left : topo_b->right;
- if (face[s] < 0) {
- Isle = plus->Isle[abs(face[s])];
- if (Isle->area > 0) {
- face[s] = Isle->area;
- }
- else { /* -> universal face */
- face[s] = 0;
- }
- }
- }
-
- sprintf(stmt, "UPDATE \"%s\".edge_data SET "
- "left_face = %d, right_face = %d "
- "WHERE edge_id = %lu", pg_info->toposchema_name,
- face[0], face[1], Line->offset);
- G_debug(2, "SQL: %s", stmt);
-
- if(Vect__execute_pg(pg_info->conn, stmt) == -1) {
- Vect__execute_pg(pg_info->conn, "ROLLBACK");
- return 0;
- }
- continue;
- }
-
- /* determine id */
- if (type == GV_CENTROID) {
- topo_c = (struct P_topo_c *) Line->topo;
- topo_id = topo_c->area;
- }
- else {
- topo_id = line; /* GV_POINT | GV_LINE */
- }
-
- if (build_topogeom_stmt(pg_info, topo_id, type, stmt) &&
- Vect__execute_pg(pg_info->conn, stmt) == -1) {
- Vect__execute_pg(pg_info->conn, "ROLLBACK");
- return 0;
- }
- }
-
- if (Vect__execute_pg(pg_info->conn, "COMMIT") == -1)
- return 0;
- return 1;
- }
- /*!
- \brief Build UPDATE statement for topo geometry element stored in
- feature table
- \param pg_info so pointer to Format_info_pg
- \param type feature type (GV_POINT, ...)
- \param id topology element id
- \param[out] stmt string buffer
-
- \return 1 on success
- \return 0 on failure
- */
- int build_topogeom_stmt(const struct Format_info_pg *pg_info,
- int id, int type, char *stmt)
- {
- int topogeom_type;
-
- switch(type) {
- case GV_POINT:
- topogeom_type = 1;
- break;
- case GV_LINE:
- case GV_BOUNDARY:
- topogeom_type = 2;
- break;
- case GV_CENTROID:
- topogeom_type = 3;
- break;
- default:
- G_warning(_("Unsupported topo geometry type %d"), type);
- return 0;
- }
-
- sprintf(stmt, "UPDATE \"%s\".\"%s\" SET %s = "
- "'(%d, 1, %d, %d)'::topology.TopoGeometry "
- "WHERE %s = %d",
- pg_info->schema_name, pg_info->table_name,
- pg_info->topogeom_column, pg_info->toposchema_id,
- id, topogeom_type, pg_info->fid_column, id);
- return 1;
- }
- /*!
- \brief Store map bounding box in DB head table
- \param pg_info pointer to Format_info_pg struct
- \param box pointer to bounding box
- \return 1 on success
- \return 0 on failure
- */
- int save_map_bbox(const struct Format_info_pg *pg_info, const struct bound_box *box)
- {
- char stmt[DB_SQL_MAX];
-
- /* create if not exists */
- if (create_topo_grass(pg_info) == -1) {
- G_warning(_("Unable to create <%s.%s>"), TOPO_SCHEMA, TOPO_TABLE);
- return 0;
- }
-
- /* update bbox */
- if (has_topo_grass(pg_info)) {
- /* -> update */
- sprintf(stmt, "UPDATE \"%s\".\"%s\" SET %s = "
- "'BOX3D(%.12f %.12f %.12f, %.12f %.12f %.12f)'::box3d WHERE %s = %d",
- TOPO_SCHEMA, TOPO_TABLE, TOPO_BBOX,
- box->W, box->S, box->B, box->E, box->N, box->T,
- TOPO_ID, pg_info->toposchema_id);
- }
- else {
- /* -> insert */
- sprintf(stmt, "INSERT INTO \"%s\".\"%s\" (%s, %s) "
- "VALUES(%d, 'BOX3D(%.12f %.12f %.12f, %.12f %.12f %.12f)'::box3d)",
- TOPO_SCHEMA, TOPO_TABLE, TOPO_ID, TOPO_BBOX, pg_info->toposchema_id,
- box->W, box->S, box->B, box->E, box->N, box->T);
- }
-
- if (Vect__execute_pg(pg_info->conn, stmt) == -1) {
- return -1;
- }
-
- return 1;
- }
- /*!
- \brief Creates 'topology.grass' table if not exists
- \return 0 table already exists
- \return 1 table successfully added
- \return -1 on error
- */
- int create_topo_grass(const struct Format_info_pg *pg_info)
- {
- char stmt[DB_SQL_MAX];
- PGresult *result;
-
- /* check if table exists */
- sprintf(stmt, "SELECT COUNT(*) FROM information_schema.tables "
- "WHERE table_schema = '%s' AND table_name = '%s'",
- TOPO_SCHEMA, TOPO_TABLE);
- result = PQexec(pg_info->conn, stmt);
- if (!result || PQresultStatus(result) != PGRES_TUPLES_OK) {
- PQclear(result);
- return -1;
- }
-
- if (atoi(PQgetvalue(result, 0, 0)) == 1) {
- /* table already exists */
- PQclear(result);
- return 1;
- }
- PQclear(result);
-
- G_debug(1, "<%s.%s> created", TOPO_SCHEMA, TOPO_TABLE);
-
- /* create table */
- sprintf(stmt, "CREATE TABLE \"%s\".\"%s\" (%s INTEGER, %s box3d)",
- TOPO_SCHEMA, TOPO_TABLE, TOPO_ID, TOPO_BBOX);
- if (Vect__execute_pg(pg_info->conn, stmt) == -1) {
- return -1;
- }
- /* add primary key */
- sprintf(stmt, "ALTER TABLE \"%s\".\"%s\" ADD PRIMARY KEY (%s)",
- TOPO_SCHEMA, TOPO_TABLE, TOPO_ID);
- if (Vect__execute_pg(pg_info->conn, stmt) == -1) {
- return -1;
- }
- /* add constraint */
- sprintf(stmt, "ALTER TABLE \"%s\".\"%s\" ADD CONSTRAINT \"%s_%s_fkey\" "
- "FOREIGN KEY (%s) REFERENCES topology.topology(id) ON DELETE CASCADE",
- TOPO_SCHEMA, TOPO_TABLE, TOPO_TABLE, TOPO_ID, TOPO_ID);
- if (Vect__execute_pg(pg_info->conn, stmt) == -1) {
- return -1;
- }
-
- return 1;
- }
- /*!
- \brief Check if 'topology_id' exists in 'topology.grass'
- \param pg_info pointer to Format_info_pg struct
- \return TRUE if exists
- \return FALSE otherwise
- \return -1 on error
- */
- int has_topo_grass(const struct Format_info_pg *pg_info)
- {
- int has_topo;
- char stmt[DB_SQL_MAX];
-
- PGresult *result;
-
- sprintf(stmt, "SELECT COUNT(*) FROM \"%s\".\"%s\" "
- "WHERE %s = %d",
- TOPO_SCHEMA, TOPO_TABLE, TOPO_ID, pg_info->toposchema_id);
- result = PQexec(pg_info->conn, stmt);
- if (!result || PQresultStatus(result) != PGRES_TUPLES_OK) {
- PQclear(result);
- return -1;
- }
-
- has_topo = FALSE;
- if (atoi(PQgetvalue(result, 0, 0)) == 1) {
- /* table already exists */
- has_topo = TRUE;
- }
- PQclear(result);
- return has_topo;
- }
- #endif
|