Coverage Report

Created: 2025-06-13 06:29

/src/gdal/ogr/ogr2kmlgeometry.cpp
Line
Count
Source (jump to first uncovered line)
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
static void MakeKMLCoordinate(char *pszTarget, size_t nTargetLen, double x,
35
                              double y, double z, bool b3D)
36
37
0
{
38
0
    constexpr double EPSILON = 1e-8;
39
40
0
    if (y < -90 || y > 90)
41
0
    {
42
0
        if (y > 90 && y < 90 + EPSILON)
43
0
        {
44
0
            y = 90;
45
0
        }
46
0
        else if (y > -90 - EPSILON && y < -90)
47
0
        {
48
0
            y = -90;
49
0
        }
50
0
        else
51
0
        {
52
0
            static bool bFirstWarning = true;
53
0
            if (bFirstWarning)
54
0
            {
55
0
                CPLError(CE_Failure, CPLE_AppDefined,
56
0
                         "Latitude %f is invalid. Valid range is [-90,90]. "
57
0
                         "This warning will not be issued any more",
58
0
                         y);
59
0
                bFirstWarning = false;
60
0
            }
61
0
        }
62
0
    }
63
64
0
    if (x < -180 || x > 180)
65
0
    {
66
0
        if (x > 180 && x < 180 + EPSILON)
67
0
        {
68
0
            x = 180;
69
0
        }
70
0
        else if (x > -180 - EPSILON && x < -180)
71
0
        {
72
0
            x = -180;
73
0
        }
74
0
        else
75
0
        {
76
0
            static bool bFirstWarning = true;
77
0
            if (bFirstWarning)
78
0
            {
79
0
                CPLError(CE_Warning, CPLE_AppDefined,
80
0
                         "Longitude %f has been modified to fit into "
81
0
                         "range [-180,180]. This warning will not be "
82
0
                         "issued any more",
83
0
                         x);
84
0
                bFirstWarning = false;
85
0
            }
86
87
            // Trash drastically non-sensical values.
88
0
            if (x > 1.0e6 || x < -1.0e6 || std::isnan(x))
89
0
            {
90
0
                static bool bFirstWarning2 = true;
91
0
                if (bFirstWarning2)
92
0
                {
93
0
                    CPLError(CE_Failure, CPLE_AppDefined,
94
0
                             "Longitude %lf is unreasonable.  Setting to 0."
95
0
                             "This warning will not be issued any more",
96
0
                             x);
97
0
                    bFirstWarning2 = false;
98
0
                }
99
0
                x = 0.0;
100
0
            }
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
0
        }
107
0
    }
108
109
0
    OGRMakeWktCoordinate(pszTarget, x, y, z, b3D ? 3 : 2);
110
0
    while (*pszTarget != '\0')
111
0
    {
112
0
        if (*pszTarget == ' ')
113
0
            *pszTarget = ',';
114
0
        pszTarget++;
115
0
        nTargetLen--;
116
0
    }
117
118
0
    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
0
}
151
152
/************************************************************************/
153
/*                            _GrowBuffer()                             */
154
/************************************************************************/
155
156
static void _GrowBuffer(size_t nNeeded, char **ppszText, size_t *pnMaxLength)
157
158
0
{
159
0
    if (nNeeded + 1 >= *pnMaxLength)
160
0
    {
161
0
        *pnMaxLength = std::max(*pnMaxLength * 2, nNeeded + 1);
162
0
        *ppszText = static_cast<char *>(CPLRealloc(*ppszText, *pnMaxLength));
163
0
    }
164
0
}
165
166
/************************************************************************/
167
/*                            AppendString()                            */
168
/************************************************************************/
169
170
static void AppendString(char **ppszText, size_t *pnLength, size_t *pnMaxLength,
171
                         const char *pszTextToAppend)
172
173
0
{
174
0
    _GrowBuffer(*pnLength + strlen(pszTextToAppend) + 1, ppszText, pnMaxLength);
175
176
0
    strcat(*ppszText + *pnLength, pszTextToAppend);
177
0
    *pnLength += strlen(*ppszText + *pnLength);
178
0
}
179
180
/************************************************************************/
181
/*                        AppendCoordinateList()                        */
182
/************************************************************************/
183
184
static void AppendCoordinateList(OGRLineString *poLine, char **ppszText,
185
                                 size_t *pnLength, size_t *pnMaxLength)
186
187
0
{
188
0
    char szCoordinate[256] = {0};
189
0
    const bool b3D = CPL_TO_BOOL(wkbHasZ(poLine->getGeometryType()));
190
191
0
    *pnLength += strlen(*ppszText + *pnLength);
192
0
    _GrowBuffer(*pnLength + 20, ppszText, pnMaxLength);
193
194
0
    strcat(*ppszText + *pnLength, "<coordinates>");
195
0
    *pnLength += strlen(*ppszText + *pnLength);
196
197
0
    for (int iPoint = 0; iPoint < poLine->getNumPoints(); iPoint++)
198
0
    {
199
0
        MakeKMLCoordinate(szCoordinate, sizeof(szCoordinate),
200
0
                          poLine->getX(iPoint), poLine->getY(iPoint),
201
0
                          poLine->getZ(iPoint), b3D);
202
0
        _GrowBuffer(*pnLength + strlen(szCoordinate) + 1, ppszText,
203
0
                    pnMaxLength);
204
205
0
        if (iPoint != 0)
206
0
            strcat(*ppszText + *pnLength, " ");
207
208
0
        strcat(*ppszText + *pnLength, szCoordinate);
209
0
        *pnLength += strlen(*ppszText + *pnLength);
210
0
    }
211
212
0
    _GrowBuffer(*pnLength + 20, ppszText, pnMaxLength);
213
0
    strcat(*ppszText + *pnLength, "</coordinates>");
214
0
    *pnLength += strlen(*ppszText + *pnLength);
215
0
}
216
217
/************************************************************************/
218
/*                       OGR2KMLGeometryAppend()                        */
219
/************************************************************************/
220
221
static bool OGR2KMLGeometryAppend(OGRGeometry *poGeometry, char **ppszText,
222
                                  size_t *pnLength, size_t *pnMaxLength,
223
                                  char *szAltitudeMode)
224
225
0
{
226
    /* -------------------------------------------------------------------- */
227
    /*      2D Point                                                        */
228
    /* -------------------------------------------------------------------- */
229
0
    if (poGeometry->getGeometryType() == wkbPoint)
230
0
    {
231
0
        OGRPoint *poPoint = poGeometry->toPoint();
232
233
0
        if (poPoint->getCoordinateDimension() == 0)
234
0
        {
235
0
            _GrowBuffer(*pnLength + 10, ppszText, pnMaxLength);
236
0
            strcat(*ppszText + *pnLength, "<Point/>");
237
0
            *pnLength += strlen(*ppszText + *pnLength);
238
0
        }
239
0
        else
240
0
        {
241
0
            char szCoordinate[256] = {0};
242
0
            MakeKMLCoordinate(szCoordinate, sizeof(szCoordinate),
243
0
                              poPoint->getX(), poPoint->getY(), 0.0, false);
244
245
0
            _GrowBuffer(*pnLength + strlen(szCoordinate) + 60, ppszText,
246
0
                        pnMaxLength);
247
248
0
            snprintf(*ppszText + *pnLength, *pnMaxLength - *pnLength,
249
0
                     "<Point><coordinates>%s</coordinates></Point>",
250
0
                     szCoordinate);
251
252
0
            *pnLength += strlen(*ppszText + *pnLength);
253
0
        }
254
0
    }
255
    /* -------------------------------------------------------------------- */
256
    /*      3D Point                                                        */
257
    /* -------------------------------------------------------------------- */
258
0
    else if (poGeometry->getGeometryType() == wkbPoint25D)
259
0
    {
260
0
        char szCoordinate[256] = {0};
261
0
        OGRPoint *poPoint = poGeometry->toPoint();
262
263
0
        MakeKMLCoordinate(szCoordinate, sizeof(szCoordinate), poPoint->getX(),
264
0
                          poPoint->getY(), poPoint->getZ(), true);
265
266
0
        if (nullptr == szAltitudeMode)
267
0
        {
268
0
            _GrowBuffer(*pnLength + strlen(szCoordinate) + 70, ppszText,
269
0
                        pnMaxLength);
270
271
0
            snprintf(*ppszText + *pnLength, *pnMaxLength - *pnLength,
272
0
                     "<Point><coordinates>%s</coordinates></Point>",
273
0
                     szCoordinate);
274
0
        }
275
0
        else
276
0
        {
277
0
            _GrowBuffer(*pnLength + strlen(szCoordinate) +
278
0
                            strlen(szAltitudeMode) + 70,
279
0
                        ppszText, pnMaxLength);
280
281
0
            snprintf(*ppszText + *pnLength, *pnMaxLength - *pnLength,
282
0
                     "<Point>%s<coordinates>%s</coordinates></Point>",
283
0
                     szAltitudeMode, szCoordinate);
284
0
        }
285
286
0
        *pnLength += strlen(*ppszText + *pnLength);
287
0
    }
288
    /* -------------------------------------------------------------------- */
289
    /*      LineString and LinearRing                                       */
290
    /* -------------------------------------------------------------------- */
291
0
    else if (poGeometry->getGeometryType() == wkbLineString ||
292
0
             poGeometry->getGeometryType() == wkbLineString25D)
293
0
    {
294
0
        const bool bRing = EQUAL(poGeometry->getGeometryName(), "LINEARRING");
295
296
0
        if (bRing)
297
0
            AppendString(ppszText, pnLength, pnMaxLength, "<LinearRing>");
298
0
        else
299
0
            AppendString(ppszText, pnLength, pnMaxLength, "<LineString>");
300
301
0
        if (nullptr != szAltitudeMode)
302
0
        {
303
0
            AppendString(ppszText, pnLength, pnMaxLength, szAltitudeMode);
304
0
        }
305
306
0
        AppendCoordinateList(poGeometry->toLineString(), ppszText, pnLength,
307
0
                             pnMaxLength);
308
309
0
        if (bRing)
310
0
            AppendString(ppszText, pnLength, pnMaxLength, "</LinearRing>");
311
0
        else
312
0
            AppendString(ppszText, pnLength, pnMaxLength, "</LineString>");
313
0
    }
314
315
    /* -------------------------------------------------------------------- */
316
    /*      Polygon                                                         */
317
    /* -------------------------------------------------------------------- */
318
0
    else if (poGeometry->getGeometryType() == wkbPolygon ||
319
0
             poGeometry->getGeometryType() == wkbPolygon25D)
320
0
    {
321
0
        OGRPolygon *poPolygon = poGeometry->toPolygon();
322
323
0
        AppendString(ppszText, pnLength, pnMaxLength, "<Polygon>");
324
325
0
        if (nullptr != szAltitudeMode)
326
0
        {
327
0
            AppendString(ppszText, pnLength, pnMaxLength, szAltitudeMode);
328
0
        }
329
330
0
        if (poPolygon->getExteriorRing() != nullptr)
331
0
        {
332
0
            AppendString(ppszText, pnLength, pnMaxLength, "<outerBoundaryIs>");
333
334
0
            if (!OGR2KMLGeometryAppend(poPolygon->getExteriorRing(), ppszText,
335
0
                                       pnLength, pnMaxLength, szAltitudeMode))
336
0
            {
337
0
                return false;
338
0
            }
339
0
            AppendString(ppszText, pnLength, pnMaxLength, "</outerBoundaryIs>");
340
0
        }
341
342
0
        for (int iRing = 0; iRing < poPolygon->getNumInteriorRings(); iRing++)
343
0
        {
344
0
            OGRLinearRing *poRing = poPolygon->getInteriorRing(iRing);
345
346
0
            AppendString(ppszText, pnLength, pnMaxLength, "<innerBoundaryIs>");
347
348
0
            if (!OGR2KMLGeometryAppend(poRing, ppszText, pnLength, pnMaxLength,
349
0
                                       szAltitudeMode))
350
0
            {
351
0
                return false;
352
0
            }
353
0
            AppendString(ppszText, pnLength, pnMaxLength, "</innerBoundaryIs>");
354
0
        }
355
356
0
        AppendString(ppszText, pnLength, pnMaxLength, "</Polygon>");
357
0
    }
358
359
    /* -------------------------------------------------------------------- */
360
    /*      MultiPolygon                                                    */
361
    /* -------------------------------------------------------------------- */
362
0
    else if (wkbFlatten(poGeometry->getGeometryType()) == wkbMultiPolygon ||
363
0
             wkbFlatten(poGeometry->getGeometryType()) == wkbMultiLineString ||
364
0
             wkbFlatten(poGeometry->getGeometryType()) == wkbMultiPoint ||
365
0
             wkbFlatten(poGeometry->getGeometryType()) == wkbGeometryCollection)
366
0
    {
367
0
        OGRGeometryCollection *poGC = poGeometry->toGeometryCollection();
368
369
0
        AppendString(ppszText, pnLength, pnMaxLength, "<MultiGeometry>");
370
371
        // XXX - mloskot
372
        // if (NULL != szAltitudeMode)
373
        //{
374
        //    AppendString( ppszText, pnLength, pnMaxLength, szAltitudeMode);
375
        //}
376
377
0
        for (auto &&poMember : poGC)
378
0
        {
379
0
            if (!OGR2KMLGeometryAppend(poMember, ppszText, pnLength,
380
0
                                       pnMaxLength, szAltitudeMode))
381
0
            {
382
0
                return false;
383
0
            }
384
0
        }
385
386
0
        AppendString(ppszText, pnLength, pnMaxLength, "</MultiGeometry>");
387
0
    }
388
0
    else
389
0
    {
390
0
        return false;
391
0
    }
392
393
0
    return true;
394
0
}
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
char *OGR_G_ExportToKML(OGRGeometryH hGeometry, const char *pszAltitudeMode)
471
0
{
472
0
    char szAltitudeMode[128];
473
474
    // TODO - mloskot: Should we use VALIDATE_POINTER1 here?
475
0
    if (hGeometry == nullptr)
476
0
        return CPLStrdup("");
477
478
0
    size_t nMaxLength = 1;
479
0
    char *pszText = static_cast<char *>(CPLMalloc(nMaxLength));
480
0
    pszText[0] = '\0';
481
482
0
    if (nullptr != pszAltitudeMode && strlen(pszAltitudeMode) < 128 - (29 + 1))
483
0
    {
484
0
        snprintf(szAltitudeMode, sizeof(szAltitudeMode),
485
0
                 "<altitudeMode>%s</altitudeMode>", pszAltitudeMode);
486
0
    }
487
0
    else
488
0
    {
489
0
        szAltitudeMode[0] = 0;
490
0
    }
491
492
0
    size_t nLength = 0;
493
0
    if (!OGR2KMLGeometryAppend(OGRGeometry::FromHandle(hGeometry), &pszText,
494
0
                               &nLength, &nMaxLength, szAltitudeMode))
495
0
    {
496
0
        CPLFree(pszText);
497
0
        return nullptr;
498
0
    }
499
500
0
    return pszText;
501
0
}
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