PostGIS  2.1.10dev-r@@SVN_REVISION@@
lwgeom_in_kml.c
Go to the documentation of this file.
1 /**********************************************************************
2  * $Id:$
3  *
4  * PostGIS - Spatial Types for PostgreSQL
5 
6  * Copyright 2009 Oslandia
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 
30 #include <libxml/tree.h>
31 #include <libxml/parser.h>
32 #include <errno.h>
33 #include <string.h>
34 
35 #include "postgres.h"
36 
37 #include "../postgis_config.h"
38 #include "lwgeom_pg.h"
39 #include "liblwgeom.h"
40 
41 
42 
43 /*
44 TODO:
45  - OGC:LonLat84_5773 explicit support (rather than EPSG:4326)
46  - altitudeModeGroup relativeToGround Z Altitude
47  computation upon Geoid
48 */
49 
50 
51 Datum geom_from_kml(PG_FUNCTION_ARGS);
52 static LWGEOM* parse_kml(xmlNodePtr xnode, bool *hasz);
53 
54 #define KML_NS ((char *) "http://www.opengis.net/kml/2.2")
55 
56 
62 Datum geom_from_kml(PG_FUNCTION_ARGS)
63 {
64  GSERIALIZED *geom;
65  LWGEOM *lwgeom, *hlwgeom;
66  xmlDocPtr xmldoc;
67  text *xml_input;
68  int xml_size;
69  char *xml;
70  bool hasz=true;
71  xmlNodePtr xmlroot=NULL;
72 
73 
74  /* Get the KML stream */
75  if (PG_ARGISNULL(0)) PG_RETURN_NULL();
76  xml_input = PG_GETARG_TEXT_P(0);
77  xml = text2cstring(xml_input);
78  xml_size = VARSIZE(xml_input) - VARHDRSZ;
79 
80  /* Begin to Parse XML doc */
81  xmlInitParser();
82  xmldoc = xmlReadMemory(xml, xml_size, NULL, NULL, XML_PARSE_SAX1);
83  if (!xmldoc || (xmlroot = xmlDocGetRootElement(xmldoc)) == NULL)
84  {
85  xmlFreeDoc(xmldoc);
86  xmlCleanupParser();
87  lwerror("invalid KML representation");
88  }
89 
90  lwgeom = parse_kml(xmlroot, &hasz);
91 
92  /* Homogenize geometry result if needed */
93  if (lwgeom->type == COLLECTIONTYPE)
94  {
95  hlwgeom = lwgeom_homogenize(lwgeom);
96  lwgeom_release(lwgeom);
97  lwgeom = hlwgeom;
98  }
99 
100  lwgeom_add_bbox(lwgeom);
101 
102  /* KML geometries could be either 2 or 3D
103  *
104  * So we deal with 3D in all structures allocation, and flag hasz
105  * to false if we met once a missing Z dimension
106  * In this case, we force recursive 2D.
107  */
108  if (!hasz)
109  {
110  LWGEOM *tmp = lwgeom_force_2d(lwgeom);
111  lwgeom_free(lwgeom);
112  lwgeom = tmp;
113  }
114 
115  geom = geometry_serialize(lwgeom);
116  lwgeom_free(lwgeom);
117 
118  xmlFreeDoc(xmldoc);
119  xmlCleanupParser();
120 
121  PG_RETURN_POINTER(geom);
122 }
123 
124 
129 static bool is_kml_namespace(xmlNodePtr xnode, bool is_strict)
130 {
131  xmlNsPtr *ns, *p;
132 
133  ns = xmlGetNsList(xnode->doc, xnode);
134  /*
135  * If no namespace is available we could return true anyway
136  * (because we work only on KML fragment, we don't want to
137  * 'oblige' to add namespace on the geometry root node)
138  */
139  if (ns == NULL) return !is_strict;
140 
141  for (p=ns ; *p ; p++)
142  {
143  if ((*p)->href == NULL || (*p)->prefix == NULL ||
144  xnode->ns == NULL || xnode->ns->prefix == NULL) continue;
145 
146  if (!xmlStrcmp(xnode->ns->prefix, (*p)->prefix))
147  {
148  if (!strcmp((char *) (*p)->href, KML_NS))
149  {
150  xmlFree(ns);
151  return true;
152  } else {
153  xmlFree(ns);
154  return false;
155  }
156  }
157  }
158 
159  xmlFree(ns);
160  return !is_strict; /* Same reason here to not return false */;
161 }
162 
163 
164 /* Temporarily disabling unused function. */
165 #if 0
166 
170 static xmlChar *kmlGetProp(xmlNodePtr xnode, xmlChar *prop)
171 {
172  xmlChar *value;
173 
174  if (!is_kml_namespace(xnode, true))
175  return xmlGetProp(xnode, prop);
176 
177  value = xmlGetNsProp(xnode, prop, (xmlChar *) KML_NS);
178 
179  /* In last case try without explicit namespace */
180  if (value == NULL) value = xmlGetNoNsProp(xnode, prop);
181 
182  return value;
183 }
184 #endif
185 
186 
187 #if 0 /* unused */
188 
191 static double parse_kml_double(char *d, bool space_before, bool space_after)
192 {
193  char *p;
194  int st;
195  enum states
196  {
197  INIT = 0,
198  NEED_DIG = 1,
199  DIG = 2,
200  NEED_DIG_DEC = 3,
201  DIG_DEC = 4,
202  EXP = 5,
203  NEED_DIG_EXP = 6,
204  DIG_EXP = 7,
205  END = 8
206  };
207 
208  /*
209  * Double pattern
210  * [-|\+]?[0-9]+(\.)?([0-9]+)?([Ee](\+|-)?[0-9]+)?
211  * We could also meet spaces before and/or after
212  * this pattern upon parameters
213  */
214 
215  if (space_before) while (isspace(*d)) d++;
216  for (st = INIT, p = d ; *p ; p++)
217  {
218 
219 lwnotice("State: %d, *p=%c", st, *p);
220 
221  if (isdigit(*p))
222  {
223  if (st == INIT || st == NEED_DIG) st = DIG;
224  else if (st == NEED_DIG_DEC) st = DIG_DEC;
225  else if (st == NEED_DIG_EXP || st == EXP) st = DIG_EXP;
226  else if (st == DIG || st == DIG_DEC || st == DIG_EXP);
227  else lwerror("invalid KML representation");
228  }
229  else if (*p == '.')
230  {
231  if (st == DIG) st = NEED_DIG_DEC;
232  else lwerror("invalid KML representation");
233  }
234  else if (*p == '-' || *p == '+')
235  {
236  if (st == INIT) st = NEED_DIG;
237  else if (st == EXP) st = NEED_DIG_EXP;
238  else lwerror("invalid KML representation");
239  }
240  else if (*p == 'e' || *p == 'E')
241  {
242  if (st == DIG || st == DIG_DEC) st = EXP;
243  else lwerror("invalid KML representation");
244  }
245  else if (isspace(*p))
246  {
247  if (!space_after) lwerror("invalid KML representation");
248  if (st == DIG || st == DIG_DEC || st == DIG_EXP)st = END;
249  else if (st == NEED_DIG_DEC) st = END;
250  else if (st == END);
251  else lwerror("invalid KML representation");
252  }
253  else lwerror("invalid KML representation");
254  }
255 
256  if (st != DIG && st != NEED_DIG_DEC && st != DIG_DEC && st != DIG_EXP && st != END)
257  lwerror("invalid KML representation");
258 
259  return atof(d);
260 }
261 #endif /* unused */
262 
263 
267 static POINTARRAY* parse_kml_coordinates(xmlNodePtr xnode, bool *hasz)
268 {
269  xmlChar *kml_coord;
270  bool found;
271  POINTARRAY *dpa;
272  int kml_dims;
273  char *p, *q;
274  POINT4D pt;
275  double d;
276 
277  if (xnode == NULL) lwerror("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) lwerror("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 //lwnotice("*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  lwerror("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  lwerror("invalid KML representation"); /* (more than 3 dimensions)"); */
318  // TODO: destroy dpa, return NULL
319  }
320 
321 //lwnotice("after strtod d:%f, *q:%c, kml_dims:%d", d, *q, kml_dims);
322 
323  if ( *q && ! isspace(*q) && *q != ',' ) {
324  lwerror("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 ) lwerror("invalid KML representation"); /* (not enough ordinates)"); */
331  if ( kml_dims < 3 ) *hasz = false;
332  ptarray_append_point(dpa, &pt, LW_TRUE);
333  kml_dims = 0;
334  }
335  p = q-1; /* will be incrementedon next iteration */
336 //lwnotice("after look-ahead *p:%c, kml_dims:%d", *p, kml_dims);
337  } else if ( *p != ',' && ! isspace(*p) ) {
338  lwerror("invalid KML representation"); /* (unexpected character %c)", *p); */
339  }
340  }
341 
342  xmlFree(kml_coord);
343 
344  /* TODO: we shouldn't need to clone here */
345  return ptarray_clone_deep(dpa);
346 }
347 
348 
352 static LWGEOM* parse_kml_point(xmlNodePtr xnode, bool *hasz)
353 {
354  POINTARRAY *pa;
355 
356  if (xnode->children == NULL) lwerror("invalid KML representation");
357  pa = parse_kml_coordinates(xnode->children, hasz);
358  if (pa->npoints != 1) lwerror("invalid KML representation");
359 
360  return (LWGEOM *) lwpoint_construct(4326, NULL, pa);
361 }
362 
363 
367 static LWGEOM* parse_kml_line(xmlNodePtr xnode, bool *hasz)
368 {
369  POINTARRAY *pa;
370 
371  if (xnode->children == NULL) lwerror("invalid KML representation");
372  pa = parse_kml_coordinates(xnode->children, hasz);
373  if (pa->npoints < 2) lwerror("invalid KML representation");
374 
375  return (LWGEOM *) lwline_construct(4326, NULL, pa);
376 }
377 
378 
382 static LWGEOM* parse_kml_polygon(xmlNodePtr xnode, bool *hasz)
383 {
384  int ring;
385  xmlNodePtr xa, xb;
386  POINTARRAY **ppa = NULL;
387  int outer_rings = 0;
388 
389  for (xa = xnode->children ; xa != NULL ; xa = xa->next)
390  {
391 
392  /* Polygon/outerBoundaryIs */
393  if (xa->type != XML_ELEMENT_NODE) continue;
394  if (!is_kml_namespace(xa, false)) continue;
395  if (strcmp((char *) xa->name, "outerBoundaryIs")) continue;
396 
397  for (xb = xa->children ; xb != NULL ; xb = xb->next)
398  {
399 
400  if (xb->type != XML_ELEMENT_NODE) continue;
401  if (!is_kml_namespace(xb, false)) continue;
402  if (strcmp((char *) xb->name, "LinearRing")) continue;
403 
404  ppa = (POINTARRAY**) lwalloc(sizeof(POINTARRAY*));
405  ppa[0] = parse_kml_coordinates(xb->children, hasz);
406 
407  if (ppa[0]->npoints < 4
408  || (!*hasz && !ptarray_is_closed_2d(ppa[0]))
409  || (*hasz && !ptarray_is_closed_3d(ppa[0])))
410  lwerror("invalid KML representation");
411 
412  outer_rings++;
413  }
414  }
415 
416  if (outer_rings != 1)
417  lwerror("invalid KML representation");
418 
419  for (ring=1, xa = xnode->children ; xa != NULL ; xa = xa->next)
420  {
421 
422  /* Polygon/innerBoundaryIs */
423  if (xa->type != XML_ELEMENT_NODE) continue;
424  if (!is_kml_namespace(xa, false)) continue;
425  if (strcmp((char *) xa->name, "innerBoundaryIs")) continue;
426 
427  for (xb = xa->children ; xb != NULL ; xb = xb->next)
428  {
429 
430  if (xb->type != XML_ELEMENT_NODE) continue;
431  if (!is_kml_namespace(xb, false)) continue;
432  if (strcmp((char *) xb->name, "LinearRing")) continue;
433 
434  ppa = (POINTARRAY**) lwrealloc(ppa, sizeof(POINTARRAY*) * (ring + 1));
435  ppa[ring] = parse_kml_coordinates(xb->children, hasz);
436 
437  if (ppa[ring]->npoints < 4
438  || (!*hasz && !ptarray_is_closed_2d(ppa[ring]))
439  || (*hasz && !ptarray_is_closed_3d(ppa[ring])))
440  lwerror("invalid KML representation");
441 
442  ring++;
443  }
444  }
445 
446  /* Exterior Ring is mandatory */
447  if (ppa == NULL || ppa[0] == NULL) lwerror("invalid KML representation");
448 
449  return (LWGEOM *) lwpoly_construct(4326, NULL, ring, ppa);
450 }
451 
452 
456 static LWGEOM* parse_kml_multi(xmlNodePtr xnode, bool *hasz)
457 {
458  LWGEOM *geom;
459  xmlNodePtr xa;
460 
461  geom = (LWGEOM *)lwcollection_construct_empty(COLLECTIONTYPE, 4326, 1, 0);
462 
463  for (xa = xnode->children ; xa != NULL ; xa = xa->next)
464  {
465 
466  if (xa->type != XML_ELEMENT_NODE) continue;
467  if (!is_kml_namespace(xa, false)) continue;
468 
469  if ( !strcmp((char *) xa->name, "Point")
470  || !strcmp((char *) xa->name, "LineString")
471  || !strcmp((char *) xa->name, "Polygon")
472  || !strcmp((char *) xa->name, "MultiGeometry"))
473  {
474 
475  if (xa->children == NULL) break;
476  geom = (LWGEOM*)lwcollection_add_lwgeom((LWCOLLECTION*)geom, parse_kml(xa, hasz));
477  }
478  }
479 
480  return geom;
481 }
482 
483 
487 static LWGEOM* parse_kml(xmlNodePtr xnode, bool *hasz)
488 {
489  xmlNodePtr xa = xnode;
490 
491  while (xa != NULL && (xa->type != XML_ELEMENT_NODE
492  || !is_kml_namespace(xa, false))) xa = xa->next;
493 
494  if (xa == NULL) lwerror("invalid KML representation");
495 
496  if (!strcmp((char *) xa->name, "Point"))
497  return parse_kml_point(xa, hasz);
498 
499  if (!strcmp((char *) xa->name, "LineString"))
500  return parse_kml_line(xa, hasz);
501 
502  if (!strcmp((char *) xa->name, "Polygon"))
503  return parse_kml_polygon(xa, hasz);
504 
505  if (!strcmp((char *) xa->name, "MultiGeometry"))
506  return parse_kml_multi(xa, hasz);
507 
508  lwerror("invalid KML representation");
509  return NULL; /* Never reach */
510 }
double x
Definition: liblwgeom.h:308
#define KML_NS
Definition: lwgeom_in_kml.c:54
Datum geom_from_kml(PG_FUNCTION_ARGS)
Definition: lwgeom_in_kml.c:62
int npoints
Definition: liblwgeom.h:327
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:678
POINTARRAY * ptarray_construct_empty(char hasz, char hasm, uint32_t maxpoints)
Create a new POINTARRAY with no points.
Definition: ptarray.c:57
void lwgeom_free(LWGEOM *geom)
Definition: lwgeom.c:1006
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:672
static LWGEOM * parse_kml(xmlNodePtr xnode, bool *hasz)
Parse KML.
void lwerror(const char *fmt,...)
Write a notice out to the error handler.
Definition: lwutil.c:67
void lwnotice(const char *fmt,...)
Write a notice out to the notice handler.
Definition: lwutil.c:54
LWGEOM * lwgeom_homogenize(const LWGEOM *geom)
Definition: lwhomogenize.c:196
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:141
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:51
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:646
char * text2cstring(const text *textptr)
POINTARRAY * ptarray_clone_deep(const POINTARRAY *ptarray)
Deep clone a pointarray (also clones serialized pointlist)
Definition: ptarray.c:619
double z
Definition: liblwgeom.h:308
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:183
void lwgeom_add_bbox(LWGEOM *lwgeom)
Compute a bbox if not already computed.
Definition: lwgeom.c:555
void lwgeom_release(LWGEOM *lwgeom)
Free the containing LWGEOM and the associated BOX.
Definition: lwgeom.c:328
uint8_t type
Definition: liblwgeom.h:352
LWPOINT * lwpoint_construct(int srid, GBOX *bbox, POINTARRAY *point)
Definition: lwpoint.c:96
void * lwalloc(size_t size)
Definition: lwutil.c:175
double y
Definition: liblwgeom.h:308
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.
#define COLLECTIONTYPE
Definition: liblwgeom.h:66
This library is the generic geometry handling section of PostGIS.