Coverage Report

Created: 2025-06-09 08:44

/src/gdal/ogr/ogrsf_frmts/jsonfg/ogrjsonfgwritelayer.cpp
Line
Count
Source (jump to first uncovered line)
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_ = osCoordRefSys_.find("[OGC:CRS84]") != std::string::npos ||
43
0
                   osCoordRefSys_.find("[OGC:CRS84h]") != std::string::npos ||
44
0
                   osCoordRefSys_.find("[EPSG:4326]") != std::string::npos ||
45
0
                   osCoordRefSys_.find("[EPSG:4979]") != std::string::npos;
46
47
0
    oWriteOptions_.nXYCoordPrecision = atoi(CSLFetchNameValueDef(
48
0
        papszOptions, "XY_COORD_PRECISION_GEOMETRY", "-1"));
49
0
    oWriteOptions_.nZCoordPrecision = atoi(
50
0
        CSLFetchNameValueDef(papszOptions, "Z_COORD_PRECISION_GEOMETRY", "-1"));
51
0
    oWriteOptions_.nSignificantFigures =
52
0
        atoi(CSLFetchNameValueDef(papszOptions, "SIGNIFICANT_FIGURES", "-1"));
53
0
    oWriteOptions_.SetRFC7946Settings();
54
0
    oWriteOptions_.SetIDOptions(papszOptions);
55
56
0
    oWriteOptionsPlace_.nXYCoordPrecision = atoi(
57
0
        CSLFetchNameValueDef(papszOptions, "XY_COORD_PRECISION_PLACE", "-1"));
58
0
    oWriteOptionsPlace_.nZCoordPrecision = atoi(
59
0
        CSLFetchNameValueDef(papszOptions, "Z_COORD_PRECISION_PLACE", "-1"));
60
0
    oWriteOptionsPlace_.nSignificantFigures =
61
0
        atoi(CSLFetchNameValueDef(papszOptions, "SIGNIFICANT_FIGURES", "-1"));
62
63
0
    bWriteFallbackGeometry_ = CPLTestBool(
64
0
        CSLFetchNameValueDef(papszOptions, "WRITE_GEOMETRY", "TRUE"));
65
66
0
    VSILFILE *fp = poDS_->GetOutputFile();
67
0
    if (poDS_->IsSingleOutputLayer())
68
0
    {
69
0
        auto poFeatureType = json_object_new_string(pszName);
70
0
        VSIFPrintfL(fp, "\"featureType\" : %s,\n",
71
0
                    json_object_to_json_string_ext(poFeatureType,
72
0
                                                   JSON_C_TO_STRING_SPACED));
73
0
        json_object_put(poFeatureType);
74
0
        if (!osCoordRefSys.empty())
75
0
            VSIFPrintfL(fp, "\"coordRefSys\" : %s,\n", osCoordRefSys.c_str());
76
0
    }
77
0
}
78
79
/************************************************************************/
80
/*                        ~OGRJSONFGWriteLayer()                        */
81
/************************************************************************/
82
83
OGRJSONFGWriteLayer::~OGRJSONFGWriteLayer()
84
0
{
85
0
    poFeatureDefn_->Release();
86
0
}
87
88
/************************************************************************/
89
/*                           SyncToDisk()                               */
90
/************************************************************************/
91
92
OGRErr OGRJSONFGWriteLayer::SyncToDisk()
93
0
{
94
0
    return poDS_->SyncToDiskInternal();
95
0
}
96
97
/************************************************************************/
98
/*                       GetValueAsDateOrDateTime()                     */
99
/************************************************************************/
100
101
static const char *GetValueAsDateOrDateTime(const OGRField *psRawValue,
102
                                            OGRFieldType eType)
103
0
{
104
0
    if (eType == OFTDate)
105
0
    {
106
0
        return CPLSPrintf("%04d-%02d-%02d", psRawValue->Date.Year,
107
0
                          psRawValue->Date.Month, psRawValue->Date.Day);
108
0
    }
109
0
    else
110
0
    {
111
0
        struct tm brokenDown;
112
0
        memset(&brokenDown, 0, sizeof(brokenDown));
113
0
        brokenDown.tm_year = psRawValue->Date.Year - 1900;
114
0
        brokenDown.tm_mon = psRawValue->Date.Month - 1;
115
0
        brokenDown.tm_mday = psRawValue->Date.Day;
116
0
        brokenDown.tm_hour = psRawValue->Date.Hour;
117
0
        brokenDown.tm_min = psRawValue->Date.Minute;
118
0
        brokenDown.tm_sec = 0;
119
0
        if (psRawValue->Date.TZFlag > 0)
120
0
        {
121
            // Force to UTC
122
0
            GIntBig nVal = CPLYMDHMSToUnixTime(&brokenDown);
123
0
            nVal -= (psRawValue->Date.TZFlag - 100) * 15 * 60;
124
0
            CPLUnixTimeToYMDHMS(nVal, &brokenDown);
125
0
        }
126
0
        if (std::fabs(std::round(psRawValue->Date.Second) -
127
0
                      psRawValue->Date.Second) < 1e-3)
128
0
        {
129
0
            return CPLSPrintf(
130
0
                "%04d-%02d-%02dT%02d:%02d:%02dZ", brokenDown.tm_year + 1900,
131
0
                brokenDown.tm_mon + 1, brokenDown.tm_mday, brokenDown.tm_hour,
132
0
                brokenDown.tm_min,
133
0
                static_cast<int>(std::round(psRawValue->Date.Second)));
134
0
        }
135
0
        else
136
0
        {
137
0
            return CPLSPrintf("%04d-%02d-%02dT%02d:%02d:%06.3fZ",
138
0
                              brokenDown.tm_year + 1900, brokenDown.tm_mon + 1,
139
0
                              brokenDown.tm_mday, brokenDown.tm_hour,
140
0
                              brokenDown.tm_min, psRawValue->Date.Second);
141
0
        }
142
0
    }
143
0
}
144
145
/************************************************************************/
146
/*                     OGRJSONFGWriteGeometry()                         */
147
/************************************************************************/
148
149
static json_object *
150
OGRJSONFGWriteGeometry(const OGRGeometry *poGeometry,
151
                       const OGRGeoJSONWriteOptions &oOptions)
152
0
{
153
0
    if (wkbFlatten(poGeometry->getGeometryType()) == wkbPolyhedralSurface)
154
0
    {
155
0
        const auto poPS = poGeometry->toPolyhedralSurface();
156
0
        json_object *poObj = json_object_new_object();
157
0
        json_object_object_add(poObj, "type",
158
0
                               json_object_new_string("Polyhedron"));
159
0
        json_object *poCoordinates = json_object_new_array();
160
0
        json_object_object_add(poObj, "coordinates", poCoordinates);
161
0
        json_object *poOuterShell = json_object_new_array();
162
0
        json_object_array_add(poCoordinates, poOuterShell);
163
0
        for (const auto *poPoly : *poPS)
164
0
        {
165
0
            json_object_array_add(poOuterShell,
166
0
                                  OGRGeoJSONWritePolygon(poPoly, oOptions));
167
0
        }
168
0
        return poObj;
169
0
    }
170
0
    else
171
0
    {
172
0
        return nullptr;
173
0
    }
174
0
}
175
176
/************************************************************************/
177
/*                           ICreateFeature()                           */
178
/************************************************************************/
179
180
OGRErr OGRJSONFGWriteLayer::ICreateFeature(OGRFeature *poFeature)
181
0
{
182
0
    VSILFILE *fp = poDS_->GetOutputFile();
183
0
    poDS_->BeforeCreateFeature();
184
185
0
    if (oWriteOptions_.bGenerateID && poFeature->GetFID() == OGRNullFID)
186
0
    {
187
0
        poFeature->SetFID(nOutCounter_);
188
0
    }
189
190
0
    json_object *poObj = json_object_new_object();
191
192
0
    json_object_object_add(poObj, "type", json_object_new_string("Feature"));
193
194
    /* -------------------------------------------------------------------- */
195
    /*      Write FID if available                                          */
196
    /* -------------------------------------------------------------------- */
197
0
    OGRGeoJSONWriteId(poFeature, poObj, /* bIdAlreadyWritten = */ false,
198
0
                      oWriteOptions_);
199
200
0
    if (!poDS_->IsSingleOutputLayer())
201
0
    {
202
0
        json_object_object_add(poObj, "featureType",
203
0
                               json_object_new_string(GetDescription()));
204
0
        if (!osCoordRefSys_.empty() && !bIsWGS84CRS_)
205
0
        {
206
0
            json_object *poCoordRefSys = nullptr;
207
0
            CPL_IGNORE_RET_VAL(
208
0
                OGRJSonParse(osCoordRefSys_.c_str(), &poCoordRefSys));
209
0
            json_object_object_add(poObj, "coordRefSys", poCoordRefSys);
210
0
        }
211
0
    }
212
213
    /* -------------------------------------------------------------------- */
214
    /*      Write feature attributes to "properties" object.                */
215
    /* -------------------------------------------------------------------- */
216
0
    json_object *poObjProps = OGRGeoJSONWriteAttributes(
217
0
        poFeature, /* bWriteIdIfFoundInAttributes = */ true, oWriteOptions_);
218
219
    /* -------------------------------------------------------------------- */
220
    /*      Deal with time properties.                                      */
221
    /* -------------------------------------------------------------------- */
222
0
    json_object *poTime = nullptr;
223
0
    int nFieldTimeIdx = poFeatureDefn_->GetFieldIndex("jsonfg_time");
224
0
    if (nFieldTimeIdx < 0)
225
0
        nFieldTimeIdx = poFeatureDefn_->GetFieldIndex("time");
226
0
    if (nFieldTimeIdx >= 0 && poFeature->IsFieldSetAndNotNull(nFieldTimeIdx))
227
0
    {
228
0
        const auto poFieldDefn = poFeatureDefn_->GetFieldDefn(nFieldTimeIdx);
229
0
        const auto eType = poFieldDefn->GetType();
230
0
        if (eType == OFTDate || eType == OFTDateTime)
231
0
        {
232
0
            json_object_object_del(poObjProps, poFieldDefn->GetNameRef());
233
0
            poTime = json_object_new_object();
234
0
            json_object_object_add(
235
0
                poTime, eType == OFTDate ? "date" : "timestamp",
236
0
                json_object_new_string(GetValueAsDateOrDateTime(
237
0
                    poFeature->GetRawFieldRef(nFieldTimeIdx), eType)));
238
0
        }
239
0
    }
240
0
    else
241
0
    {
242
0
        bool bHasStartOrStop = false;
243
0
        json_object *poTimeStart = nullptr;
244
0
        int nFieldTimeStartIdx =
245
0
            poFeatureDefn_->GetFieldIndex("jsonfg_time_start");
246
0
        if (nFieldTimeStartIdx < 0)
247
0
            nFieldTimeStartIdx = poFeatureDefn_->GetFieldIndex("time_start");
248
0
        if (nFieldTimeStartIdx >= 0 &&
249
0
            poFeature->IsFieldSetAndNotNull(nFieldTimeStartIdx))
250
0
        {
251
0
            const auto poFieldDefnStart =
252
0
                poFeatureDefn_->GetFieldDefn(nFieldTimeStartIdx);
253
0
            const auto eType = poFieldDefnStart->GetType();
254
0
            if (eType == OFTDate || eType == OFTDateTime)
255
0
            {
256
0
                json_object_object_del(poObjProps,
257
0
                                       poFieldDefnStart->GetNameRef());
258
0
                poTimeStart = json_object_new_string(GetValueAsDateOrDateTime(
259
0
                    poFeature->GetRawFieldRef(nFieldTimeStartIdx), eType));
260
0
                bHasStartOrStop = true;
261
0
            }
262
0
        }
263
264
0
        json_object *poTimeEnd = nullptr;
265
0
        int nFieldTimeEndIdx = poFeatureDefn_->GetFieldIndex("jsonfg_time_end");
266
0
        if (nFieldTimeEndIdx < 0)
267
0
            nFieldTimeEndIdx = poFeatureDefn_->GetFieldIndex("time_end");
268
0
        if (nFieldTimeEndIdx >= 0 &&
269
0
            poFeature->IsFieldSetAndNotNull(nFieldTimeEndIdx))
270
0
        {
271
0
            const auto poFieldDefnEnd =
272
0
                poFeatureDefn_->GetFieldDefn(nFieldTimeEndIdx);
273
0
            const auto eType = poFieldDefnEnd->GetType();
274
0
            if (eType == OFTDate || eType == OFTDateTime)
275
0
            {
276
0
                json_object_object_del(poObjProps,
277
0
                                       poFieldDefnEnd->GetNameRef());
278
0
                poTimeEnd = json_object_new_string(GetValueAsDateOrDateTime(
279
0
                    poFeature->GetRawFieldRef(nFieldTimeEndIdx), eType));
280
0
                bHasStartOrStop = true;
281
0
            }
282
0
        }
283
284
0
        if (bHasStartOrStop)
285
0
        {
286
0
            poTime = json_object_new_object();
287
0
            json_object *poInterval = json_object_new_array();
288
0
            json_object_object_add(poTime, "interval", poInterval);
289
0
            json_object_array_add(poInterval,
290
0
                                  poTimeStart ? poTimeStart
291
0
                                              : json_object_new_string(".."));
292
0
            json_object_array_add(poInterval,
293
0
                                  poTimeEnd ? poTimeEnd
294
0
                                            : json_object_new_string(".."));
295
0
        }
296
0
    }
297
298
0
    json_object_object_add(poObj, "properties", poObjProps);
299
300
    /* -------------------------------------------------------------------- */
301
    /*      Write place and/or geometry                                     */
302
    /* -------------------------------------------------------------------- */
303
0
    const OGRGeometry *poGeom = poFeature->GetGeometryRef();
304
0
    if (!poGeom)
305
0
    {
306
0
        json_object_object_add(poObj, "geometry", nullptr);
307
0
        json_object_object_add(poObj, "place", nullptr);
308
0
    }
309
0
    else
310
0
    {
311
0
        if (wkbFlatten(poGeom->getGeometryType()) == wkbPolyhedralSurface)
312
0
        {
313
0
            json_object_object_add(poObj, "geometry", nullptr);
314
0
            if (m_bMustSwapForPlace)
315
0
            {
316
0
                auto poGeomClone =
317
0
                    std::unique_ptr<OGRGeometry>(poGeom->clone());
318
0
                poGeomClone->swapXY();
319
0
                json_object_object_add(
320
0
                    poObj, "place",
321
0
                    OGRJSONFGWriteGeometry(poGeomClone.get(),
322
0
                                           oWriteOptionsPlace_));
323
0
            }
324
0
            else
325
0
            {
326
0
                json_object_object_add(
327
0
                    poObj, "place",
328
0
                    OGRJSONFGWriteGeometry(poGeom, oWriteOptionsPlace_));
329
0
            }
330
0
        }
331
0
        else if (bIsWGS84CRS_)
332
0
        {
333
0
            json_object_object_add(
334
0
                poObj, "geometry",
335
0
                OGRGeoJSONWriteGeometry(poGeom, oWriteOptions_));
336
0
            json_object_object_add(poObj, "place", nullptr);
337
0
        }
338
0
        else
339
0
        {
340
0
            if (bWriteFallbackGeometry_ && poCTToWGS84_)
341
0
            {
342
0
                auto poGeomClone =
343
0
                    std::unique_ptr<OGRGeometry>(poGeom->clone());
344
0
                if (poGeomClone->transform(poCTToWGS84_.get()) == OGRERR_NONE)
345
0
                {
346
0
                    json_object_object_add(
347
0
                        poObj, "geometry",
348
0
                        OGRGeoJSONWriteGeometry(poGeomClone.get(),
349
0
                                                oWriteOptions_));
350
0
                }
351
0
                else
352
0
                {
353
0
                    json_object_object_add(poObj, "geometry", nullptr);
354
0
                }
355
0
            }
356
0
            else
357
0
            {
358
0
                json_object_object_add(poObj, "geometry", nullptr);
359
0
            }
360
361
0
            if (m_bMustSwapForPlace)
362
0
            {
363
0
                auto poGeomClone =
364
0
                    std::unique_ptr<OGRGeometry>(poGeom->clone());
365
0
                poGeomClone->swapXY();
366
0
                json_object_object_add(
367
0
                    poObj, "place",
368
0
                    OGRGeoJSONWriteGeometry(poGeomClone.get(),
369
0
                                            oWriteOptionsPlace_));
370
0
            }
371
0
            else
372
0
            {
373
0
                json_object_object_add(
374
0
                    poObj, "place",
375
0
                    OGRGeoJSONWriteGeometry(poGeom, oWriteOptionsPlace_));
376
0
            }
377
0
        }
378
0
    }
379
380
0
    json_object_object_add(poObj, "time", poTime);
381
382
0
    VSIFPrintfL(fp, "%s",
383
0
                json_object_to_json_string_ext(
384
0
                    poObj, JSON_C_TO_STRING_SPACED
385
0
#ifdef JSON_C_TO_STRING_NOSLASHESCAPE
386
0
                               | JSON_C_TO_STRING_NOSLASHESCAPE
387
0
#endif
388
0
                    ));
389
390
0
    json_object_put(poObj);
391
392
0
    ++nOutCounter_;
393
394
0
    return OGRERR_NONE;
395
0
}
396
397
/************************************************************************/
398
/*                           CreateField()                              */
399
/************************************************************************/
400
401
OGRErr OGRJSONFGWriteLayer::CreateField(const OGRFieldDefn *poField,
402
                                        int /* bApproxOK */)
403
0
{
404
0
    if (poFeatureDefn_->GetFieldIndexCaseSensitive(poField->GetNameRef()) >= 0)
405
0
    {
406
0
        CPLDebug("JSONFG", "Field '%s' already present in schema",
407
0
                 poField->GetNameRef());
408
409
0
        return OGRERR_NONE;
410
0
    }
411
412
0
    poFeatureDefn_->AddFieldDefn(poField);
413
414
0
    return OGRERR_NONE;
415
0
}
416
417
/************************************************************************/
418
/*                           TestCapability()                           */
419
/************************************************************************/
420
421
int OGRJSONFGWriteLayer::TestCapability(const char *pszCap)
422
0
{
423
0
    if (EQUAL(pszCap, OLCCreateField))
424
0
        return TRUE;
425
0
    else if (EQUAL(pszCap, OLCSequentialWrite))
426
0
        return TRUE;
427
0
    else if (EQUAL(pszCap, OLCStringsAsUTF8))
428
0
        return TRUE;
429
0
    return FALSE;
430
0
}
431
432
/************************************************************************/
433
/*                             GetDataset()                             */
434
/************************************************************************/
435
436
GDALDataset *OGRJSONFGWriteLayer::GetDataset()
437
0
{
438
0
    return poDS_;
439
0
}