build_pg.c 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472
  1. /*!
  2. \file lib/vector/Vlib/build_pg.c
  3. \brief Vector library - Building topology for PostGIS layers
  4. Higher level functions for reading/writing/manipulating vectors.
  5. Line offset is
  6. - centroids : FID
  7. - other types : index of the first record (which is FID) in offset array.
  8. (C) 2012 by the GRASS Development Team
  9. This program is free software under the GNU General Public License
  10. (>=v2). Read the file COPYING that comes with GRASS for details.
  11. \author Martin Landa <landa.martin gmail.com>
  12. */
  13. #include <grass/vector.h>
  14. #include <grass/glocale.h>
  15. #ifdef HAVE_POSTGRES
  16. #include "pg_local_proto.h"
  17. static int build_topo(struct Map_info *, int);
  18. static int build_topogeom_stmt(const struct Format_info_pg *, int, int, int, char *);
  19. static int save_map_bbox(const struct Format_info_pg *, const struct bound_box*);
  20. static int create_topo_grass(const struct Format_info_pg *);
  21. static int has_topo_grass(const struct Format_info_pg *);
  22. #endif
  23. /*!
  24. \brief Build topology for PostGIS layer
  25. Build levels:
  26. - GV_BUILD_NONE
  27. - GV_BUILD_BASE
  28. - GV_BUILD_ATTACH_ISLES
  29. - GV_BUILD_CENTROIDS
  30. - GV_BUILD_ALL
  31. \param Map pointer to Map_info structure
  32. \param build build level
  33. \return 1 on success
  34. \return 0 on error
  35. */
  36. int Vect_build_pg(struct Map_info *Map, int build)
  37. {
  38. #ifdef HAVE_POSTGRES
  39. struct Plus_head *plus;
  40. struct Format_info_pg *pg_info;
  41. plus = &(Map->plus);
  42. pg_info = &(Map->fInfo.pg);
  43. G_debug(1, "Vect_build_pg(): db='%s' table='%s', build=%d",
  44. pg_info->db_name, pg_info->table_name, build);
  45. if (build == plus->built)
  46. return 1; /* do nothing */
  47. /* TODO move this init to better place (Vect_open_ ?), because in
  48. theory build may be reused on level2 */
  49. if (build >= plus->built && build > GV_BUILD_BASE) {
  50. G_free((void *)pg_info->offset.array);
  51. G_zero(&(pg_info->offset), sizeof(struct Format_info_offset));
  52. }
  53. if (!pg_info->conn) {
  54. G_warning(_("No DB connection"));
  55. return 0;
  56. }
  57. if (!pg_info->fid_column && !pg_info->toposchema_name) {
  58. G_warning(_("Feature table <%s> has no primary key defined"),
  59. pg_info->table_name);
  60. G_warning(_("Random read is not supported for this layer. "
  61. "Unable to build topology."));
  62. return 0;
  63. }
  64. /* commit transaction block (update mode only) */
  65. if (pg_info->inTransaction && Vect__execute_pg(pg_info->conn, "COMMIT") == -1)
  66. return 0;
  67. pg_info->inTransaction = FALSE;
  68. if (build > GV_BUILD_NONE) {
  69. G_message(_("Using external data format '%s' (feature type '%s')"),
  70. Vect_get_finfo_format_info(Map),
  71. Vect_get_finfo_geometry_type(Map));
  72. if (!pg_info->toposchema_name)
  73. G_message(_("Building pseudo-topology over simple features..."));
  74. else
  75. G_message(_("Building topology from PostGIS topology schema <%s>..."),
  76. pg_info->toposchema_name);
  77. }
  78. if (!pg_info->toposchema_name) /* pseudo-topology for simple features */
  79. return Vect__build_sfa(Map, build);
  80. /* PostGIS Topology */
  81. return build_topo(Map, build);
  82. #else
  83. G_fatal_error(_("GRASS is not compiled with PostgreSQL support"));
  84. return 0;
  85. #endif
  86. }
  87. #ifdef HAVE_POSTGRES
  88. /*!
  89. \brief Build from PostGIS topology schema
  90. \todo Attach isles
  91. \param Map pointer to Map_info struct
  92. \param build build level
  93. \return 1 on success
  94. \return 0 on error
  95. */
  96. int build_topo(struct Map_info *Map, int build)
  97. {
  98. int line, type, s;
  99. int area, nareas, isle, nisles;
  100. int face[2];
  101. char stmt[DB_SQL_MAX];
  102. struct Plus_head *plus;
  103. struct Format_info_pg *pg_info;
  104. struct P_line *Line;
  105. struct P_area *Area;
  106. struct P_topo_b *topo_b;
  107. struct P_isle *Isle;
  108. plus = &(Map->plus);
  109. pg_info = &(Map->fInfo.pg);
  110. /* check if upgrade or downgrade */
  111. if (build < plus->built) {
  112. /* -> downgrade */
  113. Vect__build_downgrade(Map, build);
  114. return 1;
  115. }
  116. /* -> upgrade */
  117. if (build < GV_BUILD_BASE)
  118. return 1; /* nothing to print */
  119. /* update TopoGeometry based on GRASS-like topology */
  120. Vect_build_nat(Map, build);
  121. /* store map boundig box in DB */
  122. save_map_bbox(pg_info, &(plus->box));
  123. /* begin transaction */
  124. if (Vect__execute_pg(pg_info->conn, "BEGIN"))
  125. return 0;
  126. /* update faces from GRASS Topology */
  127. if (build >= GV_BUILD_AREAS) {
  128. /* reset centroids to '0' (universal face) */
  129. sprintf(stmt, "UPDATE \"%s\".node SET containing_face = 0 WHERE "
  130. "containing_face IS NOT NULL", pg_info->toposchema_name);
  131. G_debug(2, "SQL: %s", stmt);
  132. if(Vect__execute_pg(pg_info->conn, stmt) == -1) {
  133. Vect__execute_pg(pg_info->conn, "ROLLBACK");
  134. return 0;
  135. }
  136. /* reset left|right edges */
  137. sprintf(stmt, "UPDATE \"%s\".edge_data SET left_face = 0, right_face = 0",
  138. pg_info->toposchema_name);
  139. G_debug(2, "SQL: %s", stmt);
  140. if(Vect__execute_pg(pg_info->conn, stmt) == -1) {
  141. Vect__execute_pg(pg_info->conn, "ROLLBACK");
  142. return 0;
  143. }
  144. /* delete faces */
  145. sprintf(stmt, "DELETE FROM \"%s\".face WHERE "
  146. "face_id != 0", pg_info->toposchema_name);
  147. G_debug(2, "SQL: %s", stmt);
  148. if(Vect__execute_pg(pg_info->conn, stmt) == -1) {
  149. Vect__execute_pg(pg_info->conn, "ROLLBACK");
  150. return 0;
  151. }
  152. /* insert faces & update nodes (containing_face) based on
  153. * GRASS topology */
  154. nareas = Vect_get_num_areas(Map);
  155. for (area = 1; area <= nareas; area++) {
  156. if (0 == Vect__insert_face_pg(Map, area)) {
  157. Vect__execute_pg(pg_info->conn, "ROLLBACK");
  158. return 0;
  159. }
  160. if (build < GV_BUILD_CENTROIDS)
  161. continue;
  162. /* update centroids */
  163. Area = plus->Area[area];
  164. if (Area->centroid < 1) {
  165. G_debug(3, "Area %d without centroid, skipped", area);
  166. continue;
  167. }
  168. Line = plus->Line[Area->centroid];
  169. sprintf(stmt, "UPDATE \"%s\".node SET "
  170. "containing_face = %d WHERE node_id = %lu",
  171. pg_info->toposchema_name, area, Line->offset);
  172. G_debug(2, "SQL: %s", stmt);
  173. if(Vect__execute_pg(pg_info->conn, stmt) == -1) {
  174. Vect__execute_pg(pg_info->conn, "ROLLBACK");
  175. return 0;
  176. }
  177. }
  178. /* update edges (left and right face) */
  179. for (line = 1; line <= plus->n_lines; line++) {
  180. type = Vect_read_line(Map, NULL, NULL, line);
  181. if (type != GV_BOUNDARY)
  182. continue;
  183. Line = Map->plus.Line[line];
  184. if (!Line) {
  185. G_warning(_("Inconsistency in topology detected. "
  186. "Dead line found."));
  187. return 0;
  188. }
  189. topo_b = (struct P_topo_b *) Line->topo;
  190. for (s = 0; s < 2; s++) { /* for both sides */
  191. face[s] = s == 0 ? topo_b->left : topo_b->right;
  192. if (face[s] < 0) {
  193. /* isle */
  194. Isle = plus->Isle[abs(face[s])];
  195. face[s] = Isle->area;
  196. }
  197. }
  198. G_debug(3, "update edge %d: left_face = %d, right_face = %d",
  199. (int)Line->offset, face[0], face[1]);
  200. sprintf(stmt, "UPDATE \"%s\".edge_data SET "
  201. "left_face = %d, right_face = %d "
  202. "WHERE edge_id = %d", pg_info->toposchema_name,
  203. face[0], face[1], (int) Line->offset);
  204. if (Vect__execute_pg(pg_info->conn, stmt) == -1) {
  205. Vect__execute_pg(pg_info->conn, "ROLLBACK");
  206. return 0;
  207. }
  208. }
  209. } /* build >= GV_BUILD_AREAS */
  210. if (build >= GV_BUILD_ATTACH_ISLES) {
  211. /* insert isles as faces with negative face_id */
  212. nisles = Vect_get_num_islands(Map);
  213. for(isle = 1; isle <= nisles; isle++) {
  214. Isle = plus->Isle[isle];
  215. Vect__insert_face_pg(Map, -isle);
  216. }
  217. }
  218. if (pg_info->feature_type == SF_POLYGON) {
  219. int centroid;
  220. G_message(_("Updating TopoGeometry data..."));
  221. for (area = 1; area <= plus->n_areas; area++) {
  222. centroid = Vect_get_area_centroid(Map, area);
  223. if (centroid < 1)
  224. continue;
  225. if (build_topogeom_stmt(pg_info, GV_CENTROID, area, centroid, stmt) &&
  226. Vect__execute_pg(pg_info->conn, stmt) == -1) {
  227. Vect__execute_pg(pg_info->conn, "ROLLBACK");
  228. return 0;
  229. }
  230. }
  231. }
  232. if (Vect__execute_pg(pg_info->conn, "COMMIT") == -1)
  233. return 0;
  234. return 1;
  235. }
  236. /*!
  237. \brief Build UPDATE statement for topo geometry element stored in
  238. feature table
  239. \param pg_info so pointer to Format_info_pg
  240. \param type feature type (GV_POINT, ...)
  241. \param topo_id topology element id
  242. \param fid feature id
  243. \param[out] stmt string buffer
  244. \return 1 on success
  245. \return 0 on failure
  246. */
  247. int build_topogeom_stmt(const struct Format_info_pg *pg_info,
  248. int type, int topo_id, int fid, char *stmt)
  249. {
  250. int topogeom_type;
  251. switch(type) {
  252. case GV_POINT:
  253. topogeom_type = 1;
  254. break;
  255. case GV_LINE:
  256. case GV_BOUNDARY:
  257. topogeom_type = 2;
  258. break;
  259. case GV_CENTROID:
  260. topogeom_type = 3;
  261. break;
  262. default:
  263. G_warning(_("Unsupported topo geometry type %d"), type);
  264. return 0;
  265. }
  266. sprintf(stmt, "UPDATE \"%s\".\"%s\" SET %s = "
  267. "'(%d, 1, %d, %d)'::topology.TopoGeometry "
  268. "WHERE (%s).id = %d",
  269. pg_info->schema_name, pg_info->table_name,
  270. pg_info->topogeom_column, pg_info->toposchema_id,
  271. topo_id, topogeom_type, pg_info->topogeom_column, fid);
  272. return 1;
  273. }
  274. /*!
  275. \brief Store map bounding box in DB head table
  276. \param pg_info pointer to Format_info_pg struct
  277. \param box pointer to bounding box
  278. \return 1 on success
  279. \return 0 on failure
  280. */
  281. int save_map_bbox(const struct Format_info_pg *pg_info, const struct bound_box *box)
  282. {
  283. char stmt[DB_SQL_MAX];
  284. /* create if not exists */
  285. if (create_topo_grass(pg_info) == -1) {
  286. G_warning(_("Unable to create <%s.%s>"), TOPO_SCHEMA, TOPO_TABLE);
  287. return 0;
  288. }
  289. /* update bbox */
  290. if (has_topo_grass(pg_info)) {
  291. /* -> update */
  292. sprintf(stmt, "UPDATE \"%s\".\"%s\" SET %s = "
  293. "'BOX3D(%.12f %.12f %.12f, %.12f %.12f %.12f)'::box3d WHERE %s = %d",
  294. TOPO_SCHEMA, TOPO_TABLE, TOPO_BBOX,
  295. box->W, box->S, box->B, box->E, box->N, box->T,
  296. TOPO_ID, pg_info->toposchema_id);
  297. }
  298. else {
  299. /* -> insert */
  300. sprintf(stmt, "INSERT INTO \"%s\".\"%s\" (%s, %s) "
  301. "VALUES(%d, 'BOX3D(%.12f %.12f %.12f, %.12f %.12f %.12f)'::box3d)",
  302. TOPO_SCHEMA, TOPO_TABLE, TOPO_ID, TOPO_BBOX, pg_info->toposchema_id,
  303. box->W, box->S, box->B, box->E, box->N, box->T);
  304. }
  305. if (Vect__execute_pg(pg_info->conn, stmt) == -1) {
  306. return -1;
  307. }
  308. return 1;
  309. }
  310. /*!
  311. \brief Creates 'topology.grass' table if not exists
  312. \return 0 table already exists
  313. \return 1 table successfully added
  314. \return -1 on error
  315. */
  316. int create_topo_grass(const struct Format_info_pg *pg_info)
  317. {
  318. char stmt[DB_SQL_MAX];
  319. PGresult *result;
  320. /* check if table exists */
  321. sprintf(stmt, "SELECT COUNT(*) FROM information_schema.tables "
  322. "WHERE table_schema = '%s' AND table_name = '%s'",
  323. TOPO_SCHEMA, TOPO_TABLE);
  324. result = PQexec(pg_info->conn, stmt);
  325. if (!result || PQresultStatus(result) != PGRES_TUPLES_OK) {
  326. PQclear(result);
  327. return -1;
  328. }
  329. if (atoi(PQgetvalue(result, 0, 0)) == 1) {
  330. /* table already exists */
  331. PQclear(result);
  332. return 1;
  333. }
  334. PQclear(result);
  335. G_debug(1, "<%s.%s> created", TOPO_SCHEMA, TOPO_TABLE);
  336. /* create table */
  337. sprintf(stmt, "CREATE TABLE \"%s\".\"%s\" (%s INTEGER, %s box3d)",
  338. TOPO_SCHEMA, TOPO_TABLE, TOPO_ID, TOPO_BBOX);
  339. if (Vect__execute_pg(pg_info->conn, stmt) == -1) {
  340. return -1;
  341. }
  342. /* add primary key */
  343. sprintf(stmt, "ALTER TABLE \"%s\".\"%s\" ADD PRIMARY KEY (%s)",
  344. TOPO_SCHEMA, TOPO_TABLE, TOPO_ID);
  345. if (Vect__execute_pg(pg_info->conn, stmt) == -1) {
  346. return -1;
  347. }
  348. /* add constraint */
  349. sprintf(stmt, "ALTER TABLE \"%s\".\"%s\" ADD CONSTRAINT \"%s_%s_fkey\" "
  350. "FOREIGN KEY (%s) REFERENCES topology.topology(id) ON DELETE CASCADE",
  351. TOPO_SCHEMA, TOPO_TABLE, TOPO_TABLE, TOPO_ID, TOPO_ID);
  352. if (Vect__execute_pg(pg_info->conn, stmt) == -1) {
  353. return -1;
  354. }
  355. return 1;
  356. }
  357. /*!
  358. \brief Check if 'topology_id' exists in 'topology.grass'
  359. \param pg_info pointer to Format_info_pg struct
  360. \return TRUE if exists
  361. \return FALSE otherwise
  362. \return -1 on error
  363. */
  364. int has_topo_grass(const struct Format_info_pg *pg_info)
  365. {
  366. int has_topo;
  367. char stmt[DB_SQL_MAX];
  368. PGresult *result;
  369. sprintf(stmt, "SELECT COUNT(*) FROM \"%s\".\"%s\" "
  370. "WHERE %s = %d",
  371. TOPO_SCHEMA, TOPO_TABLE, TOPO_ID, pg_info->toposchema_id);
  372. result = PQexec(pg_info->conn, stmt);
  373. if (!result || PQresultStatus(result) != PGRES_TUPLES_OK) {
  374. PQclear(result);
  375. return -1;
  376. }
  377. has_topo = FALSE;
  378. if (atoi(PQgetvalue(result, 0, 0)) == 1) {
  379. /* table already exists */
  380. has_topo = TRUE;
  381. }
  382. PQclear(result);
  383. return has_topo;
  384. }
  385. #endif