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