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