Coverage Report

Created: 2026-03-30 09:00

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.22k
OGRKMLDataSource::OGRKMLDataSource() = default;
39
40
/************************************************************************/
41
/*                         ~OGRKMLDataSource()                          */
42
/************************************************************************/
43
44
OGRKMLDataSource::~OGRKMLDataSource()
45
4.22k
{
46
4.22k
    if (fpOutput_ != nullptr)
47
607
    {
48
607
        if (nLayers_ > 0)
49
550
        {
50
550
            if (nLayers_ == 1 && papoLayers_[0]->nWroteFeatureCount_ == 0)
51
8
            {
52
8
                VSIFPrintfL(fpOutput_, "<Folder><name>%s</name>\n",
53
8
                            papoLayers_[0]->GetName());
54
8
            }
55
56
550
            VSIFPrintfL(fpOutput_, "%s", "</Folder>\n");
57
58
2.65k
            for (int i = 0; i < nLayers_; i++)
59
2.10k
            {
60
2.10k
                if (!(papoLayers_[i]->bSchemaWritten_) &&
61
1.70k
                    papoLayers_[i]->nWroteFeatureCount_ != 0)
62
1.12k
                {
63
1.12k
                    CPLString osRet = papoLayers_[i]->WriteSchema();
64
1.12k
                    if (!osRet.empty())
65
1.09k
                        VSIFPrintfL(fpOutput_, "%s", osRet.c_str());
66
1.12k
                }
67
2.10k
            }
68
550
        }
69
607
        VSIFPrintfL(fpOutput_, "%s", "</Document></kml>\n");
70
71
607
        VSIFCloseL(fpOutput_);
72
607
    }
73
74
4.22k
    CSLDestroy(papszCreateOptions_);
75
4.22k
    CPLFree(pszNameField_);
76
4.22k
    CPLFree(pszDescriptionField_);
77
4.22k
    CPLFree(pszAltitudeMode_);
78
79
6.94k
    for (int i = 0; i < nLayers_; i++)
80
2.71k
    {
81
2.71k
        delete papoLayers_[i];
82
2.71k
    }
83
84
4.22k
    CPLFree(papoLayers_);
85
86
4.22k
#ifdef HAVE_EXPAT
87
4.22k
    delete poKMLFile_;
88
4.22k
#endif
89
4.22k
}
90
91
/************************************************************************/
92
/*                                Open()                                */
93
/************************************************************************/
94
95
#ifdef HAVE_EXPAT
96
int OGRKMLDataSource::Open(const char *pszNewName, int bTestOpen)
97
3.61k
{
98
3.61k
    CPLAssert(nullptr != pszNewName);
99
100
    /* -------------------------------------------------------------------- */
101
    /*      Create a KML object and open the source file.                   */
102
    /* -------------------------------------------------------------------- */
103
3.61k
    poKMLFile_ = new KMLVector();
104
105
3.61k
    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
3.61k
    if (bTestOpen && !poKMLFile_->isValid())
116
2.99k
    {
117
2.99k
        delete poKMLFile_;
118
2.99k
        poKMLFile_ = nullptr;
119
2.99k
        return FALSE;
120
2.99k
    }
121
122
    /* -------------------------------------------------------------------- */
123
    /*      Prescan the KML file so we can later work with the structure    */
124
    /* -------------------------------------------------------------------- */
125
627
    if (!poKMLFile_->parse())
126
202
    {
127
202
        delete poKMLFile_;
128
202
        poKMLFile_ = nullptr;
129
202
        return FALSE;
130
202
    }
131
132
    /* -------------------------------------------------------------------- */
133
    /*      Classify the nodes                                              */
134
    /* -------------------------------------------------------------------- */
135
425
    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
421
    const bool bHasOnlyEmpty = poKMLFile_->hasOnlyEmpty();
147
421
    if (bHasOnlyEmpty)
148
234
        CPLDebug("KML", "Has only empty containers");
149
187
    else
150
187
        poKMLFile_->eliminateEmpty();
151
152
    /* -------------------------------------------------------------------- */
153
    /*      Find layers to use in the KML structure                         */
154
    /* -------------------------------------------------------------------- */
155
421
    poKMLFile_->findLayers(nullptr, bHasOnlyEmpty);
156
157
    /* -------------------------------------------------------------------- */
158
    /*      Print the structure                                             */
159
    /* -------------------------------------------------------------------- */
160
421
    if (CPLGetConfigOption("KML_DEBUG", nullptr) != nullptr)
161
0
        poKMLFile_->print(3);
162
163
421
    const int nLayers = poKMLFile_->getNumLayers();
164
165
    /* -------------------------------------------------------------------- */
166
    /*      Allocate memory for the Layers                                  */
167
    /* -------------------------------------------------------------------- */
168
421
    papoLayers_ =
169
421
        static_cast<OGRKMLLayer **>(CPLMalloc(sizeof(OGRKMLLayer *) * nLayers));
170
171
421
    OGRSpatialReference *poSRS =
172
421
        new OGRSpatialReference(SRS_WKT_WGS84_LAT_LONG);
173
421
    poSRS->SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
174
175
    /* -------------------------------------------------------------------- */
176
    /*      Create the Layers and fill them                                 */
177
    /* -------------------------------------------------------------------- */
178
1.03k
    for (int nCount = 0; nCount < nLayers; nCount++)
179
616
    {
180
616
        if (!poKMLFile_->selectLayer(nCount))
181
0
        {
182
0
            CPLError(CE_Failure, CPLE_AppDefined,
183
0
                     "There are no layers or a layer can not be found!");
184
0
            break;
185
0
        }
186
187
616
        OGRwkbGeometryType poGeotype = wkbUnknown;
188
616
        if (poKMLFile_->getCurrentType() == Point)
189
56
            poGeotype = wkbPoint;
190
560
        else if (poKMLFile_->getCurrentType() == LineString)
191
23
            poGeotype = wkbLineString;
192
537
        else if (poKMLFile_->getCurrentType() == Polygon)
193
13
            poGeotype = wkbPolygon;
194
524
        else if (poKMLFile_->getCurrentType() == MultiPoint)
195
0
            poGeotype = wkbMultiPoint;
196
524
        else if (poKMLFile_->getCurrentType() == MultiLineString)
197
9
            poGeotype = wkbMultiLineString;
198
515
        else if (poKMLFile_->getCurrentType() == MultiPolygon)
199
0
            poGeotype = wkbMultiPolygon;
200
515
        else if (poKMLFile_->getCurrentType() == MultiGeometry)
201
5
            poGeotype = wkbGeometryCollection;
202
203
616
        if (poGeotype != wkbUnknown && poKMLFile_->is25D())
204
35
            poGeotype = wkbSetZ(poGeotype);
205
206
        /* --------------------------------------------------------------------
207
         */
208
        /*      Create the layer object. */
209
        /* --------------------------------------------------------------------
210
         */
211
616
        CPLString sName(poKMLFile_->getCurrentName());
212
213
616
        if (sName.empty())
214
9
        {
215
9
            sName.Printf("Layer #%d", nCount);
216
9
        }
217
607
        else
218
607
        {
219
            // Build unique layer name
220
607
            int nIter = 2;
221
619
            while (true)
222
619
            {
223
619
                if (GetLayerByName(sName) == nullptr)
224
607
                    break;
225
12
                sName = CPLSPrintf("%s (#%d)",
226
12
                                   poKMLFile_->getCurrentName().c_str(), nIter);
227
12
                nIter++;
228
12
            }
229
607
        }
230
231
616
        OGRKMLLayer *poLayer =
232
616
            new OGRKMLLayer(sName.c_str(), poSRS, false, poGeotype, this);
233
234
616
        poLayer->SetLayerNumber(nCount);
235
236
        /* --------------------------------------------------------------------
237
         */
238
        /*      Add layer to data source layer list. */
239
        /* --------------------------------------------------------------------
240
         */
241
616
        papoLayers_[nCount] = poLayer;
242
243
616
        nLayers_ = nCount + 1;
244
616
    }
245
246
421
    poSRS->Release();
247
248
421
    return TRUE;
249
425
}
250
#endif /* HAVE_EXPAT */
251
252
/************************************************************************/
253
/*                               Create()                               */
254
/************************************************************************/
255
256
int OGRKMLDataSource::Create(const char *pszName, CSLConstList papszOptions)
257
607
{
258
607
    CPLAssert(nullptr != pszName);
259
260
607
    if (fpOutput_ != nullptr)
261
0
    {
262
0
        CPLAssert(false);
263
0
        return FALSE;
264
0
    }
265
266
607
    if (CSLFetchNameValue(papszOptions, "NameField"))
267
0
        pszNameField_ = CPLStrdup(CSLFetchNameValue(papszOptions, "NameField"));
268
607
    else
269
607
        pszNameField_ = CPLStrdup("Name");
270
271
607
    if (CSLFetchNameValue(papszOptions, "DescriptionField"))
272
0
        pszDescriptionField_ =
273
0
            CPLStrdup(CSLFetchNameValue(papszOptions, "DescriptionField"));
274
607
    else
275
607
        pszDescriptionField_ = CPLStrdup("Description");
276
277
607
    pszAltitudeMode_ =
278
607
        CPLStrdup(CSLFetchNameValue(papszOptions, "AltitudeMode"));
279
607
    if ((nullptr != pszAltitudeMode_) && strlen(pszAltitudeMode_) > 0)
280
0
    {
281
        // Check to see that the specified AltitudeMode is valid
282
0
        if (EQUAL(pszAltitudeMode_, "clampToGround") ||
283
0
            EQUAL(pszAltitudeMode_, "relativeToGround") ||
284
0
            EQUAL(pszAltitudeMode_, "absolute"))
285
0
        {
286
0
            CPLDebug("KML", "Using '%s' for AltitudeMode", pszAltitudeMode_);
287
0
        }
288
0
        else
289
0
        {
290
0
            CPLFree(pszAltitudeMode_);
291
0
            pszAltitudeMode_ = nullptr;
292
0
            CPLError(CE_Warning, CPLE_AppDefined,
293
0
                     "Invalid AltitudeMode specified, ignoring");
294
0
        }
295
0
    }
296
607
    else
297
607
    {
298
607
        CPLFree(pszAltitudeMode_);
299
607
        pszAltitudeMode_ = nullptr;
300
607
    }
301
302
    /* -------------------------------------------------------------------- */
303
    /*      Create the output file.                                         */
304
    /* -------------------------------------------------------------------- */
305
306
607
    if (strcmp(pszName, "/dev/stdout") == 0)
307
0
        pszName = "/vsistdout/";
308
309
607
    fpOutput_ = VSIFOpenExL(pszName, "wb", true);
310
607
    if (fpOutput_ == nullptr)
311
0
    {
312
0
        CPLError(CE_Failure, CPLE_OpenFailed,
313
0
                 "Failed to create KML file %s: %s", pszName,
314
0
                 VSIGetLastErrorMsg());
315
0
        return FALSE;
316
0
    }
317
318
    /* -------------------------------------------------------------------- */
319
    /*      Write out "standard" header.                                    */
320
    /* -------------------------------------------------------------------- */
321
607
    VSIFPrintfL(fpOutput_, "<?xml version=\"1.0\" encoding=\"utf-8\" ?>\n");
322
323
607
    VSIFPrintfL(fpOutput_,
324
607
                "<kml xmlns=\"http://www.opengis.net/kml/2.2\">\n"
325
607
                "<Document id=\"%s\">\n",
326
607
                CSLFetchNameValueDef(papszOptions, "DOCUMENT_ID", "root_doc"));
327
328
607
    return TRUE;
329
607
}
330
331
/************************************************************************/
332
/*                            ICreateLayer()                            */
333
/************************************************************************/
334
335
OGRLayer *
336
OGRKMLDataSource::ICreateLayer(const char *pszLayerName,
337
                               const OGRGeomFieldDefn *poGeomFieldDefn,
338
                               CSLConstList /* papszOptions*/)
339
2.10k
{
340
2.10k
    CPLAssert(nullptr != pszLayerName);
341
342
    /* -------------------------------------------------------------------- */
343
    /*      Verify we are in update mode.                                   */
344
    /* -------------------------------------------------------------------- */
345
2.10k
    if (fpOutput_ == nullptr)
346
0
    {
347
0
        CPLError(CE_Failure, CPLE_NoWriteAccess,
348
0
                 "Data source %s opened for read access.  "
349
0
                 "New layer %s cannot be created.",
350
0
                 GetDescription(), pszLayerName);
351
352
0
        return nullptr;
353
0
    }
354
355
2.10k
    const auto eType = poGeomFieldDefn ? poGeomFieldDefn->GetType() : wkbNone;
356
2.10k
    const auto poSRS =
357
2.10k
        poGeomFieldDefn ? poGeomFieldDefn->GetSpatialRef() : nullptr;
358
359
    /* -------------------------------------------------------------------- */
360
    /*      Close the previous layer (if there is one open)                 */
361
    /* -------------------------------------------------------------------- */
362
2.10k
    if (GetLayerCount() > 0)
363
1.55k
    {
364
1.55k
        if (nLayers_ == 1 && papoLayers_[0]->nWroteFeatureCount_ == 0)
365
147
        {
366
147
            VSIFPrintfL(fpOutput_, "<Folder><name>%s</name>\n",
367
147
                        papoLayers_[0]->GetName());
368
147
        }
369
370
1.55k
        VSIFPrintfL(fpOutput_, "</Folder>\n");
371
1.55k
        papoLayers_[GetLayerCount() - 1]->SetClosedForWriting();
372
1.55k
    }
373
374
    /* -------------------------------------------------------------------- */
375
    /*      Ensure name is safe as an element name.                         */
376
    /* -------------------------------------------------------------------- */
377
2.10k
    char *pszCleanLayerName = CPLStrdup(pszLayerName);
378
379
2.10k
    CPLCleanXMLElementName(pszCleanLayerName);
380
2.10k
    if (strcmp(pszCleanLayerName, pszLayerName) != 0)
381
818
    {
382
818
        CPLError(CE_Warning, CPLE_AppDefined,
383
818
                 "Layer name '%s' adjusted to '%s' for XML validity.",
384
818
                 pszLayerName, pszCleanLayerName);
385
818
    }
386
387
2.10k
    if (GetLayerCount() > 0)
388
1.55k
    {
389
1.55k
        VSIFPrintfL(fpOutput_, "<Folder><name>%s</name>\n", pszCleanLayerName);
390
1.55k
    }
391
392
    /* -------------------------------------------------------------------- */
393
    /*      Create the layer object.                                        */
394
    /* -------------------------------------------------------------------- */
395
2.10k
    OGRKMLLayer *poLayer =
396
2.10k
        new OGRKMLLayer(pszCleanLayerName, poSRS, true, eType, this);
397
398
2.10k
    CPLFree(pszCleanLayerName);
399
400
    /* -------------------------------------------------------------------- */
401
    /*      Add layer to data source layer list.                            */
402
    /* -------------------------------------------------------------------- */
403
2.10k
    papoLayers_ = static_cast<OGRKMLLayer **>(
404
2.10k
        CPLRealloc(papoLayers_, sizeof(OGRKMLLayer *) * (nLayers_ + 1)));
405
406
2.10k
    papoLayers_[nLayers_++] = poLayer;
407
408
2.10k
    return poLayer;
409
2.10k
}
410
411
/************************************************************************/
412
/*                           TestCapability()                           */
413
/************************************************************************/
414
415
int OGRKMLDataSource::TestCapability(const char *pszCap) const
416
417
5.08k
{
418
5.08k
    if (EQUAL(pszCap, ODsCCreateLayer))
419
2.10k
        return TRUE;
420
2.98k
    else if (EQUAL(pszCap, ODsCZGeometries))
421
0
        return TRUE;
422
423
2.98k
    return FALSE;
424
5.08k
}
425
426
/************************************************************************/
427
/*                              GetLayer()                              */
428
/************************************************************************/
429
430
const OGRLayer *OGRKMLDataSource::GetLayer(int iLayer) const
431
20.5k
{
432
20.5k
    if (iLayer < 0 || iLayer >= nLayers_)
433
0
        return nullptr;
434
435
20.5k
    return papoLayers_[iLayer];
436
20.5k
}
437
438
/************************************************************************/
439
/*                            GrowExtents()                             */
440
/************************************************************************/
441
442
void OGRKMLDataSource::GrowExtents(OGREnvelope *psGeomBounds)
443
28.4k
{
444
28.4k
    CPLAssert(nullptr != psGeomBounds);
445
446
28.4k
    oEnvelope_.Merge(*psGeomBounds);
447
28.4k
}