Coverage Report

Created: 2026-09-26 08:22

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/gdal/ogr/ogrsf_frmts/jsonfg/ogrjsonfgwritelayer.cpp
Line
Count
Source
1
/******************************************************************************
2
 *
3
 * Project:  OpenGIS Simple Features Reference Implementation
4
 * Purpose:  Implementation of OGC Features and Geometries JSON (JSON-FG)
5
 * Author:   Even Rouault <even.rouault at spatialys.com>
6
 *
7
 ******************************************************************************
8
 * Copyright (c) 2023, Even Rouault <even.rouault at spatialys.com>
9
 *
10
 * SPDX-License-Identifier: MIT
11
 ****************************************************************************/
12
13
#include "ogr_jsonfg.h"
14
#include "cpl_time.h"
15
#include "ogrlibjsonutils.h"  // OGRJSonParse()
16
17
#include <algorithm>
18
19
/************************************************************************/
20
/*                        OGRJSONFGWriteLayer()                         */
21
/************************************************************************/
22
23
OGRJSONFGWriteLayer::OGRJSONFGWriteLayer(
24
    const char *pszName, const OGRSpatialReference *poSRS,
25
    std::unique_ptr<OGRCoordinateTransformation> &&poCTToWGS84,
26
    const std::string &osCoordRefSys, OGRwkbGeometryType eGType,
27
    CSLConstList papszOptions, OGRJSONFGDataset *poDS)
28
0
    : poDS_(poDS), poFeatureDefn_(new OGRFeatureDefn(pszName)),
29
0
      poCTToWGS84_(std::move(poCTToWGS84)), osCoordRefSys_(osCoordRefSys)
30
0
{
31
0
    poFeatureDefn_->Reference();
32
0
    poFeatureDefn_->SetGeomType(eGType);
33
0
    if (eGType != wkbNone && poSRS)
34
0
    {
35
0
        auto poSRSClone = poSRS->Clone();
36
0
        poFeatureDefn_->GetGeomFieldDefn(0)->SetSpatialRef(poSRSClone);
37
0
        poSRSClone->Release();
38
0
        m_bMustSwapForPlace = OGRJSONFGMustSwapXY(poSRS);
39
0
    }
40
0
    SetDescription(poFeatureDefn_->GetName());
41
42
0
    bIsWGS84CRS_ =
43
0
        osCoordRefSys_.find("\"http://www.opengis.net/def/crs/OGC/0/CRS84\"") !=
44
0
            std::string::npos ||
45
0
        osCoordRefSys_.find(
46
0
            "\"http://www.opengis.net/def/crs/OGC/0/CRS84h\"") !=
47
0
            std::string::npos ||
48
0
        osCoordRefSys_.find("\"http://www.opengis.net/def/crs/EPSG/0/4326\"") !=
49
0
            std::string::npos ||
50
0
        osCoordRefSys_.find("\"http://www.opengis.net/def/crs/EPSG/0/4979\"") !=
51
0
            std::string::npos;
52
53
0
    oWriteOptions_.nXYCoordPrecision = atoi(CSLFetchNameValueDef(
54
0
        papszOptions, "XY_COORD_PRECISION_GEOMETRY", "-1"));
55
0
    oWriteOptions_.nZCoordPrecision = atoi(
56
0
        CSLFetchNameValueDef(papszOptions, "Z_COORD_PRECISION_GEOMETRY", "-1"));
57
0
    oWriteOptions_.nSignificantFigures =
58
0
        atoi(CSLFetchNameValueDef(papszOptions, "SIGNIFICANT_FIGURES", "-1"));
59
0
    oWriteOptions_.SetRFC7946Settings();
60
0
    oWriteOptions_.SetIDOptions(papszOptions);
61
62
0
    oWriteOptionsPlace_.nXYCoordPrecision = atoi(
63
0
        CSLFetchNameValueDef(papszOptions, "XY_COORD_PRECISION_PLACE", "-1"));
64
0
    oWriteOptionsPlace_.nZCoordPrecision = atoi(
65
0
        CSLFetchNameValueDef(papszOptions, "Z_COORD_PRECISION_PLACE", "-1"));
66
0
    oWriteOptionsPlace_.nSignificantFigures =
67
0
        atoi(CSLFetchNameValueDef(papszOptions, "SIGNIFICANT_FIGURES", "-1"));
68
0
    oWriteOptionsPlace_.bAllowCurve = true;
69
0
    oWriteOptionsPlace_.bAllowMeasure = true;
70
71
0
    bWriteFallbackGeometry_ = CPLTestBool(
72
0
        CSLFetchNameValueDef(papszOptions, "WRITE_GEOMETRY", "TRUE"));
73
74
0
    osMeasureUnit_ = CSLFetchNameValueDef(papszOptions, "MEASURE_UNIT", "");
75
0
    osMeasureDescription_ =
76
0
        CSLFetchNameValueDef(papszOptions, "MEASURE_DESCRIPTION", "");
77
78
0
    VSILFILE *fp = poDS_->GetOutputFile();
79
0
    if (poDS_->IsSingleOutputLayer())
80
0
    {
81
0
        auto poFeatureType = json_object_new_string(pszName);
82
0
        VSIFPrintfL(fp, "\"featureType\" : %s,\n",
83
0
                    json_object_to_json_string_ext(poFeatureType,
84
0
                                                   JSON_C_TO_STRING_SPACED));
85
0
        json_object_put(poFeatureType);
86
0
        if (!osCoordRefSys.empty())
87
0
            VSIFPrintfL(fp, "\"coordRefSys\" : %s,\n", osCoordRefSys.c_str());
88
89
0
        if (!osMeasureUnit_.empty() || !osMeasureDescription_.empty())
90
0
        {
91
0
            m_bMeasureWritten = true;
92
0
            bLayerLevelMeasuresWritten_ = true;
93
0
            VSIFPrintfL(fp, "\"measures\": {\n");
94
0
            VSIFPrintfL(fp, "  \"enabled\": true");
95
0
            if (!osMeasureUnit_.empty())
96
0
            {
97
0
                auto poUnit = json_object_new_string(osMeasureUnit_.c_str());
98
0
                VSIFPrintfL(fp, ",\n  \"unit\": %s",
99
0
                            json_object_to_json_string_ext(
100
0
                                poUnit, JSON_C_TO_STRING_SPACED));
101
0
                json_object_put(poUnit);
102
0
            }
103
0
            if (!osMeasureDescription_.empty())
104
0
            {
105
0
                auto poDescription =
106
0
                    json_object_new_string(osMeasureDescription_.c_str());
107
0
                VSIFPrintfL(fp, ",\n  \"description\": %s",
108
0
                            json_object_to_json_string_ext(
109
0
                                poDescription, JSON_C_TO_STRING_SPACED));
110
0
                json_object_put(poDescription);
111
0
            }
112
0
            VSIFPrintfL(fp, "\n},\n");
113
0
        }
114
0
    }
115
0
}
116
117
/************************************************************************/
118
/*                        ~OGRJSONFGWriteLayer()                        */
119
/************************************************************************/
120
121
OGRJSONFGWriteLayer::~OGRJSONFGWriteLayer()
122
0
{
123
0
    poFeatureDefn_->Release();
124
0
}
125
126
/************************************************************************/
127
/*                             SyncToDisk()                             */
128
/************************************************************************/
129
130
OGRErr OGRJSONFGWriteLayer::SyncToDisk()
131
0
{
132
0
    return poDS_->SyncToDiskInternal();
133
0
}
134
135
/************************************************************************/
136
/*                      GetValueAsDateOrDateTime()                      */
137
/************************************************************************/
138
139
static const char *GetValueAsDateOrDateTime(const OGRField *psRawValue,
140
                                            OGRFieldType eType)
141
0
{
142
0
    if (eType == OFTDate)
143
0
    {
144
0
        return CPLSPrintf("%04d-%02d-%02d", psRawValue->Date.Year,
145
0
                          psRawValue->Date.Month, psRawValue->Date.Day);
146
0
    }
147
0
    else
148
0
    {
149
0
        struct tm brokenDown;
150
0
        memset(&brokenDown, 0, sizeof(brokenDown));
151
0
        brokenDown.tm_year = psRawValue->Date.Year - 1900;
152
0
        brokenDown.tm_mon = psRawValue->Date.Month - 1;
153
0
        brokenDown.tm_mday = psRawValue->Date.Day;
154
0
        brokenDown.tm_hour = psRawValue->Date.Hour;
155
0
        brokenDown.tm_min = psRawValue->Date.Minute;
156
0
        brokenDown.tm_sec = 0;
157
0
        if (psRawValue->Date.TZFlag > 0)
158
0
        {
159
            // Force to UTC
160
0
            GIntBig nVal = CPLYMDHMSToUnixTime(&brokenDown);
161
0
            nVal -= (psRawValue->Date.TZFlag - 100) * 15 * 60;
162
0
            CPLUnixTimeToYMDHMS(nVal, &brokenDown);
163
0
        }
164
0
        if (std::fabs(std::round(psRawValue->Date.Second) -
165
0
                      psRawValue->Date.Second) < 1e-3)
166
0
        {
167
0
            return CPLSPrintf(
168
0
                "%04d-%02d-%02dT%02d:%02d:%02dZ", brokenDown.tm_year + 1900,
169
0
                brokenDown.tm_mon + 1, brokenDown.tm_mday, brokenDown.tm_hour,
170
0
                brokenDown.tm_min,
171
0
                static_cast<int>(std::round(psRawValue->Date.Second)));
172
0
        }
173
0
        else
174
0
        {
175
0
            return CPLSPrintf("%04d-%02d-%02dT%02d:%02d:%06.3fZ",
176
0
                              brokenDown.tm_year + 1900, brokenDown.tm_mon + 1,
177
0
                              brokenDown.tm_mday, brokenDown.tm_hour,
178
0
                              brokenDown.tm_min, psRawValue->Date.Second);
179
0
        }
180
0
    }
181
0
}
182
183
/************************************************************************/
184
/*                       OGRJSONFGWriteGeometry()                       */
185
/************************************************************************/
186
187
static json_object *
188
OGRJSONFGWriteGeometry(const OGRGeometry *poGeometry,
189
                       const OGRGeoJSONWriteOptions &oOptions)
190
0
{
191
0
    if (wkbFlatten(poGeometry->getGeometryType()) == wkbPolyhedralSurface)
192
0
    {
193
0
        const auto poPS = poGeometry->toPolyhedralSurface();
194
0
        json_object *poObj = json_object_new_object();
195
0
        json_object_object_add(poObj, "type",
196
0
                               json_object_new_string("Polyhedron"));
197
0
        json_object *poCoordinates = json_object_new_array();
198
0
        json_object_object_add(poObj, "coordinates", poCoordinates);
199
0
        json_object *poOuterShell = json_object_new_array();
200
0
        json_object_array_add(poCoordinates, poOuterShell);
201
0
        for (const auto *poPoly : *poPS)
202
0
        {
203
0
            json_object_array_add(poOuterShell,
204
0
                                  OGRGeoJSONWritePolygon(poPoly, oOptions));
205
0
        }
206
0
        return poObj;
207
0
    }
208
0
    else
209
0
    {
210
0
        return nullptr;
211
0
    }
212
0
}
213
214
/************************************************************************/
215
/*                           ICreateFeature()                           */
216
/************************************************************************/
217
218
OGRErr OGRJSONFGWriteLayer::ICreateFeature(OGRFeature *poFeature)
219
0
{
220
0
    VSILFILE *fp = poDS_->GetOutputFile();
221
0
    poDS_->BeforeCreateFeature();
222
223
0
    if (oWriteOptions_.bGenerateID && poFeature->GetFID() == OGRNullFID)
224
0
    {
225
0
        poFeature->SetFID(nOutCounter_);
226
0
    }
227
228
0
    json_object *poObj = json_object_new_object();
229
230
0
    json_object_object_add(poObj, "type", json_object_new_string("Feature"));
231
232
    /* -------------------------------------------------------------------- */
233
    /*      Write FID if available                                          */
234
    /* -------------------------------------------------------------------- */
235
0
    OGRGeoJSONWriteId(poFeature, poObj, /* bIdAlreadyWritten = */ false,
236
0
                      oWriteOptions_);
237
238
0
    if (!poDS_->IsSingleOutputLayer())
239
0
    {
240
0
        json_object_object_add(poObj, "featureType",
241
0
                               json_object_new_string(GetDescription()));
242
0
        if (!osCoordRefSys_.empty() && !bIsWGS84CRS_)
243
0
        {
244
0
            json_object *poCoordRefSys = nullptr;
245
0
            CPL_IGNORE_RET_VAL(
246
0
                OGRJSonParse(osCoordRefSys_.c_str(), &poCoordRefSys));
247
0
            json_object_object_add(poObj, "coordRefSys", poCoordRefSys);
248
0
        }
249
0
    }
250
251
    /* -------------------------------------------------------------------- */
252
    /*      Write feature attributes to "properties" object.                */
253
    /* -------------------------------------------------------------------- */
254
0
    json_object *poObjProps = OGRGeoJSONWriteAttributes(
255
0
        poFeature, /* bWriteIdIfFoundInAttributes = */ true, oWriteOptions_);
256
257
    /* -------------------------------------------------------------------- */
258
    /*      Deal with time properties.                                      */
259
    /* -------------------------------------------------------------------- */
260
0
    json_object *poTime = nullptr;
261
0
    int nFieldTimeIdx = poFeatureDefn_->GetFieldIndex("jsonfg_time");
262
0
    if (nFieldTimeIdx < 0)
263
0
        nFieldTimeIdx = poFeatureDefn_->GetFieldIndex("time");
264
0
    if (nFieldTimeIdx >= 0 && poFeature->IsFieldSetAndNotNull(nFieldTimeIdx))
265
0
    {
266
0
        const auto poFieldDefn = poFeatureDefn_->GetFieldDefn(nFieldTimeIdx);
267
0
        const auto eType = poFieldDefn->GetType();
268
0
        if (eType == OFTDate || eType == OFTDateTime)
269
0
        {
270
0
            json_object_object_del(poObjProps, poFieldDefn->GetNameRef());
271
0
            poTime = json_object_new_object();
272
0
            json_object_object_add(
273
0
                poTime, eType == OFTDate ? "date" : "timestamp",
274
0
                json_object_new_string(GetValueAsDateOrDateTime(
275
0
                    poFeature->GetRawFieldRef(nFieldTimeIdx), eType)));
276
0
        }
277
0
    }
278
0
    else
279
0
    {
280
0
        bool bHasStartOrStop = false;
281
0
        json_object *poTimeStart = nullptr;
282
0
        int nFieldTimeStartIdx =
283
0
            poFeatureDefn_->GetFieldIndex("jsonfg_time_start");
284
0
        if (nFieldTimeStartIdx < 0)
285
0
            nFieldTimeStartIdx = poFeatureDefn_->GetFieldIndex("time_start");
286
0
        if (nFieldTimeStartIdx >= 0 &&
287
0
            poFeature->IsFieldSetAndNotNull(nFieldTimeStartIdx))
288
0
        {
289
0
            const auto poFieldDefnStart =
290
0
                poFeatureDefn_->GetFieldDefn(nFieldTimeStartIdx);
291
0
            const auto eType = poFieldDefnStart->GetType();
292
0
            if (eType == OFTDate || eType == OFTDateTime)
293
0
            {
294
0
                json_object_object_del(poObjProps,
295
0
                                       poFieldDefnStart->GetNameRef());
296
0
                poTimeStart = json_object_new_string(GetValueAsDateOrDateTime(
297
0
                    poFeature->GetRawFieldRef(nFieldTimeStartIdx), eType));
298
0
                bHasStartOrStop = true;
299
0
            }
300
0
        }
301
302
0
        json_object *poTimeEnd = nullptr;
303
0
        int nFieldTimeEndIdx = poFeatureDefn_->GetFieldIndex("jsonfg_time_end");
304
0
        if (nFieldTimeEndIdx < 0)
305
0
            nFieldTimeEndIdx = poFeatureDefn_->GetFieldIndex("time_end");
306
0
        if (nFieldTimeEndIdx >= 0 &&
307
0
            poFeature->IsFieldSetAndNotNull(nFieldTimeEndIdx))
308
0
        {
309
0
            const auto poFieldDefnEnd =
310
0
                poFeatureDefn_->GetFieldDefn(nFieldTimeEndIdx);
311
0
            const auto eType = poFieldDefnEnd->GetType();
312
0
            if (eType == OFTDate || eType == OFTDateTime)
313
0
            {
314
0
                json_object_object_del(poObjProps,
315
0
                                       poFieldDefnEnd->GetNameRef());
316
0
                poTimeEnd = json_object_new_string(GetValueAsDateOrDateTime(
317
0
                    poFeature->GetRawFieldRef(nFieldTimeEndIdx), eType));
318
0
                bHasStartOrStop = true;
319
0
            }
320
0
        }
321
322
0
        if (bHasStartOrStop)
323
0
        {
324
0
            poTime = json_object_new_object();
325
0
            json_object *poInterval = json_object_new_array();
326
0
            json_object_object_add(poTime, "interval", poInterval);
327
0
            json_object_array_add(poInterval,
328
0
                                  poTimeStart ? poTimeStart
329
0
                                              : json_object_new_string(".."));
330
0
            json_object_array_add(poInterval,
331
0
                                  poTimeEnd ? poTimeEnd
332
0
                                            : json_object_new_string(".."));
333
0
        }
334
0
    }
335
336
0
    json_object_object_add(poObj, "properties", poObjProps);
337
338
    /* -------------------------------------------------------------------- */
339
    /*      Write place and/or geometry                                     */
340
    /* -------------------------------------------------------------------- */
341
0
    json_object *poJSONGeometry = nullptr;
342
0
    json_object *poPlace = nullptr;
343
0
    if (const OGRGeometry *poGeom = poFeature->GetGeometryRef())
344
0
    {
345
0
        const bool bHasCurve = poGeom->hasCurveGeometry(true);
346
0
        if (bHasCurve)
347
0
            m_bCurveWritten = true;
348
0
        const bool bHasMeasure = poGeom->IsMeasured();
349
0
        if (bHasMeasure)
350
0
            m_bMeasureWritten = true;
351
0
        bool bWritePlace = false;
352
0
        if (wkbFlatten(poGeom->getGeometryType()) == wkbPolyhedralSurface)
353
0
        {
354
0
            m_bPolyhedraWritten = true;
355
0
            bWritePlace = true;
356
0
        }
357
0
        else if (bIsWGS84CRS_)
358
0
        {
359
0
            bWritePlace = bHasCurve || bHasMeasure;
360
0
            poJSONGeometry = OGRGeoJSONWriteGeometry(poGeom, oWriteOptions_);
361
0
        }
362
0
        else
363
0
        {
364
0
            if (bWriteFallbackGeometry_ && poCTToWGS84_)
365
0
            {
366
0
                auto poGeomClone =
367
0
                    std::unique_ptr<OGRGeometry>(poGeom->clone());
368
0
                if (poGeomClone->transform(poCTToWGS84_.get()) == OGRERR_NONE)
369
0
                {
370
0
                    poJSONGeometry = OGRGeoJSONWriteGeometry(poGeomClone.get(),
371
0
                                                             oWriteOptions_);
372
0
                }
373
0
            }
374
375
0
            bWritePlace = true;
376
0
        }
377
378
0
        std::unique_ptr<OGRGeometry> poGeomClone;  // keep in that scope
379
0
        if (bWritePlace)
380
0
        {
381
0
            if (m_bMustSwapForPlace)
382
0
            {
383
0
                poGeomClone.reset(poGeom->clone());
384
0
                poGeomClone->swapXY();
385
0
                poGeom = poGeomClone.get();
386
0
            }
387
0
            if (wkbFlatten(poGeom->getGeometryType()) == wkbPolyhedralSurface)
388
0
            {
389
0
                poPlace = OGRJSONFGWriteGeometry(poGeom, oWriteOptionsPlace_);
390
0
            }
391
0
            else
392
0
            {
393
0
                poPlace = OGRGeoJSONWriteGeometry(poGeom, oWriteOptionsPlace_);
394
0
            }
395
0
        }
396
397
0
        if (poGeom->IsMeasured())
398
0
        {
399
0
            if (!bLayerLevelMeasuresWritten_)
400
0
            {
401
0
                json_object *poMeasures = json_object_new_object();
402
0
                json_object_object_add(poMeasures, "enabled",
403
0
                                       json_object_new_boolean(true));
404
0
                if (!poDS_->IsSingleOutputLayer())
405
0
                {
406
0
                    if (!osMeasureUnit_.empty())
407
0
                    {
408
0
                        json_object_object_add(
409
0
                            poMeasures, "unit",
410
0
                            json_object_new_string(osMeasureUnit_.c_str()));
411
0
                    }
412
0
                    if (!osMeasureDescription_.empty())
413
0
                    {
414
0
                        json_object_object_add(
415
0
                            poMeasures, "description",
416
0
                            json_object_new_string(
417
0
                                osMeasureDescription_.c_str()));
418
0
                    }
419
0
                }
420
0
                json_object_object_add(poObj, "measures", poMeasures);
421
0
            }
422
0
        }
423
0
        else if (bLayerLevelMeasuresWritten_)
424
0
        {
425
0
            json_object *poMeasures = json_object_new_object();
426
0
            json_object_object_add(poMeasures, "enabled",
427
0
                                   json_object_new_boolean(false));
428
0
            json_object_object_add(poObj, "measures", poMeasures);
429
0
        }
430
0
    }
431
432
0
    json_object_object_add(poObj, "geometry", poJSONGeometry);
433
0
    json_object_object_add(poObj, "place", poPlace);
434
435
0
    json_object_object_add(poObj, "time", poTime);
436
437
0
    VSIFPrintfL(fp, "%s",
438
0
                json_object_to_json_string_ext(
439
0
                    poObj, JSON_C_TO_STRING_SPACED
440
0
#ifdef JSON_C_TO_STRING_NOSLASHESCAPE
441
0
                               | JSON_C_TO_STRING_NOSLASHESCAPE
442
0
#endif
443
0
                    ));
444
445
0
    json_object_put(poObj);
446
447
0
    ++nOutCounter_;
448
449
0
    return OGRERR_NONE;
450
0
}
451
452
/************************************************************************/
453
/*                            CreateField()                             */
454
/************************************************************************/
455
456
OGRErr OGRJSONFGWriteLayer::CreateField(const OGRFieldDefn *poField,
457
                                        int /* bApproxOK */)
458
0
{
459
0
    if (poFeatureDefn_->GetFieldIndexCaseSensitive(poField->GetNameRef()) >= 0)
460
0
    {
461
0
        CPLDebug("JSONFG", "Field '%s' already present in schema",
462
0
                 poField->GetNameRef());
463
464
0
        return OGRERR_NONE;
465
0
    }
466
467
0
    poFeatureDefn_->AddFieldDefn(poField);
468
469
0
    return OGRERR_NONE;
470
0
}
471
472
/************************************************************************/
473
/*                           TestCapability()                           */
474
/************************************************************************/
475
476
bool OGRJSONFGWriteLayer::TestCapability(const char *pszCap) const
477
0
{
478
0
    if (EQUAL(pszCap, OLCCreateField))
479
0
        return TRUE;
480
0
    else if (EQUAL(pszCap, OLCSequentialWrite))
481
0
        return TRUE;
482
0
    else if (EQUAL(pszCap, OLCStringsAsUTF8))
483
0
        return TRUE;
484
0
    else if (EQUAL(pszCap, OLCMeasuredGeometries) ||
485
0
             EQUAL(pszCap, OLCZGeometries) || EQUAL(pszCap, OLCCurveGeometries))
486
0
        return TRUE;
487
0
    return FALSE;
488
0
}
489
490
/************************************************************************/
491
/*                             GetDataset()                             */
492
/************************************************************************/
493
494
GDALDataset *OGRJSONFGWriteLayer::GetDataset()
495
0
{
496
0
    return poDS_;
497
0
}