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