GRASS GIS 7 Programmer's Manual  7.5.svn(2017)-r71793
 All Data Structures Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Pages
write_pg.c
Go to the documentation of this file.
1 /*!
2  \file lib/vector/Vlib/write_pg.c
3 
4  \brief Vector library - write vector feature (PostGIS format)
5 
6  Higher level functions for reading/writing/manipulating vectors.
7 
8  Write subroutine inspired by OGR PostgreSQL driver.
9 
10  \todo PostGIS version of V2__delete_area_cats_from_cidx_nat()
11  \todo function to delete corresponding entry in fidx
12  \todo PostGIS version of V2__add_area_cats_to_cidx_nat
13 
14  (C) 2012-2014 by Martin Landa, and the GRASS Development Team
15 
16  This program is free software under the GNU General Public License
17  (>=v2). Read the file COPYING that comes with GRASS for details.
18 
19  \author Martin Landa <landa.martin gmail.com>
20  */
21 
22 #include <string.h>
23 
24 #include <grass/vector.h>
25 #include <grass/glocale.h>
26 
27 #include "local_proto.h"
28 
29 #ifdef HAVE_POSTGRES
30 #include "pg_local_proto.h"
31 
32 #define WKBSRIDFLAG 0x20000000
33 
34 #define TOPOGEOM_COLUMN "topo"
35 
36 /*! Use SQL statements from PostGIS Topology extension (this options
37  is quite slow. By default are used simple SQL statements (INSERT, UPDATE)
38 */
39 #define USE_TOPO_STMT 0
40 
41 static int create_table(struct Format_info_pg *);
42 static int check_schema(const struct Format_info_pg *);
43 static int create_topo_schema(struct Format_info_pg *, int);
44 static int create_pg_layer(struct Map_info *, int);
45 static char *get_sftype(SF_FeatureType);
46 static off_t write_line_sf(struct Map_info *, int,
47  const struct line_pnts **, int,
48  const struct line_cats *);
49 static off_t write_line_tp(struct Map_info *, int, int,
50  const struct line_pnts *,
51  const struct line_cats *);
52 static char *binary_to_hex(int, const unsigned char *);
53 static unsigned char *point_to_wkb(int, const struct line_pnts *, int, int *);
54 static unsigned char *linestring_to_wkb(int, const struct line_pnts *,
55  int, int *);
56 static unsigned char *polygon_to_wkb(int, const struct line_pnts **, int,
57  int, int *);
58 static char *line_to_wkb(struct Format_info_pg *, const struct line_pnts **,
59  int, int, int);
60 static int write_feature(struct Map_info *, int, int,
61  const struct line_pnts **, int, int);
62 static char *build_insert_stmt(const struct Format_info_pg *, const char *, int, int);
63 static int insert_topo_element(struct Map_info *, int, int, const char *);
64 static int update_next_edge(struct Map_info*, int, int);
65 static int delete_face(const struct Map_info *, int);
66 static int update_topo_edge(struct Map_info *, int);
67 static int update_topo_face(struct Map_info *, int);
68 static int add_line_to_topo_pg(struct Map_info *, off_t, int, const struct line_pnts *);
69 static int delete_line_from_topo_pg(struct Map_info *, int, int, const struct line_pnts *);
70 static int set_constraint_to_deferrable(struct Format_info_pg *, const char *, const char *,
71  const char *, const char *, const char *);
72 static dbDriver *open_db(struct Format_info_pg *);
73 #endif
74 
75 static struct line_pnts *Points;
76 
77 /*!
78  \brief Writes feature on level 1 (PostGIS interface)
79 
80  Notes for simple feature access:
81  - centroids are not supported in PostGIS, pseudotopo holds virtual
82  centroids
83  - boundaries are not supported in PostGIS, pseudotopo treats polygons
84  as boundaries
85 
86  Notes for PostGIS Topology access:
87  - centroids are stored as isolated nodes
88  - boundaries are stored as edges
89 
90  \param Map pointer to Map_info structure
91  \param type feature type (GV_POINT, GV_LINE, ...)
92  \param points pointer to line_pnts structure (feature geometry)
93  \param cats pointer to line_cats structure (feature categories)
94 
95  \return feature offset into file
96  \return -1 on error
97  */
98 off_t V1_write_line_pg(struct Map_info *Map, int type,
99  const struct line_pnts *points,
100  const struct line_cats *cats)
101 {
102 #ifdef HAVE_POSTGRES
103  struct Format_info_pg *pg_info;
104 
105  pg_info = &(Map->fInfo.pg);
106 
107  if (pg_info->feature_type == SF_GEOMETRY) {
108  /* create PostGIS table if doesn't exist */
109  if (create_pg_layer(Map, type) < 0)
110  return -1;
111  }
112 
113  if (!points)
114  return 0;
115 
116  if (!pg_info->toposchema_name) { /* simple features access */
117  return write_line_sf(Map, type, &points, 1, cats);
118  }
119 
120  /* PostGIS Topology access */
121  return write_line_tp(Map, type, FALSE, points, cats);
122 #else
123  G_fatal_error(_("GRASS is not compiled with PostgreSQL support"));
124  return -1;
125 #endif
126 }
127 
128 /*!
129  \brief Writes feature on topological level (PostGIS interface)
130 
131  Calls V2_write_line_sfa() for simple features access.
132 
133  \param Map pointer to Map_info structure
134  \param type feature type (GV_POINT, GV_LINE, ...)
135  \param points pointer to line_pnts structure (feature geometry)
136  \param cats pointer to line_cats structure (feature categories)
137 
138  \return feature offset into file
139  \return -1 on error
140  */
141 off_t V2_write_line_pg(struct Map_info *Map, int type,
142  const struct line_pnts *points,
143  const struct line_cats *cats)
144 {
145 #ifdef HAVE_POSTGRES
146  struct Format_info_pg *pg_info;
147 
148  pg_info = &(Map->fInfo.pg);
149 
150  if (!pg_info->toposchema_name) { /* pseudo-topology */
151  return V2_write_line_sfa(Map, type, points, cats);
152  }
153 
154  /* PostGIS Topology */
155  return write_line_tp(Map, type, FALSE, points, cats);
156 #else
157  G_fatal_error(_("GRASS is not compiled with PostgreSQL support"));
158  return -1;
159 #endif
160 }
161 
162 /*!
163  \brief Rewrites feature at the given offset (level 1) (PostGIS interface, internal use only)
164 
165  Only for simple feature access. PostGIS Topology requires level 2.
166 
167  \todo Use UPDATE statement ?
168 
169  \param Map pointer to Map_info structure
170  \param offset feature offset
171  \param type feature type (GV_POINT, GV_LINE, ...)
172  \param points feature geometry
173  \param cats feature categories
174 
175  \return feature offset (rewritten feature)
176  \return -1 on error
177  */
178 off_t V1_rewrite_line_pg(struct Map_info * Map,
179  off_t offset, int type,
180  const struct line_pnts * points,
181  const struct line_cats * cats)
182 {
183  G_debug(3, "V1_rewrite_line_pg(): type=%d offset=%"PRI_OFF_T,
184  type, offset);
185 #ifdef HAVE_POSTGRES
186  if (type != V1_read_line_pg(Map, NULL, NULL, offset)) {
187  G_warning(_("Unable to rewrite feature (incompatible feature types)"));
188  return -1;
189  }
190 
191  /* delete old */
192  V1_delete_line_pg(Map, offset);
193 
194  return V1_write_line_pg(Map, type, points, cats);
195 #else
196  G_fatal_error(_("GRASS is not compiled with PostgreSQL support"));
197  return -1;
198 #endif
199 }
200 
201 /*!
202  \brief Rewrites feature at topological level (PostGIS interface, internal use only)
203 
204  Note: Topology must be built at level >= GV_BUILD_BASE
205 
206  \todo Handle also categories
207  \todo Store original geometry in tmp table for restore
208 
209  \param Map pointer to Map_info structure
210  \param line feature id
211  \param type feature type (GV_POINT, GV_LINE, ...)
212  \param points feature geometry
213  \param cats feature categories
214 
215  \return offset where feature was rewritten
216  \return -1 on error
217 */
218 off_t V2_rewrite_line_pg(struct Map_info *Map, off_t line, int type,
219  const struct line_pnts *points, const struct line_cats *cats)
220 {
221  G_debug(3, "V2_rewrite_line_pg(): line=%d type=%d",
222  (int)line, type);
223 #ifdef HAVE_POSTGRES
224  const char *schema_name, *table_name, *keycolumn;
225  char *stmt, *geom_data;
226 
227  struct Format_info_pg *pg_info;
228  struct P_line *Line;
229  off_t offset;
230 
231  geom_data = NULL;
232  stmt = NULL;
233  pg_info = &(Map->fInfo.pg);
234 
235  if (line < 1 || line > Map->plus.n_lines) {
236  G_warning(_("Attempt to access feature with invalid id (%d)"), (int)line);
237  return -1;
238  }
239 
240  Line = Map->plus.Line[line];
241  if (Line == NULL) {
242  G_warning(_("Attempt to access dead feature %d"), (int)line);
243  return -1;
244  }
245  offset = Line->offset;
246 
247  if (!(Map->plus.update_cidx)) {
248  Map->plus.cidx_up_to_date = FALSE; /* category index will be outdated */
249  }
250 
251  if (!Points)
252  Points = Vect_new_line_struct();
253 
254  if (type != V2_read_line_pg(Map, Points, NULL, line)) {
255  G_warning(_("Unable to rewrite feature (incompatible feature types)"));
256  return -1;
257  }
258 
259  /* remove line from topology */
260  if (0 != delete_line_from_topo_pg(Map, line, type, Points))
261  return -1;
262 
263  if (pg_info->toposchema_name) { /* PostGIS Topology */
264  schema_name = pg_info->toposchema_name;
265  if (type & GV_POINTS) {
266  table_name = keycolumn = "node";
267  }
268  else {
269  table_name = "edge_data";
270  keycolumn = "edge";
271  }
272  }
273  else { /* simple features access */
274  schema_name = pg_info->schema_name;
275  table_name = pg_info->table_name;
276  keycolumn = pg_info->fid_column;
277  }
278 
279  geom_data = line_to_wkb(pg_info, &points, 1, type, Map->head.with_z);
280  G_asprintf(&stmt, "UPDATE \"%s\".\"%s\" SET geom = '%s'::GEOMETRY WHERE %s_id = %ld",
281  schema_name, table_name, geom_data, keycolumn, line);
282  G_free(geom_data);
283 
284  if (Vect__execute_pg(pg_info->conn, stmt) == -1) {
285  G_warning(_("Unable to rewrite feature %d"), (int)line);
286  Vect__execute_pg(pg_info->conn, "ROLLBACK");
287  return -1;
288  }
289 
290  /* update topology
291  note: offset is not changed */
292  return add_line_to_topo_pg(Map, offset, type, points);
293 #else
294  G_fatal_error(_("GRASS is not compiled with PostgreSQL support"));
295  return -1;
296 #endif
297 }
298 
299 /*!
300  \brief Deletes feature at the given offset (level 1)
301 
302  Only for simple feature access. PostGIS Topology requires level 2.
303 
304  \param Map pointer Map_info structure
305  \param offset feature offset
306 
307  \return 0 on success
308  \return -1 on error
309  */
310 int V1_delete_line_pg(struct Map_info *Map, off_t offset)
311 {
312 #ifdef HAVE_POSTGRES
313  long fid;
314  char stmt[DB_SQL_MAX];
315 
316  struct Format_info_pg *pg_info;
317 
318  pg_info = &(Map->fInfo.pg);
319 
320  if (!pg_info->conn || !pg_info->table_name) {
321  G_warning(_("No connection defined"));
322  return -1;
323  }
324 
325  if (offset >= pg_info->offset.array_num) {
326  G_warning(_("Invalid offset (%ld)"), offset);
327  return -1;
328  }
329 
330  fid = pg_info->offset.array[offset];
331 
332  G_debug(3, "V1_delete_line_pg(): offset = %lu -> fid = %ld",
333  (unsigned long)offset, fid);
334 
335  if (!pg_info->inTransaction) {
336  /* start transaction */
337  pg_info->inTransaction = TRUE;
338  if (Vect__execute_pg(pg_info->conn, "BEGIN") == -1)
339  return -1;
340  }
341 
342  sprintf(stmt, "DELETE FROM %s WHERE %s = %ld",
343  pg_info->table_name, pg_info->fid_column, fid);
344  G_debug(3, "SQL: %s", stmt);
345 
346  if (Vect__execute_pg(pg_info->conn, stmt) == -1) {
347  G_warning(_("Unable to delete feature"));
348  Vect__execute_pg(pg_info->conn, "ROLLBACK");
349  return -1;
350  }
351 
352  return 0;
353 #else
354  G_fatal_error(_("GRASS is not compiled with PostgreSQL support"));
355  return -1;
356 #endif
357 }
358 
359 /*!
360  \brief Deletes feature on topological level (PostGIS interface)
361 
362  Note: Topology must be built at level >= GV_BUILD_BASE
363 
364  Calls V2_delete_line_sfa() for simple feature access.
365 
366  \param Map pointer to Map_info structure
367  \param line feature id to be deleted
368 
369  \return 0 on success
370  \return -1 on error
371 */
372 int V2_delete_line_pg(struct Map_info *Map, off_t line)
373 {
374 #ifdef HAVE_POSTGRES
375  int ret;
376  struct Format_info_pg *pg_info;
377 
378  pg_info = &(Map->fInfo.pg);
379 
380  if (line < 1 || line > Map->plus.n_lines) {
381  G_warning(_("Attempt to access feature with invalid id (%d)"), (int)line);
382  return -1;
383  }
384 
385  if (!pg_info->toposchema_name) { /* pseudo-topology */
386  return V2_delete_line_sfa(Map, line);
387  }
388  else { /* PostGIS topology */
389  int type;
390  char stmt[DB_SQL_MAX];
391  const char *table_name, *keycolumn;
392 
393  struct P_line *Line;
394 
395  if (line < 1 || line > Map->plus.n_lines) {
396  G_warning(_("Attempt to access feature with invalid id (%d)"), (int)line);
397  return -1;
398  }
399 
400  Line = Map->plus.Line[line];
401  if (!Line) {
402  G_warning(_("Attempt to access dead feature %d"), (int)line);
403  return -1;
404  }
405 
406  if (!(Map->plus.update_cidx)) {
407  Map->plus.cidx_up_to_date = FALSE; /* category index will be outdated */
408  }
409 
410  Vect__execute_pg(pg_info->conn, "BEGIN");
411 
412  if (Line->type & GV_POINTS) {
413  table_name = keycolumn = "node";
414  }
415  else {
416  table_name = "edge_data";
417  keycolumn = "edge";
418 
419  /* first remove references to this edge */
420  /* (1) left next edge */
421  sprintf(stmt, "UPDATE \"%s\".\"%s\" SET abs_next_left_edge = edge_id, "
422  "next_left_edge = -edge_id WHERE abs_next_left_edge = %d",
423  pg_info->toposchema_name, table_name, (int)Line->offset);
424  if (Vect__execute_pg(pg_info->conn, stmt) == -1) {
425  Vect__execute_pg(pg_info->conn, "ROLLBACK");
426  return -1;
427  }
428 
429  /* (2) right next edge */
430  sprintf(stmt, "UPDATE \"%s\".\"%s\" SET abs_next_right_edge = edge_id, "
431  "next_right_edge = edge_id WHERE abs_next_right_edge = %d",
432  pg_info->toposchema_name, table_name, (int)Line->offset);
433  if (Vect__execute_pg(pg_info->conn, stmt) == -1) {
434  Vect__execute_pg(pg_info->conn, "ROLLBACK");
435  return -1;
436  }
437 
438  }
439 
440  /* read the line */
441  if (!Points)
442  Points = Vect_new_line_struct();
443 
444  type = V2_read_line_pg(Map, Points, NULL, line);
445  if (type < 0)
446  return -1;
447 
448  /* delete record from topology table */
449  sprintf(stmt, "DELETE FROM \"%s\".\"%s\" WHERE %s_id = %d",
450  pg_info->toposchema_name, table_name, keycolumn, (int)Line->offset);
451  if (Vect__execute_pg(pg_info->conn, stmt) == -1) {
452  G_warning(_("Unable to delete feature (%s) %d"), keycolumn,
453  (int)line);
454  Vect__execute_pg(pg_info->conn, "ROLLBACK");
455  return -1;
456  }
457 
458  if (pg_info->cache.ctype == CACHE_MAP) {
459  /* delete from cache */
460 
461  Vect_destroy_line_struct(pg_info->cache.lines[line-1]);
462  pg_info->cache.lines[line-1] = NULL;
463  pg_info->cache.lines_types[line-1] = 0;
464  pg_info->cache.lines_cats[line-1] = 0;
465  }
466 
467  /* update topology */
468  ret = delete_line_from_topo_pg(Map, line, type, Points);
469 
470  if (ret == 0)
471  Vect__execute_pg(pg_info->conn, "COMMIT");
472 
473  return ret;
474  }
475 #else
476  G_fatal_error(_("GRASS is not compiled with PostgreSQL support"));
477  return -1;
478 #endif
479 }
480 
481 #ifdef HAVE_POSTGRES
482 /*!
483  \brief Writes node on topological level (PostGIS Topology
484  interface, internal use only)
485 
486  The vector map must be open on level 2 at least with
487  GV_BUILD_BASE. PostGIS Topology schema must be defined.
488 
489  \param Map pointer to Map_info structure
490  \param node node id (starts at 1)
491  \param points pointer to line_pnts structure
492 
493  \return 0 on success
494  \return -1 on error
495 */
496 off_t V2__write_node_pg(struct Map_info *Map, const struct line_pnts *points)
497 {
498  struct Format_info_pg *pg_info;
499 
500  pg_info = &(Map->fInfo.pg);
501 
502  if (!pg_info->toposchema_name)
503  return -1; /* PostGIS Topology required */
504 
505  return write_line_tp(Map, GV_POINT, TRUE, points, NULL);
506 }
507 
508 /*!
509  \brief Writes area on topological level (PostGIS Simple Features
510  interface, internal use only)
511 
512  \param Map pointer to Map_info structure
513  \param points feature geometry (exterior + interior rings)
514  \param nparts number of parts including exterior ring
515  \param cats feature categories
516 
517  \return feature offset
518  \return -1 on error
519 */
520 off_t V2__write_area_pg(struct Map_info *Map,
521  const struct line_pnts **points, int nparts,
522  const struct line_cats *cats)
523 {
524  return write_line_sf(Map, GV_BOUNDARY, points, nparts, cats);
525 }
526 
527 /*!
528  \brief Updates simple features geometry from GRASS-like topo
529 
530  \param Map pointer to Map_info structure
531  \param points feature geometry (exterior + interior rings)
532  \param nparts number of parts including exterior ring
533  \param cat area category
534 
535  \return 0 on success
536  \return -1 on error
537 */
538 int V2__update_area_pg(struct Map_info *Map,
539  const struct line_pnts **points, int nparts,
540  int cat)
541 {
542  int part, npoints;
543  char *stmt, *geom_data;
544 
545  struct Format_info_pg *pg_info;
546 
547  pg_info = &(Map->fInfo.pg);
548 
549  for (part = 0; part < nparts; part++) {
550  npoints = points[part]->n_points - 1;
551  if (points[part]->x[0] != points[part]->x[npoints] ||
552  points[part]->y[0] != points[part]->y[npoints] ||
553  points[part]->z[0] != points[part]->z[npoints]) {
554  G_warning(_("Boundary is not closed. Skipping."));
555  return -1;
556  }
557  }
558 
559  geom_data = line_to_wkb(pg_info, points, nparts, GV_AREA, Vect_is_3d(Map));
560  if (!geom_data)
561  return -1;
562 
563  stmt = NULL;
564  G_asprintf(&stmt, "UPDATE \"%s\".\"%s\" SET %s = '%s'::GEOMETRY WHERE %s = %d",
565  pg_info->schema_name, pg_info->table_name, pg_info->geom_column,
566  geom_data, pg_info->fid_column,
567  cat);
568  if (Vect__execute_pg(pg_info->conn, stmt) == -1) {
569  /* rollback transaction */
570  Vect__execute_pg(pg_info->conn, "ROLLBACK");
571  G_free(geom_data);
572  G_free(stmt);
573  return -1;
574  }
575 
576  G_free(geom_data);
577  G_free(stmt);
578 
579  return 0;
580 }
581 
582 /*!
583  \brief Create new feature table
584 
585  \param pg_info pointer to Format_info_pg
586 
587  \return -1 on error
588  \return 0 on success
589 */
590 int create_table(struct Format_info_pg *pg_info)
591 {
592  int spatial_index, primary_key;
593  char stmt[DB_SQL_MAX];
594  char *geom_type, *def_file;
595 
596  struct field_info *Fi;
597 
598  PGresult *result;
599 
600  def_file = getenv("GRASS_VECTOR_PGFILE");
601 
602  /* by default create spatial index & add primary key */
603  spatial_index = primary_key = TRUE;
604  if (G_find_file2("", def_file ? def_file : "PG", G_mapset())) {
605  FILE *fp;
606  const char *p;
607 
608  struct Key_Value *key_val;
609 
610  fp = G_fopen_old("", def_file ? def_file : "PG", G_mapset());
611  if (!fp) {
612  G_warning(_("Unable to open PG file"));
613  }
614  else {
615  key_val = G_fread_key_value(fp);
616  fclose(fp);
617 
618  /* disable spatial index ? */
619  p = G_find_key_value("spatial_index", key_val);
620  if (p && G_strcasecmp(p, "no") == 0)
621  spatial_index = FALSE;
622 
623  /* disable primary key ? */
624  p = G_find_key_value("primary_key", key_val);
625  if (p && G_strcasecmp(p, "no") == 0)
626  primary_key = FALSE;
627 
628  G_free_key_value(key_val);
629  }
630  }
631 
632  /* create schema if not exists */
633  if (G_strcasecmp(pg_info->schema_name, "public") != 0) {
634  if (check_schema(pg_info) != 0)
635  return -1;
636  }
637 
638  /* prepare CREATE TABLE statement */
639  sprintf(stmt, "CREATE TABLE \"%s\".\"%s\" (%s SERIAL%s, %s INTEGER",
640  pg_info->schema_name, pg_info->table_name, pg_info->fid_column,
641  primary_key ? " PRIMARY KEY" : "", GV_KEY_COLUMN);
642 
643  Fi = pg_info->fi;
644 
645  if (Fi) {
646  /* append attributes */
647  int col, ncols, sqltype, length;
648  char stmt_col[DB_SQL_MAX];
649  const char *colname;
650 
651  dbString dbtable_name;
652 
653  dbDriver *driver;
654  dbTable *table;
655  dbColumn *column;
656 
657  db_init_string(&dbtable_name);
658 
659  driver = open_db(pg_info);
660  if (driver == NULL)
661  return -1;
662 
663  /* describe table */
664  db_set_string(&dbtable_name, Fi->table);
665  if (db_describe_table(driver, &dbtable_name, &table) != DB_OK) {
666  G_warning(_("Unable to describe table <%s>"),
667  Fi->table);
669  pg_info->dbdriver = NULL;
670  return -1;
671  }
672  ncols = db_get_table_number_of_columns(table);
673 
674  G_debug(3,
675  "copying attributes: driver = %s database = %s table = %s cols = %d",
676  Fi->driver, Fi->database, Fi->table, ncols);
677 
678  for (col = 0; col < ncols; col++) {
679  column = db_get_table_column(table, col);
680  colname = db_get_column_name(column);
681  sqltype = db_get_column_sqltype(column);
682  length = db_get_column_length(column);
683 
684  G_debug(3, "\tcolumn = %d name = %s type = %d length = %d",
685  col, colname, sqltype, length);
686 
687  if (G_strcasecmp(pg_info->fid_column, colname) == 0 ||
688  G_strcasecmp(GV_KEY_COLUMN, colname) == 0) {
689  /* skip fid column if exists */
690  G_debug(3, "\t%s skipped", colname);
691  continue;
692  }
693 
694  /* append column */
695  sprintf(stmt_col, ",%s %s", colname, db_sqltype_name(sqltype));
696  strcat(stmt, stmt_col);
697  if (sqltype == DB_SQL_TYPE_CHARACTER) {
698  /* length only for string columns */
699  sprintf(stmt_col, "(%d)", length);
700  strcat(stmt, stmt_col);
701  }
702  }
703 
704  db_free_string(&dbtable_name);
705  }
706  strcat(stmt, ")"); /* close CREATE TABLE statement */
707 
708  /* begin transaction (create table) */
709  if (Vect__execute_pg(pg_info->conn, "BEGIN") == -1) {
710  return -1;
711  }
712 
713  /* create table */
714  if (Vect__execute_pg(pg_info->conn, stmt) == -1) {
715  Vect__execute_pg(pg_info->conn, "ROLLBACK");
716  return -1;
717  }
718 
719  /* determine geometry type (string) */
720  switch (pg_info->feature_type) {
721  case (SF_POINT):
722  geom_type = "POINT";
723  break;
724  case (SF_LINESTRING):
725  geom_type = "LINESTRING";
726  break;
727  case (SF_POLYGON):
728  geom_type = "POLYGON";
729  break;
730  case (SF_POLYGON25D):
731  geom_type = "POLYGONZ";
732  break;
733  case (SF_GEOMETRY):
734  geom_type = "GEOMETRY";
735  break;
736  default:
737  G_warning(_("Unsupported feature type %d"), pg_info->feature_type);
738  Vect__execute_pg(pg_info->conn, "ROLLBACK");
739  return -1;
740  }
741 
742  /* add geometry column */
743  sprintf(stmt, "SELECT AddGeometryColumn('%s', '%s', "
744  "'%s', %d, '%s', %d)",
745  pg_info->schema_name, pg_info->table_name,
746  pg_info->geom_column, pg_info->srid,
747  geom_type, pg_info->coor_dim);
748  G_debug(2, "SQL: %s", stmt);
749  result = PQexec(pg_info->conn, stmt);
750 
751  if (!result || PQresultStatus(result) != PGRES_TUPLES_OK) {
752  G_warning("%s", PQresultErrorMessage(result));
753  PQclear(result);
754  Vect__execute_pg(pg_info->conn, "ROLLBACK");
755  return -1;
756  }
757 
758  /* create indices
759  - GV_KEY_COLUMN
760  - geometry column
761  */
762  sprintf(stmt,
763  "CREATE INDEX %s_%s_idx ON \"%s\".\"%s\" (%s)",
764  pg_info->table_name, GV_KEY_COLUMN,
765  pg_info->schema_name, pg_info->table_name,
766  GV_KEY_COLUMN);
767  if (Vect__execute_pg(pg_info->conn, stmt) == -1) {
768  Vect__execute_pg(pg_info->conn, "ROLLBACK");
769  return -1;
770  }
771 
772  if (spatial_index) {
773  G_verbose_message(_("Building spatial index on <%s>..."),
774  pg_info->geom_column);
775  sprintf(stmt,
776  "CREATE INDEX %s_%s_idx ON \"%s\".\"%s\" USING GIST (%s)",
777  pg_info->table_name, pg_info->geom_column,
778  pg_info->schema_name, pg_info->table_name,
779  pg_info->geom_column);
780 
781  if (Vect__execute_pg(pg_info->conn, stmt) == -1) {
782  Vect__execute_pg(pg_info->conn, "ROLLBACK");
783  return -1;
784  }
785  }
786 
787  /* close transaction (create table) */
788  if (Vect__execute_pg(pg_info->conn, "COMMIT") == -1) {
789  return -1;
790  }
791 
792  return 0;
793 }
794 
795 /*!
796  \brief Creates new schema for feature table if not exists
797 
798  \param pg_info pointer to Format_info_pg
799 
800  \return -1 on error
801  \return 0 on success
802 */
803 int check_schema(const struct Format_info_pg *pg_info)
804 {
805  int i, found, nschema;
806  char stmt[DB_SQL_MAX];
807 
808  PGresult *result;
809 
810  if (!pg_info->conn || !pg_info->table_name) {
811  G_warning(_("No connection defined"));
812  return -1;
813  }
814 
815  /* add geometry column */
816  sprintf(stmt, "SELECT nspname FROM pg_namespace");
817  G_debug(2, "SQL: %s", stmt);
818  result = PQexec(pg_info->conn, stmt);
819 
820  if (!result || PQresultStatus(result) != PGRES_TUPLES_OK) {
821  PQclear(result);
822  Vect__execute_pg(pg_info->conn, "ROLLBACK");
823  return -1;
824  }
825 
826  found = FALSE;
827  nschema = PQntuples(result);
828  for (i = 0; i < nschema && !found; i++) {
829  if (strcmp(pg_info->schema_name, PQgetvalue(result, i, 0)) == 0)
830  found = TRUE;
831  }
832 
833  PQclear(result);
834 
835  if (!found) {
836  sprintf(stmt, "CREATE SCHEMA %s", pg_info->schema_name);
837  if (Vect__execute_pg(pg_info->conn, stmt) == -1) {
838  Vect__execute_pg(pg_info->conn, "ROLLBACK");
839  return -1;
840  }
841  G_warning(_("Schema <%s> doesn't exist, created"),
842  pg_info->schema_name);
843  }
844 
845  return 0;
846 }
847 
848 /*!
849  \brief Create new PostGIS topology schema
850 
851  - create topology schema
852  - add topology column to the feature table
853 
854  \todo Add constraints for grass-like tables
855 
856  \param pg_info pointer to Format_info_pg
857 
858  \return 0 on success
859  \return 1 topology disable, nothing to do
860  \return -1 on failure
861 */
862 int create_topo_schema(struct Format_info_pg *pg_info, int with_z)
863 {
864  double tolerance;
865  char stmt[DB_SQL_MAX];
866  char *def_file;
867 
868  def_file = getenv("GRASS_VECTOR_PGFILE");
869 
870  /* read default values from PG file*/
871  tolerance = 0.;
872  if (G_find_file2("", def_file ? def_file : "PG", G_mapset())) {
873  FILE *fp;
874  const char *p;
875 
876  struct Key_Value *key_val;
877 
878  fp = G_fopen_old("", def_file ? def_file : "PG", G_mapset());
879  if (!fp) {
880  G_fatal_error(_("Unable to open PG file"));
881  }
882  key_val = G_fread_key_value(fp);
883  fclose(fp);
884 
885  /* tolerance */
886  p = G_find_key_value("topo_tolerance", key_val);
887  if (p)
888  tolerance = atof(p);
889  G_debug(1, "PG: tolerance: %f", tolerance);
890 
891  /* topogeom column */
892  p = G_find_key_value("topogeom_name", key_val);
893  if (p)
894  pg_info->topogeom_column = G_store(p);
895  else
896  pg_info->topogeom_column = G_store(TOPOGEOM_COLUMN);
897  G_debug(1, "PG: topogeom_column :%s", pg_info->topogeom_column);
898 
899  /* topo-geo only (default: no) */
900  p = G_find_key_value("topo_geo_only", key_val);
901  if (p && G_strcasecmp(p, "yes") == 0)
902  pg_info->topo_geo_only = TRUE;
903  G_debug(1, "PG: topo_geo_only :%d", pg_info->topo_geo_only);
904 
905  /* build simple features from topogeometry data */
906  p = G_find_key_value("simple_feature", key_val);
907  if (p && G_strcasecmp(p, "yes") == 0)
908  pg_info->topo_geo_only = TRUE;
909  G_debug(1, "PG: topo_geo_only :%d", pg_info->topo_geo_only);
910 
911  G_free_key_value(key_val);
912  }
913 
914  /* begin transaction (create topo schema) */
915  if (Vect__execute_pg(pg_info->conn, "BEGIN") == -1) {
916  return -1;
917  }
918 
919  /* create topology schema */
920  G_verbose_message(_("Creating topology schema <%s>..."),
921  pg_info->toposchema_name);
922  sprintf(stmt, "SELECT topology.createtopology('%s', "
923  "find_srid('%s', '%s', '%s'), %f, '%s')",
924  pg_info->toposchema_name, pg_info->schema_name,
925  pg_info->table_name, pg_info->geom_column, tolerance,
926  with_z == WITH_Z ? "t" : "f");
927  pg_info->toposchema_id = Vect__execute_get_value_pg(pg_info->conn, stmt);
928  if (pg_info->toposchema_id == -1) {
929  Vect__execute_pg(pg_info->conn, "ROLLBACK");
930  return -1;
931  }
932 
933  /* add topo column to the feature table */
934  G_verbose_message(_("Adding new topology column <%s>..."),
935  pg_info->topogeom_column);
936  sprintf(stmt, "SELECT topology.AddTopoGeometryColumn('%s', '%s', '%s', "
937  "'%s', '%s')", pg_info->toposchema_name, pg_info->schema_name,
938  pg_info->table_name, pg_info->topogeom_column,
939  get_sftype(pg_info->feature_type));
940  if (-1 == Vect__execute_get_value_pg(pg_info->conn, stmt)) {
941  Vect__execute_pg(pg_info->conn, "ROLLBACK");
942  return -1;
943  }
944 
945  /* create index on topo column */
946  sprintf(stmt, "CREATE INDEX \"%s_%s_%s_idx\" ON \"%s\".\"%s\" (((%s).id))",
947  pg_info->schema_name, pg_info->table_name, pg_info->topogeom_column,
948  pg_info->schema_name, pg_info->table_name, pg_info->topogeom_column);
949  if (-1 == Vect__execute_pg(pg_info->conn, stmt)) {
950  Vect__execute_pg(pg_info->conn, "ROLLBACK");
951  return -1;
952  }
953 
954  /* change constraints to deferrable initially deferred */
955  if (!pg_info->topo_geo_only) {
956  if (-1 == set_constraint_to_deferrable(pg_info, "node", "face_exists",
957  "containing_face", "face", "face_id") ||
958  -1 == set_constraint_to_deferrable(pg_info, "edge_data", "end_node_exists",
959  "end_node", "node", "node_id") ||
960  -1 == set_constraint_to_deferrable(pg_info, "edge_data", "left_face_exists",
961  "left_face", "face", "face_id") ||
962  -1 == set_constraint_to_deferrable(pg_info, "edge_data", "right_face_exists",
963  "right_face", "face", "face_id") ||
964  -1 == set_constraint_to_deferrable(pg_info, "edge_data", "start_node_exists",
965  "start_node", "node", "node_id"))
966  return -1;
967  }
968 
969  /* create additional tables in topological schema to store
970  GRASS topology in DB */
971  if (!pg_info->topo_geo_only) {
972  /* (1) create 'node_grass' (see P_node struct)
973 
974  todo: add constraints for lines and angles
975  */
976  sprintf(stmt, "CREATE TABLE \"%s\".%s (node_id SERIAL PRIMARY KEY, "
977  "lines integer[], angles float[])", pg_info->toposchema_name, TOPO_TABLE_NODE);
978  if (Vect__execute_pg(pg_info->conn, stmt) == -1) {
979  Vect__execute_pg(pg_info->conn, "ROLLBACK");
980  return -1;
981  }
982 
983  sprintf(stmt, "ALTER TABLE \"%s\".%s ADD CONSTRAINT node_exists "
984  "FOREIGN KEY (node_id) REFERENCES \"%s\".node (node_id) "
985  "DEFERRABLE INITIALLY DEFERRED",
986  pg_info->toposchema_name, TOPO_TABLE_NODE, pg_info->toposchema_name);
987  if (Vect__execute_pg(pg_info->conn, stmt) == -1) {
988  Vect__execute_pg(pg_info->conn, "ROLLBACK");
989  return -1;
990  }
991 
992  /* (2) create 'line_grass' (see P_line struct)
993 
994  */
995  sprintf(stmt, "CREATE TABLE \"%s\".%s (line_id SERIAL PRIMARY KEY, "
996  "left_area integer, right_area integer)",
997  pg_info->toposchema_name, TOPO_TABLE_LINE);
998  if (Vect__execute_pg(pg_info->conn, stmt) == -1) {
999  Vect__execute_pg(pg_info->conn, "ROLLBACK");
1000  return -1;
1001  }
1002 
1003  sprintf(stmt, "ALTER TABLE \"%s\".%s ADD CONSTRAINT line_exists "
1004  "FOREIGN KEY (line_id) REFERENCES \"%s\".edge_data (edge_id) "
1005  "DEFERRABLE INITIALLY DEFERRED",
1006  pg_info->toposchema_name, TOPO_TABLE_LINE, pg_info->toposchema_name);
1007  if (Vect__execute_pg(pg_info->conn, stmt) == -1) {
1008  Vect__execute_pg(pg_info->conn, "ROLLBACK");
1009  return -1;
1010  }
1011 
1012  /* (3) create 'area_grass' (see P_area struct)
1013 
1014  todo: add constraints for lines, centtroid and isles
1015  */
1016  sprintf(stmt, "CREATE TABLE \"%s\".%s (area_id SERIAL PRIMARY KEY, "
1017  "lines integer[], centroid integer, isles integer[])",
1018  pg_info->toposchema_name, TOPO_TABLE_AREA);
1019  if (Vect__execute_pg(pg_info->conn, stmt) == -1) {
1020  Vect__execute_pg(pg_info->conn, "ROLLBACK");
1021  return -1;
1022  }
1023 
1024  /* (4) create 'isle_grass' (see P_isle struct)
1025 
1026  todo: add constraints for lines and area
1027  */
1028  sprintf(stmt, "CREATE TABLE \"%s\".%s (isle_id SERIAL PRIMARY KEY, "
1029  "lines integer[], area integer)",
1030  pg_info->toposchema_name, TOPO_TABLE_ISLE);
1031  if (Vect__execute_pg(pg_info->conn, stmt) == -1) {
1032  Vect__execute_pg(pg_info->conn, "ROLLBACK");
1033  return -1;
1034  }
1035  }
1036 
1037  /* close transaction (create topo schema) */
1038  if (Vect__execute_pg(pg_info->conn, "COMMIT") == -1) {
1039  return -1;
1040  }
1041 
1042  return 0;
1043 }
1044 
1045 /*!
1046  \brief Create new PostGIS layer in given database (internal use only)
1047 
1048  V1_open_new_pg() must be called before this function.
1049 
1050  List of currently supported types:
1051  - GV_POINT (SF_POINT)
1052  - GV_LINE (SF_LINESTRING)
1053  - GV_BOUNDARY (SF_POLYGON)
1054 
1055  When PostGIS Topology the map level is updated to topological level
1056  and build level set to GV_BUILD_BASE.
1057 
1058  \param[in,out] Map pointer to Map_info structure
1059  \param type feature type (GV_POINT, GV_LINE, ...)
1060 
1061  \return 0 success
1062  \return -1 error
1063  */
1064 int create_pg_layer(struct Map_info *Map, int type)
1065 {
1066  int ndblinks;
1067 
1068  struct Format_info_pg *pg_info;
1069 
1070  pg_info = &(Map->fInfo.pg);
1071  if (!pg_info->conninfo) {
1072  G_warning(_("Connection string not defined"));
1073  return -1;
1074  }
1075 
1076  if (!pg_info->table_name) {
1077  G_warning(_("PostGIS feature table not defined"));
1078  return -1;
1079  }
1080 
1081  G_debug(1, "Vect__open_new_pg(): conninfo='%s' table='%s' -> type = %d",
1082  pg_info->conninfo, pg_info->table_name, type);
1083 
1084  /* determine geometry type */
1085 
1086  switch (type) {
1087  case GV_POINT:
1088  case GV_CENTROID:
1089  pg_info->feature_type = SF_POINT;
1090  break;
1091  case GV_LINE:
1092  case GV_BOUNDARY:
1093  pg_info->feature_type = SF_LINESTRING;
1094  break;
1095  case GV_AREA:
1096  pg_info->feature_type = SF_POLYGON;
1097  break;
1098  case GV_FACE:
1099  pg_info->feature_type = SF_POLYGON25D;
1100  break;
1101  case -2:
1102  pg_info->feature_type = SF_GEOMETRY;
1103  break;
1104  default:
1105  G_warning(_("Unsupported geometry type (%d)"), type);
1106  return -1;
1107  }
1108 
1109  /* coordinate dimension */
1110  pg_info->coor_dim = Vect_is_3d(Map) ? 3 : 2;
1111 
1112  /* create new PostGIS table */
1113  ndblinks = Vect_get_num_dblinks(Map);
1114  if (ndblinks > 0) {
1115  pg_info->fi = Vect_get_dblink(Map, 0); /* TODO: support more layers */
1116  if (pg_info->fi) {
1117  if (ndblinks > 1)
1118  G_warning(_("More layers defined, using driver <%s> and "
1119  "database <%s>"),
1120  pg_info->fi->driver, pg_info->fi->database);
1121  }
1122  else {
1123  G_warning(_("Database connection not defined. "
1124  "Unable to write attributes."));
1125  }
1126  }
1127 
1128  /* create new feature table */
1129  if (create_table(pg_info) == -1) {
1130  G_warning(_("Unable to create new PostGIS feature table"));
1131  return -1;
1132  }
1133 
1134  /* create new topology schema (if PostGIS topology support is enabled) */
1135  if (pg_info->toposchema_name) {
1136  /* force topological level */
1137  Map->level = LEVEL_2;
1138  Map->plus.built = GV_BUILD_BASE;
1139 
1140  /* track updated features, used in V2__add_line_to_topo_nat() */
1141  Vect_set_updated(Map, TRUE);
1142 
1143  if (create_topo_schema(pg_info, Vect_is_3d(Map)) == -1) {
1144  G_warning(_("Unable to create new PostGIS topology schema"));
1145  return -1;
1146  }
1147  }
1148 
1149  return 0;
1150 }
1151 
1152 /*!
1153  \brief Get simple feature type as a string
1154 
1155  Used for AddTopoGeometryColumn().
1156 
1157  Valid types:
1158  - SF_POINT
1159  - SF_LINESTRING
1160  - SF_POLYGON
1161 
1162  \return string with feature type
1163  \return empty string
1164 */
1165 char *get_sftype(SF_FeatureType sftype)
1166 {
1167  if (sftype == SF_POINT)
1168  return "POINT";
1169  else if (sftype == SF_LINESTRING)
1170  return "LINE";
1171  else if (sftype == SF_POLYGON)
1172  return "POLYGON";
1173  else if (sftype == SF_GEOMETRY || sftype == SF_GEOMETRYCOLLECTION)
1174  return "COLLECTION";
1175  else
1176  G_warning(_("Unsupported feature type %d"), sftype);
1177 
1178  return "";
1179 }
1180 
1181 /*!
1182  \brief Write vector features as PostGIS simple feature element
1183 
1184  \param Map pointer to Map_info structure
1185  \param type feature type (GV_POINT, GV_LINE, ...)
1186  \param points feature geometry (exterior + interior rings for polygonsx)
1187  \param nparts number of parts
1188  \param cats feature categories
1189 
1190  \return feature offset
1191  \return -1 on error
1192 */
1193 off_t write_line_sf(struct Map_info *Map, int type,
1194  const struct line_pnts **points, int nparts,
1195  const struct line_cats *cats)
1196 {
1197  int cat;
1198  off_t offset;
1199 
1200  SF_FeatureType sf_type;
1201 
1202  struct Format_info_pg *pg_info;
1203  struct Format_info_offset *offset_info;
1204 
1205  pg_info = &(Map->fInfo.pg);
1206  offset_info = &(pg_info->offset);
1207 
1208  if (nparts < 1)
1209  return -1;
1210 
1211  /* check required PG settings */
1212  if (!pg_info->conn) {
1213  G_warning(_("No connection defined"));
1214  return -1;
1215  }
1216  if (!pg_info->table_name) {
1217  G_warning(_("PostGIS feature table not defined"));
1218  return -1;
1219  }
1220 
1221  /* create PostGIS table if doesn't exist */
1222  if (pg_info->feature_type == SF_GEOMETRY) {
1223  if (create_pg_layer(Map, type) < 0)
1224  return -1;
1225  }
1226 
1227  /* get category & check for attributes */
1228  cat = -1;
1229  if (cats->n_cats > 0) {
1230  int field;
1231 
1232  if (pg_info->fi)
1233  field = pg_info->fi->number;
1234  else
1235  field = 1;
1236 
1237  if (!Vect_cat_get(cats, field, &cat))
1238  G_warning(_("No category defined for layer %d"), field);
1239  if (cats->n_cats > 1) {
1240  G_warning(_("Feature has more categories, using "
1241  "category %d (from layer %d)"),
1242  cat, field);
1243  }
1244  }
1245 
1246  sf_type = pg_info->feature_type;
1247 
1248  /* determine matching PostGIS feature geometry type */
1249  if (type & (GV_POINT | GV_KERNEL)) {
1250  if (sf_type != SF_POINT && sf_type != SF_POINT25D) {
1251  G_warning(_("Point skipped (output feature type: %s)"),
1253  return 0;
1254  }
1255  }
1256  else if (type & GV_LINE) {
1257  if (sf_type != SF_LINESTRING && sf_type != SF_LINESTRING25D) {
1258  G_warning(_("Line skipped (output feature type: %s)"),
1260  return 0;
1261  }
1262  }
1263  else if (type & GV_CENTROID) {
1264  if (sf_type != SF_POLYGON && sf_type != SF_POINT) {
1265  G_warning(_("Centroid skipped (output feature type: %s)"),
1267  return 0;
1268  }
1269  }
1270  else if (type & GV_BOUNDARY) {
1271  if (sf_type != SF_POLYGON && sf_type != SF_LINESTRING) {
1272  G_warning(_("Boundary skipped (output feature type: %s)"),
1274  return 0;
1275  }
1276  }
1277  else if (type & GV_FACE) {
1278  if (sf_type != SF_POLYGON25D) {
1279  G_warning(_("Face skipped (output feature type: %s)"),
1281  return 0;
1282  }
1283  }
1284  else {
1285  G_warning(_("Unsupported feature type %d"), type);
1286  return -1;
1287  }
1288 
1289  G_debug(3, "write_line_sf(): type = %d n_points = %d cat = %d",
1290  type, points[0]->n_points, cat);
1291 
1292  if (sf_type == SF_POLYGON || sf_type == SF_POLYGON25D) {
1293  /* skip this check when writing PostGIS topology */
1294  int part, npoints;
1295 
1296  for (part = 0; part < nparts; part++) {
1297  npoints = points[part]->n_points - 1;
1298  if (points[part]->x[0] != points[part]->x[npoints] ||
1299  points[part]->y[0] != points[part]->y[npoints] ||
1300  points[part]->z[0] != points[part]->z[npoints]) {
1301  G_warning(_("Boundary is not closed. Skipping."));
1302  return -1;
1303  }
1304  }
1305  }
1306 
1307  /* write feature's geometry and fid */
1308  if (-1 == write_feature(Map, -1, type, points, nparts, cat)) {
1309  Vect__execute_pg(pg_info->conn, "ROLLBACK");
1310  return -1;
1311  }
1312 
1313  /* update offset array */
1314  if (offset_info->array_num >= offset_info->array_alloc) {
1315  offset_info->array_alloc += 1000;
1316  offset_info->array = (int *)G_realloc(offset_info->array,
1317  offset_info->array_alloc *
1318  sizeof(int));
1319  }
1320  offset = offset_info->array_num;
1321 
1322  offset_info->array[offset_info->array_num++] = cat;
1323  if (sf_type == SF_POLYGON || sf_type == SF_POLYGON25D) {
1324  /* register first part in offset array */
1325  offset_info->array[offset_info->array_num++] = 0;
1326  }
1327  G_debug(3, "write_line_sf(): -> offset = %lu offset_num = %d cat = %d",
1328  (unsigned long)offset, offset_info->array_num, cat);
1329 
1330  return offset;
1331 }
1332 
1333 /*!
1334  \brief Write vector feature in PostGIS topology schema and
1335  updates internal topology structures
1336 
1337  \param Map vector map
1338  \param type feature type to be written
1339  \param points feature geometry
1340  \param is_node TRUE for nodes (written as points)
1341 
1342  \return feature id (build level >= GV_BUILD_BASE otherwise 0)
1343  \return 0 for nodes
1344  \return -1 on error
1345 */
1346 off_t write_line_tp(struct Map_info *Map, int type, int is_node,
1347  const struct line_pnts *points,
1348  const struct line_cats *cats)
1349 {
1350  int line, cat, line_id;
1351 
1352  struct Format_info_pg *pg_info;
1353  struct Plus_head *plus;
1354  struct field_info *Fi;
1355 
1356  pg_info = &(Map->fInfo.pg);
1357  plus = &(Map->plus);
1358 
1359  if (!(plus->update_cidx)) {
1360  plus->cidx_up_to_date = FALSE; /* category index will be outdated */
1361  }
1362 
1363  /* check type for nodes */
1364  if (is_node && type != GV_POINT) {
1365  G_warning(_("Invalid feature type (%d) for nodes"), type);
1366  return -1;
1367  }
1368 
1369  /* check required PG settings */
1370  if (!pg_info->conn) {
1371  G_warning(_("No connection defined"));
1372  return -1;
1373  }
1374  if (!pg_info->table_name) {
1375  G_warning(_("PostGIS feature table not defined"));
1376  return -1;
1377  }
1378  if (!pg_info->toposchema_name) {
1379  G_warning(_("PostGIS topology schema not defined"));
1380  return -1;
1381  }
1382 
1383  /* create PostGIS table if doesn't exist */
1384  if (pg_info->feature_type == SF_GEOMETRY) {
1385  if (create_pg_layer(Map, type) < 0)
1386  return -1;
1387  }
1388 
1389  if (!points)
1390  return 0;
1391 
1392  G_debug(3, "write_line_pg(): type = %d n_points = %d",
1393  type, points->n_points);
1394 
1395  Fi = pg_info->fi;
1396 
1397  cat = -1;
1398  if (cats && cats->n_cats > 0) {
1399  if (Fi) {
1400  if (!pg_info->dbdriver)
1401  open_db(pg_info);
1402  if (!Vect_cat_get(cats, Fi->number, &cat))
1403  G_warning(_("No category defined for layer %d"), Fi->number);
1404  if (cats->n_cats > 1) {
1405  G_warning(_("Feature has more categories, using "
1406  "category %d (from layer %d)"),
1407  cat, cats->field[0]);
1408  }
1409  }
1410  /* assume layer=1 */
1411  Vect_cat_get(cats, 1, &cat);
1412  }
1413 
1414  /* update GRASS topology before writing PostGIS feature */
1415  line = 0;
1416  if (plus->built >= GV_BUILD_BASE) {
1417  if (is_node) {
1418  /* nodes are given with negative id */
1419  line = -1 * dig_add_node(plus, points->x[0], points->y[0], points->z[0]);
1420  }
1421  else {
1422  off_t offset;
1423 
1424  /* better is probably to check nextval directly */
1425  if (type & GV_POINTS) {
1426  offset = Vect_get_num_primitives(Map, GV_POINTS) + 1; /* next */
1427  offset += Vect_get_num_nodes(Map); /* nodes are also stored in 'node' table */
1428  }
1429  else { /* LINES */
1430  offset = Vect_get_num_primitives(Map, GV_LINES) + 1; /* next */
1431  }
1432 
1433  line = add_line_to_topo_pg(Map, offset, type, points);
1434  }
1435  }
1436 
1437  /* write new feature to PostGIS
1438  - feature table for simple features
1439  - feature table and topo schema for topological access
1440  */
1441  line_id = write_feature(Map, line, type, &points, 1, cat);
1442  if (line_id < 0) {
1443  Vect__execute_pg(pg_info->conn, "ROLLBACK");
1444  return -1;
1445  }
1446 
1447  if (pg_info->cache.ctype == CACHE_MAP) {
1448  /* add line to the cache */
1449  Vect__reallocate_cache(&(pg_info->cache), 1, TRUE);
1450  pg_info->cache.lines[line-1] = Vect_new_line_struct();
1451  pg_info->cache.lines_types[line-1] = type;
1452  pg_info->cache.lines_cats[line-1] = cat;
1453  }
1454 
1455  /* update offset array for nodes */
1456  if (is_node) {
1457  int node;
1458 
1459  struct Format_info_offset *offset;
1460 
1461  offset = &(pg_info->offset);
1462 
1463  node = abs(line);
1464  if (node > offset->array_alloc) {
1465  offset->array_alloc += 1000;
1466  offset->array = (int *) G_realloc (offset->array, offset->array_alloc * sizeof(int));
1467  }
1468 
1469  offset->array_num = node;
1470  offset->array[node-1] = (int) line_id; /* node id starts at 1 */
1471  }
1472 
1473  /* update PostGIS-line topo */
1474  if (plus->built >= GV_BUILD_AREAS && type == GV_BOUNDARY)
1475  update_topo_face(Map, line); /* TODO: avoid extra statements */
1476 
1477  return !is_node ? line : 0;
1478 }
1479 
1480 /*!
1481  \brief Binary data to HEX
1482 
1483  Allocated buffer should be freed by G_free().
1484 
1485  \param nbytes number of bytes to allocate
1486  \param wkb_data WKB data
1487 
1488  \return allocated buffer with HEX data
1489  */
1490 char *binary_to_hex(int nbytes, const unsigned char *wkb_data)
1491 {
1492  char *hex_data;
1493  int i, nlow, nhigh;
1494  static const char ach_hex[] = "0123456789ABCDEF";
1495 
1496  hex_data = (char *)G_malloc(nbytes * 2 + 1);
1497  hex_data[nbytes * 2] = '\0';
1498 
1499  for (i = 0; i < nbytes; i++) {
1500  nlow = wkb_data[i] & 0x0f;
1501  nhigh = (wkb_data[i] & 0xf0) >> 4;
1502 
1503  hex_data[i * 2] = ach_hex[nhigh];
1504  hex_data[i * 2 + 1] = ach_hex[nlow];
1505  }
1506 
1507  return hex_data;
1508 }
1509 
1510 /*!
1511  \brief Write point into WKB buffer
1512 
1513  See OGRPoint::exportToWkb from GDAL/OGR library
1514 
1515  \param byte_order byte order (ENDIAN_LITTLE or BIG_ENDIAN)
1516  \param points feature geometry
1517  \param with_z WITH_Z for 3D data
1518  \param[out] nsize buffer size
1519 
1520  \return allocated WKB buffer
1521  \return NULL on error
1522  */
1523 unsigned char *point_to_wkb(int byte_order,
1524  const struct line_pnts *points, int with_z,
1525  int *nsize)
1526 {
1527  unsigned char *wkb_data;
1528  unsigned int sf_type;
1529 
1530  if (points->n_points != 1)
1531  return NULL;
1532 
1533  /* allocate buffer */
1534  *nsize = with_z ? 29 : 21;
1535  wkb_data = G_malloc(*nsize);
1536  G_zero(wkb_data, *nsize);
1537 
1538  G_debug(5, "\t->point size=%d (with_z = %d)", *nsize, with_z);
1539 
1540  /* set the byte order */
1541  if (byte_order == ENDIAN_LITTLE)
1542  wkb_data[0] = '\001';
1543  else
1544  wkb_data[0] = '\000';
1545 
1546  /* set the geometry feature type */
1547  sf_type = with_z ? SF_POINT25D : SF_POINT;
1548 
1549  if (byte_order == ENDIAN_LITTLE)
1550  sf_type = LSBWORD32(sf_type);
1551  else
1552  sf_type = MSBWORD32(sf_type);
1553  memcpy(wkb_data + 1, &sf_type, 4);
1554 
1555  /* copy in the raw data */
1556  memcpy(wkb_data + 5, &(points->x[0]), 8);
1557  memcpy(wkb_data + 5 + 8, &(points->y[0]), 8);
1558 
1559  if (with_z) {
1560  memcpy(wkb_data + 5 + 16, &(points->z[0]), 8);
1561  }
1562 
1563  /* swap if needed */
1564  if (byte_order == ENDIAN_BIG) {
1565  SWAPDOUBLE(wkb_data + 5);
1566  SWAPDOUBLE(wkb_data + 5 + 8);
1567 
1568  if (with_z)
1569  SWAPDOUBLE(wkb_data + 5 + 16);
1570  }
1571 
1572  return wkb_data;
1573 }
1574 
1575 /*!
1576  \bried Write linestring into WKB buffer
1577 
1578  See OGRLineString::exportToWkb from GDAL/OGR library
1579 
1580  \param byte_order byte order (ENDIAN_LITTLE or ENDIAN_BIG)
1581  \param points feature geometry
1582  \param with_z WITH_Z for 3D data
1583  \param[out] nsize buffer size
1584 
1585  \return allocated WKB buffer
1586  \return NULL on error
1587  */
1588 unsigned char *linestring_to_wkb(int byte_order,
1589  const struct line_pnts *points, int with_z,
1590  int *nsize)
1591 {
1592  int i, point_size;
1593  unsigned char *wkb_data;
1594  unsigned int sf_type;
1595 
1596  if (points->n_points < 1)
1597  return NULL;
1598 
1599  /* allocate buffer */
1600  point_size = 8 * (with_z ? 3 : 2);
1601  *nsize = 5 + 4 + points->n_points * point_size;
1602  wkb_data = G_malloc(*nsize);
1603  G_zero(wkb_data, *nsize);
1604 
1605  G_debug(5, "\t->linestring size=%d (with_z = %d)", *nsize, with_z);
1606 
1607  /* set the byte order */
1608  if (byte_order == ENDIAN_LITTLE)
1609  wkb_data[0] = '\001';
1610  else
1611  wkb_data[0] = '\000';
1612 
1613  /* set the geometry feature type */
1614  sf_type = with_z ? SF_LINESTRING25D : SF_LINESTRING;
1615 
1616  if (byte_order == ENDIAN_LITTLE)
1617  sf_type = LSBWORD32(sf_type);
1618  else
1619  sf_type = MSBWORD32(sf_type);
1620  memcpy(wkb_data + 1, &sf_type, 4);
1621 
1622  /* copy in the data count */
1623  memcpy(wkb_data + 5, &(points->n_points), 4);
1624 
1625  /* copy in the raw data */
1626  for (i = 0; i < points->n_points; i++) {
1627  memcpy(wkb_data + 9 + point_size * i, &(points->x[i]), 8);
1628  memcpy(wkb_data + 9 + 8 + point_size * i, &(points->y[i]), 8);
1629 
1630  if (with_z) {
1631  memcpy(wkb_data + 9 + 16 + point_size * i, &(points->z[i]), 8);
1632  }
1633  }
1634 
1635  /* swap if needed */
1636  if (byte_order == ENDIAN_BIG) {
1637  int npoints, nitems;
1638 
1639  npoints = SWAP32(points->n_points);
1640  memcpy(wkb_data + 5, &npoints, 4);
1641 
1642  nitems = (with_z ? 3 : 2) * points->n_points;
1643  for (i = 0; i < nitems; i++) {
1644  SWAPDOUBLE(wkb_data + 9 + 4 + 8 * i);
1645  }
1646  }
1647 
1648  return wkb_data;
1649 }
1650 
1651 /*!
1652  \bried Write polygon into WKB buffer
1653 
1654  See OGRPolygon::exportToWkb from GDAL/OGR library
1655 
1656  \param byte_order byte order (ENDIAN_LITTLE or ENDIAN_BIG)
1657  \param ipoints list of ring geometries (first is outer ring)
1658  \param nrings number of rings
1659  \param with_z WITH_Z for 3D data
1660  \param[out] nsize buffer size
1661 
1662  \return allocated WKB buffer
1663  \return NULL on error
1664  */
1665 unsigned char *polygon_to_wkb(int byte_order,
1666  const struct line_pnts** points, int nrings,
1667  int with_z, int *nsize)
1668 {
1669  int i, ring, point_size, offset;
1670  unsigned char *wkb_data;
1671  unsigned int sf_type;
1672 
1673  /* check data validity */
1674  if (nrings < 1)
1675  return NULL;
1676  for (ring = 0; ring < nrings; ring++) {
1677  if (points[ring]->n_points < 3)
1678  return NULL;
1679  }
1680 
1681  /* allocate buffer */
1682  point_size = 8 * (with_z ? 3 : 2);
1683  *nsize = 9;
1684  for (ring = 0; ring < nrings; ring++)
1685  *nsize += 4 + point_size * points[ring]->n_points;
1686  wkb_data = G_malloc(*nsize);
1687  G_zero(wkb_data, *nsize);
1688 
1689  G_debug(5, "\t->polygon size=%d (with_z = %d)", *nsize, with_z);
1690 
1691  /* set the byte order */
1692  if (byte_order == ENDIAN_LITTLE)
1693  wkb_data[0] = '\001';
1694  else
1695  wkb_data[0] = '\000';
1696 
1697  /* set the geometry feature type */
1698  sf_type = with_z ? SF_POLYGON25D : SF_POLYGON;
1699 
1700  if (byte_order == ENDIAN_LITTLE)
1701  sf_type = LSBWORD32(sf_type);
1702  else
1703  sf_type = MSBWORD32(sf_type);
1704  memcpy(wkb_data + 1, &sf_type, 4);
1705 
1706  /* copy in the raw data */
1707  if (byte_order == ENDIAN_BIG) {
1708  int ncount;
1709 
1710  ncount = SWAP32(nrings);
1711  memcpy(wkb_data + 5, &ncount, 4);
1712  }
1713  else {
1714  memcpy(wkb_data + 5, &nrings, 4);
1715  }
1716 
1717  /* serialize rings */
1718  offset = 9;
1719  for (ring = 0; ring < nrings; ring++) {
1720  memcpy(wkb_data + offset, &(points[ring]->n_points), 4);
1721  for (i = 0; i < points[ring]->n_points; i++) {
1722  memcpy(wkb_data + offset +
1723  4 + point_size * i, &(points[ring]->x[i]), 8);
1724  memcpy(wkb_data + offset +
1725  4 + 8 + point_size * i, &(points[ring]->y[i]), 8);
1726 
1727  if (with_z) {
1728  memcpy(wkb_data + offset +
1729  4 + 16 + point_size * i, &(points[ring]->z[i]), 8);
1730  }
1731  }
1732 
1733  offset += 4 + point_size * points[ring]->n_points;
1734 
1735  /* swap if needed */
1736  if (byte_order == ENDIAN_BIG) {
1737  int npoints, nitems;
1738 
1739  npoints = SWAP32(points[ring]->n_points);
1740  memcpy(wkb_data + 5, &npoints, 4);
1741 
1742  nitems = (with_z ? 3 : 2) * points[ring]->n_points;
1743  for (i = 0; i < nitems; i++) {
1744  SWAPDOUBLE(wkb_data + offset + 4 + 8 * i);
1745  }
1746  }
1747  }
1748 
1749  return wkb_data;
1750 }
1751 
1752 /*!
1753  \brief Write feature to WKB buffer
1754 
1755  Allocated string buffer should be freed by G_free().
1756 
1757  \param pg_info pointer to Format_info_pg struct
1758  \param points array of geometries which form feature
1759  \param nparts number of geometries in array
1760  \param type feature type (GV_POINT, GV_LINE, ...)
1761  \param with_z WITH_Z for 3D data
1762 
1763  \return allocated string buffer
1764  \return NULL on error
1765 */
1766 char *line_to_wkb(struct Format_info_pg *pg_info,
1767  const struct line_pnts **points, int nparts, int type, int with_z)
1768 {
1769  int byte_order, nbytes, nsize;
1770  unsigned int sf_type;
1771 
1772  unsigned char *wkb_data;
1773  char *text_data, *text_data_p, *hex_data;
1774 
1775  byte_order = dig__byte_order_out();
1776 
1777  /* get wkb data */
1778  nbytes = -1;
1779  wkb_data = NULL;
1780  if (type & GV_POINTS) /* point or centroid */
1781  wkb_data = point_to_wkb(byte_order, points[0], with_z, &nbytes);
1782  else if (type == GV_LINE ||
1783  (type == GV_BOUNDARY && pg_info->feature_type == SF_LINESTRING))
1784  wkb_data = linestring_to_wkb(byte_order, points[0], with_z, &nbytes);
1785  else if (type & (GV_BOUNDARY | GV_FACE | GV_AREA)) {
1786  if (!pg_info->toposchema_name || type == GV_AREA) {
1787  /* PostGIS simple feature access */
1788  wkb_data = polygon_to_wkb(byte_order, points, nparts,
1789  with_z, &nbytes);
1790  }
1791  else {
1792  /* PostGIS topology access */
1793  wkb_data = linestring_to_wkb(byte_order, points[0], with_z, &nbytes);
1794  }
1795  }
1796 
1797  if (!wkb_data || nbytes < 1) {
1798  G_warning(_("Unsupported feature type %d"), type);
1799  return NULL;
1800  }
1801 
1802  /* When converting to hex, each byte takes 2 hex characters. In
1803  addition we add in 8 characters to represent the SRID integer
1804  in hex, and one for a null terminator */
1805  nsize = nbytes * 2 + 8 + 1;
1806  text_data = text_data_p = (char *)G_malloc(nsize);
1807 
1808  /* convert the 1st byte, which is the endianess flag, to hex */
1809  hex_data = binary_to_hex(1, wkb_data);
1810  strcpy(text_data_p, hex_data);
1811  G_free(hex_data);
1812  text_data_p += 2;
1813 
1814  /* get the geom type which is bytes 2 through 5 */
1815  memcpy(&sf_type, wkb_data + 1, 4);
1816 
1817  /* add the SRID flag if an SRID is provided */
1818  if (pg_info->srid > 0) {
1819  unsigned int srs_flag;
1820 
1821  /* change the flag to little endianess */
1822  srs_flag = LSBWORD32(WKBSRIDFLAG);
1823  /* apply the flag */
1824  sf_type = sf_type | srs_flag;
1825  }
1826 
1827  /* write the geom type which is 4 bytes */
1828  hex_data = binary_to_hex(4, (unsigned char *)&sf_type);
1829  strcpy(text_data_p, hex_data);
1830  G_free(hex_data);
1831  text_data_p += 8;
1832 
1833  /* include SRID if provided */
1834  if (pg_info->srid > 0) {
1835  unsigned int srs_id;
1836 
1837  /* force the srsid to little endianess */
1838  srs_id = LSBWORD32(pg_info->srid);
1839  hex_data = binary_to_hex(sizeof(srs_id), (unsigned char *)&srs_id);
1840  strcpy(text_data_p, hex_data);
1841  G_free(hex_data);
1842  text_data_p += 8;
1843  }
1844 
1845  /* copy the rest of the data over - subtract 5 since we already
1846  copied 5 bytes above */
1847  hex_data = binary_to_hex(nbytes - 5, wkb_data + 5);
1848  strcpy(text_data_p, hex_data);
1849  G_free(hex_data);
1850 
1851  return text_data;
1852 }
1853 
1854 /*!
1855  \brief Insert feature into table
1856 
1857  \param Map pointer to Map_info structure
1858  \param line feature id (topo access only)
1859  \param type feature type (GV_POINT, GV_LINE, ...)
1860  \param points pointer to line_pnts struct
1861  \param nparts number of parts (rings for polygon)
1862  \param cat category number (-1 for no category)
1863 
1864  \return topo_id for PostGIS Topology
1865  \return 0 for simple features access
1866  \return -1 on error
1867  */
1868 int write_feature(struct Map_info *Map, int line, int type,
1869  const struct line_pnts **points, int nparts, int cat)
1870 {
1871  int with_z, topo_id;
1872  char *stmt, *geom_data;
1873 
1874  struct Format_info_pg *pg_info;
1875 
1876  pg_info = &(Map->fInfo.pg);
1877  with_z = Map->head.with_z;
1878 
1879  if (with_z && pg_info->coor_dim != 3) {
1880  G_warning(_("Trying to insert 3D data into feature table "
1881  "which store 2D data only"));
1882  return -1;
1883  }
1884  if (!with_z && pg_info->coor_dim != 2) {
1885  G_warning(_("Trying to insert 2D data into feature table "
1886  "which store 3D data only"));
1887  return -1;
1888  }
1889 
1890  /* build WKB geometry from line_pnts structures */
1891  geom_data = line_to_wkb(pg_info, points, nparts, type, with_z);
1892  if (!geom_data)
1893  return -1;
1894 
1895  /* start transaction */
1896  if (!pg_info->inTransaction) {
1897  pg_info->inTransaction = TRUE;
1898  if (Vect__execute_pg(pg_info->conn, "BEGIN") == -1) {
1899  G_free(geom_data);
1900  return -1;
1901  }
1902  }
1903 
1904  /* write feature in PostGIS topology schema if enabled */
1905  topo_id = -1;
1906  if (pg_info->toposchema_name) {
1907  /* insert feature into topology schema (node or edge) */
1908  topo_id = insert_topo_element(Map, line, type, geom_data);
1909  if (topo_id < 0) {
1910  G_warning(_("Unable to insert topological element into PostGIS Topology schema"));
1911  G_free(geom_data);
1912 
1913  return -1;
1914  }
1915  }
1916 
1917  /* build INSERT statement
1918  simple feature geometry + attributes
1919  */
1920  stmt = build_insert_stmt(pg_info, geom_data, topo_id, cat);
1921 
1922  /* stmt can NULL when writing PostGIS topology with no attributes
1923  * attached */
1924  if (stmt && Vect__execute_pg(pg_info->conn, stmt) == -1) {
1925  /* rollback transaction */
1926  Vect__execute_pg(pg_info->conn, "ROLLBACK");
1927  G_free(geom_data);
1928  G_free(stmt);
1929 
1930  return -1;
1931  }
1932  G_free(geom_data);
1933  G_free(stmt);
1934 
1935  return pg_info->toposchema_name ? topo_id : 0;
1936 }
1937 
1938 /*!
1939  \brief Build INSERT statement to add new feature to the feature
1940  table
1941 
1942  Note: Allocated string should be freed.
1943 
1944  \param pg_info pointer to Format_info_pg structure
1945  \param geom_data geometry data
1946  \param type feature type (GV_POINT, GV_LINE, ...) - (only for PostGIS Topology)
1947  \param id topology element id (only for PostGIS Topology)
1948  \param cat category number (or -1 for no category)
1949  \param Fi pointer to field_info structure (NULL for no attributes)
1950 
1951  \return allocated string with INSERT statement
1952  */
1953 char *build_insert_stmt(const struct Format_info_pg *pg_info,
1954  const char *geom_data, int topo_id, int cat)
1955 {
1956  int topogeom_type;
1957 
1958  char *stmt, buf[DB_SQL_MAX];
1959 
1960  struct field_info *Fi;
1961 
1962  topogeom_type = -1;
1963  if (pg_info->toposchema_name) {
1964  switch (pg_info->feature_type) {
1965  case SF_POINT:
1966  topogeom_type = 1;
1967  break;
1968  case SF_LINESTRING:
1969  topogeom_type = 2;
1970  break;
1971  case SF_POLYGON:
1972  topogeom_type = 3;
1973  break;
1974  default:
1975  G_warning(_("Unsupported feature type %d"), pg_info->feature_type);
1976  return NULL;
1977  }
1978  }
1979 
1980  Fi = pg_info->fi;
1981 
1982  stmt = NULL;
1983  if (Fi && cat > -1) {
1984  /* write attributes (simple features and topology elements) */
1985  int col, ncol, more;
1986  int sqltype, ctype, is_fid;
1987  char buf_val[DB_SQL_MAX], buf_tmp[DB_SQL_MAX];
1988 
1989  const char *colname;
1990 
1991  dbString dbstmt;
1992  dbCursor cursor;
1993  dbTable *table;
1994  dbColumn *column;
1995  dbValue *value;
1996 
1997  db_init_string(&dbstmt);
1998  buf_val[0] = '\0';
1999 
2000  /* read & set attributes */
2001  sprintf(buf, "SELECT * FROM %s WHERE %s = %d", Fi->table, Fi->key,
2002  cat);
2003  G_debug(4, "SQL: %s", buf);
2004  db_set_string(&dbstmt, buf);
2005 
2006  /* prepare INSERT statement */
2007  sprintf(buf, "INSERT INTO \"%s\".\"%s\" (",
2008  pg_info->schema_name, pg_info->table_name);
2009 
2010  /* select data */
2011  if (db_open_select_cursor(pg_info->dbdriver, &dbstmt,
2012  &cursor, DB_SEQUENTIAL) != DB_OK) {
2013  G_warning(_("Unable to select attributes for category %d"), cat);
2014  }
2015  else {
2016  if (db_fetch(&cursor, DB_NEXT, &more) != DB_OK) {
2017  G_warning(_("Unable to fetch data from table <%s>"),
2018  Fi->table);
2019  }
2020 
2021  if (!more) {
2022  G_warning(_("No database record for category %d, "
2023  "no attributes will be written"), cat);
2024  }
2025  else {
2026  table = db_get_cursor_table(&cursor);
2027  ncol = db_get_table_number_of_columns(table);
2028 
2029  for (col = 0; col < ncol; col++) {
2030  column = db_get_table_column(table, col);
2031  colname = db_get_column_name(column);
2032 
2033  /* -> values */
2034  value = db_get_column_value(column);
2035  /* for debug only */
2036  db_convert_column_value_to_string(column, &dbstmt);
2037  G_debug(3, "col %d : val = %s", col,
2038  db_get_string(&dbstmt));
2039 
2040  sqltype = db_get_column_sqltype(column);
2041  ctype = db_sqltype_to_Ctype(sqltype);
2042 
2043  is_fid = strcmp(pg_info->fid_column, colname) == 0;
2044 
2045  /* check fid column (must be integer) */
2046  if (is_fid == TRUE &&
2047  ctype != DB_C_TYPE_INT) {
2048  G_warning(_("FID column must be integer, column <%s> ignored!"),
2049  colname);
2050  continue;
2051  }
2052 
2053  /* -> columns */
2054  sprintf(buf_tmp, "%s", colname);
2055  strcat(buf, buf_tmp);
2056  if (col < ncol - 1)
2057  strcat(buf, ",");
2058 
2059  /* prevent writing NULL values */
2060  if (!db_test_value_isnull(value)) {
2061  switch (ctype) {
2062  case DB_C_TYPE_INT:
2063  sprintf(buf_tmp, "%d", db_get_value_int(value));
2064  break;
2065  case DB_C_TYPE_DOUBLE:
2066  sprintf(buf_tmp, "%.14f",
2067  db_get_value_double(value));
2068  break;
2069  case DB_C_TYPE_STRING: {
2070  char *value_tmp;
2071  value_tmp = G_str_replace(db_get_value_string(value), "'", "''");
2072  sprintf(buf_tmp, "'%s'", value_tmp);
2073  G_free(value_tmp);
2074  break;
2075  }
2076  case DB_C_TYPE_DATETIME:
2078  &dbstmt);
2079  sprintf(buf_tmp, "%s", db_get_string(&dbstmt));
2080  break;
2081  default:
2082  G_warning(_("Unsupported column type %d"), ctype);
2083  sprintf(buf_tmp, "NULL");
2084  break;
2085  }
2086  }
2087  else {
2088  if (is_fid == TRUE)
2089  G_warning(_("Invalid value for FID column: NULL"));
2090  sprintf(buf_tmp, "NULL");
2091  }
2092  strcat(buf_val, buf_tmp);
2093  if (col < ncol - 1)
2094  strcat(buf_val, ",");
2095  }
2096 
2097  if (!pg_info->toposchema_name) {
2098  /* simple feature access */
2099  G_asprintf(&stmt, "%s,%s) VALUES (%s,'%s'::GEOMETRY)",
2100  buf, pg_info->geom_column, buf_val, geom_data);
2101  }
2102  else {
2103  /* PostGIS topology access, write geometry in
2104  * topology schema, skip geometry at this point */
2105  if (buf[strlen(buf)-1] == ',') { /* last column skipped */
2106  buf[strlen(buf)-1] = '\0';
2107  buf_val[strlen(buf_val)-1] = '\0';
2108  }
2109  G_asprintf(&stmt, "%s, %s) VALUES (%s, '(%d, 1, %d, %d)'::topology.TopoGeometry)",
2110  buf, pg_info->topogeom_column, buf_val,
2111  pg_info->toposchema_id, topo_id, topogeom_type);
2112  }
2113  }
2114  }
2115  }
2116  else {
2117  /* no attributes */
2118  if (!pg_info->toposchema_name) {
2119  /* no attributes (simple features access) */
2120  if (cat > 0) {
2121  /* cetegory defined */
2122  G_asprintf(&stmt, "INSERT INTO \"%s\".\"%s\" (%s,%s) VALUES "
2123  "(%d, '%s'::GEOMETRY)",
2124  pg_info->schema_name, pg_info->table_name,
2125  GV_KEY_COLUMN, pg_info->geom_column,
2126  cat, geom_data);
2127  }
2128  else {
2129  /* no category */
2130  G_asprintf(&stmt, "INSERT INTO \"%s\".\"%s\" (%s) VALUES "
2131  "('%s'::GEOMETRY)",
2132  pg_info->schema_name, pg_info->table_name,
2133  pg_info->geom_column, geom_data);
2134  }
2135  }
2136  else {
2137  if (cat > 0) {
2138  /* no attributes (topology elements) */
2139  G_asprintf(&stmt, "INSERT INTO \"%s\".\"%s\" (%s,%s) VALUES "
2140  "(%d, '(%d, 1, %d, %d)'::topology.TopoGeometry)",
2141  pg_info->schema_name, pg_info->table_name,
2142  GV_KEY_COLUMN, pg_info->topogeom_column, cat,
2143  pg_info->toposchema_id, topo_id, topogeom_type);
2144  }
2145  }
2146  }
2147 
2148  return stmt;
2149 }
2150 
2151 /*!
2152  \brief Insert topological element into 'node' or 'edge' table
2153 
2154  Negative id for nodes, 0 for next value.
2155 
2156  \param Map pointer to Map_info struct
2157  \param id feature id (0 for next val)
2158  \param type feature type (GV_POINT, GV_LINE, ...)
2159  \param geom_data geometry in wkb
2160 
2161  \return new topo id
2162  \return -1 on error
2163 */
2164 int insert_topo_element(struct Map_info *Map, int id, int type,
2165  const char *geom_data)
2166 {
2167  int ret, topo_id;
2168  char *stmt, stmt_id[DB_SQL_MAX];
2169  struct Format_info_pg *pg_info;
2170  struct Plus_head *plus;
2171  struct P_line *Line;
2172 
2173  pg_info = &(Map->fInfo.pg);
2174  plus = &(Map->plus);
2175 
2176  Line = NULL;
2177  if (plus->built >= GV_BUILD_BASE) {
2178  if (id > 0) { /* -> feature */
2179  topo_id = id;
2180  if (topo_id > Map->plus.n_lines) {
2181  G_warning(_("Invalid feature %d (max: %d)"), topo_id, Map->plus.n_lines);
2182  return -1;
2183  }
2184  Line = Map->plus.Line[topo_id];
2185 
2186  if (Line->type & GV_POINTS) {
2187  /* set topo_id for points */
2188  topo_id = Vect_get_num_primitives(Map, GV_POINTS) + Vect_get_num_nodes(Map);
2189  }
2190 
2191  }
2192  else if (id < 0) { /* node */
2193  topo_id = abs(id);
2194  if (type != GV_POINT) {
2195  G_warning(_("Invalid feature type (%d) for node"), type);
2196  return -1;
2197  }
2198  if (topo_id > Map->plus.n_nodes) {
2199  G_warning(_("Invalid node %d (%d)"), topo_id, Map->plus.n_nodes);
2200  return -1;
2201  }
2202 
2203  /* increment topo_id - also points and centroids are
2204  * stored in 'node' table */
2205  topo_id += Vect_get_num_primitives(Map, GV_POINTS);
2206  }
2207  }
2208 
2209  stmt = NULL;
2210  switch(type) {
2211  case GV_POINT: {
2212  /* insert new node */
2213 #if USE_TOPO_STMT
2214  G_asprintf(&stmt, "SELECT topology.AddNode('%s', '%s'::GEOMETRY)",
2215  pg_info->toposchema_name, geom_data);
2216 #else
2217  if (id == 0) {
2218  /* get node_id */
2219  sprintf(stmt_id, "SELECT nextval('\"%s\".node_node_id_seq')",
2220  pg_info->toposchema_name);
2221  topo_id = Vect__execute_get_value_pg(pg_info->conn, stmt_id);
2222  }
2223 
2224  /* build insert statement */
2225  G_asprintf(&stmt, "INSERT INTO \"%s\".node (node_id, geom) VALUES "
2226  "(%d, '%s'::GEOMETRY)", pg_info->toposchema_name, topo_id, geom_data);
2227 #endif
2228  break;
2229  }
2230  case GV_LINE:
2231  case GV_BOUNDARY: {
2232  /* insert new edge */
2233 #if USE_TOPO_STMT
2234  G_asprintf(&stmt, "SELECT topology.AddEdge('%s', '%s'::GEOMETRY)",
2235  pg_info->toposchema_name, geom_data);
2236 #else
2237  int n1, n2, nle, nre;
2238 
2239  struct Format_info_offset *offset;
2240 
2241  offset = &(pg_info->offset);
2242 
2243  if (id == 0) {
2244  /* get edge_id */
2245  sprintf(stmt_id, "SELECT nextval('\"%s\".edge_data_edge_id_seq')",
2246  pg_info->toposchema_name);
2247  topo_id = Vect__execute_get_value_pg(pg_info->conn, stmt_id);
2248  }
2249 
2250  nle = -topo_id; /* assuming isolated lines */
2251  nre = topo_id;
2252 
2253  if (Line) {
2254  int i, n, next_edge;
2255  struct P_topo_l *topo = (struct P_topo_l *) Line->topo;
2256 
2257  topo_id = (int)Line->offset;
2258  /* start & end node */
2259  n1 = topo->N1;
2260  n2 = topo->N2;
2261 
2262  /* next left & right edge */
2263  for (i = 0; i < 2; i++) {
2264  n = Vect_get_node_n_lines(Map, i == 0 ? n1 : n2);
2265  if (n < 2) /* no connection */
2266  continue;
2267 
2268  next_edge = update_next_edge(Map, n,
2269  i == 0 ? topo_id : -topo_id);
2270  if (next_edge != 0) {
2271  if (i == 0)
2272  nre = next_edge; /* update next right edge for start node */
2273  else
2274  nle = next_edge; /* update next left edge for end node */
2275  }
2276  else {
2277  G_warning(_("Unable to determine next left/right edge for edge %d"), topo_id);
2278  }
2279  }
2280  }
2281  else {
2282  G_warning(_("Unable to insert new edge. Topology not available."));
2283  return -1;
2284  }
2285 
2286  G_debug(3, "new edge: id=%d next_left_edge=%d next_right_edge=%d",
2287  topo_id, nle, nre);
2288 
2289  if (n1 > offset->array_num || n2 > offset->array_num) /* node id starts at 1 */
2290  return -1;
2291 
2292  /* build insert statement */
2293  G_asprintf(&stmt, "INSERT INTO \"%s\".edge_data (edge_id, start_node, end_node, "
2294  "next_left_edge, abs_next_left_edge, next_right_edge, abs_next_right_edge, "
2295  "left_face, right_face, geom) VALUES "
2296  "(%d, %d, %d, %d, %d, %d, %d, 0, 0, '%s'::GEOMETRY)",
2297  pg_info->toposchema_name, topo_id, offset->array[n1-1], offset->array[n2-1],
2298  nle, abs(nle), nre, abs(nre), geom_data);
2299 #endif
2300  break;
2301  }
2302  case GV_CENTROID: {
2303  /* insert new node (with containing_face) */
2304 #if USE_TOPO_STMT
2305  G_asprintf(&stmt, "SELECT topology.AddNode('%s', '%s'::GEOMETRY)",
2306  pg_info->toposchema_name, geom_data);
2307 #else
2308  int area;
2309 
2310  if (id == 0) {
2311  /* get node_id */
2312  sprintf(stmt_id, "SELECT nextval('\"%s\".node_node_id_seq')",
2313  pg_info->toposchema_name);
2314  topo_id = Vect__execute_get_value_pg(pg_info->conn, stmt_id);
2315  }
2316 
2317  if (Line) {
2318  struct P_topo_c *topo = (struct P_topo_c *) Line->topo;
2319 
2320  area = topo->area;
2321  }
2322  else {
2323  area = 0;
2324  }
2325 
2326  G_asprintf(&stmt, "INSERT INTO \"%s\".node (node_id, containing_face, geom) VALUES "
2327  "(%d, %d, '%s'::GEOMETRY)", pg_info->toposchema_name,
2328  topo_id, area, geom_data);
2329 #endif
2330  break;
2331  }
2332  default:
2333  G_warning(_("Unsupported feature type %d"), type);
2334  break;
2335  }
2336 
2337  /* execute insert statement */
2338  ret = Vect__execute_pg(pg_info->conn, stmt);
2339  G_free(stmt);
2340 
2341  if (ret == -1) {
2342  /* rollback transaction */
2343  Vect__execute_pg(pg_info->conn, "ROLLBACK");
2344  return -1;
2345  }
2346 
2347  return topo_id;
2348 }
2349 
2350 /*!
2351  \brief Find next line (topo only)
2352 
2353  \param Map pointer to Map_info struct
2354  \param nlines number of lines
2355  \param line current line
2356  \param[out] left left line
2357  \param[out] right right line
2358 
2359  \return left (line < 0) or right (line > 0) next edge
2360  \return 0 on failure
2361 */
2362 int update_next_edge(struct Map_info* Map, int nlines, int line)
2363 {
2364  int ret, next_line, edge;
2365  char stmt[DB_SQL_MAX];
2366 
2367  const struct Format_info_pg *pg_info;
2368  struct P_line *Line_next, *Line;
2369 
2370  Line = Line_next = NULL;
2371 
2372  pg_info = &(Map->fInfo.pg);
2373 
2374  /* find next line
2375  start node -> next on the left
2376  end node -> next on the right
2377  */
2378  next_line = dig_angle_next_line(&(Map->plus), line, GV_LEFT, GV_LINES, NULL);
2379  G_debug(3, "line=%d next_line=%d", line, next_line);
2380  if (next_line == 0) {
2381  G_warning(_("Invalid topology"));
2382  return 0;
2383  }
2384 
2385  Line = Map->plus.Line[abs(line)];
2386  Line_next = Map->plus.Line[abs(next_line)];
2387  if (!Line || !Line_next) {
2388  G_warning(_("Invalid topology"));
2389  return 0;
2390  }
2391 
2392  if (line > 0) {
2393  edge = Line->offset;
2394  ret = next_line > 0 ? Line_next->offset : -Line_next->offset;
2395  }
2396  else {
2397  edge = -Line->offset;
2398  ret = next_line > 0 ? Line_next->offset : -Line_next->offset;
2399  }
2400 
2401  if (next_line < 0) {
2402  sprintf(stmt, "UPDATE \"%s\".edge_data SET next_left_edge = %d, "
2403  "abs_next_left_edge = %d WHERE edge_id = %d AND abs_next_left_edge = %d",
2404  pg_info->toposchema_name, edge, abs(edge), (int)Line_next->offset, (int)Line_next->offset);
2405  G_debug(3, "update edge=%d next_left_edge=%d (?)", (int)Line_next->offset, edge);
2406  }
2407  else {
2408  sprintf(stmt, "UPDATE \"%s\".edge_data SET next_right_edge = %d, "
2409  "abs_next_right_edge = %d WHERE edge_id = %d AND abs_next_right_edge = %d",
2410  pg_info->toposchema_name, edge, abs(edge), (int)Line_next->offset, (int)Line_next->offset);
2411  G_debug(3, "update edge=%d next_right_edge=%d (?)", (int)Line_next->offset, edge);
2412  }
2413 
2414  if(Vect__execute_pg(pg_info->conn, stmt) == -1) {
2415  Vect__execute_pg(pg_info->conn, "ROLLBACK");
2416  return 0;
2417  }
2418 
2419  if (nlines > 2) {
2420  /* more lines connected to the node
2421 
2422  start node -> next on the right
2423  end node -> next on the left
2424  */
2425  next_line = dig_angle_next_line(&(Map->plus), line, GV_RIGHT, GV_LINES, NULL);
2426  Line_next = Map->plus.Line[abs(next_line)];
2427 
2428  if (next_line < 0) {
2429  sprintf(stmt, "UPDATE \"%s\".edge_data SET next_left_edge = %d, "
2430  "abs_next_left_edge = %d WHERE edge_id = %d",
2431  pg_info->toposchema_name, edge, abs(edge), (int)Line_next->offset);
2432  G_debug(3, "update edge=%d next_left_edge=%d", (int)Line_next->offset, edge);
2433  }
2434  else {
2435  sprintf(stmt, "UPDATE \"%s\".edge_data SET next_right_edge = %d, "
2436  "abs_next_right_edge = %d WHERE edge_id = %d",
2437  pg_info->toposchema_name, edge, abs(edge), (int)Line_next->offset);
2438  G_debug(3, "update edge=%d next_right_edge=%d", (int)Line_next->offset, edge);
2439  }
2440 
2441  if(Vect__execute_pg(pg_info->conn, stmt) == -1) {
2442  Vect__execute_pg(pg_info->conn, "ROLLBACK");
2443  return 0;
2444  }
2445  }
2446 
2447  return ret;
2448 }
2449 
2450 /*!
2451  \brief Insert new face to the 'face' table (topo only)
2452 
2453  \param Map pointer to Map_info struct
2454  \param area area id (negative id for isles)
2455 
2456  \return 0 on error
2457  \return area id on success (>0)
2458 */
2459 int Vect__insert_face_pg(struct Map_info *Map, int area)
2460 {
2461  char *stmt;
2462 
2463  struct Format_info_pg *pg_info;
2464  struct bound_box box;
2465 
2466  if (area == 0)
2467  return 0; /* universal face has id '0' in PostGIS Topology */
2468 
2469  stmt = NULL;
2470  pg_info = &(Map->fInfo.pg);
2471 
2472  /* check if face exists */
2473 
2474  /* get mbr of the area */
2475  if (area > 0)
2476  Vect_get_area_box(Map, area, &box);
2477  else
2478  Vect_get_isle_box(Map, abs(area), &box);
2479 
2480  /* insert face if not exists */
2481  G_asprintf(&stmt, "INSERT INTO \"%s\".face (face_id, mbr) VALUES "
2482  "(%d, ST_GeomFromText('POLYGON((%.12f %.12f, %.12f %.12f, %.12f %.12f, %.12f %.12f, "
2483  "%.12f %.12f))', %d))", pg_info->toposchema_name, area,
2484  box.W, box.S, box.W, box.N, box.E, box.N,
2485  box.E, box.S, box.W, box.S, pg_info->srid);
2486  G_debug(3, "new face id=%d", area);
2487  if (Vect__execute_pg(pg_info->conn, stmt) == -1) {
2488  Vect__execute_pg(pg_info->conn, "ROLLBACK");
2489  return 0;
2490  }
2491  G_free(stmt);
2492 
2493  return area;
2494 }
2495 
2496 /*!
2497  \brief Delete existing face (currently unused)
2498 
2499  \todo Set foreign keys as DEFERRABLE INITIALLY DEFERRED and use SET
2500  CONSTRAINTS ALL DEFERRED
2501 
2502  \param Map pointer to Map_info struct
2503  \param area area id to delete
2504 
2505  \return 0 on success
2506  \return -1 on error
2507 */
2508 int delete_face(const struct Map_info *Map, int area)
2509 {
2510  char stmt[DB_SQL_MAX];
2511 
2512  const struct Format_info_pg *pg_info;
2513 
2514  pg_info = &(Map->fInfo.pg);
2515 
2516  /* update centroids first */
2517  sprintf(stmt, "UPDATE \"%s\".node SET containing_face = 0 "
2518  "WHERE containing_face = %d",
2519  pg_info->toposchema_name, area);
2520  G_debug(3, "SQL: %s", stmt);
2521  if (Vect__execute_pg(pg_info->conn, stmt) == -1) {
2522  Vect__execute_pg(pg_info->conn, "ROLLBACK");
2523  return -1;
2524  }
2525 
2526  /* update also edges (left face) */
2527  sprintf(stmt, "UPDATE \"%s\".edge_data SET left_face = 0 "
2528  "WHERE left_face = %d",
2529  pg_info->toposchema_name, area);
2530  G_debug(3, "SQL: %s", stmt);
2531  if (Vect__execute_pg(pg_info->conn, stmt) == -1) {
2532  Vect__execute_pg(pg_info->conn, "ROLLBACK");
2533  return -1;
2534  }
2535 
2536  /* update also edges (left face) */
2537  sprintf(stmt, "UPDATE \"%s\".edge_data SET right_face = 0 "
2538  "WHERE right_face = %d",
2539  pg_info->toposchema_name, area);
2540  G_debug(3, "SQL: %s", stmt);
2541  if (Vect__execute_pg(pg_info->conn, stmt) == -1) {
2542  Vect__execute_pg(pg_info->conn, "ROLLBACK");
2543  return -1;
2544  }
2545 
2546  /* delete face */
2547  sprintf(stmt, "DELETE FROM \"%s\".face WHERE face_id = %d",
2548  pg_info->toposchema_name, area);
2549  G_debug(3, "delete face id=%d", area);
2550  if (Vect__execute_pg(pg_info->conn, stmt) == -1) {
2551  Vect__execute_pg(pg_info->conn, "ROLLBACK");
2552  return -1;
2553  }
2554 
2555  return 0;
2556 }
2557 
2558 /*!
2559  \brief Update lines (next left and right edges)
2560 
2561  - isolated edges
2562  next left edge: -edge
2563  next right edge: edge
2564 
2565  - connected edges
2566  next left edge: next edge or -edge
2567  next right edge: next edge or edge
2568 
2569  \param Map pointer to Map_info struct
2570  \param line feature id
2571 
2572  \return 0 on success
2573  \return -1 on error
2574 */
2575 int update_topo_edge(struct Map_info *Map, int line)
2576 {
2577  int i, n;
2578  int nle, nre, next_edge;
2579  char stmt[DB_SQL_MAX];
2580 
2581  struct Format_info_pg *pg_info;
2582  struct P_line *Line;
2583 
2584  pg_info = &(Map->fInfo.pg);
2585 
2586  if (line < 1 || line > Map->plus.n_lines) {
2587  G_warning(_("Attempt to access non-existing feature %d"), line);
2588  return -1;
2589  }
2590  Line = Map->plus.Line[line];
2591  if (!Line) {
2592  G_warning(_("Attempt to access dead feature %d"), line);
2593  return -1;
2594  }
2595 
2596  struct P_topo_l *topo = (struct P_topo_l *) Line->topo;
2597 
2598  nre = nle = 0; /* edge = 0 is an illegal value */
2599 
2600  /* check for line connection */
2601  for (i = 0; i < 2; i++) {
2602  /* first check start node then end node */
2603  n = i == 0 ? Vect_get_node_n_lines(Map, topo->N1)
2604  : Vect_get_node_n_lines(Map, topo->N2);
2605 
2606  if (n < 2) /* no connection */
2607  continue;
2608 
2609  next_edge = update_next_edge(Map, n,
2610  i == 0 ? line : -line);
2611  if (next_edge != 0) {
2612  if (i == 0)
2613  nre = next_edge; /* update next right edge for start node */
2614  else
2615  nle = next_edge; /* update next left edge for end node */
2616  }
2617  else {
2618  G_warning(_("Unable to determine next left/right edge"));
2619  return -1;
2620  }
2621  }
2622 
2623  if (nle == 0 && nre == 0) /* nothing changed */
2624  return 0;
2625 
2626  if (nle != 0 && nre != 0) {
2627  /* update both next left and right edge */
2628  sprintf(stmt, "UPDATE \"%s\".edge_data SET "
2629  "next_left_edge = %d, abs_next_left_edge = %d, "
2630  "next_right_edge = %d, abs_next_right_edge = %d "
2631  "WHERE edge_id = %d", pg_info->toposchema_name,
2632  nle, abs(nle), nre, abs(nre), (int)Line->offset);
2633  }
2634  else if (nle != 0) {
2635  /* update next left edge only */
2636  sprintf(stmt, "UPDATE \"%s\".edge_data SET "
2637  "next_left_edge = %d, abs_next_left_edge = %d "
2638  "WHERE edge_id = %d", pg_info->toposchema_name,
2639  nle, abs(nle), (int)Line->offset);
2640  }
2641  else {
2642  /* update next right edge only */
2643  sprintf(stmt, "UPDATE \"%s\".edge_data SET "
2644  "next_right_edge = %d, abs_next_right_edge = %d "
2645  "WHERE edge_id = %d", pg_info->toposchema_name,
2646  nre, abs(nre), (int)Line->offset);
2647  }
2648  G_debug(3, "update edge=%d next_left_edge=%d next_right_edge=%d",
2649  (int)Line->offset, nle, nre);
2650 
2651  if(Vect__execute_pg(pg_info->conn, stmt) == -1) {
2652  /* rollback transaction */
2653  Vect__execute_pg(pg_info->conn, "ROLLBACK");
2654  return -1;
2655  }
2656 
2657  return 0;
2658 }
2659 
2660 /*!
2661  \brief Update lines (left and right faces)
2662 
2663  TODO: handle isles
2664 
2665  \param Map pointer to Map_info struct
2666  \param line feature id
2667 
2668  \return 0 on success
2669  \return -1 on error
2670 */
2671 int update_topo_face(struct Map_info *Map, int line)
2672 {
2673  int i, s, area, face[2];
2674  char stmt[DB_SQL_MAX];
2675 
2676  struct Format_info_pg *pg_info;
2677  struct P_line *Line, *Line_i;
2678  struct P_area *Area;
2679  struct P_topo_b *topo, *topo_i;
2680 
2681  pg_info = &(Map->fInfo.pg);
2682 
2683  if (line < 1 || line > Map->plus.n_lines) {
2684  G_warning(_("Attempt to access non-existing feature %d"), line);
2685  return -1;
2686  }
2687  Line = Map->plus.Line[line];
2688  if (!Line) {
2689  G_warning(_("Attempt to access dead feature %d"), line);
2690  return -1;
2691  }
2692 
2693  topo = (struct P_topo_b *)Line->topo;
2694 
2695  /* for both side on the current boundary (line) */
2696  /* create new faces */
2697  for (s = 0; s < 2; s++) { /* for each side */
2698  area = s == 0 ? topo->left : topo->right;
2699  if (area <= 0) /* no area - skip */
2700  continue;
2701 
2702  face[s] = Vect__insert_face_pg(Map, area);
2703  if (face[s] < 1) {
2704  G_warning(_("Unable to create new face"));
2705  return -1;
2706  }
2707  }
2708 
2709  /* update edges forming faces */
2710  for (s = 0; s < 2; s++) { /* for each side */
2711  area = s == 0 ? topo->left : topo->right;
2712  if (area <= 0) /* no area - skip */
2713  continue;
2714 
2715  Area = Map->plus.Area[area];
2716  for (i = 0; i < Area->n_lines; i++) {
2717  Line_i = Map->plus.Line[abs(Area->lines[i])];
2718  topo_i = (struct P_topo_b *)Line_i->topo;
2719 
2720  sprintf(stmt, "UPDATE \"%s\".edge_data SET "
2721  "left_face = %d, right_face = %d "
2722  "WHERE edge_id = %d", pg_info->toposchema_name,
2723  topo_i->left > 0 ? topo_i->left : 0,
2724  topo_i->right > 0 ? topo_i->right : 0,
2725  (int) Line_i->offset);
2726  G_debug(2, "SQL: %s", stmt);
2727 
2728  if(Vect__execute_pg(pg_info->conn, stmt) == -1) {
2729  Vect__execute_pg(pg_info->conn, "ROLLBACK");
2730  return -1;
2731  }
2732  }
2733 
2734  /* update also centroids (stored as nodes) */
2735  if (Area->centroid > 0) {
2736  Line_i = Map->plus.Line[Area->centroid];
2737  sprintf(stmt, "UPDATE \"%s\".node SET containing_face = %d "
2738  "WHERE node_id = %d", pg_info->toposchema_name,
2739  face[s], (int)Line_i->offset);
2740  G_debug(2, "SQL: %s", stmt);
2741 
2742  if(Vect__execute_pg(pg_info->conn, stmt) == -1) {
2743  /* rollback transaction */
2744  Vect__execute_pg(pg_info->conn, "ROLLBACK");
2745  return -1;
2746  }
2747  }
2748  }
2749 
2750  return 0;
2751 }
2752 
2753 /*!
2754  \brief Add line to native and PostGIS topology
2755 
2756  \param Map vector map
2757  \param offset ???
2758  \param type feature type
2759  \param Points feature vertices
2760 
2761  \return feature id
2762  \return -1 on error
2763 */
2764 int add_line_to_topo_pg(struct Map_info *Map, off_t offset, int type,
2765  const struct line_pnts *points)
2766 {
2767  int line, n_nodes;
2768 
2769  struct Plus_head *plus;
2770 
2771  plus = &(Map->plus);
2772 
2773  Vect_reset_updated(Map);
2774  line = V2__add_line_to_topo_nat(Map, offset, type, points, NULL,
2775  -1, NULL);
2776 
2777  /* insert new nodes into 'node' table */
2778  n_nodes = Vect_get_num_updated_nodes(Map);
2779  if (n_nodes > 0) {
2780  int i, node;
2781  double x, y, z;
2782 
2783  if (!Points)
2784  Points = Vect_new_line_struct();
2785 
2786  for (i = 0; i < n_nodes; i++) {
2787  node = Vect_get_updated_node(Map, i);
2788  /* skip updated and deleted nodes */
2789  if (node > 0 || plus->Node[abs(node)] == NULL)
2790  continue;
2791 
2792  G_debug(3, " new node: %d", node);
2793 
2794  Vect_get_node_coor(Map, abs(node), &x, &y, &z);
2795  Vect_reset_line(Points);
2796  Vect_append_point(Points, x, y, z);
2797 
2798  write_feature(Map, node, GV_POINT, (const struct line_pnts **) &Points, 1, -1);
2799  }
2800  }
2801 
2802  return line;
2803 }
2804 
2805 /*!
2806  \brief Delete line from native and PostGIS topology
2807 
2808  \param Map vector map
2809  \param line feature id to remove from topo
2810  \param type feature type
2811  \param Points feature vertices
2812 
2813  \return 0 on success
2814  \return -1 on error
2815 */
2816 int delete_line_from_topo_pg(struct Map_info *Map, int line, int type,
2817  const struct line_pnts *Points)
2818 {
2819  int N1, N2, node_id;
2820  char stmt[DB_SQL_MAX];
2821 
2822  struct Format_info_pg *pg_info;
2823  struct P_node *Node;
2824 
2825  pg_info = &(Map->fInfo.pg);
2826 
2827  Vect_reset_updated(Map);
2828 
2829  if (!(type & GV_LINES))
2830  return 0;
2831 
2832  Vect_get_line_nodes(Map, line, &N1, &N2);
2833  if (0 != V2__delete_line_from_topo_nat(Map, line, type, Points, NULL))
2834  return -1;
2835 
2836  Node = Map->plus.Node[N1];
2837  if (!Node || Node->n_lines == 0) {
2838  node_id = pg_info->offset.array[N1-1];
2839  sprintf(stmt, "DELETE FROM \"%s\".\"node\" WHERE node_id = %d",
2840  pg_info->toposchema_name, node_id);
2841  if (Vect__execute_pg(pg_info->conn, stmt) == -1) {
2842  G_warning(_("Unable to delete node %d"), node_id);
2843  Vect__execute_pg(pg_info->conn, "ROLLBACK");
2844  return -1;
2845  }
2846  }
2847 
2848  Node = Map->plus.Node[N2];
2849  if (!Node || Node->n_lines == 0) {
2850  node_id = pg_info->offset.array[N2-1];
2851  sprintf(stmt, "DELETE FROM \"%s\".\"node\" WHERE node_id = %d",
2852  pg_info->toposchema_name, node_id);
2853  if (Vect__execute_pg(pg_info->conn, stmt) == -1) {
2854  G_warning(_("Unable to delete node %d"), node_id);
2855  Vect__execute_pg(pg_info->conn, "ROLLBACK");
2856  return -1;
2857  }
2858  }
2859 
2860  return 0;
2861 }
2862 
2863 int set_constraint_to_deferrable(struct Format_info_pg *pg_info,
2864  const char *table, const char *constraint,
2865  const char *column, const char *ref_table,
2866  const char *ref_column)
2867 {
2868  char stmt[DB_SQL_MAX];
2869 
2870  sprintf(stmt, "ALTER TABLE \"%s\".%s DROP CONSTRAINT %s",
2871  pg_info->toposchema_name, table, constraint);
2872  if (-1 == Vect__execute_pg(pg_info->conn, stmt)) {
2873  Vect__execute_pg(pg_info->conn, "ROLLBACK");
2874  return -1;
2875  }
2876 
2877  sprintf(stmt, "ALTER TABLE \"%s\".%s ADD CONSTRAINT %s "
2878  "FOREIGN KEY (%s) REFERENCES \"%s\".%s (%s) "
2879  "DEFERRABLE INITIALLY DEFERRED",
2880  pg_info->toposchema_name, table, constraint, column,
2881  pg_info->toposchema_name, ref_table, ref_column);
2882  if (-1 == Vect__execute_pg(pg_info->conn, stmt)) {
2883  Vect__execute_pg(pg_info->conn, "ROLLBACK");
2884  return -1;
2885  }
2886 
2887  return 0;
2888 }
2889 
2890 /*!
2891  \brief Open database connection with attribute table
2892 
2893  \param pg_info pointer to Format_info_pg struct
2894 
2895  \return pointer to dbDriver on succes
2896  \return NULL on failure
2897 */
2898 dbDriver *open_db(struct Format_info_pg *pg_info) {
2899  dbDriver *driver;
2900  dbHandle handle;
2901 
2902  struct field_info *Fi;
2903 
2904  db_init_handle(&handle);
2905 
2906  Fi = pg_info->fi;
2907 
2908  pg_info->dbdriver = driver = db_start_driver(Fi->driver);
2909  if (!driver) {
2910  G_warning(_("Unable to start driver <%s>"), Fi->driver);
2911  return NULL;
2912  }
2913  db_set_handle(&handle, Fi->database, NULL);
2914  if (db_open_database(driver, &handle) != DB_OK) {
2915  G_warning(_("Unable to open database <%s> by driver <%s>"),
2916  Fi->database, Fi->driver);
2918  pg_info->dbdriver = NULL;
2919  return NULL;
2920  }
2921 
2922  return pg_info->dbdriver;
2923 }
2924 #endif
int Vect_get_num_updated_nodes(const struct Map_info *Map)
Get number of updated nodes.
Definition: level_two.c:223
int * array
Offset list.
Definition: dig_structs.h:446
char * toposchema_name
Topology schema name and id.
Definition: dig_structs.h:699
#define TRUE
Definition: gis.h:49
#define ENDIAN_LITTLE
Endian check.
Definition: gis.h:358
#define GV_KEY_COLUMN
Name of default key column.
Definition: gis.h:366
int Vect_get_isle_box(const struct Map_info *Map, int isle, struct bound_box *Box)
Get bounding box of isle.
int db_test_value_isnull(dbValue *value)
Check of value is null.
Definition: value.c:26
int G_strcasecmp(const char *x, const char *y)
String compare ignoring case (upper or lower)
Definition: strings.c:46
const char * db_sqltype_name(int sqltype)
Get SQL data type description.
Definition: sqltype.c:25
Bounding box.
Definition: dig_structs.h:65
const char * G_find_key_value(const char *key, const struct Key_Value *kv)
Find given key (case sensitive)
Definition: key_value1.c:84
struct driver * driver
Definition: driver/init.c:25
CELL cat
Definition: raster3d/cats.c:82
void G_free(void *buf)
Free allocated memory.
Definition: gis/alloc.c:149
int built
Highest level of topology currently available.
Definition: dig_structs.h:873
plus_t area
Area number, negative for duplicate centroid.
Definition: dig_structs.h:1537
off_t V2_write_line_pg(struct Map_info *Map, int type, const struct line_pnts *points, const struct line_cats *cats)
Writes feature on topological level (PostGIS interface)
Definition: write_pg.c:141
#define GV_LEFT
Boundary side indicator left/right.
Definition: dig_defines.h:174
const char * db_get_column_name(dbColumn *column)
Returns column name for given column.
off_t offset
Offset in coor file for line.
Definition: dig_structs.h:1593
off_t V2_rewrite_line_pg(struct Map_info *Map, off_t line, int type, const struct line_pnts *points, const struct line_cats *cats)
Rewrites feature at topological level (PostGIS interface, internal use only)
Definition: write_pg.c:218
int Vect_get_node_coor(const struct Map_info *Map, int num, double *x, double *y, double *z)
Get node coordinates.
Definition: level_two.c:278
void G_verbose_message(const char *msg,...)
Print a message to stderr but only if module is in verbose mode.
Definition: gis/error.c:108
Vector geometry.
Definition: dig_structs.h:1574
struct P_line ** Line
Array of vector geometries.
Definition: dig_structs.h:887
int Vect_get_node_n_lines(const struct Map_info *Map, int node)
Get number of lines for node.
Definition: level_two.c:384
struct Key_Value * G_fread_key_value(FILE *fd)
Read key/values pairs from file.
Definition: key_value2.c:49
int db_close_database_shutdown_driver(dbDriver *driver)
Close driver/database connection.
Definition: db.c:62
int Vect_get_area_box(const struct Map_info *Map, int area, struct bound_box *Box)
Get bounding box of area.
SF_FeatureType feature_type
Feature type (simple feature access)
Definition: dig_structs.h:635
struct line_pnts * Vect_new_line_struct()
Creates and initializes a line_pnts structure.
Definition: line.c:45
int db_describe_table(dbDriver *driver, dbString *name, dbTable **table)
Describe table.
Definition: c_desc_table.c:28
plus_t right
Area number to the right, negative for isle.
Definition: dig_structs.h:1526
#define GV_BUILD_BASE
Topology levels - basic level (without areas and isles)
Definition: dig_defines.h:125
struct P_area ** Area
Array of areas.
Definition: dig_structs.h:891
#define DB_SQL_MAX
Definition: dbmi.h:142
struct P_node ** Node
Array of nodes.
Definition: dig_structs.h:883
int n_points
Number of points.
Definition: dig_structs.h:1692
char * table
Name of DB table.
Definition: dig_structs.h:155
#define DB_C_TYPE_DATETIME
Definition: dbmi.h:110
#define GV_CENTROID
Definition: dig_defines.h:185
const char * Vect_get_finfo_geometry_type(const struct Map_info *Map)
Get geometry type as string (relevant only for non-native formats)
Definition: header_finfo.c:144
plus_t n_lines
Number of attached lines (size of lines, angle)
Definition: dig_structs.h:1472
void Vect_reset_updated(struct Map_info *Map)
Reset list of updated lines/nodes.
Definition: level_two.c:472
char * G_store(const char *s)
Copy string to allocated memory.
Definition: strings.c:86
plus_t left
Area number to the left, negative for isle.
Definition: dig_structs.h:1522
#define GV_POINTS
Definition: dig_defines.h:191
int * lines_cats
List of line cats (used only for PostGIS Topology access)
Definition: dig_structs.h:481
int db_get_column_length(dbColumn *column)
Get column&#39;s length.
void Vect_destroy_line_struct(struct line_pnts *p)
Frees all memory associated with a line_pnts structure, including the structure itself.
Definition: line.c:77
struct Format_info fInfo
Format info for non-native formats.
Definition: dig_structs.h:1415
#define PRI_OFF_T
Definition: gis.h:59
int Vect_get_num_dblinks(const struct Map_info *Map)
Get number of defined dblinks.
Definition: level_two.c:163
int G_asprintf(char **out, const char *fmt,...)
Definition: asprintf.c:70
int inTransaction
Start/Finish transaction.
Definition: dig_structs.h:658
plus_t centroid
Number of first centroid within area.
Definition: dig_structs.h:1628
int level
Topology level.
Definition: dig_structs.h:1313
char * table_name
Table name.
Definition: dig_structs.h:619
#define DB_SQL_TYPE_CHARACTER
Definition: dbmi.h:81
#define NULL
Definition: ccmath.h:32
dbDriver * dbdriver
Open DB driver when writing attributes.
Definition: dig_structs.h:652
int Vect_get_updated_node(const struct Map_info *Map, int idx)
Get updated (modified) node by index.
Definition: level_two.c:245
#define x
#define GV_POINT
Feature types used in memory on run time (may change)
Definition: dig_defines.h:182
char * db_get_string(const dbString *x)
Get string.
Definition: string.c:140
char * topogeom_column
TopoGeometry column (feature table)
Definition: dig_structs.h:695
void G_free_key_value(struct Key_Value *kv)
Free allocated Key_Value structure.
Definition: key_value1.c:103
int V1_delete_line_pg(struct Map_info *Map, off_t offset)
Deletes feature at the given offset (level 1)
Definition: write_pg.c:310
plus_t N2
End node.
Definition: dig_structs.h:1503
char * database
Definition: dig_structs.h:151
int Vect_append_point(struct line_pnts *Points, double x, double y, double z)
Appends one point to the end of a line.
Definition: line.c:149
plus_t n_lines
Current number of lines.
Definition: dig_structs.h:947
void G_fatal_error(const char *msg,...)
Print a fatal error message to stderr.
Definition: gis/error.c:159
int db_convert_column_value_to_string(dbColumn *column, dbString *string)
?
Definition: columnfmt.c:61
Feature category info.
Definition: dig_structs.h:1702
int V2_read_line_pg(struct Map_info *Map, struct line_pnts *line_p, struct line_cats *line_c, int line)
Read feature from PostGIS layer on topological level.
Definition: read_pg.c:319
int db_sqltype_to_Ctype(int sqltype)
Get C data type based on given SQL data type.
Definition: sqlCtype.c:24
#define GV_LINE
Definition: dig_defines.h:183
plus_t N1
Start node.
Definition: dig_structs.h:1499
int db_fetch(dbCursor *cursor, int position, int *more)
Fetch data from open cursor.
Definition: c_fetch.c:28
Layer (old: field) information.
Definition: dig_structs.h:134
double * x
Array of X coordinates.
Definition: dig_structs.h:1680
int dig_angle_next_line(struct Plus_head *, plus_t, int, int, float *)
Find line number of next angle to follow a line.
Definition: plus_area.c:466
char type
Line type.
Definition: dig_structs.h:1586
int cidx_up_to_date
Category index to be updated.
Definition: dig_structs.h:1156
Feature geometry info - coordinates.
Definition: dig_structs.h:1675
int with_z
2D/3D vector data
Definition: dig_structs.h:347
Centroid topology.
Definition: dig_structs.h:1532
plus_t * lines
List of boundary lines.
Definition: dig_structs.h:1621
#define ENDIAN_BIG
Definition: gis.h:359
double db_get_value_double(dbValue *value)
Get double precision value.
Definition: value.c:50
#define GV_FACE
Definition: dig_defines.h:186
int ctype
Cache type.
Definition: dig_structs.h:507
Basic topology-related info.
Definition: dig_structs.h:784
plus_t Vect_get_num_primitives(const struct Map_info *Map, int type)
Get number of primitives in vector map.
Definition: level_two.c:47
int db_get_column_sqltype(dbColumn *column)
Returns column sqltype for column.
int Vect_is_3d(const struct Map_info *Map)
Check if vector map is 3D.
Data structure used for building pseudo-topology.
Definition: dig_structs.h:397
dbTable * db_get_cursor_table(dbCursor *cursor)
Get table allocated by cursor.
Definition: cursor.c:67
#define GV_BUILD_AREAS
Topology levels - build areas.
Definition: dig_defines.h:127
int db_get_table_number_of_columns(dbTable *table)
Return the number of columns of the table.
Non-native format info (PostGIS)
Definition: dig_structs.h:602
int Vect_cat_get(const struct line_cats *Cats, int field, int *cat)
Get first found category of given field.
for(cat=0;;cat++)
char * geom_column
Geometry column (simple feature access)
Definition: dig_structs.h:631
struct Format_info_cache cache
Lines cache for reading feature.
Definition: dig_structs.h:683
Line topology.
Definition: dig_structs.h:1494
#define FALSE
Definition: gis.h:53
void * topo
Topology info.
Definition: dig_structs.h:1599
#define DB_NEXT
Definition: dbmi.h:114
Boundary topology.
Definition: dig_structs.h:1509
#define DB_C_TYPE_STRING
Definition: dbmi.h:107
struct Format_info_pg pg
PostGIS info.
Definition: dig_structs.h:726
int n_cats
Number of categories attached to element.
Definition: dig_structs.h:1715
int G_debug(int level, const char *msg,...)
Print debugging message.
Definition: debug.c:65
dbValue * db_get_column_value(dbColumn *column)
Returns column value for given column structure.
#define GV_BOUNDARY
Definition: dig_defines.h:184
fclose(fd)
struct Plus_head plus
Plus info (topology, version, ...)
Definition: dig_structs.h:1286
struct field_info * fi
Definition: dig_structs.h:653
off_t V1_rewrite_line_pg(struct Map_info *Map, off_t offset, int type, const struct line_pnts *points, const struct line_cats *cats)
Rewrites feature at the given offset (level 1) (PostGIS interface, internal use only) ...
Definition: write_pg.c:178
Topological feature - node.
Definition: dig_structs.h:1448
int topo_geo_only
Topology format.
Definition: dig_structs.h:707
off_t V1_write_line_pg(struct Map_info *Map, int type, const struct line_pnts *points, const struct line_cats *cats)
Writes feature on level 1 (PostGIS interface)
Definition: write_pg.c:98
#define DB_C_TYPE_INT
Definition: dbmi.h:108
int db_set_handle(dbHandle *handle, const char *dbName, const char *dbSchema)
Set handle (database and schema name)
Definition: handle.c:39
off_t V2_write_line_sfa(struct Map_info *Map, int type, const struct line_pnts *points, const struct line_cats *cats)
Writes feature on level 2 (OGR/PostGIS interface, pseudo-topological level)
Definition: write_sfa.c:49
#define WITH_Z
Definition: dig_defines.h:171
struct dig_head head
Header info.
Definition: dig_structs.h:1403
int array_num
Number of items in offset list.
Definition: dig_structs.h:450
Definition: gis.h:479
plus_t Vect_get_num_nodes(const struct Map_info *Map)
Get number of nodes in vector map.
Definition: level_two.c:34
int srid
Spatial reference system id (see spatial_ref_sys table)
Definition: dig_structs.h:644
int * lines_types
List of line types (GV_POINT, GV_LINE, ...)
Definition: dig_structs.h:477
int array_alloc
Space allocated for offset list.
Definition: dig_structs.h:454
Vector map info.
Definition: dig_structs.h:1259
int V2_delete_line_sfa(struct Map_info *Map, off_t line)
Deletes feature on level 2 (OGR/PostGIS interface)
Definition: write_sfa.c:191
int V2__add_line_to_topo_nat(struct Map_info *Map, off_t offset, int type, const struct line_pnts *points, const struct line_cats *cats, int restore_line, int(*external_routine)(const struct Map_info *, int))
Add feature (line) to topology (internal use only)
Definition: write_nat.c:871
struct field_info * Vect_get_dblink(const struct Map_info *Map, int link)
Get information about link to database.
Definition: field.c:421
double * y
Array of Y coordinates.
Definition: dig_structs.h:1684
int Vect_get_line_nodes(const struct Map_info *Map, int line, int *n1, int *n2)
Get line nodes.
Definition: level_two.c:307
Area (topology) info.
Definition: dig_structs.h:1605
char * driver
Name of DB driver (&#39;sqlite&#39;, &#39;dbf&#39;, ...)
Definition: dig_structs.h:147
int dig_add_node(struct Plus_head *, double, double, double)
Add new node to plus structure.
Definition: plus_node.c:102
struct Format_info_offset offset
Offset list used for building pseudo-topology (simple features access)
Definition: dig_structs.h:689
void Vect_set_updated(struct Map_info *Map, int enable)
Enable/disable maintanance of list of updated lines/nodes.
Definition: level_two.c:458
#define DB_SEQUENTIAL
Definition: dbmi.h:123
int dig__byte_order_out()
Get byte order.
Definition: portable.c:1013
char * conninfo
Connection string.
Definition: dig_structs.h:607
#define DB_C_TYPE_DOUBLE
Definition: dbmi.h:109
#define LEVEL_2
Vector level - with 2D topology.
Definition: dig_defines.h:118
int V2__delete_line_from_topo_nat(struct Map_info *Map, int line, int type, const struct line_pnts *points, const struct line_cats *cats)
Delete feature from topology (internal use only)
Definition: write_nat.c:636
int number
Layer number.
Definition: dig_structs.h:139
void db_init_handle(dbHandle *handle)
Initialize handle (i.e database/schema)
Definition: handle.c:23
struct line_pnts ** lines
Lines array.
Definition: dig_structs.h:473
#define GV_RIGHT
Definition: dig_defines.h:175
int db_set_string(dbString *x, const char *s)
Inserts string to dbString (enlarge string)
Definition: string.c:41
int coor_dim
Coordinates dimension (2D or 3D)
Definition: dig_structs.h:639
#define GV_AREA
Definition: dig_defines.h:188
double * z
Array of Z coordinates.
Definition: dig_structs.h:1688
#define _(str)
Definition: glocale.h:13
Spatial index info.
Definition: dig_structs.h:1799
void Vect_reset_line(struct line_pnts *Points)
Reset line.
Definition: line.c:130
int db_get_value_int(dbValue *value)
Get integer value.
Definition: value.c:38
int * field
Array of layers (fields)
Definition: dig_structs.h:1707
char * G_str_replace(const char *buffer, const char *old_str, const char *new_str)
Replace all occurrences of old_str in buffer with new_str.
Definition: strings.c:188
#define GV_LINES
Definition: dig_defines.h:192
const char * G_mapset(void)
Get current mapset name.
Definition: gis/mapset.c:33
int
Reads the categories file for map name in mapset and stores the categories in the pcats structure...
int db_open_database(dbDriver *driver, dbHandle *handle)
Open database connection.
Definition: c_opendb.c:27
void G_zero(void *buf, int i)
Zero out a buffer, buf, of length i.
Definition: gis/zero.c:23
FILE * G_fopen_old(const char *element, const char *name, const char *mapset)
Open a database file for reading.
Definition: gis/open.c:253
int V1_read_line_pg(struct Map_info *Map, struct line_pnts *line_p, struct line_cats *line_c, off_t offset)
Read feature from PostGIS layer at given offset (level 1 without topology)
Definition: read_pg.c:237
void db_free_string(dbString *x)
Free allocated space for dbString.
Definition: string.c:150
char * schema_name
Schema name.
Definition: dig_structs.h:615
dbColumn * db_get_table_column(dbTable *table, int idx)
Returns column structure for given table and column number.
int update_cidx
Update category index if vector is modified.
Definition: dig_structs.h:1136
char * getenv()
plus_t n_lines
Number of boundary lines.
Definition: dig_structs.h:1610
dbDriver * db_start_driver(const char *name)
Initialize a new dbDriver for db transaction.
Definition: start.c:50
int V2_delete_line_pg(struct Map_info *Map, off_t line)
Deletes feature on topological level (PostGIS interface)
Definition: write_pg.c:372
const char * db_get_value_string(dbValue *value)
Get string value.
Definition: value.c:92
void G_warning(const char *msg,...)
Print a warning message to stderr.
Definition: gis/error.c:203
const char * G_find_file2(const char *element, const char *name, const char *mapset)
Searches for a file from the mapset search list or in a specified mapset. (look but don&#39;t touch) ...
Definition: find_file.c:247
int db_open_select_cursor(dbDriver *driver, dbString *select, dbCursor *cursor, int mode)
Open select cursor.
Definition: c_openselect.c:37
void db_init_string(dbString *x)
Initialize dbString.
Definition: string.c:25
#define GV_KERNEL
Definition: dig_defines.h:187
char * fid_column
FID column.
Definition: dig_structs.h:627
#define DB_OK
Definition: dbmi.h:71
plus_t n_nodes
Current number of topological features derived from vector geometries.
Definition: dig_structs.h:939
SF_FeatureType
Simple feature types.
Definition: dig_defines.h:234
char * key
Name of key column (usually &#39;cat&#39;)
Definition: dig_structs.h:159