PostGIS 3.7.0dev-r@@SVN_REVISION@@
Loading...
Searching...
No Matches
lwgeom_in_kml.c
Go to the documentation of this file.
1/**********************************************************************
2 *
3 * PostGIS - Spatial Types for PostgreSQL
4 * http://postgis.net
5 *
6 * PostGIS is free software: you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation, either version 2 of the License, or
9 * (at your option) any later version.
10 *
11 * PostGIS is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with PostGIS. If not, see <http://www.gnu.org/licenses/>.
18 *
19 **********************************************************************
20 *
21 * Copyright 2009 Oslandia
22 *
23 **********************************************************************/
24
40#include "postgres.h"
41#include "utils/builtins.h"
42
43#include <libxml/tree.h>
44#include <libxml/parser.h>
45#include <string.h>
46
47#include "../postgis_config.h"
48#include "lwgeom_pg.h"
49#include "liblwgeom.h"
50
51
52
53/*
54TODO:
55 - OGC:LonLat84_5773 explicit support (rather than EPSG:4326)
56 - altitudeModeGroup relativeToGround Z Altitude
57 computation upon Geoid
58*/
59
60
61Datum geom_from_kml(PG_FUNCTION_ARGS);
62static LWGEOM* parse_kml(xmlNodePtr xnode, bool *hasz);
63
64#define KML_NS ((char *) "http://www.opengis.net/kml/2.2")
65
66
67static inline bool
68is_kml_element(xmlNodePtr xn, const char *kml_name)
69{
70 const char *colon_pos;
71 const char *node_name;
72
73 /* Not an element node, can't do anything */
74 if (!xn || xn->type != XML_ELEMENT_NODE)
75 return false;
76
77 /* If there's a colon in the element name, */
78 /* move past it before checking for equality with */
79 /* the element name we are looking for */
80 node_name = (const char*)xn->name;
81 colon_pos = strchr(node_name, ':');
82 if (colon_pos)
83 node_name = colon_pos + 1;
84
85 return strcmp(node_name, kml_name) == 0;
86}
87
88
94Datum geom_from_kml(PG_FUNCTION_ARGS)
95{
96 GSERIALIZED *geom;
97 LWGEOM *lwgeom, *hlwgeom;
98 xmlDocPtr xmldoc;
99 text *xml_input;
100 int xml_size;
101 char *xml;
102 bool hasz=true;
103 xmlNodePtr xmlroot=NULL;
104
105 /* Get the KML stream */
106 if (PG_ARGISNULL(0)) PG_RETURN_NULL();
107 xml_input = PG_GETARG_TEXT_P(0);
108 xml = text_to_cstring(xml_input);
109 xml_size = VARSIZE_ANY_EXHDR(xml_input);
110
111 /* Begin to Parse XML doc */
112 xmlInitParser();
113 xmldoc = xmlReadMemory(xml, xml_size, NULL, NULL, 0);
114 if (!xmldoc || (xmlroot = xmlDocGetRootElement(xmldoc)) == NULL)
115 {
116 xmlFreeDoc(xmldoc);
117 xmlCleanupParser();
118 lwpgerror("invalid KML representation");
119 }
120
121 lwgeom = parse_kml(xmlroot, &hasz);
122
123 /* Homogenize geometry result if needed */
124 if (lwgeom->type == COLLECTIONTYPE)
125 {
126 hlwgeom = lwgeom_homogenize(lwgeom);
127 lwgeom_release(lwgeom);
128 lwgeom = hlwgeom;
129 }
130
131 /* KML geometries could be either 2 or 3D
132 *
133 * So we deal with 3D in all structures allocation, and flag hasz
134 * to false if we met once a missing Z dimension
135 * In this case, we force recursive 2D.
136 */
137 if (!hasz)
138 {
139 LWGEOM *tmp = lwgeom_force_2d(lwgeom);
140 lwgeom_free(lwgeom);
141 lwgeom = tmp;
142 }
143
144 geom = geometry_serialize(lwgeom);
145 lwgeom_free(lwgeom);
146
147 xmlFreeDoc(xmldoc);
148 xmlCleanupParser();
149
150 PG_RETURN_POINTER(geom);
151}
152
153
158static bool is_kml_namespace(xmlNodePtr xnode, bool is_strict)
159{
160 xmlNsPtr *ns, *p;
161
162 ns = xmlGetNsList(xnode->doc, xnode);
163 /*
164 * If no namespace is available we could return true anyway
165 * (because we work only on KML fragment, we don't want to
166 * 'oblige' to add namespace on the geometry root node)
167 */
168 if (ns == NULL) return !is_strict;
169
170 for (p=ns ; *p ; p++)
171 {
172 if ((*p)->href == NULL || (*p)->prefix == NULL ||
173 xnode->ns == NULL || xnode->ns->prefix == NULL) continue;
174
175 if (!xmlStrcmp(xnode->ns->prefix, (*p)->prefix))
176 {
177 if (!strcmp((char *) (*p)->href, KML_NS))
178 {
179 xmlFree(ns);
180 return true;
181 } else {
182 xmlFree(ns);
183 return false;
184 }
185 }
186 }
187
188 xmlFree(ns);
189 return !is_strict; /* Same reason here to not return false */;
190}
191
192
193/* Temporarily disabling unused function. */
194#if 0
199static xmlChar *kmlGetProp(xmlNodePtr xnode, xmlChar *prop)
200{
201 xmlChar *value;
202
203 if (!is_kml_namespace(xnode, true))
204 return xmlGetProp(xnode, prop);
205
206 value = xmlGetNsProp(xnode, prop, (xmlChar *) KML_NS);
207
208 /* In last case try without explicit namespace */
209 if (value == NULL) value = xmlGetNoNsProp(xnode, prop);
210
211 return value;
212}
213#endif
214
215
216#if 0 /* unused */
220static double parse_kml_double(char *d, bool space_before, bool space_after)
221{
222 char *p;
223 int st;
224 enum states
225 {
226 INIT = 0,
227 NEED_DIG = 1,
228 DIG = 2,
229 NEED_DIG_DEC = 3,
230 DIG_DEC = 4,
231 EXP = 5,
232 NEED_DIG_EXP = 6,
233 DIG_EXP = 7,
234 END = 8
235 };
236
237 /*
238 * Double pattern
239 * [-|\+]?[0-9]+(\.)?([0-9]+)?([Ee](\+|-)?[0-9]+)?
240 * We could also meet spaces before and/or after
241 * this pattern upon parameters
242 */
243
244 if (space_before) while (isspace(*d)) d++;
245 for (st = INIT, p = d ; *p ; p++)
246 {
247
248lwpgnotice("State: %d, *p=%c", st, *p);
249
250 if (isdigit(*p))
251 {
252 if (st == INIT || st == NEED_DIG) st = DIG;
253 else if (st == NEED_DIG_DEC) st = DIG_DEC;
254 else if (st == NEED_DIG_EXP || st == EXP) st = DIG_EXP;
255 else if (st == DIG || st == DIG_DEC || st == DIG_EXP);
256 else lwpgerror("invalid KML representation");
257 }
258 else if (*p == '.')
259 {
260 if (st == DIG) st = NEED_DIG_DEC;
261 else lwpgerror("invalid KML representation");
262 }
263 else if (*p == '-' || *p == '+')
264 {
265 if (st == INIT) st = NEED_DIG;
266 else if (st == EXP) st = NEED_DIG_EXP;
267 else lwpgerror("invalid KML representation");
268 }
269 else if (*p == 'e' || *p == 'E')
270 {
271 if (st == DIG || st == DIG_DEC) st = EXP;
272 else lwpgerror("invalid KML representation");
273 }
274 else if (isspace(*p))
275 {
276 if (!space_after) lwpgerror("invalid KML representation");
277 if (st == DIG || st == DIG_DEC || st == DIG_EXP)st = END;
278 else if (st == NEED_DIG_DEC) st = END;
279 else if (st == END);
280 else lwpgerror("invalid KML representation");
281 }
282 else lwpgerror("invalid KML representation");
283 }
284
285 if (st != DIG && st != NEED_DIG_DEC && st != DIG_DEC && st != DIG_EXP && st != END)
286 lwpgerror("invalid KML representation");
287
288 return atof(d);
289}
290#endif /* unused */
291
292
296static POINTARRAY* parse_kml_coordinates(xmlNodePtr xnode, bool *hasz)
297{
298 xmlChar *kml_coord;
299 bool found;
300 POINTARRAY *dpa;
301 int seen_kml_dims = 0;
302 int kml_dims;
303 char *p, *q;
304 POINT4D pt;
305 double d;
306
307 if (xnode == NULL) lwpgerror("invalid KML representation");
308
309 for (found = false ; xnode != NULL ; xnode = xnode->next)
310 {
311 if (xnode->type != XML_ELEMENT_NODE) continue;
312 if (!is_kml_namespace(xnode, false)) continue;
313 if (!is_kml_element(xnode, "coordinates")) continue;
314
315 found = true;
316 break;
317 }
318 if (!found) lwpgerror("invalid KML representation");
319
320 /* We begin to retrieve coordinates string */
321 kml_coord = xmlNodeGetContent(xnode);
322 p = (char *) kml_coord;
323
324 /* KML coordinates pattern: x1,y1 x2,y2
325 * x1,y1,z1 x2,y2,z2
326 */
327
328 /* Now we create PointArray from coordinates values */
329 /* HasZ, !HasM, 1pt */
330 dpa = ptarray_construct_empty(1, 0, 1);
331
332 while (*p && isspace(*p)) ++p;
333 for (kml_dims=0; *p ; p++)
334 {
335//lwpgnotice("*p:%c, kml_dims:%d", *p, kml_dims);
336 if ( isdigit(*p) || *p == '+' || *p == '-' || *p == '.' ) {
337 kml_dims++;
338 errno = 0; d = strtod(p, &q);
339 if ( errno != 0 ) {
340 // TODO: destroy dpa, return NULL
341 lwpgerror("invalid KML representation"); /*: %s", strerror(errno));*/
342 }
343 if (kml_dims == 1) pt.x = d;
344 else if (kml_dims == 2) pt.y = d;
345 else if (kml_dims == 3) pt.z = d;
346 else {
347 lwpgerror("invalid KML representation"); /* (more than 3 dimensions)"); */
348 // TODO: destroy dpa, return NULL
349 }
350
351//lwpgnotice("after strtod d:%f, *q:%c, kml_dims:%d", d, *q, kml_dims);
352
353 if ( *q && ! isspace(*q) && *q != ',' ) {
354 lwpgerror("invalid KML representation"); /* (invalid character %c follows ordinate value)", *q); */
355 }
356
357 /* Look-ahead to see if we're done reading */
358 while (*q && isspace(*q)) ++q;
359 if ( isdigit(*q) || *q == '+' || *q == '-' || *q == '.' || ! *q ) {
360 if ( kml_dims < 2 ) lwpgerror("invalid KML representation"); /* (not enough ordinates)"); */
361 else if ( kml_dims < 3 ) *hasz = false;
362 if ( ! seen_kml_dims ) seen_kml_dims = kml_dims;
363 else if ( seen_kml_dims != kml_dims ) {
364 lwpgerror("invalid KML representation: mixed coordinates dimension");
365 }
366 ptarray_append_point(dpa, &pt, LW_TRUE);
367 kml_dims = 0;
368 }
369 p = q-1; /* will be incremented on next iteration */
370//lwpgnotice("after look-ahead *p:%c, kml_dims:%d", *p, kml_dims);
371 } else if ( *p != ',' && ! isspace(*p) ) {
372 lwpgerror("invalid KML representation"); /* (unexpected character %c)", *p); */
373 }
374 }
375
376 xmlFree(kml_coord);
377
378 /* TODO: we shouldn't need to clone here */
379 return ptarray_clone_deep(dpa);
380}
381
382
386static LWGEOM* parse_kml_point(xmlNodePtr xnode, bool *hasz)
387{
388 POINTARRAY *pa;
389
390 if (xnode->children == NULL) lwpgerror("invalid KML representation");
391 pa = parse_kml_coordinates(xnode->children, hasz);
392 if (pa->npoints != 1) lwpgerror("invalid KML representation");
393
394 return (LWGEOM *) lwpoint_construct(4326, NULL, pa);
395}
396
397
401static LWGEOM* parse_kml_line(xmlNodePtr xnode, bool *hasz)
402{
403 POINTARRAY *pa;
404
405 if (xnode->children == NULL) lwpgerror("invalid KML representation");
406 pa = parse_kml_coordinates(xnode->children, hasz);
407 if (pa->npoints < 2) lwpgerror("invalid KML representation");
408
409 return (LWGEOM *) lwline_construct(4326, NULL, pa);
410}
411
412
416static LWGEOM* parse_kml_polygon(xmlNodePtr xnode, bool *hasz)
417{
418 int ring;
419 xmlNodePtr xa, xb;
420 POINTARRAY **ppa = NULL;
421 int outer_rings = 0;
422
423 for (xa = xnode->children ; xa != NULL ; xa = xa->next)
424 {
425
426 /* Polygon/outerBoundaryIs */
427 if (xa->type != XML_ELEMENT_NODE) continue;
428 if (!is_kml_namespace(xa, false)) continue;
429 if (!is_kml_element(xa, "outerBoundaryIs")) continue;
430
431 for (xb = xa->children ; xb != NULL ; xb = xb->next)
432 {
433
434 if (xb->type != XML_ELEMENT_NODE) continue;
435 if (!is_kml_namespace(xb, false)) continue;
436 if (!is_kml_element(xb, "LinearRing")) continue;
437
438 ppa = (POINTARRAY**) lwalloc(sizeof(POINTARRAY*));
439 ppa[0] = parse_kml_coordinates(xb->children, hasz);
440
441 if (ppa[0]->npoints < 4)
442 lwpgerror("invalid KML representation");
443
444 if ((!*hasz && !ptarray_is_closed_2d(ppa[0])) ||
445 ( *hasz && !ptarray_is_closed_3d(ppa[0])))
446 {
447 POINT4D pt;
448 getPoint4d_p(ppa[0], 0, &pt);
449 ptarray_append_point(ppa[0], &pt, LW_TRUE);
450 lwpgnotice("forced closure on an un-closed KML polygon");
451 }
452 outer_rings++;
453 }
454 }
455
456 if (outer_rings != 1)
457 lwpgerror("invalid KML representation");
458
459 for (ring=1, xa = xnode->children ; xa != NULL ; xa = xa->next)
460 {
461
462 /* Polygon/innerBoundaryIs */
463 if (xa->type != XML_ELEMENT_NODE) continue;
464 if (!is_kml_namespace(xa, false)) continue;
465 if (!is_kml_element(xa, "innerBoundaryIs")) continue;
466
467 for (xb = xa->children ; xb != NULL ; xb = xb->next)
468 {
469
470 if (xb->type != XML_ELEMENT_NODE) continue;
471 if (!is_kml_namespace(xb, false)) continue;
472 if (!is_kml_element(xb, "LinearRing")) continue;
473
474 ppa = (POINTARRAY**) lwrealloc(ppa, sizeof(POINTARRAY*) * (ring + 1));
475 ppa[ring] = parse_kml_coordinates(xb->children, hasz);
476
477 if (ppa[ring]->npoints < 4)
478 lwpgerror("invalid KML representation");
479
480 if ((!*hasz && !ptarray_is_closed_2d(ppa[ring])) ||
481 ( *hasz && !ptarray_is_closed_3d(ppa[ring])))
482 {
483 POINT4D pt;
484 getPoint4d_p(ppa[ring], 0, &pt);
485 ptarray_append_point(ppa[ring], &pt, LW_TRUE);
486 lwpgnotice("forced closure on an un-closed KML polygon");
487 }
488
489 ring++;
490 }
491 }
492
493 /* Exterior Ring is mandatory */
494 if (ppa == NULL || ppa[0] == NULL) lwpgerror("invalid KML representation");
495
496 return (LWGEOM *) lwpoly_construct(4326, NULL, ring, ppa);
497}
498
499
503static LWGEOM* parse_kml_multi(xmlNodePtr xnode, bool *hasz)
504{
505 LWGEOM *geom;
506 xmlNodePtr xa;
507
509
510 for (xa = xnode->children ; xa != NULL ; xa = xa->next)
511 {
512
513 if (xa->type != XML_ELEMENT_NODE) continue;
514 if (!is_kml_namespace(xa, false)) continue;
515
516 if ( is_kml_element(xa, "Point")
517 || is_kml_element(xa, "LineString")
518 || is_kml_element(xa, "Polygon")
519 || is_kml_element(xa, "MultiGeometry"))
520 {
521
522 if (xa->children == NULL) break;
523 geom = (LWGEOM*)lwcollection_add_lwgeom((LWCOLLECTION*)geom, parse_kml(xa, hasz));
524 }
525 }
526
527 return geom;
528}
529
530
534static LWGEOM* parse_kml(xmlNodePtr xnode, bool *hasz)
535{
536 xmlNodePtr xa = xnode;
537
538 while (xa != NULL && (xa->type != XML_ELEMENT_NODE
539 || !is_kml_namespace(xa, false))) xa = xa->next;
540
541 if (xa == NULL) lwpgerror("invalid KML representation");
542
543 if (is_kml_element(xa, "Point"))
544 return parse_kml_point(xa, hasz);
545
546 if (is_kml_element(xa, "LineString"))
547 return parse_kml_line(xa, hasz);
548
549 if (is_kml_element(xa, "Polygon"))
550 return parse_kml_polygon(xa, hasz);
551
552 if (is_kml_element(xa, "MultiGeometry"))
553 return parse_kml_multi(xa, hasz);
554
555 lwpgerror("invalid KML representation");
556 return NULL; /* Never reach */
557}
#define COLLECTIONTYPE
Definition liblwgeom.h:108
void * lwrealloc(void *mem, size_t size)
Definition lwutil.c:242
void lwgeom_free(LWGEOM *geom)
Definition lwgeom.c:1246
int ptarray_is_closed_3d(const POINTARRAY *pa)
Definition ptarray.c:723
LWPOINT * lwpoint_construct(int32_t srid, GBOX *bbox, POINTARRAY *point)
Definition lwpoint.c:129
LWGEOM * lwgeom_force_2d(const LWGEOM *geom)
Strip out the Z/M components of an LWGEOM.
Definition lwgeom.c:821
POINTARRAY * ptarray_construct_empty(char hasz, char hasm, uint32_t maxpoints)
Create a new POINTARRAY with no points.
Definition ptarray.c:59
LWGEOM * lwgeom_homogenize(const LWGEOM *geom)
void * lwalloc(size_t size)
Definition lwutil.c:227
LWLINE * lwline_construct(int32_t srid, GBOX *bbox, POINTARRAY *points)
Definition lwline.c:42
int getPoint4d_p(const POINTARRAY *pa, uint32_t n, POINT4D *point)
Definition lwgeom_api.c:125
LWPOLY * lwpoly_construct(int32_t srid, GBOX *bbox, uint32_t nrings, POINTARRAY **points)
Definition lwpoly.c:43
LWCOLLECTION * lwcollection_construct_empty(uint8_t type, int32_t srid, char hasz, char hasm)
int ptarray_append_point(POINTARRAY *pa, const POINT4D *pt, int allow_duplicates)
Append a point to the end of an existing POINTARRAY If allow_duplicate is LW_FALSE,...
Definition ptarray.c:147
int ptarray_is_closed_2d(const POINTARRAY *pa)
Definition ptarray.c:710
#define LW_TRUE
Return types for functions with status returns.
Definition liblwgeom.h:93
LWCOLLECTION * lwcollection_add_lwgeom(LWCOLLECTION *col, const LWGEOM *geom)
Appends geom to the collection managed by col.
void lwgeom_release(LWGEOM *lwgeom)
Free the containing LWGEOM and the associated BOX.
Definition lwgeom.c:496
POINTARRAY * ptarray_clone_deep(const POINTARRAY *ptarray)
Deep clone a pointarray (also clones serialized pointlist)
Definition ptarray.c:643
This library is the generic geometry handling section of PostGIS.
static LWGEOM * parse_kml(xmlNodePtr xnode, bool *hasz)
Parse KML.
static POINTARRAY * parse_kml_coordinates(xmlNodePtr xnode, bool *hasz)
Parse kml:coordinates.
PG_FUNCTION_INFO_V1(geom_from_kml)
Ability to parse KML geometry fragment and to return an LWGEOM or an error message.
#define KML_NS
Datum geom_from_kml(PG_FUNCTION_ARGS)
static LWGEOM * parse_kml_polygon(xmlNodePtr xnode, bool *hasz)
Parse KML Polygon.
static LWGEOM * parse_kml_multi(xmlNodePtr xnode, bool *hasz)
Parse KML MultiGeometry.
static LWGEOM * parse_kml_point(xmlNodePtr xnode, bool *hasz)
Parse KML point.
static LWGEOM * parse_kml_line(xmlNodePtr xnode, bool *hasz)
Parse KML lineString.
static bool is_kml_namespace(xmlNodePtr xnode, bool is_strict)
Return false if current element namespace is not a KML one Return true otherwise.
static bool is_kml_element(xmlNodePtr xn, const char *kml_name)
int value
Definition genraster.py:62
uint8_t type
Definition liblwgeom.h:462
double x
Definition liblwgeom.h:414
double z
Definition liblwgeom.h:414
double y
Definition liblwgeom.h:414
uint32_t npoints
Definition liblwgeom.h:427