open_pg.c 56 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790
  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_GEOMETRY;
  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. * indices */
  268. dig_init_plus(plus);
  269. plus->Spidx_new = TRUE; /* force building spatial and category indices */
  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_GEOMETRY;
  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, *host, *port;
  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_login2("pg", dbname, &user, &passwd, &host, &port);
  422. /* any settings defined for pg driver disabled - can cause
  423. problems when running multiple local/remote db clusters
  424. if (strlen(dbname) > 0 && !user && !passwd)
  425. db_get_login2("pg", NULL, &user, &passwd, &host, &port);
  426. */
  427. if (user || passwd || host || port) {
  428. char conninfo[DB_SQL_MAX];
  429. sprintf(conninfo, "%s", pg_info->conninfo);
  430. if (user) {
  431. strcat(conninfo, " user=");
  432. strcat(conninfo, user);
  433. }
  434. if (passwd) {
  435. strcat(conninfo, " password=");
  436. strcat(conninfo, passwd);
  437. }
  438. if (host) {
  439. strcat(conninfo, " host=");
  440. strcat(conninfo, host);
  441. }
  442. if (port) {
  443. strcat(conninfo, " port=");
  444. strcat(conninfo, port);
  445. }
  446. G_free(pg_info->conninfo);
  447. pg_info->conninfo = G_store(conninfo);
  448. }
  449. }
  450. pg_info->conn = PQconnectdb(pg_info->conninfo);
  451. G_debug(1, " PQconnectdb(): %s", pg_info->conninfo);
  452. if (PQstatus(pg_info->conn) == CONNECTION_BAD)
  453. G_fatal_error("%s\n%s",
  454. _("Connection to PostgreSQL database failed. "
  455. "Try to set up username/password by db.login."),
  456. PQerrorMessage(pg_info->conn));
  457. /* get DB name */
  458. pg_info->db_name = G_store(PQdb(pg_info->conn));
  459. if (!pg_info->db_name)
  460. G_warning(_("Unable to get database name"));
  461. sprintf(stmt, "SELECT COUNT(*) FROM pg_tables WHERE tablename = 'spatial_ref_sys'");
  462. if (Vect__execute_get_value_pg(pg_info->conn, stmt) != 1) {
  463. PQfinish(pg_info->conn);
  464. G_fatal_error(_("<%s> is not PostGIS database. DB table 'spatial_ref_sys' not found."),
  465. pg_info->db_name ? pg_info->db_name : pg_info->conninfo);
  466. }
  467. if (pg_info->toposchema_name) {
  468. /* check if topology schema exists */
  469. sprintf(stmt, "SELECT COUNT(*) FROM pg_tables WHERE schemaname = 'topology'");
  470. if (Vect__execute_get_value_pg(pg_info->conn, stmt) == 0) {
  471. PQfinish(pg_info->conn);
  472. G_fatal_error(_("PostGIS Topology extension not found in the database <%s>"),
  473. pg_info->db_name);
  474. }
  475. }
  476. /* print notice messages only on verbose level */
  477. PQsetNoticeProcessor(pg_info->conn, notice_processor, NULL);
  478. }
  479. /*!
  480. \brief Check for topology schema (pg_info->toposchema_name)
  481. \param pg_info pointer to Format_info_pg
  482. \return 0 schema exists
  483. \return 1 schema doesn't exists
  484. */
  485. int check_topo(struct Format_info_pg *pg_info, struct Plus_head *plus)
  486. {
  487. char stmt[DB_SQL_MAX];
  488. PGresult *res;
  489. /* connect database */
  490. if (!pg_info->conn)
  491. connect_db(pg_info);
  492. if (pg_info->toposchema_name)
  493. return 0;
  494. /* check if topology layer/schema exists */
  495. sprintf(stmt,
  496. "SELECT t.id,t.name,t.hasz,l.feature_column FROM topology.layer "
  497. "AS l JOIN topology.topology AS t ON l.topology_id = t.id "
  498. "WHERE schema_name = '%s' AND table_name = '%s'",
  499. pg_info->schema_name, pg_info->table_name);
  500. G_debug(2, "SQL: %s", stmt);
  501. res = PQexec(pg_info->conn, stmt);
  502. if (!res || PQresultStatus(res) != PGRES_TUPLES_OK ||
  503. PQntuples(res) != 1) {
  504. G_debug(1, "Topology layers for '%s.%s' not found (%s)",
  505. pg_info->schema_name, pg_info->table_name,
  506. PQerrorMessage(pg_info->conn));
  507. if (res)
  508. PQclear(res);
  509. return 1;
  510. }
  511. pg_info->toposchema_id = atoi(PQgetvalue(res, 0, 0));
  512. pg_info->toposchema_name = G_store(PQgetvalue(res, 0, 1));
  513. pg_info->topogeom_column = G_store(PQgetvalue(res, 0, 3));
  514. /* check extra GRASS tables */
  515. sprintf(stmt, "SELECT COUNT(*) FROM pg_tables WHERE schemaname = '%s' "
  516. "AND tablename LIKE '%%_grass'", pg_info->toposchema_name);
  517. if (Vect__execute_get_value_pg(pg_info->conn, stmt) != TOPO_TABLE_NUM)
  518. pg_info->topo_geo_only = TRUE;
  519. G_debug(1, "PostGIS topology detected: schema = %s column = %s topo_geo_only = %d",
  520. pg_info->toposchema_name, pg_info->topogeom_column, pg_info->topo_geo_only);
  521. /* check for 3D */
  522. if (strcmp(PQgetvalue(res, 0, 2), "t") == 0)
  523. plus->with_z = WITH_Z;
  524. PQclear(res);
  525. return 0;
  526. }
  527. /*!
  528. \brief Parse BBOX string
  529. \param value string buffer
  530. \param[out] bbox pointer to output bound_box struct
  531. \return 0 on success
  532. \return -1 on error
  533. */
  534. int parse_bbox(const char *value, struct bound_box *bbox)
  535. {
  536. unsigned int i;
  537. size_t length, prefix_length;
  538. char **tokens, **tokens_coord, *coord;
  539. if (strlen(value) < 1) {
  540. G_warning(_("Empty bounding box"));
  541. return -1;
  542. }
  543. prefix_length = strlen("box3d(");
  544. if (G_strncasecmp(value, "box3d(", prefix_length) != 0)
  545. return -1;
  546. /* strip off "bbox3d(...)" */
  547. length = strlen(value);
  548. coord = G_malloc(length - prefix_length);
  549. for (i = prefix_length; i < length; i++)
  550. coord[i-prefix_length] = value[i];
  551. coord[length-prefix_length-1] = '\0';
  552. tokens = G_tokenize(coord, ",");
  553. G_free(coord);
  554. if (G_number_of_tokens(tokens) != 2) {
  555. G_free_tokens(tokens);
  556. return -1;
  557. }
  558. /* parse bbox LL corner */
  559. tokens_coord = G_tokenize(tokens[0], " ");
  560. if (G_number_of_tokens(tokens_coord) != 3) {
  561. G_free_tokens(tokens);
  562. G_free_tokens(tokens_coord);
  563. }
  564. bbox->W = atof(tokens_coord[0]);
  565. bbox->S = atof(tokens_coord[1]);
  566. bbox->B = atof(tokens_coord[2]);
  567. G_free_tokens(tokens_coord);
  568. /* parse bbox UR corner */
  569. tokens_coord = G_tokenize(tokens[1], " ");
  570. if (G_number_of_tokens(tokens_coord) != 3) {
  571. G_free_tokens(tokens);
  572. G_free_tokens(tokens_coord);
  573. }
  574. bbox->E = atof(tokens_coord[0]);
  575. bbox->N = atof(tokens_coord[1]);
  576. bbox->T = atof(tokens_coord[2]);
  577. G_free_tokens(tokens_coord);
  578. G_free_tokens(tokens);
  579. return 0;
  580. }
  581. /*!
  582. \brief Read P_node structure
  583. See dig_Rd_P_node() for reference.
  584. \param plus pointer to Plus_head structure
  585. \param n index (starts at 1)
  586. \param id node id (table "node")
  587. \param wkb_data geometry data (wkb)
  588. \param lines_data lines array or NULL
  589. \param angles_data angles array or NULL
  590. \param pg_info pointer to Format_info_pg sttucture
  591. \param geom_only TRUE to read node's geometry
  592. \return pointer to new P_node struct
  593. \return NULL on error
  594. */
  595. struct P_node *read_p_node(struct Plus_head *plus, int n,
  596. int id, const char *wkb_data, const char *lines_data,
  597. const char *angles_data, struct Format_info_pg *pg_info,
  598. int geom_only)
  599. {
  600. int i, cnt;
  601. char **lines, **angles;
  602. struct P_node *node;
  603. struct line_pnts *points;
  604. PGresult *res;
  605. /* get lines connected to the node */
  606. cnt = 0;
  607. lines = angles = NULL;
  608. if (!geom_only) {
  609. if (!lines_data && !angles_data) { /* pg_info->topo_geo_only == TRUE */
  610. char stmt[DB_SQL_MAX];
  611. sprintf(stmt,
  612. "SELECT edge_id,'s' as node,"
  613. "ST_Azimuth(ST_StartPoint(geom), ST_PointN(geom, 2)) AS angle"
  614. " FROM \"%s\".edge WHERE start_node = %d UNION ALL "
  615. "SELECT edge_id,'e' as node,"
  616. "ST_Azimuth(ST_EndPoint(geom), ST_PointN(geom, ST_NumPoints(geom) - 1)) AS angle"
  617. " FROM \"%s\".edge WHERE end_node = %d"
  618. " ORDER BY angle DESC",
  619. pg_info->toposchema_name, id,
  620. pg_info->toposchema_name, id);
  621. G_debug(2, "SQL: %s", stmt);
  622. res = PQexec(pg_info->conn, stmt);
  623. if (!res || PQresultStatus(res) != PGRES_TUPLES_OK) {
  624. G_warning(_("Inconsistency in topology: unable to read node %d"), id);
  625. if (res)
  626. PQclear(res);
  627. return NULL;
  628. }
  629. cnt = PQntuples(res);
  630. }
  631. else { /* pg_info->topo_geo_only != TRUE */
  632. lines = scan_array(lines_data);
  633. angles = scan_array(angles_data);
  634. cnt = G_number_of_tokens(lines);
  635. if (cnt != G_number_of_tokens(angles))
  636. return NULL; /* 'lines' and 'angles' array must have the same size */
  637. }
  638. if (cnt == 0) { /* dead */
  639. plus->Node[n] = NULL;
  640. return NULL;
  641. }
  642. }
  643. node = dig_alloc_node();
  644. node->n_lines = cnt;
  645. G_debug(4, "read_p_node(): id = %d, n_lines = %d", id, cnt);
  646. if (!geom_only) {
  647. if (dig_node_alloc_line(node, node->n_lines) == -1)
  648. return NULL;
  649. /* lines / angles */
  650. if (lines) {
  651. for (i = 0; i < node->n_lines; i++) {
  652. node->lines[i] = atoi(lines[i]);
  653. node->angles[i] = atof(angles[i]);
  654. G_debug(5, "\tline = %d angle = %f", node->lines[i],
  655. node->angles[i]);
  656. }
  657. G_free_tokens(lines);
  658. G_free_tokens(angles);
  659. }
  660. else {
  661. for (i = 0; i < node->n_lines; i++) {
  662. node->lines[i] = atoi(PQgetvalue(res, i, 0));
  663. if (strcmp(PQgetvalue(res, i, 1), "s") != 0) {
  664. /* end node */
  665. node->lines[i] *= -1;
  666. }
  667. node->angles[i] = M_PI / 2 - atof(PQgetvalue(res, i, 2));
  668. /* angles range <-PI; PI> */
  669. if (node->angles[i] > M_PI)
  670. node->angles[i] = node->angles[i] - 2 * M_PI;
  671. if (node->angles[i] < -1.0 * M_PI)
  672. node->angles[i] = node->angles[i] + 2 * M_PI;
  673. G_debug(5, "\tline = %d angle = %f", node->lines[i],
  674. node->angles[i]);
  675. }
  676. PQclear(res);
  677. }
  678. }
  679. /* get node coordinates */
  680. if (SF_POINT != Vect__cache_feature_pg(wkb_data, FALSE, FALSE,
  681. &(pg_info->cache), NULL))
  682. G_warning(_("Inconsistency in topology: node %d - unexpected feature type %d"),
  683. n, pg_info->cache.sf_type);
  684. points = pg_info->cache.lines[0];
  685. node->x = points->x[0];
  686. node->y = points->y[0];
  687. if (plus->with_z)
  688. node->z = points->z[0];
  689. else
  690. node->z = 0.0;
  691. /* update spatial index */
  692. if (plus->Spidx_new)
  693. dig_spidx_add_node(plus, n, node->x, node->y, node->z);
  694. if (plus->uplist.do_uplist)
  695. /* collect updated nodes if requested */
  696. dig_node_add_updated(plus, n);
  697. plus->Node[n] = node;
  698. return node;
  699. }
  700. /*!
  701. \brief Read P_line structure
  702. See dig_Rd_P_line() for reference.
  703. Supported feature types:
  704. - GV_POINT
  705. - GV_LINE
  706. - GV_BOUNDARY
  707. \param plus pointer to Plus_head structure
  708. \param n index (starts at 1)
  709. \param data edge data (id, start/end node, left/right face, ...)
  710. \param topo_geo_only TRUE for topo-geo-only mode
  711. \param cache pointer to Format_info_cache structure
  712. \return pointer to P_line struct
  713. \return NULL on error
  714. */
  715. struct P_line *read_p_line(struct Plus_head *plus, int n,
  716. const struct line_data *data, int topo_geo_only,
  717. struct Format_info_cache *cache)
  718. {
  719. int tp, cat;
  720. struct P_line *line;
  721. if (data->start_node == 0 && data->end_node == 0) {
  722. if (data->left_face == 0)
  723. tp = GV_POINT;
  724. else
  725. tp = GV_CENTROID;
  726. }
  727. else if (data->left_face == 0 && data->right_face == 0) {
  728. tp = GV_LINE;
  729. }
  730. else {
  731. tp = GV_BOUNDARY;
  732. }
  733. if (tp == 0) { /* dead ??? */
  734. plus->Line[n] = NULL;
  735. return NULL;
  736. }
  737. line = dig_alloc_line();
  738. /* type & offset ( = id) */
  739. line->type = tp;
  740. line->offset = data->id;
  741. G_debug(4, "read_p_line(): id/offset = %d type = %d", data->id, line->type);
  742. /* topo */
  743. if (line->type == GV_POINT) {
  744. line->topo = NULL;
  745. }
  746. else {
  747. line->topo = dig_alloc_topo(line->type);
  748. if ((line->type & GV_LINES) & (data->start_node < 0 || data->end_node < 0))
  749. return NULL;
  750. /* lines */
  751. if (line->type == GV_LINE) {
  752. struct P_topo_l *topo = (struct P_topo_l *)line->topo;
  753. topo->N1 = data->start_node;
  754. topo->N2 = data->end_node;
  755. }
  756. /* boundaries */
  757. else if (line->type == GV_BOUNDARY) {
  758. struct P_topo_b *topo = (struct P_topo_b *)line->topo;
  759. topo->N1 = data->start_node;
  760. topo->N2 = data->end_node;
  761. if (topo_geo_only) {
  762. topo->left = topo->right = 0;
  763. }
  764. else {
  765. topo->left = data->left_face;
  766. topo->right = data->right_face;
  767. }
  768. }
  769. /* centroids */
  770. else if (line->type == GV_CENTROID) {
  771. struct P_topo_c *topo = (struct P_topo_c *)line->topo;
  772. topo->area = data->left_face;
  773. }
  774. }
  775. Vect__cache_feature_pg(data->wkb_geom, FALSE, tp, cache, NULL);
  776. cat = cache->lines_cats[cache->lines_num-1] = data->fid > 0 ? data->fid : -1;
  777. /* update spatial index */
  778. if (plus->Spidx_new) {
  779. struct line_pnts *points;
  780. struct bound_box box;
  781. points = cache->lines[cache->lines_num-1];
  782. dig_line_box(points, &box);
  783. dig_spidx_add_line(plus, n, &box);
  784. }
  785. /* update category index */
  786. if (plus->update_cidx)
  787. dig_cidx_add_cat(plus, cat > 0 ? 1 : 0, cat > 0 ? cat : 0, n, tp);
  788. if (plus->uplist.do_uplist) {
  789. /* collect updated lines if requested */
  790. dig_line_add_updated(plus, n, line->offset);
  791. }
  792. plus->Line[n] = line;
  793. return line;
  794. }
  795. /*!
  796. \brief Read P_area structure
  797. \param plus pointer to Plus_head structure
  798. \param n index (starts at 1)
  799. \param lines_data lines array (see P_area struct)
  800. \param centroid centroid id (see P_area struct)
  801. \param isles_data lines array (see P_area struct)
  802. \return pointer to P_area struct
  803. \return NULL on error
  804. */
  805. struct P_area *read_p_area(struct Plus_head *plus, int n,
  806. const char *lines_data, int centroid, const char *isles_data)
  807. {
  808. int i;
  809. int nlines, nisles;
  810. char **lines, **isles;
  811. struct P_area *area;
  812. lines = scan_array(lines_data);
  813. nlines = G_number_of_tokens(lines);
  814. isles = scan_array(isles_data);
  815. nisles = G_number_of_tokens(isles);
  816. if (nlines < 1) {
  817. G_warning(_("Area %d without boundary detected"), n);
  818. return NULL;
  819. }
  820. G_debug(3, "read_p_area(): n = %d nlines = %d nisles = %d", n, nlines, nisles);
  821. /* allocate area */
  822. area = dig_alloc_area();
  823. dig_area_alloc_line(area, nlines);
  824. dig_area_alloc_isle(area, nisles);
  825. /* set lines */
  826. area->n_lines = nlines;
  827. for (i = 0; i < nlines; i++) {
  828. area->lines[i] = atoi(lines[i]);
  829. }
  830. /* set isles */
  831. area->n_isles = nisles;
  832. for (i = 0; i < nisles; i++) {
  833. area->isles[i] = atoi(isles[i]);
  834. }
  835. /* set centroid */
  836. area->centroid = remap_line(plus, centroid, GV_CENTROID);
  837. G_free_tokens(lines);
  838. G_free_tokens(isles);
  839. plus->Area[n] = area;
  840. return area;
  841. }
  842. /*!
  843. \brief Read P_isle structure
  844. \param plus pointer to Plus_head structure
  845. \param n index (starts at 1)
  846. \param lines_data lines array (see P_isle struct)
  847. \param area area id (see P_isle struct)
  848. \return pointer to P_isle struct
  849. \return NULL on error
  850. */
  851. struct P_isle *read_p_isle(struct Plus_head *plus, int n,
  852. const char *lines_data, int area)
  853. {
  854. int i;
  855. int nlines;
  856. char **lines;
  857. struct P_isle *isle;
  858. lines = scan_array(lines_data);
  859. nlines = G_number_of_tokens(lines);
  860. if (nlines < 1) {
  861. G_warning(_("Isle %d without boundary detected"), n);
  862. return NULL;
  863. }
  864. G_debug(3, "read_p_isle(): n = %d nlines = %d", n, nlines);
  865. /* allocate isle */
  866. isle = dig_alloc_isle();
  867. dig_isle_alloc_line(isle, nlines);
  868. /* set lines */
  869. isle->n_lines = nlines;
  870. for (i = 0; i < nlines; i++) {
  871. isle->lines[i] = atoi(lines[i]);
  872. }
  873. /* set area */
  874. isle->area = area;
  875. G_free_tokens(lines);
  876. plus->Isle[n] = isle;
  877. return isle;
  878. }
  879. /*!
  880. \brief Read topo from PostGIS topology schema -- header info only
  881. \param[in,out] plus pointer to Plus_head struct
  882. \return 0 on success
  883. \return -1 on error
  884. */
  885. int Vect__load_plus_head(struct Map_info *Map)
  886. {
  887. char stmt[DB_SQL_MAX];
  888. struct Format_info_pg *pg_info;
  889. struct Plus_head *plus;
  890. PGresult *res;
  891. plus = &(Map->plus);
  892. pg_info = &(Map->fInfo.pg);
  893. plus->off_t_size = -1;
  894. /* get map bounding box
  895. first try to get info from 'topology.grass' table */
  896. sprintf(stmt,
  897. "SELECT %s FROM \"%s\".\"%s\" WHERE %s = %d",
  898. TOPO_BBOX, TOPO_SCHEMA, TOPO_TABLE, TOPO_ID, pg_info->toposchema_id);
  899. G_debug(2, "SQL: %s", stmt);
  900. res = PQexec(pg_info->conn, stmt);
  901. if (!res || PQresultStatus(res) != PGRES_TUPLES_OK ||
  902. PQntuples(res) != 1) {
  903. PQclear(res);
  904. /* otherwise try to calculate bbox from TopoGeometry elements */
  905. sprintf(stmt,
  906. "SELECT ST_3DExtent(%s) FROM \"%s\".\"%s\"",
  907. pg_info->topogeom_column, pg_info->schema_name, pg_info->table_name);
  908. G_debug(2, "SQL: %s", stmt);
  909. res = PQexec(pg_info->conn, stmt);
  910. if (!res || PQresultStatus(res) != PGRES_TUPLES_OK ||
  911. PQntuples(res) != 1 || strlen(PQgetvalue(res, 0, 0)) < 1) {
  912. G_warning(_("Unable to get map bounding box from topology"));
  913. PQclear(res);
  914. return -1;
  915. }
  916. }
  917. if (parse_bbox(PQgetvalue(res, 0, 0), &(plus->box)) != 0) {
  918. G_warning(_("Unable to parse map bounding box:\n%s"),
  919. PQgetvalue(res, 0, 0));
  920. return -1;
  921. }
  922. PQclear(res);
  923. /* get number of topological elements */
  924. /* nodes
  925. note: isolated nodes are registered in GRASS Topology model */
  926. sprintf(stmt,
  927. "SELECT COUNT(DISTINCT node) FROM (SELECT start_node AS node "
  928. "FROM \"%s\".edge GROUP BY start_node UNION ALL SELECT end_node "
  929. "AS node FROM \"%s\".edge GROUP BY end_node) AS foo",
  930. pg_info->toposchema_name, pg_info->toposchema_name);
  931. plus->n_nodes = Vect__execute_get_value_pg(pg_info->conn, stmt);
  932. if (!pg_info->topo_geo_only) {
  933. int n_nodes;
  934. /* check nodes consistency */
  935. sprintf(stmt, "SELECT COUNT(*) FROM \"%s\".%s",
  936. pg_info->toposchema_name, TOPO_TABLE_NODE);
  937. n_nodes = Vect__execute_get_value_pg(pg_info->conn, stmt);
  938. if (n_nodes != plus->n_nodes) {
  939. G_warning(_("Different number of nodes detected (%d, %d)"),
  940. plus->n_nodes, n_nodes);
  941. return -1;
  942. }
  943. }
  944. G_debug(3, "Vect_open_topo_pg(): n_nodes=%d", plus->n_nodes);
  945. /* lines (edges in PostGIS Topology model) */
  946. sprintf(stmt,
  947. "SELECT COUNT(*) FROM \"%s\".edge",
  948. pg_info->toposchema_name);
  949. /* + isolated nodes as points
  950. + centroids */
  951. plus->n_lines = Vect__execute_get_value_pg(pg_info->conn, stmt);
  952. /* areas (faces with face_id > 0 in PostGIS Topology model) */
  953. sprintf(stmt,
  954. "SELECT COUNT(*) FROM \"%s\".face WHERE face_id > 0",
  955. pg_info->toposchema_name);
  956. plus->n_areas = Vect__execute_get_value_pg(pg_info->conn, stmt);
  957. if (!pg_info->topo_geo_only) {
  958. int n_areas;
  959. /* check areas consistency */
  960. sprintf(stmt, "SELECT COUNT(*) FROM \"%s\".%s",
  961. pg_info->toposchema_name, TOPO_TABLE_AREA);
  962. n_areas = Vect__execute_get_value_pg(pg_info->conn, stmt);
  963. if (n_areas != plus->n_areas) {
  964. G_warning(_("Different number of areas detected (%d, %d)"),
  965. plus->n_areas, n_areas);
  966. return -1;
  967. }
  968. }
  969. G_debug(3, "Vect_open_topo_pg(): n_areas=%d", plus->n_areas);
  970. /* isles (faces with face_id <=0 in PostGIS Topology model)
  971. note: universal face is represented in GRASS Topology model as isle (area=0)
  972. */
  973. sprintf(stmt,
  974. "SELECT COUNT(*) FROM \"%s\".face WHERE face_id < 0",
  975. pg_info->toposchema_name);
  976. plus->n_isles = Vect__execute_get_value_pg(pg_info->conn, stmt);
  977. if (!pg_info->topo_geo_only) {
  978. int n_isles;
  979. /* check areas consistency */
  980. sprintf(stmt, "SELECT COUNT(*) FROM \"%s\".%s",
  981. pg_info->toposchema_name, TOPO_TABLE_ISLE);
  982. n_isles = Vect__execute_get_value_pg(pg_info->conn, stmt);
  983. if (n_isles != plus->n_isles) {
  984. G_warning(_("Different number of areas detected (%d, %d)"),
  985. plus->n_isles, n_isles);
  986. return -1;
  987. }
  988. }
  989. G_debug(3, "Vect_open_topo_pg(): n_isles=%d", plus->n_isles);
  990. /* number of features according the type */
  991. /* points */
  992. sprintf(stmt,
  993. "SELECT COUNT(*) FROM \"%s\".node WHERE containing_face "
  994. "IS NULL AND node_id NOT IN "
  995. "(SELECT node FROM (SELECT start_node AS node FROM \"%s\".edge "
  996. "GROUP BY start_node UNION ALL SELECT end_node AS node FROM "
  997. "\"%s\".edge GROUP BY end_node) AS foo)",
  998. pg_info->toposchema_name, pg_info->toposchema_name,
  999. pg_info->toposchema_name);
  1000. plus->n_plines = Vect__execute_get_value_pg(pg_info->conn, stmt);
  1001. G_debug(3, "Vect_open_topo_pg(): n_plines=%d", plus->n_plines);
  1002. /* lines */
  1003. sprintf(stmt,
  1004. "SELECT COUNT(*) FROM \"%s\".edge WHERE "
  1005. "left_face = 0 AND right_face = 0",
  1006. pg_info->toposchema_name);
  1007. plus->n_llines = Vect__execute_get_value_pg(pg_info->conn, stmt);
  1008. G_debug(3, "Vect_open_topo_pg(): n_llines=%d", plus->n_llines);
  1009. /* boundaries */
  1010. sprintf(stmt,
  1011. "SELECT COUNT(*) FROM \"%s\".edge WHERE "
  1012. "left_face != 0 OR right_face != 0",
  1013. pg_info->toposchema_name);
  1014. plus->n_blines = Vect__execute_get_value_pg(pg_info->conn, stmt);
  1015. G_debug(3, "Vect_open_topo_pg(): n_blines=%d", plus->n_blines);
  1016. /* centroids */
  1017. sprintf(stmt,
  1018. "SELECT COUNT(*) FROM \"%s\".node WHERE containing_face "
  1019. "IS NOT NULL AND node_id NOT IN "
  1020. "(SELECT node FROM (SELECT start_node AS node FROM \"%s\".edge "
  1021. "GROUP BY start_node UNION ALL SELECT end_node AS node FROM "
  1022. "\"%s\".edge GROUP BY end_node) AS foo)",
  1023. pg_info->toposchema_name, pg_info->toposchema_name,
  1024. pg_info->toposchema_name);
  1025. plus->n_clines = Vect__execute_get_value_pg(pg_info->conn, stmt);
  1026. G_debug(3, "Vect_open_topo_pg(): n_clines=%d", plus->n_clines);
  1027. /* update number of lines - add points and centroids */
  1028. plus->n_lines += plus->n_plines + plus->n_clines;
  1029. G_debug(3, "Vect_open_topo_pg(): n_lines=%d", plus->n_lines);
  1030. return 0;
  1031. }
  1032. /*!
  1033. \brief Read topo info from PostGIS topology schema
  1034. \param pg_info pointer to Format_info_pg
  1035. \param[in,out] plus pointer to Plus_head struct
  1036. \param head_only TRUE to read only header info
  1037. \return 0 on success
  1038. \return -1 on error
  1039. */
  1040. int Vect__load_plus_pg(struct Map_info *Map, int head_only)
  1041. {
  1042. int i, side, line;
  1043. char stmt[DB_SQL_MAX];
  1044. struct Format_info_pg *pg_info;
  1045. struct Plus_head *plus;
  1046. struct P_line *Line;
  1047. struct line_pnts *Points;
  1048. struct ilist *List;
  1049. struct bound_box box;
  1050. PGresult *res;
  1051. pg_info = &(Map->fInfo.pg);
  1052. plus = &(Map->plus);
  1053. if (Vect__load_plus_head(Map) != 0)
  1054. return -1;
  1055. if (head_only)
  1056. return 0;
  1057. Points = Vect_new_line_struct();
  1058. List = Vect_new_list();
  1059. /* read nodes (GRASS Topo)
  1060. note: standalone nodes (ie. points/centroids) are ignored
  1061. */
  1062. Vect__load_map_nodes_pg(Map, FALSE);
  1063. /* read lines (GRASS Topo)
  1064. - standalone nodes -> points|centroids
  1065. - edges -> lines/boundaries
  1066. */
  1067. Vect__free_cache(&(pg_info->cache));
  1068. pg_info->cache.ctype = CACHE_MAP;
  1069. Vect__load_map_lines_pg(Map);
  1070. /* build areas */
  1071. if (pg_info->topo_geo_only) {
  1072. /* build areas for boundaries
  1073. reset values -> build from scratch */
  1074. plus->n_areas = plus->n_isles = 0;
  1075. for (line = 1; line <= plus->n_lines; line++) {
  1076. Line = plus->Line[line]; /* centroids: Line is NULL */
  1077. if (!Line || Line->type != GV_BOUNDARY)
  1078. continue;
  1079. for (i = 0; i < 2; i++) { /* for both sides build an area/isle */
  1080. side = i == 0 ? GV_LEFT : GV_RIGHT;
  1081. G_debug(3, "Build area for line = %d, side = %d",
  1082. i, side);
  1083. Vect_build_line_area(Map, line, side);
  1084. }
  1085. }
  1086. }
  1087. else {
  1088. int cat;
  1089. /* read areas from 'area_grass' table */
  1090. sprintf(stmt,
  1091. "SELECT area_id,lines,centroid,isles FROM \"%s\".%s ORDER BY area_id",
  1092. pg_info->toposchema_name, TOPO_TABLE_AREA);
  1093. G_debug(2, "SQL: %s", stmt);
  1094. res = PQexec(pg_info->conn, stmt);
  1095. if (!res || PQresultStatus(res) != PGRES_TUPLES_OK ||
  1096. plus->n_areas != PQntuples(res)) {
  1097. if (res)
  1098. PQclear(res);
  1099. return -1;
  1100. }
  1101. dig_alloc_areas(plus, plus->n_areas);
  1102. G_zero(plus->Area, sizeof(struct P_area *) * (plus->n_areas + 1)); /* index starts at 1 */
  1103. G_debug(3, "Vect_open_topo_pg(): n_areas=%d", plus->n_areas);
  1104. for (i = 0; i < plus->n_areas; i++) {
  1105. read_p_area(plus, i + 1, (char *)PQgetvalue(res, i, 1),
  1106. atoi(PQgetvalue(res, i, 2)), (char *)PQgetvalue(res, i, 3));
  1107. if (plus->Spidx_new) {
  1108. /* update spatial index */
  1109. Vect_get_area_points(Map, i+1, Points);
  1110. dig_line_box(Points, &box);
  1111. dig_spidx_add_area(&(Map->plus), i+1, &box);
  1112. }
  1113. if (plus->update_cidx) {
  1114. /* update category index */
  1115. cat = pg_info->cache.lines_cats[plus->Area[i+1]->centroid-1];
  1116. dig_cidx_add_cat(plus, cat > 0 ? 1 : 0, cat > 0 ? cat : 0, i+1, GV_AREA);
  1117. }
  1118. }
  1119. PQclear(res);
  1120. }
  1121. plus->built = GV_BUILD_AREAS;
  1122. /* attach isles */
  1123. if (pg_info->topo_geo_only) {
  1124. plus->n_isles = 0; /* reset isles */
  1125. G_warning(_("To be implemented: isles not attached in Topo-Geo-only mode"));
  1126. }
  1127. else {
  1128. /* read isles from 'isle_grass' table */
  1129. sprintf(stmt,
  1130. "SELECT isle_id,lines,area FROM \"%s\".%s ORDER BY isle_id",
  1131. pg_info->toposchema_name, TOPO_TABLE_ISLE);
  1132. G_debug(2, "SQL: %s", stmt);
  1133. res = PQexec(pg_info->conn, stmt);
  1134. if (!res || PQresultStatus(res) != PGRES_TUPLES_OK ||
  1135. plus->n_isles != PQntuples(res)) {
  1136. if (res)
  1137. PQclear(res);
  1138. return -1;
  1139. }
  1140. dig_alloc_isles(plus, plus->n_isles);
  1141. G_zero(plus->Isle, sizeof(struct P_isle *) * (plus->n_isles + 1)); /* index starts at 1 */
  1142. G_debug(3, "Vect_open_topo_pg(): n_isles=%d", plus->n_isles);
  1143. for (i = 0; i < plus->n_isles; i++) {
  1144. read_p_isle(plus, i + 1, (char *)PQgetvalue(res, i, 1),
  1145. atoi(PQgetvalue(res, i, 2)));
  1146. if (plus->Spidx_new) {
  1147. /* update spatial index */
  1148. Vect_get_isle_points(Map, i+1, Points);
  1149. dig_line_box(Points, &box);
  1150. dig_spidx_add_isle(&(Map->plus), i+1, &box);
  1151. }
  1152. }
  1153. PQclear(res);
  1154. }
  1155. plus->built = GV_BUILD_ATTACH_ISLES;
  1156. /* attach centroids */
  1157. if (pg_info->topo_geo_only && plus->n_areas > 0) {
  1158. int area;
  1159. struct P_area *Area;
  1160. struct P_topo_c *topo;
  1161. for (line = 1; line <= plus->n_lines; line++) {
  1162. Line = plus->Line[line];
  1163. if (Line->type != GV_CENTROID)
  1164. continue;
  1165. Vect_read_line(Map, Points, NULL, line);
  1166. area = Vect_find_area(Map, Points->x[0], Points->y[0]);
  1167. topo = (struct P_topo_c *)Line->topo;
  1168. topo->area = area;
  1169. Area = plus->Area[topo->area];
  1170. Area->centroid = Line->offset;
  1171. }
  1172. }
  1173. plus->built = GV_BUILD_CENTROIDS;
  1174. /* done */
  1175. plus->built = GV_BUILD_ALL;
  1176. Vect_destroy_line_struct(Points);
  1177. Vect_destroy_list(List);
  1178. return 0;
  1179. }
  1180. /*!
  1181. \brief Read nodes from DB
  1182. \param Map pointer to Map_info struct
  1183. \param geom_only read only node's geometry
  1184. \return number of nodes
  1185. */
  1186. int Vect__load_map_nodes_pg(struct Map_info *Map, int geom_only)
  1187. {
  1188. int i, id, n_nodes;
  1189. char stmt[DB_SQL_MAX];
  1190. struct Plus_head *plus;
  1191. struct Format_info_pg *pg_info;
  1192. struct Format_info_offset *offset;
  1193. PGresult *res;
  1194. plus = &(Map->plus);
  1195. pg_info = &(Map->fInfo.pg);
  1196. offset = &(pg_info->offset);
  1197. if (pg_info->topo_geo_only || geom_only)
  1198. sprintf(stmt,
  1199. "SELECT node_id,geom FROM \"%s\".node WHERE node_id IN "
  1200. "(SELECT node FROM (SELECT start_node AS node FROM \"%s\".edge "
  1201. "GROUP BY start_node UNION ALL SELECT end_node AS node FROM "
  1202. "\"%s\".edge GROUP BY end_node) AS foo) ORDER BY node_id",
  1203. pg_info->toposchema_name, pg_info->toposchema_name,
  1204. pg_info->toposchema_name);
  1205. else
  1206. sprintf(stmt, "SELECT node.node_id,geom,lines,angles FROM \"%s\".node AS node "
  1207. "JOIN \"%s\".%s AS node_grass ON node.node_id = node_grass.node_id "
  1208. "ORDER BY node_id", pg_info->toposchema_name, pg_info->toposchema_name,
  1209. TOPO_TABLE_NODE);
  1210. G_debug(2, "SQL: %s", stmt);
  1211. res = PQexec(pg_info->conn, stmt);
  1212. if (!res || PQresultStatus(res) != PGRES_TUPLES_OK ||
  1213. (!geom_only && PQntuples(res) != plus->n_nodes)) {
  1214. G_warning(_("Inconsistency in topology: number of "
  1215. "nodes %d (should be %d)"),
  1216. PQntuples(res), plus->n_nodes);
  1217. if (res)
  1218. PQclear(res);
  1219. return -1;
  1220. }
  1221. n_nodes = PQntuples(res);
  1222. G_debug(3, "load_plus(): n_nodes = %d", n_nodes);
  1223. dig_alloc_nodes(plus, n_nodes);
  1224. offset->array = (int *) G_malloc (sizeof(int) * n_nodes);
  1225. offset->array_num = offset->array_alloc = n_nodes;
  1226. for (i = 0; i < n_nodes; i++) {
  1227. G_debug(5, "node: %d", i);
  1228. id = atoi(PQgetvalue(res, i, 0));
  1229. read_p_node(plus, i + 1, /* node index starts at 1 */
  1230. id, (const char *) PQgetvalue(res, i, 1),
  1231. !pg_info->topo_geo_only ? (const char *) PQgetvalue(res, i, 2) : NULL,
  1232. !pg_info->topo_geo_only ? (const char *) PQgetvalue(res, i, 3) : NULL,
  1233. pg_info, geom_only);
  1234. /* update offset */
  1235. offset->array[i] = id;
  1236. }
  1237. PQclear(res);
  1238. return n_nodes;
  1239. }
  1240. /*!
  1241. \brief Read features from DB
  1242. \param Map pointer to Map_info struct
  1243. \return number of features
  1244. */
  1245. int Vect__load_map_lines_pg(struct Map_info *Map)
  1246. {
  1247. int i, id, ntuples;
  1248. char stmt[DB_SQL_MAX];
  1249. struct Plus_head *plus;
  1250. struct Format_info_pg *pg_info;
  1251. struct line_data line_data;
  1252. struct Format_info_offset *offset;
  1253. PGresult *res;
  1254. plus = &(Map->plus);
  1255. pg_info = &(Map->fInfo.pg);
  1256. offset = &(pg_info->offset);
  1257. dig_alloc_lines(plus, plus->n_lines);
  1258. G_zero(plus->Line, sizeof(struct P_line *) * (plus->n_lines + 1)); /* index starts at 1 */
  1259. /* read PostGIS Topo standalone nodes (containing_face is null)
  1260. -> points
  1261. */
  1262. if (pg_info->topo_geo_only)
  1263. sprintf(stmt,
  1264. "SELECT tt.node_id,tt.geom,ft.%s FROM \"%s\".node AS tt "
  1265. "LEFT JOIN \"%s\".\"%s\" AS ft ON "
  1266. "(%s).type = 1 AND (%s).id = node_id WHERE containing_face "
  1267. "IS NULL AND node_id NOT IN "
  1268. "(SELECT node FROM (SELECT start_node AS node FROM \"%s\".edge "
  1269. "GROUP BY start_node UNION ALL SELECT end_node AS node FROM "
  1270. "\"%s\".edge GROUP BY end_node) AS foo) ORDER BY node_id",
  1271. pg_info->fid_column, pg_info->toposchema_name, pg_info->schema_name, pg_info->table_name,
  1272. pg_info->topogeom_column, pg_info->topogeom_column, pg_info->toposchema_name,
  1273. pg_info->toposchema_name);
  1274. else
  1275. sprintf(stmt,
  1276. "SELECT tt.node_id,tt.geom,ft.%s "
  1277. "FROM \"%s\".node AS tt LEFT JOIN \"%s\".\"%s\" AS ft ON "
  1278. "(%s).type = 1 AND (%s).id = node_id WHERE node_id NOT IN "
  1279. "(SELECT node_id FROM \"%s\".%s) AND containing_face IS NULL ORDER BY node_id",
  1280. pg_info->fid_column, pg_info->toposchema_name, pg_info->schema_name, pg_info->table_name,
  1281. pg_info->topogeom_column, pg_info->topogeom_column,
  1282. pg_info->toposchema_name, TOPO_TABLE_NODE);
  1283. G_debug(2, "SQL: %s", stmt);
  1284. res = PQexec(pg_info->conn, stmt);
  1285. if (!res || PQresultStatus(res) != PGRES_TUPLES_OK ||
  1286. PQntuples(res) > plus->n_plines) {
  1287. G_warning(_("Inconsistency in topology: number of "
  1288. "points %d (should be %d)"),
  1289. PQntuples(res), plus->n_plines);
  1290. if (res)
  1291. PQclear(res);
  1292. return -1;
  1293. }
  1294. ntuples = PQntuples(res); /* plus->n_plines */
  1295. G_zero(&line_data, sizeof(struct line_data));
  1296. for (i = 0; i < ntuples; i++) {
  1297. /* process standalone nodes (PostGIS Topo) */
  1298. line_data.id = atoi(PQgetvalue(res, i, 0));
  1299. line_data.wkb_geom = (char *) PQgetvalue(res, i, 1);
  1300. line_data.fid = atoi(PQgetvalue(res, i, 2)); /* feature id */
  1301. read_p_line(plus, i + 1, &line_data, pg_info->topo_geo_only, &(pg_info->cache));
  1302. }
  1303. PQclear(res);
  1304. /* read PostGIS Topo edges
  1305. -> lines
  1306. -> boundaries
  1307. */
  1308. if (pg_info->topo_geo_only)
  1309. sprintf(stmt,
  1310. "SELECT edge_id,start_node,end_node,left_face,right_face AS right_area,tt.geom,ft.%s "
  1311. "FROM \"%s\".edge AS tt LEFT JOIN \"%s\".\"%s\" AS ft ON (%s).type = 2 AND "
  1312. "(%s).id = edge_id ORDER BY edge_id",
  1313. pg_info->fid_column, pg_info->toposchema_name, pg_info->schema_name, pg_info->table_name,
  1314. pg_info->topogeom_column, pg_info->topogeom_column);
  1315. else
  1316. sprintf(stmt,
  1317. "SELECT edge_id,start_node,end_node,left_area,right_area,tt.geom,ft.%s "
  1318. "FROM \"%s\".edge AS tt LEFT JOIN \"%s\".\"%s\" ON "
  1319. "edge_id = line_id LEFT JOIN \"%s\".\"%s\" AS ft ON (%s).type = 2 AND "
  1320. "(%s).id = edge_id ORDER BY edge_id",
  1321. pg_info->fid_column, pg_info->toposchema_name, pg_info->toposchema_name, TOPO_TABLE_LINE,
  1322. pg_info->schema_name, pg_info->table_name, pg_info->topogeom_column,
  1323. pg_info->topogeom_column);
  1324. G_debug(2, "SQL: %s", stmt);
  1325. res = PQexec(pg_info->conn, stmt);
  1326. if (!res || PQresultStatus(res) != PGRES_TUPLES_OK ||
  1327. PQntuples(res) > plus->n_lines) {
  1328. G_warning(_("Inconsistency in topology: number of "
  1329. "lines %d (should be %d)"),
  1330. PQntuples(res), plus->n_lines);
  1331. if (res)
  1332. PQclear(res);
  1333. return -1;
  1334. }
  1335. /* process edges (PostGIS Topo) */
  1336. ntuples = PQntuples(res);
  1337. for (i = 0; i < ntuples; i++) {
  1338. line_data.id = atoi(PQgetvalue(res, i, 0));
  1339. line_data.start_node = remap_node(offset, atoi(PQgetvalue(res, i, 1)));
  1340. line_data.end_node = remap_node(offset, atoi(PQgetvalue(res, i, 2)));
  1341. line_data.left_face = atoi(PQgetvalue(res, i, 3));
  1342. line_data.right_face = atoi(PQgetvalue(res, i, 4));
  1343. line_data.wkb_geom = (char *) PQgetvalue(res, i, 5);
  1344. line_data.fid = atoi(PQgetvalue(res, i, 6)); /* feature id */
  1345. id = plus->n_plines + i + 1; /* points already registered */
  1346. read_p_line(plus, id, &line_data, pg_info->topo_geo_only, &(pg_info->cache));
  1347. }
  1348. PQclear(res);
  1349. /* read PostGIS Topo standalone nodes (containing_face is not null)
  1350. -> centroids
  1351. */
  1352. if (pg_info->topo_geo_only)
  1353. sprintf(stmt,
  1354. "SELECT node_id,tt.geom,containing_face,ft.%s FROM "
  1355. "\"%s\".node AS tt LEFT JOIN \"%s\".\"%s\" AS ft ON "
  1356. "(%s).type = 3 AND (%s).id = containing_face WHERE containing_face "
  1357. "IS NOT NULL AND node_id NOT IN "
  1358. "(SELECT node FROM (SELECT start_node AS node FROM \"%s\".edge "
  1359. "GROUP BY start_node UNION ALL SELECT end_node AS node FROM "
  1360. "\"%s\".edge GROUP BY end_node) AS foo) ORDER BY node_id",
  1361. pg_info->fid_column, pg_info->toposchema_name, pg_info->schema_name, pg_info->table_name,
  1362. pg_info->topogeom_column, pg_info->topogeom_column,
  1363. pg_info->toposchema_name,
  1364. pg_info->toposchema_name);
  1365. else
  1366. sprintf(stmt,
  1367. "SELECT tt.node_id,tt.geom,containing_face,ft.%s FROM "
  1368. "\"%s\".node AS tt LEFT JOIN \"%s\".\"%s\" AS ft ON "
  1369. "(%s).type = 3 AND (%s).id = containing_face WHERE "
  1370. "node_id NOT IN (SELECT node_id FROM \"%s\".%s) AND containing_face "
  1371. "IS NOT NULL ORDER BY node_id",
  1372. pg_info->fid_column, pg_info->toposchema_name, pg_info->schema_name, pg_info->table_name,
  1373. pg_info->topogeom_column, pg_info->topogeom_column,
  1374. pg_info->toposchema_name, TOPO_TABLE_NODE);
  1375. G_debug(2, "SQL: %s", stmt);
  1376. res = PQexec(pg_info->conn, stmt);
  1377. if (!res || PQresultStatus(res) != PGRES_TUPLES_OK ||
  1378. PQntuples(res) != plus->n_clines) {
  1379. G_warning(_("Inconsistency in topology: number of "
  1380. "centroids %d (should be %d)"),
  1381. PQntuples(res), plus->n_clines);
  1382. if (res)
  1383. PQclear(res);
  1384. return -1;
  1385. }
  1386. G_zero(&line_data, sizeof(struct line_data));
  1387. id = plus->n_plines + plus->n_llines + plus->n_blines + 1;
  1388. for (i = 0; i < plus->n_clines; i++) {
  1389. line_data.id = atoi(PQgetvalue(res, i, 0));
  1390. line_data.wkb_geom = (char *)PQgetvalue(res, i, 1);
  1391. line_data.left_face = atoi(PQgetvalue(res, i, 2)); /* face id */
  1392. line_data.fid = atoi(PQgetvalue(res, i, 3)); /* feature id */
  1393. /* area id and face id can be different */
  1394. read_p_line(plus, id + i, &line_data, pg_info->topo_geo_only, &(pg_info->cache));
  1395. }
  1396. PQclear(res);
  1397. plus->built = GV_BUILD_BASE;
  1398. return plus->n_lines;
  1399. }
  1400. /*
  1401. \brief PostgreSQL notice processor
  1402. Print out NOTICE message only on verbose level
  1403. */
  1404. void notice_processor(void *arg, const char *message)
  1405. {
  1406. if (G_verbose() > G_verbose_std()) {
  1407. fprintf(stderr, "%s", message);
  1408. }
  1409. }
  1410. /*!
  1411. \brief Scan string array
  1412. Creates tokens based on string array, eg. '{1, 2, 3}' become
  1413. [1,2,3].
  1414. Allocated tokes should be freed by G_free_tokens().
  1415. \param sArray string array
  1416. \return tokens
  1417. */
  1418. char **scan_array(const char *sarray)
  1419. {
  1420. char *buf, **tokens;
  1421. int i, len;
  1422. /* remove '{}' */
  1423. len = strlen(sarray) - 1; /* skip '}' */
  1424. buf = (char *)G_malloc(len);
  1425. for (i = 1; i < len; i++)
  1426. buf[i-1] = sarray[i];
  1427. buf[len-1] = '\0';
  1428. tokens = G_tokenize(buf, ",");
  1429. G_free(buf);
  1430. return tokens;
  1431. }
  1432. /*!
  1433. \brief Get node id from offset
  1434. \param offset pointer to Format_info_offset struct
  1435. \param node node to find
  1436. \return node id
  1437. \return -1 not found
  1438. */
  1439. int remap_node(const struct Format_info_offset *offset, int node)
  1440. {
  1441. /* probably not needed
  1442. int i;
  1443. for (i = node-1; i < offset->array_num; i++) {
  1444. if (offset->array[i] == node)
  1445. return i + 1;
  1446. }
  1447. return -1;
  1448. */
  1449. return offset->array[node-1];
  1450. }
  1451. /*!
  1452. \brief Get line id from offset
  1453. \param plus pointer to Plus_head struct
  1454. \param offset line offset
  1455. \param type line type
  1456. \return line id
  1457. \return -1 not found
  1458. */
  1459. int remap_line(const struct Plus_head* plus, off_t offset, int type)
  1460. {
  1461. int i;
  1462. struct P_line *Line;
  1463. for (i = (int) offset; i <= plus->n_lines; i++) {
  1464. Line = plus->Line[i];
  1465. if (!Line || Line->type != type)
  1466. continue;
  1467. if ((int) Line->offset == offset)
  1468. return i;
  1469. }
  1470. return -1;
  1471. }
  1472. #endif