Skip to content

Commit e63cdbb

Browse files
committed
JSONFG reader/writer: report/write measure unit and description
1 parent 87851e1 commit e63cdbb

File tree

7 files changed

+273
-1
lines changed

7 files changed

+273
-1
lines changed

apps/ogr2ogr_lib.cpp

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4868,6 +4868,29 @@ SetupTargetLayer::Setup(OGRLayer *poSrcLayer, const char *pszNewLayerName,
48684868
oCoordPrec.dfMResolution = psOptions->dfMRes;
48694869
}
48704870

4871+
// For JSONFG
4872+
CSLConstList papszMeasures = poSrcLayer->GetMetadata("MEASURES");
4873+
if (papszMeasures && pszDestCreationOptions)
4874+
{
4875+
for (const char *pszItem : {"UNIT", "DESCRIPTION"})
4876+
{
4877+
const char *pszValue =
4878+
CSLFetchNameValue(papszMeasures, pszItem);
4879+
if (pszValue)
4880+
{
4881+
const std::string osOptionName =
4882+
std::string("MEASURE_").append(pszItem);
4883+
if (strstr(pszDestCreationOptions, osOptionName.c_str()) &&
4884+
CSLFetchNameValue(m_papszLCO, osOptionName.c_str()) ==
4885+
nullptr)
4886+
{
4887+
papszLCOTemp = CSLSetNameValue(
4888+
papszLCOTemp, osOptionName.c_str(), pszValue);
4889+
}
4890+
}
4891+
}
4892+
}
4893+
48714894
auto poSrcDriver = m_poSrcDS->GetDriver();
48724895

48734896
// Force FID column as 64 bit if the source feature has a 64 bit FID,

autotest/ogr/ogr_jsonfg.py

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1627,3 +1627,60 @@ def test_jsonfg_write_circular_string_longer_than_11_points(tmp_vsimem):
16271627
f.GetGeometryRef().ExportToIsoWkt()
16281628
== "COMPOUNDCURVE (CIRCULARSTRING (0 0,1 0,2 0,3 0,4 0,5 0,6 0,7 0,8 0,9 0,10 0),CIRCULARSTRING (10 0,11 0,12 0))"
16291629
)
1630+
1631+
1632+
@pytest.mark.parametrize("single_layer", ["YES", "NO"])
1633+
def test_jsonfg_write_read_measure_unit_description(tmp_vsimem, single_layer):
1634+
1635+
out_filename = tmp_vsimem / "out.json"
1636+
with gdal.GetDriverByName("JSONFG").CreateVector(
1637+
out_filename, options=["SINGLE_LAYER=" + single_layer]
1638+
) as ds:
1639+
srs = osr.SpatialReference(epsg=32631)
1640+
lyr = ds.CreateLayer(
1641+
"test",
1642+
srs=srs,
1643+
options=["MEASURE_UNIT=my_unit", "MEASURE_DESCRIPTION=my_description"],
1644+
)
1645+
f = ogr.Feature(lyr.GetLayerDefn())
1646+
g = ogr.CreateGeometryFromWkt("POINT M (1 2 3)")
1647+
f.SetGeometry(g)
1648+
lyr.CreateFeature(f)
1649+
1650+
with gdal.VSIFile(out_filename, "rb") as f:
1651+
j = json.loads(f.read())
1652+
if single_layer == "YES":
1653+
assert "measures" in j
1654+
assert j["measures"] == {
1655+
"enabled": True,
1656+
"unit": "my_unit",
1657+
"description": "my_description",
1658+
}
1659+
else:
1660+
assert "measures" in j["features"][0]
1661+
assert j["features"][0]["measures"] == {
1662+
"enabled": True,
1663+
"unit": "my_unit",
1664+
"description": "my_description",
1665+
}
1666+
1667+
with ogr.Open(out_filename) as ds:
1668+
lyr = ds.GetLayer(0)
1669+
assert lyr.GetMetadata_Dict("MEASURES") == {
1670+
"UNIT": "my_unit",
1671+
"DESCRIPTION": "my_description",
1672+
}
1673+
f = lyr.GetNextFeature()
1674+
assert f.GetGeometryRef().ExportToIsoWkt() == "POINT M (1 2 3)"
1675+
1676+
out2_filename = tmp_vsimem / "out2.json"
1677+
gdal.VectorTranslate(out2_filename, out_filename, format="JSONFG")
1678+
1679+
with ogr.Open(out2_filename) as ds:
1680+
lyr = ds.GetLayer(0)
1681+
assert lyr.GetMetadata_Dict("MEASURES") == {
1682+
"UNIT": "my_unit",
1683+
"DESCRIPTION": "my_description",
1684+
}
1685+
f = lyr.GetNextFeature()
1686+
assert f.GetGeometryRef().ExportToIsoWkt() == "POINT M (1 2 3)"

doc/source/drivers/vector/jsonfg.rst

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,16 @@ Circular-arc geometry support
108108
Reading and writing of CircularString, CompoundCurve, CurvePolygon, MultiCurve
109109
and MultiSurface geometries is supported.
110110

111+
Geometry measure support
112+
------------------------
113+
114+
.. versionadded:: 3.12
115+
116+
Geometry with measure values (M component) can be read and written. The unit
117+
and description of the measure, if included in the file, is reported in the
118+
``MEASURES`` layer metadata domain. On the writing side, the :lco:`MEASURE_UNIT`
119+
and :lco:`MEASURE_DESCRIPTION` layer creation option can be set.
120+
111121
Configuration options
112122
---------------------
113123

@@ -210,6 +220,18 @@ The following layer creation options are supported:
210220

211221
Auto-generate feature ids
212222

223+
- .. lco:: MEASURE_UNIT
224+
:choices: <string>
225+
:since: 3.12
226+
227+
Unit of measures (M) values
228+
229+
- .. lco:: MEASURE_DESCRIPTION
230+
:choices: <string>
231+
:since: 3.12
232+
233+
Description of measures (M) values
234+
213235
VSI Virtual File System API support
214236
-----------------------------------
215237

ogr/ogrsf_frmts/jsonfg/ogr_jsonfg.h

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -236,6 +236,9 @@ class OGRJSONFGWriteLayer final : public OGRLayer
236236
bool m_bPolyhedraWritten = false;
237237
bool m_bCurveWritten = false;
238238
bool m_bMeasureWritten = false;
239+
bool bLayerLevelMeasuresWritten_ = false;
240+
std::string osMeasureUnit_{};
241+
std::string osMeasureDescription_{};
239242

240243
OGRGeoJSONWriteOptions oWriteOptions_{};
241244
OGRGeoJSONWriteOptions oWriteOptionsPlace_{};
@@ -415,6 +418,8 @@ class OGRJSONFGReader
415418
char chNestedAttributeSeparator_ = 0;
416419
bool bArrayAsString_ = false;
417420
bool bDateAsString_ = false;
421+
std::string osMeasureUnit_{};
422+
std::string osMeasureDescription_{};
418423

419424
/** Layer building context, specific to one layer. */
420425
struct LayerDefnBuildContext
@@ -515,6 +520,14 @@ class OGRJSONFGReader
515520
* AnalyzeWithStreamingParser() mode) */
516521
OGRJSONFGStreamedLayer *poStreamedLayer = nullptr;
517522

523+
bool bSameMeasureMetadata = true;
524+
525+
//! Measure unit
526+
std::string osMeasureUnit{};
527+
528+
//! Measure description
529+
std::string osMeasureDescription{};
530+
518531
LayerDefnBuildContext() = default;
519532
LayerDefnBuildContext(LayerDefnBuildContext &&) = default;
520533
LayerDefnBuildContext &operator=(LayerDefnBuildContext &&) = default;

ogr/ogrsf_frmts/jsonfg/ogrjsonfgdriver.cpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,10 @@ void RegisterOGRJSONFG()
135135
" </Option>"
136136
" <Option name='ID_GENERATE' type='boolean' "
137137
"description='Auto-generate feature ids' default='NO'/>"
138+
" <Option name='MEASURE_UNIT' type='string' "
139+
"description='Unit of measures (M) values'/>"
140+
" <Option name='MEASURE_DESCRIPTION' type='string' "
141+
"description='Description of measures (M) values'/>"
138142
"</LayerCreationOptionList>");
139143

140144
poDriver->SetMetadataItem(

ogr/ogrsf_frmts/jsonfg/ogrjsonfgreader.cpp

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -446,6 +446,32 @@ bool OGRJSONFGReader::FinalizeGenerateLayerDefns(bool bStreamedLayer)
446446
}
447447
}
448448

449+
json_object *poMeasures = nullptr;
450+
if (json_object_object_get_ex(poObject_, "measures", &poMeasures) &&
451+
json_object_get_type(poMeasures) == json_type_object)
452+
{
453+
json_object *poEnabled = nullptr;
454+
if (json_object_object_get_ex(poMeasures, "enabled", &poEnabled) &&
455+
json_object_get_type(poEnabled) == json_type_boolean &&
456+
json_object_get_boolean(poEnabled))
457+
{
458+
json_object *poUnit = nullptr;
459+
if (json_object_object_get_ex(poMeasures, "unit", &poUnit) &&
460+
json_object_get_type(poUnit) == json_type_string)
461+
{
462+
osMeasureUnit_ = json_object_get_string(poUnit);
463+
}
464+
465+
json_object *poDescription = nullptr;
466+
if (json_object_object_get_ex(poMeasures, "description",
467+
&poDescription) &&
468+
json_object_get_type(poDescription) == json_type_string)
469+
{
470+
osMeasureDescription_ = json_object_get_string(poDescription);
471+
}
472+
}
473+
}
474+
449475
// Finalize layer definition building and create OGRLayer objects
450476
for (auto &oBuildContextIter : oMapBuildContext_)
451477
{
@@ -673,6 +699,38 @@ void OGRJSONFGReader::FinalizeBuildContext(LayerDefnBuildContext &oBuildContext,
673699
if (oBuildContext.bNeedFID64)
674700
poLayer->SetMetadataItem(OLMD_FID64, "YES");
675701

702+
if (oBuildContext.bSameMeasureMetadata &&
703+
(!oBuildContext.osMeasureUnit.empty() ||
704+
!oBuildContext.osMeasureDescription.empty()))
705+
{
706+
if (!oBuildContext.osMeasureUnit.empty())
707+
{
708+
poLayer->SetMetadataItem(
709+
"UNIT", oBuildContext.osMeasureUnit.c_str(), "MEASURES");
710+
}
711+
712+
if (!oBuildContext.osMeasureDescription.empty())
713+
{
714+
poLayer->SetMetadataItem("DESCRIPTION",
715+
oBuildContext.osMeasureDescription.c_str(),
716+
"MEASURES");
717+
}
718+
}
719+
else
720+
{
721+
if (!osMeasureUnit_.empty())
722+
{
723+
poLayer->SetMetadataItem("UNIT", osMeasureUnit_.c_str(),
724+
"MEASURES");
725+
}
726+
727+
if (!osMeasureDescription_.empty())
728+
{
729+
poLayer->SetMetadataItem("DESCRIPTION",
730+
osMeasureDescription_.c_str(), "MEASURES");
731+
}
732+
}
733+
676734
if (poStreamedLayer)
677735
{
678736
poStreamedLayer->SetFeatureCount(oBuildContext.nFeatureCount);
@@ -1058,6 +1116,45 @@ bool OGRJSONFGReader::GenerateLayerDefnFromFeature(json_object *poObj)
10581116
}
10591117
}
10601118

1119+
if (poContext->bSameMeasureMetadata)
1120+
{
1121+
json_object *poMeasures = nullptr;
1122+
if (json_object_object_get_ex(poObj, "measures", &poMeasures) &&
1123+
json_object_get_type(poMeasures) == json_type_object)
1124+
{
1125+
json_object *poEnabled = nullptr;
1126+
if (json_object_object_get_ex(poMeasures, "enabled", &poEnabled) &&
1127+
json_object_get_type(poEnabled) == json_type_boolean &&
1128+
json_object_get_boolean(poEnabled))
1129+
{
1130+
json_object *poUnit = nullptr;
1131+
if (json_object_object_get_ex(poMeasures, "unit", &poUnit) &&
1132+
json_object_get_type(poUnit) == json_type_string)
1133+
{
1134+
if (poContext->osMeasureUnit.empty())
1135+
poContext->osMeasureUnit =
1136+
json_object_get_string(poUnit);
1137+
else if (poContext->osMeasureUnit !=
1138+
json_object_get_string(poUnit))
1139+
poContext->bSameMeasureMetadata = false;
1140+
}
1141+
1142+
json_object *poDescription = nullptr;
1143+
if (json_object_object_get_ex(poMeasures, "description",
1144+
&poDescription) &&
1145+
json_object_get_type(poDescription) == json_type_string)
1146+
{
1147+
if (poContext->osMeasureDescription.empty())
1148+
poContext->osMeasureDescription =
1149+
json_object_get_string(poDescription);
1150+
else if (poContext->osMeasureDescription !=
1151+
json_object_get_string(poDescription))
1152+
poContext->bSameMeasureMetadata = false;
1153+
}
1154+
}
1155+
}
1156+
}
1157+
10611158
/* -------------------------------------------------------------------- */
10621159
/* Deal with place / geometry */
10631160
/* -------------------------------------------------------------------- */

ogr/ogrsf_frmts/jsonfg/ogrjsonfgwritelayer.cpp

Lines changed: 57 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,10 @@ OGRJSONFGWriteLayer::OGRJSONFGWriteLayer(
7171
bWriteFallbackGeometry_ = CPLTestBool(
7272
CSLFetchNameValueDef(papszOptions, "WRITE_GEOMETRY", "TRUE"));
7373

74+
osMeasureUnit_ = CSLFetchNameValueDef(papszOptions, "MEASURE_UNIT", "");
75+
osMeasureDescription_ =
76+
CSLFetchNameValueDef(papszOptions, "MEASURE_DESCRIPTION", "");
77+
7478
VSILFILE *fp = poDS_->GetOutputFile();
7579
if (poDS_->IsSingleOutputLayer())
7680
{
@@ -81,6 +85,32 @@ OGRJSONFGWriteLayer::OGRJSONFGWriteLayer(
8185
json_object_put(poFeatureType);
8286
if (!osCoordRefSys.empty())
8387
VSIFPrintfL(fp, "\"coordRefSys\" : %s,\n", osCoordRefSys.c_str());
88+
89+
if (!osMeasureUnit_.empty() || !osMeasureDescription_.empty())
90+
{
91+
m_bMeasureWritten = true;
92+
bLayerLevelMeasuresWritten_ = true;
93+
VSIFPrintfL(fp, "\"measures\": {\n");
94+
VSIFPrintfL(fp, " \"enabled\": true");
95+
if (!osMeasureUnit_.empty())
96+
{
97+
auto poUnit = json_object_new_string(osMeasureUnit_.c_str());
98+
VSIFPrintfL(fp, ",\n \"unit\": %s",
99+
json_object_to_json_string_ext(
100+
poUnit, JSON_C_TO_STRING_SPACED));
101+
json_object_put(poUnit);
102+
}
103+
if (!osMeasureDescription_.empty())
104+
{
105+
auto poDescription =
106+
json_object_new_string(osMeasureDescription_.c_str());
107+
VSIFPrintfL(fp, ",\n \"description\": %s",
108+
json_object_to_json_string_ext(
109+
poDescription, JSON_C_TO_STRING_SPACED));
110+
json_object_put(poDescription);
111+
}
112+
VSIFPrintfL(fp, "\n},\n");
113+
}
84114
}
85115
}
86116

@@ -365,10 +395,36 @@ OGRErr OGRJSONFGWriteLayer::ICreateFeature(OGRFeature *poFeature)
365395
}
366396

367397
if (poGeom->IsMeasured())
398+
{
399+
if (!bLayerLevelMeasuresWritten_)
400+
{
401+
json_object *poMeasures = json_object_new_object();
402+
json_object_object_add(poMeasures, "enabled",
403+
json_object_new_boolean(true));
404+
if (!poDS_->IsSingleOutputLayer())
405+
{
406+
if (!osMeasureUnit_.empty())
407+
{
408+
json_object_object_add(
409+
poMeasures, "unit",
410+
json_object_new_string(osMeasureUnit_.c_str()));
411+
}
412+
if (!osMeasureDescription_.empty())
413+
{
414+
json_object_object_add(
415+
poMeasures, "description",
416+
json_object_new_string(
417+
osMeasureDescription_.c_str()));
418+
}
419+
}
420+
json_object_object_add(poObj, "measures", poMeasures);
421+
}
422+
}
423+
else if (bLayerLevelMeasuresWritten_)
368424
{
369425
json_object *poMeasures = json_object_new_object();
370426
json_object_object_add(poMeasures, "enabled",
371-
json_object_new_boolean(true));
427+
json_object_new_boolean(false));
372428
json_object_object_add(poObj, "measures", poMeasures);
373429
}
374430
}

0 commit comments

Comments
 (0)