build_pg.c 13 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, 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, topo_id, 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_c *topo_c;
  107. struct P_topo_b *topo_b;
  108. struct P_isle *Isle;
  109. plus = &(Map->plus);
  110. pg_info = &(Map->fInfo.pg);
  111. /* check if upgrade or downgrade */
  112. if (build < plus->built) {
  113. /* -> downgrade */
  114. Vect__build_downgrade(Map, build);
  115. return 1;
  116. }
  117. /* -> upgrade */
  118. if (build < GV_BUILD_BASE)
  119. return 1; /* nothing to print */
  120. /* update TopoGeometry based on GRASS-like topology */
  121. Vect_build_nat(Map, build);
  122. /* store map boundig box in DB */
  123. save_map_bbox(pg_info, &(plus->box));
  124. /* begin transaction */
  125. if (Vect__execute_pg(pg_info->conn, "BEGIN"))
  126. return 0;
  127. /* update faces from GRASS Topology */
  128. if (build >= GV_BUILD_AREAS) {
  129. /* reset centroids to '0' (universal face) */
  130. sprintf(stmt, "UPDATE \"%s\".node SET containing_face = 0 WHERE "
  131. "containing_face IS NOT NULL", pg_info->toposchema_name);
  132. G_debug(2, "SQL: %s", stmt);
  133. if(Vect__execute_pg(pg_info->conn, stmt) == -1) {
  134. Vect__execute_pg(pg_info->conn, "ROLLBACK");
  135. return 0;
  136. }
  137. /* reset left|right edges */
  138. sprintf(stmt, "UPDATE \"%s\".edge_data SET left_face = 0, right_face = 0",
  139. pg_info->toposchema_name);
  140. G_debug(2, "SQL: %s", stmt);
  141. if(Vect__execute_pg(pg_info->conn, stmt) == -1) {
  142. Vect__execute_pg(pg_info->conn, "ROLLBACK");
  143. return 0;
  144. }
  145. /* delete faces */
  146. sprintf(stmt, "DELETE FROM \"%s\".face WHERE "
  147. "face_id != 0", pg_info->toposchema_name);
  148. G_debug(2, "SQL: %s", stmt);
  149. if(Vect__execute_pg(pg_info->conn, stmt) == -1) {
  150. Vect__execute_pg(pg_info->conn, "ROLLBACK");
  151. return 0;
  152. }
  153. /* insert face from 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_warning(_("Area %d without centroid"), 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. }
  179. if (build >= GV_BUILD_ATTACH_ISLES) {
  180. /* insert isles as faces with negative face_id */
  181. nisles = Vect_get_num_islands(Map);
  182. for(isle = 1; isle <= nisles; isle++) {
  183. Isle = plus->Isle[isle];
  184. Vect__insert_face_pg(Map, -isle);
  185. }
  186. }
  187. G_message(_("Updating TopoGeometry data..."));
  188. for (line = 1; line <= plus->n_lines; line++) {
  189. type = Vect_read_line(Map, NULL, NULL, line);
  190. G_percent(line, plus->n_lines, 3);
  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. if (build >= GV_BUILD_AREAS && type == GV_BOUNDARY) {
  198. topo_b = (struct P_topo_b *) Line->topo;
  199. for (s = 0; s < 2; s++) { /* for both sides */
  200. face[s] = s == 0 ? topo_b->left : topo_b->right;
  201. if (face[s] < 0) {
  202. Isle = plus->Isle[abs(face[s])];
  203. if (Isle->area > 0) {
  204. face[s] = Isle->area;
  205. }
  206. else { /* -> universal face */
  207. face[s] = 0;
  208. }
  209. }
  210. }
  211. sprintf(stmt, "UPDATE \"%s\".edge_data SET "
  212. "left_face = %d, right_face = %d "
  213. "WHERE edge_id = %lu", pg_info->toposchema_name,
  214. face[0], face[1], Line->offset);
  215. G_debug(2, "SQL: %s", stmt);
  216. if(Vect__execute_pg(pg_info->conn, stmt) == -1) {
  217. Vect__execute_pg(pg_info->conn, "ROLLBACK");
  218. return 0;
  219. }
  220. continue;
  221. }
  222. /* determine id */
  223. if (type == GV_CENTROID) {
  224. topo_c = (struct P_topo_c *) Line->topo;
  225. topo_id = topo_c->area;
  226. }
  227. else {
  228. topo_id = line; /* GV_POINT | GV_LINE */
  229. }
  230. if (build_topogeom_stmt(pg_info, topo_id, type, stmt) &&
  231. Vect__execute_pg(pg_info->conn, stmt) == -1) {
  232. Vect__execute_pg(pg_info->conn, "ROLLBACK");
  233. return 0;
  234. }
  235. }
  236. if (Vect__execute_pg(pg_info->conn, "COMMIT") == -1)
  237. return 0;
  238. return 1;
  239. }
  240. /*!
  241. \brief Build UPDATE statement for topo geometry element stored in
  242. feature table
  243. \param pg_info so pointer to Format_info_pg
  244. \param type feature type (GV_POINT, ...)
  245. \param id topology element id
  246. \param[out] stmt string buffer
  247. \return 1 on success
  248. \return 0 on failure
  249. */
  250. int build_topogeom_stmt(const struct Format_info_pg *pg_info,
  251. int id, int type, char *stmt)
  252. {
  253. int topogeom_type;
  254. switch(type) {
  255. case GV_POINT:
  256. topogeom_type = 1;
  257. break;
  258. case GV_LINE:
  259. case GV_BOUNDARY:
  260. topogeom_type = 2;
  261. break;
  262. case GV_CENTROID:
  263. topogeom_type = 3;
  264. break;
  265. default:
  266. G_warning(_("Unsupported topo geometry type %d"), type);
  267. return 0;
  268. }
  269. sprintf(stmt, "UPDATE \"%s\".\"%s\" SET %s = "
  270. "'(%d, 1, %d, %d)'::topology.TopoGeometry "
  271. "WHERE %s = %d",
  272. pg_info->schema_name, pg_info->table_name,
  273. pg_info->topogeom_column, pg_info->toposchema_id,
  274. id, topogeom_type, pg_info->fid_column, id);
  275. return 1;
  276. }
  277. /*!
  278. \brief Store map bounding box in DB head table
  279. \param pg_info pointer to Format_info_pg struct
  280. \param box pointer to bounding box
  281. \return 1 on success
  282. \return 0 on failure
  283. */
  284. int save_map_bbox(const struct Format_info_pg *pg_info, const struct bound_box *box)
  285. {
  286. char stmt[DB_SQL_MAX];
  287. /* create if not exists */
  288. if (create_topo_grass(pg_info) == -1) {
  289. G_warning(_("Unable to create <%s.%s>"), TOPO_SCHEMA, TOPO_TABLE);
  290. return 0;
  291. }
  292. /* update bbox */
  293. if (has_topo_grass(pg_info)) {
  294. /* -> update */
  295. sprintf(stmt, "UPDATE \"%s\".\"%s\" SET %s = "
  296. "'BOX3D(%.12f %.12f %.12f, %.12f %.12f %.12f)'::box3d WHERE %s = %d",
  297. TOPO_SCHEMA, TOPO_TABLE, TOPO_BBOX,
  298. box->W, box->S, box->B, box->E, box->N, box->T,
  299. TOPO_ID, pg_info->toposchema_id);
  300. }
  301. else {
  302. /* -> insert */
  303. sprintf(stmt, "INSERT INTO \"%s\".\"%s\" (%s, %s) "
  304. "VALUES(%d, 'BOX3D(%.12f %.12f %.12f, %.12f %.12f %.12f)'::box3d)",
  305. TOPO_SCHEMA, TOPO_TABLE, TOPO_ID, TOPO_BBOX, pg_info->toposchema_id,
  306. box->W, box->S, box->B, box->E, box->N, box->T);
  307. }
  308. if (Vect__execute_pg(pg_info->conn, stmt) == -1) {
  309. return -1;
  310. }
  311. return 1;
  312. }
  313. /*!
  314. \brief Creates 'topology.grass' table if not exists
  315. \return 0 table already exists
  316. \return 1 table successfully added
  317. \return -1 on error
  318. */
  319. int create_topo_grass(const struct Format_info_pg *pg_info)
  320. {
  321. char stmt[DB_SQL_MAX];
  322. PGresult *result;
  323. /* check if table exists */
  324. sprintf(stmt, "SELECT COUNT(*) FROM information_schema.tables "
  325. "WHERE table_schema = '%s' AND table_name = '%s'",
  326. TOPO_SCHEMA, TOPO_TABLE);
  327. result = PQexec(pg_info->conn, stmt);
  328. if (!result || PQresultStatus(result) != PGRES_TUPLES_OK) {
  329. PQclear(result);
  330. return -1;
  331. }
  332. if (atoi(PQgetvalue(result, 0, 0)) == 1) {
  333. /* table already exists */
  334. PQclear(result);
  335. return 1;
  336. }
  337. PQclear(result);
  338. G_debug(1, "<%s.%s> created", TOPO_SCHEMA, TOPO_TABLE);
  339. /* create table */
  340. sprintf(stmt, "CREATE TABLE \"%s\".\"%s\" (%s INTEGER, %s box3d)",
  341. TOPO_SCHEMA, TOPO_TABLE, TOPO_ID, TOPO_BBOX);
  342. if (Vect__execute_pg(pg_info->conn, stmt) == -1) {
  343. return -1;
  344. }
  345. /* add primary key */
  346. sprintf(stmt, "ALTER TABLE \"%s\".\"%s\" ADD PRIMARY KEY (%s)",
  347. TOPO_SCHEMA, TOPO_TABLE, TOPO_ID);
  348. if (Vect__execute_pg(pg_info->conn, stmt) == -1) {
  349. return -1;
  350. }
  351. /* add constraint */
  352. sprintf(stmt, "ALTER TABLE \"%s\".\"%s\" ADD CONSTRAINT \"%s_%s_fkey\" "
  353. "FOREIGN KEY (%s) REFERENCES topology.topology(id) ON DELETE CASCADE",
  354. TOPO_SCHEMA, TOPO_TABLE, TOPO_TABLE, TOPO_ID, TOPO_ID);
  355. if (Vect__execute_pg(pg_info->conn, stmt) == -1) {
  356. return -1;
  357. }
  358. return 1;
  359. }
  360. /*!
  361. \brief Check if 'topology_id' exists in 'topology.grass'
  362. \param pg_info pointer to Format_info_pg struct
  363. \return TRUE if exists
  364. \return FALSE otherwise
  365. \return -1 on error
  366. */
  367. int has_topo_grass(const struct Format_info_pg *pg_info)
  368. {
  369. int has_topo;
  370. char stmt[DB_SQL_MAX];
  371. PGresult *result;
  372. sprintf(stmt, "SELECT COUNT(*) FROM \"%s\".\"%s\" "
  373. "WHERE %s = %d",
  374. TOPO_SCHEMA, TOPO_TABLE, TOPO_ID, pg_info->toposchema_id);
  375. result = PQexec(pg_info->conn, stmt);
  376. if (!result || PQresultStatus(result) != PGRES_TUPLES_OK) {
  377. PQclear(result);
  378. return -1;
  379. }
  380. has_topo = FALSE;
  381. if (atoi(PQgetvalue(result, 0, 0)) == 1) {
  382. /* table already exists */
  383. has_topo = TRUE;
  384. }
  385. PQclear(result);
  386. return has_topo;
  387. }
  388. #endif