diff --git a/data/crsjson.schema.json b/data/crsjson.schema.json index 5e2d41d06e..e38f303832 100644 --- a/data/crsjson.schema.json +++ b/data/crsjson.schema.json @@ -6,6 +6,7 @@ "oneOf": [ { "$ref": "#/definitions/crs" }, { "$ref": "#/definitions/datum" }, + { "$ref": "#/definitions/datum_ensemble" }, { "$ref": "#/definitions/ellipsoid" }, { "$ref": "#/definitions/prime_meridian" }, { "$ref": "#/definitions/single_operation" }, @@ -147,14 +148,20 @@ "crs": { "oneOf": [ - { "$ref": "#/definitions/geodetic_crs" }, - { "$ref": "#/definitions/derived_crs" }, - { "$ref": "#/definitions/vertical_crs" }, - { "$ref": "#/definitions/compound_crs" }, { "$ref": "#/definitions/bound_crs" }, + { "$ref": "#/definitions/compound_crs" }, + { "$ref": "#/definitions/derived_engineering_crs" }, + { "$ref": "#/definitions/derived_geodetic_crs" }, + { "$ref": "#/definitions/derived_parametric_crs" }, + { "$ref": "#/definitions/derived_projected_crs" }, + { "$ref": "#/definitions/derived_temporal_crs" }, + { "$ref": "#/definitions/derived_vertical_crs" }, { "$ref": "#/definitions/engineering_crs" }, + { "$ref": "#/definitions/geodetic_crs" }, { "$ref": "#/definitions/parametric_crs" }, - { "$ref": "#/definitions/temporal_crs" } + { "$ref": "#/definitions/projected_crs" }, + { "$ref": "#/definitions/temporal_crs" }, + { "$ref": "#/definitions/vertical_crs" } ] }, @@ -162,27 +169,132 @@ "oneOf": [ { "$ref": "#/definitions/geodetic_reference_frame" }, { "$ref": "#/definitions/vertical_reference_frame" }, + { "$ref": "#/definitions/dynamic_geodetic_reference_frame" }, + { "$ref": "#/definitions/dynamic_vertical_reference_frame" }, { "$ref": "#/definitions/temporal_datum" }, { "$ref": "#/definitions/parametric_datum" }, { "$ref": "#/definitions/engineering_datum" } ] }, - "derived_crs": { + "datum_ensemble": { + "type": "object", + "properties": { + "type": { "type": "string", "enum": ["DatumEnsemble"] }, + "name": { "type": "string" }, + "members": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { "type": "string" }, + "id": { "$ref": "#/definitions/id" } + }, + "required" : [ "name" ], + "additionalProperties": false + } + }, + "ellipsoid": { "$ref": "#/definitions/ellipsoid" }, + "accuracy": { "type": "string" }, + "id": { "$ref": "#/definitions/id" } + }, + "required" : [ "name", "members", "accuracy" ], + "additionalProperties": false + }, + + "derived_engineering_crs": { + "type": "object", + "allOf": [{ "$ref": "#/definitions/object_usage" }], + "properties": { + "type": { "type": "string", + "enum": ["DerivedEngineeringCRS"] }, + "name": { "type": "string" }, + "base_crs": { "$ref": "#/definitions/engineering_crs" }, + "conversion": { "$ref": "#/definitions/conversion" }, + "coordinate_system": { "$ref": "#/definitions/coordinate_system" }, + "scope": {}, + "area": {}, + "bbox": {}, + "usages": {}, + "remarks": {}, + "id": {} + }, + "required" : [ "name", "base_crs", "conversion", "coordinate_system" ], + "additionalProperties": false + }, + + "derived_geodetic_crs": { + "type": "object", + "allOf": [{ "$ref": "#/definitions/object_usage" }], + "properties": { + "type": { "type": "string", + "enum": ["DerivedGeodeticCRS", + "DerivedGeographicCRS"] }, + "name": { "type": "string" }, + "base_crs": { "$ref": "#/definitions/geodetic_crs" }, + "conversion": { "$ref": "#/definitions/conversion" }, + "coordinate_system": { "$ref": "#/definitions/coordinate_system" }, + "scope": {}, + "area": {}, + "bbox": {}, + "usages": {}, + "remarks": {}, + "id": {} + }, + "required" : [ "name", "base_crs", "conversion", "coordinate_system" ], + "additionalProperties": false + }, + + "derived_parametric_crs": { + "type": "object", + "allOf": [{ "$ref": "#/definitions/object_usage" }], + "properties": { + "type": { "type": "string", + "enum": ["DerivedParametricCRS"] }, + "name": { "type": "string" }, + "base_crs": { "$ref": "#/definitions/parametric_crs" }, + "conversion": { "$ref": "#/definitions/conversion" }, + "coordinate_system": { "$ref": "#/definitions/coordinate_system" }, + "scope": {}, + "area": {}, + "bbox": {}, + "usages": {}, + "remarks": {}, + "id": {} + }, + "required" : [ "name", "base_crs", "conversion", "coordinate_system" ], + "additionalProperties": false + }, + + "derived_projected_crs": { + "type": "object", + "allOf": [{ "$ref": "#/definitions/object_usage" }], + "properties": { + "type": { "type": "string", + "enum": ["DerivedProjectedCRS"] }, + "name": { "type": "string" }, + "base_crs": { "$ref": "#/definitions/projected_crs" }, + "conversion": { "$ref": "#/definitions/conversion" }, + "coordinate_system": { "$ref": "#/definitions/coordinate_system" }, + "scope": {}, + "area": {}, + "bbox": {}, + "usages": {}, + "remarks": {}, + "id": {} + }, + "required" : [ "name", "base_crs", "conversion", "coordinate_system" ], + "additionalProperties": false + }, + + "derived_temporal_crs": { "type": "object", "allOf": [{ "$ref": "#/definitions/object_usage" }], "properties": { "type": { "type": "string", - "enum": ["ProjectedCRS", - "DerivedGeodeticCRS", - "DerivedGeographicCRS", - "DerivedProjectedCRS", - "DerivedVerticalCRS", - "DerivedTemporalCRS", - "DerivedParametricCRS", - "DerivedEngineeringCRS"] }, + "enum": ["DerivedTemporalCRS"] }, "name": { "type": "string" }, - "base_crs": { "$ref": "#/definitions/crs" }, + "base_crs": { "$ref": "#/definitions/temporal_crs" }, "conversion": { "$ref": "#/definitions/conversion" }, "coordinate_system": { "$ref": "#/definitions/coordinate_system" }, "scope": {}, @@ -196,6 +308,69 @@ "additionalProperties": false }, + "derived_vertical_crs": { + "type": "object", + "allOf": [{ "$ref": "#/definitions/object_usage" }], + "properties": { + "type": { "type": "string", + "enum": ["DerivedVerticalCRS"] }, + "name": { "type": "string" }, + "base_crs": { "$ref": "#/definitions/vertical_crs" }, + "conversion": { "$ref": "#/definitions/conversion" }, + "coordinate_system": { "$ref": "#/definitions/coordinate_system" }, + "scope": {}, + "area": {}, + "bbox": {}, + "usages": {}, + "remarks": {}, + "id": {} + }, + "required" : [ "name", "base_crs", "conversion", "coordinate_system" ], + "additionalProperties": false + }, + + "dynamic_geodetic_reference_frame": { + "type": "object", + "allOf": [{ "$ref": "#/definitions/geodetic_reference_frame" }], + "properties": { + "type": { "type": "string", "enum": ["DynamicGeodeticReferenceFrame"] }, + "name": {}, + "anchor": {}, + "ellipsoid": {}, + "prime_meridian": {}, + "frame_reference_epoch": { "type": "number" }, + "deformation_model": { "type": "string" }, + "scope": {}, + "area": {}, + "bbox": {}, + "usages": {}, + "remarks": {}, + "id": {} + }, + "required" : [ "name", "ellipsoid", "frame_reference_epoch" ], + "additionalProperties": false + }, + + "dynamic_vertical_reference_frame": { + "type": "object", + "allOf": [{ "$ref": "#/definitions/vertical_reference_frame" }], + "properties": { + "type": { "type": "string", "enum": ["DynamicVerticalReferenceFrame"] }, + "name": {}, + "anchor": {}, + "frame_reference_epoch": { "type": "number" }, + "deformation_model": { "type": "string" }, + "scope": {}, + "area": {}, + "bbox": {}, + "usages": {}, + "remarks": {}, + "id": {} + }, + "required" : [ "name", "frame_reference_epoch" ], + "additionalProperties": false + }, + "ellipsoid": { "type": "object", "oneOf":[ @@ -273,11 +448,16 @@ "geodetic_crs": { "type": "object", - "allOf": [{ "$ref": "#/definitions/object_usage" }], "properties": { "type": { "type": "string", "enum": ["GeodeticCRS", "GeographicCRS"] }, "name": { "type": "string" }, - "datum": { "$ref": "#/definitions/geodetic_reference_frame" }, + "datum": { + "oneOf": [ + { "$ref": "#/definitions/geodetic_reference_frame" }, + { "$ref": "#/definitions/dynamic_geodetic_reference_frame" } + ] + }, + "datum_ensemble": { "$ref": "#/definitions/datum_ensemble" }, "coordinate_system": { "$ref": "#/definitions/coordinate_system" }, "scope": {}, "area": {}, @@ -286,7 +466,12 @@ "remarks": {}, "id": {} }, - "required" : [ "name", "datum" ], + "required" : [ "name" ], + "description": "One and only one of datum and datum_ensemble must be provided", + "allOf": [ + { "$ref": "#/definitions/object_usage" }, + { "$ref": "#/definitions/one_and_only_one_of_datum_or_datum_ensemble" } + ], "additionalProperties": false }, @@ -333,6 +518,23 @@ "additionalProperties": false }, + "one_and_only_one_of_datum_or_datum_ensemble": { + "allOf": [ + { + "not": { + "type": "object", + "required": [ "datum", "datum_ensemble" ] + } + }, + { + "oneOf": [ + { "type": "object", "required": ["datum"] }, + { "type": "object", "required": ["datum_ensemble"] } + ] + } + ] + }, + "object_usage": { "anyOf": [ { @@ -430,6 +632,27 @@ ] }, + "projected_crs": { + "type": "object", + "allOf": [{ "$ref": "#/definitions/object_usage" }], + "properties": { + "type": { "type": "string", + "enum": ["ProjectedCRS"] }, + "name": { "type": "string" }, + "base_crs": { "$ref": "#/definitions/geodetic_crs" }, + "conversion": { "$ref": "#/definitions/conversion" }, + "coordinate_system": { "$ref": "#/definitions/coordinate_system" }, + "scope": {}, + "area": {}, + "bbox": {}, + "usages": {}, + "remarks": {}, + "id": {} + }, + "required" : [ "name", "base_crs", "conversion", "coordinate_system" ], + "additionalProperties": false + }, + "temporal_crs": { "type": "object", "allOf": [{ "$ref": "#/definitions/object_usage" }], @@ -548,11 +771,16 @@ "vertical_crs": { "type": "object", - "allOf": [{ "$ref": "#/definitions/object_usage" }], "properties": { "type": { "type": "string", "enum": ["VerticalCRS"] }, "name": { "type": "string" }, - "datum": { "$ref": "#/definitions/vertical_reference_frame" }, + "datum": { + "oneOf": [ + { "$ref": "#/definitions/vertical_reference_frame" }, + { "$ref": "#/definitions/dynamic_vertical_reference_frame" } + ] + }, + "datum_ensemble": { "$ref": "#/definitions/datum_ensemble" }, "coordinate_system": { "$ref": "#/definitions/coordinate_system" }, "scope": {}, "area": {}, @@ -561,7 +789,12 @@ "remarks": {}, "id": {} }, - "required" : [ "name", "datum" ], + "required" : [ "name"], + "description": "One and only one of datum and datum_ensemble must be provided", + "allOf": [ + { "$ref": "#/definitions/object_usage" }, + { "$ref": "#/definitions/one_and_only_one_of_datum_or_datum_ensemble" } + ], "additionalProperties": false }, diff --git a/include/proj/datum.hpp b/include/proj/datum.hpp index 3724bfb144..a26c94b904 100644 --- a/include/proj/datum.hpp +++ b/include/proj/datum.hpp @@ -118,7 +118,8 @@ using DatumEnsembleNNPtr = util::nn; * * \remark Implements DatumEnsemble from \ref ISO_19111_2019 */ -class PROJ_GCC_DLL DatumEnsemble final : public common::IdentifiedObject { +class PROJ_GCC_DLL DatumEnsemble final : public common::IdentifiedObject, + public io::IJSONExportable { public: //! @cond Doxygen_Suppress PROJ_DLL ~DatumEnsemble() override; @@ -135,6 +136,9 @@ class PROJ_GCC_DLL DatumEnsemble final : public common::IdentifiedObject { //! @cond Doxygen_Suppress PROJ_INTERNAL void _exportToWKT(io::WKTFormatter *formatter) + const override; // throw(io::FormattingException) + + PROJ_INTERNAL void _exportToJSON(io::JSONFormatter *formatter) const override; // throw(io::FormattingException) //! @endcond diff --git a/src/iso19111/crs.cpp b/src/iso19111/crs.cpp index 4f2ab7c655..b03ece2330 100644 --- a/src/iso19111/crs.cpp +++ b/src/iso19111/crs.cpp @@ -1362,7 +1362,9 @@ void GeodeticCRS::_exportToJSON( writer.AddObjKey("datum"); l_datum->_exportToJSON(formatter); } else { - // TODO DatumEnsemble + writer.AddObjKey("datum_ensemble"); + formatter->setOmitTypeInImmediateChild(); + datumEnsemble()->_exportToJSON(formatter); } writer.AddObjKey("coordinate_system"); @@ -2113,7 +2115,9 @@ void GeographicCRS::_exportToJSON( writer.AddObjKey("datum"); l_datum->_exportToJSON(formatter); } else { - // TODO DatumEnsemble + writer.AddObjKey("datum_ensemble"); + formatter->setOmitTypeInImmediateChild(); + datumEnsemble()->_exportToJSON(formatter); } writer.AddObjKey("coordinate_system"); @@ -2305,8 +2309,15 @@ void VerticalCRS::_exportToJSON( writer.Add(l_name); } - writer.AddObjKey("datum"); - datum()->_exportToJSON(formatter); + const auto &l_datum(datum()); + if (l_datum) { + writer.AddObjKey("datum"); + l_datum->_exportToJSON(formatter); + } else { + writer.AddObjKey("datum_ensemble"); + formatter->setOmitTypeInImmediateChild(); + datumEnsemble()->_exportToJSON(formatter); + } writer.AddObjKey("coordinate_system"); formatter->setOmitTypeInImmediateChild(); diff --git a/src/iso19111/datum.cpp b/src/iso19111/datum.cpp index d6e6bc2132..65905ca9eb 100644 --- a/src/iso19111/datum.cpp +++ b/src/iso19111/datum.cpp @@ -1264,8 +1264,11 @@ void GeodeticReferenceFrame::_exportToWKT( void GeodeticReferenceFrame::_exportToJSON( io::JSONFormatter *formatter) const // throw(FormattingException) { - auto objectContext(formatter->MakeObjectContext("GeodeticReferenceFrame", - !identifiers().empty())); + auto dynamicGRF = dynamic_cast(this); + + auto objectContext(formatter->MakeObjectContext( + dynamicGRF ? "DynamicGeodeticReferenceFrame" : "GeodeticReferenceFrame", + !identifiers().empty())); auto &writer = formatter->writer(); writer.AddObjKey("name"); @@ -1278,6 +1281,17 @@ void GeodeticReferenceFrame::_exportToJSON( Datum::getPrivate()->exportAnchorDefinition(formatter); + if (dynamicGRF) { + writer.AddObjKey("frame_reference_epoch"); + writer.Add(dynamicGRF->frameReferenceEpoch().value()); + + const auto &deformationModel = dynamicGRF->deformationModelName(); + if (deformationModel.has_value()) { + writer.AddObjKey("deformation_model"); + writer.Add(*deformationModel); + } + } + writer.AddObjKey("ellipsoid"); formatter->setOmitTypeInImmediateChild(); ellipsoid()->_exportToJSON(formatter); @@ -1422,7 +1436,7 @@ void DynamicGeodeticReferenceFrame::_exportToWKT( // --------------------------------------------------------------------------- -/** \brief Instantiate a DyanmicGeodeticReferenceFrame +/** \brief Instantiate a DynamicGeodeticReferenceFrame * * @param properties See \ref general_properties. * At minimum the name should be defined. @@ -1431,7 +1445,7 @@ void DynamicGeodeticReferenceFrame::_exportToWKT( * @param primeMeridian the PrimeMeridian. * @param frameReferenceEpochIn the frame reference epoch. * @param deformationModelNameIn deformation model name, or empty - * @return new DyanmicGeodeticReferenceFrame. + * @return new DynamicGeodeticReferenceFrame. */ DynamicGeodeticReferenceFrameNNPtr DynamicGeodeticReferenceFrame::create( const util::PropertyMap &properties, const EllipsoidNNPtr &ellipsoid, @@ -1562,6 +1576,56 @@ void DatumEnsemble::_exportToWKT( // --------------------------------------------------------------------------- +//! @cond Doxygen_Suppress +void DatumEnsemble::_exportToJSON( + io::JSONFormatter *formatter) const // throw(FormattingException) +{ + auto objectContext( + formatter->MakeObjectContext("DatumEnsemble", !identifiers().empty())); + auto &writer = formatter->writer(); + + writer.AddObjKey("name"); + auto l_name = nameStr(); + if (l_name.empty()) { + writer.Add("unnamed"); + } else { + writer.Add(l_name); + } + + auto l_datums = datums(); + writer.AddObjKey("members"); + { + auto membersContext(writer.MakeArrayContext(false)); + for (const auto &datum : l_datums) { + auto memberContext(writer.MakeObjectContext()); + writer.AddObjKey("name"); + const auto &l_datum_name = datum->nameStr(); + if (!l_datum_name.empty()) { + writer.Add(l_datum_name); + } else { + writer.Add("unnamed"); + } + datum->formatID(formatter); + } + } + + auto grfFirst = std::dynamic_pointer_cast( + l_datums[0].as_nullable()); + if (grfFirst) { + writer.AddObjKey("ellipsoid"); + formatter->setOmitTypeInImmediateChild(); + grfFirst->ellipsoid()->_exportToJSON(formatter); + } + + writer.AddObjKey("accuracy"); + writer.Add(positionalAccuracy()->value()); + + formatID(formatter); +} +//! @endcond + +// --------------------------------------------------------------------------- + /** \brief Instantiate a DatumEnsemble. * * @param properties See \ref general_properties. @@ -1729,8 +1793,11 @@ void VerticalReferenceFrame::_exportToWKT( void VerticalReferenceFrame::_exportToJSON( io::JSONFormatter *formatter) const // throw(FormattingException) { - auto objectContext(formatter->MakeObjectContext("VerticalReferenceFrame", - !identifiers().empty())); + auto dynamicGRF = dynamic_cast(this); + + auto objectContext(formatter->MakeObjectContext( + dynamicGRF ? "DynamicVerticalReferenceFrame" : "VerticalReferenceFrame", + !identifiers().empty())); auto &writer = formatter->writer(); writer.AddObjKey("name"); @@ -1743,6 +1810,17 @@ void VerticalReferenceFrame::_exportToJSON( Datum::getPrivate()->exportAnchorDefinition(formatter); + if (dynamicGRF) { + writer.AddObjKey("frame_reference_epoch"); + writer.Add(dynamicGRF->frameReferenceEpoch().value()); + + const auto &deformationModel = dynamicGRF->deformationModelName(); + if (deformationModel.has_value()) { + writer.AddObjKey("deformation_model"); + writer.Add(*deformationModel); + } + } + ObjectUsage::baseExportToJSON(formatter); } //! @endcond @@ -1882,7 +1960,7 @@ void DynamicVerticalReferenceFrame::_exportToWKT( // --------------------------------------------------------------------------- -/** \brief Instantiate a DyanmicVerticalReferenceFrame +/** \brief Instantiate a DynamicVerticalReferenceFrame * * @param properties See \ref general_properties. * At minimum the name should be defined. @@ -1890,7 +1968,7 @@ void DynamicVerticalReferenceFrame::_exportToWKT( * @param realizationMethodIn the realization method, or empty. * @param frameReferenceEpochIn the frame reference epoch. * @param deformationModelNameIn deformation model name, or empty - * @return new DyanmicVerticalReferenceFrame. + * @return new DynamicVerticalReferenceFrame. */ DynamicVerticalReferenceFrameNNPtr DynamicVerticalReferenceFrame::create( const util::PropertyMap &properties, diff --git a/src/iso19111/io.cpp b/src/iso19111/io.cpp index eadc54cc84..8467a9e126 100644 --- a/src/iso19111/io.cpp +++ b/src/iso19111/io.cpp @@ -4396,8 +4396,13 @@ class JSONParser { GeodeticCRSNNPtr buildGeodeticCRS(const json &j); ProjectedCRSNNPtr buildProjectedCRS(const json &j); ConversionNNPtr buildConversion(const json &j); + DatumEnsembleNNPtr buildDatumEnsemble(const json &j); GeodeticReferenceFrameNNPtr buildGeodeticReferenceFrame(const json &j); VerticalReferenceFrameNNPtr buildVerticalReferenceFrame(const json &j); + DynamicGeodeticReferenceFrameNNPtr + buildDynamicGeodeticReferenceFrame(const json &j); + DynamicVerticalReferenceFrameNNPtr + buildDynamicVerticalReferenceFrame(const json &j); EllipsoidNNPtr buildEllipsoid(const json &j); PrimeMeridianNNPtr buildPrimeMeridian(const json &j); CoordinateSystemNNPtr buildCS(const json &j); @@ -4690,12 +4695,21 @@ BaseObjectNNPtr JSONParser::create(const json &j) if (type == "BoundCRS") { return buildBoundCRS(j); } + if (type == "DatumEnsemble") { + return buildDatumEnsemble(j); + } if (type == "GeodeticReferenceFrame") { return buildGeodeticReferenceFrame(j); } if (type == "VerticalReferenceFrame") { return buildVerticalReferenceFrame(j); } + if (type == "DynamicGeodeticReferenceFrame") { + return buildDynamicGeodeticReferenceFrame(j); + } + if (type == "DynamicVerticalReferenceFrame") { + return buildDynamicVerticalReferenceFrame(j); + } if (type == "Ellipsoid") { return buildEllipsoid(j); } @@ -4720,12 +4734,20 @@ BaseObjectNNPtr JSONParser::create(const json &j) // --------------------------------------------------------------------------- GeographicCRSNNPtr JSONParser::buildGeographicCRS(const json &j) { - auto datumJ = getObject(j, "datum"); - if (getType(datumJ) != "GeodeticReferenceFrame") { - throw ParsingException("Unsupported type for datum."); - } - auto datum = buildGeodeticReferenceFrame(datumJ); + GeodeticReferenceFramePtr datum; DatumEnsemblePtr datumEnsemble; + if (j.contains("datum")) { + auto datumJ = getObject(j, "datum"); + datum = util::nn_dynamic_pointer_cast( + create(datumJ)); + if (!datum) { + throw ParsingException("datum of wrong type"); + } + + } else { + datumEnsemble = + buildDatumEnsemble(getObject(j, "datum_ensemble")).as_nullable(); + } auto csJ = getObject(j, "coordinate_system"); auto ellipsoidalCS = util::nn_dynamic_pointer_cast(buildCS(csJ)); @@ -4793,17 +4815,25 @@ ProjectedCRSNNPtr JSONParser::buildProjectedCRS(const json &j) { // --------------------------------------------------------------------------- VerticalCRSNNPtr JSONParser::buildVerticalCRS(const json &j) { - auto datumJ = getObject(j, "datum"); - if (getType(datumJ) != "VerticalReferenceFrame") { - throw ParsingException("Unsupported type for datum."); + VerticalReferenceFramePtr datum; + DatumEnsemblePtr datumEnsemble; + if (j.contains("datum")) { + auto datumJ = getObject(j, "datum"); + datum = util::nn_dynamic_pointer_cast( + create(datumJ)); + if (!datum) { + throw ParsingException("datum of wrong type"); + } + } else { + datumEnsemble = + buildDatumEnsemble(getObject(j, "datum_ensemble")).as_nullable(); } - auto datum = buildVerticalReferenceFrame(datumJ); auto csJ = getObject(j, "coordinate_system"); auto verticalCS = util::nn_dynamic_pointer_cast(buildCS(csJ)); if (!verticalCS) { throw ParsingException("expected a vertical CS"); } - return VerticalCRS::create(buildProperties(j), datum, + return VerticalCRS::create(buildProperties(j), datum, datumEnsemble, NN_NO_CHECK(verticalCS)); } @@ -5098,6 +5128,70 @@ CoordinateSystemNNPtr JSONParser::buildCS(const json &j) { // --------------------------------------------------------------------------- +DatumEnsembleNNPtr JSONParser::buildDatumEnsemble(const json &j) { + auto membersJ = getArray(j, "members"); + std::vector datums; + const bool hasEllipsoid(j.contains("ellipsoid")); + for (const auto &memberJ : membersJ) { + if (!memberJ.is_object()) { + throw ParsingException( + "Unexpected type for value of a \"members\" member"); + } + auto datumName(getName(memberJ)); + if (dbContext_ && memberJ.contains("id")) { + auto id = getObject(memberJ, "id"); + auto authority = getString(id, "authority"); + auto authFactory = + AuthorityFactory::create(NN_NO_CHECK(dbContext_), authority); + auto code = id["code"]; + std::string codeStr; + if (code.is_string()) { + codeStr = code.get(); + } else if (code.is_number_integer()) { + codeStr = internal::toString(code.get()); + } else { + throw ParsingException("Unexpected type for value of \"code\""); + } + try { + datums.push_back(authFactory->createDatum(codeStr)); + } catch (const std::exception &) { + throw ParsingException("No Datum of code " + codeStr); + } + continue; + } else if (dbContext_) { + auto authFactory = AuthorityFactory::create(NN_NO_CHECK(dbContext_), + std::string()); + auto list = authFactory->createObjectsFromName( + datumName, {AuthorityFactory::ObjectType::DATUM}, + false /* approximate=false*/); + if (!list.empty()) { + auto datum = util::nn_dynamic_pointer_cast(list.front()); + if (!datum) + throw ParsingException( + "DatumEnsemble member is not a datum"); + datums.push_back(NN_NO_CHECK(datum)); + continue; + } + } + + // Fallback if no db match + if (hasEllipsoid) { + datums.emplace_back(GeodeticReferenceFrame::create( + buildProperties(memberJ), + buildEllipsoid(getObject(j, "ellipsoid")), + optional(), PrimeMeridian::GREENWICH)); + } else { + datums.emplace_back( + VerticalReferenceFrame::create(buildProperties(memberJ))); + } + } + return DatumEnsemble::create( + buildProperties(j), datums, + PositionalAccuracy::create(getString(j, "accuracy"))); +} + +// --------------------------------------------------------------------------- + GeodeticReferenceFrameNNPtr JSONParser::buildGeodeticReferenceFrame(const json &j) { auto ellipsoidJ = getObject(j, "ellipsoid"); @@ -5114,6 +5208,29 @@ JSONParser::buildGeodeticReferenceFrame(const json &j) { // --------------------------------------------------------------------------- +DynamicGeodeticReferenceFrameNNPtr +JSONParser::buildDynamicGeodeticReferenceFrame(const json &j) { + auto ellipsoidJ = getObject(j, "ellipsoid"); + auto pm = j.contains("prime_meridian") + ? buildPrimeMeridian(getObject(j, "prime_meridian")) + : PrimeMeridian::GREENWICH; + optional anchor; + if (j.contains("anchor")) { + anchor = getString(j, "anchor"); + } + Measure frameReferenceEpoch(getNumber(j, "frame_reference_epoch"), + UnitOfMeasure::YEAR); + optional deformationModel; + if (j.contains("deformation_model")) { + deformationModel = getString(j, "deformation_model"); + } + return DynamicGeodeticReferenceFrame::create( + buildProperties(j), buildEllipsoid(ellipsoidJ), anchor, pm, + frameReferenceEpoch, deformationModel); +} + +// --------------------------------------------------------------------------- + VerticalReferenceFrameNNPtr JSONParser::buildVerticalReferenceFrame(const json &j) { optional anchor; @@ -5125,6 +5242,25 @@ JSONParser::buildVerticalReferenceFrame(const json &j) { // --------------------------------------------------------------------------- +DynamicVerticalReferenceFrameNNPtr +JSONParser::buildDynamicVerticalReferenceFrame(const json &j) { + optional anchor; + if (j.contains("anchor")) { + anchor = getString(j, "anchor"); + } + Measure frameReferenceEpoch(getNumber(j, "frame_reference_epoch"), + UnitOfMeasure::YEAR); + optional deformationModel; + if (j.contains("deformation_model")) { + deformationModel = getString(j, "deformation_model"); + } + return DynamicVerticalReferenceFrame::create( + buildProperties(j), anchor, util::optional(), + frameReferenceEpoch, deformationModel); +} + +// --------------------------------------------------------------------------- + PrimeMeridianNNPtr JSONParser::buildPrimeMeridian(const json &j) { if (!j.contains("longitude")) { throw ParsingException("Missing \"longitude\" key"); diff --git a/test/unit/test_io.cpp b/test/unit/test_io.cpp index d3c4c8ec31..67fdcb778d 100644 --- a/test/unit/test_io.cpp +++ b/test/unit/test_io.cpp @@ -9135,7 +9135,7 @@ TEST(wkt_export, invalid_angular_unit) { // --------------------------------------------------------------------------- -TEST(wkt_export, json_import_ellipsoid_flattened_sphere) { +TEST(json_import, ellipsoid_flattened_sphere) { auto json = "{\n" " \"type\": \"Ellipsoid\",\n" " \"name\": \"WGS 84\",\n" @@ -9154,7 +9154,7 @@ TEST(wkt_export, json_import_ellipsoid_flattened_sphere) { // --------------------------------------------------------------------------- -TEST(wkt_export, json_import_ellipsoid_major_minor_custom_unit) { +TEST(json_import, ellipsoid_major_minor_custom_unit) { auto json = "{\n" " \"type\": \"Ellipsoid\",\n" " \"name\": \"foo\",\n" @@ -9176,7 +9176,7 @@ TEST(wkt_export, json_import_ellipsoid_major_minor_custom_unit) { // --------------------------------------------------------------------------- -TEST(wkt_export, json_import_ellipsoid_sphere) { +TEST(json_import, ellipsoid_sphere) { auto json = "{\n" " \"type\": \"Ellipsoid\",\n" " \"name\": \"Sphere\",\n" @@ -9194,7 +9194,7 @@ TEST(wkt_export, json_import_ellipsoid_sphere) { // --------------------------------------------------------------------------- -TEST(wkt_export, json_import_ellipsoid_errors) { +TEST(json_import, ellipsoid_errors) { EXPECT_THROW(createFromUserInput("{", nullptr), ParsingException); EXPECT_THROW(createFromUserInput("{}", nullptr), ParsingException); EXPECT_THROW(createFromUserInput("{ \"type\": \"Ellipsoid\" }", nullptr), @@ -9215,7 +9215,7 @@ TEST(wkt_export, json_import_ellipsoid_errors) { // --------------------------------------------------------------------------- -TEST(wkt_export, json_import_prime_meridian) { +TEST(json_import, prime_meridian) { auto json = "{\n" " \"type\": \"PrimeMeridian\",\n" " \"name\": \"Paris\",\n" @@ -9236,7 +9236,7 @@ TEST(wkt_export, json_import_prime_meridian) { // --------------------------------------------------------------------------- -TEST(wkt_export, json_import_prime_meridian_errors) { +TEST(json_import, prime_meridian_errors) { EXPECT_THROW(createFromUserInput("{ \"type\": \"PrimeMeridian\", \"name\": " "\"foo\" }", nullptr), @@ -9249,8 +9249,7 @@ TEST(wkt_export, json_import_prime_meridian_errors) { // --------------------------------------------------------------------------- -TEST(wkt_export, - json_import_geodetic_reference_frame_with_implicit_prime_meridian) { +TEST(json_import, geodetic_reference_frame_with_implicit_prime_meridian) { auto json = "{\n" " \"type\": \"GeodeticReferenceFrame\",\n" " \"name\": \"World Geodetic System 1984\",\n" @@ -9261,15 +9260,14 @@ TEST(wkt_export, " }\n" "}"; auto obj = createFromUserInput(json, nullptr); - auto gdr = nn_dynamic_pointer_cast(obj); - ASSERT_TRUE(gdr != nullptr); - EXPECT_EQ(gdr->exportToJSON((JSONFormatter::create().get())), json); + auto grf = nn_dynamic_pointer_cast(obj); + ASSERT_TRUE(grf != nullptr); + EXPECT_EQ(grf->exportToJSON((JSONFormatter::create().get())), json); } // --------------------------------------------------------------------------- -TEST(wkt_export, - json_import_geodetic_reference_frame_with_explicit_prime_meridian) { +TEST(json_import, geodetic_reference_frame_with_explicit_prime_meridian) { auto json = "{\n" " \"type\": \"GeodeticReferenceFrame\",\n" " \"name\": \"Nouvelle Triangulation Francaise (Paris)\",\n" @@ -9291,14 +9289,35 @@ TEST(wkt_export, " }\n" "}"; auto obj = createFromUserInput(json, nullptr); - auto gdr = nn_dynamic_pointer_cast(obj); - ASSERT_TRUE(gdr != nullptr); - EXPECT_EQ(gdr->exportToJSON((JSONFormatter::create().get())), json); + auto grf = nn_dynamic_pointer_cast(obj); + ASSERT_TRUE(grf != nullptr); + EXPECT_EQ(grf->exportToJSON((JSONFormatter::create().get())), json); +} + +// --------------------------------------------------------------------------- + +TEST(json_import, + dynamic_geodetic_reference_frame_with_implicit_prime_meridian) { + auto json = "{\n" + " \"type\": \"DynamicGeodeticReferenceFrame\",\n" + " \"name\": \"World Geodetic System 1984\",\n" + " \"frame_reference_epoch\": 1,\n" + " \"deformation_model\": \"foo\",\n" + " \"ellipsoid\": {\n" + " \"name\": \"WGS 84\",\n" + " \"semi_major_axis\": 6378137,\n" + " \"inverse_flattening\": 298.257223563\n" + " }\n" + "}"; + auto obj = createFromUserInput(json, nullptr); + auto dgrf = nn_dynamic_pointer_cast(obj); + ASSERT_TRUE(dgrf != nullptr); + EXPECT_EQ(dgrf->exportToJSON((JSONFormatter::create().get())), json); } // --------------------------------------------------------------------------- -TEST(wkt_export, json_import_geodetic_reference_frame_errors) { +TEST(json_import, geodetic_reference_frame_errors) { EXPECT_THROW( createFromUserInput( "{ \"type\": \"GeodeticReferenceFrame\", \"name\": \"foo\" }", @@ -9308,7 +9327,22 @@ TEST(wkt_export, json_import_geodetic_reference_frame_errors) { // --------------------------------------------------------------------------- -TEST(wkt_export, json_import_several_usages) { +TEST(json_import, dynamic_vertical_reference_frame) { + auto json = "{\n" + " \"type\": \"DynamicVerticalReferenceFrame\",\n" + " \"name\": \"bar\",\n" + " \"frame_reference_epoch\": 1,\n" + " \"deformation_model\": \"foo\"\n" + "}"; + auto obj = createFromUserInput(json, nullptr); + auto dvrf = nn_dynamic_pointer_cast(obj); + ASSERT_TRUE(dvrf != nullptr); + EXPECT_EQ(dvrf->exportToJSON((JSONFormatter::create().get())), json); +} + +// --------------------------------------------------------------------------- + +TEST(json_import, several_usages) { auto json = "{\n" " \"type\": \"GeodeticReferenceFrame\",\n" " \"name\": \"World Geodetic System 1984\",\n" @@ -9341,7 +9375,7 @@ TEST(wkt_export, json_import_several_usages) { // --------------------------------------------------------------------------- -TEST(wkt_export, json_import_geographic_crs) { +TEST(json_import, geographic_crs) { auto json = "{\n" " \"type\": \"GeographicCRS\",\n" " \"name\": \"WGS 84\",\n" @@ -9392,7 +9426,7 @@ TEST(wkt_export, json_import_geographic_crs) { // --------------------------------------------------------------------------- -TEST(wkt_export, json_import_geographic_crs_errors) { +TEST(json_import, geographic_crs_errors) { EXPECT_THROW( createFromUserInput( "{ \"type\": \"GeographicCRS\", \"name\": \"foo\" }", nullptr), @@ -9450,7 +9484,7 @@ TEST(wkt_export, json_import_geographic_crs_errors) { // --------------------------------------------------------------------------- -TEST(wkt_export, json_import_geocentric_crs) { +TEST(json_import, geocentric_crs) { auto json = "{\n" " \"type\": \"GeodeticCRS\",\n" " \"name\": \"WGS 84\",\n" @@ -9495,7 +9529,7 @@ TEST(wkt_export, json_import_geocentric_crs) { // --------------------------------------------------------------------------- -TEST(wkt_export, json_import_projected_crs) { +TEST(json_import, projected_crs) { auto json = "{\n" " \"type\": \"ProjectedCRS\",\n" " \"name\": \"WGS 84 / UTM zone 31N\",\n" @@ -9615,7 +9649,7 @@ TEST(wkt_export, json_import_projected_crs) { // --------------------------------------------------------------------------- -TEST(wkt_export, json_import_compound_crs) { +TEST(json_import, compound_crs) { auto json = "{\n" " \"type\": \"CompoundCRS\",\n" " \"name\": \"WGS 84 + EGM2008 height\",\n" @@ -9687,7 +9721,7 @@ TEST(wkt_export, json_import_compound_crs) { // --------------------------------------------------------------------------- -TEST(wkt_export, json_import_bound_crs) { +TEST(json_import, bound_crs) { auto json = "{\n" " \"type\": \"BoundCRS\",\n" @@ -9788,7 +9822,7 @@ TEST(wkt_export, json_import_bound_crs) { // --------------------------------------------------------------------------- -TEST(wkt_export, json_import_transformation) { +TEST(json_import, transformation) { auto json = "{\n" " \"type\": \"Transformation\",\n" " \"name\": \"GDA94 to GDA2020 (1)\",\n" @@ -9975,7 +10009,7 @@ TEST(wkt_export, json_import_transformation) { // --------------------------------------------------------------------------- -TEST(wkt_export, json_import_concatenated_operation) { +TEST(json_import, concatenated_operation) { auto json = "{\n" " \"type\": \"ConcatenatedOperation\",\n" @@ -10402,3 +10436,183 @@ TEST(wkt_export, json_import_concatenated_operation) { ASSERT_TRUE(concat != nullptr); EXPECT_EQ(concat->exportToJSON((JSONFormatter::create().get())), json); } + +// --------------------------------------------------------------------------- + +TEST(json_import, geographic_crs_with_datum_ensemble) { + auto json = "{\n" + " \"type\": \"GeographicCRS\",\n" + " \"name\": \"WGS 84\",\n" + " \"datum_ensemble\": {\n" + " \"name\": \"WGS 84 ensemble\",\n" + " \"members\": [\n" + " {\n" + " \"name\": \"World Geodetic System 1984 (Transit)\"\n" + " },\n" + " {\n" + " \"name\": \"World Geodetic System 1984 (G730)\"\n" + " }\n" + " ],\n" + " \"ellipsoid\": {\n" + " \"name\": \"WGS 84\",\n" + " \"semi_major_axis\": 6378137,\n" + " \"inverse_flattening\": 298.257223563\n" + " },\n" + " \"accuracy\": \"2\"\n" + " },\n" + " \"coordinate_system\": {\n" + " \"subtype\": \"ellipsoidal\",\n" + " \"axis\": [\n" + " {\n" + " \"name\": \"Latitude\",\n" + " \"abbreviation\": \"lat\",\n" + " \"direction\": \"north\",\n" + " \"unit\": \"degree\"\n" + " },\n" + " {\n" + " \"name\": \"Longitude\",\n" + " \"abbreviation\": \"lon\",\n" + " \"direction\": \"east\",\n" + " \"unit\": \"degree\"\n" + " }\n" + " ]\n" + " }\n" + "}"; + + auto expected_json = + "{\n" + " \"type\": \"GeographicCRS\",\n" + " \"name\": \"WGS 84\",\n" + " \"datum_ensemble\": {\n" + " \"name\": \"WGS 84 ensemble\",\n" + " \"members\": [\n" + " {\n" + " \"name\": \"World Geodetic System 1984 (Transit)\",\n" + " \"id\": {\n" + " \"authority\": \"EPSG\",\n" + " \"code\": 1166\n" + " }\n" + " },\n" + " {\n" + " \"name\": \"World Geodetic System 1984 (G730)\",\n" + " \"id\": {\n" + " \"authority\": \"EPSG\",\n" + " \"code\": 1152\n" + " }\n" + " }\n" + " ],\n" + " \"ellipsoid\": {\n" + " \"name\": \"WGS 84\",\n" + " \"semi_major_axis\": 6378137,\n" + " \"inverse_flattening\": 298.257223563,\n" + " \"id\": {\n" + " \"authority\": \"EPSG\",\n" + " \"code\": 7030\n" + " }\n" + " },\n" + " \"accuracy\": \"2\"\n" + " },\n" + " \"coordinate_system\": {\n" + " \"subtype\": \"ellipsoidal\",\n" + " \"axis\": [\n" + " {\n" + " \"name\": \"Latitude\",\n" + " \"abbreviation\": \"lat\",\n" + " \"direction\": \"north\",\n" + " \"unit\": \"degree\"\n" + " },\n" + " {\n" + " \"name\": \"Longitude\",\n" + " \"abbreviation\": \"lon\",\n" + " \"direction\": \"east\",\n" + " \"unit\": \"degree\"\n" + " }\n" + " ]\n" + " }\n" + "}"; + + { + // No database + auto obj = createFromUserInput(json, nullptr); + auto gcrs = nn_dynamic_pointer_cast(obj); + ASSERT_TRUE(gcrs != nullptr); + EXPECT_EQ(gcrs->exportToJSON((JSONFormatter::create().get())), json); + } + { + auto obj = createFromUserInput(json, DatabaseContext::create()); + auto gcrs = nn_dynamic_pointer_cast(obj); + ASSERT_TRUE(gcrs != nullptr); + EXPECT_EQ(gcrs->exportToJSON((JSONFormatter::create().get())), + expected_json); + } + { + auto obj = + createFromUserInput(expected_json, DatabaseContext::create()); + auto gcrs = nn_dynamic_pointer_cast(obj); + ASSERT_TRUE(gcrs != nullptr); + EXPECT_EQ(gcrs->exportToJSON((JSONFormatter::create().get())), + expected_json); + } +} + +// --------------------------------------------------------------------------- + +TEST(json_import, datum_ensemble_without_ellipsoid) { + auto json = "{\n" + " \"type\": \"DatumEnsemble\",\n" + " \"name\": \"ensemble\",\n" + " \"members\": [\n" + " {\n" + " \"name\": \"member1\"\n" + " },\n" + " {\n" + " \"name\": \"member2\"\n" + " }\n" + " ],\n" + " \"accuracy\": \"2\"\n" + "}"; + + // No database + auto obj = createFromUserInput(json, nullptr); + auto ensemble = nn_dynamic_pointer_cast(obj); + ASSERT_TRUE(ensemble != nullptr); + EXPECT_EQ(ensemble->exportToJSON((JSONFormatter::create().get())), json); +} + +// --------------------------------------------------------------------------- + +TEST(json_import, vertical_crs_with_datum_ensemble) { + auto json = "{\n" + " \"type\": \"VerticalCRS\",\n" + " \"name\": \"foo\",\n" + " \"datum_ensemble\": {\n" + " \"name\": \"ensemble\",\n" + " \"members\": [\n" + " {\n" + " \"name\": \"member1\"\n" + " },\n" + " {\n" + " \"name\": \"member2\"\n" + " }\n" + " ],\n" + " \"accuracy\": \"2\"\n" + " },\n" + " \"coordinate_system\": {\n" + " \"subtype\": \"vertical\",\n" + " \"axis\": [\n" + " {\n" + " \"name\": \"Gravity-related height\",\n" + " \"abbreviation\": \"H\",\n" + " \"direction\": \"up\",\n" + " \"unit\": \"metre\"\n" + " }\n" + " ]\n" + " }\n" + "}"; + + // No database + auto obj = createFromUserInput(json, nullptr); + auto vcrs = nn_dynamic_pointer_cast(obj); + ASSERT_TRUE(vcrs != nullptr); + EXPECT_EQ(vcrs->exportToJSON((JSONFormatter::create().get())), json); +}