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