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