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