build_pg.c 30 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029
  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. #include "local_proto.h"
  16. #ifdef HAVE_POSTGRES
  17. #include "pg_local_proto.h"
  18. static int build_topo(struct Map_info *, int);
  19. static int build_topogeom_stmt(const struct Format_info_pg *, int, int, int, char *);
  20. static int save_map_bbox(const struct Format_info_pg *, const struct bound_box*);
  21. static int create_topo_grass(const struct Format_info_pg *);
  22. static int has_topo_grass(const struct Format_info_pg *);
  23. static int write_nodes(const struct Plus_head *, const struct Format_info_pg *);
  24. static int write_lines(const struct Plus_head *, const struct Format_info_pg *);
  25. static int write_areas(const struct Plus_head *, const struct Format_info_pg *);
  26. static int write_isles(const struct Plus_head *, const struct Format_info_pg *);
  27. static void build_stmt_id(const void *, int, int, const struct Plus_head *, char **, size_t *);
  28. static int create_simple_feature_from_topo(struct Map_info *);
  29. #endif
  30. /*!
  31. \brief Build topology for PostGIS layer
  32. Build levels:
  33. - GV_BUILD_NONE
  34. - GV_BUILD_BASE
  35. - GV_BUILD_ATTACH_ISLES
  36. - GV_BUILD_CENTROIDS
  37. - GV_BUILD_ALL
  38. \param Map pointer to Map_info structure
  39. \param build build level
  40. \return 1 on success
  41. \return 0 on error
  42. */
  43. int Vect_build_pg(struct Map_info *Map, int build)
  44. {
  45. #ifdef HAVE_POSTGRES
  46. struct Plus_head *plus;
  47. struct Format_info_pg *pg_info;
  48. plus = &(Map->plus);
  49. pg_info = &(Map->fInfo.pg);
  50. G_debug(1, "Vect_build_pg(): db='%s' table='%s', build=%d",
  51. pg_info->db_name, pg_info->table_name, build);
  52. /* commit transaction block (update mode only) */
  53. if (pg_info->inTransaction && Vect__execute_pg(pg_info->conn, "COMMIT") == -1)
  54. return 0;
  55. pg_info->inTransaction = FALSE;
  56. if (pg_info->feature_type == SF_GEOMETRY)
  57. return 1;
  58. if (build == plus->built)
  59. return 1; /* do nothing */
  60. /* TODO move this init to better place (Vect_open_ ?), because in
  61. theory build may be reused on level2 */
  62. if (!pg_info->toposchema_name && build >= plus->built && build > GV_BUILD_BASE) {
  63. G_free(pg_info->offset.array);
  64. G_zero(&(pg_info->offset), sizeof(struct Format_info_offset));
  65. }
  66. if (!pg_info->conn) {
  67. G_warning(_("No DB connection"));
  68. return 0;
  69. }
  70. if (!pg_info->fid_column && !pg_info->toposchema_name) {
  71. G_warning(_("Feature table <%s> has no primary key defined"),
  72. pg_info->table_name);
  73. G_warning(_("Random read is not supported for this layer. "
  74. "Unable to build topology."));
  75. return 0;
  76. }
  77. if (build > GV_BUILD_NONE) {
  78. G_message(_("Using external data format '%s' (feature type '%s')"),
  79. Vect_get_finfo_format_info(Map),
  80. Vect_get_finfo_geometry_type(Map));
  81. if (!pg_info->toposchema_name)
  82. G_message(_("Building pseudo-topology over simple features..."));
  83. else
  84. G_message(_("Building topology from PostGIS topology schema <%s>..."),
  85. pg_info->toposchema_name);
  86. }
  87. if (!pg_info->toposchema_name) /* pseudo-topology for simple features */
  88. return Vect__build_sfa(Map, build);
  89. /* PostGIS Topology */
  90. return build_topo(Map, build);
  91. #else
  92. G_fatal_error(_("GRASS is not compiled with PostgreSQL support"));
  93. return 0;
  94. #endif
  95. }
  96. #ifdef HAVE_POSTGRES
  97. /*!
  98. \brief Build from PostGIS topology schema
  99. \todo Attach isles
  100. \param Map pointer to Map_info struct
  101. \param build build level
  102. \return 1 on success
  103. \return 0 on error
  104. */
  105. int build_topo(struct Map_info *Map, int build)
  106. {
  107. int line, type, s, n_nodes;
  108. int area, nareas, isle, nisles;
  109. int face[2];
  110. char stmt[DB_SQL_MAX];
  111. char *def_file;
  112. struct Plus_head *plus;
  113. struct Format_info_pg *pg_info;
  114. struct P_line *Line;
  115. struct P_area *Area;
  116. struct P_topo_b *topo_b;
  117. struct P_isle *Isle;
  118. plus = &(Map->plus);
  119. pg_info = &(Map->fInfo.pg);
  120. /* check if upgrade or downgrade */
  121. if (build < plus->built) {
  122. /* -> downgrade */
  123. Vect__build_downgrade(Map, build);
  124. return 1;
  125. }
  126. /* -> upgrade */
  127. if (build < GV_BUILD_BASE)
  128. return 1; /* nothing to print */
  129. /* cache features to speed-up random access (when attaching isles
  130. to areas) */
  131. if (build >= GV_BUILD_BASE) {
  132. /* clean-up GRASS topology tables in DB */
  133. if (!pg_info->topo_geo_only)
  134. Vect__clean_grass_db_topo(&(Map->fInfo.pg));
  135. if (Map->mode == GV_MODE_RW &&
  136. pg_info->cache.lines_num > 0) {
  137. /* read line cache from scratch when map is open in update
  138. * mode, before building native topology read nodes from
  139. * PostGIS Topology */
  140. /* clean-up spatial a category indices */
  141. dig_free_plus(&(Map->plus));
  142. dig_init_plus(&(Map->plus));
  143. plus->Spidx_new = TRUE;
  144. plus->update_cidx = TRUE;
  145. /* reset cache for reading features */
  146. Vect__free_cache(&(pg_info->cache));
  147. }
  148. }
  149. if (plus->built >= GV_BUILD_BASE &&
  150. pg_info->cache.lines_num < 1) {
  151. /* features are not cached, build from scratch */
  152. Vect_build_partial(Map, GV_BUILD_NONE);
  153. }
  154. if (plus->built < GV_BUILD_BASE) {
  155. /* force loading nodes from DB to get up-to-date node
  156. * offsets, see write_nodes() for details */
  157. Vect__free_offset(&(pg_info->offset));
  158. pg_info->cache.ctype = CACHE_FEATURE; /* do not cache nodes */
  159. n_nodes = Map->plus.n_nodes = Vect__load_map_nodes_pg(Map, TRUE);
  160. Vect__free_cache(&(pg_info->cache));
  161. }
  162. if (build > GV_BUILD_BASE)
  163. pg_info->cache.ctype = CACHE_MAP; /* cache all features */
  164. /* update TopoGeometry based on GRASS-like topology */
  165. Vect_build_nat(Map, build);
  166. if (n_nodes != Map->plus.n_nodes)
  167. G_warning(_("Inconsistency in topology: number of nodes %d (should be %d)"),
  168. Map->plus.n_nodes, n_nodes);
  169. /* store map boundig box in DB */
  170. save_map_bbox(pg_info, &(plus->box));
  171. /* begin transaction */
  172. if (Vect__execute_pg(pg_info->conn, "BEGIN"))
  173. return 0;
  174. Vect__execute_pg(pg_info->conn, "SET CONSTRAINTS ALL DEFERRED");
  175. /* write full node topo info to DB if requested */
  176. if (!pg_info->topo_geo_only) {
  177. write_nodes(plus, pg_info);
  178. write_lines(plus, pg_info);
  179. }
  180. /* update faces from GRASS Topology */
  181. if (build >= GV_BUILD_AREAS) {
  182. /* do clean up (1-3)
  183. insert new faces (4)
  184. update edges (5)
  185. */
  186. G_message(_("Cleaning-up topology schema..."));
  187. /* 1) reset centroids to '0' (universal face) */
  188. sprintf(stmt, "UPDATE \"%s\".node SET containing_face = 0 WHERE "
  189. "containing_face IS NOT NULL", pg_info->toposchema_name);
  190. G_debug(2, "SQL: %s", stmt);
  191. if(Vect__execute_pg(pg_info->conn, stmt) == -1) {
  192. Vect__execute_pg(pg_info->conn, "ROLLBACK");
  193. return 0;
  194. }
  195. /* 2) reset left|right edges */
  196. sprintf(stmt, "UPDATE \"%s\".edge_data SET left_face = 0, right_face = 0",
  197. pg_info->toposchema_name);
  198. G_debug(2, "SQL: %s", stmt);
  199. if(Vect__execute_pg(pg_info->conn, stmt) == -1) {
  200. Vect__execute_pg(pg_info->conn, "ROLLBACK");
  201. return 0;
  202. }
  203. /* 3) delete faces (areas/isles) */
  204. sprintf(stmt, "DELETE FROM \"%s\".face WHERE "
  205. "face_id != 0", pg_info->toposchema_name);
  206. G_debug(2, "SQL: %s", stmt);
  207. if(Vect__execute_pg(pg_info->conn, stmt) == -1) {
  208. Vect__execute_pg(pg_info->conn, "ROLLBACK");
  209. return 0;
  210. }
  211. if (!pg_info->topo_geo_only) {
  212. sprintf(stmt, "DELETE FROM \"%s\".%s", pg_info->toposchema_name, TOPO_TABLE_AREA);
  213. G_debug(2, "SQL: %s", stmt);
  214. if(Vect__execute_pg(pg_info->conn, stmt) == -1) {
  215. Vect__execute_pg(pg_info->conn, "ROLLBACK");
  216. return 0;
  217. }
  218. sprintf(stmt, "DELETE FROM \"%s\".%s", pg_info->toposchema_name, TOPO_TABLE_ISLE);
  219. G_debug(2, "SQL: %s", stmt);
  220. if(Vect__execute_pg(pg_info->conn, stmt) == -1) {
  221. Vect__execute_pg(pg_info->conn, "ROLLBACK");
  222. return 0;
  223. }
  224. }
  225. /* 4) insert faces & update nodes (containing_face) based on
  226. * GRASS topology */
  227. G_message(_("Updating faces..."));
  228. nareas = Vect_get_num_areas(Map);
  229. for (area = 1; area <= nareas; area++) {
  230. G_percent(area, nareas, 5);
  231. if (0 == Vect__insert_face_pg(Map, area)) {
  232. Vect__execute_pg(pg_info->conn, "ROLLBACK");
  233. return 0;
  234. }
  235. if (build < GV_BUILD_CENTROIDS)
  236. continue;
  237. /* update centroids (node -> containing_face) */
  238. Area = plus->Area[area];
  239. if (Area->centroid < 1) {
  240. G_debug(3, "Area %d without centroid, skipped", area);
  241. continue;
  242. }
  243. Line = plus->Line[Area->centroid];
  244. sprintf(stmt, "UPDATE \"%s\".node SET "
  245. "containing_face = %d WHERE node_id = %d",
  246. pg_info->toposchema_name, area, (int)Line->offset);
  247. G_debug(2, "SQL: %s", stmt);
  248. if(Vect__execute_pg(pg_info->conn, stmt) == -1) {
  249. Vect__execute_pg(pg_info->conn, "ROLLBACK");
  250. return 0;
  251. }
  252. }
  253. /* 5) update edges (left and right face) */
  254. G_message(_("Updating edges..."));
  255. for (line = 1; line <= plus->n_lines; line++) {
  256. G_percent(line, plus->n_lines, 5);
  257. type = Vect_read_line(Map, NULL, NULL, line);
  258. if (type != GV_BOUNDARY)
  259. continue;
  260. Line = Map->plus.Line[line];
  261. if (!Line) {
  262. G_warning(_("Inconsistency in topology detected. "
  263. "Dead line found."));
  264. return 0;
  265. }
  266. topo_b = (struct P_topo_b *) Line->topo;
  267. for (s = 0; s < 2; s++) { /* for both sides */
  268. face[s] = s == 0 ? topo_b->left : topo_b->right;
  269. if (face[s] < 0) {
  270. /* isle */
  271. Isle = plus->Isle[abs(face[s])];
  272. face[s] = Isle->area;
  273. }
  274. }
  275. G_debug(3, "update edge %d: left_face = %d, right_face = %d",
  276. (int)Line->offset, face[0], face[1]);
  277. sprintf(stmt, "UPDATE \"%s\".edge_data SET "
  278. "left_face = %d, right_face = %d "
  279. "WHERE edge_id = %d", pg_info->toposchema_name,
  280. face[0], face[1], (int) Line->offset);
  281. if (Vect__execute_pg(pg_info->conn, stmt) == -1) {
  282. Vect__execute_pg(pg_info->conn, "ROLLBACK");
  283. return 0;
  284. }
  285. }
  286. /* write full area topo info to DB if requested */
  287. if (!pg_info->topo_geo_only) {
  288. write_areas(plus, pg_info);
  289. }
  290. } /* build >= GV_BUILD_AREAS */
  291. if (build >= GV_BUILD_ATTACH_ISLES) {
  292. /* insert isles as faces with negative face_id */
  293. nisles = Vect_get_num_islands(Map);
  294. for(isle = 1; isle <= nisles; isle++) {
  295. Isle = plus->Isle[isle];
  296. Vect__insert_face_pg(Map, -isle);
  297. }
  298. /* write full isles topo info to DB if requested */
  299. if (!pg_info->topo_geo_only) {
  300. write_isles(plus, pg_info);
  301. }
  302. } /* build >= GV_BUILD_ISLES */
  303. if (pg_info->feature_type == SF_POLYGON) {
  304. int centroid;
  305. struct P_line *Line;
  306. G_message(_("Updating TopoGeometry data..."));
  307. for (area = 1; area <= plus->n_areas; area++) {
  308. G_percent(area, plus->n_areas, 5);
  309. centroid = Vect_get_area_centroid(Map, area);
  310. if (centroid < 1)
  311. continue;
  312. Line = plus->Line[centroid];
  313. if (!Line)
  314. continue;
  315. /* update topogeometry object: centroid -> face */
  316. if (build_topogeom_stmt(pg_info, GV_CENTROID, area, (int) Line->offset, stmt) &&
  317. Vect__execute_pg(pg_info->conn, stmt) == -1) {
  318. Vect__execute_pg(pg_info->conn, "ROLLBACK");
  319. return 0;
  320. }
  321. Vect__define_topo_relation(pg_info, area, area);
  322. }
  323. }
  324. if (Vect__execute_pg(pg_info->conn, "COMMIT") == -1)
  325. return 0;
  326. /* check if we want to create simple features from topogeometry
  327. data */
  328. def_file = getenv("GRASS_VECTOR_PGFILE");
  329. if (G_find_file2("", def_file ? def_file : "PG", G_mapset())) {
  330. FILE *fp;
  331. const char *p;
  332. struct Key_Value *key_val;
  333. fp = G_fopen_old("", def_file ? def_file : "PG", G_mapset());
  334. if (!fp) {
  335. G_fatal_error(_("Unable to open PG file"));
  336. }
  337. key_val = G_fread_key_value(fp);
  338. fclose(fp);
  339. /* build simple features from topogeometry data */
  340. p = G_find_key_value("simple_feature", key_val);
  341. if (p && G_strcasecmp(p, "yes") == 0) {
  342. if (build > GV_BUILD_BASE)
  343. Map->level = LEVEL_2; /* force level to avoid errors */
  344. if (create_simple_feature_from_topo(Map) != 0)
  345. return 0;
  346. }
  347. G_free_key_value(key_val);
  348. }
  349. return 1;
  350. }
  351. /*!
  352. \brief Build UPDATE statement for topo geometry element stored in
  353. feature table
  354. \param pg_info so pointer to Format_info_pg
  355. \param type feature type (GV_POINT, ...)
  356. \param topo_id topology element id
  357. \param fid feature id
  358. \param[out] stmt string buffer
  359. \return 1 on success
  360. \return 0 on failure
  361. */
  362. int build_topogeom_stmt(const struct Format_info_pg *pg_info,
  363. int type, int topo_id, int fid, char *stmt)
  364. {
  365. int topogeom_type;
  366. switch(type) {
  367. case GV_POINT:
  368. topogeom_type = 1;
  369. break;
  370. case GV_LINE:
  371. case GV_BOUNDARY:
  372. topogeom_type = 2;
  373. break;
  374. case GV_CENTROID:
  375. topogeom_type = 3;
  376. break;
  377. default:
  378. G_warning(_("Unsupported topo geometry type %d"), type);
  379. return 0;
  380. }
  381. sprintf(stmt, "UPDATE \"%s\".\"%s\" SET %s = "
  382. "'(%d, 1, %d, %d)'::topology.TopoGeometry "
  383. "WHERE (%s).id = %d",
  384. pg_info->schema_name, pg_info->table_name,
  385. pg_info->topogeom_column, pg_info->toposchema_id,
  386. topo_id, topogeom_type, pg_info->topogeom_column, fid);
  387. return 1;
  388. }
  389. /*!
  390. \brief Store map bounding box in DB head table
  391. \param pg_info pointer to Format_info_pg struct
  392. \param box pointer to bounding box
  393. \return 1 on success
  394. \return 0 on failure
  395. */
  396. int save_map_bbox(const struct Format_info_pg *pg_info, const struct bound_box *box)
  397. {
  398. char stmt[DB_SQL_MAX];
  399. /* create if not exists */
  400. if (create_topo_grass(pg_info) == -1) {
  401. G_warning(_("Unable to create <%s.%s>"), TOPO_SCHEMA, TOPO_TABLE);
  402. return 0;
  403. }
  404. /* update bbox */
  405. if (has_topo_grass(pg_info)) {
  406. /* -> update */
  407. sprintf(stmt, "UPDATE \"%s\".\"%s\" SET %s = "
  408. "'BOX3D(%.12f %.12f %.12f, %.12f %.12f %.12f)'::box3d WHERE %s = %d",
  409. TOPO_SCHEMA, TOPO_TABLE, TOPO_BBOX,
  410. box->W, box->S, box->B, box->E, box->N, box->T,
  411. TOPO_ID, pg_info->toposchema_id);
  412. }
  413. else {
  414. /* -> insert */
  415. sprintf(stmt, "INSERT INTO \"%s\".\"%s\" (%s, %s) "
  416. "VALUES(%d, 'BOX3D(%.12f %.12f %.12f, %.12f %.12f %.12f)'::box3d)",
  417. TOPO_SCHEMA, TOPO_TABLE, TOPO_ID, TOPO_BBOX, pg_info->toposchema_id,
  418. box->W, box->S, box->B, box->E, box->N, box->T);
  419. }
  420. if (Vect__execute_pg(pg_info->conn, stmt) == -1) {
  421. return -1;
  422. }
  423. return 1;
  424. }
  425. /*!
  426. \brief Creates 'topology.grass' table if not exists
  427. \return 0 table already exists
  428. \return 1 table successfully added
  429. \return -1 on error
  430. */
  431. int create_topo_grass(const struct Format_info_pg *pg_info)
  432. {
  433. char stmt[DB_SQL_MAX];
  434. PGresult *result;
  435. /* check if table exists */
  436. sprintf(stmt, "SELECT COUNT(*) FROM information_schema.tables "
  437. "WHERE table_schema = '%s' AND table_name = '%s'",
  438. TOPO_SCHEMA, TOPO_TABLE);
  439. result = PQexec(pg_info->conn, stmt);
  440. if (!result || PQresultStatus(result) != PGRES_TUPLES_OK) {
  441. PQclear(result);
  442. return -1;
  443. }
  444. if (atoi(PQgetvalue(result, 0, 0)) == 1) {
  445. /* table already exists */
  446. PQclear(result);
  447. return 1;
  448. }
  449. PQclear(result);
  450. G_debug(1, "<%s.%s> created", TOPO_SCHEMA, TOPO_TABLE);
  451. /* create table */
  452. sprintf(stmt, "CREATE TABLE \"%s\".\"%s\" (%s INTEGER, %s box3d)",
  453. TOPO_SCHEMA, TOPO_TABLE, TOPO_ID, TOPO_BBOX);
  454. if (Vect__execute_pg(pg_info->conn, stmt) == -1) {
  455. return -1;
  456. }
  457. /* add primary key */
  458. sprintf(stmt, "ALTER TABLE \"%s\".\"%s\" ADD PRIMARY KEY (%s)",
  459. TOPO_SCHEMA, TOPO_TABLE, TOPO_ID);
  460. if (Vect__execute_pg(pg_info->conn, stmt) == -1) {
  461. return -1;
  462. }
  463. /* add constraint */
  464. sprintf(stmt, "ALTER TABLE \"%s\".\"%s\" ADD CONSTRAINT \"%s_%s_fkey\" "
  465. "FOREIGN KEY (%s) REFERENCES topology.topology(id) ON DELETE CASCADE",
  466. TOPO_SCHEMA, TOPO_TABLE, TOPO_TABLE, TOPO_ID, TOPO_ID);
  467. if (Vect__execute_pg(pg_info->conn, stmt) == -1) {
  468. return -1;
  469. }
  470. return 1;
  471. }
  472. /*!
  473. \brief Check if 'topology_id' exists in 'topology.grass'
  474. \param pg_info pointer to Format_info_pg struct
  475. \return TRUE if exists
  476. \return FALSE otherwise
  477. \return -1 on error
  478. */
  479. int has_topo_grass(const struct Format_info_pg *pg_info)
  480. {
  481. int has_topo;
  482. char stmt[DB_SQL_MAX];
  483. PGresult *result;
  484. sprintf(stmt, "SELECT COUNT(*) FROM \"%s\".\"%s\" "
  485. "WHERE %s = %d",
  486. TOPO_SCHEMA, TOPO_TABLE, TOPO_ID, pg_info->toposchema_id);
  487. result = PQexec(pg_info->conn, stmt);
  488. if (!result || PQresultStatus(result) != PGRES_TUPLES_OK) {
  489. PQclear(result);
  490. return -1;
  491. }
  492. has_topo = FALSE;
  493. if (atoi(PQgetvalue(result, 0, 0)) == 1) {
  494. /* table already exists */
  495. has_topo = TRUE;
  496. }
  497. PQclear(result);
  498. return has_topo;
  499. }
  500. /*!
  501. \brief Insert node into 'node_grass' table
  502. Writes (see P_node struct):
  503. - lines
  504. - angles
  505. Already stored in Topo-Geo:
  506. - x,y,z (geom)
  507. \param plus pointer to Plus_head struct
  508. \param pg_info pointer to Format_info_pg struct
  509. \return 0 on success
  510. \return -1 on error
  511. */
  512. int write_nodes(const struct Plus_head *plus,
  513. const struct Format_info_pg *pg_info)
  514. {
  515. int i, node_id;
  516. size_t stmt_lines_size, stmt_angles_size, stmt_size;
  517. char *stmt_lines, *stmt_angles, *stmt;
  518. const struct P_node *Node;
  519. const struct Format_info_offset *offset;
  520. offset = &(pg_info->offset);
  521. if (offset->array_num < 1) /* nothing to write */
  522. return 0;
  523. if (plus->n_nodes != offset->array_num) {
  524. G_warning(_("Unable to write nodes, offset array mismatch"));
  525. return -1;
  526. }
  527. stmt_size = 2 * DB_SQL_MAX + 512;
  528. stmt = (char *) G_malloc(stmt_size);
  529. stmt_lines = stmt_angles = NULL;
  530. for (i = 1; i <= plus->n_nodes; i++) {
  531. Node = plus->Node[i];
  532. if (!Node)
  533. continue; /* should not happen */
  534. node_id = offset->array[i-1];
  535. /* 'lines' array */
  536. build_stmt_id(Node->lines, Node->n_lines, TRUE, plus, &stmt_lines, &stmt_lines_size);
  537. /* 'angle' array */
  538. build_stmt_id(Node->angles, Node->n_lines, FALSE, NULL, &stmt_angles, &stmt_angles_size);
  539. /* build SQL statement to add new node into 'node_grass' */
  540. if (stmt_lines_size + stmt_angles_size + 512 > stmt_size) {
  541. stmt_size = stmt_lines_size + stmt_angles_size + 512;
  542. stmt = (char *) G_realloc(stmt, stmt_size);
  543. }
  544. sprintf(stmt, "INSERT INTO \"%s\".%s VALUES ("
  545. "%d, '{%s}', '{%s}')", pg_info->toposchema_name, TOPO_TABLE_NODE,
  546. node_id, stmt_lines, stmt_angles);
  547. if (Vect__execute_pg(pg_info->conn, stmt) == -1) {
  548. G_warning(_("Unable to write nodes"));
  549. return -1;
  550. }
  551. }
  552. G_free(stmt_lines);
  553. G_free(stmt_angles);
  554. G_free(stmt);
  555. return 0;
  556. }
  557. /*!
  558. \brief Insert lines into 'line_grass' table
  559. Writes (see P_line struct) - only for boundaries:
  560. - left, right area
  561. Already stored in Topo-Geo:
  562. - edge_id, left_face, right_face
  563. \param plus pointer to Plus_head struct
  564. \param pg_info pointer to Format_info_pg struct
  565. \return 0 on success
  566. \return -1 on error
  567. */
  568. int write_lines(const struct Plus_head *plus,
  569. const struct Format_info_pg *pg_info)
  570. {
  571. int i, row, offset;
  572. char stmt[DB_SQL_MAX];
  573. const struct P_line *Line;
  574. const struct P_topo_b *topo;
  575. PGresult *res;
  576. sprintf(stmt, "SELECT edge_id FROM \"%s\".edge_data WHERE "
  577. "left_face != 0 OR right_face != 0 ORDER BY edge_id",
  578. pg_info->toposchema_name);
  579. G_debug(2, "SQL: %s", stmt);
  580. res = PQexec(pg_info->conn, stmt);
  581. if (!res || PQresultStatus(res) != PGRES_TUPLES_OK ||
  582. (PQntuples(res) > 0 && PQntuples(res) != plus->n_blines)) {
  583. G_warning(_("Inconsistency in topology: number of "
  584. "boundaries %d (should be %d)"),
  585. PQntuples(res), plus->n_blines);
  586. if (res)
  587. PQclear(res);
  588. return -1;
  589. }
  590. for (row = 0, i = 1; i <= plus->n_lines; i++) {
  591. Line = plus->Line[i];
  592. if (!Line || Line->type != GV_BOUNDARY)
  593. continue;
  594. if (Line->offset == 0L)
  595. offset = atoi(PQgetvalue(res, row++, 0));
  596. else
  597. offset = (int)Line->offset;
  598. topo = (struct P_topo_b *)Line->topo;
  599. sprintf(stmt, "INSERT INTO \"%s\".%s VALUES ("
  600. "%d, %d, %d)", pg_info->toposchema_name, TOPO_TABLE_LINE,
  601. offset, topo->left, topo->right);
  602. if (Vect__execute_pg(pg_info->conn, stmt) == -1) {
  603. G_warning(_("Unable to write lines"));
  604. return -1;
  605. }
  606. }
  607. return 0;
  608. }
  609. /*!
  610. \brief Insert area into 'area_grass' table
  611. Writes (see P_area struct):
  612. - lines
  613. - centroid
  614. - isles
  615. \param plus pointer to Plus_head struct
  616. \param pg_info pointer to Format_info_pg struct
  617. \return 0 on success
  618. \return -1 on error
  619. */
  620. int write_areas(const struct Plus_head *plus,
  621. const struct Format_info_pg *pg_info)
  622. {
  623. int area, centroid;
  624. size_t stmt_lines_size, stmt_isles_size, stmt_size;
  625. char *stmt_lines, *stmt_isles, *stmt;
  626. const struct P_line *Line;
  627. const struct P_area *Area;
  628. stmt_size = 2 * DB_SQL_MAX + 512;
  629. stmt = (char *) G_malloc(stmt_size);
  630. stmt_lines = stmt_isles = NULL;
  631. for (area = 1; area <= plus->n_areas; area++) {
  632. Area = plus->Area[area];
  633. if (!Area) {
  634. G_debug(3, "Area %d skipped (dead)", area);
  635. continue; /* should not happen */
  636. }
  637. /* 'lines' array */
  638. build_stmt_id(Area->lines, Area->n_lines, TRUE, NULL, &stmt_lines, &stmt_lines_size);
  639. /* 'isles' array */
  640. build_stmt_id(Area->isles, Area->n_isles, TRUE, NULL, &stmt_isles, &stmt_isles_size);
  641. if (Area->centroid != 0) {
  642. Line = plus->Line[Area->centroid];
  643. if (!Line) {
  644. G_warning(_("Topology for centroid %d not available. Area %d skipped"),
  645. Area->centroid, area);
  646. continue;
  647. }
  648. centroid = (int) Line->offset;
  649. }
  650. else {
  651. centroid = 0;
  652. }
  653. /* build SQL statement to add new node into 'node_grass' */
  654. if (stmt_lines_size + stmt_isles_size + 512 > stmt_size) {
  655. stmt_size = stmt_lines_size + stmt_isles_size + 512;
  656. stmt = (char *) G_realloc(stmt, stmt_size);
  657. }
  658. sprintf(stmt, "INSERT INTO \"%s\".%s VALUES ("
  659. "%d, '{%s}', %d, '{%s}')", pg_info->toposchema_name, TOPO_TABLE_AREA,
  660. area, stmt_lines, centroid, stmt_isles);
  661. if (Vect__execute_pg(pg_info->conn, stmt) == -1) {
  662. return -1;
  663. }
  664. }
  665. G_free(stmt_lines);
  666. G_free(stmt_isles);
  667. G_free(stmt);
  668. return 0;
  669. }
  670. /*!
  671. \brief Insert isle into 'isle_grass' table
  672. Writes (see P_isle struct):
  673. - lines
  674. - area
  675. \param plus pointer to Plus_head struct
  676. \param pg_info pointer to Format_info_pg struct
  677. \return 0 on success
  678. \return -1 on error
  679. */
  680. int write_isles(const struct Plus_head *plus,
  681. const struct Format_info_pg *pg_info)
  682. {
  683. int isle;
  684. size_t stmt_lines_size, stmt_size;
  685. char *stmt_lines, *stmt;
  686. const struct P_isle *Isle;
  687. stmt_size = DB_SQL_MAX + 512;
  688. stmt = (char *) G_malloc(stmt_size);
  689. stmt_lines = NULL;
  690. for (isle = 1; isle <= plus->n_isles; isle++) {
  691. Isle = plus->Isle[isle];
  692. if (!Isle)
  693. continue; /* should not happen */
  694. /* 'lines' array */
  695. build_stmt_id(Isle->lines, Isle->n_lines, TRUE, NULL, &stmt_lines, &stmt_lines_size);
  696. /* build SQL statement to add new node into 'node_grass' */
  697. if (stmt_lines_size + 512 > stmt_size) {
  698. stmt_size = stmt_lines_size + 512;
  699. stmt = (char *) G_realloc(stmt, stmt_size);
  700. }
  701. sprintf(stmt, "INSERT INTO \"%s\".%s VALUES ("
  702. "%d, '{%s}', %d)", pg_info->toposchema_name, TOPO_TABLE_ISLE,
  703. isle, stmt_lines, Isle->area);
  704. if (Vect__execute_pg(pg_info->conn, stmt) == -1) {
  705. return -1;
  706. }
  707. }
  708. G_free(stmt_lines);
  709. G_free(stmt);
  710. return 0;
  711. }
  712. /*!
  713. \brief Create PG-like array for int/float array
  714. \param array array of items
  715. \param nitems number of items in the array
  716. \param is_int TRUE for array of integers otherwise floats
  717. \param plus pointer to Plus_head struct
  718. \param[in,out] output buffer (re-used)
  719. \param[in,out] buffer size
  720. */
  721. void build_stmt_id(const void *array, int nitems, int is_int, const struct Plus_head *plus,
  722. char **stmt, size_t *stmt_size)
  723. {
  724. int i, ivalue;
  725. int *iarray;
  726. float *farray;
  727. size_t stmt_id_size;
  728. char *stmt_id, buf_id[128];
  729. struct P_line *Line;
  730. if (is_int)
  731. iarray = (int *) array;
  732. else
  733. farray = (float *) array;
  734. if (!(*stmt)) {
  735. stmt_id_size = DB_SQL_MAX;
  736. stmt_id = (char *) G_malloc(stmt_id_size);
  737. }
  738. else {
  739. stmt_id_size = *stmt_size;
  740. stmt_id = *stmt;
  741. }
  742. /* reset array */
  743. stmt_id[0] = '\0';
  744. for (i = 0; i < nitems; i++) {
  745. /* realloc array if needed */
  746. if (strlen(stmt_id) + 100 > stmt_id_size) {
  747. stmt_id_size = strlen(stmt_id) + DB_SQL_MAX;
  748. stmt_id = (char *) G_realloc(stmt_id, stmt_id_size);
  749. }
  750. if (is_int) {
  751. if (plus) {
  752. Line = plus->Line[abs(iarray[i])];
  753. ivalue = (int) Line->offset;
  754. if (iarray[i] < 0)
  755. ivalue *= -1;
  756. }
  757. else {
  758. ivalue = iarray[i];
  759. }
  760. sprintf(buf_id, "%d", ivalue);
  761. }
  762. else {
  763. sprintf(buf_id, "%f", farray[i]);
  764. }
  765. if (i > 0)
  766. strcat(stmt_id, ",");
  767. strcat(stmt_id, buf_id);
  768. }
  769. *stmt = stmt_id;
  770. *stmt_size = stmt_id_size;
  771. }
  772. /*!
  773. \brief Clean-up GRASS Topology tables
  774. \param pg_info pointer to Format_info_pg pg_info
  775. \return 0 on success
  776. \return -1 on error
  777. */
  778. int Vect__clean_grass_db_topo(struct Format_info_pg *pg_info)
  779. {
  780. char stmt[DB_SQL_MAX];
  781. sprintf(stmt, "DELETE FROM \"%s\".\"%s\"",
  782. pg_info->toposchema_name, TOPO_TABLE_NODE);
  783. if (-1 == Vect__execute_pg(pg_info->conn, stmt))
  784. return -1;
  785. sprintf(stmt, "DELETE FROM \"%s\".\"%s\"",
  786. pg_info->toposchema_name, TOPO_TABLE_LINE);
  787. if (-1 == Vect__execute_pg(pg_info->conn, stmt))
  788. return -1;
  789. sprintf(stmt, "DELETE FROM \"%s\".\"%s\"",
  790. pg_info->toposchema_name, TOPO_TABLE_AREA);
  791. if (-1 == Vect__execute_pg(pg_info->conn, stmt))
  792. return -1;
  793. sprintf(stmt, "DELETE FROM \"%s\".\"%s\"",
  794. pg_info->toposchema_name, TOPO_TABLE_ISLE);
  795. if (-1 == Vect__execute_pg(pg_info->conn, stmt))
  796. return -1;
  797. return 0;
  798. }
  799. /*!
  800. \brief Create simple features geometry from topogeometry data
  801. \param Map pointer to Map_info struct
  802. \return 0 on success
  803. \return -1 on error
  804. */
  805. int create_simple_feature_from_topo(struct Map_info *Map)
  806. {
  807. char stmt[DB_SQL_MAX];
  808. struct Format_info_pg *pg_info;
  809. pg_info = &(Map->fInfo.pg);
  810. G_debug(1, "build_simple_feature_from_topo(): %d", pg_info->feature_type);
  811. G_message(_("Create simple features topology from topogeometry data..."));
  812. Vect__execute_pg(pg_info->conn, "BEGIN");
  813. if (pg_info->feature_type == SF_POINT ||
  814. pg_info->feature_type == SF_LINESTRING) {
  815. sprintf(stmt, "UPDATE \"%s\".\"%s\" SET %s = (SELECT geom FROM \"%s\".node "
  816. "WHERE node_id = (%s).id)", pg_info->schema_name, pg_info->table_name,
  817. pg_info->geom_column, pg_info->toposchema_name, pg_info->topogeom_column);
  818. if(Vect__execute_pg(pg_info->conn, stmt) == -1) {
  819. Vect__execute_pg(pg_info->conn, "ROLLBACK");
  820. return -1;
  821. }
  822. }
  823. else if (pg_info->feature_type == SF_POLYGON) {
  824. Vect__copy_areas(Map, 1, Map);
  825. }
  826. else {
  827. G_warning(_("Unable to build simple features from topogeometry data. "
  828. "Unsupported type %d."), pg_info->feature_type);
  829. }
  830. Vect__execute_pg(pg_info->conn, "COMMIT");
  831. return 0;
  832. }
  833. #endif