1 /**********************************************************************
2 *
3 * PostGIS - Spatial Types for PostgreSQL
4 * http://postgis.net
5 *
6 * Copyright (C) 2015-2021 Sandro Santilli <strk@kbt.io>
7 *
8 * This is free software; you can redistribute and/or modify it under
9 * the terms of the GNU General Public Licence. See the COPYING file.
10 *
11 **********************************************************************/
12
13 #include "postgres.h"
14 #include "fmgr.h"
15 #include "c.h" /* for UINT64_FORMAT and uint64 */
16 #include "utils/builtins.h" /* for cstring_to_text */
17 #include "utils/elog.h"
18 #include "utils/memutils.h" /* for TopMemoryContext */
19 #include "utils/array.h" /* for ArrayType */
20 #include "catalog/pg_type.h" /* for INT4OID, TEXTOID */
21 #include "lib/stringinfo.h"
22 #include "access/htup_details.h" /* for heap_form_tuple() */
23 #include "access/xact.h" /* for RegisterXactCallback */
24 #include "funcapi.h" /* for FuncCallContext */
25 #include "executor/spi.h" /* this is what you need to work with SPI */
26 #include "inttypes.h" /* for PRId64 */
27 #include "../postgis_config.h"
28
29 #include "liblwgeom_internal.h" /* for gbox_clone */
30 #include "liblwgeom_topo.h"
31
32 /*#define POSTGIS_DEBUG_LEVEL 1*/
33 #include "lwgeom_log.h"
34 #include "lwgeom_pg.h"
35 #include "pgsql_compat.h"
36
37 #include <stdarg.h>
38
39 #ifndef __GNUC__
40 # define __attribute__ (x)
41 #endif
42
43 #define ABS(x) (x<0?-x:x)
44
45 #ifdef WIN32
46 # define LWTFMT_ELEMID "lld"
47 #else
48 # define LWTFMT_ELEMID PRId64
49 #endif
50
51 /*
52 * This is required for builds against pgsql
53 */
54 PG_MODULE_MAGIC;
55
56 LWT_BE_IFACE* be_iface;
57
58 /*
59 * Private data we'll use for this backend
60 */
61 #define MAXERRLEN 256
62 struct LWT_BE_DATA_T
63 {
64 char lastErrorMsg[MAXERRLEN];
65 /*
66 * This flag will need to be set to false
67 * at top-level function enter and set true
68 * whenever an callback changes the data
69 * in the database.
70 * It will be used by SPI_execute calls to
71 * make sure to see any data change occurring
72 * doring operations.
73 */
74 bool data_changed;
75
76 int topoLoadFailMessageFlavor; /* 0:sql, 1:AddPoint */
77 };
78
79 LWT_BE_DATA be_data;
80
81 struct LWT_BE_TOPOLOGY_T
82 {
83 LWT_BE_DATA* be_data;
84 char *name;
85 int id;
86 int32_t srid;
87 double precision;
88 int hasZ;
89 Oid geometryOID;
90 };
91
92 /* utility funx */
93
94 static void cberror(const LWT_BE_DATA* be, const char *fmt, ...)
95 __attribute__ (( format(printf, 2, 3) ));
96
97 static void
cberror(const LWT_BE_DATA * be_in,const char * fmt,...)98 cberror(const LWT_BE_DATA* be_in, const char *fmt, ...)
99 {
100 LWT_BE_DATA *be = (LWT_BE_DATA*)be_in;/*const cast*/
101 va_list ap;
102
103 va_start(ap, fmt);
104
105 vsnprintf (be->lastErrorMsg, MAXERRLEN, fmt, ap);
106 be->lastErrorMsg[MAXERRLEN-1]='\0';
107
108 va_end(ap);
109 }
110
111 static void
_lwtype_upper_name(int type,char * buf,size_t buflen)112 _lwtype_upper_name(int type, char *buf, size_t buflen)
113 {
114 char *ptr;
115 snprintf(buf, buflen, "%s", lwtype_name(type));
116 buf[buflen-1] = '\0';
117 ptr = buf;
118 while (*ptr)
119 {
120 *ptr = toupper(*ptr);
121 ++ptr;
122 }
123 }
124
125 /* Return an lwalloc'ed geometrical representation of the box */
126 static LWGEOM *
_box2d_to_lwgeom(const GBOX * bbox,int32_t srid)127 _box2d_to_lwgeom(const GBOX *bbox, int32_t srid)
128 {
129 POINTARRAY *pa = ptarray_construct(0, 0, 2);
130 POINT4D p;
131 LWLINE *line;
132
133 p.x = bbox->xmin;
134 p.y = bbox->ymin;
135 ptarray_set_point4d(pa, 0, &p);
136 p.x = bbox->xmax;
137 p.y = bbox->ymax;
138 ptarray_set_point4d(pa, 1, &p);
139 line = lwline_construct(srid, NULL, pa);
140 return lwline_as_lwgeom(line);
141 }
142
143 /* Return lwalloc'ed hexwkb representation for a GBOX */
144 static char *
_box2d_to_hexwkb(const GBOX * bbox,int32_t srid)145 _box2d_to_hexwkb(const GBOX *bbox, int32_t srid)
146 {
147 char *hex;
148 LWGEOM *geom = _box2d_to_lwgeom(bbox, srid);
149 hex = lwgeom_to_hexwkb_buffer(geom, WKT_EXTENDED);
150 lwgeom_free(geom);
151 return hex;
152 }
153
154 /* Backend callbacks */
155
156 static const char*
cb_lastErrorMessage(const LWT_BE_DATA * be)157 cb_lastErrorMessage(const LWT_BE_DATA* be)
158 {
159 return be->lastErrorMsg;
160 }
161
162 static LWT_BE_TOPOLOGY*
cb_loadTopologyByName(const LWT_BE_DATA * be,const char * name)163 cb_loadTopologyByName(const LWT_BE_DATA* be, const char *name)
164 {
165 int spi_result;
166 const char *sql;
167 Datum dat;
168 bool isnull;
169 LWT_BE_TOPOLOGY *topo;
170 MemoryContext oldcontext = CurrentMemoryContext;
171 Datum values[1];
172 Oid argtypes[1];
173 static SPIPlanPtr plan = NULL;
174
175 argtypes[0] = CSTRINGOID;
176 sql =
177 "SELECT id,srid,precision,null::geometry "
178 "FROM topology.topology WHERE name = $1::varchar";
179 if ( ! plan ) /* prepare on first call */
180 {
181 plan = SPI_prepare(sql, 1, argtypes);
182 if ( ! plan )
183 {
184 cberror(be, "unexpected return (%d) from query preparation: %s",
185 SPI_result, sql);
186 return NULL;
187 }
188 SPI_keepplan(plan);
189 /* SPI_freeplan to free, eventually */
190 }
191
192 /* execute */
193 values[0] = CStringGetDatum(name);
194 spi_result = SPI_execute_plan(plan, values, NULL, !be->data_changed, 1);
195 MemoryContextSwitchTo( oldcontext ); /* switch back */
196 if ( spi_result != SPI_OK_SELECT )
197 {
198 cberror(be, "unexpected return (%d) from query execution: %s", spi_result, sql);
199 return NULL;
200 }
201 if ( ! SPI_processed )
202 {
203 if ( be->topoLoadFailMessageFlavor == 1 )
204 {
205 cberror(be, "No topology with name \"%s\" in topology.topology", name);
206 }
207 else
208 {
209 cberror(be, "SQL/MM Spatial exception - invalid topology name");
210 }
211 return NULL;
212 }
213 if ( SPI_processed > 1 )
214 {
215 cberror(be, "multiple topologies named '%s' were found", name);
216 return NULL;
217 }
218
219 topo = palloc(sizeof(LWT_BE_TOPOLOGY));
220 topo->be_data = (LWT_BE_DATA *)be; /* const cast.. */
221 topo->name = pstrdup(name);
222 topo->hasZ = 0;
223
224 dat = SPI_getbinval(SPI_tuptable->vals[0], SPI_tuptable->tupdesc, 1, &isnull);
225 if ( isnull )
226 {
227 cberror(be, "Topology '%s' has null identifier", name);
228 SPI_freetuptable(SPI_tuptable);
229 return NULL;
230 }
231 topo->id = DatumGetInt32(dat);
232
233 dat = SPI_getbinval(SPI_tuptable->vals[0], SPI_tuptable->tupdesc, 2, &isnull);
234 if ( isnull )
235 {
236 cberror(be, "Topology '%s' has null SRID", name);
237 SPI_freetuptable(SPI_tuptable);
238 return NULL;
239 }
240 topo->srid = DatumGetInt32(dat);
241 if ( topo->srid < 0 )
242 {
243 lwnotice("Topology SRID value %d converted to "
244 "the officially unknown SRID value %d", topo->srid, SRID_UNKNOWN);
245 topo->srid = SRID_UNKNOWN;
246 }
247
248 dat = SPI_getbinval(SPI_tuptable->vals[0], SPI_tuptable->tupdesc, 3, &isnull);
249 if ( isnull )
250 {
251 lwnotice("Topology '%s' has null precision, taking as 0", name);
252 topo->precision = 0; /* TODO: should this be -1 instead ? */
253 }
254 else
255 {
256 topo->precision = DatumGetFloat8(dat);
257 }
258
259 /* we're dynamically querying geometry type here */
260 topo->geometryOID = TupleDescAttr(SPI_tuptable->tupdesc, 3)->atttypid;
261
262 POSTGIS_DEBUGF(1, "cb_loadTopologyByName: topo '%s' has "
263 "id %d, srid %d, precision %g",
264 name, topo->id, topo->srid, topo->precision);
265
266 SPI_freetuptable(SPI_tuptable);
267
268 return topo;
269 }
270
271 static int
cb_topoGetSRID(const LWT_BE_TOPOLOGY * topo)272 cb_topoGetSRID(const LWT_BE_TOPOLOGY* topo)
273 {
274 return topo->srid;
275 }
276
277 static int
cb_topoHasZ(const LWT_BE_TOPOLOGY * topo)278 cb_topoHasZ(const LWT_BE_TOPOLOGY* topo)
279 {
280 return topo->hasZ;
281 }
282
283 static double
cb_topoGetPrecision(const LWT_BE_TOPOLOGY * topo)284 cb_topoGetPrecision(const LWT_BE_TOPOLOGY* topo)
285 {
286 return topo->precision;
287 }
288
289 static int
cb_freeTopology(LWT_BE_TOPOLOGY * topo)290 cb_freeTopology(LWT_BE_TOPOLOGY* topo)
291 {
292 pfree(topo->name);
293 pfree(topo);
294 return 1;
295 }
296
297 static void
addEdgeFields(StringInfo str,int fields,int fullEdgeData)298 addEdgeFields(StringInfo str, int fields, int fullEdgeData)
299 {
300 const char *sep = "";
301
302 if ( fields & LWT_COL_EDGE_EDGE_ID )
303 {
304 appendStringInfoString(str, "edge_id");
305 sep = ",";
306 }
307 if ( fields & LWT_COL_EDGE_START_NODE )
308 {
309 appendStringInfo(str, "%sstart_node", sep);
310 sep = ",";
311 }
312 if ( fields & LWT_COL_EDGE_END_NODE )
313 {
314 appendStringInfo(str, "%send_node", sep);
315 sep = ",";
316 }
317 if ( fields & LWT_COL_EDGE_FACE_LEFT )
318 {
319 appendStringInfo(str, "%sleft_face", sep);
320 sep = ",";
321 }
322 if ( fields & LWT_COL_EDGE_FACE_RIGHT )
323 {
324 appendStringInfo(str, "%sright_face", sep);
325 sep = ",";
326 }
327 if ( fields & LWT_COL_EDGE_NEXT_LEFT )
328 {
329 appendStringInfo(str, "%snext_left_edge", sep);
330 if ( fullEdgeData ) appendStringInfoString(str, ", abs_next_left_edge");
331 sep = ",";
332 }
333 if ( fields & LWT_COL_EDGE_NEXT_RIGHT )
334 {
335 appendStringInfo(str, "%snext_right_edge", sep);
336 if ( fullEdgeData ) appendStringInfoString(str, ", abs_next_right_edge");
337 sep = ",";
338 }
339 if ( fields & LWT_COL_EDGE_GEOM )
340 {
341 appendStringInfo(str, "%sgeom", sep);
342 }
343 }
344
345 /* Add edge values in text form, include the parens */
346 static void
addEdgeValues(StringInfo str,const LWT_ISO_EDGE * edge,int fields,int fullEdgeData)347 addEdgeValues(StringInfo str, const LWT_ISO_EDGE *edge, int fields, int fullEdgeData)
348 {
349 char *hexewkb;
350 const char *sep = "";
351
352 appendStringInfoChar(str, '(');
353 if ( fields & LWT_COL_EDGE_EDGE_ID )
354 {
355 if ( edge->edge_id != -1 )
356 appendStringInfo(str, "%" LWTFMT_ELEMID, edge->edge_id);
357 else
358 appendStringInfoString(str, "DEFAULT");
359 sep = ",";
360 }
361 if ( fields & LWT_COL_EDGE_START_NODE )
362 {
363 appendStringInfo(str, "%s%" LWTFMT_ELEMID, sep, edge->start_node);
364 sep = ",";
365 }
366 if ( fields & LWT_COL_EDGE_END_NODE )
367 {
368 appendStringInfo(str, "%s%" LWTFMT_ELEMID, sep, edge->end_node);
369 sep = ",";
370 }
371 if ( fields & LWT_COL_EDGE_FACE_LEFT )
372 {
373 appendStringInfo(str, "%s%" LWTFMT_ELEMID, sep, edge->face_left);
374 sep = ",";
375 }
376 if ( fields & LWT_COL_EDGE_FACE_RIGHT )
377 {
378 appendStringInfo(str, "%s%" LWTFMT_ELEMID, sep, edge->face_right);
379 sep = ",";
380 }
381 if ( fields & LWT_COL_EDGE_NEXT_LEFT )
382 {
383 appendStringInfo(str, "%s%" LWTFMT_ELEMID, sep, edge->next_left);
384 if ( fullEdgeData )
385 appendStringInfo(str, ",%" LWTFMT_ELEMID, ABS(edge->next_left));
386 sep = ",";
387 }
388 if ( fields & LWT_COL_EDGE_NEXT_RIGHT )
389 {
390 appendStringInfo(str, "%s%" LWTFMT_ELEMID, sep, edge->next_right);
391 if ( fullEdgeData )
392 appendStringInfo(str, ",%" LWTFMT_ELEMID, ABS(edge->next_right));
393 sep = ",";
394 }
395 if ( fields & LWT_COL_EDGE_GEOM )
396 {
397 if ( edge->geom )
398 {
399 hexewkb = lwgeom_to_hexwkb_buffer(lwline_as_lwgeom(edge->geom), WKB_EXTENDED);
400 appendStringInfo(str, "%s'%s'::geometry", sep, hexewkb);
401 lwfree(hexewkb);
402 }
403 else
404 {
405 appendStringInfo(str, "%snull", sep);
406 }
407 }
408 appendStringInfoChar(str, ')');
409 }
410
411 enum UpdateType
412 {
413 updSet,
414 updSel,
415 updNot
416 };
417
418 static void
addEdgeUpdate(StringInfo str,const LWT_ISO_EDGE * edge,int fields,int fullEdgeData,enum UpdateType updType)419 addEdgeUpdate(StringInfo str, const LWT_ISO_EDGE* edge, int fields,
420 int fullEdgeData, enum UpdateType updType)
421 {
422 const char *sep = "";
423 const char *sep1;
424 const char *op;
425 char *hexewkb;
426
427 switch (updType)
428 {
429 case updSet:
430 op = "=";
431 sep1 = ",";
432 break;
433 case updSel:
434 op = "=";
435 sep1 = " AND ";
436 break;
437 case updNot:
438 default:
439 op = "!=";
440 sep1 = " AND ";
441 break;
442 }
443
444 if ( fields & LWT_COL_EDGE_EDGE_ID )
445 {
446 appendStringInfoString(str, "edge_id ");
447 appendStringInfo(str, "%s %" LWTFMT_ELEMID, op, edge->edge_id);
448 sep = sep1;
449 }
450 if ( fields & LWT_COL_EDGE_START_NODE )
451 {
452 appendStringInfo(str, "%sstart_node ", sep);
453 appendStringInfo(str, "%s %" LWTFMT_ELEMID, op, edge->start_node);
454 sep = sep1;
455 }
456 if ( fields & LWT_COL_EDGE_END_NODE )
457 {
458 appendStringInfo(str, "%send_node", sep);
459 appendStringInfo(str, "%s %" LWTFMT_ELEMID, op, edge->end_node);
460 sep = sep1;
461 }
462 if ( fields & LWT_COL_EDGE_FACE_LEFT )
463 {
464 appendStringInfo(str, "%sleft_face", sep);
465 appendStringInfo(str, "%s %" LWTFMT_ELEMID, op, edge->face_left);
466 sep = sep1;
467 }
468 if ( fields & LWT_COL_EDGE_FACE_RIGHT )
469 {
470 appendStringInfo(str, "%sright_face", sep);
471 appendStringInfo(str, "%s %" LWTFMT_ELEMID, op, edge->face_right);
472 sep = sep1;
473 }
474 if ( fields & LWT_COL_EDGE_NEXT_LEFT )
475 {
476 appendStringInfo(str, "%snext_left_edge", sep);
477 appendStringInfo(str, "%s %" LWTFMT_ELEMID, op, edge->next_left);
478 sep = sep1;
479 if ( fullEdgeData )
480 {
481 appendStringInfo(str, "%s abs_next_left_edge", sep);
482 appendStringInfo(str, "%s %" LWTFMT_ELEMID, op, ABS(edge->next_left));
483 }
484 }
485 if ( fields & LWT_COL_EDGE_NEXT_RIGHT )
486 {
487 appendStringInfo(str, "%snext_right_edge", sep);
488 appendStringInfo(str, "%s %" LWTFMT_ELEMID, op, edge->next_right);
489 sep = sep1;
490 if ( fullEdgeData )
491 {
492 appendStringInfo(str, "%s abs_next_right_edge", sep);
493 appendStringInfo(str, "%s %" LWTFMT_ELEMID, op, ABS(edge->next_right));
494 }
495 }
496 if ( fields & LWT_COL_EDGE_GEOM )
497 {
498 appendStringInfo(str, "%sgeom", sep);
499 hexewkb = lwgeom_to_hexwkb_buffer(lwline_as_lwgeom(edge->geom), WKB_EXTENDED);
500 appendStringInfo(str, "%s'%s'::geometry", op, hexewkb);
501 lwfree(hexewkb);
502 }
503 }
504
505 static void
addNodeUpdate(StringInfo str,const LWT_ISO_NODE * node,int fields,int fullNodeData,enum UpdateType updType)506 addNodeUpdate(StringInfo str, const LWT_ISO_NODE* node, int fields,
507 int fullNodeData, enum UpdateType updType)
508 {
509 const char *sep = "";
510 const char *sep1;
511 const char *op;
512 char *hexewkb;
513
514 switch (updType)
515 {
516 case updSet:
517 op = "=";
518 sep1 = ",";
519 break;
520 case updSel:
521 op = "=";
522 sep1 = " AND ";
523 break;
524 case updNot:
525 default:
526 op = "!=";
527 sep1 = " AND ";
528 break;
529 }
530
531 if ( fields & LWT_COL_NODE_NODE_ID )
532 {
533 appendStringInfoString(str, "node_id ");
534 appendStringInfo(str, "%s %" LWTFMT_ELEMID, op, node->node_id);
535 sep = sep1;
536 }
537 if ( fields & LWT_COL_NODE_CONTAINING_FACE )
538 {
539 appendStringInfo(str, "%scontaining_face %s", sep, op);
540 if ( node->containing_face != -1 )
541 {
542 appendStringInfo(str, "%" LWTFMT_ELEMID, node->containing_face);
543 }
544 else
545 {
546 appendStringInfoString(str, "null::int");
547 }
548 sep = sep1;
549 }
550 if ( fields & LWT_COL_NODE_GEOM )
551 {
552 appendStringInfo(str, "%sgeom", sep);
553 hexewkb = lwgeom_to_hexwkb_buffer(lwpoint_as_lwgeom(node->geom), WKB_EXTENDED);
554 appendStringInfo(str, "%s'%s'::geometry", op, hexewkb);
555 lwfree(hexewkb);
556 }
557 }
558
559 static void
addNodeFields(StringInfo str,int fields)560 addNodeFields(StringInfo str, int fields)
561 {
562 const char *sep = "";
563
564 if ( fields & LWT_COL_NODE_NODE_ID )
565 {
566 appendStringInfoString(str, "node_id");
567 sep = ",";
568 }
569 if ( fields & LWT_COL_NODE_CONTAINING_FACE )
570 {
571 appendStringInfo(str, "%scontaining_face", sep);
572 sep = ",";
573 }
574 if ( fields & LWT_COL_NODE_GEOM )
575 {
576 appendStringInfo(str, "%sgeom", sep);
577 }
578 }
579
580 static void
addFaceFields(StringInfo str,int fields)581 addFaceFields(StringInfo str, int fields)
582 {
583 const char *sep = "";
584
585 if ( fields & LWT_COL_FACE_FACE_ID )
586 {
587 appendStringInfoString(str, "face_id");
588 sep = ",";
589 }
590 if ( fields & LWT_COL_FACE_MBR )
591 {
592 appendStringInfo(str, "%smbr", sep);
593 sep = ",";
594 }
595 }
596
597 /* Add node values for an insert, in text form */
598 static void
addNodeValues(StringInfo str,const LWT_ISO_NODE * node,int fields)599 addNodeValues(StringInfo str, const LWT_ISO_NODE *node, int fields)
600 {
601 char *hexewkb;
602 const char *sep = "";
603
604 appendStringInfoChar(str, '(');
605
606 if ( fields & LWT_COL_NODE_NODE_ID )
607 {
608 if ( node->node_id != -1 )
609 appendStringInfo(str, "%" LWTFMT_ELEMID, node->node_id);
610 else
611 appendStringInfoString(str, "DEFAULT");
612 sep = ",";
613 }
614
615 if ( fields & LWT_COL_NODE_CONTAINING_FACE )
616 {
617 if ( node->containing_face != -1 )
618 appendStringInfo(str, "%s%" LWTFMT_ELEMID, sep, node->containing_face);
619 else appendStringInfo(str, "%snull::int", sep);
620 }
621
622 if ( fields & LWT_COL_NODE_GEOM )
623 {
624 if ( node->geom )
625 {
626 hexewkb = lwgeom_to_hexwkb_buffer(lwpoint_as_lwgeom(node->geom), WKB_EXTENDED);
627 appendStringInfo(str, "%s'%s'::geometry", sep, hexewkb);
628 lwfree(hexewkb);
629 }
630 else
631 {
632 appendStringInfo(str, "%snull::geometry", sep);
633 }
634 }
635
636 appendStringInfoChar(str, ')');
637 }
638
639 /* Add face values for an insert, in text form */
640 static void
addFaceValues(StringInfo str,LWT_ISO_FACE * face,int32_t srid)641 addFaceValues(StringInfo str, LWT_ISO_FACE *face, int32_t srid)
642 {
643 if ( face->face_id != -1 )
644 appendStringInfo(str, "(%" LWTFMT_ELEMID, face->face_id);
645 else
646 appendStringInfoString(str, "(DEFAULT");
647
648 if ( face->mbr )
649 {
650 {
651 char *hexbox;
652 hexbox = _box2d_to_hexwkb(face->mbr, srid);
653 appendStringInfo(str, ",ST_Envelope('%s'::geometry))", hexbox);
654 lwfree(hexbox);
655 }
656 }
657 else
658 {
659 appendStringInfoString(str, ",null::geometry)");
660 }
661 }
662
663 static void
fillEdgeFields(LWT_ISO_EDGE * edge,HeapTuple row,TupleDesc rowdesc,int fields)664 fillEdgeFields(LWT_ISO_EDGE* edge, HeapTuple row, TupleDesc rowdesc, int fields)
665 {
666 bool isnull;
667 Datum dat;
668 int val;
669 GSERIALIZED *geom;
670 LWGEOM *lwg;
671 int colno = 0;
672
673 POSTGIS_DEBUGF(2, "fillEdgeFields: got %d atts and fields %x",
674 rowdesc->natts, fields);
675
676 if ( fields & LWT_COL_EDGE_EDGE_ID )
677 {
678 dat = SPI_getbinval(row, rowdesc, ++colno, &isnull);
679 if ( isnull )
680 {
681 lwpgwarning("Found edge with NULL edge_id");
682 edge->edge_id = -1;
683 }
684 else
685 {
686 val = DatumGetInt32(dat);
687 POSTGIS_DEBUGF(2, "fillEdgeFields: colno%d (edge_id)"
688 " has int32 val of %d",
689 colno, val);
690 edge->edge_id = val;
691 }
692
693 }
694 if ( fields & LWT_COL_EDGE_START_NODE )
695 {
696 dat = SPI_getbinval(row, rowdesc, ++colno, &isnull);
697 if ( isnull )
698 {
699 lwpgwarning("Found edge with NULL start_node");
700 edge->start_node = -1;
701 }
702 else
703 {
704 val = DatumGetInt32(dat);
705 POSTGIS_DEBUGF(2, "fillEdgeFields: colno%d (start_node)"
706 " has int32 val of %d", colno, val);
707 edge->start_node = val;
708 }
709 }
710 if ( fields & LWT_COL_EDGE_END_NODE )
711 {
712 dat = SPI_getbinval(row, rowdesc, ++colno, &isnull);
713 if ( isnull )
714 {
715 lwpgwarning("Found edge with NULL end_node");
716 edge->end_node = -1;
717 }
718 else
719 {
720 val = DatumGetInt32(dat);
721 POSTGIS_DEBUGF(2, "fillEdgeFields: colno%d (end_node)"
722 " has int32 val of %d", colno, val);
723 edge->end_node = val;
724 }
725 }
726 if ( fields & LWT_COL_EDGE_FACE_LEFT )
727 {
728 dat = SPI_getbinval(row, rowdesc, ++colno, &isnull);
729 if ( isnull )
730 {
731 lwpgwarning("Found edge with NULL face_left");
732 edge->face_left = -1;
733 }
734 else
735 {
736 val = DatumGetInt32(dat);
737 POSTGIS_DEBUGF(2, "fillEdgeFields: colno%d (face_left)"
738 " has int32 val of %d", colno, val);
739 edge->face_left = val;
740 }
741 }
742 if ( fields & LWT_COL_EDGE_FACE_RIGHT )
743 {
744 dat = SPI_getbinval(row, rowdesc, ++colno, &isnull);
745 if ( isnull )
746 {
747 lwpgwarning("Found edge with NULL face_right");
748 edge->face_right = -1;
749 }
750 else
751 {
752 val = DatumGetInt32(dat);
753 POSTGIS_DEBUGF(2, "fillEdgeFields: colno%d (face_right)"
754 " has int32 val of %d", colno, val);
755 edge->face_right = val;
756 }
757 }
758 if ( fields & LWT_COL_EDGE_NEXT_LEFT )
759 {
760 dat = SPI_getbinval(row, rowdesc, ++colno, &isnull);
761 if ( isnull )
762 {
763 lwpgwarning("Found edge with NULL next_left");
764 edge->next_left = -1;
765 }
766 else
767 {
768 val = DatumGetInt32(dat);
769 POSTGIS_DEBUGF(2, "fillEdgeFields: colno%d (next_left)"
770 " has int32 val of %d", colno, val);
771 edge->next_left = val;
772 }
773 }
774 if ( fields & LWT_COL_EDGE_NEXT_RIGHT )
775 {
776 dat = SPI_getbinval(row, rowdesc, ++colno, &isnull);
777 if ( isnull )
778 {
779 lwpgwarning("Found edge with NULL next_right");
780 edge->next_right = -1;
781 }
782 else
783 {
784 val = DatumGetInt32(dat);
785 POSTGIS_DEBUGF(2, "fillEdgeFields: colno%d (next_right)"
786 " has int32 val of %d", colno, val);
787 edge->next_right = val;
788 }
789 }
790 if ( fields & LWT_COL_EDGE_GEOM )
791 {
792 dat = SPI_getbinval(row, rowdesc, ++colno, &isnull);
793 if ( ! isnull )
794 {
795 {
796 MemoryContext oldcontext = CurrentMemoryContext;
797 geom = (GSERIALIZED *)PG_DETOAST_DATUM(dat);
798 lwg = lwgeom_from_gserialized(geom);
799 MemoryContextSwitchTo( TopMemoryContext );
800 edge->geom = lwgeom_as_lwline(lwgeom_clone_deep(lwg));
801 MemoryContextSwitchTo( oldcontext ); /* switch back */
802 lwgeom_free(lwg);
803 if ( DatumGetPointer(dat) != (Pointer)geom ) pfree(geom); /* IF_COPY */
804 }
805 }
806 else
807 {
808 lwpgwarning("Found edge with NULL geometry !");
809 edge->geom = NULL;
810 }
811 }
812 else
813 {
814 edge->geom = NULL;
815 }
816 }
817
818 static void
fillNodeFields(LWT_ISO_NODE * node,HeapTuple row,TupleDesc rowdesc,int fields)819 fillNodeFields(LWT_ISO_NODE* node, HeapTuple row, TupleDesc rowdesc, int fields)
820 {
821 bool isnull;
822 Datum dat;
823 GSERIALIZED *geom;
824 LWGEOM *lwg;
825 int colno = 0;
826
827 if ( fields & LWT_COL_NODE_NODE_ID )
828 {
829 dat = SPI_getbinval(row, rowdesc, ++colno, &isnull);
830 node->node_id = DatumGetInt32(dat);
831 }
832 if ( fields & LWT_COL_NODE_CONTAINING_FACE )
833 {
834 dat = SPI_getbinval(row, rowdesc, ++colno, &isnull);
835 if ( isnull ) node->containing_face = -1;
836 else node->containing_face = DatumGetInt32(dat);
837 }
838 if ( fields & LWT_COL_NODE_GEOM )
839 {
840 dat = SPI_getbinval(row, rowdesc, ++colno, &isnull);
841 if ( ! isnull )
842 {
843 geom = (GSERIALIZED *)PG_DETOAST_DATUM(dat);
844 lwg = lwgeom_from_gserialized(geom);
845 node->geom = lwgeom_as_lwpoint(lwgeom_clone_deep(lwg));
846 lwgeom_free(lwg);
847 if ( DatumGetPointer(dat) != (Pointer)geom ) pfree(geom); /* IF_COPY */
848 }
849 else
850 {
851 lwpgnotice("Found node with NULL geometry !");
852 node->geom = NULL;
853 }
854 }
855 }
856
857 static void
fillFaceFields(LWT_ISO_FACE * face,HeapTuple row,TupleDesc rowdesc,int fields)858 fillFaceFields(LWT_ISO_FACE* face, HeapTuple row, TupleDesc rowdesc, int fields)
859 {
860 bool isnull;
861 Datum dat;
862 GSERIALIZED *geom;
863 LWGEOM *g;
864 const GBOX *box;
865 int colno = 0;
866
867 if ( fields & LWT_COL_FACE_FACE_ID )
868 {
869 dat = SPI_getbinval(row, rowdesc, ++colno, &isnull);
870 face->face_id = DatumGetInt32(dat);
871 }
872 if ( fields & LWT_COL_FACE_MBR )
873 {
874 dat = SPI_getbinval(row, rowdesc, ++colno, &isnull);
875 if ( ! isnull )
876 {
877 /* NOTE: this is a geometry of which we want to take (and clone) the BBOX */
878 geom = (GSERIALIZED *)PG_DETOAST_DATUM(dat);
879 g = lwgeom_from_gserialized(geom);
880 lwgeom_refresh_bbox(g); /* Ensure we use a fit mbr, see #4149 */
881 box = lwgeom_get_bbox(g);
882 if ( box )
883 {
884 POSTGIS_DEBUGF(1, "Face %" LWTFMT_ELEMID " bbox xmin is %.15g", face->face_id, box->xmin);
885 face->mbr = gbox_clone(box);
886 }
887 else
888 {
889 lwpgnotice("Found face with EMPTY MBR !");
890 face->mbr = NULL;
891 }
892 lwgeom_free(g);
893 if ( DatumGetPointer(dat) != (Pointer)geom ) pfree(geom);
894 }
895 else
896 {
897 /* NOTE: perfectly fine for universe face */
898 POSTGIS_DEBUG(1, "Found face with NULL MBR");
899 face->mbr = NULL;
900 }
901 }
902 }
903
904 /* return 0 on failure (null) 1 otherwise */
905 static int
getNotNullInt32(HeapTuple row,TupleDesc desc,int col,int32 * val)906 getNotNullInt32( HeapTuple row, TupleDesc desc, int col, int32 *val )
907 {
908 bool isnull;
909 Datum dat = SPI_getbinval( row, desc, col, &isnull );
910 if ( isnull ) return 0;
911 *val = DatumGetInt32(dat);
912 return 1;
913 }
914
915 /* ----------------- Callbacks start here ------------------------ */
916
917 static LWT_ISO_EDGE *
cb_getEdgeById(const LWT_BE_TOPOLOGY * topo,const LWT_ELEMID * ids,uint64_t * numelems,int fields)918 cb_getEdgeById(const LWT_BE_TOPOLOGY *topo, const LWT_ELEMID *ids, uint64_t *numelems, int fields)
919 {
920 LWT_ISO_EDGE *edges;
921 int spi_result;
922 MemoryContext oldcontext = CurrentMemoryContext;
923 StringInfoData sqldata;
924 StringInfo sql = &sqldata;
925 uint64_t i;
926
927 initStringInfo(sql);
928 appendStringInfoString(sql, "SELECT ");
929 addEdgeFields(sql, fields, 0);
930 appendStringInfo(sql, " FROM \"%s\".edge_data", topo->name);
931 appendStringInfoString(sql, " WHERE edge_id IN (");
932 // add all identifiers here
933 for (i=0; i<*numelems; ++i)
934 {
935 appendStringInfo(sql, "%s%" LWTFMT_ELEMID, (i?",":""), ids[i]);
936 }
937 appendStringInfoString(sql, ")");
938 POSTGIS_DEBUGF(1, "cb_getEdgeById query: %s", sql->data);
939
940 spi_result = SPI_execute(sql->data, !topo->be_data->data_changed, *numelems);
941 MemoryContextSwitchTo( oldcontext ); /* switch back */
942 if ( spi_result != SPI_OK_SELECT )
943 {
944 cberror(topo->be_data, "unexpected return (%d) from query execution: %s", spi_result, sql->data);
945 pfree(sqldata.data);
946 *numelems = UINT64_MAX;
947 return NULL;
948 }
949 pfree(sqldata.data);
950
951 POSTGIS_DEBUGF(1, "cb_getEdgeById: edge query returned " UINT64_FORMAT " rows", SPI_processed);
952 *numelems = SPI_processed;
953 if ( ! SPI_processed )
954 {
955 return NULL;
956 }
957
958 edges = palloc( sizeof(LWT_ISO_EDGE) * *numelems );
959 for ( i=0; i<*numelems; ++i )
960 {
961 HeapTuple row = SPI_tuptable->vals[i];
962 fillEdgeFields(&edges[i], row, SPI_tuptable->tupdesc, fields);
963 }
964
965 SPI_freetuptable(SPI_tuptable);
966
967 return edges;
968 }
969
970 static LWT_ISO_EDGE *
cb_getEdgeByNode(const LWT_BE_TOPOLOGY * topo,const LWT_ELEMID * ids,uint64_t * numelems,int fields)971 cb_getEdgeByNode(const LWT_BE_TOPOLOGY *topo, const LWT_ELEMID *ids, uint64_t *numelems, int fields)
972 {
973 LWT_ISO_EDGE *edges;
974 int spi_result;
975
976 StringInfoData sqldata;
977 StringInfo sql = &sqldata;
978 uint64_t i;
979 MemoryContext oldcontext = CurrentMemoryContext;
980
981 initStringInfo(sql);
982 appendStringInfoString(sql, "SELECT ");
983 addEdgeFields(sql, fields, 0);
984 appendStringInfo(sql, " FROM \"%s\".edge_data", topo->name);
985 appendStringInfoString(sql, " WHERE start_node IN (");
986 // add all identifiers here
987 for (i=0; i<*numelems; ++i)
988 {
989 appendStringInfo(sql, "%s%" LWTFMT_ELEMID, (i?",":""), ids[i]);
990 }
991 appendStringInfoString(sql, ") OR end_node IN (");
992 // add all identifiers here
993 for (i=0; i<*numelems; ++i)
994 {
995 appendStringInfo(sql, "%s%" LWTFMT_ELEMID, (i?",":""), ids[i]);
996 }
997 appendStringInfoString(sql, ")");
998
999 POSTGIS_DEBUGF(1, "cb_getEdgeByNode query: %s", sql->data);
1000 POSTGIS_DEBUGF(1, "data_changed is %d", topo->be_data->data_changed);
1001
1002 spi_result = SPI_execute(sql->data, !topo->be_data->data_changed, 0);
1003 MemoryContextSwitchTo( oldcontext ); /* switch back */
1004 if ( spi_result != SPI_OK_SELECT )
1005 {
1006 cberror(topo->be_data, "unexpected return (%d) from query execution: %s", spi_result, sql->data);
1007 pfree(sqldata.data);
1008 *numelems = UINT64_MAX;
1009 return NULL;
1010 }
1011 pfree(sqldata.data);
1012
1013 POSTGIS_DEBUGF(1, "cb_getEdgeByNode: edge query returned " UINT64_FORMAT " rows", SPI_processed);
1014 *numelems = SPI_processed;
1015 if ( ! SPI_processed )
1016 {
1017 return NULL;
1018 }
1019
1020 edges = palloc( sizeof(LWT_ISO_EDGE) * *numelems );
1021 for ( i=0; i<*numelems; ++i )
1022 {
1023 HeapTuple row = SPI_tuptable->vals[i];
1024 fillEdgeFields(&edges[i], row, SPI_tuptable->tupdesc, fields);
1025 }
1026
1027 SPI_freetuptable(SPI_tuptable);
1028
1029 return edges;
1030 }
1031
1032 static LWT_ISO_EDGE *
cb_getEdgeByFace(const LWT_BE_TOPOLOGY * topo,const LWT_ELEMID * ids,uint64_t * numelems,int fields,const GBOX * box)1033 cb_getEdgeByFace(const LWT_BE_TOPOLOGY *topo, const LWT_ELEMID *ids, uint64_t *numelems, int fields, const GBOX *box)
1034 {
1035 LWT_ISO_EDGE *edges;
1036 int spi_result;
1037 MemoryContext oldcontext = CurrentMemoryContext;
1038 StringInfoData sqldata;
1039 StringInfo sql = &sqldata;
1040 uint64_t i;
1041 ArrayType *array_ids;
1042 Datum *datum_ids;
1043 Datum values[2];
1044 Oid argtypes[2];
1045 int nargs = 1;
1046 GSERIALIZED *gser = NULL;
1047
1048 datum_ids = palloc(sizeof(Datum)*(*numelems));
1049 for (i=0; i<*numelems; ++i) datum_ids[i] = Int32GetDatum(ids[i]);
1050 array_ids = construct_array(datum_ids, *numelems, INT4OID, 4, true, 's');
1051
1052 initStringInfo(sql);
1053 appendStringInfoString(sql, "SELECT ");
1054 addEdgeFields(sql, fields, 0);
1055 appendStringInfo(sql, " FROM \"%s\".edge_data"
1056 " WHERE ( left_face = ANY($1) "
1057 " OR right_face = ANY ($1) )",
1058 topo->name);
1059
1060 values[0] = PointerGetDatum(array_ids);
1061 argtypes[0] = INT4ARRAYOID;
1062
1063 if ( box )
1064 {
1065 LWGEOM *g = _box2d_to_lwgeom(box, topo->srid);
1066 gser = geometry_serialize(g);
1067 lwgeom_free(g);
1068 appendStringInfo(sql, " AND geom && $2");
1069
1070 values[1] = PointerGetDatum(gser);
1071 argtypes[1] = topo->geometryOID;
1072 ++nargs;
1073 }
1074
1075 POSTGIS_DEBUGF(1, "cb_getEdgeByFace query: %s", sql->data);
1076 POSTGIS_DEBUGF(1, "data_changed is %d", topo->be_data->data_changed);
1077
1078 spi_result = SPI_execute_with_args(sql->data, nargs, argtypes, values, NULL,
1079 !topo->be_data->data_changed, 0);
1080 pfree(array_ids); /* not needed anymore */
1081 if ( gser ) pfree(gser); /* not needed anymore */
1082 MemoryContextSwitchTo( oldcontext ); /* switch back */
1083 if ( spi_result != SPI_OK_SELECT )
1084 {
1085 cberror(topo->be_data, "unexpected return (%d) from query execution: %s", spi_result, sql->data);
1086 pfree(sqldata.data);
1087 *numelems = UINT64_MAX;
1088 return NULL;
1089 }
1090 pfree(sqldata.data);
1091
1092 POSTGIS_DEBUGF(1, "cb_getEdgeByFace: edge query returned " UINT64_FORMAT " rows", SPI_processed);
1093 *numelems = SPI_processed;
1094 if ( ! SPI_processed )
1095 {
1096 return NULL;
1097 }
1098
1099 edges = palloc( sizeof(LWT_ISO_EDGE) * *numelems );
1100 for ( i=0; i<*numelems; ++i )
1101 {
1102 HeapTuple row = SPI_tuptable->vals[i];
1103 fillEdgeFields(&edges[i], row, SPI_tuptable->tupdesc, fields);
1104 }
1105
1106 SPI_freetuptable(SPI_tuptable);
1107
1108 return edges;
1109 }
1110
1111 static LWT_ISO_FACE *
cb_getFacesById(const LWT_BE_TOPOLOGY * topo,const LWT_ELEMID * ids,uint64_t * numelems,int fields)1112 cb_getFacesById(const LWT_BE_TOPOLOGY *topo, const LWT_ELEMID *ids, uint64_t *numelems, int fields)
1113 {
1114 LWT_ISO_FACE *faces;
1115 int spi_result;
1116 StringInfoData sqldata;
1117 StringInfo sql = &sqldata;
1118 uint64_t i;
1119 MemoryContext oldcontext = CurrentMemoryContext;
1120
1121 initStringInfo(sql);
1122 appendStringInfoString(sql, "SELECT ");
1123 addFaceFields(sql, fields);
1124 appendStringInfo(sql, " FROM \"%s\".face", topo->name);
1125 appendStringInfoString(sql, " WHERE face_id IN (");
1126 // add all identifiers here
1127 for (i=0; i<*numelems; ++i)
1128 {
1129 appendStringInfo(sql, "%s%" LWTFMT_ELEMID, (i?",":""), ids[i]);
1130 }
1131 appendStringInfoString(sql, ")");
1132
1133 POSTGIS_DEBUGF(1, "cb_getFaceById query: %s", sql->data);
1134 POSTGIS_DEBUGF(1, "data_changed is %d", topo->be_data->data_changed);
1135
1136 spi_result = SPI_execute(sql->data, !topo->be_data->data_changed, 0);
1137 MemoryContextSwitchTo( oldcontext ); /* switch back */
1138 if ( spi_result != SPI_OK_SELECT )
1139 {
1140 cberror(topo->be_data, "unexpected return (%d) from query execution: %s", spi_result, sql->data);
1141 pfree(sqldata.data);
1142 *numelems = UINT64_MAX;
1143 return NULL;
1144 }
1145 pfree(sqldata.data);
1146
1147 POSTGIS_DEBUGF(1, "cb_getFaceById: face query returned " UINT64_FORMAT " rows", SPI_processed);
1148 *numelems = SPI_processed;
1149 if ( ! SPI_processed )
1150 {
1151 return NULL;
1152 }
1153
1154 faces = palloc( sizeof(LWT_ISO_EDGE) * *numelems );
1155 for ( i=0; i<*numelems; ++i )
1156 {
1157 HeapTuple row = SPI_tuptable->vals[i];
1158 fillFaceFields(&faces[i], row, SPI_tuptable->tupdesc, fields);
1159 }
1160
1161 SPI_freetuptable(SPI_tuptable);
1162
1163 return faces;
1164 }
1165
1166 static LWT_ELEMID *
cb_getRingEdges(const LWT_BE_TOPOLOGY * topo,LWT_ELEMID edge,uint64_t * numelems,int limit)1167 cb_getRingEdges(const LWT_BE_TOPOLOGY *topo, LWT_ELEMID edge, uint64_t *numelems, int limit)
1168 {
1169 LWT_ELEMID *edges;
1170 int spi_result;
1171 TupleDesc rowdesc;
1172 StringInfoData sqldata;
1173 StringInfo sql = &sqldata;
1174 uint64_t i;
1175 MemoryContext oldcontext = CurrentMemoryContext;
1176
1177 initStringInfo(sql);
1178 appendStringInfo(sql, "WITH RECURSIVE edgering AS ( "
1179 "SELECT %" LWTFMT_ELEMID
1180 " as signed_edge_id, edge_id, next_left_edge, next_right_edge "
1181 "FROM \"%s\".edge_data WHERE edge_id = %" LWTFMT_ELEMID " UNION "
1182 "SELECT CASE WHEN "
1183 "p.signed_edge_id < 0 THEN p.next_right_edge ELSE p.next_left_edge END, "
1184 "e.edge_id, e.next_left_edge, e.next_right_edge "
1185 "FROM \"%s\".edge_data e, edgering p WHERE "
1186 "e.edge_id = CASE WHEN p.signed_edge_id < 0 THEN "
1187 "abs(p.next_right_edge) ELSE abs(p.next_left_edge) END ) "
1188 "SELECT * FROM edgering",
1189 edge, topo->name, ABS(edge), topo->name);
1190 if ( limit )
1191 {
1192 ++limit; /* so we know if we hit it */
1193 appendStringInfo(sql, " LIMIT %d", limit);
1194 }
1195
1196 POSTGIS_DEBUGF(1, "cb_getRingEdges query (limit %d): %s", limit, sql->data);
1197 spi_result = SPI_execute(sql->data, !topo->be_data->data_changed, limit);
1198 MemoryContextSwitchTo( oldcontext ); /* switch back */
1199 if ( spi_result != SPI_OK_SELECT )
1200 {
1201 cberror(topo->be_data, "unexpected return (%d) from query execution: %s", spi_result, sql->data);
1202 pfree(sqldata.data);
1203 *numelems = UINT64_MAX;
1204 return NULL;
1205 }
1206 pfree(sqldata.data);
1207
1208 POSTGIS_DEBUGF(1, "cb_getRingEdges: edge query returned " UINT64_FORMAT " rows", SPI_processed);
1209 *numelems = SPI_processed;
1210 if ( ! SPI_processed )
1211 {
1212 cberror(
1213 topo->be_data,
1214 "No edge with id %" LWTFMT_ELEMID" in Topology \"%s\"",
1215 ABS(edge),
1216 topo->name
1217 );
1218 return NULL;
1219 }
1220 if (limit && *numelems == (uint64_t)limit)
1221 {
1222 cberror(topo->be_data, "Max traversing limit hit: %d", limit - 1);
1223 *numelems = UINT64_MAX;
1224 return NULL;
1225 }
1226
1227 edges = palloc( sizeof(LWT_ELEMID) * *numelems );
1228 rowdesc = SPI_tuptable->tupdesc;
1229 for ( i=0; i<*numelems; ++i )
1230 {
1231 HeapTuple row = SPI_tuptable->vals[i];
1232 bool isnull;
1233 Datum dat;
1234 int32 val;
1235 dat = SPI_getbinval(row, rowdesc, 1, &isnull);
1236 if ( isnull )
1237 {
1238 lwfree(edges);
1239 cberror(topo->be_data, "Found edge with NULL edge_id");
1240 *numelems = UINT64_MAX;
1241 return NULL;
1242 }
1243 val = DatumGetInt32(dat);
1244 edges[i] = val;
1245 POSTGIS_DEBUGF(1, "Component " UINT64_FORMAT " in ring of edge %" LWTFMT_ELEMID " is edge %d", i, edge, val);
1246
1247 /* For the last entry, check that we returned back to start
1248 * point, or complain about topology being corrupted */
1249 if ( i == *numelems - 1 )
1250 {
1251 int32 nextedge;
1252 int sidecol = val > 0 ? 3 : 4;
1253 const char *sidetext = val > 0 ? "left" : "right";
1254
1255 dat = SPI_getbinval(row, rowdesc, sidecol, &isnull);
1256 if ( isnull )
1257 {
1258 lwfree(edges);
1259 cberror(topo->be_data, "Edge %d" /*LWTFMT_ELEMID*/
1260 " has NULL next_%s_edge",
1261 val, sidetext);
1262 *numelems = UINT64_MAX;
1263 return NULL;
1264 }
1265 nextedge = DatumGetInt32(dat);
1266 POSTGIS_DEBUGF(1, "Last component in ring of edge %"
1267 LWTFMT_ELEMID " (%d) has next_%s_edge %d",
1268 edge, val, sidetext, nextedge);
1269 if ( nextedge != edge )
1270 {
1271 lwfree(edges);
1272 cberror(topo->be_data, "Corrupted topology: ring of edge %"
1273 LWTFMT_ELEMID " is topologically non-closed",
1274 edge);
1275 *numelems = UINT64_MAX;
1276 return NULL;
1277 }
1278 }
1279
1280 }
1281
1282 SPI_freetuptable(SPI_tuptable);
1283
1284 return edges;
1285 }
1286
1287 static LWT_ISO_NODE *
cb_getNodeById(const LWT_BE_TOPOLOGY * topo,const LWT_ELEMID * ids,uint64_t * numelems,int fields)1288 cb_getNodeById(const LWT_BE_TOPOLOGY *topo, const LWT_ELEMID *ids, uint64_t *numelems, int fields)
1289 {
1290 LWT_ISO_NODE *nodes;
1291 int spi_result;
1292
1293 StringInfoData sqldata;
1294 StringInfo sql = &sqldata;
1295 uint64_t i;
1296 MemoryContext oldcontext = CurrentMemoryContext;
1297
1298 initStringInfo(sql);
1299 appendStringInfoString(sql, "SELECT ");
1300 addNodeFields(sql, fields);
1301 appendStringInfo(sql, " FROM \"%s\".node", topo->name);
1302 appendStringInfoString(sql, " WHERE node_id IN (");
1303 // add all identifiers here
1304 for (i=0; i<*numelems; ++i)
1305 {
1306 appendStringInfo(sql, "%s%" LWTFMT_ELEMID, (i?",":""), ids[i]);
1307 }
1308 appendStringInfoString(sql, ")");
1309 POSTGIS_DEBUGF(1, "cb_getNodeById query: %s", sql->data);
1310 spi_result = SPI_execute(sql->data, !topo->be_data->data_changed, *numelems);
1311 MemoryContextSwitchTo( oldcontext ); /* switch back */
1312 if ( spi_result != SPI_OK_SELECT )
1313 {
1314 cberror(topo->be_data, "unexpected return (%d) from query execution: %s", spi_result, sql->data);
1315 pfree(sqldata.data);
1316 *numelems = -1;
1317 return NULL;
1318 }
1319 pfree(sqldata.data);
1320
1321 POSTGIS_DEBUGF(1, "cb_getNodeById: edge query returned " UINT64_FORMAT " rows", SPI_processed);
1322 *numelems = SPI_processed;
1323 if ( ! SPI_processed )
1324 {
1325 return NULL;
1326 }
1327
1328 nodes = palloc( sizeof(LWT_ISO_NODE) * *numelems );
1329 for ( i=0; i<*numelems; ++i )
1330 {
1331 HeapTuple row = SPI_tuptable->vals[i];
1332 fillNodeFields(&nodes[i], row, SPI_tuptable->tupdesc, fields);
1333 }
1334
1335 SPI_freetuptable(SPI_tuptable);
1336
1337 return nodes;
1338 }
1339
1340 static LWT_ISO_NODE *
cb_getNodeByFace(const LWT_BE_TOPOLOGY * topo,const LWT_ELEMID * ids,uint64_t * numelems,int fields,const GBOX * box)1341 cb_getNodeByFace(const LWT_BE_TOPOLOGY *topo, const LWT_ELEMID *ids, uint64_t *numelems, int fields, const GBOX *box)
1342 {
1343 LWT_ISO_NODE *nodes;
1344 int spi_result;
1345 MemoryContext oldcontext = CurrentMemoryContext;
1346 StringInfoData sqldata;
1347 StringInfo sql = &sqldata;
1348 uint64_t i;
1349 char *hexbox;
1350
1351 initStringInfo(sql);
1352 appendStringInfoString(sql, "SELECT ");
1353 addNodeFields(sql, fields);
1354 appendStringInfo(sql, " FROM \"%s\".node", topo->name);
1355 appendStringInfoString(sql, " WHERE containing_face IN (");
1356 // add all identifiers here
1357 for (i=0; i<*numelems; ++i)
1358 {
1359 appendStringInfo(sql, "%s%" LWTFMT_ELEMID, (i?",":""), ids[i]);
1360 }
1361 appendStringInfoString(sql, ")");
1362 if ( box )
1363 {
1364 hexbox = _box2d_to_hexwkb(box, topo->srid);
1365 appendStringInfo(sql, " AND geom && '%s'::geometry", hexbox);
1366 lwfree(hexbox);
1367 }
1368 POSTGIS_DEBUGF(1, "cb_getNodeByFace query: %s", sql->data);
1369 POSTGIS_DEBUGF(1, "data_changed is %d", topo->be_data->data_changed);
1370 spi_result = SPI_execute(sql->data, !topo->be_data->data_changed, 0);
1371 MemoryContextSwitchTo( oldcontext ); /* switch back */
1372 if ( spi_result != SPI_OK_SELECT )
1373 {
1374 cberror(topo->be_data, "unexpected return (%d) from query execution: %s", spi_result, sql->data);
1375 pfree(sqldata.data);
1376 *numelems = UINT64_MAX;
1377 return NULL;
1378 }
1379 pfree(sqldata.data);
1380
1381 POSTGIS_DEBUGF(1, "cb_getNodeByFace: edge query returned " UINT64_FORMAT " rows", SPI_processed);
1382 *numelems = SPI_processed;
1383 if ( ! SPI_processed )
1384 {
1385 return NULL;
1386 }
1387
1388 nodes = palloc( sizeof(LWT_ISO_NODE) * *numelems );
1389 for ( i=0; i<*numelems; ++i )
1390 {
1391 HeapTuple row = SPI_tuptable->vals[i];
1392 fillNodeFields(&nodes[i], row, SPI_tuptable->tupdesc, fields);
1393 }
1394
1395 SPI_freetuptable(SPI_tuptable);
1396
1397 return nodes;
1398 }
1399
1400 static LWT_ISO_EDGE *
cb_getEdgeWithinDistance2D(const LWT_BE_TOPOLOGY * topo,const LWPOINT * pt,double dist,uint64_t * numelems,int fields,int64_t limit)1401 cb_getEdgeWithinDistance2D(const LWT_BE_TOPOLOGY *topo,
1402 const LWPOINT *pt,
1403 double dist,
1404 uint64_t *numelems,
1405 int fields,
1406 int64_t limit)
1407 {
1408 LWT_ISO_EDGE *edges;
1409 int spi_result;
1410 int64_t elems_requested = limit;
1411 char *hexewkb;
1412 MemoryContext oldcontext = CurrentMemoryContext;
1413 StringInfoData sqldata;
1414 StringInfo sql = &sqldata;
1415 uint64_t i;
1416
1417 initStringInfo(sql);
1418 if ( elems_requested == -1 )
1419 {
1420 appendStringInfoString(sql, "SELECT EXISTS ( SELECT 1");
1421 }
1422 else
1423 {
1424 appendStringInfoString(sql, "SELECT ");
1425 addEdgeFields(sql, fields, 0);
1426 }
1427 appendStringInfo(sql, " FROM \"%s\".edge_data", topo->name);
1428 // TODO: use binary cursor here ?
1429 hexewkb = lwgeom_to_hexwkb_buffer(lwpoint_as_lwgeom(pt), WKB_EXTENDED);
1430 if ( dist )
1431 {
1432 appendStringInfo(sql, " WHERE ST_DWithin('%s'::geometry, geom, %g)", hexewkb, dist);
1433 }
1434 else
1435 {
1436 appendStringInfo(sql, " WHERE ST_Within('%s'::geometry, geom)", hexewkb);
1437 }
1438 lwfree(hexewkb);
1439 if ( elems_requested == -1 )
1440 {
1441 appendStringInfoString(sql, ")");
1442 }
1443 else if ( elems_requested > 0 )
1444 {
1445 appendStringInfo(sql, " LIMIT " INT64_FORMAT, elems_requested);
1446 }
1447 POSTGIS_DEBUGF(1, "cb_getEdgeWithinDistance2D: query is: %s", sql->data);
1448 spi_result = SPI_execute(sql->data, !topo->be_data->data_changed, limit >= 0 ? limit : 0);
1449 MemoryContextSwitchTo( oldcontext ); /* switch back */
1450 if ( spi_result != SPI_OK_SELECT )
1451 {
1452 cberror(topo->be_data, "unexpected return (%d) from query execution: %s", spi_result, sql->data);
1453 pfree(sqldata.data);
1454 *numelems = UINT64_MAX;
1455 return NULL;
1456 }
1457 pfree(sqldata.data);
1458
1459 POSTGIS_DEBUGF(1,
1460 "cb_getEdgeWithinDistance2D: edge query "
1461 "(limited by " INT64_FORMAT ") returned " UINT64_FORMAT " rows",
1462 elems_requested,
1463 SPI_processed);
1464 *numelems = SPI_processed;
1465 if ( ! SPI_processed )
1466 {
1467 return NULL;
1468 }
1469
1470 if ( elems_requested == -1 )
1471 {
1472 /* This was an EXISTS query */
1473 {
1474 Datum dat;
1475 bool isnull, exists;
1476 dat = SPI_getbinval(SPI_tuptable->vals[0], SPI_tuptable->tupdesc, 1, &isnull);
1477 exists = DatumGetBool(dat);
1478 *numelems = exists ? 1 : 0;
1479 POSTGIS_DEBUGF(1, "cb_getEdgeWithinDistance2D: exists ? " UINT64_FORMAT, *numelems);
1480 }
1481
1482 SPI_freetuptable(SPI_tuptable);
1483
1484 return NULL;
1485 }
1486
1487 edges = palloc( sizeof(LWT_ISO_EDGE) * *numelems );
1488 for ( i=0; i<*numelems; ++i )
1489 {
1490 HeapTuple row = SPI_tuptable->vals[i];
1491 fillEdgeFields(&edges[i], row, SPI_tuptable->tupdesc, fields);
1492 }
1493
1494 SPI_freetuptable(SPI_tuptable);
1495
1496 return edges;
1497 }
1498
1499 static LWT_ISO_NODE *
cb_getNodeWithinDistance2D(const LWT_BE_TOPOLOGY * topo,const LWPOINT * pt,double dist,uint64_t * numelems,int fields,int64_t limit)1500 cb_getNodeWithinDistance2D(const LWT_BE_TOPOLOGY *topo,
1501 const LWPOINT *pt,
1502 double dist,
1503 uint64_t *numelems,
1504 int fields,
1505 int64_t limit)
1506 {
1507 MemoryContext oldcontext = CurrentMemoryContext;
1508 LWT_ISO_NODE *nodes;
1509 int spi_result;
1510 char *hexewkb;
1511 StringInfoData sqldata;
1512 StringInfo sql = &sqldata;
1513 int64_t elems_requested = limit;
1514 uint64_t i;
1515
1516 initStringInfo(sql);
1517 if ( elems_requested == -1 )
1518 {
1519 appendStringInfoString(sql, "SELECT EXISTS ( SELECT 1");
1520 }
1521 else
1522 {
1523 appendStringInfoString(sql, "SELECT ");
1524 if ( fields ) addNodeFields(sql, fields);
1525 else
1526 {
1527 lwpgwarning("liblwgeom-topo invoked 'getNodeWithinDistance2D' "
1528 "backend callback with limit=%d and no fields",
1529 elems_requested);
1530 appendStringInfo(sql, "*");
1531 }
1532 }
1533 appendStringInfo(sql, " FROM \"%s\".node", topo->name);
1534 // TODO: use binary cursor here ?
1535 hexewkb = lwgeom_to_hexwkb_buffer(lwpoint_as_lwgeom(pt), WKB_EXTENDED);
1536 if ( dist )
1537 {
1538 appendStringInfo(sql, " WHERE ST_DWithin(geom, '%s'::geometry, %g)",
1539 hexewkb, dist);
1540 }
1541 else
1542 {
1543 appendStringInfo(sql, " WHERE ST_Equals(geom, '%s'::geometry)", hexewkb);
1544 }
1545 lwfree(hexewkb);
1546 if ( elems_requested == -1 )
1547 {
1548 appendStringInfoString(sql, ")");
1549 }
1550 else if ( elems_requested > 0 )
1551 {
1552 appendStringInfo(sql, " LIMIT " INT64_FORMAT, elems_requested);
1553 }
1554 spi_result = SPI_execute(sql->data, !topo->be_data->data_changed, limit >= 0 ? limit : 0);
1555 MemoryContextSwitchTo( oldcontext ); /* switch back */
1556 if ( spi_result != SPI_OK_SELECT )
1557 {
1558 cberror(topo->be_data, "unexpected return (%d) from query execution: %s",
1559 spi_result, sql->data);
1560 pfree(sqldata.data);
1561 *numelems = UINT64_MAX;
1562 return NULL;
1563 }
1564 pfree(sqldata.data);
1565
1566 POSTGIS_DEBUGF(1,
1567 "cb_getNodeWithinDistance2D: node query "
1568 "(limited by " INT64_FORMAT ") returned " UINT64_FORMAT " rows",
1569 elems_requested,
1570 SPI_processed);
1571 if ( ! SPI_processed )
1572 {
1573 *numelems = 0;
1574 return NULL;
1575 }
1576
1577 if ( elems_requested == -1 )
1578 {
1579 /* This was an EXISTS query */
1580 {
1581 Datum dat;
1582 bool isnull, exists;
1583 dat = SPI_getbinval(SPI_tuptable->vals[0], SPI_tuptable->tupdesc, 1, &isnull);
1584 exists = DatumGetBool(dat);
1585 *numelems = exists ? 1 : 0;
1586 }
1587
1588 SPI_freetuptable(SPI_tuptable);
1589
1590 return NULL;
1591 }
1592 else
1593 {
1594 *numelems = SPI_processed;
1595 nodes = palloc( sizeof(LWT_ISO_EDGE) * *numelems );
1596 for ( i=0; i<*numelems; ++i )
1597 {
1598 HeapTuple row = SPI_tuptable->vals[i];
1599 fillNodeFields(&nodes[i], row, SPI_tuptable->tupdesc, fields);
1600 }
1601
1602 SPI_freetuptable(SPI_tuptable);
1603
1604 return nodes;
1605 }
1606 }
1607
1608 static int
cb_insertNodes(const LWT_BE_TOPOLOGY * topo,LWT_ISO_NODE * nodes,uint64_t numelems)1609 cb_insertNodes(const LWT_BE_TOPOLOGY *topo, LWT_ISO_NODE *nodes, uint64_t numelems)
1610 {
1611 MemoryContext oldcontext = CurrentMemoryContext;
1612 int spi_result;
1613 StringInfoData sqldata;
1614 StringInfo sql = &sqldata;
1615 uint64_t i;
1616
1617 initStringInfo(sql);
1618 appendStringInfo(sql, "INSERT INTO \"%s\".node (", topo->name);
1619 addNodeFields(sql, LWT_COL_NODE_ALL);
1620 appendStringInfoString(sql, ") VALUES ");
1621 for ( i=0; i<numelems; ++i )
1622 {
1623 if ( i ) appendStringInfoString(sql, ",");
1624 // TODO: prepare and execute ?
1625 addNodeValues(sql, &nodes[i], LWT_COL_NODE_ALL);
1626 }
1627 appendStringInfoString(sql, " RETURNING node_id");
1628
1629 POSTGIS_DEBUGF(1, "cb_insertNodes query: %s", sql->data);
1630
1631 spi_result = SPI_execute(sql->data, false, numelems);
1632 MemoryContextSwitchTo( oldcontext ); /* switch back */
1633 if ( spi_result != SPI_OK_INSERT_RETURNING )
1634 {
1635 cberror(topo->be_data, "unexpected return (%d) from query execution: %s",
1636 spi_result, sql->data);
1637 pfree(sqldata.data);
1638 return 0;
1639 }
1640 pfree(sqldata.data);
1641
1642 if ( SPI_processed ) topo->be_data->data_changed = true;
1643
1644 if (SPI_processed != numelems)
1645 {
1646 cberror(topo->be_data,
1647 "processed " UINT64_FORMAT " rows, expected " UINT64_FORMAT,
1648 (uint64_t)SPI_processed,
1649 numelems);
1650 return 0;
1651 }
1652
1653 /* Set node_id (could skip this if none had it set to -1) */
1654 /* TODO: check for -1 values in the first loop */
1655 for ( i=0; i<numelems; ++i )
1656 {
1657 if ( nodes[i].node_id != -1 ) continue;
1658 fillNodeFields(&nodes[i], SPI_tuptable->vals[i],
1659 SPI_tuptable->tupdesc, LWT_COL_NODE_NODE_ID);
1660 }
1661
1662 SPI_freetuptable(SPI_tuptable);
1663
1664 return 1;
1665 }
1666
1667 static int
cb_insertEdges(const LWT_BE_TOPOLOGY * topo,LWT_ISO_EDGE * edges,uint64_t numelems)1668 cb_insertEdges(const LWT_BE_TOPOLOGY *topo, LWT_ISO_EDGE *edges, uint64_t numelems)
1669 {
1670 MemoryContext oldcontext = CurrentMemoryContext;
1671 int spi_result;
1672 StringInfoData sqldata;
1673 StringInfo sql = &sqldata;
1674 uint64_t i;
1675 int needsEdgeIdReturn = 0;
1676
1677 initStringInfo(sql);
1678 /* NOTE: we insert into "edge", on which an insert rule is defined */
1679 appendStringInfo(sql, "INSERT INTO \"%s\".edge_data (", topo->name);
1680 addEdgeFields(sql, LWT_COL_EDGE_ALL, 1);
1681 appendStringInfoString(sql, ") VALUES ");
1682 for ( i=0; i<numelems; ++i )
1683 {
1684 if ( i ) appendStringInfoString(sql, ",");
1685 // TODO: prepare and execute ?
1686 addEdgeValues(sql, &edges[i], LWT_COL_EDGE_ALL, 1);
1687 if ( edges[i].edge_id == -1 ) needsEdgeIdReturn = 1;
1688 }
1689 if ( needsEdgeIdReturn ) appendStringInfoString(sql, " RETURNING edge_id");
1690
1691 POSTGIS_DEBUGF(1, "cb_insertEdges query (" UINT64_FORMAT " elems): %s", numelems, sql->data);
1692 spi_result = SPI_execute(sql->data, false, numelems);
1693 MemoryContextSwitchTo( oldcontext ); /* switch back */
1694 if ( spi_result != ( needsEdgeIdReturn ? SPI_OK_INSERT_RETURNING : SPI_OK_INSERT ) )
1695 {
1696 cberror(topo->be_data, "unexpected return (%d) from query execution: %s",
1697 spi_result, sql->data);
1698 pfree(sqldata.data);
1699 return -1;
1700 }
1701 pfree(sqldata.data);
1702 if ( SPI_processed ) topo->be_data->data_changed = true;
1703 POSTGIS_DEBUGF(1, "cb_insertEdges query processed " UINT64_FORMAT " rows", SPI_processed);
1704 if ( SPI_processed != (uint64) numelems )
1705 {
1706 cberror(topo->be_data,
1707 "processed " UINT64_FORMAT " rows, expected " UINT64_FORMAT,
1708 (uint64_t)SPI_processed,
1709 numelems);
1710 return -1;
1711 }
1712
1713 if ( needsEdgeIdReturn )
1714 {
1715 /* Set node_id for items that need it */
1716 for (i = 0; i < SPI_processed; ++i)
1717 {
1718 if ( edges[i].edge_id != -1 ) continue;
1719 fillEdgeFields(&edges[i], SPI_tuptable->vals[i],
1720 SPI_tuptable->tupdesc, LWT_COL_EDGE_EDGE_ID);
1721 }
1722 }
1723
1724 SPI_freetuptable(SPI_tuptable);
1725
1726 return SPI_processed;
1727 }
1728
1729 static int
cb_insertFaces(const LWT_BE_TOPOLOGY * topo,LWT_ISO_FACE * faces,uint64_t numelems)1730 cb_insertFaces(const LWT_BE_TOPOLOGY *topo, LWT_ISO_FACE *faces, uint64_t numelems)
1731 {
1732 MemoryContext oldcontext = CurrentMemoryContext;
1733 int spi_result;
1734 StringInfoData sqldata;
1735 StringInfo sql = &sqldata;
1736 uint64_t i;
1737 int needsFaceIdReturn = 0;
1738
1739 initStringInfo(sql);
1740 appendStringInfo(sql, "INSERT INTO \"%s\".face (", topo->name);
1741 addFaceFields(sql, LWT_COL_FACE_ALL);
1742 appendStringInfoString(sql, ") VALUES ");
1743 for ( i=0; i<numelems; ++i )
1744 {
1745 if ( i ) appendStringInfoString(sql, ",");
1746 // TODO: prepare and execute ?
1747 addFaceValues(sql, &faces[i], topo->srid);
1748 if ( faces[i].face_id == -1 ) needsFaceIdReturn = 1;
1749 }
1750 if ( needsFaceIdReturn ) appendStringInfoString(sql, " RETURNING face_id");
1751
1752 POSTGIS_DEBUGF(1, "cb_insertFaces query (" UINT64_FORMAT " elems): %s", numelems, sql->data);
1753 spi_result = SPI_execute(sql->data, false, numelems);
1754 MemoryContextSwitchTo( oldcontext ); /* switch back */
1755 if ( spi_result != ( needsFaceIdReturn ? SPI_OK_INSERT_RETURNING : SPI_OK_INSERT ) )
1756 {
1757 cberror(topo->be_data, "unexpected return (%d) from query execution: %s",
1758 spi_result, sql->data);
1759 pfree(sqldata.data);
1760 return -1;
1761 }
1762 pfree(sqldata.data);
1763 if ( SPI_processed ) topo->be_data->data_changed = true;
1764 POSTGIS_DEBUGF(1, "cb_insertFaces query processed " UINT64_FORMAT " rows", SPI_processed);
1765 if (SPI_processed != numelems)
1766 {
1767 cberror(topo->be_data,
1768 "processed " UINT64_FORMAT " rows, expected " UINT64_FORMAT,
1769 (uint64_t)SPI_processed,
1770 numelems);
1771 return -1;
1772 }
1773
1774 if ( needsFaceIdReturn )
1775 {
1776 /* Set node_id for items that need it */
1777 for ( i=0; i<numelems; ++i )
1778 {
1779 if ( faces[i].face_id != -1 ) continue;
1780 fillFaceFields(&faces[i], SPI_tuptable->vals[i],
1781 SPI_tuptable->tupdesc, LWT_COL_FACE_FACE_ID);
1782 }
1783 }
1784
1785 SPI_freetuptable(SPI_tuptable);
1786
1787 return SPI_processed;
1788 }
1789
1790 static int
cb_updateEdges(const LWT_BE_TOPOLOGY * topo,const LWT_ISO_EDGE * sel_edge,int sel_fields,const LWT_ISO_EDGE * upd_edge,int upd_fields,const LWT_ISO_EDGE * exc_edge,int exc_fields)1791 cb_updateEdges( const LWT_BE_TOPOLOGY* topo,
1792 const LWT_ISO_EDGE* sel_edge, int sel_fields,
1793 const LWT_ISO_EDGE* upd_edge, int upd_fields,
1794 const LWT_ISO_EDGE* exc_edge, int exc_fields )
1795 {
1796 MemoryContext oldcontext = CurrentMemoryContext;
1797 int spi_result;
1798 StringInfoData sqldata;
1799 StringInfo sql = &sqldata;
1800
1801 initStringInfo(sql);
1802 appendStringInfo(sql, "UPDATE \"%s\".edge_data SET ", topo->name);
1803 addEdgeUpdate( sql, upd_edge, upd_fields, 1, updSet );
1804 if ( exc_edge || sel_edge ) appendStringInfoString(sql, " WHERE ");
1805 if ( sel_edge )
1806 {
1807 addEdgeUpdate( sql, sel_edge, sel_fields, 1, updSel );
1808 if ( exc_edge ) appendStringInfoString(sql, " AND ");
1809 }
1810 if ( exc_edge )
1811 {
1812 addEdgeUpdate( sql, exc_edge, exc_fields, 1, updNot );
1813 }
1814
1815 POSTGIS_DEBUGF(1, "cb_updateEdges query: %s", sql->data);
1816
1817 spi_result = SPI_execute( sql->data, false, 0 );
1818 MemoryContextSwitchTo( oldcontext ); /* switch back */
1819 if ( spi_result != SPI_OK_UPDATE )
1820 {
1821 cberror(topo->be_data, "unexpected return (%d) from query execution: %s",
1822 spi_result, sql->data);
1823 pfree(sqldata.data);
1824 return -1;
1825 }
1826 pfree(sqldata.data);
1827
1828 if ( SPI_processed ) topo->be_data->data_changed = true;
1829
1830 POSTGIS_DEBUGF(1, "cb_updateEdges: update query processed " UINT64_FORMAT " rows", SPI_processed);
1831
1832 return SPI_processed;
1833 }
1834
1835 static int
cb_updateNodes(const LWT_BE_TOPOLOGY * topo,const LWT_ISO_NODE * sel_node,int sel_fields,const LWT_ISO_NODE * upd_node,int upd_fields,const LWT_ISO_NODE * exc_node,int exc_fields)1836 cb_updateNodes( const LWT_BE_TOPOLOGY* topo,
1837 const LWT_ISO_NODE* sel_node, int sel_fields,
1838 const LWT_ISO_NODE* upd_node, int upd_fields,
1839 const LWT_ISO_NODE* exc_node, int exc_fields )
1840 {
1841 MemoryContext oldcontext = CurrentMemoryContext;
1842 int spi_result;
1843 StringInfoData sqldata;
1844 StringInfo sql = &sqldata;
1845
1846 initStringInfo(sql);
1847 appendStringInfo(sql, "UPDATE \"%s\".node SET ", topo->name);
1848 addNodeUpdate( sql, upd_node, upd_fields, 1, updSet );
1849 if ( exc_node || sel_node ) appendStringInfoString(sql, " WHERE ");
1850 if ( sel_node )
1851 {
1852 addNodeUpdate( sql, sel_node, sel_fields, 1, updSel );
1853 if ( exc_node ) appendStringInfoString(sql, " AND ");
1854 }
1855 if ( exc_node )
1856 {
1857 addNodeUpdate( sql, exc_node, exc_fields, 1, updNot );
1858 }
1859
1860 POSTGIS_DEBUGF(1, "cb_updateNodes: %s", sql->data);
1861
1862 spi_result = SPI_execute( sql->data, false, 0 );
1863 MemoryContextSwitchTo( oldcontext ); /* switch back */
1864 if ( spi_result != SPI_OK_UPDATE )
1865 {
1866 cberror(topo->be_data, "unexpected return (%d) from query execution: %s",
1867 spi_result, sql->data);
1868 pfree(sqldata.data);
1869 return -1;
1870 }
1871 pfree(sqldata.data);
1872
1873 if ( SPI_processed ) topo->be_data->data_changed = true;
1874
1875 POSTGIS_DEBUGF(1, "cb_updateNodes: update query processed " UINT64_FORMAT " rows", SPI_processed);
1876
1877 return SPI_processed;
1878 }
1879
1880 static int
cb_updateNodesById(const LWT_BE_TOPOLOGY * topo,const LWT_ISO_NODE * nodes,uint64_t numnodes,int fields)1881 cb_updateNodesById(const LWT_BE_TOPOLOGY *topo, const LWT_ISO_NODE *nodes, uint64_t numnodes, int fields)
1882 {
1883 MemoryContext oldcontext = CurrentMemoryContext;
1884 uint64_t i;
1885 int spi_result;
1886 StringInfoData sqldata;
1887 StringInfo sql = &sqldata;
1888 const char *sep = "";
1889 const char *sep1 = ",";
1890
1891 if ( ! fields )
1892 {
1893 cberror(topo->be_data,
1894 "updateNodesById callback called with no update fields!");
1895 return -1;
1896 }
1897
1898 POSTGIS_DEBUGF(1,
1899 "cb_updateNodesById got " UINT64_FORMAT
1900 " nodes to update"
1901 " (fields:%d)",
1902 numnodes,
1903 fields);
1904
1905 initStringInfo(sql);
1906 appendStringInfoString(sql, "WITH newnodes(node_id,");
1907 addNodeFields(sql, fields);
1908 appendStringInfoString(sql, ") AS ( VALUES ");
1909 for (i=0; i<numnodes; ++i)
1910 {
1911 const LWT_ISO_NODE* node = &(nodes[i]);
1912 if ( i ) appendStringInfoString(sql, ",");
1913 addNodeValues(sql, node, LWT_COL_NODE_NODE_ID|fields);
1914 }
1915 appendStringInfo(sql, " ) UPDATE \"%s\".node n SET ", topo->name);
1916
1917 /* TODO: turn the following into a function */
1918 if ( fields & LWT_COL_NODE_NODE_ID )
1919 {
1920 appendStringInfo(sql, "%snode_id = o.node_id", sep);
1921 sep = sep1;
1922 }
1923 if ( fields & LWT_COL_NODE_CONTAINING_FACE )
1924 {
1925 appendStringInfo(sql, "%scontaining_face = o.containing_face", sep);
1926 sep = sep1;
1927 }
1928 if ( fields & LWT_COL_NODE_GEOM )
1929 {
1930 appendStringInfo(sql, "%sgeom = o.geom", sep);
1931 }
1932
1933 appendStringInfo(sql, " FROM newnodes o WHERE n.node_id = o.node_id");
1934
1935 POSTGIS_DEBUGF(1, "cb_updateNodesById query: %s", sql->data);
1936
1937 spi_result = SPI_execute( sql->data, false, 0 );
1938 MemoryContextSwitchTo( oldcontext ); /* switch back */
1939 if ( spi_result != SPI_OK_UPDATE )
1940 {
1941 cberror(topo->be_data, "unexpected return (%d) from query execution: %s",
1942 spi_result, sql->data);
1943 pfree(sqldata.data);
1944 return -1;
1945 }
1946 pfree(sqldata.data);
1947
1948 if ( SPI_processed ) topo->be_data->data_changed = true;
1949
1950 POSTGIS_DEBUGF(1, "cb_updateNodesById: update query processed " UINT64_FORMAT " rows", SPI_processed);
1951
1952 return SPI_processed;
1953 }
1954
1955 static uint64_t
cb_updateFacesById(const LWT_BE_TOPOLOGY * topo,const LWT_ISO_FACE * faces,uint64_t numfaces)1956 cb_updateFacesById( const LWT_BE_TOPOLOGY* topo,
1957 const LWT_ISO_FACE* faces, uint64_t numfaces )
1958 {
1959 MemoryContext oldcontext = CurrentMemoryContext;
1960 uint64_t i;
1961 int spi_result;
1962 StringInfoData sqldata;
1963 StringInfo sql = &sqldata;
1964
1965 initStringInfo(sql);
1966 appendStringInfoString(sql, "WITH newfaces(id,mbr) AS ( VALUES ");
1967 for (i=0; i<numfaces; ++i)
1968 {
1969 const LWT_ISO_FACE* face = &(faces[i]);
1970 char *hexbox = _box2d_to_hexwkb(face->mbr, topo->srid);
1971
1972 if ( i ) appendStringInfoChar(sql, ',');
1973
1974 appendStringInfo(sql, "(%" LWTFMT_ELEMID
1975 ", ST_Envelope('%s'::geometry))",
1976 face->face_id, hexbox);
1977 lwfree(hexbox);
1978 }
1979 appendStringInfo(sql, ") UPDATE \"%s\".face o SET mbr = i.mbr "
1980 "FROM newfaces i WHERE o.face_id = i.id",
1981 topo->name);
1982
1983 POSTGIS_DEBUGF(1, "cb_updateFacesById query: %s", sql->data);
1984
1985 spi_result = SPI_execute( sql->data, false, 0 );
1986 MemoryContextSwitchTo( oldcontext ); /* switch back */
1987 if ( spi_result != SPI_OK_UPDATE )
1988 {
1989 cberror(topo->be_data, "unexpected return (%d) from query execution: %s",
1990 spi_result, sql->data);
1991 pfree(sqldata.data);
1992 return UINT64_MAX;
1993 }
1994 pfree(sqldata.data);
1995
1996 if ( SPI_processed ) topo->be_data->data_changed = true;
1997
1998 POSTGIS_DEBUGF(1, "cb_updateFacesById: update query processed " UINT64_FORMAT " rows", SPI_processed);
1999
2000 return SPI_processed;
2001 }
2002
2003 static int
cb_updateEdgesById(const LWT_BE_TOPOLOGY * topo,const LWT_ISO_EDGE * edges,uint64_t numedges,int fields)2004 cb_updateEdgesById(const LWT_BE_TOPOLOGY *topo, const LWT_ISO_EDGE *edges, uint64_t numedges, int fields)
2005 {
2006 MemoryContext oldcontext = CurrentMemoryContext;
2007 uint64_t i;
2008 int spi_result;
2009 StringInfoData sqldata;
2010 StringInfo sql = &sqldata;
2011 const char *sep = "";
2012 const char *sep1 = ",";
2013
2014 if ( ! fields )
2015 {
2016 cberror(topo->be_data,
2017 "updateEdgesById callback called with no update fields!");
2018 return -1;
2019 }
2020
2021 initStringInfo(sql);
2022 appendStringInfoString(sql, "WITH newedges(edge_id,");
2023 addEdgeFields(sql, fields, 0);
2024 appendStringInfoString(sql, ") AS ( VALUES ");
2025 for (i=0; i<numedges; ++i)
2026 {
2027 const LWT_ISO_EDGE* edge = &(edges[i]);
2028 if ( i ) appendStringInfoString(sql, ",");
2029 addEdgeValues(sql, edge, fields|LWT_COL_EDGE_EDGE_ID, 0);
2030 }
2031 appendStringInfo(sql, ") UPDATE \"%s\".edge_data e SET ", topo->name);
2032
2033 /* TODO: turn the following into a function */
2034 if ( fields & LWT_COL_EDGE_START_NODE )
2035 {
2036 appendStringInfo(sql, "%sstart_node = o.start_node", sep);
2037 sep = sep1;
2038 }
2039 if ( fields & LWT_COL_EDGE_END_NODE )
2040 {
2041 appendStringInfo(sql, "%send_node = o.end_node", sep);
2042 sep = sep1;
2043 }
2044 if ( fields & LWT_COL_EDGE_FACE_LEFT )
2045 {
2046 appendStringInfo(sql, "%sleft_face = o.left_face", sep);
2047 sep = sep1;
2048 }
2049 if ( fields & LWT_COL_EDGE_FACE_RIGHT )
2050 {
2051 appendStringInfo(sql, "%sright_face = o.right_face", sep);
2052 sep = sep1;
2053 }
2054 if ( fields & LWT_COL_EDGE_NEXT_LEFT )
2055 {
2056 appendStringInfo(sql,
2057 "%snext_left_edge = o.next_left_edge, "
2058 "abs_next_left_edge = abs(o.next_left_edge)", sep);
2059 sep = sep1;
2060 }
2061 if ( fields & LWT_COL_EDGE_NEXT_RIGHT )
2062 {
2063 appendStringInfo(sql,
2064 "%snext_right_edge = o.next_right_edge, "
2065 "abs_next_right_edge = abs(o.next_right_edge)", sep);
2066 sep = sep1;
2067 }
2068 if ( fields & LWT_COL_EDGE_GEOM )
2069 {
2070 appendStringInfo(sql, "%sgeom = o.geom", sep);
2071 }
2072
2073 appendStringInfo(sql, " FROM newedges o WHERE e.edge_id = o.edge_id");
2074
2075 POSTGIS_DEBUGF(1, "cb_updateEdgesById query: %s", sql->data);
2076
2077 spi_result = SPI_execute( sql->data, false, 0 );
2078 MemoryContextSwitchTo( oldcontext ); /* switch back */
2079 if ( spi_result != SPI_OK_UPDATE )
2080 {
2081 cberror(topo->be_data, "unexpected return (%d) from query execution: %s",
2082 spi_result, sql->data);
2083 pfree(sqldata.data);
2084 return -1;
2085 }
2086 pfree(sqldata.data);
2087
2088 if ( SPI_processed ) topo->be_data->data_changed = true;
2089
2090 POSTGIS_DEBUGF(1, "cb_updateEdgesById: update query processed " UINT64_FORMAT " rows", SPI_processed);
2091
2092 return SPI_processed;
2093 }
2094
2095 static int
cb_deleteEdges(const LWT_BE_TOPOLOGY * topo,const LWT_ISO_EDGE * sel_edge,int sel_fields)2096 cb_deleteEdges( const LWT_BE_TOPOLOGY* topo,
2097 const LWT_ISO_EDGE* sel_edge, int sel_fields )
2098 {
2099 MemoryContext oldcontext = CurrentMemoryContext;
2100 int spi_result;
2101 StringInfoData sqldata;
2102 StringInfo sql = &sqldata;
2103
2104 initStringInfo(sql);
2105 appendStringInfo(sql, "DELETE FROM \"%s\".edge_data WHERE ", topo->name);
2106 addEdgeUpdate( sql, sel_edge, sel_fields, 0, updSel );
2107
2108 POSTGIS_DEBUGF(1, "cb_deleteEdges: %s", sql->data);
2109
2110 spi_result = SPI_execute( sql->data, false, 0 );
2111 MemoryContextSwitchTo( oldcontext ); /* switch back */
2112 if ( spi_result != SPI_OK_DELETE )
2113 {
2114 cberror(topo->be_data, "unexpected return (%d) from query execution: %s",
2115 spi_result, sql->data);
2116 pfree(sqldata.data);
2117 return -1;
2118 }
2119 pfree(sqldata.data);
2120
2121 if ( SPI_processed ) topo->be_data->data_changed = true;
2122
2123 POSTGIS_DEBUGF(1, "cb_deleteEdges: delete query processed " UINT64_FORMAT " rows", SPI_processed);
2124
2125 return SPI_processed;
2126 }
2127
2128 static LWT_ELEMID
cb_getNextEdgeId(const LWT_BE_TOPOLOGY * topo)2129 cb_getNextEdgeId( const LWT_BE_TOPOLOGY* topo )
2130 {
2131 MemoryContext oldcontext = CurrentMemoryContext;
2132 int spi_result;
2133 StringInfoData sqldata;
2134 StringInfo sql = &sqldata;
2135 bool isnull;
2136 Datum dat;
2137 LWT_ELEMID edge_id;
2138
2139 initStringInfo(sql);
2140 appendStringInfo(sql, "SELECT nextval('\"%s\".edge_data_edge_id_seq')",
2141 topo->name);
2142 spi_result = SPI_execute(sql->data, false, 0);
2143 MemoryContextSwitchTo( oldcontext ); /* switch back */
2144 if ( spi_result != SPI_OK_SELECT )
2145 {
2146 cberror(topo->be_data, "unexpected return (%d) from query execution: %s",
2147 spi_result, sql->data);
2148 pfree(sqldata.data);
2149 return -1;
2150 }
2151 pfree(sqldata.data);
2152
2153 if ( SPI_processed ) topo->be_data->data_changed = true;
2154
2155 if ( SPI_processed != 1 )
2156 {
2157 cberror(topo->be_data, "processed " UINT64_FORMAT " rows, expected 1", (uint64_t)SPI_processed);
2158 return -1;
2159 }
2160
2161 dat = SPI_getbinval( SPI_tuptable->vals[0],
2162 SPI_tuptable->tupdesc, 1, &isnull );
2163 if ( isnull )
2164 {
2165 cberror(topo->be_data, "nextval for edge_id returned null");
2166 return -1;
2167 }
2168 edge_id = DatumGetInt64(dat); /* sequences return 64bit integers */
2169
2170 SPI_freetuptable(SPI_tuptable);
2171
2172 return edge_id;
2173 }
2174
2175 static int
cb_updateTopoGeomEdgeSplit(const LWT_BE_TOPOLOGY * topo,LWT_ELEMID split_edge,LWT_ELEMID new_edge1,LWT_ELEMID new_edge2)2176 cb_updateTopoGeomEdgeSplit ( const LWT_BE_TOPOLOGY* topo,
2177 LWT_ELEMID split_edge, LWT_ELEMID new_edge1, LWT_ELEMID new_edge2 )
2178 {
2179 MemoryContext oldcontext = CurrentMemoryContext;
2180 int spi_result;
2181 StringInfoData sqldata;
2182 StringInfo sql = &sqldata;
2183 int i, ntopogeoms;
2184 const char *proj = "r.element_id, r.topogeo_id, r.layer_id, r.element_type";
2185
2186 initStringInfo(sql);
2187 if ( new_edge2 == -1 )
2188 {
2189 appendStringInfo(sql, "SELECT %s", proj);
2190 }
2191 else
2192 {
2193 appendStringInfoString(sql, "DELETE");
2194 }
2195 appendStringInfo( sql, " FROM \"%s\".relation r %s topology.layer l WHERE "
2196 "l.topology_id = %d AND l.level = 0 AND l.layer_id = r.layer_id "
2197 "AND abs(r.element_id) = %" LWTFMT_ELEMID " AND r.element_type = 2",
2198 topo->name, (new_edge2 == -1 ? "," : "USING" ), topo->id, split_edge );
2199 if ( new_edge2 != -1 )
2200 {
2201 appendStringInfo(sql, " RETURNING %s", proj);
2202 }
2203
2204 POSTGIS_DEBUGF(1, "cb_updateTopoGeomEdgeSplit query: %s", sql->data);
2205
2206 spi_result = SPI_execute(sql->data, new_edge2 == -1 ? !topo->be_data->data_changed : false, 0);
2207 MemoryContextSwitchTo( oldcontext ); /* switch back */
2208 if ( spi_result != ( new_edge2 == -1 ? SPI_OK_SELECT : SPI_OK_DELETE_RETURNING ) )
2209 {
2210 cberror(topo->be_data, "unexpected return (%d) from query execution: %s",
2211 spi_result, sql->data);
2212 pfree(sqldata.data);
2213 return 0;
2214 }
2215
2216 if ( spi_result == SPI_OK_DELETE_RETURNING && SPI_processed )
2217 {
2218 POSTGIS_DEBUGF(1, "cb_updateTopoGeomEdgeSplit: deleted " UINT64_FORMAT " faces", SPI_processed);
2219 topo->be_data->data_changed = true;
2220 }
2221
2222 ntopogeoms = SPI_processed;
2223 if ( ntopogeoms )
2224 {
2225 resetStringInfo(sql);
2226 appendStringInfo(sql, "INSERT INTO \"%s\".relation VALUES ", topo->name);
2227 for ( i=0; i<ntopogeoms; ++i )
2228 {
2229 HeapTuple row = SPI_tuptable->vals[i];
2230 TupleDesc tdesc = SPI_tuptable->tupdesc;
2231 int negate;
2232 int element_id;
2233 int topogeo_id;
2234 int layer_id;
2235 int element_type;
2236
2237 if ( ! getNotNullInt32( row, tdesc, 1, &element_id ) )
2238 {
2239 cberror(topo->be_data,
2240 "unexpected null element_id in \"%s\".relation",
2241 topo->name);
2242 return 0;
2243 }
2244 negate = ( element_id < 0 );
2245
2246 if ( ! getNotNullInt32( row, tdesc, 2, &topogeo_id ) )
2247 {
2248 cberror(topo->be_data,
2249 "unexpected null topogeo_id in \"%s\".relation",
2250 topo->name);
2251 return 0;
2252 }
2253
2254 if ( ! getNotNullInt32( row, tdesc, 3, &layer_id ) )
2255 {
2256 cberror(topo->be_data,
2257 "unexpected null layer_id in \"%s\".relation",
2258 topo->name);
2259 return 0;
2260 }
2261
2262 if ( ! getNotNullInt32( row, tdesc, 4, &element_type ) )
2263 {
2264 cberror(topo->be_data,
2265 "unexpected null element_type in \"%s\".relation",
2266 topo->name);
2267 return 0;
2268 }
2269
2270 if ( i ) appendStringInfoChar(sql, ',');
2271 appendStringInfo(sql, "(%d,%d,%" LWTFMT_ELEMID ",%d)",
2272 topogeo_id, layer_id, negate ? -new_edge1 : new_edge1, element_type);
2273 if ( new_edge2 != -1 )
2274 {
2275 resetStringInfo(sql);
2276 appendStringInfo(sql,
2277 ",VALUES (%d,%d,%" LWTFMT_ELEMID ",%d",
2278 topogeo_id, layer_id, negate ? -new_edge2 : new_edge2, element_type);
2279 }
2280 }
2281
2282 SPI_freetuptable(SPI_tuptable);
2283
2284 POSTGIS_DEBUGF(1, "cb_updateTopoGeomEdgeSplit query: %s", sql->data);
2285 spi_result = SPI_execute(sql->data, false, 0);
2286 MemoryContextSwitchTo( oldcontext ); /* switch back */
2287 if ( spi_result != SPI_OK_INSERT )
2288 {
2289 cberror(topo->be_data, "unexpected return (%d) from query execution: %s",
2290 spi_result, sql->data);
2291 pfree(sqldata.data);
2292 return 0;
2293 }
2294 if ( SPI_processed ) topo->be_data->data_changed = true;
2295 }
2296
2297 POSTGIS_DEBUGF(1, "cb_updateTopoGeomEdgeSplit: updated %d topogeoms", ntopogeoms);
2298
2299 pfree(sqldata.data);
2300 return 1;
2301 }
2302
2303 static int
cb_updateTopoGeomFaceSplit(const LWT_BE_TOPOLOGY * topo,LWT_ELEMID split_face,LWT_ELEMID new_face1,LWT_ELEMID new_face2)2304 cb_updateTopoGeomFaceSplit ( const LWT_BE_TOPOLOGY* topo,
2305 LWT_ELEMID split_face, LWT_ELEMID new_face1, LWT_ELEMID new_face2 )
2306 {
2307 MemoryContext oldcontext = CurrentMemoryContext;
2308 int spi_result;
2309 StringInfoData sqldata;
2310 StringInfo sql = &sqldata;
2311 int i, ntopogeoms;
2312 const char *proj = "r.element_id, r.topogeo_id, r.layer_id, r.element_type";
2313
2314 POSTGIS_DEBUGF(1, "cb_updateTopoGeomFaceSplit signalled "
2315 "split of face %" LWTFMT_ELEMID " into %"
2316 LWTFMT_ELEMID " and %" LWTFMT_ELEMID,
2317 split_face, new_face1, new_face2);
2318
2319 initStringInfo(sql);
2320 if ( new_face2 == -1 )
2321 {
2322 appendStringInfo(sql, "SELECT %s", proj);
2323 }
2324 else
2325 {
2326 appendStringInfoString(sql, "DELETE");
2327 }
2328 appendStringInfo( sql, " FROM \"%s\".relation r %s topology.layer l WHERE "
2329 "l.topology_id = %d AND l.level = 0 AND l.layer_id = r.layer_id "
2330 "AND abs(r.element_id) = %" LWTFMT_ELEMID " AND r.element_type = 3",
2331 topo->name, (new_face2 == -1 ? "," : "USING" ), topo->id, split_face );
2332 if ( new_face2 != -1 )
2333 {
2334 appendStringInfo(sql, " RETURNING %s", proj);
2335 }
2336
2337 POSTGIS_DEBUGF(1, "cb_updateTopoGeomFaceSplit query: %s", sql->data);
2338
2339 spi_result = SPI_execute(sql->data, new_face2 == -1 ? !topo->be_data->data_changed : false, 0);
2340 MemoryContextSwitchTo( oldcontext ); /* switch back */
2341 if ( spi_result != ( new_face2 == -1 ? SPI_OK_SELECT : SPI_OK_DELETE_RETURNING ) )
2342 {
2343 cberror(topo->be_data, "unexpected return (%d) from query execution: %s",
2344 spi_result, sql->data);
2345 pfree(sqldata.data);
2346 return 0;
2347 }
2348
2349 if ( spi_result == SPI_OK_DELETE_RETURNING && SPI_processed )
2350 {
2351 topo->be_data->data_changed = true;
2352 }
2353
2354 ntopogeoms = SPI_processed;
2355 if ( ntopogeoms )
2356 {
2357 resetStringInfo(sql);
2358 appendStringInfo(sql, "INSERT INTO \"%s\".relation VALUES ", topo->name);
2359 for ( i=0; i<ntopogeoms; ++i )
2360 {
2361 HeapTuple row = SPI_tuptable->vals[i];
2362 TupleDesc tdesc = SPI_tuptable->tupdesc;
2363 int negate;
2364 int element_id;
2365 int topogeo_id;
2366 int layer_id;
2367 int element_type;
2368
2369 if ( ! getNotNullInt32( row, tdesc, 1, &element_id ) )
2370 {
2371 cberror(topo->be_data,
2372 "unexpected null element_id in \"%s\".relation",
2373 topo->name);
2374 return 0;
2375 }
2376 negate = ( element_id < 0 );
2377
2378 if ( ! getNotNullInt32( row, tdesc, 2, &topogeo_id ) )
2379 {
2380 cberror(topo->be_data,
2381 "unexpected null topogeo_id in \"%s\".relation",
2382 topo->name);
2383 return 0;
2384 }
2385
2386 if ( ! getNotNullInt32( row, tdesc, 3, &layer_id ) )
2387 {
2388 cberror(topo->be_data,
2389 "unexpected null layer_id in \"%s\".relation",
2390 topo->name);
2391 return 0;
2392 }
2393
2394 if ( ! getNotNullInt32( row, tdesc, 4, &element_type ) )
2395 {
2396 cberror(topo->be_data,
2397 "unexpected null element_type in \"%s\".relation",
2398 topo->name);
2399 return 0;
2400 }
2401
2402 if ( i ) appendStringInfoChar(sql, ',');
2403 appendStringInfo(sql,
2404 "(%d,%d,%" LWTFMT_ELEMID ",%d)",
2405 topogeo_id, layer_id, negate ? -new_face1 : new_face1, element_type);
2406
2407 if ( new_face2 != -1 )
2408 {
2409 appendStringInfo(sql,
2410 ",(%d,%d,%" LWTFMT_ELEMID ",%d)",
2411 topogeo_id, layer_id, negate ? -new_face2 : new_face2, element_type);
2412 }
2413 }
2414
2415 SPI_freetuptable(SPI_tuptable);
2416
2417 POSTGIS_DEBUGF(1, "cb_updateTopoGeomFaceSplit query: %s", sql->data);
2418 spi_result = SPI_execute(sql->data, false, 0);
2419 MemoryContextSwitchTo( oldcontext ); /* switch back */
2420 if ( spi_result != SPI_OK_INSERT )
2421 {
2422 cberror(topo->be_data, "unexpected return (%d) from query execution: %s",
2423 spi_result, sql->data);
2424 pfree(sqldata.data);
2425 return 0;
2426 }
2427
2428 if ( SPI_processed ) topo->be_data->data_changed = true;
2429 }
2430
2431 POSTGIS_DEBUGF(1, "cb_updateTopoGeomFaceSplit: updated %d topogeoms", ntopogeoms);
2432
2433 pfree(sqldata.data);
2434 return 1;
2435 }
2436
2437 static int
cb_checkTopoGeomRemEdge(const LWT_BE_TOPOLOGY * topo,LWT_ELEMID rem_edge,LWT_ELEMID face_left,LWT_ELEMID face_right)2438 cb_checkTopoGeomRemEdge ( const LWT_BE_TOPOLOGY* topo,
2439 LWT_ELEMID rem_edge, LWT_ELEMID face_left, LWT_ELEMID face_right )
2440 {
2441 MemoryContext oldcontext = CurrentMemoryContext;
2442 int spi_result;
2443 StringInfoData sqldata;
2444 StringInfo sql = &sqldata;
2445 const char *tg_id, *layer_id;
2446 const char *schema_name, *table_name, *col_name;
2447 HeapTuple row;
2448 TupleDesc tdesc;
2449
2450 POSTGIS_DEBUG(1, "cb_checkTopoGeomRemEdge enter ");
2451
2452 initStringInfo(sql);
2453 appendStringInfo( sql, "SELECT r.topogeo_id, r.layer_id, "
2454 "l.schema_name, l.table_name, l.feature_column FROM "
2455 "topology.layer l INNER JOIN \"%s\".relation r "
2456 "ON (l.layer_id = r.layer_id) WHERE l.level = 0 AND "
2457 "l.feature_type IN ( 2, 4 ) AND l.topology_id = %d"
2458 " AND r.element_type = 2 AND abs(r.element_id) = %" LWTFMT_ELEMID,
2459 topo->name, topo->id, rem_edge );
2460
2461 POSTGIS_DEBUGF(1, "cb_checkTopoGeomRemEdge query 1: %s", sql->data);
2462
2463 spi_result = SPI_execute(sql->data, !topo->be_data->data_changed, 0);
2464 MemoryContextSwitchTo( oldcontext ); /* switch back */
2465 if ( spi_result != SPI_OK_SELECT )
2466 {
2467 cberror(topo->be_data, "unexpected return (%d) from query execution: %s",
2468 spi_result, sql->data);
2469 pfree(sqldata.data);
2470 return 0;
2471 }
2472
2473 if ( SPI_processed )
2474 {
2475 row = SPI_tuptable->vals[0];
2476 tdesc = SPI_tuptable->tupdesc;
2477
2478 tg_id = SPI_getvalue(row, tdesc, 1);
2479 layer_id = SPI_getvalue(row, tdesc, 2);
2480 schema_name = SPI_getvalue(row, tdesc, 3);
2481 table_name = SPI_getvalue(row, tdesc, 4);
2482 col_name = SPI_getvalue(row, tdesc, 5);
2483
2484 SPI_freetuptable(SPI_tuptable);
2485
2486 cberror(topo->be_data, "TopoGeom %s in layer %s "
2487 "(%s.%s.%s) cannot be represented "
2488 "dropping edge %" LWTFMT_ELEMID,
2489 tg_id, layer_id, schema_name, table_name,
2490 col_name, rem_edge);
2491 return 0;
2492 }
2493
2494
2495 if ( face_left != face_right )
2496 {
2497 POSTGIS_DEBUGF(1, "Deletion of edge %" LWTFMT_ELEMID " joins faces %"
2498 LWTFMT_ELEMID " and %" LWTFMT_ELEMID,
2499 rem_edge, face_left, face_right);
2500 /*
2501 check if any topo_geom is defined only by one of the
2502 joined faces. In such case there would be no way to adapt
2503 the definition in case of healing, so we'd have to bail out
2504 */
2505 initStringInfo(sql);
2506 appendStringInfo( sql, "SELECT t.* FROM ( SELECT r.topogeo_id, "
2507 "r.layer_id, l.schema_name, l.table_name, l.feature_column, "
2508 "array_agg(r.element_id) as elems FROM topology.layer l "
2509 " INNER JOIN \"%s\".relation r ON (l.layer_id = r.layer_id) "
2510 "WHERE l.level = 0 and l.feature_type IN (3, 4) "
2511 "AND l.topology_id = %d"
2512 " AND r.element_type = 3 AND r.element_id = ANY (ARRAY[%" LWTFMT_ELEMID ",%" LWTFMT_ELEMID
2513 "]::int4[]) group by r.topogeo_id, r.layer_id, l.schema_name, "
2514 "l.table_name, l.feature_column ) t WHERE NOT t.elems @> ARRAY[%"
2515 LWTFMT_ELEMID ",%" LWTFMT_ELEMID "]::int4[]",
2516
2517 topo->name, topo->id,
2518 face_left, face_right, face_left, face_right );
2519
2520 POSTGIS_DEBUGF(1, "cb_checkTopoGeomRemEdge query 2: %s", sql->data);
2521 spi_result = SPI_execute(sql->data, !topo->be_data->data_changed, 0);
2522 MemoryContextSwitchTo( oldcontext ); /* switch back */
2523
2524 if ( spi_result != SPI_OK_SELECT )
2525 {
2526 cberror(topo->be_data, "unexpected return (%d) from query execution: %s",
2527 spi_result, sql->data);
2528 pfree(sqldata.data);
2529 return 0;
2530 }
2531
2532 if ( SPI_processed )
2533 {
2534 row = SPI_tuptable->vals[0];
2535 tdesc = SPI_tuptable->tupdesc;
2536
2537 tg_id = SPI_getvalue(row, tdesc, 1);
2538 layer_id = SPI_getvalue(row, tdesc, 2);
2539 schema_name = SPI_getvalue(row, tdesc, 3);
2540 table_name = SPI_getvalue(row, tdesc, 4);
2541 col_name = SPI_getvalue(row, tdesc, 5);
2542
2543 SPI_freetuptable(SPI_tuptable);
2544
2545 cberror(topo->be_data, "TopoGeom %s in layer %s "
2546 "(%s.%s.%s) cannot be represented "
2547 "healing faces %" LWTFMT_ELEMID
2548 " and %" LWTFMT_ELEMID,
2549 tg_id, layer_id, schema_name, table_name,
2550 col_name, face_right, face_left);
2551 return 0;
2552 }
2553 }
2554
2555 return 1;
2556 }
2557
2558 static int
cb_checkTopoGeomRemIsoEdge(const LWT_BE_TOPOLOGY * topo,LWT_ELEMID rem_edge)2559 cb_checkTopoGeomRemIsoEdge ( const LWT_BE_TOPOLOGY* topo,
2560 LWT_ELEMID rem_edge )
2561 {
2562 MemoryContext oldcontext = CurrentMemoryContext;
2563 int spi_result;
2564 StringInfoData sqldata;
2565 StringInfo sql = &sqldata;
2566 const char *tg_id, *layer_id;
2567 const char *schema_name, *table_name, *col_name;
2568 HeapTuple row;
2569 TupleDesc tdesc;
2570
2571 POSTGIS_DEBUG(1, "cb_checkTopoGeomRemIsoEdge enter ");
2572
2573 initStringInfo(sql);
2574 appendStringInfo( sql, "SELECT r.topogeo_id, r.layer_id, "
2575 "l.schema_name, l.table_name, l.feature_column FROM "
2576 "topology.layer l INNER JOIN \"%s\".relation r "
2577 "ON (l.layer_id = r.layer_id) WHERE l.level = 0 AND "
2578 "l.feature_type IN ( 2, 4 ) AND l.topology_id = %d"
2579 " AND r.element_type = 2 AND abs(r.element_id) = %" LWTFMT_ELEMID,
2580 topo->name, topo->id, rem_edge );
2581
2582 POSTGIS_DEBUGF(1, "cb_checkTopoGeomRemIsoEdge query 1: %s", sql->data);
2583
2584 spi_result = SPI_execute(sql->data, !topo->be_data->data_changed, 0);
2585 MemoryContextSwitchTo( oldcontext ); /* switch back */
2586 if ( spi_result != SPI_OK_SELECT )
2587 {
2588 cberror(topo->be_data, "unexpected return (%d) from query execution: %s",
2589 spi_result, sql->data);
2590 pfree(sqldata.data);
2591 return 0;
2592 }
2593
2594 if ( SPI_processed )
2595 {
2596 row = SPI_tuptable->vals[0];
2597 tdesc = SPI_tuptable->tupdesc;
2598
2599 tg_id = SPI_getvalue(row, tdesc, 1);
2600 layer_id = SPI_getvalue(row, tdesc, 2);
2601 schema_name = SPI_getvalue(row, tdesc, 3);
2602 table_name = SPI_getvalue(row, tdesc, 4);
2603 col_name = SPI_getvalue(row, tdesc, 5);
2604
2605 SPI_freetuptable(SPI_tuptable);
2606
2607 cberror(topo->be_data, "TopoGeom %s in layer %s "
2608 "(%s.%s.%s) cannot be represented "
2609 "dropping edge %" LWTFMT_ELEMID,
2610 tg_id, layer_id, schema_name, table_name,
2611 col_name, rem_edge);
2612 return 0;
2613 }
2614
2615 return 1;
2616 }
2617
2618 static int
cb_checkTopoGeomRemNode(const LWT_BE_TOPOLOGY * topo,LWT_ELEMID rem_node,LWT_ELEMID edge1,LWT_ELEMID edge2)2619 cb_checkTopoGeomRemNode ( const LWT_BE_TOPOLOGY* topo,
2620 LWT_ELEMID rem_node, LWT_ELEMID edge1, LWT_ELEMID edge2 )
2621 {
2622 MemoryContext oldcontext = CurrentMemoryContext;
2623 int spi_result;
2624 StringInfoData sqldata;
2625 StringInfo sql = &sqldata;
2626 const char *tg_id, *layer_id;
2627 const char *schema_name, *table_name, *col_name;
2628 HeapTuple row;
2629 TupleDesc tdesc;
2630
2631 initStringInfo(sql);
2632
2633 /* 1: check for lineal TopoGeometry objects being defined by
2634 * only one of the edges to be merged */
2635 appendStringInfo( sql, "SELECT t.* FROM ( SELECT r.topogeo_id, "
2636 "r.layer_id, l.schema_name, l.table_name, l.feature_column, "
2637 "array_agg(abs(r.element_id)) as elems FROM topology.layer l "
2638 " INNER JOIN \"%s\".relation r ON (l.layer_id = r.layer_id) "
2639 "WHERE l.level = 0 and l.feature_type in ( 2, 4 ) "
2640 "AND l.topology_id = %d"
2641 " AND r.element_type = 2 AND abs(r.element_id) = ANY (ARRAY[%" LWTFMT_ELEMID ",%" LWTFMT_ELEMID
2642 "]::int4[]) group by r.topogeo_id, r.layer_id, l.schema_name, "
2643 "l.table_name, l.feature_column ) t WHERE NOT t.elems @> ARRAY[%"
2644 LWTFMT_ELEMID ",%" LWTFMT_ELEMID "]::int4[] LIMIT 1",
2645 topo->name, topo->id,
2646 edge1, edge2, edge1, edge2 );
2647
2648 POSTGIS_DEBUGF(1, "cb_checkTopoGeomRemNode query 1: %s", sql->data);
2649
2650 spi_result = SPI_execute(sql->data, !topo->be_data->data_changed, 0);
2651 MemoryContextSwitchTo( oldcontext ); /* switch back */
2652 if ( spi_result != SPI_OK_SELECT )
2653 {
2654 cberror(topo->be_data, "unexpected return (%d) from query execution: %s",
2655 spi_result, sql->data);
2656 pfree(sqldata.data);
2657 return 0;
2658 }
2659
2660 if ( SPI_processed )
2661 {
2662 row = SPI_tuptable->vals[0];
2663 tdesc = SPI_tuptable->tupdesc;
2664
2665 tg_id = SPI_getvalue(row, tdesc, 1);
2666 layer_id = SPI_getvalue(row, tdesc, 2);
2667 schema_name = SPI_getvalue(row, tdesc, 3);
2668 table_name = SPI_getvalue(row, tdesc, 4);
2669 col_name = SPI_getvalue(row, tdesc, 5);
2670
2671 SPI_freetuptable(SPI_tuptable);
2672
2673 cberror(topo->be_data, "TopoGeom %s in layer %s "
2674 "(%s.%s.%s) cannot be represented "
2675 "healing edges %" LWTFMT_ELEMID
2676 " and %" LWTFMT_ELEMID,
2677 tg_id, layer_id, schema_name, table_name,
2678 col_name, edge1, edge2);
2679 return 0;
2680 }
2681
2682 /* 2: check for puntual TopoGeometry objects being defined by the common
2683 * node, see https://trac.osgeo.org/postgis/ticket/3239 */
2684 resetStringInfo(sql);
2685 appendStringInfo( sql, "SELECT t.* FROM ( SELECT r.topogeo_id, "
2686 "r.layer_id, l.schema_name, l.table_name, l.feature_column, "
2687 "array_agg(abs(r.element_id)) as elems FROM topology.layer l "
2688 " INNER JOIN \"%s\".relation r ON (l.layer_id = r.layer_id) "
2689 "WHERE l.level = 0 and l.feature_type in ( 1, 4 ) "
2690 "AND l.topology_id = %d"
2691 " AND r.element_type = 1 AND r.element_id = %" LWTFMT_ELEMID
2692 " group by r.topogeo_id, r.layer_id, l.schema_name, "
2693 "l.table_name, l.feature_column ) t LIMIT 1",
2694 topo->name, topo->id,
2695 rem_node );
2696
2697 POSTGIS_DEBUGF(1, "cb_checkTopoGeomRemNode query 2: %s", sql->data);
2698
2699 spi_result = SPI_execute(sql->data, !topo->be_data->data_changed, 0);
2700 MemoryContextSwitchTo( oldcontext ); /* switch back */
2701 if ( spi_result != SPI_OK_SELECT )
2702 {
2703 cberror(topo->be_data, "unexpected return (%d) from query execution: %s",
2704 spi_result, sql->data);
2705 pfree(sqldata.data);
2706 return 0;
2707 }
2708
2709 if ( SPI_processed )
2710 {
2711 row = SPI_tuptable->vals[0];
2712 tdesc = SPI_tuptable->tupdesc;
2713
2714 tg_id = SPI_getvalue(row, tdesc, 1);
2715 layer_id = SPI_getvalue(row, tdesc, 2);
2716 schema_name = SPI_getvalue(row, tdesc, 3);
2717 table_name = SPI_getvalue(row, tdesc, 4);
2718 col_name = SPI_getvalue(row, tdesc, 5);
2719
2720 SPI_freetuptable(SPI_tuptable);
2721
2722 cberror(topo->be_data, "TopoGeom %s in layer %s "
2723 "(%s.%s.%s) cannot be represented "
2724 "removing node %" LWTFMT_ELEMID
2725 " connecting edges %" LWTFMT_ELEMID
2726 " and %" LWTFMT_ELEMID,
2727 tg_id, layer_id, schema_name, table_name,
2728 col_name, rem_node, edge1, edge2);
2729 return 0;
2730 }
2731
2732 return 1;
2733 }
2734
2735 static int
cb_checkTopoGeomRemIsoNode(const LWT_BE_TOPOLOGY * topo,LWT_ELEMID rem_node)2736 cb_checkTopoGeomRemIsoNode ( const LWT_BE_TOPOLOGY* topo, LWT_ELEMID rem_node )
2737 {
2738 MemoryContext oldcontext = CurrentMemoryContext;
2739 int spi_result;
2740 StringInfoData sqldata;
2741 StringInfo sql = &sqldata;
2742 const char *tg_id, *layer_id;
2743 const char *schema_name, *table_name, *col_name;
2744 HeapTuple row;
2745 TupleDesc tdesc;
2746
2747 initStringInfo(sql);
2748
2749 /* Check for puntual TopoGeometry objects being defined by the common
2750 * node, see https://trac.osgeo.org/postgis/ticket/3231 */
2751 resetStringInfo(sql);
2752 appendStringInfo( sql, "SELECT t.* FROM ( SELECT r.topogeo_id, "
2753 "r.layer_id, l.schema_name, l.table_name, l.feature_column, "
2754 "array_agg(abs(r.element_id)) as elems FROM topology.layer l "
2755 " INNER JOIN \"%s\".relation r ON (l.layer_id = r.layer_id) "
2756 "WHERE l.level = 0 and l.feature_type in ( 1, 4 ) "
2757 "AND l.topology_id = %d"
2758 " AND r.element_type = 1 AND r.element_id = %" LWTFMT_ELEMID
2759 " group by r.topogeo_id, r.layer_id, l.schema_name, "
2760 "l.table_name, l.feature_column ) t LIMIT 1",
2761 topo->name, topo->id,
2762 rem_node );
2763
2764 POSTGIS_DEBUGF(1, "cb_checkTopoGeomRemIsoNode query 2: %s", sql->data);
2765
2766 spi_result = SPI_execute(sql->data, !topo->be_data->data_changed, 0);
2767 MemoryContextSwitchTo( oldcontext ); /* switch back */
2768 if ( spi_result != SPI_OK_SELECT )
2769 {
2770 cberror(topo->be_data, "unexpected return (%d) from query execution: %s",
2771 spi_result, sql->data);
2772 pfree(sqldata.data);
2773 return 0;
2774 }
2775
2776 if ( SPI_processed )
2777 {
2778 row = SPI_tuptable->vals[0];
2779 tdesc = SPI_tuptable->tupdesc;
2780
2781 tg_id = SPI_getvalue(row, tdesc, 1);
2782 layer_id = SPI_getvalue(row, tdesc, 2);
2783 schema_name = SPI_getvalue(row, tdesc, 3);
2784 table_name = SPI_getvalue(row, tdesc, 4);
2785 col_name = SPI_getvalue(row, tdesc, 5);
2786
2787 SPI_freetuptable(SPI_tuptable);
2788
2789 cberror(topo->be_data, "TopoGeom %s in layer %s "
2790 "(%s.%s.%s) cannot be represented "
2791 "removing node %" LWTFMT_ELEMID,
2792 tg_id, layer_id, schema_name, table_name,
2793 col_name, rem_node);
2794 return 0;
2795 }
2796
2797 return 1;
2798 }
2799
2800 static int
cb_updateTopoGeomFaceHeal(const LWT_BE_TOPOLOGY * topo,LWT_ELEMID face1,LWT_ELEMID face2,LWT_ELEMID newface)2801 cb_updateTopoGeomFaceHeal ( const LWT_BE_TOPOLOGY* topo,
2802 LWT_ELEMID face1, LWT_ELEMID face2, LWT_ELEMID newface )
2803 {
2804 MemoryContext oldcontext = CurrentMemoryContext;
2805 int spi_result;
2806 StringInfoData sqldata;
2807 StringInfo sql = &sqldata;
2808
2809 POSTGIS_DEBUG(1, "cb_updateTopoGeomFaceHeal enter ");
2810
2811 /* delete oldfaces (not equal to newface) from the
2812 * set of primitives defining the TopoGeometries found before */
2813
2814 if ( newface == face1 || newface == face2 )
2815 {
2816 initStringInfo(sql);
2817 /* this query can be optimized */
2818 appendStringInfo( sql, "DELETE FROM \"%s\".relation r "
2819 "USING topology.layer l WHERE l.level = 0"
2820 " AND l.feature_type IN (3,4)"
2821 " AND l.topology_id = %d AND l.layer_id = r.layer_id "
2822 " AND r.element_type = 3"
2823 " AND abs(r.element_id) IN ( %" LWTFMT_ELEMID ",%" LWTFMT_ELEMID ")"
2824 " AND abs(r.element_id) != %" LWTFMT_ELEMID,
2825 topo->name, topo->id, face1, face2, newface );
2826 POSTGIS_DEBUGF(1, "cb_updateTopoGeomFaceHeal query: %s", sql->data);
2827
2828 spi_result = SPI_execute(sql->data, false, 0);
2829 MemoryContextSwitchTo( oldcontext ); /* switch back */
2830 if ( spi_result != SPI_OK_DELETE )
2831 {
2832 cberror(topo->be_data, "unexpected return (%d) from query execution: %s",
2833 spi_result, sql->data);
2834 pfree(sqldata.data);
2835 return 0;
2836 }
2837 if ( SPI_processed ) topo->be_data->data_changed = true;
2838 }
2839 else
2840 {
2841 initStringInfo(sql);
2842 /* delete face1 */
2843 appendStringInfo( sql, "DELETE FROM \"%s\".relation r "
2844 "USING topology.layer l WHERE l.level = 0"
2845 " AND l.feature_type IN (3,4)"
2846 " AND l.topology_id = %d AND l.layer_id = r.layer_id "
2847 " AND r.element_type = 3"
2848 " AND abs(r.element_id) = %" LWTFMT_ELEMID,
2849 topo->name, topo->id, face1 );
2850 POSTGIS_DEBUGF(1, "cb_updateTopoGeomFaceHeal query 1: %s", sql->data);
2851
2852 spi_result = SPI_execute(sql->data, false, 0);
2853 MemoryContextSwitchTo( oldcontext ); /* switch back */
2854 if ( spi_result != SPI_OK_DELETE )
2855 {
2856 cberror(topo->be_data, "unexpected return (%d) from query execution: %s",
2857 spi_result, sql->data);
2858 pfree(sqldata.data);
2859 return 0;
2860 }
2861 if ( SPI_processed ) topo->be_data->data_changed = true;
2862
2863 initStringInfo(sql);
2864 /* update face2 to newface */
2865 appendStringInfo( sql, "UPDATE \"%s\".relation r "
2866 "SET element_id = %" LWTFMT_ELEMID " FROM topology.layer l "
2867 "WHERE l.level = 0 AND l.feature_type IN (3,4)"
2868 " AND l.topology_id = %d"
2869 " AND l.layer_id = r.layer_id"
2870 " AND r.element_type = 3"
2871 " AND r.element_id = %" LWTFMT_ELEMID,
2872 topo->name, newface, topo->id, face2 );
2873 POSTGIS_DEBUGF(1, "cb_updateTopoGeomFaceHeal query 2: %s", sql->data);
2874
2875 spi_result = SPI_execute(sql->data, false, 0);
2876 MemoryContextSwitchTo( oldcontext ); /* switch back */
2877 if ( spi_result != SPI_OK_UPDATE )
2878 {
2879 cberror(topo->be_data, "unexpected return (%d) from query execution: %s",
2880 spi_result, sql->data);
2881 pfree(sqldata.data);
2882 return 0;
2883 }
2884 if ( SPI_processed ) topo->be_data->data_changed = true;
2885 }
2886
2887 return 1;
2888 }
2889
2890 static int
cb_updateTopoGeomEdgeHeal(const LWT_BE_TOPOLOGY * topo,LWT_ELEMID edge1,LWT_ELEMID edge2,LWT_ELEMID newedge)2891 cb_updateTopoGeomEdgeHeal ( const LWT_BE_TOPOLOGY* topo,
2892 LWT_ELEMID edge1, LWT_ELEMID edge2, LWT_ELEMID newedge )
2893 {
2894 MemoryContext oldcontext = CurrentMemoryContext;
2895 int spi_result;
2896 StringInfoData sqldata;
2897 StringInfo sql = &sqldata;
2898
2899 /* delete old edges (not equal to new edge) from the
2900 * set of primitives defining the TopoGeometries found before */
2901
2902 if ( newedge == edge1 || newedge == edge2 )
2903 {
2904 initStringInfo(sql);
2905 /* this query can be optimized */
2906 appendStringInfo( sql, "DELETE FROM \"%s\".relation r "
2907 "USING topology.layer l WHERE l.level = 0"
2908 " AND l.feature_type IN (2,4)"
2909 " AND l.topology_id = %d AND l.layer_id = r.layer_id "
2910 " AND r.element_type = 2"
2911 " AND abs(r.element_id) IN ( %" LWTFMT_ELEMID ",%" LWTFMT_ELEMID ")"
2912 " AND abs(r.element_id) != %" LWTFMT_ELEMID,
2913 topo->name, topo->id, edge1, edge2, newedge );
2914 POSTGIS_DEBUGF(1, "cb_updateTopoGeomEdgeHeal query: %s", sql->data);
2915
2916 spi_result = SPI_execute(sql->data, false, 0);
2917 MemoryContextSwitchTo( oldcontext ); /* switch back */
2918 if ( spi_result != SPI_OK_DELETE )
2919 {
2920 cberror(topo->be_data, "unexpected return (%d) from query execution: %s",
2921 spi_result, sql->data);
2922 pfree(sqldata.data);
2923 return 0;
2924 }
2925 if ( SPI_processed ) topo->be_data->data_changed = true;
2926 }
2927 else
2928 {
2929 initStringInfo(sql);
2930 /* delete edge1 */
2931 appendStringInfo( sql, "DELETE FROM \"%s\".relation r "
2932 "USING topology.layer l WHERE l.level = 0"
2933 " AND l.feature_type IN ( 2, 4 )"
2934 " AND l.topology_id = %d AND l.layer_id = r.layer_id "
2935 " AND r.element_type = 2"
2936 " AND abs(r.element_id) = %" LWTFMT_ELEMID,
2937 topo->name, topo->id, edge2 );
2938 POSTGIS_DEBUGF(1, "cb_updateTopoGeomEdgeHeal query 1: %s", sql->data);
2939
2940 spi_result = SPI_execute(sql->data, false, 0);
2941 MemoryContextSwitchTo( oldcontext ); /* switch back */
2942 if ( spi_result != SPI_OK_DELETE )
2943 {
2944 cberror(topo->be_data, "unexpected return (%d) from query execution: %s",
2945 spi_result, sql->data);
2946 pfree(sqldata.data);
2947 return 0;
2948 }
2949 if ( SPI_processed ) topo->be_data->data_changed = true;
2950
2951 initStringInfo(sql);
2952 /* update edge2 to newedge */
2953 appendStringInfo( sql, "UPDATE \"%s\".relation r "
2954 "SET element_id = %" LWTFMT_ELEMID
2955 " *(element_id/%" LWTFMT_ELEMID
2956 ") FROM topology.layer l "
2957 "WHERE l.level = 0 AND l.feature_type IN (2,4)"
2958 " AND l.topology_id = %d AND l.layer_id = r.layer_id"
2959 " AND r.element_type = 2"
2960 " AND abs(r.element_id) = %" LWTFMT_ELEMID,
2961 topo->name, newedge, edge1, topo->id, edge1 );
2962 POSTGIS_DEBUGF(1, "cb_updateTopoGeomEdgeHeal query 2: %s", sql->data);
2963
2964 spi_result = SPI_execute(sql->data, false, 0);
2965 MemoryContextSwitchTo( oldcontext ); /* switch back */
2966 if ( spi_result != SPI_OK_UPDATE )
2967 {
2968 cberror(topo->be_data, "unexpected return (%d) from query execution: %s",
2969 spi_result, sql->data);
2970 pfree(sqldata.data);
2971 return 0;
2972 }
2973 if ( SPI_processed ) topo->be_data->data_changed = true;
2974 }
2975
2976 return 1;
2977 }
2978
2979 /* Return pointer to closest edge.
2980 * NULL if no edges are in the topology (*numelems=0).
2981 * or there's an error (*numedges=UINT64_MAX)
2982 */
2983 static LWT_ISO_EDGE *
cb_getClosestEdge(const LWT_BE_TOPOLOGY * topo,const LWPOINT * pt,uint64_t * numedges,int fields)2984 cb_getClosestEdge( const LWT_BE_TOPOLOGY* topo, const LWPOINT* pt, uint64_t *numedges, int fields )
2985 {
2986 MemoryContext oldcontext = CurrentMemoryContext;
2987 HeapTuple row;
2988 int spi_result;
2989 StringInfoData sqldata;
2990 StringInfo sql = &sqldata;
2991 GSERIALIZED *pts;
2992 Datum values[1];
2993 Oid argtypes[1];
2994 LWT_ISO_EDGE* edges;
2995
2996 pts = geometry_serialize(lwpoint_as_lwgeom(pt));
2997 if ( ! pts )
2998 {
2999 cberror(topo->be_data, "%s:%d: could not serialize query point",
3000 __FILE__, __LINE__);
3001 *numedges = UINT64_MAX;
3002 return NULL;
3003 }
3004
3005 initStringInfo(sql);
3006
3007
3008 appendStringInfoString(sql, "SELECT ");
3009 addEdgeFields(sql, fields, 0);
3010 appendStringInfo(sql, " FROM \"%s\".edge_data ORDER BY geom <-> $1 ASC, edge_id ASC LIMIT 1", topo->name);
3011
3012 POSTGIS_DEBUGF(1, "cb_getClosestEdge query: %s", sql->data);
3013
3014 values[0] = PointerGetDatum(pts);
3015 argtypes[0] = topo->geometryOID;
3016
3017 spi_result = SPI_execute_with_args(sql->data, 1, argtypes, values, NULL,
3018 !topo->be_data->data_changed, 1);
3019 MemoryContextSwitchTo( oldcontext ); /* switch back */
3020 pfree(pts); /* not needed anymore */
3021 if ( spi_result != SPI_OK_SELECT )
3022 {
3023 cberror(topo->be_data, "unexpected return (%d) from query execution: %s",
3024 spi_result, sql->data);
3025 pfree(sqldata.data);
3026 *numedges = UINT64_MAX;
3027 return NULL;
3028 }
3029
3030 if ( SPI_processed == 0 )
3031 {
3032 /* No edges in topology, point is in universal face */
3033 pfree(sqldata.data);
3034 *numedges = 0;
3035 return NULL;
3036 }
3037
3038 /* Got closest edge, return it */
3039 *numedges = 1;
3040
3041 edges = palloc( sizeof(LWT_ISO_EDGE) );
3042 row = SPI_tuptable->vals[0];
3043 fillEdgeFields(edges, row, SPI_tuptable->tupdesc, fields);
3044 SPI_freetuptable(SPI_tuptable);
3045
3046 return edges;
3047 }
3048
3049 static int
cb_deleteFacesById(const LWT_BE_TOPOLOGY * topo,const LWT_ELEMID * ids,uint64_t numelems)3050 cb_deleteFacesById(const LWT_BE_TOPOLOGY *topo, const LWT_ELEMID *ids, uint64_t numelems)
3051 {
3052 MemoryContext oldcontext = CurrentMemoryContext;
3053 int spi_result;
3054 StringInfoData sqldata;
3055 StringInfo sql = &sqldata;
3056
3057 initStringInfo(sql);
3058 appendStringInfo(sql, "DELETE FROM \"%s\".face WHERE face_id IN (", topo->name);
3059 for (uint64_t i = 0; i < numelems; ++i)
3060 {
3061 appendStringInfo(sql, "%s%" LWTFMT_ELEMID, (i?",":""), ids[i]);
3062 }
3063 appendStringInfoString(sql, ")");
3064
3065 POSTGIS_DEBUGF(1, "cb_deleteFacesById query: %s", sql->data);
3066
3067 spi_result = SPI_execute( sql->data, false, 0 );
3068 MemoryContextSwitchTo( oldcontext ); /* switch back */
3069 if ( spi_result != SPI_OK_DELETE )
3070 {
3071 cberror(topo->be_data, "unexpected return (%d) from query execution: %s",
3072 spi_result, sql->data);
3073 pfree(sqldata.data);
3074 return -1;
3075 }
3076 pfree(sqldata.data);
3077
3078 if ( SPI_processed ) topo->be_data->data_changed = true;
3079
3080 POSTGIS_DEBUGF(1, "cb_deleteFacesById: delete query processed " UINT64_FORMAT " rows", SPI_processed);
3081
3082 return SPI_processed;
3083 }
3084
3085 static int
cb_deleteNodesById(const LWT_BE_TOPOLOGY * topo,const LWT_ELEMID * ids,uint64_t numelems)3086 cb_deleteNodesById(const LWT_BE_TOPOLOGY *topo, const LWT_ELEMID *ids, uint64_t numelems)
3087 {
3088 MemoryContext oldcontext = CurrentMemoryContext;
3089 int spi_result;
3090 StringInfoData sqldata;
3091 StringInfo sql = &sqldata;
3092
3093 initStringInfo(sql);
3094 appendStringInfo(sql, "DELETE FROM \"%s\".node WHERE node_id IN (",
3095 topo->name);
3096 for (uint64_t i = 0; i < numelems; ++i)
3097 {
3098 appendStringInfo(sql, "%s%" LWTFMT_ELEMID, (i?",":""), ids[i]);
3099 }
3100 appendStringInfoString(sql, ")");
3101
3102 POSTGIS_DEBUGF(1, "cb_deleteNodesById query: %s", sql->data);
3103
3104 spi_result = SPI_execute( sql->data, false, 0 );
3105 MemoryContextSwitchTo( oldcontext ); /* switch back */
3106 if ( spi_result != SPI_OK_DELETE )
3107 {
3108 cberror(topo->be_data, "unexpected return (%d) from query execution: %s",
3109 spi_result, sql->data);
3110 pfree(sqldata.data);
3111 return -1;
3112 }
3113 pfree(sqldata.data);
3114
3115 if ( SPI_processed ) topo->be_data->data_changed = true;
3116
3117 POSTGIS_DEBUGF(1, "cb_deleteNodesById: delete query processed " UINT64_FORMAT " rows", SPI_processed);
3118
3119 return SPI_processed;
3120 }
3121
3122 static LWT_ISO_NODE *
cb_getNodeWithinBox2D(const LWT_BE_TOPOLOGY * topo,const GBOX * box,uint64_t * numelems,int fields,int limit)3123 cb_getNodeWithinBox2D(const LWT_BE_TOPOLOGY *topo, const GBOX *box, uint64_t *numelems, int fields, int limit)
3124 {
3125 MemoryContext oldcontext = CurrentMemoryContext;
3126 int spi_result;
3127 StringInfoData sqldata;
3128 StringInfo sql = &sqldata;
3129 uint64_t i;
3130 int elems_requested = limit;
3131 LWT_ISO_NODE* nodes;
3132 char *hexbox;
3133
3134 initStringInfo(sql);
3135
3136 if ( elems_requested == -1 )
3137 {
3138 appendStringInfoString(sql, "SELECT EXISTS ( SELECT 1");
3139 }
3140 else
3141 {
3142 appendStringInfoString(sql, "SELECT ");
3143 addNodeFields(sql, fields);
3144 }
3145 hexbox = _box2d_to_hexwkb(box, topo->srid);
3146 appendStringInfo(sql, " FROM \"%s\".node WHERE geom && '%s'::geometry",
3147 topo->name, hexbox);
3148 lwfree(hexbox);
3149 if ( elems_requested == -1 )
3150 {
3151 appendStringInfoString(sql, ")");
3152 }
3153 else if ( elems_requested > 0 )
3154 {
3155 appendStringInfo(sql, " LIMIT %d", elems_requested);
3156 }
3157 POSTGIS_DEBUGF(1,"cb_getNodeWithinBox2D: query is: %s", sql->data);
3158 spi_result = SPI_execute(sql->data, !topo->be_data->data_changed, limit >= 0 ? limit : 0);
3159 MemoryContextSwitchTo( oldcontext ); /* switch back */
3160 if ( spi_result != SPI_OK_SELECT )
3161 {
3162 cberror(topo->be_data, "unexpected return (%d) from query execution: %s", spi_result, sql->data);
3163 pfree(sqldata.data);
3164 *numelems = UINT64_MAX;
3165 return NULL;
3166 }
3167 pfree(sqldata.data);
3168
3169 POSTGIS_DEBUGF(1,
3170 "cb_getNodeWithinBox2D: edge query "
3171 "(limited by %d) returned " UINT64_FORMAT " rows",
3172 elems_requested,
3173 SPI_processed);
3174 *numelems = SPI_processed;
3175 if ( ! SPI_processed )
3176 {
3177 return NULL;
3178 }
3179
3180 if ( elems_requested == -1 )
3181 {
3182 /* This was an EXISTS query */
3183 {
3184 Datum dat;
3185 bool isnull, exists;
3186 dat = SPI_getbinval(SPI_tuptable->vals[0], SPI_tuptable->tupdesc, 1, &isnull);
3187 exists = DatumGetBool(dat);
3188 SPI_freetuptable(SPI_tuptable);
3189 *numelems = exists ? 1 : 0;
3190 POSTGIS_DEBUGF(1, "cb_getNodeWithinBox2D: exists ? " UINT64_FORMAT, *numelems);
3191 }
3192 return NULL;
3193 }
3194
3195 nodes = palloc( sizeof(LWT_ISO_EDGE) * *numelems );
3196 for ( i=0; i<*numelems; ++i )
3197 {
3198 HeapTuple row = SPI_tuptable->vals[i];
3199 fillNodeFields(&nodes[i], row, SPI_tuptable->tupdesc, fields);
3200 }
3201
3202 SPI_freetuptable(SPI_tuptable);
3203
3204 return nodes;
3205 }
3206
3207 static LWT_ISO_EDGE *
cb_getEdgeWithinBox2D(const LWT_BE_TOPOLOGY * topo,const GBOX * box,uint64_t * numelems,int fields,int limit)3208 cb_getEdgeWithinBox2D(const LWT_BE_TOPOLOGY *topo, const GBOX *box, uint64_t *numelems, int fields, int limit)
3209 {
3210 MemoryContext oldcontext = CurrentMemoryContext;
3211 int spi_result;
3212 StringInfoData sqldata;
3213 StringInfo sql = &sqldata;
3214 uint64_t i;
3215 int elems_requested = limit;
3216 LWT_ISO_EDGE* edges;
3217 char *hexbox;
3218
3219 initStringInfo(sql);
3220
3221 if ( elems_requested == -1 )
3222 {
3223 appendStringInfoString(sql, "SELECT EXISTS ( SELECT 1");
3224 }
3225 else
3226 {
3227 appendStringInfoString(sql, "SELECT ");
3228 addEdgeFields(sql, fields, 0);
3229 }
3230 appendStringInfo(sql, " FROM \"%s\".edge", topo->name);
3231
3232 if ( box )
3233 {
3234 hexbox = _box2d_to_hexwkb(box, topo->srid);
3235 appendStringInfo(sql, " WHERE geom && '%s'::geometry", hexbox);
3236 lwfree(hexbox);
3237 }
3238
3239 if ( elems_requested == -1 )
3240 {
3241 appendStringInfoString(sql, ")");
3242 }
3243 else if ( elems_requested > 0 )
3244 {
3245 appendStringInfo(sql, " LIMIT %d", elems_requested);
3246 }
3247 POSTGIS_DEBUGF(1,"cb_getEdgeWithinBox2D: query is: %s", sql->data);
3248 spi_result = SPI_execute(sql->data, !topo->be_data->data_changed, limit >= 0 ? limit : 0);
3249 MemoryContextSwitchTo( oldcontext ); /* switch back */
3250 if ( spi_result != SPI_OK_SELECT )
3251 {
3252 cberror(topo->be_data, "unexpected return (%d) from query execution: %s", spi_result, sql->data);
3253 pfree(sqldata.data);
3254 *numelems = UINT64_MAX;
3255 return NULL;
3256 }
3257 pfree(sqldata.data);
3258
3259 POSTGIS_DEBUGF(1,
3260 "cb_getEdgeWithinBox2D: edge query "
3261 "(limited by %d) returned " UINT64_FORMAT " rows",
3262 elems_requested,
3263 SPI_processed);
3264 *numelems = SPI_processed;
3265 if ( ! SPI_processed )
3266 {
3267 return NULL;
3268 }
3269
3270 if ( elems_requested == -1 )
3271 {
3272 /* This was an EXISTS query */
3273 {
3274 Datum dat;
3275 bool isnull, exists;
3276 dat = SPI_getbinval(SPI_tuptable->vals[0], SPI_tuptable->tupdesc, 1, &isnull);
3277 exists = DatumGetBool(dat);
3278 *numelems = exists ? 1 : 0;
3279 SPI_freetuptable(SPI_tuptable);
3280 POSTGIS_DEBUGF(1, "cb_getEdgeWithinBox2D: exists ? " UINT64_FORMAT, *numelems);
3281 }
3282 return NULL;
3283 }
3284
3285 edges = palloc( sizeof(LWT_ISO_EDGE) * *numelems );
3286 for ( i=0; i<*numelems; ++i )
3287 {
3288 HeapTuple row = SPI_tuptable->vals[i];
3289 fillEdgeFields(&edges[i], row, SPI_tuptable->tupdesc, fields);
3290 }
3291
3292 SPI_freetuptable(SPI_tuptable);
3293
3294 return edges;
3295 }
3296
3297 static LWT_ISO_FACE *
cb_getFaceWithinBox2D(const LWT_BE_TOPOLOGY * topo,const GBOX * box,uint64_t * numelems,int fields,int limit)3298 cb_getFaceWithinBox2D(const LWT_BE_TOPOLOGY *topo, const GBOX *box, uint64_t *numelems, int fields, int limit)
3299 {
3300 MemoryContext oldcontext = CurrentMemoryContext;
3301 int spi_result;
3302 StringInfoData sqldata;
3303 StringInfo sql = &sqldata;
3304 uint64_t i;
3305 int elems_requested = limit;
3306 LWT_ISO_FACE* faces;
3307 char *hexbox;
3308
3309 initStringInfo(sql);
3310
3311 if ( elems_requested == -1 )
3312 {
3313 appendStringInfoString(sql, "SELECT EXISTS ( SELECT 1");
3314 }
3315 else
3316 {
3317 appendStringInfoString(sql, "SELECT ");
3318 addFaceFields(sql, fields);
3319 }
3320 hexbox = _box2d_to_hexwkb(box, topo->srid);
3321 appendStringInfo(sql, " FROM \"%s\".face WHERE mbr && '%s'::geometry",
3322 topo->name, hexbox);
3323 lwfree(hexbox);
3324 if ( elems_requested == -1 )
3325 {
3326 appendStringInfoString(sql, ")");
3327 }
3328 else if ( elems_requested > 0 )
3329 {
3330 appendStringInfo(sql, " LIMIT %d", elems_requested);
3331 }
3332 POSTGIS_DEBUGF(1,"cb_getFaceWithinBox2D: query is: %s", sql->data);
3333 spi_result = SPI_execute(sql->data, !topo->be_data->data_changed, limit >= 0 ? limit : 0);
3334 MemoryContextSwitchTo( oldcontext ); /* switch back */
3335 if ( spi_result != SPI_OK_SELECT )
3336 {
3337 cberror(topo->be_data, "unexpected return (%d) from query execution: %s", spi_result, sql->data);
3338 pfree(sqldata.data);
3339 *numelems = UINT64_MAX;
3340 return NULL;
3341 }
3342 pfree(sqldata.data);
3343
3344 POSTGIS_DEBUGF(1,
3345 "cb_getFaceWithinBox2D: face query "
3346 "(limited by %d) returned " UINT64_FORMAT " rows",
3347 elems_requested,
3348 SPI_processed);
3349 *numelems = SPI_processed;
3350 if ( ! SPI_processed )
3351 {
3352 return NULL;
3353 }
3354
3355 if ( elems_requested == -1 )
3356 {
3357 /* This was an EXISTS query */
3358 {
3359 Datum dat;
3360 bool isnull, exists;
3361 dat = SPI_getbinval(SPI_tuptable->vals[0], SPI_tuptable->tupdesc, 1, &isnull);
3362 exists = DatumGetBool(dat);
3363 *numelems = exists ? 1 : 0;
3364 POSTGIS_DEBUGF(1, "cb_getFaceWithinBox2D: exists ? " UINT64_FORMAT, *numelems);
3365 }
3366
3367 SPI_freetuptable(SPI_tuptable);
3368
3369 return NULL;
3370 }
3371
3372 faces = palloc( sizeof(LWT_ISO_EDGE) * *numelems );
3373 for ( i=0; i<*numelems; ++i )
3374 {
3375 HeapTuple row = SPI_tuptable->vals[i];
3376 fillFaceFields(&faces[i], row, SPI_tuptable->tupdesc, fields);
3377 }
3378
3379 SPI_freetuptable(SPI_tuptable);
3380
3381 return faces;
3382 }
3383
3384
3385 static LWT_BE_CALLBACKS be_callbacks =
3386 {
3387 cb_lastErrorMessage,
3388 NULL, /* createTopology */
3389 cb_loadTopologyByName,
3390 cb_freeTopology,
3391 cb_getNodeById,
3392 cb_getNodeWithinDistance2D,
3393 cb_insertNodes,
3394 cb_getEdgeById,
3395 cb_getEdgeWithinDistance2D,
3396 cb_getNextEdgeId,
3397 cb_insertEdges,
3398 cb_updateEdges,
3399 cb_getFacesById,
3400 cb_updateTopoGeomEdgeSplit,
3401 cb_deleteEdges,
3402 cb_getNodeWithinBox2D,
3403 cb_getEdgeWithinBox2D,
3404 cb_getEdgeByNode,
3405 cb_updateNodes,
3406 cb_updateTopoGeomFaceSplit,
3407 cb_insertFaces,
3408 cb_updateFacesById,
3409 cb_getRingEdges,
3410 cb_updateEdgesById,
3411 cb_getEdgeByFace,
3412 cb_getNodeByFace,
3413 cb_updateNodesById,
3414 cb_deleteFacesById,
3415 cb_topoGetSRID,
3416 cb_topoGetPrecision,
3417 cb_topoHasZ,
3418 cb_deleteNodesById,
3419 cb_checkTopoGeomRemEdge,
3420 cb_updateTopoGeomFaceHeal,
3421 cb_checkTopoGeomRemNode,
3422 cb_updateTopoGeomEdgeHeal,
3423 cb_getFaceWithinBox2D,
3424 cb_checkTopoGeomRemIsoNode,
3425 cb_checkTopoGeomRemIsoEdge,
3426 cb_getClosestEdge
3427 };
3428
3429 static void
xact_callback(XactEvent event,void * arg)3430 xact_callback(XactEvent event, void *arg)
3431 {
3432 LWT_BE_DATA* data = (LWT_BE_DATA *)arg;
3433 POSTGIS_DEBUGF(1, "xact_callback called with event %d", event);
3434 data->data_changed = false;
3435 }
3436
3437
3438 /*
3439 * Module load callback
3440 */
3441 void _PG_init(void);
3442 void
_PG_init(void)3443 _PG_init(void)
3444 {
3445 MemoryContext old_context;
3446
3447 /*
3448 * install PostgreSQL handlers for liblwgeom
3449 * NOTE: they may be already in place!
3450 */
3451 pg_install_lwgeom_handlers();
3452
3453 /* Switch to the top memory context so that the backend interface
3454 * is valid for the whole backend lifetime */
3455 old_context = MemoryContextSwitchTo( TopMemoryContext );
3456
3457 /* initialize backend data */
3458 be_data.data_changed = false;
3459 be_data.topoLoadFailMessageFlavor = 0;
3460
3461 /* hook on transaction end to reset data_changed */
3462 RegisterXactCallback(xact_callback, &be_data);
3463
3464 /* register callbacks against liblwgeom-topo */
3465 be_iface = lwt_CreateBackendIface(&be_data);
3466 lwt_BackendIfaceRegisterCallbacks(be_iface, &be_callbacks);
3467
3468 /* Switch back to whatever memory context was in place
3469 * at time of _PG_init enter.
3470 * See http://www.postgresql.org/message-id/20150623114125.GD5835@localhost
3471 */
3472 MemoryContextSwitchTo(old_context);
3473 }
3474
3475 /*
3476 * Module unload callback
3477 */
3478 void _PG_fini(void);
3479 void
_PG_fini(void)3480 _PG_fini(void)
3481 {
3482 elog(NOTICE, "Goodbye from PostGIS Topology %s", POSTGIS_VERSION);
3483
3484 UnregisterXactCallback(xact_callback, &be_data);
3485 lwt_FreeBackendIface(be_iface);
3486 }
3487
3488 /* ST_ModEdgeSplit(atopology, anedge, apoint) */
3489 Datum ST_ModEdgeSplit(PG_FUNCTION_ARGS);
3490 PG_FUNCTION_INFO_V1(ST_ModEdgeSplit);
ST_ModEdgeSplit(PG_FUNCTION_ARGS)3491 Datum ST_ModEdgeSplit(PG_FUNCTION_ARGS)
3492 {
3493 text* toponame_text;
3494 char* toponame;
3495 LWT_ELEMID edge_id;
3496 LWT_ELEMID node_id;
3497 GSERIALIZED *geom;
3498 LWGEOM *lwgeom;
3499 LWPOINT *pt;
3500 LWT_TOPOLOGY *topo;
3501
3502 if ( PG_ARGISNULL(0) || PG_ARGISNULL(1) || PG_ARGISNULL(2) )
3503 {
3504 lwpgerror("SQL/MM Spatial exception - null argument");
3505 PG_RETURN_NULL();
3506 }
3507
3508 toponame_text = PG_GETARG_TEXT_P(0);
3509 toponame = text_to_cstring(toponame_text);
3510 PG_FREE_IF_COPY(toponame_text, 0);
3511
3512 edge_id = PG_GETARG_INT32(1) ;
3513
3514 geom = PG_GETARG_GSERIALIZED_P(2);
3515 lwgeom = lwgeom_from_gserialized(geom);
3516 pt = lwgeom_as_lwpoint(lwgeom);
3517 if ( ! pt )
3518 {
3519 lwgeom_free(lwgeom);
3520 PG_FREE_IF_COPY(geom, 2);
3521 lwpgerror("ST_ModEdgeSplit third argument must be a point geometry");
3522 PG_RETURN_NULL();
3523 }
3524
3525 if ( SPI_OK_CONNECT != SPI_connect() )
3526 {
3527 lwpgerror("Could not connect to SPI");
3528 PG_RETURN_NULL();
3529 }
3530
3531 topo = lwt_LoadTopology(be_iface, toponame);
3532 pfree(toponame);
3533 if ( ! topo )
3534 {
3535 /* should never reach this point, as lwerror would raise an exception */
3536 SPI_finish();
3537 PG_RETURN_NULL();
3538 }
3539
3540 POSTGIS_DEBUG(1, "Calling lwt_ModEdgeSplit");
3541 node_id = lwt_ModEdgeSplit(topo, edge_id, pt, 0);
3542 POSTGIS_DEBUG(1, "lwt_ModEdgeSplit returned");
3543 lwgeom_free(lwgeom);
3544 PG_FREE_IF_COPY(geom, 3);
3545 lwt_FreeTopology(topo);
3546
3547 if ( node_id == -1 )
3548 {
3549 /* should never reach this point, as lwerror would raise an exception */
3550 SPI_finish();
3551 PG_RETURN_NULL();
3552 }
3553
3554 SPI_finish();
3555 PG_RETURN_INT32(node_id);
3556 }
3557
3558 /* ST_NewEdgesSplit(atopology, anedge, apoint) */
3559 Datum ST_NewEdgesSplit(PG_FUNCTION_ARGS);
3560 PG_FUNCTION_INFO_V1(ST_NewEdgesSplit);
ST_NewEdgesSplit(PG_FUNCTION_ARGS)3561 Datum ST_NewEdgesSplit(PG_FUNCTION_ARGS)
3562 {
3563 text* toponame_text;
3564 char* toponame;
3565 LWT_ELEMID edge_id;
3566 LWT_ELEMID node_id;
3567 GSERIALIZED *geom;
3568 LWGEOM *lwgeom;
3569 LWPOINT *pt;
3570 LWT_TOPOLOGY *topo;
3571
3572 if ( PG_ARGISNULL(0) || PG_ARGISNULL(1) || PG_ARGISNULL(2) )
3573 {
3574 lwpgerror("SQL/MM Spatial exception - null argument");
3575 PG_RETURN_NULL();
3576 }
3577
3578 toponame_text = PG_GETARG_TEXT_P(0);
3579 toponame = text_to_cstring(toponame_text);
3580 PG_FREE_IF_COPY(toponame_text, 0);
3581
3582 edge_id = PG_GETARG_INT32(1) ;
3583
3584 geom = PG_GETARG_GSERIALIZED_P(2);
3585 lwgeom = lwgeom_from_gserialized(geom);
3586 pt = lwgeom_as_lwpoint(lwgeom);
3587 if ( ! pt )
3588 {
3589 lwgeom_free(lwgeom);
3590 PG_FREE_IF_COPY(geom, 2);
3591 lwpgerror("ST_NewEdgesSplit third argument must be a point geometry");
3592 PG_RETURN_NULL();
3593 }
3594
3595 if ( SPI_OK_CONNECT != SPI_connect() )
3596 {
3597 lwpgerror("Could not connect to SPI");
3598 PG_RETURN_NULL();
3599 }
3600
3601 topo = lwt_LoadTopology(be_iface, toponame);
3602 pfree(toponame);
3603 if ( ! topo )
3604 {
3605 /* should never reach this point, as lwerror would raise an exception */
3606 SPI_finish();
3607 PG_RETURN_NULL();
3608 }
3609
3610 POSTGIS_DEBUG(1, "Calling lwt_NewEdgesSplit");
3611 node_id = lwt_NewEdgesSplit(topo, edge_id, pt, 0);
3612 POSTGIS_DEBUG(1, "lwt_NewEdgesSplit returned");
3613 lwgeom_free(lwgeom);
3614 PG_FREE_IF_COPY(geom, 3);
3615 lwt_FreeTopology(topo);
3616
3617 if ( node_id == -1 )
3618 {
3619 /* should never reach this point, as lwerror would raise an exception */
3620 SPI_finish();
3621 PG_RETURN_NULL();
3622 }
3623
3624 SPI_finish();
3625 PG_RETURN_INT32(node_id);
3626 }
3627
3628 /* ST_AddIsoNode(atopology, aface, apoint) */
3629 Datum ST_AddIsoNode(PG_FUNCTION_ARGS);
3630 PG_FUNCTION_INFO_V1(ST_AddIsoNode);
ST_AddIsoNode(PG_FUNCTION_ARGS)3631 Datum ST_AddIsoNode(PG_FUNCTION_ARGS)
3632 {
3633 text* toponame_text;
3634 char* toponame;
3635 LWT_ELEMID containing_face;
3636 LWT_ELEMID node_id;
3637 GSERIALIZED *geom;
3638 LWGEOM *lwgeom;
3639 LWPOINT *pt;
3640 LWT_TOPOLOGY *topo;
3641
3642 if ( PG_ARGISNULL(0) || PG_ARGISNULL(2) )
3643 {
3644 lwpgerror("SQL/MM Spatial exception - null argument");
3645 PG_RETURN_NULL();
3646 }
3647
3648 toponame_text = PG_GETARG_TEXT_P(0);
3649 toponame = text_to_cstring(toponame_text);
3650 PG_FREE_IF_COPY(toponame_text, 0);
3651
3652 if ( PG_ARGISNULL(1) ) containing_face = -1;
3653 else
3654 {
3655 containing_face = PG_GETARG_INT32(1);
3656 if ( containing_face < 0 )
3657 {
3658 lwpgerror("SQL/MM Spatial exception - not within face");
3659 PG_RETURN_NULL();
3660 }
3661 }
3662
3663 geom = PG_GETARG_GSERIALIZED_P(2);
3664 lwgeom = lwgeom_from_gserialized(geom);
3665 pt = lwgeom_as_lwpoint(lwgeom);
3666 if ( ! pt )
3667 {
3668 lwgeom_free(lwgeom);
3669 PG_FREE_IF_COPY(geom, 2);
3670 lwpgerror("SQL/MM Spatial exception - invalid point");
3671 PG_RETURN_NULL();
3672 }
3673 if ( lwpoint_is_empty(pt) )
3674 {
3675 lwgeom_free(lwgeom);
3676 PG_FREE_IF_COPY(geom, 2);
3677 lwpgerror("SQL/MM Spatial exception - empty point");
3678 PG_RETURN_NULL();
3679 }
3680
3681 if ( SPI_OK_CONNECT != SPI_connect() )
3682 {
3683 lwpgerror("Could not connect to SPI");
3684 PG_RETURN_NULL();
3685 }
3686
3687 topo = lwt_LoadTopology(be_iface, toponame);
3688 pfree(toponame);
3689 if ( ! topo )
3690 {
3691 /* should never reach this point, as lwerror would raise an exception */
3692 SPI_finish();
3693 PG_RETURN_NULL();
3694 }
3695
3696 POSTGIS_DEBUG(1, "Calling lwt_AddIsoNode");
3697 node_id = lwt_AddIsoNode(topo, containing_face, pt, 0);
3698 POSTGIS_DEBUG(1, "lwt_AddIsoNode returned");
3699 lwgeom_free(lwgeom);
3700 PG_FREE_IF_COPY(geom, 2);
3701 lwt_FreeTopology(topo);
3702
3703 if ( node_id == -1 )
3704 {
3705 /* should never reach this point, as lwerror would raise an exception */
3706 SPI_finish();
3707 PG_RETURN_NULL();
3708 }
3709
3710 SPI_finish();
3711 PG_RETURN_INT32(node_id);
3712 }
3713
3714 /* ST_AddIsoEdge(atopology, anode, anothernode, acurve) */
3715 Datum ST_AddIsoEdge(PG_FUNCTION_ARGS);
3716 PG_FUNCTION_INFO_V1(ST_AddIsoEdge);
ST_AddIsoEdge(PG_FUNCTION_ARGS)3717 Datum ST_AddIsoEdge(PG_FUNCTION_ARGS)
3718 {
3719 text* toponame_text;
3720 char* toponame;
3721 LWT_ELEMID edge_id;
3722 LWT_ELEMID start_node, end_node;
3723 GSERIALIZED *geom;
3724 LWGEOM *lwgeom;
3725 LWLINE *curve;
3726 LWT_TOPOLOGY *topo;
3727
3728 if ( PG_ARGISNULL(0) || PG_ARGISNULL(1) ||
3729 PG_ARGISNULL(2) || PG_ARGISNULL(3) )
3730 {
3731 lwpgerror("SQL/MM Spatial exception - null argument");
3732 PG_RETURN_NULL();
3733 }
3734
3735 toponame_text = PG_GETARG_TEXT_P(0);
3736 toponame = text_to_cstring(toponame_text);
3737 PG_FREE_IF_COPY(toponame_text, 0);
3738
3739 start_node = PG_GETARG_INT32(1);
3740 end_node = PG_GETARG_INT32(2);
3741
3742 if ( start_node == end_node )
3743 {
3744 lwpgerror("Closed edges would not be isolated, try ST_AddEdgeNewFaces");
3745 PG_RETURN_NULL();
3746 }
3747
3748 geom = PG_GETARG_GSERIALIZED_P(3);
3749 lwgeom = lwgeom_from_gserialized(geom);
3750 curve = lwgeom_as_lwline(lwgeom);
3751 if ( ! curve )
3752 {
3753 lwgeom_free(lwgeom);
3754 PG_FREE_IF_COPY(geom, 3);
3755 lwpgerror("SQL/MM Spatial exception - invalid curve");
3756 PG_RETURN_NULL();
3757 }
3758
3759 if ( SPI_OK_CONNECT != SPI_connect() )
3760 {
3761 lwpgerror("Could not connect to SPI");
3762 PG_RETURN_NULL();
3763 }
3764
3765 topo = lwt_LoadTopology(be_iface, toponame);
3766 pfree(toponame);
3767 if ( ! topo )
3768 {
3769 /* should never reach this point, as lwerror would raise an exception */
3770 SPI_finish();
3771 PG_RETURN_NULL();
3772 }
3773
3774 POSTGIS_DEBUG(1, "Calling lwt_AddIsoEdge");
3775 edge_id = lwt_AddIsoEdge(topo, start_node, end_node, curve);
3776 POSTGIS_DEBUG(1, "lwt_AddIsoNode returned");
3777 lwgeom_free(lwgeom);
3778 PG_FREE_IF_COPY(geom, 3);
3779 lwt_FreeTopology(topo);
3780
3781 if ( edge_id == -1 )
3782 {
3783 /* should never reach this point, as lwerror would raise an exception */
3784 SPI_finish();
3785 PG_RETURN_NULL();
3786 }
3787
3788 SPI_finish();
3789 PG_RETURN_INT32(edge_id);
3790 }
3791
3792 /* ST_AddEdgeModFace(atopology, snode, enode, line) */
3793 Datum ST_AddEdgeModFace(PG_FUNCTION_ARGS);
3794 PG_FUNCTION_INFO_V1(ST_AddEdgeModFace);
ST_AddEdgeModFace(PG_FUNCTION_ARGS)3795 Datum ST_AddEdgeModFace(PG_FUNCTION_ARGS)
3796 {
3797 text* toponame_text;
3798 char* toponame;
3799 LWT_ELEMID startnode_id, endnode_id;
3800 int edge_id;
3801 GSERIALIZED *geom;
3802 LWGEOM *lwgeom;
3803 LWLINE *line;
3804 LWT_TOPOLOGY *topo;
3805
3806 if ( PG_ARGISNULL(0) || PG_ARGISNULL(1) || PG_ARGISNULL(2) || PG_ARGISNULL(3) )
3807 {
3808 lwpgerror("SQL/MM Spatial exception - null argument");
3809 PG_RETURN_NULL();
3810 }
3811
3812 toponame_text = PG_GETARG_TEXT_P(0);
3813 toponame = text_to_cstring(toponame_text);
3814 PG_FREE_IF_COPY(toponame_text, 0);
3815
3816 startnode_id = PG_GETARG_INT32(1) ;
3817 endnode_id = PG_GETARG_INT32(2) ;
3818
3819 geom = PG_GETARG_GSERIALIZED_P(3);
3820 lwgeom = lwgeom_from_gserialized(geom);
3821 line = lwgeom_as_lwline(lwgeom);
3822 if ( ! line )
3823 {
3824 lwgeom_free(lwgeom);
3825 PG_FREE_IF_COPY(geom, 3);
3826 lwpgerror("ST_AddEdgeModFace fourth argument must be a line geometry");
3827 PG_RETURN_NULL();
3828 }
3829
3830 if ( SPI_OK_CONNECT != SPI_connect() )
3831 {
3832 lwpgerror("Could not connect to SPI");
3833 PG_RETURN_NULL();
3834 }
3835
3836 topo = lwt_LoadTopology(be_iface, toponame);
3837 pfree(toponame);
3838 if ( ! topo )
3839 {
3840 /* should never reach this point, as lwerror would raise an exception */
3841 SPI_finish();
3842 PG_RETURN_NULL();
3843 }
3844
3845 POSTGIS_DEBUG(1, "Calling lwt_AddEdgeModFace");
3846 edge_id = lwt_AddEdgeModFace(topo, startnode_id, endnode_id, line, 0);
3847 POSTGIS_DEBUG(1, "lwt_AddEdgeModFace returned");
3848 lwgeom_free(lwgeom);
3849 PG_FREE_IF_COPY(geom, 3);
3850 lwt_FreeTopology(topo);
3851
3852 if ( edge_id == -1 )
3853 {
3854 /* should never reach this point, as lwerror would raise an exception */
3855 SPI_finish();
3856 PG_RETURN_NULL();
3857 }
3858
3859 SPI_finish();
3860 PG_RETURN_INT32(edge_id);
3861 }
3862
3863 /* ST_AddEdgeNewFaces(atopology, snode, enode, line) */
3864 Datum ST_AddEdgeNewFaces(PG_FUNCTION_ARGS);
3865 PG_FUNCTION_INFO_V1(ST_AddEdgeNewFaces);
ST_AddEdgeNewFaces(PG_FUNCTION_ARGS)3866 Datum ST_AddEdgeNewFaces(PG_FUNCTION_ARGS)
3867 {
3868 text* toponame_text;
3869 char* toponame;
3870 LWT_ELEMID startnode_id, endnode_id;
3871 int edge_id;
3872 GSERIALIZED *geom;
3873 LWGEOM *lwgeom;
3874 LWLINE *line;
3875 LWT_TOPOLOGY *topo;
3876
3877 if ( PG_ARGISNULL(0) || PG_ARGISNULL(1) || PG_ARGISNULL(2) || PG_ARGISNULL(3) )
3878 {
3879 lwpgerror("SQL/MM Spatial exception - null argument");
3880 PG_RETURN_NULL();
3881 }
3882
3883 toponame_text = PG_GETARG_TEXT_P(0);
3884 toponame = text_to_cstring(toponame_text);
3885 PG_FREE_IF_COPY(toponame_text, 0);
3886
3887 startnode_id = PG_GETARG_INT32(1) ;
3888 endnode_id = PG_GETARG_INT32(2) ;
3889
3890 geom = PG_GETARG_GSERIALIZED_P(3);
3891 lwgeom = lwgeom_from_gserialized(geom);
3892 line = lwgeom_as_lwline(lwgeom);
3893 if ( ! line )
3894 {
3895 lwgeom_free(lwgeom);
3896 PG_FREE_IF_COPY(geom, 3);
3897 lwpgerror("ST_AddEdgeModFace fourth argument must be a line geometry");
3898 PG_RETURN_NULL();
3899 }
3900
3901 if ( SPI_OK_CONNECT != SPI_connect() )
3902 {
3903 lwpgerror("Could not connect to SPI");
3904 PG_RETURN_NULL();
3905 }
3906
3907 topo = lwt_LoadTopology(be_iface, toponame);
3908 pfree(toponame);
3909 if ( ! topo )
3910 {
3911 /* should never reach this point, as lwerror would raise an exception */
3912 SPI_finish();
3913 PG_RETURN_NULL();
3914 }
3915
3916 POSTGIS_DEBUG(1, "Calling lwt_AddEdgeNewFaces");
3917 edge_id = lwt_AddEdgeNewFaces(topo, startnode_id, endnode_id, line, 0);
3918 POSTGIS_DEBUG(1, "lwt_AddEdgeNewFaces returned");
3919 lwgeom_free(lwgeom);
3920 PG_FREE_IF_COPY(geom, 3);
3921 lwt_FreeTopology(topo);
3922
3923 if ( edge_id == -1 )
3924 {
3925 /* should never reach this point, as lwerror would raise an exception */
3926 SPI_finish();
3927 PG_RETURN_NULL();
3928 }
3929
3930 SPI_finish();
3931 PG_RETURN_INT32(edge_id);
3932 }
3933
3934 /* ST_GetFaceGeometry(atopology, aface) */
3935 Datum ST_GetFaceGeometry(PG_FUNCTION_ARGS);
3936 PG_FUNCTION_INFO_V1(ST_GetFaceGeometry);
ST_GetFaceGeometry(PG_FUNCTION_ARGS)3937 Datum ST_GetFaceGeometry(PG_FUNCTION_ARGS)
3938 {
3939 text* toponame_text;
3940 char* toponame;
3941 LWT_ELEMID face_id;
3942 LWGEOM *lwgeom;
3943 LWT_TOPOLOGY *topo;
3944 GSERIALIZED *geom;
3945 MemoryContext old_context;
3946
3947 if ( PG_ARGISNULL(0) || PG_ARGISNULL(1) )
3948 {
3949 lwpgerror("SQL/MM Spatial exception - null argument");
3950 PG_RETURN_NULL();
3951 }
3952
3953 toponame_text = PG_GETARG_TEXT_P(0);
3954 toponame = text_to_cstring(toponame_text);
3955 PG_FREE_IF_COPY(toponame_text, 0);
3956
3957 face_id = PG_GETARG_INT32(1) ;
3958
3959 if ( SPI_OK_CONNECT != SPI_connect() )
3960 {
3961 lwpgerror("Could not connect to SPI");
3962 PG_RETURN_NULL();
3963 }
3964
3965 topo = lwt_LoadTopology(be_iface, toponame);
3966 pfree(toponame);
3967 if ( ! topo )
3968 {
3969 /* should never reach this point, as lwerror would raise an exception */
3970 SPI_finish();
3971 PG_RETURN_NULL();
3972 }
3973
3974 POSTGIS_DEBUG(1, "Calling lwt_GetFaceGeometry");
3975 lwgeom = lwt_GetFaceGeometry(topo, face_id);
3976 POSTGIS_DEBUG(1, "lwt_GetFaceGeometry returned");
3977 lwt_FreeTopology(topo);
3978
3979 if ( lwgeom == NULL )
3980 {
3981 /* should never reach this point, as lwerror would raise an exception */
3982 SPI_finish();
3983 PG_RETURN_NULL();
3984 }
3985
3986 /* Serialize in upper memory context (outside of SPI) */
3987 /* TODO: use a narrower context to switch to */
3988 old_context = MemoryContextSwitchTo( TopMemoryContext );
3989 geom = geometry_serialize(lwgeom);
3990 MemoryContextSwitchTo(old_context);
3991
3992 SPI_finish();
3993
3994 PG_RETURN_POINTER(geom);
3995 }
3996
3997 typedef struct FACEEDGESSTATE
3998 {
3999 LWT_ELEMID *elems;
4000 int nelems;
4001 int curr;
4002 }
4003 FACEEDGESSTATE;
4004
4005 /* ST_GetFaceEdges(atopology, aface) */
4006 Datum ST_GetFaceEdges(PG_FUNCTION_ARGS);
4007 PG_FUNCTION_INFO_V1(ST_GetFaceEdges);
ST_GetFaceEdges(PG_FUNCTION_ARGS)4008 Datum ST_GetFaceEdges(PG_FUNCTION_ARGS)
4009 {
4010 text* toponame_text;
4011 char* toponame;
4012 LWT_ELEMID face_id;
4013 int nelems;
4014 LWT_ELEMID *elems;
4015 LWT_TOPOLOGY *topo;
4016 FuncCallContext *funcctx;
4017 MemoryContext oldcontext, newcontext;
4018 TupleDesc tupdesc;
4019 HeapTuple tuple;
4020 AttInMetadata *attinmeta;
4021 FACEEDGESSTATE *state;
4022 char buf[64];
4023 char *values[2];
4024 Datum result;
4025
4026 values[0] = buf;
4027 values[1] = &(buf[32]);
4028
4029 if (SRF_IS_FIRSTCALL())
4030 {
4031
4032 POSTGIS_DEBUG(1, "ST_GetFaceEdges first call");
4033 funcctx = SRF_FIRSTCALL_INIT();
4034 newcontext = funcctx->multi_call_memory_ctx;
4035
4036 if ( PG_ARGISNULL(0) || PG_ARGISNULL(1) )
4037 {
4038 lwpgerror("SQL/MM Spatial exception - null argument");
4039 PG_RETURN_NULL();
4040 }
4041
4042 toponame_text = PG_GETARG_TEXT_P(0);
4043 toponame = text_to_cstring(toponame_text);
4044 PG_FREE_IF_COPY(toponame_text, 0);
4045
4046 face_id = PG_GETARG_INT32(1) ;
4047
4048 if ( SPI_OK_CONNECT != SPI_connect() )
4049 {
4050 lwpgerror("Could not connect to SPI");
4051 PG_RETURN_NULL();
4052 }
4053
4054 topo = lwt_LoadTopology(be_iface, toponame);
4055 oldcontext = MemoryContextSwitchTo( newcontext );
4056 pfree(toponame);
4057 if ( ! topo )
4058 {
4059 /* should never reach this point, as lwerror would raise an exception */
4060 SPI_finish();
4061 PG_RETURN_NULL();
4062 }
4063
4064 POSTGIS_DEBUG(1, "Calling lwt_GetFaceEdges");
4065 nelems = lwt_GetFaceEdges(topo, face_id, &elems);
4066 POSTGIS_DEBUGF(1, "lwt_GetFaceEdges returned %d", nelems);
4067 lwt_FreeTopology(topo);
4068
4069 if ( nelems < 0 )
4070 {
4071 /* should never reach this point, as lwerror would raise an exception */
4072 SPI_finish();
4073 PG_RETURN_NULL();
4074 }
4075
4076 state = lwalloc(sizeof(FACEEDGESSTATE));
4077 state->elems = elems;
4078 state->nelems = nelems;
4079 state->curr = 0;
4080 funcctx->user_fctx = state;
4081
4082 /*
4083 * Build a tuple description for a
4084 * getfaceedges_returntype tuple
4085 */
4086 tupdesc = RelationNameGetTupleDesc("topology.getfaceedges_returntype");
4087
4088 /*
4089 * generate attribute metadata needed later to produce
4090 * tuples from raw C strings
4091 */
4092 attinmeta = TupleDescGetAttInMetadata(tupdesc);
4093 funcctx->attinmeta = attinmeta;
4094
4095 POSTGIS_DEBUG(1, "lwt_GetFaceEdges calling SPI_finish");
4096
4097 MemoryContextSwitchTo(oldcontext);
4098
4099 SPI_finish();
4100 }
4101
4102 /* stuff done on every call of the function */
4103 funcctx = SRF_PERCALL_SETUP();
4104
4105 /* get state */
4106 state = funcctx->user_fctx;
4107
4108 if ( state->curr == state->nelems )
4109 {
4110 SRF_RETURN_DONE(funcctx);
4111 }
4112
4113 if ( snprintf(values[0], 32, "%d", state->curr+1) >= 32 )
4114 {
4115 lwerror("Face edge sequence number does not fit 32 chars ?!: %d",
4116 state->curr+1);
4117 }
4118 if ( snprintf(values[1], 32, "%" LWTFMT_ELEMID,
4119 state->elems[state->curr]) >= 32 )
4120 {
4121 lwerror("Signed edge identifier does not fit 32 chars ?!: %"
4122 LWTFMT_ELEMID, state->elems[state->curr]);
4123 }
4124
4125 POSTGIS_DEBUGF(1, "ST_GetFaceEdges: cur:%d, val0:%s, val1:%s",
4126 state->curr, values[0], values[1]);
4127
4128 tuple = BuildTupleFromCStrings(funcctx->attinmeta, values);
4129 result = HeapTupleGetDatum(tuple);
4130 state->curr++;
4131
4132 SRF_RETURN_NEXT(funcctx, result);
4133 }
4134
4135 /* ST_ChangeEdgeGeom(atopology, anedge, acurve) */
4136 Datum ST_ChangeEdgeGeom(PG_FUNCTION_ARGS);
4137 PG_FUNCTION_INFO_V1(ST_ChangeEdgeGeom);
ST_ChangeEdgeGeom(PG_FUNCTION_ARGS)4138 Datum ST_ChangeEdgeGeom(PG_FUNCTION_ARGS)
4139 {
4140 text* toponame_text;
4141 char buf[64];
4142 char* toponame;
4143 int ret;
4144 LWT_ELEMID edge_id;
4145 GSERIALIZED *geom;
4146 LWGEOM *lwgeom;
4147 LWLINE *line;
4148 LWT_TOPOLOGY *topo;
4149
4150 if ( PG_ARGISNULL(0) || PG_ARGISNULL(1) || PG_ARGISNULL(2) )
4151 {
4152 lwpgerror("SQL/MM Spatial exception - null argument");
4153 PG_RETURN_NULL();
4154 }
4155
4156 toponame_text = PG_GETARG_TEXT_P(0);
4157 toponame = text_to_cstring(toponame_text);
4158 PG_FREE_IF_COPY(toponame_text, 0);
4159
4160 edge_id = PG_GETARG_INT32(1) ;
4161
4162 geom = PG_GETARG_GSERIALIZED_P(2);
4163 lwgeom = lwgeom_from_gserialized(geom);
4164 line = lwgeom_as_lwline(lwgeom);
4165 if ( ! line )
4166 {
4167 lwgeom_free(lwgeom);
4168 PG_FREE_IF_COPY(geom, 2);
4169 lwpgerror("ST_ChangeEdgeGeom third argument must be a line geometry");
4170 PG_RETURN_NULL();
4171 }
4172
4173 if ( SPI_OK_CONNECT != SPI_connect() )
4174 {
4175 lwpgerror("Could not connect to SPI");
4176 PG_RETURN_NULL();
4177 }
4178
4179 topo = lwt_LoadTopology(be_iface, toponame);
4180 pfree(toponame);
4181 if ( ! topo )
4182 {
4183 /* should never reach this point, as lwerror would raise an exception */
4184 SPI_finish();
4185 PG_RETURN_NULL();
4186 }
4187
4188 POSTGIS_DEBUG(1, "Calling lwt_ChangeEdgeGeom");
4189 ret = lwt_ChangeEdgeGeom(topo, edge_id, line);
4190 POSTGIS_DEBUG(1, "lwt_ChangeEdgeGeom returned");
4191 lwgeom_free(lwgeom);
4192 PG_FREE_IF_COPY(geom, 2);
4193 lwt_FreeTopology(topo);
4194
4195 if ( ret == -1 )
4196 {
4197 /* should never reach this point, as lwerror would raise an exception */
4198 SPI_finish();
4199 PG_RETURN_NULL();
4200 }
4201
4202 SPI_finish();
4203
4204 if ( snprintf(buf, 64, "Edge %" LWTFMT_ELEMID " changed", edge_id) >= 64 )
4205 {
4206 buf[63] = '\0';
4207 }
4208 PG_RETURN_TEXT_P(cstring_to_text(buf));
4209 }
4210
4211 /* ST_RemoveIsoNode(atopology, anode) */
4212 Datum ST_RemoveIsoNode(PG_FUNCTION_ARGS);
4213 PG_FUNCTION_INFO_V1(ST_RemoveIsoNode);
ST_RemoveIsoNode(PG_FUNCTION_ARGS)4214 Datum ST_RemoveIsoNode(PG_FUNCTION_ARGS)
4215 {
4216 text* toponame_text;
4217 char buf[64];
4218 char* toponame;
4219 int ret;
4220 LWT_ELEMID node_id;
4221 LWT_TOPOLOGY *topo;
4222
4223 if ( PG_ARGISNULL(0) || PG_ARGISNULL(1) )
4224 {
4225 lwpgerror("SQL/MM Spatial exception - null argument");
4226 PG_RETURN_NULL();
4227 }
4228
4229 toponame_text = PG_GETARG_TEXT_P(0);
4230 toponame = text_to_cstring(toponame_text);
4231 PG_FREE_IF_COPY(toponame_text, 0);
4232
4233 node_id = PG_GETARG_INT32(1) ;
4234
4235 if ( SPI_OK_CONNECT != SPI_connect() )
4236 {
4237 lwpgerror("Could not connect to SPI");
4238 PG_RETURN_NULL();
4239 }
4240
4241 topo = lwt_LoadTopology(be_iface, toponame);
4242 pfree(toponame);
4243 if ( ! topo )
4244 {
4245 /* should never reach this point, as lwerror would raise an exception */
4246 SPI_finish();
4247 PG_RETURN_NULL();
4248 }
4249
4250 POSTGIS_DEBUG(1, "Calling lwt_RemoveIsoNode");
4251 ret = lwt_RemoveIsoNode(topo, node_id);
4252 POSTGIS_DEBUG(1, "lwt_RemoveIsoNode returned");
4253 lwt_FreeTopology(topo);
4254
4255 if ( ret == -1 )
4256 {
4257 /* should never reach this point, as lwerror would raise an exception */
4258 SPI_finish();
4259 PG_RETURN_NULL();
4260 }
4261
4262 SPI_finish();
4263
4264 if ( snprintf(buf, 64, "Isolated node %" LWTFMT_ELEMID
4265 " removed", node_id) >= 64 )
4266 {
4267 buf[63] = '\0';
4268 }
4269 PG_RETURN_TEXT_P(cstring_to_text(buf));
4270 }
4271
4272 /* ST_RemIsoEdge(atopology, anedge) */
4273 Datum ST_RemIsoEdge(PG_FUNCTION_ARGS);
4274 PG_FUNCTION_INFO_V1(ST_RemIsoEdge);
ST_RemIsoEdge(PG_FUNCTION_ARGS)4275 Datum ST_RemIsoEdge(PG_FUNCTION_ARGS)
4276 {
4277 text* toponame_text;
4278 char buf[64];
4279 char* toponame;
4280 int ret;
4281 LWT_ELEMID node_id;
4282 LWT_TOPOLOGY *topo;
4283
4284 if ( PG_ARGISNULL(0) || PG_ARGISNULL(1) )
4285 {
4286 lwpgerror("SQL/MM Spatial exception - null argument");
4287 PG_RETURN_NULL();
4288 }
4289
4290 toponame_text = PG_GETARG_TEXT_P(0);
4291 toponame = text_to_cstring(toponame_text);
4292 PG_FREE_IF_COPY(toponame_text, 0);
4293
4294 node_id = PG_GETARG_INT32(1) ;
4295
4296 if ( SPI_OK_CONNECT != SPI_connect() )
4297 {
4298 lwpgerror("Could not connect to SPI");
4299 PG_RETURN_NULL();
4300 }
4301
4302 topo = lwt_LoadTopology(be_iface, toponame);
4303 pfree(toponame);
4304 if ( ! topo )
4305 {
4306 /* should never reach this point, as lwerror would raise an exception */
4307 SPI_finish();
4308 PG_RETURN_NULL();
4309 }
4310
4311 POSTGIS_DEBUG(1, "Calling lwt_RemIsoEdge");
4312 ret = lwt_RemIsoEdge(topo, node_id);
4313 POSTGIS_DEBUG(1, "lwt_RemIsoEdge returned");
4314 lwt_FreeTopology(topo);
4315
4316 if ( ret == -1 )
4317 {
4318 /* should never reach this point, as lwerror would raise an exception */
4319 SPI_finish();
4320 PG_RETURN_NULL();
4321 }
4322
4323 SPI_finish();
4324
4325 if ( snprintf(buf, 64, "Isolated edge %" LWTFMT_ELEMID
4326 " removed", node_id) >= 64 )
4327 {
4328 buf[63] = '\0';
4329 }
4330 PG_RETURN_TEXT_P(cstring_to_text(buf));
4331 }
4332
4333 /* ST_MoveIsoNode(atopology, anode, apoint) */
4334 Datum ST_MoveIsoNode(PG_FUNCTION_ARGS);
4335 PG_FUNCTION_INFO_V1(ST_MoveIsoNode);
ST_MoveIsoNode(PG_FUNCTION_ARGS)4336 Datum ST_MoveIsoNode(PG_FUNCTION_ARGS)
4337 {
4338 text* toponame_text;
4339 char buf[64];
4340 char* toponame;
4341 int ret;
4342 LWT_ELEMID node_id;
4343 GSERIALIZED *geom;
4344 LWGEOM *lwgeom;
4345 LWPOINT *pt;
4346 LWT_TOPOLOGY *topo;
4347 POINT2D p;
4348
4349 if ( PG_ARGISNULL(0) || PG_ARGISNULL(1) || PG_ARGISNULL(2) )
4350 {
4351 lwpgerror("SQL/MM Spatial exception - null argument");
4352 PG_RETURN_NULL();
4353 }
4354
4355 toponame_text = PG_GETARG_TEXT_P(0);
4356 toponame = text_to_cstring(toponame_text);
4357 PG_FREE_IF_COPY(toponame_text, 0);
4358
4359 node_id = PG_GETARG_INT32(1) ;
4360
4361 geom = PG_GETARG_GSERIALIZED_P(2);
4362 lwgeom = lwgeom_from_gserialized(geom);
4363 pt = lwgeom_as_lwpoint(lwgeom);
4364 if ( ! pt )
4365 {
4366 lwgeom_free(lwgeom);
4367 PG_FREE_IF_COPY(geom, 2);
4368 lwpgerror("SQL/MM Spatial exception - invalid point");
4369 PG_RETURN_NULL();
4370 }
4371
4372 if ( ! getPoint2d_p(pt->point, 0, &p) )
4373 {
4374 /* Do not let empty points in, see
4375 * https://trac.osgeo.org/postgis/ticket/3234
4376 */
4377 lwpgerror("SQL/MM Spatial exception - empty point");
4378 PG_RETURN_NULL();
4379 }
4380
4381 /* TODO: check point for NaN values ? */
4382
4383 if ( SPI_OK_CONNECT != SPI_connect() )
4384 {
4385 lwpgerror("Could not connect to SPI");
4386 PG_RETURN_NULL();
4387 }
4388
4389 topo = lwt_LoadTopology(be_iface, toponame);
4390 pfree(toponame);
4391 if ( ! topo )
4392 {
4393 /* should never reach this point, as lwerror would raise an exception */
4394 SPI_finish();
4395 PG_RETURN_NULL();
4396 }
4397
4398 POSTGIS_DEBUG(1, "Calling lwt_MoveIsoNode");
4399 ret = lwt_MoveIsoNode(topo, node_id, pt);
4400 POSTGIS_DEBUG(1, "lwt_MoveIsoNode returned");
4401 lwgeom_free(lwgeom);
4402 PG_FREE_IF_COPY(geom, 2);
4403 lwt_FreeTopology(topo);
4404
4405 if ( ret == -1 )
4406 {
4407 /* should never reach this point, as lwerror would raise an exception */
4408 SPI_finish();
4409 PG_RETURN_NULL();
4410 }
4411
4412 SPI_finish();
4413
4414 if ( snprintf(buf, 64, "Isolated Node %" LWTFMT_ELEMID
4415 " moved to location %g,%g",
4416 node_id, p.x, p.y) >= 64 )
4417 {
4418 buf[63] = '\0';
4419 }
4420 PG_RETURN_TEXT_P(cstring_to_text(buf));
4421 }
4422
4423 /* ST_RemEdgeModFace(atopology, anedge) */
4424 Datum ST_RemEdgeModFace(PG_FUNCTION_ARGS);
4425 PG_FUNCTION_INFO_V1(ST_RemEdgeModFace);
ST_RemEdgeModFace(PG_FUNCTION_ARGS)4426 Datum ST_RemEdgeModFace(PG_FUNCTION_ARGS)
4427 {
4428 text* toponame_text;
4429 char* toponame;
4430 int ret;
4431 LWT_ELEMID node_id;
4432 LWT_TOPOLOGY *topo;
4433
4434 if ( PG_ARGISNULL(0) || PG_ARGISNULL(1) )
4435 {
4436 lwpgerror("SQL/MM Spatial exception - null argument");
4437 PG_RETURN_NULL();
4438 }
4439
4440 toponame_text = PG_GETARG_TEXT_P(0);
4441 toponame = text_to_cstring(toponame_text);
4442 PG_FREE_IF_COPY(toponame_text, 0);
4443
4444 node_id = PG_GETARG_INT32(1) ;
4445
4446 if ( SPI_OK_CONNECT != SPI_connect() )
4447 {
4448 lwpgerror("Could not connect to SPI");
4449 PG_RETURN_NULL();
4450 }
4451
4452 topo = lwt_LoadTopology(be_iface, toponame);
4453 pfree(toponame);
4454 if ( ! topo )
4455 {
4456 /* should never reach this point, as lwerror would raise an exception */
4457 SPI_finish();
4458 PG_RETURN_NULL();
4459 }
4460
4461 POSTGIS_DEBUG(1, "Calling lwt_RemEdgeModFace");
4462 ret = lwt_RemEdgeModFace(topo, node_id);
4463 POSTGIS_DEBUG(1, "lwt_RemEdgeModFace returned");
4464 lwt_FreeTopology(topo);
4465
4466 if ( ret == -1 )
4467 {
4468 /* should never reach this point, as lwerror would raise an exception */
4469 SPI_finish();
4470 PG_RETURN_NULL();
4471 }
4472
4473 SPI_finish();
4474
4475 PG_RETURN_INT32(ret);
4476 }
4477
4478 /* ST_RemEdgeNewFace(atopology, anedge) */
4479 Datum ST_RemEdgeNewFace(PG_FUNCTION_ARGS);
4480 PG_FUNCTION_INFO_V1(ST_RemEdgeNewFace);
ST_RemEdgeNewFace(PG_FUNCTION_ARGS)4481 Datum ST_RemEdgeNewFace(PG_FUNCTION_ARGS)
4482 {
4483 text* toponame_text;
4484 char* toponame;
4485 int ret;
4486 LWT_ELEMID node_id;
4487 LWT_TOPOLOGY *topo;
4488
4489 if ( PG_ARGISNULL(0) || PG_ARGISNULL(1) )
4490 {
4491 lwpgerror("SQL/MM Spatial exception - null argument");
4492 PG_RETURN_NULL();
4493 }
4494
4495 toponame_text = PG_GETARG_TEXT_P(0);
4496 toponame = text_to_cstring(toponame_text);
4497 PG_FREE_IF_COPY(toponame_text, 0);
4498
4499 node_id = PG_GETARG_INT32(1) ;
4500
4501 if ( SPI_OK_CONNECT != SPI_connect() )
4502 {
4503 lwpgerror("Could not connect to SPI");
4504 PG_RETURN_NULL();
4505 }
4506
4507 topo = lwt_LoadTopology(be_iface, toponame);
4508 pfree(toponame);
4509 if ( ! topo )
4510 {
4511 /* should never reach this point, as lwerror would raise an exception */
4512 SPI_finish();
4513 PG_RETURN_NULL();
4514 }
4515
4516 POSTGIS_DEBUG(1, "Calling lwt_RemEdgeNewFace");
4517 ret = lwt_RemEdgeNewFace(topo, node_id);
4518 POSTGIS_DEBUG(1, "lwt_RemEdgeNewFace returned");
4519 lwt_FreeTopology(topo);
4520 SPI_finish();
4521
4522 if ( ret <= 0 )
4523 {
4524 /* error or no face created */
4525 PG_RETURN_NULL();
4526 }
4527
4528 PG_RETURN_INT32(ret);
4529 }
4530
4531 /* ST_ModEdgeHeal(atopology, anedge, anotheredge) */
4532 Datum ST_ModEdgeHeal(PG_FUNCTION_ARGS);
4533 PG_FUNCTION_INFO_V1(ST_ModEdgeHeal);
ST_ModEdgeHeal(PG_FUNCTION_ARGS)4534 Datum ST_ModEdgeHeal(PG_FUNCTION_ARGS)
4535 {
4536 text* toponame_text;
4537 char* toponame;
4538 int ret;
4539 LWT_ELEMID eid1, eid2;
4540 LWT_TOPOLOGY *topo;
4541
4542 if ( PG_ARGISNULL(0) || PG_ARGISNULL(1) || PG_ARGISNULL(2) )
4543 {
4544 lwpgerror("SQL/MM Spatial exception - null argument");
4545 PG_RETURN_NULL();
4546 }
4547
4548 toponame_text = PG_GETARG_TEXT_P(0);
4549 toponame = text_to_cstring(toponame_text);
4550 PG_FREE_IF_COPY(toponame_text, 0);
4551
4552 eid1 = PG_GETARG_INT32(1) ;
4553 eid2 = PG_GETARG_INT32(2) ;
4554
4555 if ( SPI_OK_CONNECT != SPI_connect() )
4556 {
4557 lwpgerror("Could not connect to SPI");
4558 PG_RETURN_NULL();
4559 }
4560
4561 topo = lwt_LoadTopology(be_iface, toponame);
4562 pfree(toponame);
4563 if ( ! topo )
4564 {
4565 /* should never reach this point, as lwerror would raise an exception */
4566 SPI_finish();
4567 PG_RETURN_NULL();
4568 }
4569
4570 POSTGIS_DEBUG(1, "Calling lwt_ModEdgeHeal");
4571 ret = lwt_ModEdgeHeal(topo, eid1, eid2);
4572 POSTGIS_DEBUG(1, "lwt_ModEdgeHeal returned");
4573 lwt_FreeTopology(topo);
4574 SPI_finish();
4575
4576 if ( ret <= 0 )
4577 {
4578 /* error, should have sent message already */
4579 PG_RETURN_NULL();
4580 }
4581
4582 PG_RETURN_INT32(ret);
4583 }
4584
4585 /* ST_NewEdgeHeal(atopology, anedge, anotheredge) */
4586 Datum ST_NewEdgeHeal(PG_FUNCTION_ARGS);
4587 PG_FUNCTION_INFO_V1(ST_NewEdgeHeal);
ST_NewEdgeHeal(PG_FUNCTION_ARGS)4588 Datum ST_NewEdgeHeal(PG_FUNCTION_ARGS)
4589 {
4590 text* toponame_text;
4591 char* toponame;
4592 int ret;
4593 LWT_ELEMID eid1, eid2;
4594 LWT_TOPOLOGY *topo;
4595
4596 if ( PG_ARGISNULL(0) || PG_ARGISNULL(1) || PG_ARGISNULL(2) )
4597 {
4598 lwpgerror("SQL/MM Spatial exception - null argument");
4599 PG_RETURN_NULL();
4600 }
4601
4602 toponame_text = PG_GETARG_TEXT_P(0);
4603 toponame = text_to_cstring(toponame_text);
4604 PG_FREE_IF_COPY(toponame_text, 0);
4605
4606 eid1 = PG_GETARG_INT32(1) ;
4607 eid2 = PG_GETARG_INT32(2) ;
4608
4609 if ( SPI_OK_CONNECT != SPI_connect() )
4610 {
4611 lwpgerror("Could not connect to SPI");
4612 PG_RETURN_NULL();
4613 }
4614
4615 topo = lwt_LoadTopology(be_iface, toponame);
4616 pfree(toponame);
4617 if ( ! topo )
4618 {
4619 /* should never reach this point, as lwerror would raise an exception */
4620 SPI_finish();
4621 PG_RETURN_NULL();
4622 }
4623
4624 POSTGIS_DEBUG(1, "Calling lwt_NewEdgeHeal");
4625 ret = lwt_NewEdgeHeal(topo, eid1, eid2);
4626 POSTGIS_DEBUG(1, "lwt_NewEdgeHeal returned");
4627 lwt_FreeTopology(topo);
4628 SPI_finish();
4629
4630 if ( ret <= 0 )
4631 {
4632 /* error, should have sent message already */
4633 PG_RETURN_NULL();
4634 }
4635
4636 PG_RETURN_INT32(ret);
4637 }
4638
4639 /* GetNodeByPoint(atopology, point, tolerance) */
4640 Datum GetNodeByPoint(PG_FUNCTION_ARGS);
4641 PG_FUNCTION_INFO_V1(GetNodeByPoint);
GetNodeByPoint(PG_FUNCTION_ARGS)4642 Datum GetNodeByPoint(PG_FUNCTION_ARGS)
4643 {
4644 text* toponame_text;
4645 char* toponame;
4646 double tol;
4647 LWT_ELEMID node_id;
4648 GSERIALIZED *geom;
4649 LWGEOM *lwgeom;
4650 LWPOINT *pt;
4651 LWT_TOPOLOGY *topo;
4652
4653 toponame_text = PG_GETARG_TEXT_P(0);
4654 toponame = text_to_cstring(toponame_text);
4655 PG_FREE_IF_COPY(toponame_text, 0);
4656
4657 geom = PG_GETARG_GSERIALIZED_P(1);
4658 lwgeom = lwgeom_from_gserialized(geom);
4659 pt = lwgeom_as_lwpoint(lwgeom);
4660 if ( ! pt )
4661 {
4662 lwgeom_free(lwgeom);
4663 PG_FREE_IF_COPY(geom, 1);
4664 lwpgerror("Node geometry must be a point");
4665 PG_RETURN_NULL();
4666 }
4667
4668 tol = PG_GETARG_FLOAT8(2);
4669 if ( tol < 0 )
4670 {
4671 PG_FREE_IF_COPY(geom, 1);
4672 lwpgerror("Tolerance must be >=0");
4673 PG_RETURN_NULL();
4674 }
4675
4676 if ( SPI_OK_CONNECT != SPI_connect() )
4677 {
4678 lwpgerror("Could not connect to SPI");
4679 PG_RETURN_NULL();
4680 }
4681
4682 topo = lwt_LoadTopology(be_iface, toponame);
4683 pfree(toponame);
4684 if ( ! topo )
4685 {
4686 /* should never reach this point, as lwerror would raise an exception */
4687 SPI_finish();
4688 PG_RETURN_NULL();
4689 }
4690
4691 POSTGIS_DEBUG(1, "Calling lwt_GetNodeByPoint");
4692 node_id = lwt_GetNodeByPoint(topo, pt, tol);
4693 POSTGIS_DEBUG(1, "lwt_GetNodeByPoint returned");
4694 lwgeom_free(lwgeom);
4695 PG_FREE_IF_COPY(geom, 1);
4696 lwt_FreeTopology(topo);
4697
4698 if ( node_id == -1 )
4699 {
4700 /* should never reach this point, as lwerror would raise an exception */
4701 SPI_finish();
4702 PG_RETURN_NULL();
4703 }
4704
4705 SPI_finish();
4706 PG_RETURN_INT32(node_id);
4707 }
4708
4709 /* GetEdgeByPoint(atopology, point, tolerance) */
4710 Datum GetEdgeByPoint(PG_FUNCTION_ARGS);
4711 PG_FUNCTION_INFO_V1(GetEdgeByPoint);
GetEdgeByPoint(PG_FUNCTION_ARGS)4712 Datum GetEdgeByPoint(PG_FUNCTION_ARGS)
4713 {
4714 text* toponame_text;
4715 char* toponame;
4716 double tol;
4717 LWT_ELEMID node_id;
4718 GSERIALIZED *geom;
4719 LWGEOM *lwgeom;
4720 LWPOINT *pt;
4721 LWT_TOPOLOGY *topo;
4722
4723 toponame_text = PG_GETARG_TEXT_P(0);
4724 toponame = text_to_cstring(toponame_text);
4725 PG_FREE_IF_COPY(toponame_text, 0);
4726
4727 geom = PG_GETARG_GSERIALIZED_P(1);
4728 lwgeom = lwgeom_from_gserialized(geom);
4729 pt = lwgeom_as_lwpoint(lwgeom);
4730 if ( ! pt )
4731 {
4732 lwgeom_free(lwgeom);
4733 PG_FREE_IF_COPY(geom, 1);
4734 lwpgerror("Node geometry must be a point");
4735 PG_RETURN_NULL();
4736 }
4737
4738 tol = PG_GETARG_FLOAT8(2);
4739 if ( tol < 0 )
4740 {
4741 PG_FREE_IF_COPY(geom, 1);
4742 lwpgerror("Tolerance must be >=0");
4743 PG_RETURN_NULL();
4744 }
4745
4746 if ( SPI_OK_CONNECT != SPI_connect() )
4747 {
4748 lwpgerror("Could not connect to SPI");
4749 PG_RETURN_NULL();
4750 }
4751
4752 topo = lwt_LoadTopology(be_iface, toponame);
4753 pfree(toponame);
4754 if ( ! topo )
4755 {
4756 /* should never reach this point, as lwerror would raise an exception */
4757 SPI_finish();
4758 PG_RETURN_NULL();
4759 }
4760
4761 POSTGIS_DEBUG(1, "Calling lwt_GetEdgeByPoint");
4762 node_id = lwt_GetEdgeByPoint(topo, pt, tol);
4763 POSTGIS_DEBUG(1, "lwt_GetEdgeByPoint returned");
4764 lwgeom_free(lwgeom);
4765 PG_FREE_IF_COPY(geom, 1);
4766 lwt_FreeTopology(topo);
4767
4768 if ( node_id == -1 )
4769 {
4770 /* should never reach this point, as lwerror would raise an exception */
4771 SPI_finish();
4772 PG_RETURN_NULL();
4773 }
4774
4775 SPI_finish();
4776 PG_RETURN_INT32(node_id);
4777 }
4778
4779 /* GetFaceByPoint(atopology, point, tolerance) */
4780 Datum GetFaceByPoint(PG_FUNCTION_ARGS);
4781 PG_FUNCTION_INFO_V1(GetFaceByPoint);
GetFaceByPoint(PG_FUNCTION_ARGS)4782 Datum GetFaceByPoint(PG_FUNCTION_ARGS)
4783 {
4784 text* toponame_text;
4785 char* toponame;
4786 double tol;
4787 LWT_ELEMID node_id;
4788 GSERIALIZED *geom;
4789 LWGEOM *lwgeom;
4790 LWPOINT *pt;
4791 LWT_TOPOLOGY *topo;
4792
4793 lwpgwarning("This function should not be hit, please upgrade your PostGIS install");
4794
4795 toponame_text = PG_GETARG_TEXT_P(0);
4796 toponame = text_to_cstring(toponame_text);
4797 PG_FREE_IF_COPY(toponame_text, 0);
4798
4799 geom = PG_GETARG_GSERIALIZED_P(1);
4800 lwgeom = lwgeom_from_gserialized(geom);
4801 pt = lwgeom_as_lwpoint(lwgeom);
4802 if ( ! pt )
4803 {
4804 lwgeom_free(lwgeom);
4805 PG_FREE_IF_COPY(geom, 1);
4806 lwpgerror("Node geometry must be a point");
4807 PG_RETURN_NULL();
4808 }
4809
4810 tol = PG_GETARG_FLOAT8(2);
4811 if ( tol < 0 )
4812 {
4813 PG_FREE_IF_COPY(geom, 1);
4814 lwpgerror("Tolerance must be >=0");
4815 PG_RETURN_NULL();
4816 }
4817
4818 if ( SPI_OK_CONNECT != SPI_connect() )
4819 {
4820 lwpgerror("Could not connect to SPI");
4821 PG_RETURN_NULL();
4822 }
4823
4824 topo = lwt_LoadTopology(be_iface, toponame);
4825 pfree(toponame);
4826 if ( ! topo )
4827 {
4828 /* should never reach this point, as lwerror would raise an exception */
4829 SPI_finish();
4830 PG_RETURN_NULL();
4831 }
4832
4833 POSTGIS_DEBUG(1, "Calling lwt_GetFaceByPoint");
4834 node_id = lwt_GetFaceByPoint(topo, pt, tol);
4835 POSTGIS_DEBUG(1, "lwt_GetFaceByPoint returned");
4836 lwgeom_free(lwgeom);
4837 PG_FREE_IF_COPY(geom, 1);
4838 lwt_FreeTopology(topo);
4839
4840 if ( node_id == -1 )
4841 {
4842 /* should never reach this point, as lwerror would raise an exception */
4843 SPI_finish();
4844 PG_RETURN_NULL();
4845 }
4846
4847 SPI_finish();
4848 PG_RETURN_INT32(node_id);
4849 }
4850
4851 /* TopoGeo_AddPoint(atopology, point, tolerance) */
4852 Datum TopoGeo_AddPoint(PG_FUNCTION_ARGS);
4853 PG_FUNCTION_INFO_V1(TopoGeo_AddPoint);
TopoGeo_AddPoint(PG_FUNCTION_ARGS)4854 Datum TopoGeo_AddPoint(PG_FUNCTION_ARGS)
4855 {
4856 text* toponame_text;
4857 char* toponame;
4858 double tol;
4859 LWT_ELEMID node_id;
4860 GSERIALIZED *geom;
4861 LWGEOM *lwgeom;
4862 LWPOINT *pt;
4863 LWT_TOPOLOGY *topo;
4864
4865 if ( PG_ARGISNULL(0) || PG_ARGISNULL(1) || PG_ARGISNULL(2) )
4866 {
4867 lwpgerror("SQL/MM Spatial exception - null argument");
4868 PG_RETURN_NULL();
4869 }
4870
4871 toponame_text = PG_GETARG_TEXT_P(0);
4872 toponame = text_to_cstring(toponame_text);
4873 PG_FREE_IF_COPY(toponame_text, 0);
4874
4875 geom = PG_GETARG_GSERIALIZED_P(1);
4876 lwgeom = lwgeom_from_gserialized(geom);
4877 pt = lwgeom_as_lwpoint(lwgeom);
4878 if ( ! pt )
4879 {
4880 {
4881 char buf[32];
4882 _lwtype_upper_name(lwgeom_get_type(lwgeom), buf, 32);
4883 lwgeom_free(lwgeom);
4884 PG_FREE_IF_COPY(geom, 1);
4885 lwpgerror("Invalid geometry type (%s) passed to TopoGeo_AddPoint"
4886 ", expected POINT", buf );
4887 PG_RETURN_NULL();
4888 }
4889 }
4890
4891 tol = PG_GETARG_FLOAT8(2);
4892 if ( tol < 0 )
4893 {
4894 PG_FREE_IF_COPY(geom, 1);
4895 lwpgerror("Tolerance must be >=0");
4896 PG_RETURN_NULL();
4897 }
4898
4899 if ( SPI_OK_CONNECT != SPI_connect() )
4900 {
4901 lwpgerror("Could not connect to SPI");
4902 PG_RETURN_NULL();
4903 }
4904
4905 {
4906 int pre = be_data.topoLoadFailMessageFlavor;
4907 be_data.topoLoadFailMessageFlavor = 1;
4908 topo = lwt_LoadTopology(be_iface, toponame);
4909 be_data.topoLoadFailMessageFlavor = pre;
4910 }
4911 pfree(toponame);
4912 if ( ! topo )
4913 {
4914 /* should never reach this point, as lwerror would raise an exception */
4915 SPI_finish();
4916 PG_RETURN_NULL();
4917 }
4918
4919 POSTGIS_DEBUG(1, "Calling lwt_AddPoint");
4920 node_id = lwt_AddPoint(topo, pt, tol);
4921 POSTGIS_DEBUG(1, "lwt_AddPoint returned");
4922 lwgeom_free(lwgeom);
4923 PG_FREE_IF_COPY(geom, 1);
4924 lwt_FreeTopology(topo);
4925
4926 if ( node_id == -1 )
4927 {
4928 /* should never reach this point, as lwerror would raise an exception */
4929 SPI_finish();
4930 PG_RETURN_NULL();
4931 }
4932
4933 SPI_finish();
4934 PG_RETURN_INT32(node_id);
4935 }
4936
4937 /* TopoGeo_AddLinestring(atopology, point, tolerance) */
4938 Datum TopoGeo_AddLinestring(PG_FUNCTION_ARGS);
4939 PG_FUNCTION_INFO_V1(TopoGeo_AddLinestring);
TopoGeo_AddLinestring(PG_FUNCTION_ARGS)4940 Datum TopoGeo_AddLinestring(PG_FUNCTION_ARGS)
4941 {
4942 text* toponame_text;
4943 char* toponame;
4944 double tol;
4945 LWT_ELEMID *elems;
4946 int nelems;
4947 GSERIALIZED *geom;
4948 LWGEOM *lwgeom;
4949 LWLINE *ln;
4950 LWT_TOPOLOGY *topo;
4951 FuncCallContext *funcctx;
4952 MemoryContext oldcontext, newcontext;
4953 FACEEDGESSTATE *state;
4954 Datum result;
4955 LWT_ELEMID id;
4956
4957 if (SRF_IS_FIRSTCALL())
4958 {
4959 POSTGIS_DEBUG(1, "TopoGeo_AddLinestring first call");
4960 funcctx = SRF_FIRSTCALL_INIT();
4961 newcontext = funcctx->multi_call_memory_ctx;
4962
4963 if ( PG_ARGISNULL(0) || PG_ARGISNULL(1) || PG_ARGISNULL(2) )
4964 {
4965 lwpgerror("SQL/MM Spatial exception - null argument");
4966 PG_RETURN_NULL();
4967 }
4968
4969 toponame_text = PG_GETARG_TEXT_P(0);
4970 toponame = text_to_cstring(toponame_text);
4971 PG_FREE_IF_COPY(toponame_text, 0);
4972
4973 geom = PG_GETARG_GSERIALIZED_P(1);
4974 lwgeom = lwgeom_from_gserialized(geom);
4975 ln = lwgeom_as_lwline(lwgeom);
4976 if ( ! ln )
4977 {
4978 {
4979 char buf[32];
4980 _lwtype_upper_name(lwgeom_get_type(lwgeom), buf, 32);
4981 lwgeom_free(lwgeom);
4982 PG_FREE_IF_COPY(geom, 1);
4983 lwpgerror("Invalid geometry type (%s) passed to "
4984 "TopoGeo_AddLinestring, expected LINESTRING", buf);
4985 PG_RETURN_NULL();
4986 }
4987 }
4988
4989 tol = PG_GETARG_FLOAT8(2);
4990 if ( tol < 0 )
4991 {
4992 PG_FREE_IF_COPY(geom, 1);
4993 lwpgerror("Tolerance must be >=0");
4994 PG_RETURN_NULL();
4995 }
4996
4997 if ( SPI_OK_CONNECT != SPI_connect() )
4998 {
4999 lwpgerror("Could not connect to SPI");
5000 PG_RETURN_NULL();
5001 }
5002
5003 {
5004 int pre = be_data.topoLoadFailMessageFlavor;
5005 be_data.topoLoadFailMessageFlavor = 1;
5006 topo = lwt_LoadTopology(be_iface, toponame);
5007 be_data.topoLoadFailMessageFlavor = pre;
5008 }
5009 oldcontext = MemoryContextSwitchTo( newcontext );
5010 pfree(toponame);
5011 if ( ! topo )
5012 {
5013 /* should never reach this point, as lwerror would raise an exception */
5014 SPI_finish();
5015 PG_RETURN_NULL();
5016 }
5017
5018 POSTGIS_DEBUG(1, "Calling lwt_AddLine");
5019 elems = lwt_AddLine(topo, ln, tol, &nelems);
5020 POSTGIS_DEBUG(1, "lwt_AddLine returned");
5021 lwgeom_free(lwgeom);
5022 PG_FREE_IF_COPY(geom, 1);
5023 lwt_FreeTopology(topo);
5024
5025 if ( nelems < 0 )
5026 {
5027 /* should never reach this point, as lwerror would raise an exception */
5028 SPI_finish();
5029 PG_RETURN_NULL();
5030 }
5031
5032 state = lwalloc(sizeof(FACEEDGESSTATE));
5033 state->elems = elems;
5034 state->nelems = nelems;
5035 state->curr = 0;
5036 funcctx->user_fctx = state;
5037
5038 POSTGIS_DEBUG(1, "TopoGeo_AddLinestring calling SPI_finish");
5039
5040 MemoryContextSwitchTo(oldcontext);
5041
5042 SPI_finish();
5043 }
5044
5045 POSTGIS_DEBUG(1, "Per-call invocation");
5046
5047 /* stuff done on every call of the function */
5048 funcctx = SRF_PERCALL_SETUP();
5049
5050 /* get state */
5051 state = funcctx->user_fctx;
5052
5053 if ( state->curr == state->nelems )
5054 {
5055 POSTGIS_DEBUG(1, "We're done, cleaning up all");
5056 SRF_RETURN_DONE(funcctx);
5057 }
5058
5059 id = state->elems[state->curr++];
5060 POSTGIS_DEBUGF(1, "TopoGeo_AddLinestring: cur:%d, val:%" LWTFMT_ELEMID,
5061 state->curr-1, id);
5062
5063 result = Int32GetDatum((int32)id);
5064
5065 SRF_RETURN_NEXT(funcctx, result);
5066 }
5067
5068 /* TopoGeo_AddPolygon(atopology, poly, tolerance) */
5069 Datum TopoGeo_AddPolygon(PG_FUNCTION_ARGS);
5070 PG_FUNCTION_INFO_V1(TopoGeo_AddPolygon);
TopoGeo_AddPolygon(PG_FUNCTION_ARGS)5071 Datum TopoGeo_AddPolygon(PG_FUNCTION_ARGS)
5072 {
5073 text* toponame_text;
5074 char* toponame;
5075 double tol;
5076 LWT_ELEMID *elems;
5077 int nelems;
5078 GSERIALIZED *geom;
5079 LWGEOM *lwgeom;
5080 LWPOLY *pol;
5081 LWT_TOPOLOGY *topo;
5082 FuncCallContext *funcctx;
5083 MemoryContext oldcontext, newcontext;
5084 FACEEDGESSTATE *state;
5085 Datum result;
5086 LWT_ELEMID id;
5087
5088 if (SRF_IS_FIRSTCALL())
5089 {
5090 POSTGIS_DEBUG(1, "TopoGeo_AddPolygon first call");
5091 funcctx = SRF_FIRSTCALL_INIT();
5092 newcontext = funcctx->multi_call_memory_ctx;
5093
5094 if ( PG_ARGISNULL(0) || PG_ARGISNULL(1) || PG_ARGISNULL(2) )
5095 {
5096 lwpgerror("SQL/MM Spatial exception - null argument");
5097 PG_RETURN_NULL();
5098 }
5099
5100 toponame_text = PG_GETARG_TEXT_P(0);
5101 toponame = text_to_cstring(toponame_text);
5102 PG_FREE_IF_COPY(toponame_text, 0);
5103
5104 geom = PG_GETARG_GSERIALIZED_P(1);
5105 lwgeom = lwgeom_from_gserialized(geom);
5106 pol = lwgeom_as_lwpoly(lwgeom);
5107 if ( ! pol )
5108 {
5109 {
5110 char buf[32];
5111 _lwtype_upper_name(lwgeom_get_type(lwgeom), buf, 32);
5112 lwgeom_free(lwgeom);
5113 PG_FREE_IF_COPY(geom, 1);
5114 lwpgerror("Invalid geometry type (%s) passed to "
5115 "TopoGeo_AddPolygon, expected POLYGON", buf);
5116 PG_RETURN_NULL();
5117 }
5118 }
5119
5120 tol = PG_GETARG_FLOAT8(2);
5121 if ( tol < 0 )
5122 {
5123 PG_FREE_IF_COPY(geom, 1);
5124 lwpgerror("Tolerance must be >=0");
5125 PG_RETURN_NULL();
5126 }
5127
5128 if ( SPI_OK_CONNECT != SPI_connect() )
5129 {
5130 lwpgerror("Could not connect to SPI");
5131 PG_RETURN_NULL();
5132 }
5133
5134 {
5135 int pre = be_data.topoLoadFailMessageFlavor;
5136 be_data.topoLoadFailMessageFlavor = 1;
5137 topo = lwt_LoadTopology(be_iface, toponame);
5138 be_data.topoLoadFailMessageFlavor = pre;
5139 }
5140 oldcontext = MemoryContextSwitchTo( newcontext );
5141 pfree(toponame);
5142 if ( ! topo )
5143 {
5144 /* should never reach this point, as lwerror would raise an exception */
5145 SPI_finish();
5146 PG_RETURN_NULL();
5147 }
5148
5149 POSTGIS_DEBUG(1, "Calling lwt_AddPolygon");
5150 elems = lwt_AddPolygon(topo, pol, tol, &nelems);
5151 POSTGIS_DEBUG(1, "lwt_AddPolygon returned");
5152 lwgeom_free(lwgeom);
5153 PG_FREE_IF_COPY(geom, 1);
5154 lwt_FreeTopology(topo);
5155
5156 if ( nelems < 0 )
5157 {
5158 /* should never reach this point, as lwerror would raise an exception */
5159 SPI_finish();
5160 PG_RETURN_NULL();
5161 }
5162
5163 state = lwalloc(sizeof(FACEEDGESSTATE));
5164 state->elems = elems;
5165 state->nelems = nelems;
5166 state->curr = 0;
5167 funcctx->user_fctx = state;
5168
5169 POSTGIS_DEBUG(1, "TopoGeo_AddPolygon calling SPI_finish");
5170
5171 MemoryContextSwitchTo(oldcontext);
5172
5173 SPI_finish();
5174 }
5175
5176 POSTGIS_DEBUG(1, "Per-call invocation");
5177
5178 /* stuff done on every call of the function */
5179 funcctx = SRF_PERCALL_SETUP();
5180
5181 /* get state */
5182 state = funcctx->user_fctx;
5183
5184 if ( state->curr == state->nelems )
5185 {
5186 POSTGIS_DEBUG(1, "We're done, cleaning up all");
5187 SRF_RETURN_DONE(funcctx);
5188 }
5189
5190 id = state->elems[state->curr++];
5191 POSTGIS_DEBUGF(1, "TopoGeo_AddPolygon: cur:%d, val:%" LWTFMT_ELEMID,
5192 state->curr-1, id);
5193
5194 result = Int32GetDatum((int32)id);
5195
5196 SRF_RETURN_NEXT(funcctx, result);
5197 }
5198
5199 /* GetRingEdges(atopology, anedge, maxedges default null) */
5200 Datum GetRingEdges(PG_FUNCTION_ARGS);
5201 PG_FUNCTION_INFO_V1(GetRingEdges);
GetRingEdges(PG_FUNCTION_ARGS)5202 Datum GetRingEdges(PG_FUNCTION_ARGS)
5203 {
5204 text* toponame_text;
5205 char* toponame;
5206 LWT_ELEMID edge_id;
5207 int maxedges = 0;
5208 uint64_t nelems;
5209 LWT_ELEMID *elems;
5210 LWT_BE_TOPOLOGY *topo;
5211 FuncCallContext *funcctx;
5212 MemoryContext oldcontext, newcontext;
5213 FACEEDGESSTATE *state;
5214 Datum result;
5215 HeapTuple tuple;
5216 Datum ret[2];
5217 bool isnull[2] = {0,0}; /* needed to say neither value is null */
5218
5219 if (SRF_IS_FIRSTCALL())
5220 {
5221 POSTGIS_DEBUG(1, "GetRingEdges first call");
5222 funcctx = SRF_FIRSTCALL_INIT();
5223 newcontext = funcctx->multi_call_memory_ctx;
5224
5225
5226 if ( PG_ARGISNULL(0) )
5227 {
5228 lwpgerror("GetRingEdges: topology name cannot be null");
5229 PG_RETURN_NULL();
5230 }
5231 toponame_text = PG_GETARG_TEXT_P(0);
5232 toponame = text_to_cstring(toponame_text);
5233 PG_FREE_IF_COPY(toponame_text, 0);
5234
5235 if ( PG_ARGISNULL(1) )
5236 {
5237 lwpgerror("GetRingEdges: edge id cannot be null");
5238 PG_RETURN_NULL();
5239 }
5240 edge_id = PG_GETARG_INT32(1) ;
5241
5242 if ( ! PG_ARGISNULL(2) )
5243 {
5244 maxedges = PG_GETARG_INT32(2) ;
5245 }
5246
5247 if ( SPI_OK_CONNECT != SPI_connect() )
5248 {
5249 lwpgerror("Could not connect to SPI");
5250 PG_RETURN_NULL();
5251 }
5252
5253 {
5254 int pre = be_data.topoLoadFailMessageFlavor;
5255 be_data.topoLoadFailMessageFlavor = 1;
5256 topo = cb_loadTopologyByName(&be_data, toponame);
5257 be_data.topoLoadFailMessageFlavor = pre;
5258 }
5259 oldcontext = MemoryContextSwitchTo( newcontext );
5260 pfree(toponame);
5261 if ( ! topo )
5262 {
5263 SPI_finish();
5264 lwpgerror("%s", cb_lastErrorMessage(&be_data));
5265 PG_RETURN_NULL();
5266 }
5267
5268 POSTGIS_DEBUG(1, "Calling cb_getRingEdges");
5269 elems = cb_getRingEdges(topo, edge_id, &nelems, maxedges);
5270 POSTGIS_DEBUG(1, "cb_getRingEdges returned");
5271 cb_freeTopology(topo);
5272
5273 if ( ! elems )
5274 {
5275 SPI_finish();
5276 lwpgerror("%s", cb_lastErrorMessage(&be_data));
5277 PG_RETURN_NULL();
5278 }
5279
5280 state = lwalloc(sizeof(FACEEDGESSTATE));
5281 state->elems = elems;
5282 state->nelems = nelems;
5283 state->curr = 0;
5284 funcctx->user_fctx = state;
5285
5286 POSTGIS_DEBUG(1, "GetRingEdges calling SPI_finish");
5287
5288 /*
5289 * Get tuple description for return type
5290 */
5291 get_call_result_type(fcinfo, 0, &funcctx->tuple_desc);
5292 BlessTupleDesc(funcctx->tuple_desc);
5293
5294 MemoryContextSwitchTo(oldcontext);
5295
5296 SPI_finish();
5297 }
5298
5299 POSTGIS_DEBUG(1, "Per-call invocation");
5300
5301 /* stuff done on every call of the function */
5302 funcctx = SRF_PERCALL_SETUP();
5303
5304 /* get state */
5305 state = funcctx->user_fctx;
5306
5307 if ( state->curr == state->nelems )
5308 {
5309 POSTGIS_DEBUG(1, "We're done, cleaning up all");
5310 SRF_RETURN_DONE(funcctx);
5311 }
5312
5313 edge_id = state->elems[state->curr++];
5314 POSTGIS_DEBUGF(1, "GetRingEdges: cur:%d, val:%" LWTFMT_ELEMID,
5315 state->curr-1, edge_id);
5316
5317
5318 ret[0] = Int32GetDatum(state->curr);
5319 ret[1] = Int32GetDatum(edge_id);
5320 tuple = heap_form_tuple(funcctx->tuple_desc, ret, isnull);
5321 result = HeapTupleGetDatum(tuple);
5322
5323 SRF_RETURN_NEXT(funcctx, result);
5324 }
5325
5326 /* GetFaceContainingPoint(atopology, point) */
5327 Datum GetFaceContainingPoint(PG_FUNCTION_ARGS);
5328 PG_FUNCTION_INFO_V1(GetFaceContainingPoint);
GetFaceContainingPoint(PG_FUNCTION_ARGS)5329 Datum GetFaceContainingPoint(PG_FUNCTION_ARGS)
5330 {
5331 text* toponame_text;
5332 char* toponame;
5333 LWT_ELEMID face_id;
5334 GSERIALIZED *geom;
5335 LWGEOM *lwgeom;
5336 LWPOINT *pt;
5337 LWT_TOPOLOGY *topo;
5338
5339 toponame_text = PG_GETARG_TEXT_P(0);
5340 toponame = text_to_cstring(toponame_text);
5341 PG_FREE_IF_COPY(toponame_text, 0);
5342
5343 geom = PG_GETARG_GSERIALIZED_P(1);
5344 lwgeom = lwgeom_from_gserialized(geom);
5345 pt = lwgeom_as_lwpoint(lwgeom);
5346 if ( ! pt )
5347 {
5348 lwgeom_free(lwgeom);
5349 PG_FREE_IF_COPY(geom, 1);
5350 lwpgerror("Second argument must be a point geometry");
5351 PG_RETURN_NULL();
5352 }
5353
5354 if ( SPI_OK_CONNECT != SPI_connect() )
5355 {
5356 lwpgerror("Could not connect to SPI");
5357 PG_RETURN_NULL();
5358 }
5359
5360 topo = lwt_LoadTopology(be_iface, toponame);
5361 pfree(toponame);
5362 if ( ! topo )
5363 {
5364 /* should never reach this point, as lwerror would raise an exception */
5365 SPI_finish();
5366 PG_RETURN_NULL();
5367 }
5368
5369 POSTGIS_DEBUG(1, "Calling lwt_GetFaceContainingPoint");
5370 face_id = lwt_GetFaceContainingPoint(topo, pt);
5371 POSTGIS_DEBUG(1, "lwt_GetFaceContainingPoint returned");
5372 lwgeom_free(lwgeom);
5373 PG_FREE_IF_COPY(geom, 1);
5374 lwt_FreeTopology(topo);
5375
5376 if ( face_id == -1 )
5377 {
5378 /* should never reach this point, as lwerror would raise an exception */
5379 SPI_finish();
5380 PG_RETURN_NULL();
5381 }
5382
5383 SPI_finish();
5384 PG_RETURN_INT32(face_id);
5385 }
5386