build_pg.c 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480
  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 (simple features only) is
  6. - centroids : FID
  7. - other types : index of the first record (which is FID) in offset array.
  8. (C) 2012-2013 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. /* commit transaction block (update mode only) */
  46. if (pg_info->inTransaction && Vect__execute_pg(pg_info->conn, "COMMIT") == -1)
  47. return 0;
  48. pg_info->inTransaction = FALSE;
  49. if (build == plus->built)
  50. return 1; /* do nothing */
  51. /* TODO move this init to better place (Vect_open_ ?), because in
  52. theory build may be reused on level2 */
  53. if (build >= plus->built && build > GV_BUILD_BASE) {
  54. G_free((void *)pg_info->offset.array);
  55. G_zero(&(pg_info->offset), sizeof(struct Format_info_offset));
  56. }
  57. if (!pg_info->conn) {
  58. G_warning(_("No DB connection"));
  59. return 0;
  60. }
  61. if (!pg_info->fid_column && !pg_info->toposchema_name) {
  62. G_warning(_("Feature table <%s> has no primary key defined"),
  63. pg_info->table_name);
  64. G_warning(_("Random read is not supported for this layer. "
  65. "Unable to build topology."));
  66. return 0;
  67. }
  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. /* do clean up (1-3)
  129. insert new faces (4)
  130. update edges (5)
  131. */
  132. /* 1) reset centroids to '0' (universal face) */
  133. sprintf(stmt, "UPDATE \"%s\".node SET containing_face = 0 WHERE "
  134. "containing_face IS NOT NULL", pg_info->toposchema_name);
  135. G_debug(2, "SQL: %s", stmt);
  136. if(Vect__execute_pg(pg_info->conn, stmt) == -1) {
  137. Vect__execute_pg(pg_info->conn, "ROLLBACK");
  138. return 0;
  139. }
  140. /* 2) reset left|right edges */
  141. sprintf(stmt, "UPDATE \"%s\".edge_data SET left_face = 0, right_face = 0",
  142. pg_info->toposchema_name);
  143. G_debug(2, "SQL: %s", stmt);
  144. if(Vect__execute_pg(pg_info->conn, stmt) == -1) {
  145. Vect__execute_pg(pg_info->conn, "ROLLBACK");
  146. return 0;
  147. }
  148. /* 3) delete faces */
  149. sprintf(stmt, "DELETE FROM \"%s\".face WHERE "
  150. "face_id != 0", pg_info->toposchema_name);
  151. G_debug(2, "SQL: %s", stmt);
  152. if(Vect__execute_pg(pg_info->conn, stmt) == -1) {
  153. Vect__execute_pg(pg_info->conn, "ROLLBACK");
  154. return 0;
  155. }
  156. /* 4) insert faces & update nodes (containing_face) based on
  157. * GRASS topology */
  158. G_message(_("Updating faces..."));
  159. nareas = Vect_get_num_areas(Map);
  160. for (area = 1; area <= nareas; area++) {
  161. G_percent(area, nareas, 5);
  162. if (0 == Vect__insert_face_pg(Map, area)) {
  163. Vect__execute_pg(pg_info->conn, "ROLLBACK");
  164. return 0;
  165. }
  166. if (build < GV_BUILD_CENTROIDS)
  167. continue;
  168. /* update centroids (node -> containing_face) */
  169. Area = plus->Area[area];
  170. if (Area->centroid < 1) {
  171. G_debug(3, "Area %d without centroid, skipped", area);
  172. continue;
  173. }
  174. Line = plus->Line[Area->centroid];
  175. sprintf(stmt, "UPDATE \"%s\".node SET "
  176. "containing_face = %d WHERE node_id = %d",
  177. pg_info->toposchema_name, area, (int)Line->offset);
  178. G_debug(2, "SQL: %s", stmt);
  179. if(Vect__execute_pg(pg_info->conn, stmt) == -1) {
  180. Vect__execute_pg(pg_info->conn, "ROLLBACK");
  181. return 0;
  182. }
  183. }
  184. /* 5) update edges (left and right face) */
  185. G_message(_("Updating edges..."));
  186. for (line = 1; line <= plus->n_lines; line++) {
  187. G_percent(line, plus->n_lines, 5);
  188. type = Vect_read_line(Map, NULL, NULL, line);
  189. if (type != GV_BOUNDARY)
  190. continue;
  191. Line = Map->plus.Line[line];
  192. if (!Line) {
  193. G_warning(_("Inconsistency in topology detected. "
  194. "Dead line found."));
  195. return 0;
  196. }
  197. topo_b = (struct P_topo_b *) Line->topo;
  198. for (s = 0; s < 2; s++) { /* for both sides */
  199. face[s] = s == 0 ? topo_b->left : topo_b->right;
  200. if (face[s] < 0) {
  201. /* isle */
  202. Isle = plus->Isle[abs(face[s])];
  203. face[s] = Isle->area;
  204. }
  205. }
  206. G_debug(3, "update edge %d: left_face = %d, right_face = %d",
  207. (int)Line->offset, face[0], face[1]);
  208. sprintf(stmt, "UPDATE \"%s\".edge_data SET "
  209. "left_face = %d, right_face = %d "
  210. "WHERE edge_id = %d", pg_info->toposchema_name,
  211. face[0], face[1], (int) Line->offset);
  212. if (Vect__execute_pg(pg_info->conn, stmt) == -1) {
  213. Vect__execute_pg(pg_info->conn, "ROLLBACK");
  214. return 0;
  215. }
  216. }
  217. } /* build >= GV_BUILD_AREAS */
  218. if (build >= GV_BUILD_ATTACH_ISLES) {
  219. /* insert isles as faces with negative face_id */
  220. nisles = Vect_get_num_islands(Map);
  221. for(isle = 1; isle <= nisles; isle++) {
  222. Isle = plus->Isle[isle];
  223. Vect__insert_face_pg(Map, -isle);
  224. }
  225. } /* build >= GV_BUILD_ISLES */
  226. if (pg_info->feature_type == SF_POLYGON) {
  227. int centroid;
  228. G_message(_("Updating TopoGeometry data..."));
  229. for (area = 1; area <= plus->n_areas; area++) {
  230. G_percent(area, plus->n_areas, 5);
  231. centroid = Vect_get_area_centroid(Map, area);
  232. if (centroid < 1)
  233. continue;
  234. /* update topogeometry object: centroid -> face */
  235. if (build_topogeom_stmt(pg_info, GV_CENTROID, area, centroid, stmt) &&
  236. Vect__execute_pg(pg_info->conn, stmt) == -1) {
  237. Vect__execute_pg(pg_info->conn, "ROLLBACK");
  238. return 0;
  239. }
  240. }
  241. }
  242. if (Vect__execute_pg(pg_info->conn, "COMMIT") == -1)
  243. return 0;
  244. return 1;
  245. }
  246. /*!
  247. \brief Build UPDATE statement for topo geometry element stored in
  248. feature table
  249. \param pg_info so pointer to Format_info_pg
  250. \param type feature type (GV_POINT, ...)
  251. \param topo_id topology element id
  252. \param fid feature id
  253. \param[out] stmt string buffer
  254. \return 1 on success
  255. \return 0 on failure
  256. */
  257. int build_topogeom_stmt(const struct Format_info_pg *pg_info,
  258. int type, int topo_id, int fid, char *stmt)
  259. {
  260. int topogeom_type;
  261. switch(type) {
  262. case GV_POINT:
  263. topogeom_type = 1;
  264. break;
  265. case GV_LINE:
  266. case GV_BOUNDARY:
  267. topogeom_type = 2;
  268. break;
  269. case GV_CENTROID:
  270. topogeom_type = 3;
  271. break;
  272. default:
  273. G_warning(_("Unsupported topo geometry type %d"), type);
  274. return 0;
  275. }
  276. sprintf(stmt, "UPDATE \"%s\".\"%s\" SET %s = "
  277. "'(%d, 1, %d, %d)'::topology.TopoGeometry "
  278. "WHERE (%s).id = %d",
  279. pg_info->schema_name, pg_info->table_name,
  280. pg_info->topogeom_column, pg_info->toposchema_id,
  281. topo_id, topogeom_type, pg_info->topogeom_column, fid);
  282. return 1;
  283. }
  284. /*!
  285. \brief Store map bounding box in DB head table
  286. \param pg_info pointer to Format_info_pg struct
  287. \param box pointer to bounding box
  288. \return 1 on success
  289. \return 0 on failure
  290. */
  291. int save_map_bbox(const struct Format_info_pg *pg_info, const struct bound_box *box)
  292. {
  293. char stmt[DB_SQL_MAX];
  294. /* create if not exists */
  295. if (create_topo_grass(pg_info) == -1) {
  296. G_warning(_("Unable to create <%s.%s>"), TOPO_SCHEMA, TOPO_TABLE);
  297. return 0;
  298. }
  299. /* update bbox */
  300. if (has_topo_grass(pg_info)) {
  301. /* -> update */
  302. sprintf(stmt, "UPDATE \"%s\".\"%s\" SET %s = "
  303. "'BOX3D(%.12f %.12f %.12f, %.12f %.12f %.12f)'::box3d WHERE %s = %d",
  304. TOPO_SCHEMA, TOPO_TABLE, TOPO_BBOX,
  305. box->W, box->S, box->B, box->E, box->N, box->T,
  306. TOPO_ID, pg_info->toposchema_id);
  307. }
  308. else {
  309. /* -> insert */
  310. sprintf(stmt, "INSERT INTO \"%s\".\"%s\" (%s, %s) "
  311. "VALUES(%d, 'BOX3D(%.12f %.12f %.12f, %.12f %.12f %.12f)'::box3d)",
  312. TOPO_SCHEMA, TOPO_TABLE, TOPO_ID, TOPO_BBOX, pg_info->toposchema_id,
  313. box->W, box->S, box->B, box->E, box->N, box->T);
  314. }
  315. if (Vect__execute_pg(pg_info->conn, stmt) == -1) {
  316. return -1;
  317. }
  318. return 1;
  319. }
  320. /*!
  321. \brief Creates 'topology.grass' table if not exists
  322. \return 0 table already exists
  323. \return 1 table successfully added
  324. \return -1 on error
  325. */
  326. int create_topo_grass(const struct Format_info_pg *pg_info)
  327. {
  328. char stmt[DB_SQL_MAX];
  329. PGresult *result;
  330. /* check if table exists */
  331. sprintf(stmt, "SELECT COUNT(*) FROM information_schema.tables "
  332. "WHERE table_schema = '%s' AND table_name = '%s'",
  333. TOPO_SCHEMA, TOPO_TABLE);
  334. result = PQexec(pg_info->conn, stmt);
  335. if (!result || PQresultStatus(result) != PGRES_TUPLES_OK) {
  336. PQclear(result);
  337. return -1;
  338. }
  339. if (atoi(PQgetvalue(result, 0, 0)) == 1) {
  340. /* table already exists */
  341. PQclear(result);
  342. return 1;
  343. }
  344. PQclear(result);
  345. G_debug(1, "<%s.%s> created", TOPO_SCHEMA, TOPO_TABLE);
  346. /* create table */
  347. sprintf(stmt, "CREATE TABLE \"%s\".\"%s\" (%s INTEGER, %s box3d)",
  348. TOPO_SCHEMA, TOPO_TABLE, TOPO_ID, TOPO_BBOX);
  349. if (Vect__execute_pg(pg_info->conn, stmt) == -1) {
  350. return -1;
  351. }
  352. /* add primary key */
  353. sprintf(stmt, "ALTER TABLE \"%s\".\"%s\" ADD PRIMARY KEY (%s)",
  354. TOPO_SCHEMA, TOPO_TABLE, TOPO_ID);
  355. if (Vect__execute_pg(pg_info->conn, stmt) == -1) {
  356. return -1;
  357. }
  358. /* add constraint */
  359. sprintf(stmt, "ALTER TABLE \"%s\".\"%s\" ADD CONSTRAINT \"%s_%s_fkey\" "
  360. "FOREIGN KEY (%s) REFERENCES topology.topology(id) ON DELETE CASCADE",
  361. TOPO_SCHEMA, TOPO_TABLE, TOPO_TABLE, TOPO_ID, TOPO_ID);
  362. if (Vect__execute_pg(pg_info->conn, stmt) == -1) {
  363. return -1;
  364. }
  365. return 1;
  366. }
  367. /*!
  368. \brief Check if 'topology_id' exists in 'topology.grass'
  369. \param pg_info pointer to Format_info_pg struct
  370. \return TRUE if exists
  371. \return FALSE otherwise
  372. \return -1 on error
  373. */
  374. int has_topo_grass(const struct Format_info_pg *pg_info)
  375. {
  376. int has_topo;
  377. char stmt[DB_SQL_MAX];
  378. PGresult *result;
  379. sprintf(stmt, "SELECT COUNT(*) FROM \"%s\".\"%s\" "
  380. "WHERE %s = %d",
  381. TOPO_SCHEMA, TOPO_TABLE, TOPO_ID, pg_info->toposchema_id);
  382. result = PQexec(pg_info->conn, stmt);
  383. if (!result || PQresultStatus(result) != PGRES_TUPLES_OK) {
  384. PQclear(result);
  385. return -1;
  386. }
  387. has_topo = FALSE;
  388. if (atoi(PQgetvalue(result, 0, 0)) == 1) {
  389. /* table already exists */
  390. has_topo = TRUE;
  391. }
  392. PQclear(result);
  393. return has_topo;
  394. }
  395. #endif