open_pg.c 48 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588
  1. /*!
  2. \file lib/vector/Vlib/open_pg.c
  3. \brief Vector library - Open PostGIS layer as vector map layer
  4. Higher level functions for reading/writing/manipulating vectors.
  5. (C) 2011-2013 by the GRASS Development Team
  6. This program is free software under the GNU General Public License
  7. (>=v2). Read the file COPYING that comes with GRASS for details.
  8. \author Martin Landa <landa.martin gmail.com>
  9. */
  10. #include <string.h>
  11. #include <stdlib.h>
  12. #include <grass/vector.h>
  13. #include <grass/dbmi.h>
  14. #include <grass/glocale.h>
  15. #ifdef HAVE_POSTGRES
  16. #include "pg_local_proto.h"
  17. struct edge_data {
  18. int id;
  19. int start_node;
  20. int end_node;
  21. int left_face;
  22. int right_face;
  23. char *wkb_geom;
  24. };
  25. static char *get_key_column(struct Format_info_pg *);
  26. static SF_FeatureType ftype_from_string(const char *);
  27. static int drop_table(struct Format_info_pg *);
  28. static void connect_db(struct Format_info_pg *);
  29. static int check_topo(struct Format_info_pg *, struct Plus_head *);
  30. static int parse_bbox(const char *, struct bound_box *);
  31. static struct P_node *read_p_node(struct Plus_head *, int, int,
  32. const char *, const char *, const char *,
  33. struct Format_info_pg *);
  34. static struct P_line *read_p_line(struct Plus_head *, int,
  35. const struct edge_data *,
  36. struct Format_info_cache *);
  37. static struct P_area *read_p_area(struct Plus_head *, int,
  38. const char *, int, const char *);
  39. static struct P_isle *read_p_isle(struct Plus_head *, int,
  40. const char *, int);
  41. static int load_plus_head(struct Format_info_pg *, struct Plus_head *);
  42. static void notice_processor(void *, const char *);
  43. static char **scan_array(const char *);
  44. static int remap_node(const struct Format_info_offset *, int);
  45. static int remap_line(const struct Plus_head*, int);
  46. #endif
  47. /*!
  48. \brief Open vector map - PostGIS feature table on non-topological
  49. level
  50. \param[in,out] Map pointer to Map_info structure
  51. \param update TRUE for write mode, otherwise read-only
  52. \return 0 success
  53. \return -1 error
  54. */
  55. int V1_open_old_pg(struct Map_info *Map, int update)
  56. {
  57. #ifdef HAVE_POSTGRES
  58. int found;
  59. char stmt[DB_SQL_MAX];
  60. PGresult *res;
  61. struct Format_info_pg *pg_info;
  62. G_debug(2, "V1_open_old_pg(): update = %d", update);
  63. pg_info = &(Map->fInfo.pg);
  64. if (!pg_info->conninfo) {
  65. G_warning(_("Connection string not defined"));
  66. return -1;
  67. }
  68. if (!pg_info->table_name) {
  69. G_warning(_("PostGIS feature table not defined"));
  70. return -1;
  71. }
  72. G_debug(1, "V1_open_old_pg(): conninfo='%s' table='%s'",
  73. pg_info->conninfo, pg_info->table_name);
  74. /* connect database */
  75. if (!pg_info->conn)
  76. connect_db(pg_info);
  77. /* get DB name */
  78. pg_info->db_name = G_store(PQdb(pg_info->conn));
  79. if (!pg_info->db_name) {
  80. G_warning(_("Unable to get database name"));
  81. return -1;
  82. }
  83. /* get fid and geometry column */
  84. sprintf(stmt, "SELECT f_geometry_column, coord_dimension, srid, type "
  85. "FROM geometry_columns WHERE f_table_schema = '%s' AND "
  86. "f_table_name = '%s'", pg_info->schema_name, pg_info->table_name);
  87. G_debug(2, "SQL: %s", stmt);
  88. res = PQexec(pg_info->conn, stmt);
  89. if (!res || PQresultStatus(res) != PGRES_TUPLES_OK)
  90. G_fatal_error("%s\n%s", _("No feature tables found in database."),
  91. PQresultErrorMessage(res));
  92. found = PQntuples(res) > 0 ? TRUE : FALSE;
  93. if (found) {
  94. /* geometry column */
  95. pg_info->geom_column = G_store(PQgetvalue(res, 0, 0));
  96. G_debug(3, "\t-> table = %s column = %s", pg_info->table_name,
  97. pg_info->geom_column);
  98. /* fid column */
  99. pg_info->fid_column = get_key_column(pg_info);
  100. /* coordinates dimension */
  101. pg_info->coor_dim = atoi(PQgetvalue(res, 0, 1));
  102. /* SRS ID */
  103. pg_info->srid = atoi(PQgetvalue(res, 0, 2));
  104. /* feature type */
  105. pg_info->feature_type = ftype_from_string(PQgetvalue(res, 0, 3));
  106. }
  107. PQclear(res);
  108. /* no feature in cache */
  109. pg_info->cache.fid = -1;
  110. if (!found) {
  111. G_warning(_("Feature table <%s> not found in 'geometry_columns'"),
  112. pg_info->table_name);
  113. return -1;
  114. }
  115. /* check for topo schema */
  116. check_topo(pg_info, &(Map->plus));
  117. return 0;
  118. #else
  119. G_fatal_error(_("GRASS is not compiled with PostgreSQL support"));
  120. return -1;
  121. #endif
  122. }
  123. /*!
  124. \brief Open vector map - PostGIS feature table on topological level
  125. Simple feature access:
  126. - open feature index file
  127. PostGIS Topology:
  128. - check if topological schema exists
  129. \param[in,out] Map pointer to Map_info structure
  130. \return 0 success
  131. \return -1 error
  132. */
  133. int V2_open_old_pg(struct Map_info *Map)
  134. {
  135. #ifdef HAVE_POSTGRES
  136. struct Format_info_pg *pg_info;
  137. PGresult *res;
  138. G_debug(3, "V2_open_old_pg(): name = %s mapset = %s", Map->name,
  139. Map->mapset);
  140. pg_info = &(Map->fInfo.pg);
  141. if (pg_info->toposchema_name) {
  142. char stmt[DB_SQL_MAX];
  143. /* get topo schema id */
  144. sprintf(stmt, "SELECT id FROM topology.topology WHERE name = '%s'",
  145. pg_info->toposchema_name);
  146. res = PQexec(pg_info->conn, stmt);
  147. if (!res || PQresultStatus(res) != PGRES_TUPLES_OK) {
  148. G_warning("%s\n%s", _("Topology schema not found."),
  149. PQresultErrorMessage(res));
  150. if (res)
  151. PQclear(res);
  152. return -1;
  153. }
  154. pg_info->toposchema_id = atoi(PQgetvalue(res, 0, 0));
  155. PQclear(res);
  156. }
  157. else {
  158. /* fidx file needed only for simple features access */
  159. if (Vect_open_fidx(Map, &(pg_info->offset)) != 0) {
  160. G_warning(_("Unable to open feature index file for vector map <%s>"),
  161. Vect_get_full_name(Map));
  162. G_zero(&(pg_info->offset), sizeof(struct Format_info_offset));
  163. }
  164. }
  165. return 0;
  166. #else
  167. G_fatal_error(_("GRASS is not compiled with PostgreSQL support"));
  168. return -1;
  169. #endif
  170. }
  171. /*!
  172. \brief Prepare PostGIS database for creating new feature table
  173. (level 1)
  174. New PostGIS table is created when writing features by
  175. Vect_wrile_line().
  176. \param[out] Map pointer to Map_info structure
  177. \param name name of PostGIS feature table to create
  178. \param with_z WITH_Z for 3D vector data otherwise WITHOUT_Z
  179. \return 0 success
  180. \return -1 error
  181. */
  182. int V1_open_new_pg(struct Map_info *Map, const char *name, int with_z)
  183. {
  184. #ifdef HAVE_POSTGRES
  185. char stmt[DB_SQL_MAX];
  186. struct Format_info_pg *pg_info;
  187. PGresult *res;
  188. G_debug(2, "V1_open_new_pg(): name = %s with_z = %d", name, with_z);
  189. pg_info = &(Map->fInfo.pg);
  190. if (!pg_info->conninfo) {
  191. G_warning(_("Connection string not defined"));
  192. return -1;
  193. }
  194. if (!pg_info->table_name) {
  195. G_warning(_("PostGIS feature table not defined"));
  196. return -1;
  197. }
  198. G_debug(1, "V1_open_new_pg(): conninfo='%s' table='%s'",
  199. pg_info->conninfo, pg_info->table_name);
  200. /* connect database */
  201. connect_db(pg_info);
  202. /* get DB name */
  203. pg_info->db_name = G_store(PQdb(pg_info->conn));
  204. if (!pg_info->db_name) {
  205. G_warning(_("Unable to get database name"));
  206. return -1;
  207. }
  208. /* if schema not defined, use 'public' */
  209. if (!pg_info->schema_name)
  210. pg_info->schema_name = G_store("public");
  211. /* if fid_column not defined, use 'fid' */
  212. if (!pg_info->fid_column)
  213. pg_info->fid_column = G_store(GV_PG_FID_COLUMN);
  214. /* if geom_column not defined, use 'geom' */
  215. if (!pg_info->geom_column)
  216. pg_info->geom_column = G_store(GV_PG_GEOMETRY_COLUMN);
  217. /* check if feature table already exists */
  218. sprintf(stmt, "SELECT * FROM pg_tables "
  219. "WHERE schemaname = '%s' AND tablename = '%s'",
  220. pg_info->schema_name, pg_info->table_name);
  221. G_debug(2, "SQL: %s", stmt);
  222. res = PQexec(pg_info->conn, stmt);
  223. if (!res || PQresultStatus(res) != PGRES_TUPLES_OK)
  224. G_fatal_error("%s\n%s", _("No feature tables found in database."),
  225. PQresultErrorMessage(res));
  226. if (PQntuples(res) > 0) {
  227. /* table found */
  228. if (G_get_overwrite()) {
  229. G_warning(_("PostGIS layer <%s.%s> already exists and will be overwritten"),
  230. pg_info->schema_name, pg_info->table_name);
  231. if (drop_table(pg_info) == -1) {
  232. G_warning(_("Unable to delete PostGIS layer <%s>"),
  233. pg_info->table_name);
  234. return -1;
  235. }
  236. }
  237. else {
  238. G_warning(_("PostGIS layer <%s.%s> already exists in database '%s'"),
  239. pg_info->schema_name, pg_info->table_name,
  240. pg_info->db_name);
  241. return -1;
  242. }
  243. }
  244. /* no feature in cache */
  245. pg_info->cache.fid = -1;
  246. /* unknown feature type */
  247. pg_info->feature_type = SF_UNKNOWN;
  248. PQclear(res);
  249. return 0;
  250. #else
  251. G_fatal_error(_("GRASS is not compiled with PostgreSQL support"));
  252. return -1;
  253. #endif
  254. }
  255. /*!
  256. \brief Read full-topology for PostGIS links
  257. Note: Only 2D topological primitives are currently supported
  258. \param[in,out] Map pointer to Map_info structure
  259. \param head_only TRUE to read only header
  260. \return 0 on success
  261. \return 1 topology layer does not exist
  262. \return -1 on error
  263. */
  264. int Vect_open_topo_pg(struct Map_info *Map, int head_only)
  265. {
  266. #ifdef HAVE_POSTGRES
  267. struct Plus_head *plus;
  268. struct Format_info_pg *pg_info;
  269. Map->open = VECT_OPEN_CODE; /* needed by load_plus */
  270. plus = &(Map->plus);
  271. pg_info = &(Map->fInfo.pg);
  272. /* check for topo schema */
  273. if (check_topo(pg_info, plus) != 0)
  274. return 1;
  275. /* free and init plus structure */
  276. dig_init_plus(plus);
  277. plus->Spidx_new = TRUE;
  278. return Vect__load_plus_pg(Map, head_only);
  279. #else
  280. G_fatal_error(_("GRASS is not compiled with PostgreSQL support"));
  281. return -1;
  282. #endif
  283. }
  284. #ifdef HAVE_POSTGRES
  285. /*!
  286. \brief Get key column for feature table
  287. \param pg_info pointer to Format_info_pg
  288. \return string buffer with key column name
  289. \return NULL on missing key column
  290. */
  291. char *get_key_column(struct Format_info_pg *pg_info)
  292. {
  293. char *key_column;
  294. char stmt[DB_SQL_MAX];
  295. PGresult *res;
  296. sprintf(stmt,
  297. "SELECT kcu.column_name "
  298. "FROM INFORMATION_SCHEMA.TABLES t "
  299. "LEFT JOIN INFORMATION_SCHEMA.TABLE_CONSTRAINTS tc "
  300. "ON tc.table_catalog = t.table_catalog "
  301. "AND tc.table_schema = t.table_schema "
  302. "AND tc.table_name = t.table_name "
  303. "AND tc.constraint_type = 'PRIMARY KEY' "
  304. "LEFT JOIN INFORMATION_SCHEMA.KEY_COLUMN_USAGE kcu "
  305. "ON kcu.table_catalog = tc.table_catalog "
  306. "AND kcu.table_schema = tc.table_schema "
  307. "AND kcu.table_name = tc.table_name "
  308. "AND kcu.constraint_name = tc.constraint_name "
  309. "WHERE t.table_schema = '%s' AND t.table_name = '%s'",
  310. pg_info->schema_name, pg_info->table_name);
  311. G_debug(2, "SQL: %s", stmt);
  312. res = PQexec(pg_info->conn, stmt);
  313. if (!res || PQresultStatus(res) != PGRES_TUPLES_OK ||
  314. PQntuples(res) != 1 || strlen(PQgetvalue(res, 0, 0)) < 1) {
  315. G_warning(_("No key column detected."));
  316. if (res)
  317. PQclear(res);
  318. return NULL;
  319. }
  320. key_column = G_store(PQgetvalue(res, 0, 0));
  321. PQclear(res);
  322. return key_column;
  323. }
  324. /*!
  325. \brief Get simple feature type from string
  326. \param type string
  327. \return SF type
  328. */
  329. SF_FeatureType ftype_from_string(const char *type)
  330. {
  331. SF_FeatureType sf_type;
  332. if (G_strcasecmp(type, "POINT") == 0)
  333. return SF_POINT;
  334. else if (G_strcasecmp(type, "LINESTRING") == 0)
  335. return SF_LINESTRING;
  336. else if (G_strcasecmp(type, "POLYGON") == 0)
  337. return SF_POLYGON;
  338. else if (G_strcasecmp(type, "MULTIPOINT") == 0)
  339. return SF_MULTIPOINT;
  340. else if (G_strcasecmp(type, "MULTILINESTRING") == 0)
  341. return SF_MULTILINESTRING;
  342. else if (G_strcasecmp(type, "MULTIPOLYGON") == 0)
  343. return SF_MULTIPOLYGON;
  344. else if (G_strcasecmp(type, "GEOMETRYCOLLECTION") == 0)
  345. return SF_GEOMETRYCOLLECTION;
  346. else
  347. return SF_UNKNOWN;
  348. G_debug(3, "ftype_from_string(): type='%s' -> %d", type, sf_type);
  349. return sf_type;
  350. }
  351. /*!
  352. \brief Drop feature table and topology schema if exists
  353. \param pg_info pointer to Format_info_pg
  354. \return -1 on error
  355. \return 0 on success
  356. */
  357. int drop_table(struct Format_info_pg *pg_info)
  358. {
  359. int i;
  360. char stmt[DB_SQL_MAX];
  361. char *topo_schema;
  362. PGresult *result, *result_drop;
  363. /* drop topology schema(s) related to the feature table */
  364. sprintf(stmt, "SELECT t.name FROM topology.layer AS l JOIN "
  365. "topology.topology AS t ON l.topology_id = t.id "
  366. "WHERE l.table_name = '%s'", pg_info->table_name);
  367. G_debug(2, "SQL: %s", stmt);
  368. result = PQexec(pg_info->conn, stmt);
  369. if (!result || PQresultStatus(result) != PGRES_TUPLES_OK) {
  370. G_warning(_("Execution failed: %s"), PQerrorMessage(pg_info->conn));
  371. PQclear(result);
  372. return -1;
  373. }
  374. for (i = 0; i < PQntuples(result); i++) {
  375. topo_schema = PQgetvalue(result, i, 0);
  376. sprintf(stmt, "SELECT topology.DropTopology('%s')",
  377. topo_schema);
  378. G_debug(2, "SQL: %s", stmt);
  379. result_drop = PQexec(pg_info->conn, stmt);
  380. if (!result_drop || PQresultStatus(result_drop) != PGRES_TUPLES_OK)
  381. G_warning(_("Execution failed: %s"), PQerrorMessage(pg_info->conn));
  382. G_verbose_message(_("PostGIS topology schema <%s> dropped"),
  383. topo_schema);
  384. PQclear(result_drop);
  385. }
  386. PQclear(result);
  387. /* drop feature table */
  388. sprintf(stmt, "DROP TABLE \"%s\".\"%s\"",
  389. pg_info->schema_name, pg_info->table_name);
  390. G_debug(2, "SQL: %s", stmt);
  391. if (Vect__execute_pg(pg_info->conn, stmt) == -1) {
  392. return -1;
  393. }
  394. return 0;
  395. }
  396. /*!
  397. \brief Establish PG connection (pg_info->conninfo)
  398. Check if DB is spatial as defined by PostGIS.
  399. \param pg_info pointer to Format_info_pg
  400. */
  401. void connect_db(struct Format_info_pg *pg_info)
  402. {
  403. char stmt[DB_SQL_MAX];
  404. pg_info->conn = PQconnectdb(pg_info->conninfo);
  405. G_debug(2, " PQconnectdb(): %s", pg_info->conninfo);
  406. if (PQstatus(pg_info->conn) == CONNECTION_BAD)
  407. G_fatal_error("%s\n%s",
  408. _("Connection to PostgreSQL database failed."),
  409. PQerrorMessage(pg_info->conn));
  410. sprintf(stmt, "SELECT COUNT(*) FROM pg_tables WHERE tablename = 'spatial_ref_sys'");
  411. if (Vect__execute_get_value_pg(pg_info->conn, stmt) != 1) {
  412. PQfinish(pg_info->conn);
  413. G_fatal_error(_("Spatial-enabled PostGIS database is required"));
  414. }
  415. /* print notice messages only on verbose level */
  416. PQsetNoticeProcessor(pg_info->conn, notice_processor, NULL);
  417. }
  418. /*!
  419. \brief Check for topology schema (pg_info->toposchema_name)
  420. \param pg_info pointer to Format_info_pg
  421. \return 0 schema exists
  422. \return 1 schema doesn't exists
  423. */
  424. int check_topo(struct Format_info_pg *pg_info, struct Plus_head *plus)
  425. {
  426. char stmt[DB_SQL_MAX];
  427. PGresult *res;
  428. /* connect database */
  429. if (!pg_info->conn)
  430. connect_db(pg_info);
  431. if (pg_info->toposchema_name)
  432. return 0;
  433. /* check if topology layer/schema exists */
  434. sprintf(stmt,
  435. "SELECT t.id,t.name,t.hasz,l.feature_column FROM topology.layer "
  436. "AS l JOIN topology.topology AS t ON l.topology_id = t.id "
  437. "WHERE schema_name = '%s' AND table_name = '%s'",
  438. pg_info->schema_name, pg_info->table_name);
  439. G_debug(2, "SQL: %s", stmt);
  440. res = PQexec(pg_info->conn, stmt);
  441. if (!res || PQresultStatus(res) != PGRES_TUPLES_OK ||
  442. PQntuples(res) != 1) {
  443. G_debug(1, "Topology layers for '%s.%s' not found (%s)",
  444. pg_info->schema_name, pg_info->table_name,
  445. PQerrorMessage(pg_info->conn));
  446. if (res)
  447. PQclear(res);
  448. return 1;
  449. }
  450. pg_info->toposchema_id = atoi(PQgetvalue(res, 0, 0));
  451. pg_info->toposchema_name = G_store(PQgetvalue(res, 0, 1));
  452. pg_info->topogeom_column = G_store(PQgetvalue(res, 0, 3));
  453. /* check extra GRASS tables */
  454. sprintf(stmt, "SELECT COUNT(*) FROM pg_tables WHERE schemaname = '%s' "
  455. "AND tablename LIKE '%%_grass'", pg_info->toposchema_name);
  456. if (Vect__execute_get_value_pg(pg_info->conn, stmt) != TOPO_TABLE_NUM)
  457. pg_info->topo_geo_only = TRUE;
  458. G_debug(1, "PostGIS topology detected: schema = %s column = %s topo_geo_only = %d",
  459. pg_info->toposchema_name, pg_info->topogeom_column, pg_info->topo_geo_only);
  460. /* check for 3D */
  461. if (strcmp(PQgetvalue(res, 0, 2), "t") == 0)
  462. plus->with_z = WITH_Z;
  463. PQclear(res);
  464. return 0;
  465. }
  466. /*!
  467. \brief Parse BBOX string
  468. \param value string buffer
  469. \param[out] bbox pointer to output bound_box struct
  470. \return 0 on success
  471. \return -1 on error
  472. */
  473. int parse_bbox(const char *value, struct bound_box *bbox)
  474. {
  475. unsigned int i;
  476. size_t length, prefix_length;
  477. char **tokens, **tokens_coord, *coord;
  478. if (strlen(value) < 1) {
  479. G_warning(_("Empty bounding box"));
  480. return -1;
  481. }
  482. prefix_length = strlen("box3d(");
  483. if (G_strncasecmp(value, "box3d(", prefix_length) != 0)
  484. return -1;
  485. /* strip off "bbox3d(...)" */
  486. length = strlen(value);
  487. coord = G_malloc(length - prefix_length);
  488. for (i = prefix_length; i < length; i++)
  489. coord[i-prefix_length] = value[i];
  490. coord[length-prefix_length-1] = '\0';
  491. tokens = G_tokenize(coord, ",");
  492. G_free(coord);
  493. if (G_number_of_tokens(tokens) != 2) {
  494. G_free_tokens(tokens);
  495. return -1;
  496. }
  497. /* parse bbox LL corner */
  498. tokens_coord = G_tokenize(tokens[0], " ");
  499. if (G_number_of_tokens(tokens_coord) != 3) {
  500. G_free_tokens(tokens);
  501. G_free_tokens(tokens_coord);
  502. }
  503. bbox->W = atof(tokens_coord[0]);
  504. bbox->S = atof(tokens_coord[1]);
  505. bbox->B = atof(tokens_coord[2]);
  506. G_free_tokens(tokens_coord);
  507. /* parse bbox UR corner */
  508. tokens_coord = G_tokenize(tokens[1], " ");
  509. if (G_number_of_tokens(tokens_coord) != 3) {
  510. G_free_tokens(tokens);
  511. G_free_tokens(tokens_coord);
  512. }
  513. bbox->E = atof(tokens_coord[0]);
  514. bbox->N = atof(tokens_coord[1]);
  515. bbox->T = atof(tokens_coord[2]);
  516. G_free_tokens(tokens_coord);
  517. G_free_tokens(tokens);
  518. return 0;
  519. }
  520. /*!
  521. \brief Read P_node structure
  522. See dig_Rd_P_node() for reference.
  523. \param plus pointer to Plus_head structure
  524. \param n index (starts at 1)
  525. \param id node id (table "node")
  526. \param wkb_data geometry data (wkb)
  527. \param lines_data lines array or NULL
  528. \param angles_data angles array or NULL
  529. \param pg_info pointer to Format_info_pg sttucture
  530. \return pointer to new P_node struct
  531. \return NULL on error
  532. */
  533. struct P_node *read_p_node(struct Plus_head *plus, int n,
  534. int id, const char *wkb_data, const char *lines_data,
  535. const char *angles_data, struct Format_info_pg *pg_info)
  536. {
  537. int i, cnt;
  538. char **lines, **angles;
  539. struct P_node *node;
  540. struct line_pnts *points;
  541. PGresult *res;
  542. /* get lines connected to the node */
  543. lines = angles = NULL;
  544. if (!lines_data && !angles_data) { /* pg_info->topo_geo_only == TRUE */
  545. char stmt[DB_SQL_MAX];
  546. sprintf(stmt,
  547. "SELECT edge_id,'s' as node,"
  548. "ST_Azimuth(ST_StartPoint(geom), ST_PointN(geom, 2)) AS angle"
  549. " FROM \"%s\".edge WHERE start_node = %d UNION ALL "
  550. "SELECT edge_id,'e' as node,"
  551. "ST_Azimuth(ST_EndPoint(geom), ST_PointN(geom, ST_NumPoints(geom) - 1)) AS angle"
  552. " FROM \"%s\".edge WHERE end_node = %d"
  553. " ORDER BY angle DESC",
  554. pg_info->toposchema_name, id,
  555. pg_info->toposchema_name, id);
  556. G_debug(2, "SQL: %s", stmt);
  557. res = PQexec(pg_info->conn, stmt);
  558. if (!res || PQresultStatus(res) != PGRES_TUPLES_OK) {
  559. G_warning(_("Inconsistency in topology: unable to read node %d"), id);
  560. if (res)
  561. PQclear(res);
  562. return NULL;
  563. }
  564. cnt = PQntuples(res);
  565. }
  566. else { /* pg_info->topo_geo_only != TRUE */
  567. lines = scan_array(lines_data);
  568. angles = scan_array(angles_data);
  569. cnt = G_number_of_tokens(lines);
  570. if (cnt != G_number_of_tokens(angles))
  571. return NULL; /* 'lines' and 'angles' array must have the same size */
  572. }
  573. if (cnt == 0) { /* dead */
  574. plus->Node[n] = NULL;
  575. return NULL;
  576. }
  577. node = dig_alloc_node();
  578. node->n_lines = cnt;
  579. G_debug(4, "read_p_node(): id = %d, n_lines = %d", id, cnt);
  580. if (dig_node_alloc_line(node, node->n_lines) == -1)
  581. return NULL;
  582. /* lines / angles */
  583. if (lines) {
  584. for (i = 0; i < node->n_lines; i++) {
  585. node->lines[i] = atoi(lines[i]);
  586. node->angles[i] = atof(angles[i]);
  587. G_debug(5, "\tline = %d angle = %f", node->lines[i],
  588. node->angles[i]);
  589. }
  590. G_free_tokens(lines);
  591. G_free_tokens(angles);
  592. }
  593. else {
  594. for (i = 0; i < node->n_lines; i++) {
  595. node->lines[i] = atoi(PQgetvalue(res, i, 0));
  596. if (strcmp(PQgetvalue(res, i, 1), "s") != 0) {
  597. /* end node */
  598. node->lines[i] *= -1;
  599. }
  600. node->angles[i] = M_PI / 2 - atof(PQgetvalue(res, i, 2));
  601. /* angles range <-PI; PI> */
  602. if (node->angles[i] > M_PI)
  603. node->angles[i] = node->angles[i] - 2 * M_PI;
  604. if (node->angles[i] < -1.0 * M_PI)
  605. node->angles[i] = node->angles[i] + 2 * M_PI;
  606. G_debug(5, "\tline = %d angle = %f", node->lines[i],
  607. node->angles[i]);
  608. }
  609. PQclear(res);
  610. }
  611. /* get node coordinates */
  612. if (SF_POINT != Vect__cache_feature_pg(wkb_data, FALSE, FALSE,
  613. &(pg_info->cache), NULL))
  614. G_warning(_("Inconsistency in topology: node %d - unexpected feature type %d"),
  615. n, pg_info->cache.sf_type);
  616. points = pg_info->cache.lines[0];
  617. node->x = points->x[0];
  618. node->y = points->y[0];
  619. if (plus->with_z)
  620. node->z = points->z[0];
  621. else
  622. node->z = 0.0;
  623. /* update spatial index */
  624. dig_spidx_add_node(plus, n, node->x, node->y, node->z);
  625. if (plus->uplist.do_uplist)
  626. /* collect updated nodes if requested */
  627. dig_node_add_updated(plus, n);
  628. plus->Node[n] = node;
  629. return node;
  630. }
  631. /*!
  632. \brief Read P_line structure
  633. See dig_Rd_P_line() for reference.
  634. Supported feature types:
  635. - GV_POINT
  636. - GV_LINE
  637. - GV_BOUNDARY
  638. \param plus pointer to Plus_head structure
  639. \param n index (starts at 1)
  640. \param data edge data (id, start/end node, left/right face, ...)
  641. \param pg_info pointer to Format_info_pg sttucture
  642. \return pointer to P_line struct
  643. \return NULL on error
  644. */
  645. struct P_line *read_p_line(struct Plus_head *plus, int n,
  646. const struct edge_data *data,
  647. struct Format_info_cache *cache)
  648. {
  649. int tp, itype;
  650. struct P_line *line;
  651. struct line_pnts *points;
  652. struct bound_box box;
  653. if (data->start_node == 0 && data->end_node == 0) {
  654. if (data->left_face == 0)
  655. tp = GV_POINT;
  656. else
  657. tp = GV_CENTROID;
  658. }
  659. else if (data->left_face == 0 && data->right_face == 0) {
  660. tp = GV_LINE;
  661. }
  662. else {
  663. tp = GV_BOUNDARY;
  664. }
  665. if (tp == 0) { /* dead ??? */
  666. plus->Line[n] = NULL;
  667. return NULL;
  668. }
  669. line = dig_alloc_line();
  670. /* type & offset ( = id) */
  671. line->type = tp;
  672. line->offset = data->id;
  673. G_debug(4, "read_p_line(): id/offset = %d type = %d", data->id, line->type);
  674. /* topo */
  675. if (line->type == GV_POINT) {
  676. line->topo = NULL;
  677. }
  678. else {
  679. line->topo = dig_alloc_topo(line->type);
  680. if ((line->type & GV_LINES) & (data->start_node < 0 || data->end_node < 0))
  681. return NULL;
  682. /* lines */
  683. if (line->type == GV_LINE) {
  684. struct P_topo_l *topo = (struct P_topo_l *)line->topo;
  685. topo->N1 = data->start_node;
  686. topo->N2 = data->end_node;
  687. }
  688. /* boundaries */
  689. else if (line->type == GV_BOUNDARY) {
  690. struct P_topo_b *topo = (struct P_topo_b *)line->topo;
  691. topo->N1 = data->start_node;
  692. topo->N2 = data->end_node;
  693. /* skip left/right area - will be detected when building
  694. areas/isles */
  695. topo->left = topo->right = 0;
  696. }
  697. /* centroids */
  698. else if (line->type == GV_CENTROID) {
  699. struct P_topo_c *topo = (struct P_topo_c *)line->topo;
  700. topo->area = data->left_face;
  701. }
  702. }
  703. /* update spatial index */
  704. Vect__cache_feature_pg(data->wkb_geom, FALSE, FALSE, cache, NULL);
  705. itype = cache->lines_types[0];
  706. if ((line->type & GV_POINTS && itype != GV_POINT) ||
  707. (line->type & GV_LINES && itype != GV_LINE))
  708. G_warning(_("Inconsistency in topology: line %d - unexpected feature type"), n);
  709. points = cache->lines[0];
  710. dig_line_box(points, &box);
  711. dig_spidx_add_line(plus, n, &box);
  712. if (plus->uplist.do_uplist) {
  713. /* collect updated lines if requested */
  714. dig_line_add_updated(plus, n);
  715. plus->uplist.uplines_offset[plus->uplist.n_uplines - 1] = line->offset;
  716. }
  717. plus->Line[n] = line;
  718. return line;
  719. }
  720. /*!
  721. \brief Read P_area structure
  722. \param plus pointer to Plus_head structure
  723. \param n index (starts at 1)
  724. \param lines_data lines array (see P_area struct)
  725. \param centroid centroid id (see P_area struct)
  726. \param isles_data lines array (see P_area struct)
  727. \return pointer to P_area struct
  728. \return NULL on error
  729. */
  730. struct P_area *read_p_area(struct Plus_head *plus, int n,
  731. const char *lines_data, int centroid, const char *isles_data)
  732. {
  733. int i;
  734. int nlines, nisles;
  735. char **lines, **isles;
  736. struct P_area *area;
  737. lines = scan_array(lines_data);
  738. nlines = G_number_of_tokens(lines);
  739. isles = scan_array(isles_data);
  740. nisles = G_number_of_tokens(isles);
  741. if (nlines < 1) {
  742. G_warning(_("Area %d without boundary detected"), n);
  743. return NULL;
  744. }
  745. G_debug(3, "read_p_area(): n = %d nlines = %d nisles = %d", n, nlines, nisles);
  746. /* allocate area */
  747. area = dig_alloc_area();
  748. dig_area_alloc_line(area, nlines);
  749. dig_area_alloc_isle(area, nisles);
  750. /* set lines */
  751. area->n_lines = nlines;
  752. for (i = 0; i < nlines; i++) {
  753. area->lines[i] = atoi(lines[i]);
  754. }
  755. /* set isles */
  756. area->n_isles = nisles;
  757. for (i = 0; i < nisles; i++) {
  758. area->isles[i] = atoi(isles[i]);
  759. }
  760. /* set centroid */
  761. area->centroid = remap_line(plus, centroid);
  762. G_free_tokens(lines);
  763. G_free_tokens(isles);
  764. plus->Area[n] = area;
  765. return area;
  766. }
  767. /*!
  768. \brief Read P_isle structure
  769. \param plus pointer to Plus_head structure
  770. \param n index (starts at 1)
  771. \param lines_data lines array (see P_isle struct)
  772. \param area area id (see P_isle struct)
  773. \return pointer to P_isle struct
  774. \return NULL on error
  775. */
  776. struct P_isle *read_p_isle(struct Plus_head *plus, int n,
  777. const char *lines_data, int area)
  778. {
  779. int i;
  780. int nlines;
  781. char **lines;
  782. struct P_isle *isle;
  783. lines = scan_array(lines_data);
  784. nlines = G_number_of_tokens(lines);
  785. if (nlines < 1) {
  786. G_warning(_("Isle %d without boundary detected"), n);
  787. return NULL;
  788. }
  789. G_debug(3, "read_p_isle(): n = %d nlines = %d", n, nlines);
  790. /* allocate isle */
  791. isle = dig_alloc_isle();
  792. dig_isle_alloc_line(isle, nlines);
  793. /* set lines */
  794. isle->n_lines = nlines;
  795. for (i = 0; i < nlines; i++) {
  796. isle->lines[i] = atoi(lines[i]);
  797. }
  798. /* set area */
  799. isle->area = area;
  800. G_free_tokens(lines);
  801. plus->Isle[n] = isle;
  802. return isle;
  803. }
  804. /*!
  805. \brief Read topo from PostGIS topology schema -- header info only
  806. \param[in,out] plus pointer to Plus_head struct
  807. \return 0 on success
  808. \return -1 on error
  809. */
  810. int load_plus_head(struct Format_info_pg *pg_info, struct Plus_head *plus)
  811. {
  812. char stmt[DB_SQL_MAX];
  813. PGresult *res;
  814. plus->off_t_size = -1;
  815. /* get map bounding box
  816. fisrt try to get info from 'topology.grass' table */
  817. sprintf(stmt,
  818. "SELECT %s FROM \"%s\".\"%s\" WHERE %s = %d",
  819. TOPO_BBOX, TOPO_SCHEMA, TOPO_TABLE, TOPO_ID, pg_info->toposchema_id);
  820. G_debug(2, "SQL: %s", stmt);
  821. res = PQexec(pg_info->conn, stmt);
  822. if (!res || PQresultStatus(res) != PGRES_TUPLES_OK ||
  823. PQntuples(res) != 1) {
  824. PQclear(res);
  825. /* otherwise try to calculate bbox from TopoGeometry elements */
  826. sprintf(stmt,
  827. "SELECT ST_3DExtent(%s) FROM \"%s\".\"%s\"",
  828. pg_info->topogeom_column, pg_info->schema_name, pg_info->table_name);
  829. G_debug(2, "SQL: %s", stmt);
  830. res = PQexec(pg_info->conn, stmt);
  831. if (!res || PQresultStatus(res) != PGRES_TUPLES_OK ||
  832. PQntuples(res) != 1 || strlen(PQgetvalue(res, 0, 0)) < 1) {
  833. G_warning(_("Unable to get map bounding box from topology"));
  834. PQclear(res);
  835. return -1;
  836. }
  837. }
  838. if (parse_bbox(PQgetvalue(res, 0, 0), &(plus->box)) != 0) {
  839. G_warning(_("Unable to parse map bounding box:\n%s"),
  840. PQgetvalue(res, 0, 0));
  841. return -1;
  842. }
  843. PQclear(res);
  844. /* get number of topological elements */
  845. /* nodes
  846. note: isolated nodes are registered in GRASS Topology model */
  847. sprintf(stmt,
  848. "SELECT COUNT(DISTINCT node) FROM (SELECT start_node AS node "
  849. "FROM \"%s\".edge GROUP BY start_node UNION ALL SELECT end_node "
  850. "AS node FROM \"%s\".edge GROUP BY end_node) AS foo",
  851. pg_info->toposchema_name, pg_info->toposchema_name);
  852. plus->n_nodes = Vect__execute_get_value_pg(pg_info->conn, stmt);
  853. if (!pg_info->topo_geo_only) {
  854. int n_nodes;
  855. /* check nodes consistency */
  856. sprintf(stmt, "SELECT COUNT(*) FROM \"%s\".%s",
  857. pg_info->toposchema_name, TOPO_TABLE_NODE);
  858. n_nodes = Vect__execute_get_value_pg(pg_info->conn, stmt);
  859. if (n_nodes != plus->n_nodes) {
  860. G_warning(_("Different number of nodes detected (%d, %d)"),
  861. plus->n_nodes, n_nodes);
  862. return -1;
  863. }
  864. }
  865. G_debug(3, "Vect_open_topo_pg(): n_nodes=%d", plus->n_nodes);
  866. /* lines (edges in PostGIS Topology model) */
  867. sprintf(stmt,
  868. "SELECT COUNT(*) FROM \"%s\".edge",
  869. pg_info->toposchema_name);
  870. /* + isolated nodes as points
  871. + centroids */
  872. plus->n_lines = Vect__execute_get_value_pg(pg_info->conn, stmt);
  873. /* areas (faces with face_id > 0 in PostGIS Topology model) */
  874. sprintf(stmt,
  875. "SELECT COUNT(*) FROM \"%s\".face WHERE face_id > 0",
  876. pg_info->toposchema_name);
  877. plus->n_areas = Vect__execute_get_value_pg(pg_info->conn, stmt);
  878. if (!pg_info->topo_geo_only) {
  879. int n_areas;
  880. /* check areas consistency */
  881. sprintf(stmt, "SELECT COUNT(*) FROM \"%s\".%s",
  882. pg_info->toposchema_name, TOPO_TABLE_AREA);
  883. n_areas = Vect__execute_get_value_pg(pg_info->conn, stmt);
  884. if (n_areas != plus->n_areas) {
  885. G_warning(_("Different number of areas detected (%d, %d)"),
  886. plus->n_areas, n_areas);
  887. return -1;
  888. }
  889. }
  890. G_debug(3, "Vect_open_topo_pg(): n_areas=%d", plus->n_areas);
  891. /* isles (faces with face_id <=0 in PostGIS Topology model)
  892. note: universal face is represented in GRASS Topology model as isle (area=0)
  893. */
  894. sprintf(stmt,
  895. "SELECT COUNT(*) FROM \"%s\".face WHERE face_id < 0",
  896. pg_info->toposchema_name);
  897. plus->n_isles = Vect__execute_get_value_pg(pg_info->conn, stmt);
  898. if (!pg_info->topo_geo_only) {
  899. int n_isles;
  900. /* check areas consistency */
  901. sprintf(stmt, "SELECT COUNT(*) FROM \"%s\".%s",
  902. pg_info->toposchema_name, TOPO_TABLE_ISLE);
  903. n_isles = Vect__execute_get_value_pg(pg_info->conn, stmt);
  904. if (n_isles != plus->n_isles) {
  905. G_warning(_("Different number of areas detected (%d, %d)"),
  906. plus->n_isles, n_isles);
  907. return -1;
  908. }
  909. }
  910. G_debug(3, "Vect_open_topo_pg(): n_isles=%d", plus->n_isles);
  911. /* number of features according the type */
  912. /* points */
  913. sprintf(stmt,
  914. "SELECT COUNT(*) FROM \"%s\".node WHERE containing_face "
  915. "IS NULL AND node_id NOT IN "
  916. "(SELECT node FROM (SELECT start_node AS node FROM \"%s\".edge "
  917. "GROUP BY start_node UNION ALL SELECT end_node AS node FROM "
  918. "\"%s\".edge GROUP BY end_node) AS foo)",
  919. pg_info->toposchema_name, pg_info->toposchema_name,
  920. pg_info->toposchema_name);
  921. plus->n_plines = Vect__execute_get_value_pg(pg_info->conn, stmt);
  922. G_debug(3, "Vect_open_topo_pg(): n_plines=%d", plus->n_plines);
  923. /* lines */
  924. sprintf(stmt,
  925. "SELECT COUNT(*) FROM \"%s\".edge WHERE "
  926. "left_face = 0 AND right_face = 0",
  927. pg_info->toposchema_name);
  928. plus->n_llines = Vect__execute_get_value_pg(pg_info->conn, stmt);
  929. G_debug(3, "Vect_open_topo_pg(): n_llines=%d", plus->n_llines);
  930. /* boundaries */
  931. sprintf(stmt,
  932. "SELECT COUNT(*) FROM \"%s\".edge WHERE "
  933. "left_face != 0 OR right_face != 0",
  934. pg_info->toposchema_name);
  935. plus->n_blines = Vect__execute_get_value_pg(pg_info->conn, stmt);
  936. G_debug(3, "Vect_open_topo_pg(): n_blines=%d", plus->n_blines);
  937. /* centroids */
  938. sprintf(stmt,
  939. "SELECT COUNT(*) FROM \"%s\".node WHERE containing_face "
  940. "IS NOT NULL AND node_id NOT IN "
  941. "(SELECT node FROM (SELECT start_node AS node FROM \"%s\".edge "
  942. "GROUP BY start_node UNION ALL SELECT end_node AS node FROM "
  943. "\"%s\".edge GROUP BY end_node) AS foo)",
  944. pg_info->toposchema_name, pg_info->toposchema_name,
  945. pg_info->toposchema_name);
  946. plus->n_clines = Vect__execute_get_value_pg(pg_info->conn, stmt);
  947. G_debug(3, "Vect_open_topo_pg(): n_clines=%d", plus->n_clines);
  948. /* update number of lines - add points and centroids */
  949. plus->n_lines += plus->n_plines + plus->n_clines;
  950. G_debug(3, "Vect_open_topo_pg(): n_lines=%d", plus->n_lines);
  951. return 0;
  952. }
  953. /*!
  954. \brief Read topo info from PostGIS topology schema
  955. \param pg_info pointer to Format_info_pg
  956. \param[in,out] plus pointer to Plus_head struct
  957. \param head_only TRUE to read only header info
  958. \return 0 on success
  959. \return -1 on error
  960. */
  961. int Vect__load_plus_pg(struct Map_info *Map, int head_only)
  962. {
  963. int i, side, line, id, ntuples;
  964. char stmt[DB_SQL_MAX];
  965. struct edge_data line_data;
  966. struct Format_info_pg *pg_info;
  967. struct Format_info_offset *offset;
  968. struct Plus_head *plus;
  969. struct P_line *Line;
  970. struct line_pnts *Points;
  971. struct ilist *List;
  972. PGresult *res;
  973. pg_info = &(Map->fInfo.pg);
  974. plus = &(Map->plus);
  975. offset = &(pg_info->offset);
  976. if (load_plus_head(pg_info, plus) != 0)
  977. return -1;
  978. if (head_only)
  979. return 0;
  980. Points = Vect_new_line_struct();
  981. List = Vect_new_list();
  982. /* read nodes (GRASS Topo)
  983. note: standalone nodes (ie. points/centroids) are ignored
  984. */
  985. if (pg_info->topo_geo_only)
  986. sprintf(stmt,
  987. "SELECT node_id,geom FROM \"%s\".node WHERE node_id IN "
  988. "(SELECT node FROM (SELECT start_node AS node FROM \"%s\".edge "
  989. "GROUP BY start_node UNION ALL SELECT end_node AS node FROM "
  990. "\"%s\".edge GROUP BY end_node) AS foo) ORDER BY node_id",
  991. pg_info->toposchema_name, pg_info->toposchema_name,
  992. pg_info->toposchema_name);
  993. else
  994. sprintf(stmt, "SELECT node.node_id,geom,lines,angles FROM \"%s\".node AS node "
  995. "join \"%s\".%s AS node_grass ON node.node_id = node_grass.node_id "
  996. "ORDER BY node_id", pg_info->toposchema_name, pg_info->toposchema_name,
  997. TOPO_TABLE_NODE);
  998. G_debug(2, "SQL: %s", stmt);
  999. res = PQexec(pg_info->conn, stmt);
  1000. if (!res || PQresultStatus(res) != PGRES_TUPLES_OK ||
  1001. PQntuples(res) != plus->n_nodes) {
  1002. G_warning(_("Inconsistency in topology: number of "
  1003. "nodes %d (should be %d)"),
  1004. PQntuples(res), plus->n_nodes);
  1005. if (res)
  1006. PQclear(res);
  1007. return -1;
  1008. }
  1009. G_debug(3, "load_plus(): n_nodes = %d", plus->n_nodes);
  1010. dig_alloc_nodes(plus, plus->n_nodes);
  1011. offset->array = (int *) G_malloc (sizeof(int) * plus->n_nodes);
  1012. offset->array_num = offset->array_alloc = plus->n_nodes;
  1013. for (i = 0; i < plus->n_nodes; i++) {
  1014. G_debug(5, "node: %d", i);
  1015. id = atoi(PQgetvalue(res, i, 0));
  1016. read_p_node(plus, i + 1, /* node index starts at 1 */
  1017. id, (const char *) PQgetvalue(res, i, 1),
  1018. !pg_info->topo_geo_only ? (const char *) PQgetvalue(res, i, 2) : NULL,
  1019. !pg_info->topo_geo_only ? (const char *) PQgetvalue(res, i, 3) : NULL,
  1020. pg_info);
  1021. /* update offset */
  1022. offset->array[i] = id;
  1023. }
  1024. PQclear(res);
  1025. /* read lines (GRASS Topo)
  1026. - standalone nodes -> points|centroids
  1027. - edges -> lines/boundaries
  1028. */
  1029. G_debug(3, "load_plus(): n_lines = %d", plus->n_lines);
  1030. dig_alloc_lines(plus, plus->n_lines);
  1031. G_zero(plus->Line, sizeof(struct P_line *) * (plus->n_lines + 1)); /* index starts at 1 */
  1032. /* read PostGIS Topo standalone nodes (containing_face is null)
  1033. -> points
  1034. */
  1035. if (pg_info->topo_geo_only)
  1036. sprintf(stmt,
  1037. "SELECT node_id,geom FROM \"%s\".node WHERE containing_face "
  1038. "IS NULL AND node_id NOT IN "
  1039. "(SELECT node FROM (SELECT start_node AS node FROM \"%s\".edge "
  1040. "GROUP BY start_node UNION ALL SELECT end_node AS node FROM "
  1041. "\"%s\".edge GROUP BY end_node) AS foo) ORDER BY node_id",
  1042. pg_info->toposchema_name, pg_info->toposchema_name,
  1043. pg_info->toposchema_name);
  1044. else
  1045. sprintf(stmt,
  1046. "SELECT node.node_id,geom FROM \"%s\".node AS node WHERE node_id NOT IN "
  1047. "(SELECT node_id FROM \"%s\".%s) AND containing_face IS NULL ORDER BY node_id",
  1048. pg_info->toposchema_name, pg_info->toposchema_name, TOPO_TABLE_NODE);
  1049. G_debug(2, "SQL: %s", stmt);
  1050. res = PQexec(pg_info->conn, stmt);
  1051. if (!res || PQresultStatus(res) != PGRES_TUPLES_OK ||
  1052. PQntuples(res) > plus->n_plines) {
  1053. G_warning(_("Inconsistency in topology: number of "
  1054. "points %d (should be %d)"),
  1055. PQntuples(res), plus->n_plines);
  1056. if (res)
  1057. PQclear(res);
  1058. return -1;
  1059. }
  1060. ntuples = PQntuples(res); /* plus->n_plines */
  1061. G_zero(&line_data, sizeof(struct edge_data));
  1062. for (i = 0; i < ntuples; i++) {
  1063. /* process standalone nodes (PostGIS Topo) */
  1064. line_data.id = atoi(PQgetvalue(res, i, 0));
  1065. line_data.wkb_geom = (char *) PQgetvalue(res, i, 1);
  1066. read_p_line(plus, i + 1, &line_data, &(pg_info->cache));
  1067. }
  1068. PQclear(res);
  1069. /* read PostGIS Topo edges
  1070. -> lines
  1071. -> boundaries
  1072. */
  1073. sprintf(stmt,
  1074. "SELECT edge_id,start_node,end_node,left_face,right_face,geom "
  1075. "FROM \"%s\".edge ORDER BY edge_id",
  1076. pg_info->toposchema_name);
  1077. G_debug(2, "SQL: %s", stmt);
  1078. res = PQexec(pg_info->conn, stmt);
  1079. if (!res || PQresultStatus(res) != PGRES_TUPLES_OK ||
  1080. PQntuples(res) > plus->n_lines) {
  1081. G_warning(_("Inconsistency in topology: number of "
  1082. "lines %d (should be %d)"),
  1083. PQntuples(res), plus->n_lines);
  1084. if (res)
  1085. PQclear(res);
  1086. return -1;
  1087. }
  1088. /* process edges (PostGIS Topo) */
  1089. ntuples = PQntuples(res);
  1090. for (i = 0; i < ntuples; i++) {
  1091. line_data.id = atoi(PQgetvalue(res, i, 0));
  1092. line_data.start_node = remap_node(offset, atoi(PQgetvalue(res, i, 1)));
  1093. line_data.end_node = remap_node(offset, atoi(PQgetvalue(res, i, 2)));
  1094. line_data.left_face = atoi(PQgetvalue(res, i, 3));
  1095. line_data.right_face = atoi(PQgetvalue(res, i, 4));
  1096. line_data.wkb_geom = (char *) PQgetvalue(res, i, 5);
  1097. id = plus->n_plines + i + 1; /* points already registered */
  1098. read_p_line(plus, id, &line_data, &(pg_info->cache));
  1099. /* TODO: update category index */
  1100. }
  1101. PQclear(res);
  1102. /* read PostGIS Topo standalone nodes (containing_face is not null)
  1103. -> centroids
  1104. */
  1105. if (pg_info->topo_geo_only)
  1106. sprintf(stmt,
  1107. "SELECT node_id,geom,containing_face FROM \"%s\".node WHERE containing_face "
  1108. "IS NOT NULL AND node_id NOT IN "
  1109. "(SELECT node FROM (SELECT start_node AS node FROM \"%s\".edge "
  1110. "GROUP BY start_node UNION ALL SELECT end_node AS node FROM "
  1111. "\"%s\".edge GROUP BY end_node) AS foo) ORDER BY node_id",
  1112. pg_info->toposchema_name, pg_info->toposchema_name,
  1113. pg_info->toposchema_name);
  1114. else
  1115. sprintf(stmt,
  1116. "SELECT node.node_id,geom,containing_face FROM \"%s\".node AS node WHERE "
  1117. "node_id NOT IN (SELECT node_id FROM \"%s\".%s) AND containing_face "
  1118. "IS NOT NULL ORDER BY node_id",
  1119. pg_info->toposchema_name, pg_info->toposchema_name, TOPO_TABLE_NODE);
  1120. G_debug(2, "SQL: %s", stmt);
  1121. res = PQexec(pg_info->conn, stmt);
  1122. if (!res || PQresultStatus(res) != PGRES_TUPLES_OK ||
  1123. PQntuples(res) != plus->n_clines) {
  1124. G_warning(_("Inconsistency in topology: number of "
  1125. "centroids %d (should be %d)"),
  1126. PQntuples(res), plus->n_clines);
  1127. if (res)
  1128. PQclear(res);
  1129. return -1;
  1130. }
  1131. G_zero(&line_data, sizeof(struct edge_data));
  1132. id = plus->n_plines + plus->n_llines + plus->n_blines + 1;
  1133. for (i = 0; i < plus->n_clines; i++) {
  1134. line_data.id = atoi(PQgetvalue(res, i, 0));
  1135. line_data.wkb_geom = (char *)PQgetvalue(res, i, 1);
  1136. line_data.left_face = atoi(PQgetvalue(res, i, 2)); /* face id */
  1137. /* area id and face id can be different */
  1138. read_p_line(plus, id + i, &line_data, &(pg_info->cache));
  1139. }
  1140. PQclear(res);
  1141. plus->built = GV_BUILD_BASE;
  1142. /* build areas */
  1143. if (pg_info->topo_geo_only) {
  1144. /* build areas for boundaries
  1145. reset values -> build from scratch */
  1146. plus->n_areas = plus->n_isles = 0;
  1147. for (line = 1; line <= plus->n_lines; line++) {
  1148. Line = plus->Line[line]; /* centroids: Line is NULL */
  1149. if (!Line || Line->type != GV_BOUNDARY)
  1150. continue;
  1151. for (i = 0; i < 2; i++) { /* for both sides build an area/isle */
  1152. side = i == 0 ? GV_LEFT : GV_RIGHT;
  1153. G_debug(3, "Build area for line = %d, side = %d",
  1154. id, side);
  1155. Vect_build_line_area(Map, line, side);
  1156. }
  1157. }
  1158. }
  1159. else {
  1160. /* read areas from 'area_grass' table */
  1161. sprintf(stmt,
  1162. "SELECT area_id,lines,centroid,isles FROM \"%s\".%s ORDER BY area_id",
  1163. pg_info->toposchema_name, TOPO_TABLE_AREA);
  1164. G_debug(2, "SQL: %s", stmt);
  1165. res = PQexec(pg_info->conn, stmt);
  1166. if (!res || PQresultStatus(res) != PGRES_TUPLES_OK ||
  1167. plus->n_areas != PQntuples(res)) {
  1168. if (res)
  1169. PQclear(res);
  1170. return -1;
  1171. }
  1172. dig_alloc_areas(plus, plus->n_areas);
  1173. G_zero(plus->Area, sizeof(struct P_area *) * (plus->n_areas + 1)); /* index starts at 1 */
  1174. G_debug(3, "Vect_open_topo_pg(): n_areas=%d", plus->n_areas);
  1175. for (i = 0; i < plus->n_areas; i++) {
  1176. read_p_area(plus, i + 1, (char *)PQgetvalue(res, i, 1),
  1177. atoi(PQgetvalue(res, i, 2)), (char *)PQgetvalue(res, i, 3));
  1178. }
  1179. PQclear(res);
  1180. }
  1181. plus->built = GV_BUILD_AREAS;
  1182. /* attach isles */
  1183. if (pg_info->topo_geo_only) {
  1184. plus->n_isles = 0; /* reset isles */
  1185. G_warning(_("To be implemented: isles not attached in Topo-Geo-only mode"));
  1186. }
  1187. else {
  1188. /* read isles from 'isle_grass' table */
  1189. sprintf(stmt,
  1190. "SELECT isle_id,lines,area FROM \"%s\".%s ORDER BY isle_id",
  1191. pg_info->toposchema_name, TOPO_TABLE_ISLE);
  1192. G_debug(2, "SQL: %s", stmt);
  1193. res = PQexec(pg_info->conn, stmt);
  1194. if (!res || PQresultStatus(res) != PGRES_TUPLES_OK ||
  1195. plus->n_isles != PQntuples(res)) {
  1196. if (res)
  1197. PQclear(res);
  1198. return -1;
  1199. }
  1200. dig_alloc_isles(plus, plus->n_isles);
  1201. G_zero(plus->Isle, sizeof(struct P_isle *) * (plus->n_isles + 1)); /* index starts at 1 */
  1202. G_debug(3, "Vect_open_topo_pg(): n_isles=%d", plus->n_isles);
  1203. for (i = 0; i < plus->n_isles; i++) {
  1204. read_p_isle(plus, i + 1, (char *)PQgetvalue(res, i, 1),
  1205. atoi(PQgetvalue(res, i, 2)));
  1206. }
  1207. PQclear(res);
  1208. }
  1209. plus->built = GV_BUILD_ATTACH_ISLES;
  1210. /* attach centroids */
  1211. if (pg_info->topo_geo_only && plus->n_areas > 0) {
  1212. int area;
  1213. struct P_area *Area;
  1214. struct P_topo_c *topo;
  1215. for (line = 1; line <= plus->n_lines; line++) {
  1216. Line = plus->Line[line];
  1217. if (Line->type != GV_CENTROID)
  1218. continue;
  1219. Vect_read_line(Map, Points, NULL, line);
  1220. area = Vect_find_area(Map, Points->x[0], Points->y[0]);
  1221. topo = (struct P_topo_c *)Line->topo;
  1222. topo->area = area;
  1223. Area = plus->Area[topo->area];
  1224. Area->centroid = Line->offset;
  1225. }
  1226. }
  1227. plus->built = GV_BUILD_CENTROIDS;
  1228. /* done */
  1229. plus->built = GV_BUILD_ALL;
  1230. Vect_destroy_line_struct(Points);
  1231. Vect_destroy_list(List);
  1232. return 0;
  1233. }
  1234. /*
  1235. \brief PostgreSQL notice processor
  1236. Print out NOTICE message only on verbose level
  1237. */
  1238. void notice_processor(void *arg, const char *message)
  1239. {
  1240. if (G_verbose() > G_verbose_std()) {
  1241. fprintf(stderr, "%s", message);
  1242. }
  1243. }
  1244. /*!
  1245. \brief Scan string array
  1246. Creates tokens based on string array, eg. '{1, 2, 3}' become
  1247. [1,2,3].
  1248. Allocated tokes should be freed by G_free_tokens().
  1249. \param sArray string array
  1250. \return tokens
  1251. */
  1252. char **scan_array(const char *sarray)
  1253. {
  1254. char *buf, **tokens;
  1255. int i, len;
  1256. /* remove '{}' */
  1257. len = strlen(sarray) - 1; /* skip '}' */
  1258. buf = (char *)G_malloc(len);
  1259. for (i = 1; i < len; i++)
  1260. buf[i-1] = sarray[i];
  1261. buf[len-1] = '\0';
  1262. tokens = G_tokenize(buf, ",");
  1263. G_free(buf);
  1264. return tokens;
  1265. }
  1266. /*!
  1267. \brief Get node id from offset
  1268. \todo speed up
  1269. \param offset pointer to Format_info_offset struct
  1270. \param node node to find
  1271. \return node id
  1272. \return -1 not found
  1273. */
  1274. int remap_node(const struct Format_info_offset *offset, int node)
  1275. {
  1276. int i;
  1277. for (i = 0; i < offset->array_num; i++) {
  1278. if (offset->array[i] == node)
  1279. return i + 1; /* node id starts at 1 */
  1280. }
  1281. return -1;
  1282. }
  1283. /*!
  1284. \brief Get line id from offset
  1285. \todo speed up
  1286. \param plus pointer to Plus_head struct
  1287. \param line line to find
  1288. \return line id
  1289. \return -1 not found
  1290. */
  1291. int remap_line(const struct Plus_head* plus, int line)
  1292. {
  1293. int i;
  1294. struct P_line *Line;
  1295. for (i = 1; i <= plus->n_lines; i++) {
  1296. Line = plus->Line[i];
  1297. if (!Line)
  1298. continue;
  1299. if ((int) Line->offset == line)
  1300. return i;
  1301. }
  1302. return -1;
  1303. }
  1304. #endif