PostGIS  3.3.9dev-r@@SVN_REVISION@@
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 /*
54 TODO:
55  - OGC:LonLat84_5773 explicit support (rather than EPSG:4326)
56  - altitudeModeGroup relativeToGround Z Altitude
57  computation upon Geoid
58 */
59 
60 
61 Datum geom_from_kml(PG_FUNCTION_ARGS);
62 static LWGEOM* parse_kml(xmlNodePtr xnode, bool *hasz);
63 
64 #define KML_NS ((char *) "http://www.opengis.net/kml/2.2")
65 
66 
67 static inline bool
68 is_kml_element(xmlNodePtr xn, const char *kml_name)
69 {
70  char *colon_pos;
71  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 = (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 
94 Datum 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  // xmldoc = xmlReadMemory(xml, xml_size, NULL, NULL, XML_PARSE_SAX1);
115  if (!xmldoc || (xmlroot = xmlDocGetRootElement(xmldoc)) == NULL)
116  {
117  xmlFreeDoc(xmldoc);
118  xmlCleanupParser();
119  lwpgerror("invalid KML representation");
120  }
121 
122  lwgeom = parse_kml(xmlroot, &hasz);
123 
124  /* Homogenize geometry result if needed */
125  if (lwgeom->type == COLLECTIONTYPE)
126  {
127  hlwgeom = lwgeom_homogenize(lwgeom);
128  lwgeom_release(lwgeom);
129  lwgeom = hlwgeom;
130  }
131 
132  /* KML geometries could be either 2 or 3D
133  *
134  * So we deal with 3D in all structures allocation, and flag hasz
135  * to false if we met once a missing Z dimension
136  * In this case, we force recursive 2D.
137  */
138  if (!hasz)
139  {
140  LWGEOM *tmp = lwgeom_force_2d(lwgeom);
141  lwgeom_free(lwgeom);
142  lwgeom = tmp;
143  }
144 
145  geom = geometry_serialize(lwgeom);
146  lwgeom_free(lwgeom);
147 
148  xmlFreeDoc(xmldoc);
149  xmlCleanupParser();
150 
151  PG_RETURN_POINTER(geom);
152 }
153 
154 
159 static bool is_kml_namespace(xmlNodePtr xnode, bool is_strict)
160 {
161  xmlNsPtr *ns, *p;
162 
163  ns = xmlGetNsList(xnode->doc, xnode);
164  /*
165  * If no namespace is available we could return true anyway
166  * (because we work only on KML fragment, we don't want to
167  * 'oblige' to add namespace on the geometry root node)
168  */
169  if (ns == NULL) return !is_strict;
170 
171  for (p=ns ; *p ; p++)
172  {
173  if ((*p)->href == NULL || (*p)->prefix == NULL ||
174  xnode->ns == NULL || xnode->ns->prefix == NULL) continue;
175 
176  if (!xmlStrcmp(xnode->ns->prefix, (*p)->prefix))
177  {
178  if (!strcmp((char *) (*p)->href, KML_NS))
179  {
180  xmlFree(ns);
181  return true;
182  } else {
183  xmlFree(ns);
184  return false;
185  }
186  }
187  }
188 
189  xmlFree(ns);
190  return !is_strict; /* Same reason here to not return false */;
191 }
192 
193 
194 /* Temporarily disabling unused function. */
195 #if 0
200 static xmlChar *kmlGetProp(xmlNodePtr xnode, xmlChar *prop)
201 {
202  xmlChar *value;
203 
204  if (!is_kml_namespace(xnode, true))
205  return xmlGetProp(xnode, prop);
206 
207  value = xmlGetNsProp(xnode, prop, (xmlChar *) KML_NS);
208 
209  /* In last case try without explicit namespace */
210  if (value == NULL) value = xmlGetNoNsProp(xnode, prop);
211 
212  return value;
213 }
214 #endif
215 
216 
217 #if 0 /* unused */
221 static double parse_kml_double(char *d, bool space_before, bool space_after)
222 {
223  char *p;
224  int st;
225  enum states
226  {
227  INIT = 0,
228  NEED_DIG = 1,
229  DIG = 2,
230  NEED_DIG_DEC = 3,
231  DIG_DEC = 4,
232  EXP = 5,
233  NEED_DIG_EXP = 6,
234  DIG_EXP = 7,
235  END = 8
236  };
237 
238  /*
239  * Double pattern
240  * [-|\+]?[0-9]+(\.)?([0-9]+)?([Ee](\+|-)?[0-9]+)?
241  * We could also meet spaces before and/or after
242  * this pattern upon parameters
243  */
244 
245  if (space_before) while (isspace(*d)) d++;
246  for (st = INIT, p = d ; *p ; p++)
247  {
248 
249 lwpgnotice("State: %d, *p=%c", st, *p);
250 
251  if (isdigit(*p))
252  {
253  if (st == INIT || st == NEED_DIG) st = DIG;
254  else if (st == NEED_DIG_DEC) st = DIG_DEC;
255  else if (st == NEED_DIG_EXP || st == EXP) st = DIG_EXP;
256  else if (st == DIG || st == DIG_DEC || st == DIG_EXP);
257  else lwpgerror("invalid KML representation");
258  }
259  else if (*p == '.')
260  {
261  if (st == DIG) st = NEED_DIG_DEC;
262  else lwpgerror("invalid KML representation");
263  }
264  else if (*p == '-' || *p == '+')
265  {
266  if (st == INIT) st = NEED_DIG;
267  else if (st == EXP) st = NEED_DIG_EXP;
268  else lwpgerror("invalid KML representation");
269  }
270  else if (*p == 'e' || *p == 'E')
271  {
272  if (st == DIG || st == DIG_DEC) st = EXP;
273  else lwpgerror("invalid KML representation");
274  }
275  else if (isspace(*p))
276  {
277  if (!space_after) lwpgerror("invalid KML representation");
278  if (st == DIG || st == DIG_DEC || st == DIG_EXP)st = END;
279  else if (st == NEED_DIG_DEC) st = END;
280  else if (st == END);
281  else lwpgerror("invalid KML representation");
282  }
283  else lwpgerror("invalid KML representation");
284  }
285 
286  if (st != DIG && st != NEED_DIG_DEC && st != DIG_DEC && st != DIG_EXP && st != END)
287  lwpgerror("invalid KML representation");
288 
289  return atof(d);
290 }
291 #endif /* unused */
292 
293 
297 static POINTARRAY* parse_kml_coordinates(xmlNodePtr xnode, bool *hasz)
298 {
299  xmlChar *kml_coord;
300  bool found;
301  POINTARRAY *dpa;
302  int seen_kml_dims = 0;
303  int kml_dims;
304  char *p, *q;
305  POINT4D pt;
306  double d;
307 
308  if (xnode == NULL) lwpgerror("invalid KML representation");
309 
310  for (found = false ; xnode != NULL ; xnode = xnode->next)
311  {
312  if (xnode->type != XML_ELEMENT_NODE) continue;
313  if (!is_kml_namespace(xnode, false)) continue;
314  if (!is_kml_element(xnode, "coordinates")) continue;
315 
316  found = true;
317  break;
318  }
319  if (!found) lwpgerror("invalid KML representation");
320 
321  /* We begin to retrieve coordinates string */
322  kml_coord = xmlNodeGetContent(xnode);
323  p = (char *) kml_coord;
324 
325  /* KML coordinates pattern: x1,y1 x2,y2
326  * x1,y1,z1 x2,y2,z2
327  */
328 
329  /* Now we create PointArray from coordinates values */
330  /* HasZ, !HasM, 1pt */
331  dpa = ptarray_construct_empty(1, 0, 1);
332 
333  while (*p && isspace(*p)) ++p;
334  for (kml_dims=0; *p ; p++)
335  {
336 //lwpgnotice("*p:%c, kml_dims:%d", *p, kml_dims);
337  if ( isdigit(*p) || *p == '+' || *p == '-' || *p == '.' ) {
338  kml_dims++;
339  errno = 0; d = strtod(p, &q);
340  if ( errno != 0 ) {
341  // TODO: destroy dpa, return NULL
342  lwpgerror("invalid KML representation"); /*: %s", strerror(errno));*/
343  }
344  if (kml_dims == 1) pt.x = d;
345  else if (kml_dims == 2) pt.y = d;
346  else if (kml_dims == 3) pt.z = d;
347  else {
348  lwpgerror("invalid KML representation"); /* (more than 3 dimensions)"); */
349  // TODO: destroy dpa, return NULL
350  }
351 
352 //lwpgnotice("after strtod d:%f, *q:%c, kml_dims:%d", d, *q, kml_dims);
353 
354  if ( *q && ! isspace(*q) && *q != ',' ) {
355  lwpgerror("invalid KML representation"); /* (invalid character %c follows ordinate value)", *q); */
356  }
357 
358  /* Look-ahead to see if we're done reading */
359  while (*q && isspace(*q)) ++q;
360  if ( isdigit(*q) || *q == '+' || *q == '-' || *q == '.' || ! *q ) {
361  if ( kml_dims < 2 ) lwpgerror("invalid KML representation"); /* (not enough ordinates)"); */
362  else if ( kml_dims < 3 ) *hasz = false;
363  if ( ! seen_kml_dims ) seen_kml_dims = kml_dims;
364  else if ( seen_kml_dims != kml_dims ) {
365  lwpgerror("invalid KML representation: mixed coordinates dimension");
366  }
367  ptarray_append_point(dpa, &pt, LW_TRUE);
368  kml_dims = 0;
369  }
370  p = q-1; /* will be incremented on next iteration */
371 //lwpgnotice("after look-ahead *p:%c, kml_dims:%d", *p, kml_dims);
372  } else if ( *p != ',' && ! isspace(*p) ) {
373  lwpgerror("invalid KML representation"); /* (unexpected character %c)", *p); */
374  }
375  }
376 
377  xmlFree(kml_coord);
378 
379  /* TODO: we shouldn't need to clone here */
380  return ptarray_clone_deep(dpa);
381 }
382 
383 
387 static LWGEOM* parse_kml_point(xmlNodePtr xnode, bool *hasz)
388 {
389  POINTARRAY *pa;
390 
391  if (xnode->children == NULL) lwpgerror("invalid KML representation");
392  pa = parse_kml_coordinates(xnode->children, hasz);
393  if (pa->npoints != 1) lwpgerror("invalid KML representation");
394 
395  return (LWGEOM *) lwpoint_construct(4326, NULL, pa);
396 }
397 
398 
402 static LWGEOM* parse_kml_line(xmlNodePtr xnode, bool *hasz)
403 {
404  POINTARRAY *pa;
405 
406  if (xnode->children == NULL) lwpgerror("invalid KML representation");
407  pa = parse_kml_coordinates(xnode->children, hasz);
408  if (pa->npoints < 2) lwpgerror("invalid KML representation");
409 
410  return (LWGEOM *) lwline_construct(4326, NULL, pa);
411 }
412 
413 
417 static LWGEOM* parse_kml_polygon(xmlNodePtr xnode, bool *hasz)
418 {
419  int ring;
420  xmlNodePtr xa, xb;
421  POINTARRAY **ppa = NULL;
422  int outer_rings = 0;
423 
424  for (xa = xnode->children ; xa != NULL ; xa = xa->next)
425  {
426 
427  /* Polygon/outerBoundaryIs */
428  if (xa->type != XML_ELEMENT_NODE) continue;
429  if (!is_kml_namespace(xa, false)) continue;
430  if (!is_kml_element(xa, "outerBoundaryIs")) continue;
431 
432  for (xb = xa->children ; xb != NULL ; xb = xb->next)
433  {
434 
435  if (xb->type != XML_ELEMENT_NODE) continue;
436  if (!is_kml_namespace(xb, false)) continue;
437  if (!is_kml_element(xb, "LinearRing")) continue;
438 
439  ppa = (POINTARRAY**) lwalloc(sizeof(POINTARRAY*));
440  ppa[0] = parse_kml_coordinates(xb->children, hasz);
441 
442  if (ppa[0]->npoints < 4)
443  lwpgerror("invalid KML representation");
444 
445  if ((!*hasz && !ptarray_is_closed_2d(ppa[0])) ||
446  ( *hasz && !ptarray_is_closed_3d(ppa[0])))
447  {
448  POINT4D pt;
449  getPoint4d_p(ppa[0], 0, &pt);
450  ptarray_append_point(ppa[0], &pt, LW_TRUE);
451  lwpgnotice("forced closure on an un-closed KML polygon");
452  }
453  outer_rings++;
454  }
455  }
456 
457  if (outer_rings != 1)
458  lwpgerror("invalid KML representation");
459 
460  for (ring=1, xa = xnode->children ; xa != NULL ; xa = xa->next)
461  {
462 
463  /* Polygon/innerBoundaryIs */
464  if (xa->type != XML_ELEMENT_NODE) continue;
465  if (!is_kml_namespace(xa, false)) continue;
466  if (!is_kml_element(xa, "innerBoundaryIs")) continue;
467 
468  for (xb = xa->children ; xb != NULL ; xb = xb->next)
469  {
470 
471  if (xb->type != XML_ELEMENT_NODE) continue;
472  if (!is_kml_namespace(xb, false)) continue;
473  if (!is_kml_element(xb, "LinearRing")) continue;
474 
475  ppa = (POINTARRAY**) lwrealloc(ppa, sizeof(POINTARRAY*) * (ring + 1));
476  ppa[ring] = parse_kml_coordinates(xb->children, hasz);
477 
478  if (ppa[ring]->npoints < 4)
479  lwpgerror("invalid KML representation");
480 
481  if ((!*hasz && !ptarray_is_closed_2d(ppa[ring])) ||
482  ( *hasz && !ptarray_is_closed_3d(ppa[ring])))
483  {
484  POINT4D pt;
485  getPoint4d_p(ppa[ring], 0, &pt);
486  ptarray_append_point(ppa[ring], &pt, LW_TRUE);
487  lwpgnotice("forced closure on an un-closed KML polygon");
488  }
489 
490  ring++;
491  }
492  }
493 
494  /* Exterior Ring is mandatory */
495  if (ppa == NULL || ppa[0] == NULL) lwpgerror("invalid KML representation");
496 
497  return (LWGEOM *) lwpoly_construct(4326, NULL, ring, ppa);
498 }
499 
500 
504 static LWGEOM* parse_kml_multi(xmlNodePtr xnode, bool *hasz)
505 {
506  LWGEOM *geom;
507  xmlNodePtr xa;
508 
509  geom = (LWGEOM *)lwcollection_construct_empty(COLLECTIONTYPE, 4326, 1, 0);
510 
511  for (xa = xnode->children ; xa != NULL ; xa = xa->next)
512  {
513 
514  if (xa->type != XML_ELEMENT_NODE) continue;
515  if (!is_kml_namespace(xa, false)) continue;
516 
517  if ( is_kml_element(xa, "Point")
518  || is_kml_element(xa, "LineString")
519  || is_kml_element(xa, "Polygon")
520  || is_kml_element(xa, "MultiGeometry"))
521  {
522 
523  if (xa->children == NULL) break;
524  geom = (LWGEOM*)lwcollection_add_lwgeom((LWCOLLECTION*)geom, parse_kml(xa, hasz));
525  }
526  }
527 
528  return geom;
529 }
530 
531 
535 static LWGEOM* parse_kml(xmlNodePtr xnode, bool *hasz)
536 {
537  xmlNodePtr xa = xnode;
538 
539  while (xa != NULL && (xa->type != XML_ELEMENT_NODE
540  || !is_kml_namespace(xa, false))) xa = xa->next;
541 
542  if (xa == NULL) lwpgerror("invalid KML representation");
543 
544  if (is_kml_element(xa, "Point"))
545  return parse_kml_point(xa, hasz);
546 
547  if (is_kml_element(xa, "LineString"))
548  return parse_kml_line(xa, hasz);
549 
550  if (is_kml_element(xa, "Polygon"))
551  return parse_kml_polygon(xa, hasz);
552 
553  if (is_kml_element(xa, "MultiGeometry"))
554  return parse_kml_multi(xa, hasz);
555 
556  lwpgerror("invalid KML representation");
557  return NULL; /* Never reach */
558 }
#define COLLECTIONTYPE
Definition: liblwgeom.h:123
void lwgeom_free(LWGEOM *geom)
Definition: lwgeom.c:1155
int ptarray_is_closed_3d(const POINTARRAY *pa)
Definition: ptarray.c:714
POINTARRAY * ptarray_clone_deep(const POINTARRAY *ptarray)
Deep clone a pointarray (also clones serialized pointlist)
Definition: ptarray.c:634
LWLINE * lwline_construct(int32_t srid, GBOX *bbox, POINTARRAY *points)
Definition: lwline.c:42
void * lwrealloc(void *mem, size_t size)
Definition: lwutil.c:235
LWGEOM * lwgeom_homogenize(const LWGEOM *geom)
Definition: lwhomogenize.c:208
LWPOINT * lwpoint_construct(int32_t srid, GBOX *bbox, POINTARRAY *point)
Definition: lwpoint.c:129
POINTARRAY * ptarray_construct_empty(char hasz, char hasm, uint32_t maxpoints)
Create a new POINTARRAY with no points.
Definition: ptarray.c:59
LWCOLLECTION * lwcollection_construct_empty(uint8_t type, int32_t srid, char hasz, char hasm)
Definition: lwcollection.c:92
int getPoint4d_p(const POINTARRAY *pa, uint32_t n, POINT4D *point)
Definition: lwgeom_api.c:126
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
LWCOLLECTION * lwcollection_add_lwgeom(LWCOLLECTION *col, const LWGEOM *geom)
Appends geom to the collection managed by col.
Definition: lwcollection.c:188
void * lwalloc(size_t size)
Definition: lwutil.c:227
int ptarray_is_closed_2d(const POINTARRAY *pa)
Definition: ptarray.c:701
#define LW_TRUE
Return types for functions with status returns.
Definition: liblwgeom.h:108
void lwgeom_release(LWGEOM *lwgeom)
Free the containing LWGEOM and the associated BOX.
Definition: lwgeom.c:468
LWPOLY * lwpoly_construct(int32_t srid, GBOX *bbox, uint32_t nrings, POINTARRAY **points)
Definition: lwpoly.c:43
LWGEOM * lwgeom_force_2d(const LWGEOM *geom)
Strip out the Z/M components of an LWGEOM.
Definition: lwgeom.c:793
This library is the generic geometry handling section of PostGIS.
static LWGEOM * parse_kml(xmlNodePtr xnode, bool *hasz)
Parse KML.
static LWGEOM * parse_kml_line(xmlNodePtr xnode, bool *hasz)
Parse KML lineString.
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
Definition: lwgeom_in_kml.c:64
static LWGEOM * parse_kml_multi(xmlNodePtr xnode, bool *hasz)
Parse KML MultiGeometry.
static LWGEOM * parse_kml_polygon(xmlNodePtr xnode, bool *hasz)
Parse KML Polygon.
static LWGEOM * parse_kml_point(xmlNodePtr xnode, bool *hasz)
Parse KML point.
Datum geom_from_kml(PG_FUNCTION_ARGS)
Definition: lwgeom_in_kml.c:94
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)
Definition: lwgeom_in_kml.c:68
int value
Definition: genraster.py:62
uint8_t type
Definition: liblwgeom.h:477
double x
Definition: liblwgeom.h:429
double z
Definition: liblwgeom.h:429
double y
Definition: liblwgeom.h:429
uint32_t npoints
Definition: liblwgeom.h:442