open_pg.c 49 KB

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