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