Coverage Report

Created: 2025-12-31 08:30

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