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