open_pg.c 56 KB

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