Coverage Report

Created: 2026-04-01 06:20

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/gdal/ogr/ogr2kmlgeometry.cpp
Line
Count
Source
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 bool 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
            CPLError(CE_Failure, CPLE_AppDefined,
53
0
                     "Latitude %f is invalid. Valid range is [-90,90].", y);
54
0
            return false;
55
0
        }
56
0
    }
57
58
0
    if (x < -180 || x > 180)
59
0
    {
60
0
        if (x > 180 && x < 180 + EPSILON)
61
0
        {
62
0
            x = 180;
63
0
        }
64
0
        else if (x > -180 - EPSILON && x < -180)
65
0
        {
66
0
            x = -180;
67
0
        }
68
0
        else
69
0
        {
70
0
            static bool bFirstWarning = true;
71
0
            if (bFirstWarning)
72
0
            {
73
0
                CPLError(CE_Warning, CPLE_AppDefined,
74
0
                         "Longitude %f has been modified to fit into "
75
0
                         "range [-180,180]. This warning will not be "
76
0
                         "issued any more",
77
0
                         x);
78
0
                bFirstWarning = false;
79
0
            }
80
81
            // Trash drastically non-sensical values.
82
0
            if (x > 1.0e6 || x < -1.0e6 || std::isnan(x))
83
0
            {
84
0
                CPLError(CE_Failure, CPLE_AppDefined,
85
0
                         "Longitude %lf is unreasonable.", x);
86
0
                return false;
87
0
            }
88
89
0
            if (x > 180)
90
0
                x -= (static_cast<int>((x + 180) / 360) * 360);
91
0
            else if (x < -180)
92
0
                x += (static_cast<int>(180 - x) / 360) * 360;
93
0
        }
94
0
    }
95
96
0
    OGRMakeWktCoordinate(pszTarget, x, y, z, b3D ? 3 : 2);
97
0
    while (*pszTarget != '\0')
98
0
    {
99
0
        if (*pszTarget == ' ')
100
0
            *pszTarget = ',';
101
0
        pszTarget++;
102
0
    }
103
104
0
    return true;
105
0
}
106
107
/************************************************************************/
108
/*                            _GrowBuffer()                             */
109
/************************************************************************/
110
111
static void _GrowBuffer(size_t nNeeded, char **ppszText, size_t *pnMaxLength)
112
113
0
{
114
0
    if (nNeeded + 1 >= *pnMaxLength)
115
0
    {
116
0
        *pnMaxLength = std::max(*pnMaxLength * 2, nNeeded + 1);
117
0
        *ppszText = static_cast<char *>(CPLRealloc(*ppszText, *pnMaxLength));
118
0
    }
119
0
}
120
121
/************************************************************************/
122
/*                            AppendString()                            */
123
/************************************************************************/
124
125
static void AppendString(char **ppszText, size_t *pnLength, size_t *pnMaxLength,
126
                         const char *pszTextToAppend)
127
128
0
{
129
0
    _GrowBuffer(*pnLength + strlen(pszTextToAppend) + 1, ppszText, pnMaxLength);
130
131
0
    strcat(*ppszText + *pnLength, pszTextToAppend);
132
0
    *pnLength += strlen(*ppszText + *pnLength);
133
0
}
134
135
/************************************************************************/
136
/*                        AppendCoordinateList()                        */
137
/************************************************************************/
138
139
static bool AppendCoordinateList(const OGRLineString *poLine, char **ppszText,
140
                                 size_t *pnLength, size_t *pnMaxLength)
141
142
0
{
143
0
    char szCoordinate[256] = {0};
144
0
    const bool b3D = CPL_TO_BOOL(wkbHasZ(poLine->getGeometryType()));
145
146
0
    AppendString(ppszText, pnLength, pnMaxLength, "<coordinates>");
147
148
0
    for (int iPoint = 0; iPoint < poLine->getNumPoints(); iPoint++)
149
0
    {
150
0
        if (!MakeKMLCoordinate(szCoordinate, sizeof(szCoordinate),
151
0
                               poLine->getX(iPoint), poLine->getY(iPoint),
152
0
                               poLine->getZ(iPoint), b3D))
153
0
        {
154
0
            return false;
155
0
        }
156
157
0
        if (iPoint > 0)
158
0
            AppendString(ppszText, pnLength, pnMaxLength, " ");
159
0
        AppendString(ppszText, pnLength, pnMaxLength, szCoordinate);
160
0
    }
161
162
0
    AppendString(ppszText, pnLength, pnMaxLength, "</coordinates>");
163
164
0
    return true;
165
0
}
166
167
/************************************************************************/
168
/*                       OGR2KMLGeometryAppend()                        */
169
/************************************************************************/
170
171
static bool OGR2KMLGeometryAppend(const OGRGeometry *poGeometry,
172
                                  char **ppszText, size_t *pnLength,
173
                                  size_t *pnMaxLength, char *szAltitudeMode)
174
175
0
{
176
0
    const auto eGeomType = poGeometry->getGeometryType();
177
178
    /* -------------------------------------------------------------------- */
179
    /*      2D Point                                                        */
180
    /* -------------------------------------------------------------------- */
181
0
    if (eGeomType == wkbPoint)
182
0
    {
183
0
        const OGRPoint *poPoint = poGeometry->toPoint();
184
185
0
        if (poPoint->IsEmpty())
186
0
        {
187
0
            AppendString(ppszText, pnLength, pnMaxLength, "<Point/>");
188
0
        }
189
0
        else
190
0
        {
191
0
            char szCoordinate[256] = {0};
192
0
            if (!MakeKMLCoordinate(szCoordinate, sizeof(szCoordinate),
193
0
                                   poPoint->getX(), poPoint->getY(), 0.0,
194
0
                                   false))
195
0
            {
196
0
                return false;
197
0
            }
198
199
0
            AppendString(ppszText, pnLength, pnMaxLength,
200
0
                         "<Point><coordinates>");
201
0
            AppendString(ppszText, pnLength, pnMaxLength, szCoordinate);
202
0
            AppendString(ppszText, pnLength, pnMaxLength,
203
0
                         "</coordinates></Point>");
204
0
        }
205
0
    }
206
    /* -------------------------------------------------------------------- */
207
    /*      3D Point                                                        */
208
    /* -------------------------------------------------------------------- */
209
0
    else if (eGeomType == wkbPoint25D)
210
0
    {
211
0
        char szCoordinate[256] = {0};
212
0
        const OGRPoint *poPoint = poGeometry->toPoint();
213
214
0
        if (!MakeKMLCoordinate(szCoordinate, sizeof(szCoordinate),
215
0
                               poPoint->getX(), poPoint->getY(),
216
0
                               poPoint->getZ(), true))
217
0
        {
218
0
            return false;
219
0
        }
220
221
0
        AppendString(ppszText, pnLength, pnMaxLength, "<Point>");
222
0
        if (szAltitudeMode)
223
0
            AppendString(ppszText, pnLength, pnMaxLength, szAltitudeMode);
224
0
        AppendString(ppszText, pnLength, pnMaxLength, "<coordinates>");
225
0
        AppendString(ppszText, pnLength, pnMaxLength, szCoordinate);
226
0
        AppendString(ppszText, pnLength, pnMaxLength, "</coordinates></Point>");
227
0
    }
228
    /* -------------------------------------------------------------------- */
229
    /*      LineString and LinearRing                                       */
230
    /* -------------------------------------------------------------------- */
231
0
    else if (eGeomType == wkbLineString || eGeomType == wkbLineString25D)
232
0
    {
233
0
        const bool bRing = EQUAL(poGeometry->getGeometryName(), "LINEARRING");
234
235
0
        if (bRing)
236
0
            AppendString(ppszText, pnLength, pnMaxLength, "<LinearRing>");
237
0
        else
238
0
            AppendString(ppszText, pnLength, pnMaxLength, "<LineString>");
239
240
0
        if (nullptr != szAltitudeMode)
241
0
        {
242
0
            AppendString(ppszText, pnLength, pnMaxLength, szAltitudeMode);
243
0
        }
244
245
0
        if (!AppendCoordinateList(poGeometry->toLineString(), ppszText,
246
0
                                  pnLength, pnMaxLength))
247
0
        {
248
0
            return false;
249
0
        }
250
251
0
        if (bRing)
252
0
            AppendString(ppszText, pnLength, pnMaxLength, "</LinearRing>");
253
0
        else
254
0
            AppendString(ppszText, pnLength, pnMaxLength, "</LineString>");
255
0
    }
256
257
    /* -------------------------------------------------------------------- */
258
    /*      Polygon                                                         */
259
    /* -------------------------------------------------------------------- */
260
0
    else if (eGeomType == wkbPolygon || eGeomType == wkbPolygon25D)
261
0
    {
262
0
        const OGRPolygon *poPolygon = poGeometry->toPolygon();
263
264
0
        AppendString(ppszText, pnLength, pnMaxLength, "<Polygon>");
265
266
0
        if (nullptr != szAltitudeMode)
267
0
        {
268
0
            AppendString(ppszText, pnLength, pnMaxLength, szAltitudeMode);
269
0
        }
270
271
0
        if (poPolygon->getExteriorRing() != nullptr)
272
0
        {
273
0
            AppendString(ppszText, pnLength, pnMaxLength, "<outerBoundaryIs>");
274
275
0
            if (!OGR2KMLGeometryAppend(poPolygon->getExteriorRing(), ppszText,
276
0
                                       pnLength, pnMaxLength, szAltitudeMode))
277
0
            {
278
0
                return false;
279
0
            }
280
0
            AppendString(ppszText, pnLength, pnMaxLength, "</outerBoundaryIs>");
281
0
        }
282
283
0
        for (int iRing = 0; iRing < poPolygon->getNumInteriorRings(); iRing++)
284
0
        {
285
0
            const OGRLinearRing *poRing = poPolygon->getInteriorRing(iRing);
286
287
0
            AppendString(ppszText, pnLength, pnMaxLength, "<innerBoundaryIs>");
288
289
0
            if (!OGR2KMLGeometryAppend(poRing, ppszText, pnLength, pnMaxLength,
290
0
                                       szAltitudeMode))
291
0
            {
292
0
                return false;
293
0
            }
294
0
            AppendString(ppszText, pnLength, pnMaxLength, "</innerBoundaryIs>");
295
0
        }
296
297
0
        AppendString(ppszText, pnLength, pnMaxLength, "</Polygon>");
298
0
    }
299
300
    /* -------------------------------------------------------------------- */
301
    /*      MultiPolygon                                                    */
302
    /* -------------------------------------------------------------------- */
303
0
    else if (wkbFlatten(eGeomType) == wkbMultiPolygon ||
304
0
             wkbFlatten(eGeomType) == wkbMultiLineString ||
305
0
             wkbFlatten(eGeomType) == wkbMultiPoint ||
306
0
             wkbFlatten(eGeomType) == wkbGeometryCollection)
307
0
    {
308
0
        const OGRGeometryCollection *poGC = poGeometry->toGeometryCollection();
309
310
0
        AppendString(ppszText, pnLength, pnMaxLength, "<MultiGeometry>");
311
312
0
        for (const auto *poMember : *poGC)
313
0
        {
314
0
            if (!OGR2KMLGeometryAppend(poMember, ppszText, pnLength,
315
0
                                       pnMaxLength, szAltitudeMode))
316
0
            {
317
0
                return false;
318
0
            }
319
0
        }
320
0
        AppendString(ppszText, pnLength, pnMaxLength, "</MultiGeometry>");
321
0
    }
322
0
    else
323
0
    {
324
0
        CPLError(CE_Failure, CPLE_NotSupported,
325
0
                 "Unsupported geometry type in KML: %s",
326
0
                 OGRGeometryTypeToName(eGeomType));
327
0
        return false;
328
0
    }
329
330
0
    return true;
331
0
}
332
333
/************************************************************************/
334
/*                         OGR_G_ExportToKML()                          */
335
/************************************************************************/
336
337
/**
338
 * \brief Convert a geometry into KML format.
339
 *
340
 * The returned string should be freed with CPLFree() when no longer required.
341
 *
342
 * This method is the same as the C++ method OGRGeometry::exportToKML().
343
 *
344
 * @param hGeometry handle to the geometry.
345
 * @param pszAltitudeMode value to write in altitudeMode element, or NULL.
346
 * @return A KML fragment or NULL in case of error.
347
 */
348
349
char *OGR_G_ExportToKML(OGRGeometryH hGeometry, const char *pszAltitudeMode)
350
0
{
351
0
    char szAltitudeMode[128];
352
353
0
    if (hGeometry == nullptr)
354
0
        return nullptr;
355
356
0
    size_t nMaxLength = 128;
357
0
    char *pszText = static_cast<char *>(CPLMalloc(nMaxLength));
358
0
    pszText[0] = '\0';
359
360
0
    if (pszAltitudeMode &&
361
0
        strlen(pszAltitudeMode) < sizeof(szAltitudeMode) - (29 + 1))
362
0
    {
363
0
        snprintf(szAltitudeMode, sizeof(szAltitudeMode),
364
0
                 "<altitudeMode>%s</altitudeMode>", pszAltitudeMode);
365
0
    }
366
0
    else
367
0
    {
368
0
        szAltitudeMode[0] = 0;
369
0
    }
370
371
0
    size_t nLength = 0;
372
0
    if (!OGR2KMLGeometryAppend(OGRGeometry::FromHandle(hGeometry), &pszText,
373
0
                               &nLength, &nMaxLength, szAltitudeMode))
374
0
    {
375
0
        CPLFree(pszText);
376
0
        return nullptr;
377
0
    }
378
379
0
    return pszText;
380
0
}