Coverage Report

Created: 2026-07-25 06:50

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/gdal/ogr/ogrsf_frmts/geojson/ogrgeojsonwritelayer.cpp
Line
Count
Source
1
/******************************************************************************
2
 *
3
 * Project:  OpenGIS Simple Features Reference Implementation
4
 * Purpose:  Implementation of OGRGeoJSONWriteLayer class (OGR GeoJSON Driver).
5
 * Author:   Mateusz Loskot, mateusz@loskot.net
6
 *
7
 ******************************************************************************
8
 * Copyright (c) 2011, Even Rouault <even dot rouault at spatialys.com>
9
 * Copyright (c) 2007, Mateusz Loskot
10
 *
11
 * SPDX-License-Identifier: MIT
12
 ****************************************************************************/
13
14
#include "ogr_geojson.h"
15
#include "ogrgeojsonwriter.h"
16
17
#include "cpl_vsi_virtual.h"
18
19
#include <algorithm>
20
21
/************************************************************************/
22
/*                        OGRGeoJSONWriteLayer()                        */
23
/************************************************************************/
24
25
OGRGeoJSONWriteLayer::OGRGeoJSONWriteLayer(const char *pszName,
26
                                           OGRwkbGeometryType eGType,
27
                                           CSLConstList papszOptions,
28
                                           bool bWriteFC_BBOXIn,
29
                                           OGRCoordinateTransformation *poCT,
30
                                           OGRGeoJSONDataSource *poDS)
31
0
    : poDS_(poDS), poFeatureDefn_(new OGRFeatureDefn(pszName)), nOutCounter_(0),
32
0
      bWriteBBOX(CPLTestBool(
33
0
          CSLFetchNameValueDef(papszOptions, "WRITE_BBOX", "FALSE"))),
34
0
      bBBOX3D(false), bWriteFC_BBOX(bWriteFC_BBOXIn),
35
0
      nSignificantFigures_(atoi(
36
0
          CSLFetchNameValueDef(papszOptions, "SIGNIFICANT_FIGURES", "-1"))),
37
      bRFC7946_(
38
0
          CPLTestBool(CSLFetchNameValueDef(papszOptions, "RFC7946", "FALSE"))),
39
0
      bWrapDateLine_(CPLTestBool(
40
0
          CSLFetchNameValueDef(papszOptions, "WRAPDATELINE", "YES"))),
41
0
      osForeignMembers_(
42
0
          CSLFetchNameValueDef(papszOptions, "FOREIGN_MEMBERS_FEATURE", "")),
43
0
      poCT_(poCT)
44
0
{
45
0
    if (!osForeignMembers_.empty())
46
0
    {
47
        // Already checked in OGRGeoJSONDataSource::ICreateLayer()
48
0
        CPLAssert(osForeignMembers_.front() == '{');
49
0
        CPLAssert(osForeignMembers_.back() == '}');
50
0
        osForeignMembers_ =
51
0
            osForeignMembers_.substr(1, osForeignMembers_.size() - 2);
52
0
    }
53
0
    poFeatureDefn_->Reference();
54
0
    poFeatureDefn_->SetGeomType(eGType);
55
0
    SetDescription(poFeatureDefn_->GetName());
56
0
    const char *pszCoordPrecision =
57
0
        CSLFetchNameValue(papszOptions, "COORDINATE_PRECISION");
58
0
    if (pszCoordPrecision)
59
0
    {
60
0
        oWriteOptions_.nXYCoordPrecision = atoi(pszCoordPrecision);
61
0
        oWriteOptions_.nZCoordPrecision = atoi(pszCoordPrecision);
62
0
    }
63
0
    else
64
0
    {
65
0
        oWriteOptions_.nXYCoordPrecision = atoi(CSLFetchNameValueDef(
66
0
            papszOptions, "XY_COORD_PRECISION", bRFC7946_ ? "7" : "-1"));
67
0
        oWriteOptions_.nZCoordPrecision = atoi(CSLFetchNameValueDef(
68
0
            papszOptions, "Z_COORD_PRECISION", bRFC7946_ ? "3" : "-1"));
69
0
    }
70
0
    oWriteOptions_.bWriteBBOX = bWriteBBOX;
71
0
    oWriteOptions_.nSignificantFigures = nSignificantFigures_;
72
0
    if (bRFC7946_)
73
0
    {
74
0
        oWriteOptions_.SetRFC7946Settings();
75
0
    }
76
0
    oWriteOptions_.SetIDOptions(papszOptions);
77
0
    oWriteOptions_.bAllowNonFiniteValues = CPLTestBool(
78
0
        CSLFetchNameValueDef(papszOptions, "WRITE_NON_FINITE_VALUES", "FALSE"));
79
0
    oWriteOptions_.bAutodetectJsonStrings = CPLTestBool(
80
0
        CSLFetchNameValueDef(papszOptions, "AUTODETECT_JSON_STRINGS", "TRUE"));
81
0
}
82
83
/************************************************************************/
84
/*                       ~OGRGeoJSONWriteLayer()                        */
85
/************************************************************************/
86
87
OGRGeoJSONWriteLayer::~OGRGeoJSONWriteLayer()
88
0
{
89
0
    FinishWriting();
90
91
0
    if (nullptr != poFeatureDefn_)
92
0
    {
93
0
        poFeatureDefn_->Release();
94
0
    }
95
96
0
    delete poCT_;
97
0
}
98
99
/************************************************************************/
100
/*                           FinishWriting()                            */
101
/************************************************************************/
102
103
void OGRGeoJSONWriteLayer::FinishWriting()
104
0
{
105
0
    if (m_nPositionBeforeFCClosed == 0)
106
0
    {
107
0
        VSILFILE *fp = poDS_->GetOutputFile();
108
109
0
        m_nPositionBeforeFCClosed = fp->Tell();
110
111
0
        VSIFPrintfL(fp, "\n]");
112
113
0
        if (bWriteFC_BBOX && sEnvelopeLayer.IsInit())
114
0
        {
115
0
            CPLString osBBOX = "[ ";
116
0
            char szFormat[32];
117
0
            if (oWriteOptions_.nXYCoordPrecision >= 0)
118
0
                snprintf(szFormat, sizeof(szFormat), "%%.%df",
119
0
                         oWriteOptions_.nXYCoordPrecision);
120
0
            else
121
0
                snprintf(szFormat, sizeof(szFormat), "%s", "%.15g");
122
123
0
            osBBOX += CPLSPrintf(szFormat, sEnvelopeLayer.MinX);
124
0
            osBBOX += ", ";
125
0
            osBBOX += CPLSPrintf(szFormat, sEnvelopeLayer.MinY);
126
0
            osBBOX += ", ";
127
0
            if (bBBOX3D)
128
0
            {
129
0
                osBBOX += CPLSPrintf(szFormat, sEnvelopeLayer.MinZ);
130
0
                osBBOX += ", ";
131
0
            }
132
0
            osBBOX += CPLSPrintf(szFormat, sEnvelopeLayer.MaxX);
133
0
            osBBOX += ", ";
134
0
            osBBOX += CPLSPrintf(szFormat, sEnvelopeLayer.MaxY);
135
0
            if (bBBOX3D)
136
0
            {
137
0
                osBBOX += ", ";
138
0
                osBBOX += CPLSPrintf(szFormat, sEnvelopeLayer.MaxZ);
139
0
            }
140
0
            osBBOX += " ]";
141
142
0
            if (poDS_->GetFpOutputIsSeekable() &&
143
0
                osBBOX.size() + 9 < OGRGeoJSONDataSource::SPACE_FOR_BBOX)
144
0
            {
145
0
                VSIFSeekL(fp, poDS_->GetBBOXInsertLocation(), SEEK_SET);
146
0
                VSIFPrintfL(fp, "\"bbox\": %s,", osBBOX.c_str());
147
0
                VSIFSeekL(fp, 0, SEEK_END);
148
0
            }
149
0
            else
150
0
            {
151
0
                VSIFPrintfL(fp, ",\n\"bbox\": %s", osBBOX.c_str());
152
0
            }
153
0
        }
154
155
0
        VSIFPrintfL(fp, "\n}\n");
156
0
        fp->Flush();
157
0
    }
158
0
}
159
160
/************************************************************************/
161
/*                             SyncToDisk()                             */
162
/************************************************************************/
163
164
OGRErr OGRGeoJSONWriteLayer::SyncToDisk()
165
0
{
166
0
    if (m_nPositionBeforeFCClosed == 0 && poDS_->GetFpOutputIsSeekable())
167
0
    {
168
0
        FinishWriting();
169
0
    }
170
171
0
    return OGRERR_NONE;
172
0
}
173
174
/************************************************************************/
175
/*                           ICreateFeature()                           */
176
/************************************************************************/
177
178
OGRErr OGRGeoJSONWriteLayer::ICreateFeature(OGRFeature *poFeature)
179
0
{
180
0
    VSILFILE *fp = poDS_->GetOutputFile();
181
182
0
    OGRFeature *poFeatureToWrite;
183
0
    if (poCT_ != nullptr || bRFC7946_)
184
0
    {
185
0
        poFeatureToWrite = new OGRFeature(poFeatureDefn_);
186
0
        poFeatureToWrite->SetFrom(poFeature);
187
0
        poFeatureToWrite->SetFID(poFeature->GetFID());
188
0
        OGRGeometry *poGeometry = poFeatureToWrite->GetGeometryRef();
189
0
        if (poGeometry)
190
0
        {
191
0
            const char *const apszOptions[] = {
192
0
                bWrapDateLine_ ? "WRAPDATELINE=YES" : nullptr, nullptr};
193
0
            OGRGeometry *poNewGeom = OGRGeometryFactory::transformWithOptions(
194
0
                poGeometry, poCT_, const_cast<char **>(apszOptions),
195
0
                oTransformCache_);
196
0
            if (poNewGeom == nullptr)
197
0
            {
198
0
                delete poFeatureToWrite;
199
0
                return OGRERR_FAILURE;
200
0
            }
201
202
0
            OGREnvelope sEnvelope;
203
0
            poNewGeom->getEnvelope(&sEnvelope);
204
0
            if (sEnvelope.MinX < -180.0 || sEnvelope.MaxX > 180.0 ||
205
0
                sEnvelope.MinY < -90.0 || sEnvelope.MaxY > 90.0)
206
0
            {
207
0
                CPLError(CE_Failure, CPLE_AppDefined,
208
0
                         "Geometry extent outside of "
209
0
                         "[-180.0,180.0]x[-90.0,90.0] bounds");
210
0
                delete poFeatureToWrite;
211
0
                return OGRERR_FAILURE;
212
0
            }
213
214
0
            poFeatureToWrite->SetGeometryDirectly(poNewGeom);
215
0
        }
216
0
    }
217
0
    else
218
0
    {
219
0
        poFeatureToWrite = poFeature;
220
0
    }
221
222
    // Special processing to detect and repair invalid geometries due to
223
    // coordinate precision.
224
    // Normally drivers shouldn't do that as similar code is triggered by
225
    // setting the OGR_APPLY_GEOM_SET_PRECISION=YES configuration option by
226
    // the generic OGRLayer::CreateFeature() code path. But this code predates
227
    // its introduction and RFC99, and can be useful in RFC7946 mode due to
228
    // coordinate reprojection.
229
0
    std::string osReason;
230
0
    OGRGeometry *poOrigGeom = poFeature->GetGeometryRef();
231
0
    if (OGRGeometryFactory::haveGEOS() &&
232
0
        oWriteOptions_.nXYCoordPrecision >= 0 && poOrigGeom &&
233
0
        wkbFlatten(poOrigGeom->getGeometryType()) != wkbPoint &&
234
0
        poOrigGeom->IsValid(&osReason))
235
0
    {
236
0
        const double dfXYResolution =
237
0
            std::pow(10.0, double(-oWriteOptions_.nXYCoordPrecision));
238
0
        auto poNewGeom = std::unique_ptr<OGRGeometry>(
239
0
            poFeatureToWrite->GetGeometryRef()->clone());
240
0
        OGRGeomCoordinatePrecision sPrecision;
241
0
        sPrecision.dfXYResolution = dfXYResolution;
242
0
        poNewGeom->roundCoordinates(sPrecision);
243
0
        if (!poNewGeom->IsValid(&osReason))
244
0
        {
245
0
            std::unique_ptr<OGRGeometry> poValidGeom;
246
0
            if (poFeature == poFeatureToWrite)
247
0
            {
248
0
                CPLDebug("GeoJSON",
249
0
                         "Running SetPrecision() to correct an invalid "
250
0
                         "geometry due to reduced precision output");
251
0
                poValidGeom.reset(
252
0
                    poOrigGeom->SetPrecision(dfXYResolution, /* nFlags = */ 0));
253
0
            }
254
0
            else
255
0
            {
256
0
                CPLDebug("GeoJSON", "Running MakeValid() to correct an invalid "
257
0
                                    "geometry due to reduced precision output");
258
0
                poValidGeom.reset(poNewGeom->MakeValid());
259
0
                if (poValidGeom)
260
0
                {
261
0
                    auto poValidGeomRoundCoordinates =
262
0
                        std::unique_ptr<OGRGeometry>(poValidGeom->clone());
263
0
                    poValidGeomRoundCoordinates->roundCoordinates(sPrecision);
264
0
                    if (!poValidGeomRoundCoordinates->IsValid(&osReason))
265
0
                    {
266
0
                        CPLDebug("GeoJSON",
267
0
                                 "Running SetPrecision() to correct an invalid "
268
0
                                 "geometry due to reduced precision output");
269
0
                        auto poValidGeom2 = std::unique_ptr<OGRGeometry>(
270
0
                            poValidGeom->SetPrecision(dfXYResolution,
271
0
                                                      /* nFlags = */ 0));
272
0
                        if (poValidGeom2)
273
0
                            poValidGeom = std::move(poValidGeom2);
274
0
                    }
275
0
                }
276
0
                else
277
0
                {
278
0
                    CPLError(CE_Warning, CPLE_AppDefined,
279
0
                             "Geometry %s is not valid: %s",
280
0
                             poNewGeom->exportToWkt().c_str(),
281
0
                             osReason.c_str());
282
0
                }
283
0
            }
284
0
            if (poValidGeom)
285
0
            {
286
0
                if (poFeature == poFeatureToWrite)
287
0
                {
288
0
                    poFeatureToWrite = new OGRFeature(poFeatureDefn_);
289
0
                    poFeatureToWrite->SetFrom(poFeature);
290
0
                    poFeatureToWrite->SetFID(poFeature->GetFID());
291
0
                }
292
0
                poFeatureToWrite->SetGeometryDirectly(poValidGeom.release());
293
0
            }
294
0
        }
295
0
    }
296
297
0
    if (oWriteOptions_.bGenerateID && poFeatureToWrite->GetFID() == OGRNullFID)
298
0
    {
299
0
        poFeatureToWrite->SetFID(nOutCounter_);
300
0
    }
301
0
    json_object *poObj =
302
0
        OGRGeoJSONWriteFeature(poFeatureToWrite, oWriteOptions_);
303
0
    CPLAssert(nullptr != poObj);
304
305
0
    if (m_nPositionBeforeFCClosed)
306
0
    {
307
        // If we had called SyncToDisk() previously, undo its effects
308
0
        fp->Seek(m_nPositionBeforeFCClosed, SEEK_SET);
309
0
        m_nPositionBeforeFCClosed = 0;
310
0
    }
311
312
0
    if (nOutCounter_ > 0)
313
0
    {
314
        /* Separate "Feature" entries in "FeatureCollection" object. */
315
0
        VSIFPrintfL(fp, ",\n");
316
0
    }
317
0
    const char *pszJson = json_object_to_json_string_ext(
318
0
        poObj, JSON_C_TO_STRING_PLAIN
319
0
#ifdef JSON_C_TO_STRING_NOSLASHESCAPE
320
0
                   | JSON_C_TO_STRING_NOSLASHESCAPE
321
0
#endif
322
0
    );
323
324
0
    OGRErr eErr = OGRERR_NONE;
325
0
    size_t nLen = strlen(pszJson);
326
0
    if (!osForeignMembers_.empty())
327
0
    {
328
0
        if (nLen > 1 && pszJson[nLen - 1] == '}')
329
0
        {
330
0
            nLen -= 1;
331
0
        }
332
0
        else
333
0
        {
334
            // should not happen
335
0
            CPLError(CE_Failure, CPLE_AppDefined,
336
0
                     "Unexpected JSON output for feature. Cannot write foreign "
337
0
                     "member");
338
0
            osForeignMembers_.clear();
339
0
        }
340
0
    }
341
0
    if (VSIFWriteL(pszJson, nLen, 1, fp) != 1)
342
0
    {
343
0
        CPLError(CE_Failure, CPLE_FileIO, "Cannot write feature");
344
0
        eErr = OGRERR_FAILURE;
345
0
    }
346
0
    else if (!osForeignMembers_.empty() &&
347
0
             (VSIFWriteL(",", 1, 1, fp) != 1 ||
348
0
              VSIFWriteL(osForeignMembers_.c_str(), osForeignMembers_.size(), 1,
349
0
                         fp) != 1 ||
350
0
              VSIFWriteL("}", 1, 1, fp) != 1))
351
0
    {
352
0
        CPLError(CE_Failure, CPLE_FileIO, "Cannot write feature");
353
0
        eErr = OGRERR_FAILURE;
354
0
    }
355
356
0
    json_object_put(poObj);
357
358
0
    ++nOutCounter_;
359
360
0
    OGRGeometry *poGeometry = poFeatureToWrite->GetGeometryRef();
361
0
    if (poGeometry != nullptr && !poGeometry->IsEmpty())
362
0
    {
363
0
        OGREnvelope3D sEnvelope = OGRGeoJSONGetBBox(poGeometry, oWriteOptions_);
364
0
        if (poGeometry->getCoordinateDimension() == 3)
365
0
            bBBOX3D = true;
366
367
0
        if (!sEnvelopeLayer.IsInit())
368
0
        {
369
0
            sEnvelopeLayer = sEnvelope;
370
0
        }
371
0
        else if (oWriteOptions_.bBBOXRFC7946)
372
0
        {
373
0
            const bool bEnvelopeCrossAM = (sEnvelope.MinX > sEnvelope.MaxX);
374
0
            const bool bEnvelopeLayerCrossAM =
375
0
                (sEnvelopeLayer.MinX > sEnvelopeLayer.MaxX);
376
0
            if (bEnvelopeCrossAM)
377
0
            {
378
0
                if (bEnvelopeLayerCrossAM)
379
0
                {
380
0
                    sEnvelopeLayer.MinX =
381
0
                        std::min(sEnvelopeLayer.MinX, sEnvelope.MinX);
382
0
                    sEnvelopeLayer.MaxX =
383
0
                        std::max(sEnvelopeLayer.MaxX, sEnvelope.MaxX);
384
0
                }
385
0
                else
386
0
                {
387
0
                    if (sEnvelopeLayer.MinX > 0)
388
0
                    {
389
0
                        sEnvelopeLayer.MinX =
390
0
                            std::min(sEnvelopeLayer.MinX, sEnvelope.MinX);
391
0
                        sEnvelopeLayer.MaxX = sEnvelope.MaxX;
392
0
                    }
393
0
                    else if (sEnvelopeLayer.MaxX < 0)
394
0
                    {
395
0
                        sEnvelopeLayer.MaxX =
396
0
                            std::max(sEnvelopeLayer.MaxX, sEnvelope.MaxX);
397
0
                        sEnvelopeLayer.MinX = sEnvelope.MinX;
398
0
                    }
399
0
                    else
400
0
                    {
401
0
                        sEnvelopeLayer.MinX = -180.0;
402
0
                        sEnvelopeLayer.MaxX = 180.0;
403
0
                    }
404
0
                }
405
0
            }
406
0
            else if (bEnvelopeLayerCrossAM)
407
0
            {
408
0
                if (sEnvelope.MinX > 0)
409
0
                {
410
0
                    sEnvelopeLayer.MinX =
411
0
                        std::min(sEnvelopeLayer.MinX, sEnvelope.MinX);
412
0
                }
413
0
                else if (sEnvelope.MaxX < 0)
414
0
                {
415
0
                    sEnvelopeLayer.MaxX =
416
0
                        std::max(sEnvelopeLayer.MaxX, sEnvelope.MaxX);
417
0
                }
418
0
                else
419
0
                {
420
0
                    sEnvelopeLayer.MinX = -180.0;
421
0
                    sEnvelopeLayer.MaxX = 180.0;
422
0
                }
423
0
            }
424
0
            else
425
0
            {
426
0
                sEnvelopeLayer.MinX =
427
0
                    std::min(sEnvelopeLayer.MinX, sEnvelope.MinX);
428
0
                sEnvelopeLayer.MaxX =
429
0
                    std::max(sEnvelopeLayer.MaxX, sEnvelope.MaxX);
430
0
            }
431
432
0
            sEnvelopeLayer.MinY = std::min(sEnvelopeLayer.MinY, sEnvelope.MinY);
433
0
            sEnvelopeLayer.MaxY = std::max(sEnvelopeLayer.MaxY, sEnvelope.MaxY);
434
0
        }
435
0
        else
436
0
        {
437
0
            sEnvelopeLayer.Merge(sEnvelope);
438
0
        }
439
0
    }
440
441
0
    if (poFeatureToWrite != poFeature)
442
0
        delete poFeatureToWrite;
443
444
0
    return eErr;
445
0
}
446
447
/************************************************************************/
448
/*                            CreateField()                             */
449
/************************************************************************/
450
451
OGRErr OGRGeoJSONWriteLayer::CreateField(const OGRFieldDefn *poField,
452
                                         int /* bApproxOK */)
453
0
{
454
0
    if (poFeatureDefn_->GetFieldIndexCaseSensitive(poField->GetNameRef()) >= 0)
455
0
    {
456
0
        CPLDebug("GeoJSON", "Field '%s' already present in schema",
457
0
                 poField->GetNameRef());
458
459
        // TODO - mloskot: Is this return code correct?
460
0
        return OGRERR_NONE;
461
0
    }
462
463
0
    poFeatureDefn_->AddFieldDefn(poField);
464
465
0
    return OGRERR_NONE;
466
0
}
467
468
/************************************************************************/
469
/*                           TestCapability()                           */
470
/************************************************************************/
471
472
int OGRGeoJSONWriteLayer::TestCapability(const char *pszCap) const
473
0
{
474
0
    if (EQUAL(pszCap, OLCCreateField))
475
0
        return TRUE;
476
0
    else if (EQUAL(pszCap, OLCSequentialWrite))
477
0
        return TRUE;
478
0
    else if (EQUAL(pszCap, OLCStringsAsUTF8))
479
0
        return TRUE;
480
0
    return FALSE;
481
0
}
482
483
/************************************************************************/
484
/*                             IGetExtent()                             */
485
/************************************************************************/
486
487
OGRErr OGRGeoJSONWriteLayer::IGetExtent(int /*iGeomField*/,
488
                                        OGREnvelope *psExtent, bool)
489
0
{
490
0
    if (sEnvelopeLayer.IsInit())
491
0
    {
492
0
        *psExtent = sEnvelopeLayer;
493
0
        return OGRERR_NONE;
494
0
    }
495
0
    return OGRERR_FAILURE;
496
0
}
497
498
/************************************************************************/
499
/*                             GetDataset()                             */
500
/************************************************************************/
501
502
GDALDataset *OGRGeoJSONWriteLayer::GetDataset()
503
0
{
504
0
    return poDS_;
505
0
}