open_pg.c 48 KB

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