Line data Source code
1 : /******************************************************************************
2 : *
3 : * Project: KML Driver
4 : * Purpose: Implementation of OGR -> KML geometries writer.
5 : * Author: Christopher Condit, condit@sdsc.edu
6 : *
7 : ******************************************************************************
8 : * Copyright (c) 2006, Christopher Condit
9 : * Copyright (c) 2007-2010, Even Rouault <even dot rouault at spatialys.com>
10 : *
11 : * SPDX-License-Identifier: MIT
12 : ****************************************************************************/
13 :
14 : #include "cpl_port.h"
15 : #include "ogr_api.h"
16 :
17 : #include <stddef.h>
18 : #include <stdio.h>
19 : #include <string.h>
20 : #include <algorithm>
21 : #include <cmath>
22 :
23 : #include "cpl_conv.h"
24 : #include "cpl_error.h"
25 : #include "cpl_minixml.h"
26 : #include "ogr_core.h"
27 : #include "ogr_geometry.h"
28 : #include "ogr_p.h"
29 :
30 : /************************************************************************/
31 : /* MakeKMLCoordinate() */
32 : /************************************************************************/
33 :
34 270 : static void MakeKMLCoordinate(char *pszTarget, size_t nTargetLen, double x,
35 : double y, double z, bool b3D)
36 :
37 : {
38 270 : constexpr double EPSILON = 1e-8;
39 :
40 270 : if (y < -90 || y > 90)
41 : {
42 0 : if (y > 90 && y < 90 + EPSILON)
43 : {
44 0 : y = 90;
45 : }
46 0 : else if (y > -90 - EPSILON && y < -90)
47 : {
48 0 : y = -90;
49 : }
50 : else
51 : {
52 : static bool bFirstWarning = true;
53 0 : if (bFirstWarning)
54 : {
55 0 : CPLError(CE_Failure, CPLE_AppDefined,
56 : "Latitude %f is invalid. Valid range is [-90,90]. "
57 : "This warning will not be issued any more",
58 : y);
59 0 : bFirstWarning = false;
60 : }
61 : }
62 : }
63 :
64 270 : if (x < -180 || x > 180)
65 : {
66 0 : if (x > 180 && x < 180 + EPSILON)
67 : {
68 0 : x = 180;
69 : }
70 0 : else if (x > -180 - EPSILON && x < -180)
71 : {
72 0 : x = -180;
73 : }
74 : else
75 : {
76 : static bool bFirstWarning = true;
77 0 : if (bFirstWarning)
78 : {
79 0 : CPLError(CE_Warning, CPLE_AppDefined,
80 : "Longitude %f has been modified to fit into "
81 : "range [-180,180]. This warning will not be "
82 : "issued any more",
83 : x);
84 0 : bFirstWarning = false;
85 : }
86 :
87 : // Trash drastically non-sensical values.
88 0 : if (x > 1.0e6 || x < -1.0e6 || std::isnan(x))
89 : {
90 : static bool bFirstWarning2 = true;
91 0 : if (bFirstWarning2)
92 : {
93 0 : CPLError(CE_Failure, CPLE_AppDefined,
94 : "Longitude %lf is unreasonable. Setting to 0."
95 : "This warning will not be issued any more",
96 : x);
97 0 : bFirstWarning2 = false;
98 : }
99 0 : x = 0.0;
100 : }
101 :
102 0 : if (x > 180)
103 0 : x -= (static_cast<int>((x + 180) / 360) * 360);
104 0 : else if (x < -180)
105 0 : x += (static_cast<int>(180 - x) / 360) * 360;
106 : }
107 : }
108 :
109 270 : OGRMakeWktCoordinate(pszTarget, x, y, z, b3D ? 3 : 2);
110 3842 : while (*pszTarget != '\0')
111 : {
112 3572 : if (*pszTarget == ' ')
113 346 : *pszTarget = ',';
114 3572 : pszTarget++;
115 3572 : nTargetLen--;
116 : }
117 :
118 270 : CPL_IGNORE_RET_VAL(nTargetLen);
119 :
120 : #if 0
121 : if( !b3D )
122 : {
123 : if( x == static_cast<int>(x) && y == static_cast<int>(y) )
124 : snprintf( pszTarget, nTargetLen, "%d,%d",
125 : static_cast<int>(x), static_cast<int>(y) );
126 : else if( fabs(x) < 370 && fabs(y) < 370 )
127 : CPLsnprintf( pszTarget, nTargetLen, "%.16g,%.16g", x, y );
128 : else if( fabs(x) > 100000000.0 || fabs(y) > 100000000.0 )
129 : CPLsnprintf( pszTarget, nTargetLen, "%.16g,%.16g", x, y );
130 : else
131 : CPLsnprintf( pszTarget, nTargetLen, "%.3f,%.3f", x, y );
132 : }
133 : else
134 : {
135 : if( x == static_cast<int>(x) &&
136 : y == static_cast<int>(y) &&
137 : z == static_cast<int>(z) )
138 : snprintf( pszTarget, nTargetLen, "%d,%d,%d",
139 : static_cast<int>(x), static_cast<int>(y),
140 : static_cast<int>(z) );
141 : else if( fabs(x) < 370 && fabs(y) < 370 )
142 : CPLsnprintf( pszTarget, nTargetLen, "%.16g,%.16g,%.16g", x, y, z );
143 : else if( fabs(x) > 100000000.0 || fabs(y) > 100000000.0
144 : || fabs(z) > 100000000.0 )
145 : CPLsnprintf( pszTarget, nTargetLen, "%.16g,%.16g,%.16g", x, y, z );
146 : else
147 : CPLsnprintf( pszTarget, nTargetLen, "%.3f,%.3f,%.3f", x, y, z );
148 : }
149 : #endif
150 270 : }
151 :
152 : /************************************************************************/
153 : /* _GrowBuffer() */
154 : /************************************************************************/
155 :
156 842 : static void _GrowBuffer(size_t nNeeded, char **ppszText, size_t *pnMaxLength)
157 :
158 : {
159 842 : if (nNeeded + 1 >= *pnMaxLength)
160 : {
161 359 : *pnMaxLength = std::max(*pnMaxLength * 2, nNeeded + 1);
162 359 : *ppszText = static_cast<char *>(CPLRealloc(*ppszText, *pnMaxLength));
163 : }
164 842 : }
165 :
166 : /************************************************************************/
167 : /* AppendString() */
168 : /************************************************************************/
169 :
170 410 : static void AppendString(char **ppszText, size_t *pnLength, size_t *pnMaxLength,
171 : const char *pszTextToAppend)
172 :
173 : {
174 410 : _GrowBuffer(*pnLength + strlen(pszTextToAppend) + 1, ppszText, pnMaxLength);
175 :
176 410 : strcat(*ppszText + *pnLength, pszTextToAppend);
177 410 : *pnLength += strlen(*ppszText + *pnLength);
178 410 : }
179 :
180 : /************************************************************************/
181 : /* AppendCoordinateList() */
182 : /************************************************************************/
183 :
184 81 : static void AppendCoordinateList(OGRLineString *poLine, char **ppszText,
185 : size_t *pnLength, size_t *pnMaxLength)
186 :
187 : {
188 81 : char szCoordinate[256] = {0};
189 81 : const bool b3D = CPL_TO_BOOL(wkbHasZ(poLine->getGeometryType()));
190 :
191 81 : *pnLength += strlen(*ppszText + *pnLength);
192 81 : _GrowBuffer(*pnLength + 20, ppszText, pnMaxLength);
193 :
194 81 : strcat(*ppszText + *pnLength, "<coordinates>");
195 81 : *pnLength += strlen(*ppszText + *pnLength);
196 :
197 322 : for (int iPoint = 0; iPoint < poLine->getNumPoints(); iPoint++)
198 : {
199 241 : MakeKMLCoordinate(szCoordinate, sizeof(szCoordinate),
200 : poLine->getX(iPoint), poLine->getY(iPoint),
201 : poLine->getZ(iPoint), b3D);
202 241 : _GrowBuffer(*pnLength + strlen(szCoordinate) + 1, ppszText,
203 : pnMaxLength);
204 :
205 241 : if (iPoint != 0)
206 160 : strcat(*ppszText + *pnLength, " ");
207 :
208 241 : strcat(*ppszText + *pnLength, szCoordinate);
209 241 : *pnLength += strlen(*ppszText + *pnLength);
210 : }
211 :
212 81 : _GrowBuffer(*pnLength + 20, ppszText, pnMaxLength);
213 81 : strcat(*ppszText + *pnLength, "</coordinates>");
214 81 : *pnLength += strlen(*ppszText + *pnLength);
215 81 : }
216 :
217 : /************************************************************************/
218 : /* OGR2KMLGeometryAppend() */
219 : /************************************************************************/
220 :
221 159 : static bool OGR2KMLGeometryAppend(OGRGeometry *poGeometry, char **ppszText,
222 : size_t *pnLength, size_t *pnMaxLength,
223 : char *szAltitudeMode)
224 :
225 : {
226 : /* -------------------------------------------------------------------- */
227 : /* 2D Point */
228 : /* -------------------------------------------------------------------- */
229 159 : if (poGeometry->getGeometryType() == wkbPoint)
230 : {
231 18 : OGRPoint *poPoint = poGeometry->toPoint();
232 :
233 18 : if (poPoint->getCoordinateDimension() == 0)
234 : {
235 0 : _GrowBuffer(*pnLength + 10, ppszText, pnMaxLength);
236 0 : strcat(*ppszText + *pnLength, "<Point/>");
237 0 : *pnLength += strlen(*ppszText + *pnLength);
238 : }
239 : else
240 : {
241 18 : char szCoordinate[256] = {0};
242 18 : MakeKMLCoordinate(szCoordinate, sizeof(szCoordinate),
243 : poPoint->getX(), poPoint->getY(), 0.0, false);
244 :
245 18 : _GrowBuffer(*pnLength + strlen(szCoordinate) + 60, ppszText,
246 : pnMaxLength);
247 :
248 18 : snprintf(*ppszText + *pnLength, *pnMaxLength - *pnLength,
249 : "<Point><coordinates>%s</coordinates></Point>",
250 : szCoordinate);
251 :
252 18 : *pnLength += strlen(*ppszText + *pnLength);
253 : }
254 : }
255 : /* -------------------------------------------------------------------- */
256 : /* 3D Point */
257 : /* -------------------------------------------------------------------- */
258 141 : else if (poGeometry->getGeometryType() == wkbPoint25D)
259 : {
260 11 : char szCoordinate[256] = {0};
261 11 : OGRPoint *poPoint = poGeometry->toPoint();
262 :
263 11 : MakeKMLCoordinate(szCoordinate, sizeof(szCoordinate), poPoint->getX(),
264 : poPoint->getY(), poPoint->getZ(), true);
265 :
266 11 : if (nullptr == szAltitudeMode)
267 : {
268 0 : _GrowBuffer(*pnLength + strlen(szCoordinate) + 70, ppszText,
269 : pnMaxLength);
270 :
271 0 : snprintf(*ppszText + *pnLength, *pnMaxLength - *pnLength,
272 : "<Point><coordinates>%s</coordinates></Point>",
273 : szCoordinate);
274 : }
275 : else
276 : {
277 11 : _GrowBuffer(*pnLength + strlen(szCoordinate) +
278 11 : strlen(szAltitudeMode) + 70,
279 : ppszText, pnMaxLength);
280 :
281 11 : snprintf(*ppszText + *pnLength, *pnMaxLength - *pnLength,
282 : "<Point>%s<coordinates>%s</coordinates></Point>",
283 : szAltitudeMode, szCoordinate);
284 : }
285 :
286 11 : *pnLength += strlen(*ppszText + *pnLength);
287 : }
288 : /* -------------------------------------------------------------------- */
289 : /* LineString and LinearRing */
290 : /* -------------------------------------------------------------------- */
291 198 : else if (poGeometry->getGeometryType() == wkbLineString ||
292 68 : poGeometry->getGeometryType() == wkbLineString25D)
293 : {
294 81 : const bool bRing = EQUAL(poGeometry->getGeometryName(), "LINEARRING");
295 :
296 81 : if (bRing)
297 24 : AppendString(ppszText, pnLength, pnMaxLength, "<LinearRing>");
298 : else
299 57 : AppendString(ppszText, pnLength, pnMaxLength, "<LineString>");
300 :
301 81 : if (nullptr != szAltitudeMode)
302 : {
303 81 : AppendString(ppszText, pnLength, pnMaxLength, szAltitudeMode);
304 : }
305 :
306 81 : AppendCoordinateList(poGeometry->toLineString(), ppszText, pnLength,
307 : pnMaxLength);
308 :
309 81 : if (bRing)
310 24 : AppendString(ppszText, pnLength, pnMaxLength, "</LinearRing>");
311 : else
312 57 : AppendString(ppszText, pnLength, pnMaxLength, "</LineString>");
313 : }
314 :
315 : /* -------------------------------------------------------------------- */
316 : /* Polygon */
317 : /* -------------------------------------------------------------------- */
318 86 : else if (poGeometry->getGeometryType() == wkbPolygon ||
319 37 : poGeometry->getGeometryType() == wkbPolygon25D)
320 : {
321 21 : OGRPolygon *poPolygon = poGeometry->toPolygon();
322 :
323 21 : AppendString(ppszText, pnLength, pnMaxLength, "<Polygon>");
324 :
325 21 : if (nullptr != szAltitudeMode)
326 : {
327 21 : AppendString(ppszText, pnLength, pnMaxLength, szAltitudeMode);
328 : }
329 :
330 21 : if (poPolygon->getExteriorRing() != nullptr)
331 : {
332 21 : AppendString(ppszText, pnLength, pnMaxLength, "<outerBoundaryIs>");
333 :
334 21 : if (!OGR2KMLGeometryAppend(poPolygon->getExteriorRing(), ppszText,
335 : pnLength, pnMaxLength, szAltitudeMode))
336 : {
337 0 : return false;
338 : }
339 21 : AppendString(ppszText, pnLength, pnMaxLength, "</outerBoundaryIs>");
340 : }
341 :
342 24 : for (int iRing = 0; iRing < poPolygon->getNumInteriorRings(); iRing++)
343 : {
344 3 : OGRLinearRing *poRing = poPolygon->getInteriorRing(iRing);
345 :
346 3 : AppendString(ppszText, pnLength, pnMaxLength, "<innerBoundaryIs>");
347 :
348 3 : if (!OGR2KMLGeometryAppend(poRing, ppszText, pnLength, pnMaxLength,
349 : szAltitudeMode))
350 : {
351 0 : return false;
352 : }
353 3 : AppendString(ppszText, pnLength, pnMaxLength, "</innerBoundaryIs>");
354 : }
355 :
356 21 : AppendString(ppszText, pnLength, pnMaxLength, "</Polygon>");
357 : }
358 :
359 : /* -------------------------------------------------------------------- */
360 : /* MultiPolygon */
361 : /* -------------------------------------------------------------------- */
362 28 : else if (wkbFlatten(poGeometry->getGeometryType()) == wkbMultiPolygon ||
363 21 : wkbFlatten(poGeometry->getGeometryType()) == wkbMultiLineString ||
364 56 : wkbFlatten(poGeometry->getGeometryType()) == wkbMultiPoint ||
365 7 : wkbFlatten(poGeometry->getGeometryType()) == wkbGeometryCollection)
366 : {
367 28 : OGRGeometryCollection *poGC = poGeometry->toGeometryCollection();
368 :
369 28 : AppendString(ppszText, pnLength, pnMaxLength, "<MultiGeometry>");
370 :
371 : // XXX - mloskot
372 : // if (NULL != szAltitudeMode)
373 : //{
374 : // AppendString( ppszText, pnLength, pnMaxLength, szAltitudeMode);
375 : //}
376 :
377 72 : for (auto &&poMember : poGC)
378 : {
379 44 : if (!OGR2KMLGeometryAppend(poMember, ppszText, pnLength,
380 : pnMaxLength, szAltitudeMode))
381 : {
382 0 : return false;
383 : }
384 : }
385 :
386 28 : AppendString(ppszText, pnLength, pnMaxLength, "</MultiGeometry>");
387 : }
388 : else
389 : {
390 0 : return false;
391 : }
392 :
393 159 : return true;
394 : }
395 :
396 : /************************************************************************/
397 : /* OGR_G_ExportEnvelopeToKMLTree() */
398 : /* */
399 : /* Export the envelope of a geometry as a KML:Box. */
400 : /************************************************************************/
401 :
402 : #if 0
403 : CPLXMLNode* OGR_G_ExportEnvelopeToKMLTree( OGRGeometryH hGeometry )
404 : {
405 : VALIDATE_POINTER1( hGeometry, "OGR_G_ExportEnvelopeToKMLTree", NULL );
406 :
407 : OGREnvelope sEnvelope;
408 :
409 : memset( &sEnvelope, 0, sizeof(sEnvelope) );
410 : ((OGRGeometry*)(hGeometry))->getEnvelope( &sEnvelope );
411 :
412 : if( sEnvelope.MinX == 0 && sEnvelope.MaxX == 0
413 : && sEnvelope.MaxX == 0 && sEnvelope.MaxY == 0 )
414 : {
415 : /* There is apparently a special way of representing a null box
416 : geometry ... we should use it here eventually. */
417 :
418 : return NULL;
419 : }
420 :
421 : CPLXMLNode* psBox = CPLCreateXMLNode( NULL, CXT_Element, "Box" );
422 :
423 : /* -------------------------------------------------------------------- */
424 : /* Add minxy coordinate. */
425 : /* -------------------------------------------------------------------- */
426 : CPLXMLNode* psCoord = CPLCreateXMLNode( psBox, CXT_Element, "coord" );
427 :
428 : char szCoordinate[256] = { 0 };
429 : MakeKMLCoordinate( szCoordinate, sEnvelope.MinX, sEnvelope.MinY, 0.0,
430 : false );
431 : char* pszY = strstr(szCoordinate,",") + 1;
432 : pszY[-1] = '\0';
433 :
434 : CPLCreateXMLElementAndValue( psCoord, "X", szCoordinate );
435 : CPLCreateXMLElementAndValue( psCoord, "Y", pszY );
436 :
437 : /* -------------------------------------------------------------------- */
438 : /* Add maxxy coordinate. */
439 : /* -------------------------------------------------------------------- */
440 : psCoord = CPLCreateXMLNode( psBox, CXT_Element, "coord" );
441 :
442 : MakeKMLCoordinate( szCoordinate, sEnvelope.MaxX, sEnvelope.MaxY, 0.0,
443 : false );
444 : pszY = strstr(szCoordinate,",") + 1;
445 : pszY[-1] = '\0';
446 :
447 : CPLCreateXMLElementAndValue( psCoord, "X", szCoordinate );
448 : CPLCreateXMLElementAndValue( psCoord, "Y", pszY );
449 :
450 : return psBox;
451 : }
452 : #endif
453 :
454 : /************************************************************************/
455 : /* OGR_G_ExportToKML() */
456 : /************************************************************************/
457 :
458 : /**
459 : * \brief Convert a geometry into KML format.
460 : *
461 : * The returned string should be freed with CPLFree() when no longer required.
462 : *
463 : * This method is the same as the C++ method OGRGeometry::exportToKML().
464 : *
465 : * @param hGeometry handle to the geometry.
466 : * @param pszAltitudeMode value to write in altitudeMode element, or NULL.
467 : * @return A KML fragment or NULL in case of error.
468 : */
469 :
470 91 : char *OGR_G_ExportToKML(OGRGeometryH hGeometry, const char *pszAltitudeMode)
471 : {
472 : char szAltitudeMode[128];
473 :
474 : // TODO - mloskot: Should we use VALIDATE_POINTER1 here?
475 91 : if (hGeometry == nullptr)
476 0 : return CPLStrdup("");
477 :
478 91 : size_t nMaxLength = 1;
479 91 : char *pszText = static_cast<char *>(CPLMalloc(nMaxLength));
480 91 : pszText[0] = '\0';
481 :
482 91 : if (nullptr != pszAltitudeMode && strlen(pszAltitudeMode) < 128 - (29 + 1))
483 : {
484 0 : snprintf(szAltitudeMode, sizeof(szAltitudeMode),
485 : "<altitudeMode>%s</altitudeMode>", pszAltitudeMode);
486 : }
487 : else
488 : {
489 91 : szAltitudeMode[0] = 0;
490 : }
491 :
492 91 : size_t nLength = 0;
493 91 : if (!OGR2KMLGeometryAppend(OGRGeometry::FromHandle(hGeometry), &pszText,
494 : &nLength, &nMaxLength, szAltitudeMode))
495 : {
496 0 : CPLFree(pszText);
497 0 : return nullptr;
498 : }
499 :
500 91 : return pszText;
501 : }
502 :
503 : /************************************************************************/
504 : /* OGR_G_ExportToKMLTree() */
505 : /************************************************************************/
506 :
507 : #if 0
508 : CPLXMLNode *OGR_G_ExportToKMLTree( OGRGeometryH hGeometry )
509 : {
510 : // TODO - mloskot: If passed geometry is null the pszText is non-null,
511 : // so the condition below is false.
512 : char *pszText = OGR_G_ExportToKML( hGeometry, NULL );
513 : if( pszText == NULL )
514 : return NULL;
515 :
516 : CPLXMLNode *psTree = CPLParseXMLString( pszText );
517 :
518 : CPLFree( pszText );
519 :
520 : return psTree;
521 : }
522 : #endif
|