Coverage Report

Created: 2026-08-14 09:29

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/gdal/ogr/ogrsf_frmts/kml/ogrkmldatasource.cpp
Line
Count
Source
1
/******************************************************************************
2
 *
3
 * Project:  KML Driver
4
 * Purpose:  Implementation of OGRKMLDataSource class.
5
 * Author:   Christopher Condit, condit@sdsc.edu;
6
 *           Jens Oberender, j.obi@troja.net
7
 *
8
 ******************************************************************************
9
 * Copyright (c) 2006, Christopher Condit
10
 *               2007, Jens Oberender
11
 * Copyright (c) 2007-2014, Even Rouault <even dot rouault at spatialys.com>
12
 *
13
 * SPDX-License-Identifier: MIT
14
 ****************************************************************************/
15
#include "cpl_port.h"
16
#include "ogr_kml.h"
17
18
#include <cstring>
19
#include <string>
20
21
#include "cpl_conv.h"
22
#include "cpl_error.h"
23
#include "cpl_minixml.h"
24
#include "cpl_string.h"
25
#include "cpl_vsi.h"
26
#include "cpl_vsi_error.h"
27
#include "ogr_core.h"
28
#include "ogr_spatialref.h"
29
#include "kml.h"
30
#include "kmlutility.h"
31
#include "kmlvector.h"
32
#include "ogrsf_frmts.h"
33
34
/************************************************************************/
35
/*                          OGRKMLDataSource()                          */
36
/************************************************************************/
37
38
4.89k
OGRKMLDataSource::OGRKMLDataSource() = default;
39
40
/************************************************************************/
41
/*                         ~OGRKMLDataSource()                          */
42
/************************************************************************/
43
44
OGRKMLDataSource::~OGRKMLDataSource()
45
4.89k
{
46
4.89k
    if (fpOutput_ != nullptr)
47
619
    {
48
619
        if (nLayers_ > 0)
49
564
        {
50
564
            if (nLayers_ == 1 && papoLayers_[0]->nWroteFeatureCount_ == 0)
51
10
            {
52
10
                VSIFPrintfL(fpOutput_, "<Folder><name>%s</name>\n",
53
10
                            papoLayers_[0]->GetName());
54
10
            }
55
56
564
            VSIFPrintfL(fpOutput_, "%s", "</Folder>\n");
57
58
2.79k
            for (int i = 0; i < nLayers_; i++)
59
2.23k
            {
60
2.23k
                if (!(papoLayers_[i]->bSchemaWritten_) &&
61
1.82k
                    papoLayers_[i]->nWroteFeatureCount_ != 0)
62
1.21k
                {
63
1.21k
                    CPLString osRet = papoLayers_[i]->WriteSchema();
64
1.21k
                    if (!osRet.empty())
65
1.17k
                        VSIFPrintfL(fpOutput_, "%s", osRet.c_str());
66
1.21k
                }
67
2.23k
            }
68
564
        }
69
619
        VSIFPrintfL(fpOutput_, "%s", "</Document></kml>\n");
70
71
619
        VSIFCloseL(fpOutput_);
72
619
    }
73
74
4.89k
    CSLDestroy(papszCreateOptions_);
75
4.89k
    CPLFree(pszNameField_);
76
4.89k
    CPLFree(pszDescriptionField_);
77
4.89k
    CPLFree(pszAltitudeMode_);
78
79
7.42k
    for (int i = 0; i < nLayers_; i++)
80
2.53k
    {
81
2.53k
        delete papoLayers_[i];
82
2.53k
    }
83
84
4.89k
    CPLFree(papoLayers_);
85
86
4.89k
#ifdef HAVE_EXPAT
87
4.89k
    delete poKMLFile_;
88
4.89k
#endif
89
4.89k
}
90
91
/************************************************************************/
92
/*                                Open()                                */
93
/************************************************************************/
94
95
#ifdef HAVE_EXPAT
96
int OGRKMLDataSource::Open(const char *pszNewName, int bTestOpen)
97
4.27k
{
98
4.27k
    CPLAssert(nullptr != pszNewName);
99
100
    /* -------------------------------------------------------------------- */
101
    /*      Create a KML object and open the source file.                   */
102
    /* -------------------------------------------------------------------- */
103
4.27k
    poKMLFile_ = new KMLVector();
104
105
4.27k
    if (!poKMLFile_->open(pszNewName))
106
0
    {
107
0
        delete poKMLFile_;
108
0
        poKMLFile_ = nullptr;
109
0
        return FALSE;
110
0
    }
111
112
    /* -------------------------------------------------------------------- */
113
    /*      If we aren't sure it is KML, validate it by start parsing       */
114
    /* -------------------------------------------------------------------- */
115
4.27k
    if (bTestOpen && !poKMLFile_->isValid())
116
3.61k
    {
117
3.61k
        delete poKMLFile_;
118
3.61k
        poKMLFile_ = nullptr;
119
3.61k
        return FALSE;
120
3.61k
    }
121
122
    /* -------------------------------------------------------------------- */
123
    /*      Prescan the KML file so we can later work with the structure    */
124
    /* -------------------------------------------------------------------- */
125
666
    if (!poKMLFile_->parse())
126
217
    {
127
217
        delete poKMLFile_;
128
217
        poKMLFile_ = nullptr;
129
217
        return FALSE;
130
217
    }
131
132
    /* -------------------------------------------------------------------- */
133
    /*      Classify the nodes                                              */
134
    /* -------------------------------------------------------------------- */
135
449
    if (!poKMLFile_->classifyNodes())
136
4
    {
137
4
        delete poKMLFile_;
138
4
        poKMLFile_ = nullptr;
139
4
        return FALSE;
140
4
    }
141
142
    /* -------------------------------------------------------------------- */
143
    /*      Eliminate the empty containers (if there is at least one        */
144
    /*      valid container !)                                              */
145
    /* -------------------------------------------------------------------- */
146
445
    const bool bHasOnlyEmpty = poKMLFile_->hasOnlyEmpty();
147
445
    if (bHasOnlyEmpty)
148
71
        CPLDebug("KML", "Has only empty containers");
149
374
    else
150
374
        poKMLFile_->eliminateEmpty();
151
152
    /* -------------------------------------------------------------------- */
153
    /*      Find layers to use in the KML structure                         */
154
    /* -------------------------------------------------------------------- */
155
445
    poKMLFile_->findLayers(nullptr, bHasOnlyEmpty);
156
157
445
    poKMLFile_->findSchemas();
158
159
    /* -------------------------------------------------------------------- */
160
    /*      Print the structure                                             */
161
    /* -------------------------------------------------------------------- */
162
445
    if (CPLGetConfigOption("KML_DEBUG", nullptr) != nullptr)
163
0
        poKMLFile_->print(3);
164
165
445
    const int nLayers = poKMLFile_->getNumLayers();
166
167
    /* -------------------------------------------------------------------- */
168
    /*      Allocate memory for the Layers                                  */
169
    /* -------------------------------------------------------------------- */
170
445
    papoLayers_ =
171
445
        static_cast<OGRKMLLayer **>(CPLMalloc(sizeof(OGRKMLLayer *) * nLayers));
172
173
445
    OGRSpatialReference *poSRS =
174
445
        new OGRSpatialReference(SRS_WKT_WGS84_LAT_LONG);
175
445
    poSRS->SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
176
177
445
    const auto &oMapSchemas = poKMLFile_->GetSchemas();
178
179
    /* -------------------------------------------------------------------- */
180
    /*      Create the Layers and fill them                                 */
181
    /* -------------------------------------------------------------------- */
182
740
    for (int nCount = 0; nCount < nLayers; nCount++)
183
295
    {
184
295
        if (!poKMLFile_->selectLayer(nCount))
185
0
        {
186
0
            CPLError(CE_Failure, CPLE_AppDefined,
187
0
                     "There are no layers or a layer can not be found!");
188
0
            break;
189
0
        }
190
191
295
        OGRwkbGeometryType poGeotype = wkbUnknown;
192
295
        if (poKMLFile_->getCurrentType() == Point)
193
58
            poGeotype = wkbPoint;
194
237
        else if (poKMLFile_->getCurrentType() == LineString)
195
17
            poGeotype = wkbLineString;
196
220
        else if (poKMLFile_->getCurrentType() == Polygon)
197
15
            poGeotype = wkbPolygon;
198
205
        else if (poKMLFile_->getCurrentType() == MultiPoint)
199
0
            poGeotype = wkbMultiPoint;
200
205
        else if (poKMLFile_->getCurrentType() == MultiLineString)
201
14
            poGeotype = wkbMultiLineString;
202
191
        else if (poKMLFile_->getCurrentType() == MultiPolygon)
203
0
            poGeotype = wkbMultiPolygon;
204
191
        else if (poKMLFile_->getCurrentType() == MultiGeometry)
205
6
            poGeotype = wkbGeometryCollection;
206
207
295
        if (poGeotype != wkbUnknown && poKMLFile_->is25D())
208
48
            poGeotype = wkbSetZ(poGeotype);
209
210
        /* --------------------------------------------------------------------
211
         */
212
        /*      Create the layer object. */
213
        /* --------------------------------------------------------------------
214
         */
215
295
        CPLString sName(poKMLFile_->getCurrentName());
216
217
295
        const auto oIterSchema = oMapSchemas.find(sName);
218
219
295
        if (sName.empty())
220
9
        {
221
9
            sName.Printf("Layer #%d", nCount);
222
9
        }
223
286
        else
224
286
        {
225
            // Build unique layer name
226
286
            int nIter = 2;
227
292
            while (true)
228
292
            {
229
292
                if (GetLayerByName(sName) == nullptr)
230
286
                    break;
231
6
                sName = CPLSPrintf("%s (#%d)",
232
6
                                   poKMLFile_->getCurrentName().c_str(), nIter);
233
6
                nIter++;
234
6
            }
235
286
        }
236
237
295
        OGRKMLLayer *poLayer =
238
295
            new OGRKMLLayer(sName.c_str(), poSRS, false, poGeotype, this);
239
240
295
        poLayer->SetLayerNumber(nCount);
241
242
295
        if (oIterSchema != oMapSchemas.end())
243
90
        {
244
90
            for (const auto &poFieldDefn : oIterSchema->second)
245
372
            {
246
372
                if (strcmp(poFieldDefn->GetNameRef(), "Name") != 0 &&
247
372
                    strcmp(poFieldDefn->GetNameRef(), "Description") != 0)
248
372
                {
249
372
                    poLayer->GetLayerDefn()->AddFieldDefn(poFieldDefn.get());
250
372
                }
251
372
            }
252
90
        }
253
254
        /* --------------------------------------------------------------------
255
         */
256
        /*      Add layer to data source layer list. */
257
        /* --------------------------------------------------------------------
258
         */
259
295
        papoLayers_[nCount] = poLayer;
260
261
295
        nLayers_ = nCount + 1;
262
295
    }
263
264
445
    poSRS->Release();
265
266
445
    return TRUE;
267
449
}
268
#endif /* HAVE_EXPAT */
269
270
/************************************************************************/
271
/*                               Create()                               */
272
/************************************************************************/
273
274
int OGRKMLDataSource::Create(const char *pszName, CSLConstList papszOptions)
275
619
{
276
619
    CPLAssert(nullptr != pszName);
277
278
619
    if (fpOutput_ != nullptr)
279
0
    {
280
0
        CPLAssert(false);
281
0
        return FALSE;
282
0
    }
283
284
619
    if (CSLFetchNameValue(papszOptions, "NameField"))
285
0
        pszNameField_ = CPLStrdup(CSLFetchNameValue(papszOptions, "NameField"));
286
619
    else
287
619
        pszNameField_ = CPLStrdup("Name");
288
289
619
    if (CSLFetchNameValue(papszOptions, "DescriptionField"))
290
0
        pszDescriptionField_ =
291
0
            CPLStrdup(CSLFetchNameValue(papszOptions, "DescriptionField"));
292
619
    else
293
619
        pszDescriptionField_ = CPLStrdup("Description");
294
295
619
    pszAltitudeMode_ =
296
619
        CPLStrdup(CSLFetchNameValue(papszOptions, "AltitudeMode"));
297
619
    if ((nullptr != pszAltitudeMode_) && strlen(pszAltitudeMode_) > 0)
298
0
    {
299
        // Check to see that the specified AltitudeMode is valid
300
0
        if (EQUAL(pszAltitudeMode_, "clampToGround") ||
301
0
            EQUAL(pszAltitudeMode_, "relativeToGround") ||
302
0
            EQUAL(pszAltitudeMode_, "absolute"))
303
0
        {
304
0
            CPLDebug("KML", "Using '%s' for AltitudeMode", pszAltitudeMode_);
305
0
        }
306
0
        else
307
0
        {
308
0
            CPLFree(pszAltitudeMode_);
309
0
            pszAltitudeMode_ = nullptr;
310
0
            CPLError(CE_Warning, CPLE_AppDefined,
311
0
                     "Invalid AltitudeMode specified, ignoring");
312
0
        }
313
0
    }
314
619
    else
315
619
    {
316
619
        CPLFree(pszAltitudeMode_);
317
619
        pszAltitudeMode_ = nullptr;
318
619
    }
319
320
    /* -------------------------------------------------------------------- */
321
    /*      Create the output file.                                         */
322
    /* -------------------------------------------------------------------- */
323
324
619
    if (strcmp(pszName, "/dev/stdout") == 0)
325
0
        pszName = "/vsistdout/";
326
327
619
    fpOutput_ = VSIFOpenExL(pszName, "wb", true);
328
619
    if (fpOutput_ == nullptr)
329
0
    {
330
0
        CPLError(CE_Failure, CPLE_OpenFailed,
331
0
                 "Failed to create KML file %s: %s", pszName,
332
0
                 VSIGetLastErrorMsg());
333
0
        return FALSE;
334
0
    }
335
336
    /* -------------------------------------------------------------------- */
337
    /*      Write out "standard" header.                                    */
338
    /* -------------------------------------------------------------------- */
339
619
    VSIFPrintfL(fpOutput_, "<?xml version=\"1.0\" encoding=\"utf-8\" ?>\n");
340
341
619
    VSIFPrintfL(fpOutput_,
342
619
                "<kml xmlns=\"http://www.opengis.net/kml/2.2\">\n"
343
619
                "<Document id=\"%s\">\n",
344
619
                CSLFetchNameValueDef(papszOptions, "DOCUMENT_ID", "root_doc"));
345
346
619
    return TRUE;
347
619
}
348
349
/************************************************************************/
350
/*                            ICreateLayer()                            */
351
/************************************************************************/
352
353
OGRLayer *
354
OGRKMLDataSource::ICreateLayer(const char *pszLayerName,
355
                               const OGRGeomFieldDefn *poGeomFieldDefn,
356
                               CSLConstList /* papszOptions*/)
357
2.23k
{
358
2.23k
    CPLAssert(nullptr != pszLayerName);
359
360
    /* -------------------------------------------------------------------- */
361
    /*      Verify we are in update mode.                                   */
362
    /* -------------------------------------------------------------------- */
363
2.23k
    if (fpOutput_ == nullptr)
364
0
    {
365
0
        CPLError(CE_Failure, CPLE_NoWriteAccess,
366
0
                 "Data source %s opened for read access.  "
367
0
                 "New layer %s cannot be created.",
368
0
                 GetDescription(), pszLayerName);
369
370
0
        return nullptr;
371
0
    }
372
373
2.23k
    const auto eType = poGeomFieldDefn ? poGeomFieldDefn->GetType() : wkbNone;
374
2.23k
    const auto poSRS =
375
2.23k
        poGeomFieldDefn ? poGeomFieldDefn->GetSpatialRef() : nullptr;
376
377
    /* -------------------------------------------------------------------- */
378
    /*      Close the previous layer (if there is one open)                 */
379
    /* -------------------------------------------------------------------- */
380
2.23k
    if (GetLayerCount() > 0)
381
1.67k
    {
382
1.67k
        if (nLayers_ == 1 && papoLayers_[0]->nWroteFeatureCount_ == 0)
383
148
        {
384
148
            VSIFPrintfL(fpOutput_, "<Folder><name>%s</name>\n",
385
148
                        papoLayers_[0]->GetName());
386
148
        }
387
388
1.67k
        VSIFPrintfL(fpOutput_, "</Folder>\n");
389
1.67k
        papoLayers_[GetLayerCount() - 1]->SetClosedForWriting();
390
1.67k
    }
391
392
    /* -------------------------------------------------------------------- */
393
    /*      Ensure name is safe as an element name.                         */
394
    /* -------------------------------------------------------------------- */
395
2.23k
    char *pszCleanLayerName = CPLStrdup(pszLayerName);
396
397
2.23k
    CPLCleanXMLElementName(pszCleanLayerName);
398
2.23k
    if (strcmp(pszCleanLayerName, pszLayerName) != 0)
399
867
    {
400
867
        CPLError(CE_Warning, CPLE_AppDefined,
401
867
                 "Layer name '%s' adjusted to '%s' for XML validity.",
402
867
                 pszLayerName, pszCleanLayerName);
403
867
    }
404
405
2.23k
    if (GetLayerCount() > 0)
406
1.67k
    {
407
1.67k
        VSIFPrintfL(fpOutput_, "<Folder><name>%s</name>\n", pszCleanLayerName);
408
1.67k
    }
409
410
    /* -------------------------------------------------------------------- */
411
    /*      Create the layer object.                                        */
412
    /* -------------------------------------------------------------------- */
413
2.23k
    OGRKMLLayer *poLayer =
414
2.23k
        new OGRKMLLayer(pszCleanLayerName, poSRS, true, eType, this);
415
416
2.23k
    CPLFree(pszCleanLayerName);
417
418
    /* -------------------------------------------------------------------- */
419
    /*      Add layer to data source layer list.                            */
420
    /* -------------------------------------------------------------------- */
421
2.23k
    papoLayers_ = static_cast<OGRKMLLayer **>(
422
2.23k
        CPLRealloc(papoLayers_, sizeof(OGRKMLLayer *) * (nLayers_ + 1)));
423
424
2.23k
    papoLayers_[nLayers_++] = poLayer;
425
426
2.23k
    return poLayer;
427
2.23k
}
428
429
/************************************************************************/
430
/*                           TestCapability()                           */
431
/************************************************************************/
432
433
int OGRKMLDataSource::TestCapability(const char *pszCap) const
434
435
5.37k
{
436
5.37k
    if (EQUAL(pszCap, ODsCCreateLayer))
437
2.23k
        return TRUE;
438
3.13k
    else if (EQUAL(pszCap, ODsCZGeometries))
439
0
        return TRUE;
440
441
3.13k
    return FALSE;
442
5.37k
}
443
444
/************************************************************************/
445
/*                              GetLayer()                              */
446
/************************************************************************/
447
448
const OGRLayer *OGRKMLDataSource::GetLayer(int iLayer) const
449
23.0k
{
450
23.0k
    if (iLayer < 0 || iLayer >= nLayers_)
451
0
        return nullptr;
452
453
23.0k
    return papoLayers_[iLayer];
454
23.0k
}
455
456
/************************************************************************/
457
/*                            GrowExtents()                             */
458
/************************************************************************/
459
460
void OGRKMLDataSource::GrowExtents(OGREnvelope *psGeomBounds)
461
28.7k
{
462
28.7k
    CPLAssert(nullptr != psGeomBounds);
463
464
28.7k
    oEnvelope_.Merge(*psGeomBounds);
465
28.7k
}