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