Coverage Report

Created: 2026-06-30 08:33

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.70k
OGRKMLDataSource::OGRKMLDataSource() = default;
39
40
/************************************************************************/
41
/*                         ~OGRKMLDataSource()                          */
42
/************************************************************************/
43
44
OGRKMLDataSource::~OGRKMLDataSource()
45
4.70k
{
46
4.70k
    if (fpOutput_ != nullptr)
47
607
    {
48
607
        if (nLayers_ > 0)
49
551
        {
50
551
            if (nLayers_ == 1 && papoLayers_[0]->nWroteFeatureCount_ == 0)
51
12
            {
52
12
                VSIFPrintfL(fpOutput_, "<Folder><name>%s</name>\n",
53
12
                            papoLayers_[0]->GetName());
54
12
            }
55
56
551
            VSIFPrintfL(fpOutput_, "%s", "</Folder>\n");
57
58
2.63k
            for (int i = 0; i < nLayers_; i++)
59
2.08k
            {
60
2.08k
                if (!(papoLayers_[i]->bSchemaWritten_) &&
61
1.69k
                    papoLayers_[i]->nWroteFeatureCount_ != 0)
62
1.11k
                {
63
1.11k
                    CPLString osRet = papoLayers_[i]->WriteSchema();
64
1.11k
                    if (!osRet.empty())
65
1.09k
                        VSIFPrintfL(fpOutput_, "%s", osRet.c_str());
66
1.11k
                }
67
2.08k
            }
68
551
        }
69
607
        VSIFPrintfL(fpOutput_, "%s", "</Document></kml>\n");
70
71
607
        VSIFCloseL(fpOutput_);
72
607
    }
73
74
4.70k
    CSLDestroy(papszCreateOptions_);
75
4.70k
    CPLFree(pszNameField_);
76
4.70k
    CPLFree(pszDescriptionField_);
77
4.70k
    CPLFree(pszAltitudeMode_);
78
79
7.08k
    for (int i = 0; i < nLayers_; i++)
80
2.37k
    {
81
2.37k
        delete papoLayers_[i];
82
2.37k
    }
83
84
4.70k
    CPLFree(papoLayers_);
85
86
4.70k
#ifdef HAVE_EXPAT
87
4.70k
    delete poKMLFile_;
88
4.70k
#endif
89
4.70k
}
90
91
/************************************************************************/
92
/*                                Open()                                */
93
/************************************************************************/
94
95
#ifdef HAVE_EXPAT
96
int OGRKMLDataSource::Open(const char *pszNewName, int bTestOpen)
97
4.10k
{
98
4.10k
    CPLAssert(nullptr != pszNewName);
99
100
    /* -------------------------------------------------------------------- */
101
    /*      Create a KML object and open the source file.                   */
102
    /* -------------------------------------------------------------------- */
103
4.10k
    poKMLFile_ = new KMLVector();
104
105
4.10k
    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.10k
    if (bTestOpen && !poKMLFile_->isValid())
116
3.43k
    {
117
3.43k
        delete poKMLFile_;
118
3.43k
        poKMLFile_ = nullptr;
119
3.43k
        return FALSE;
120
3.43k
    }
121
122
    /* -------------------------------------------------------------------- */
123
    /*      Prescan the KML file so we can later work with the structure    */
124
    /* -------------------------------------------------------------------- */
125
667
    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
450
    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
446
    const bool bHasOnlyEmpty = poKMLFile_->hasOnlyEmpty();
147
446
    if (bHasOnlyEmpty)
148
72
        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
446
    poKMLFile_->findLayers(nullptr, bHasOnlyEmpty);
156
157
446
    poKMLFile_->findSchemas();
158
159
    /* -------------------------------------------------------------------- */
160
    /*      Print the structure                                             */
161
    /* -------------------------------------------------------------------- */
162
446
    if (CPLGetConfigOption("KML_DEBUG", nullptr) != nullptr)
163
0
        poKMLFile_->print(3);
164
165
446
    const int nLayers = poKMLFile_->getNumLayers();
166
167
    /* -------------------------------------------------------------------- */
168
    /*      Allocate memory for the Layers                                  */
169
    /* -------------------------------------------------------------------- */
170
446
    papoLayers_ =
171
446
        static_cast<OGRKMLLayer **>(CPLMalloc(sizeof(OGRKMLLayer *) * nLayers));
172
173
446
    OGRSpatialReference *poSRS =
174
446
        new OGRSpatialReference(SRS_WKT_WGS84_LAT_LONG);
175
446
    poSRS->SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
176
177
446
    const auto &oMapSchemas = poKMLFile_->GetSchemas();
178
179
    /* -------------------------------------------------------------------- */
180
    /*      Create the Layers and fill them                                 */
181
    /* -------------------------------------------------------------------- */
182
736
    for (int nCount = 0; nCount < nLayers; nCount++)
183
290
    {
184
290
        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
290
        OGRwkbGeometryType poGeotype = wkbUnknown;
192
290
        if (poKMLFile_->getCurrentType() == Point)
193
50
            poGeotype = wkbPoint;
194
240
        else if (poKMLFile_->getCurrentType() == LineString)
195
18
            poGeotype = wkbLineString;
196
222
        else if (poKMLFile_->getCurrentType() == Polygon)
197
14
            poGeotype = wkbPolygon;
198
208
        else if (poKMLFile_->getCurrentType() == MultiPoint)
199
0
            poGeotype = wkbMultiPoint;
200
208
        else if (poKMLFile_->getCurrentType() == MultiLineString)
201
13
            poGeotype = wkbMultiLineString;
202
195
        else if (poKMLFile_->getCurrentType() == MultiPolygon)
203
0
            poGeotype = wkbMultiPolygon;
204
195
        else if (poKMLFile_->getCurrentType() == MultiGeometry)
205
4
            poGeotype = wkbGeometryCollection;
206
207
290
        if (poGeotype != wkbUnknown && poKMLFile_->is25D())
208
43
            poGeotype = wkbSetZ(poGeotype);
209
210
        /* --------------------------------------------------------------------
211
         */
212
        /*      Create the layer object. */
213
        /* --------------------------------------------------------------------
214
         */
215
290
        CPLString sName(poKMLFile_->getCurrentName());
216
217
290
        const auto oIterSchema = oMapSchemas.find(sName);
218
219
290
        if (sName.empty())
220
9
        {
221
9
            sName.Printf("Layer #%d", nCount);
222
9
        }
223
281
        else
224
281
        {
225
            // Build unique layer name
226
281
            int nIter = 2;
227
287
            while (true)
228
287
            {
229
287
                if (GetLayerByName(sName) == nullptr)
230
281
                    break;
231
6
                sName = CPLSPrintf("%s (#%d)",
232
6
                                   poKMLFile_->getCurrentName().c_str(), nIter);
233
6
                nIter++;
234
6
            }
235
281
        }
236
237
290
        OGRKMLLayer *poLayer =
238
290
            new OGRKMLLayer(sName.c_str(), poSRS, false, poGeotype, this);
239
240
290
        poLayer->SetLayerNumber(nCount);
241
242
290
        if (oIterSchema != oMapSchemas.end())
243
84
        {
244
84
            for (const auto &poFieldDefn : oIterSchema->second)
245
300
            {
246
300
                if (strcmp(poFieldDefn->GetNameRef(), "Name") != 0 &&
247
300
                    strcmp(poFieldDefn->GetNameRef(), "Description") != 0)
248
300
                {
249
300
                    poLayer->GetLayerDefn()->AddFieldDefn(poFieldDefn.get());
250
300
                }
251
300
            }
252
84
        }
253
254
        /* --------------------------------------------------------------------
255
         */
256
        /*      Add layer to data source layer list. */
257
        /* --------------------------------------------------------------------
258
         */
259
290
        papoLayers_[nCount] = poLayer;
260
261
290
        nLayers_ = nCount + 1;
262
290
    }
263
264
446
    poSRS->Release();
265
266
446
    return TRUE;
267
450
}
268
#endif /* HAVE_EXPAT */
269
270
/************************************************************************/
271
/*                               Create()                               */
272
/************************************************************************/
273
274
int OGRKMLDataSource::Create(const char *pszName, CSLConstList papszOptions)
275
607
{
276
607
    CPLAssert(nullptr != pszName);
277
278
607
    if (fpOutput_ != nullptr)
279
0
    {
280
0
        CPLAssert(false);
281
0
        return FALSE;
282
0
    }
283
284
607
    if (CSLFetchNameValue(papszOptions, "NameField"))
285
0
        pszNameField_ = CPLStrdup(CSLFetchNameValue(papszOptions, "NameField"));
286
607
    else
287
607
        pszNameField_ = CPLStrdup("Name");
288
289
607
    if (CSLFetchNameValue(papszOptions, "DescriptionField"))
290
0
        pszDescriptionField_ =
291
0
            CPLStrdup(CSLFetchNameValue(papszOptions, "DescriptionField"));
292
607
    else
293
607
        pszDescriptionField_ = CPLStrdup("Description");
294
295
607
    pszAltitudeMode_ =
296
607
        CPLStrdup(CSLFetchNameValue(papszOptions, "AltitudeMode"));
297
607
    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
607
    else
315
607
    {
316
607
        CPLFree(pszAltitudeMode_);
317
607
        pszAltitudeMode_ = nullptr;
318
607
    }
319
320
    /* -------------------------------------------------------------------- */
321
    /*      Create the output file.                                         */
322
    /* -------------------------------------------------------------------- */
323
324
607
    if (strcmp(pszName, "/dev/stdout") == 0)
325
0
        pszName = "/vsistdout/";
326
327
607
    fpOutput_ = VSIFOpenExL(pszName, "wb", true);
328
607
    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
607
    VSIFPrintfL(fpOutput_, "<?xml version=\"1.0\" encoding=\"utf-8\" ?>\n");
340
341
607
    VSIFPrintfL(fpOutput_,
342
607
                "<kml xmlns=\"http://www.opengis.net/kml/2.2\">\n"
343
607
                "<Document id=\"%s\">\n",
344
607
                CSLFetchNameValueDef(papszOptions, "DOCUMENT_ID", "root_doc"));
345
346
607
    return TRUE;
347
607
}
348
349
/************************************************************************/
350
/*                            ICreateLayer()                            */
351
/************************************************************************/
352
353
OGRLayer *
354
OGRKMLDataSource::ICreateLayer(const char *pszLayerName,
355
                               const OGRGeomFieldDefn *poGeomFieldDefn,
356
                               CSLConstList /* papszOptions*/)
357
2.08k
{
358
2.08k
    CPLAssert(nullptr != pszLayerName);
359
360
    /* -------------------------------------------------------------------- */
361
    /*      Verify we are in update mode.                                   */
362
    /* -------------------------------------------------------------------- */
363
2.08k
    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.08k
    const auto eType = poGeomFieldDefn ? poGeomFieldDefn->GetType() : wkbNone;
374
2.08k
    const auto poSRS =
375
2.08k
        poGeomFieldDefn ? poGeomFieldDefn->GetSpatialRef() : nullptr;
376
377
    /* -------------------------------------------------------------------- */
378
    /*      Close the previous layer (if there is one open)                 */
379
    /* -------------------------------------------------------------------- */
380
2.08k
    if (GetLayerCount() > 0)
381
1.53k
    {
382
1.53k
        if (nLayers_ == 1 && papoLayers_[0]->nWroteFeatureCount_ == 0)
383
145
        {
384
145
            VSIFPrintfL(fpOutput_, "<Folder><name>%s</name>\n",
385
145
                        papoLayers_[0]->GetName());
386
145
        }
387
388
1.53k
        VSIFPrintfL(fpOutput_, "</Folder>\n");
389
1.53k
        papoLayers_[GetLayerCount() - 1]->SetClosedForWriting();
390
1.53k
    }
391
392
    /* -------------------------------------------------------------------- */
393
    /*      Ensure name is safe as an element name.                         */
394
    /* -------------------------------------------------------------------- */
395
2.08k
    char *pszCleanLayerName = CPLStrdup(pszLayerName);
396
397
2.08k
    CPLCleanXMLElementName(pszCleanLayerName);
398
2.08k
    if (strcmp(pszCleanLayerName, pszLayerName) != 0)
399
811
    {
400
811
        CPLError(CE_Warning, CPLE_AppDefined,
401
811
                 "Layer name '%s' adjusted to '%s' for XML validity.",
402
811
                 pszLayerName, pszCleanLayerName);
403
811
    }
404
405
2.08k
    if (GetLayerCount() > 0)
406
1.53k
    {
407
1.53k
        VSIFPrintfL(fpOutput_, "<Folder><name>%s</name>\n", pszCleanLayerName);
408
1.53k
    }
409
410
    /* -------------------------------------------------------------------- */
411
    /*      Create the layer object.                                        */
412
    /* -------------------------------------------------------------------- */
413
2.08k
    OGRKMLLayer *poLayer =
414
2.08k
        new OGRKMLLayer(pszCleanLayerName, poSRS, true, eType, this);
415
416
2.08k
    CPLFree(pszCleanLayerName);
417
418
    /* -------------------------------------------------------------------- */
419
    /*      Add layer to data source layer list.                            */
420
    /* -------------------------------------------------------------------- */
421
2.08k
    papoLayers_ = static_cast<OGRKMLLayer **>(
422
2.08k
        CPLRealloc(papoLayers_, sizeof(OGRKMLLayer *) * (nLayers_ + 1)));
423
424
2.08k
    papoLayers_[nLayers_++] = poLayer;
425
426
2.08k
    return poLayer;
427
2.08k
}
428
429
/************************************************************************/
430
/*                           TestCapability()                           */
431
/************************************************************************/
432
433
int OGRKMLDataSource::TestCapability(const char *pszCap) const
434
435
5.04k
{
436
5.04k
    if (EQUAL(pszCap, ODsCCreateLayer))
437
2.08k
        return TRUE;
438
2.95k
    else if (EQUAL(pszCap, ODsCZGeometries))
439
0
        return TRUE;
440
441
2.95k
    return FALSE;
442
5.04k
}
443
444
/************************************************************************/
445
/*                              GetLayer()                              */
446
/************************************************************************/
447
448
const OGRLayer *OGRKMLDataSource::GetLayer(int iLayer) const
449
19.8k
{
450
19.8k
    if (iLayer < 0 || iLayer >= nLayers_)
451
0
        return nullptr;
452
453
19.8k
    return papoLayers_[iLayer];
454
19.8k
}
455
456
/************************************************************************/
457
/*                            GrowExtents()                             */
458
/************************************************************************/
459
460
void OGRKMLDataSource::GrowExtents(OGREnvelope *psGeomBounds)
461
28.3k
{
462
28.3k
    CPLAssert(nullptr != psGeomBounds);
463
464
28.3k
    oEnvelope_.Merge(*psGeomBounds);
465
28.3k
}