diff options
Diffstat (limited to 'src/iso19111/io.cpp')
| -rw-r--r-- | src/iso19111/io.cpp | 1256 |
1 files changed, 1256 insertions, 0 deletions
diff --git a/src/iso19111/io.cpp b/src/iso19111/io.cpp index 0db97d0b..5ccd9642 100644 --- a/src/iso19111/io.cpp +++ b/src/iso19111/io.cpp @@ -58,8 +58,11 @@ #include "proj/internal/internal.hpp" #include "proj/internal/io_internal.hpp" +#include "proj/internal/include_nlohmann_json.hpp" + #include "proj_constants.h" +#include "proj_json_streaming_writer.hpp" #include "wkt1_parser.h" #include "wkt2_parser.h" @@ -79,8 +82,14 @@ using namespace NS_PROJ::metadata; using namespace NS_PROJ::operation; using namespace NS_PROJ::util; +using json = nlohmann::json; + //! @cond Doxygen_Suppress static const std::string emptyString{}; + +// If changing that value, change it in data/projjson.schema.json as well +#define PROJJSON_CURRENT_VERSION \ + "https://proj.org/schemas/v0.1/projjson.schema.json" //! @endcond #if 0 @@ -4371,10 +4380,1078 @@ BaseObjectNNPtr WKTParser::Private::build(const WKTNodeNNPtr &node) { // --------------------------------------------------------------------------- +class JSONParser { + DatabaseContextPtr dbContext_{}; + + static std::string getString(const json &j, const char *key); + static json getObject(const json &j, const char *key); + static json getArray(const json &j, const char *key); + static double getNumber(const json &j, const char *key); + static UnitOfMeasure getUnit(const json &j, const char *key); + static std::string getName(const json &j); + static std::string getType(const json &j); + static Length getLength(const json &j, const char *key); + static Measure getMeasure(const json &j); + + IdentifierNNPtr buildId(const json &j, bool removeInverseOf); + ObjectDomainPtr buildObjectDomain(const json &j); + PropertyMap buildProperties(const json &j, bool removeInverseOf = false); + + GeographicCRSNNPtr buildGeographicCRS(const json &j); + 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); + CoordinateSystemAxisNNPtr buildAxis(const json &j); + VerticalCRSNNPtr buildVerticalCRS(const json &j); + CRSNNPtr buildCRS(const json &j); + CompoundCRSNNPtr buildCompoundCRS(const json &j); + BoundCRSNNPtr buildBoundCRS(const json &j); + TransformationNNPtr buildTransformation(const json &j); + ConcatenatedOperationNNPtr buildConcatenatedOperation(const json &j); + + static util::optional<std::string> getAnchor(const json &j) { + util::optional<std::string> anchor; + if (j.contains("anchor")) { + anchor = getString(j, "anchor"); + } + return anchor; + } + + EngineeringDatumNNPtr buildEngineeringDatum(const json &j) { + return EngineeringDatum::create(buildProperties(j), getAnchor(j)); + } + + ParametricDatumNNPtr buildParametricDatum(const json &j) { + return ParametricDatum::create(buildProperties(j), getAnchor(j)); + } + + TemporalDatumNNPtr buildTemporalDatum(const json &j) { + auto calendar = getString(j, "calendar"); + auto origin = DateTime::create(j.contains("time_origin") + ? getString(j, "time_origin") + : std::string()); + return TemporalDatum::create(buildProperties(j), origin, calendar); + } + + template <class TargetCRS, class DatumBuilderType, + class CS = CoordinateSystem> + util::nn<std::shared_ptr<TargetCRS>> buildCRS(const json &j, + DatumBuilderType f) { + auto datum = (this->*f)(getObject(j, "datum")); + auto cs = buildCS(getObject(j, "coordinate_system")); + auto csCast = util::nn_dynamic_pointer_cast<CS>(cs); + if (!csCast) { + throw ParsingException("coordinate_system not of expected type"); + } + return TargetCRS::create(buildProperties(j), datum, + NN_NO_CHECK(csCast)); + } + + template <class TargetCRS, class BaseCRS, class CS = CoordinateSystem> + util::nn<std::shared_ptr<TargetCRS>> buildDerivedCRS(const json &j) { + auto baseCRSObj = create(getObject(j, "base_crs")); + auto baseCRS = util::nn_dynamic_pointer_cast<BaseCRS>(baseCRSObj); + if (!baseCRS) { + throw ParsingException("base_crs not of expected type"); + } + auto cs = buildCS(getObject(j, "coordinate_system")); + auto csCast = util::nn_dynamic_pointer_cast<CS>(cs); + if (!csCast) { + throw ParsingException("coordinate_system not of expected type"); + } + auto conv = buildConversion(getObject(j, "conversion")); + return TargetCRS::create(buildProperties(j), NN_NO_CHECK(baseCRS), conv, + NN_NO_CHECK(csCast)); + } + + public: + JSONParser() = default; + + JSONParser &attachDatabaseContext(const DatabaseContextPtr &dbContext) { + dbContext_ = dbContext; + return *this; + } + + BaseObjectNNPtr create(const json &j); +}; + +// --------------------------------------------------------------------------- + +std::string JSONParser::getString(const json &j, const char *key) { + if (!j.contains(key)) { + throw ParsingException(std::string("Missing \"") + key + "\" key"); + } + auto v = j[key]; + if (!v.is_string()) { + throw ParsingException(std::string("The value of \"") + key + + "\" should be a string"); + } + return v.get<std::string>(); +} + +// --------------------------------------------------------------------------- + +json JSONParser::getObject(const json &j, const char *key) { + if (!j.contains(key)) { + throw ParsingException(std::string("Missing \"") + key + "\" key"); + } + auto v = j[key]; + if (!v.is_object()) { + throw ParsingException(std::string("The value of \"") + key + + "\" should be a object"); + } + return v.get<json>(); +} + +// --------------------------------------------------------------------------- + +json JSONParser::getArray(const json &j, const char *key) { + if (!j.contains(key)) { + throw ParsingException(std::string("Missing \"") + key + "\" key"); + } + auto v = j[key]; + if (!v.is_array()) { + throw ParsingException(std::string("The value of \"") + key + + "\" should be a array"); + } + return v.get<json>(); +} + +// --------------------------------------------------------------------------- + +double JSONParser::getNumber(const json &j, const char *key) { + if (!j.contains(key)) { + throw ParsingException(std::string("Missing \"") + key + "\" key"); + } + auto v = j[key]; + if (!v.is_number()) { + throw ParsingException(std::string("The value of \"") + key + + "\" should be a number"); + } + return v.get<double>(); +} + +// --------------------------------------------------------------------------- + +UnitOfMeasure JSONParser::getUnit(const json &j, const char *key) { + if (!j.contains(key)) { + throw ParsingException(std::string("Missing \"") + key + "\" key"); + } + auto v = j[key]; + if (v.is_string()) { + auto vStr = v.get<std::string>(); + for (const auto &unit : {UnitOfMeasure::METRE, UnitOfMeasure::DEGREE, + UnitOfMeasure::SCALE_UNITY}) { + if (vStr == unit.name()) + return unit; + } + throw ParsingException("Unknown unit name: " + vStr); + } + if (!v.is_object()) { + throw ParsingException(std::string("The value of \"") + key + + "\" should be a string or an object"); + } + auto typeStr = getType(v); + UnitOfMeasure::Type type = UnitOfMeasure::Type::UNKNOWN; + if (typeStr == "LinearUnit") { + type = UnitOfMeasure::Type::LINEAR; + } else if (typeStr == "AngularUnit") { + type = UnitOfMeasure::Type::ANGULAR; + } else if (typeStr == "ScaleUnit") { + type = UnitOfMeasure::Type::SCALE; + } else if (typeStr == "TimeUnit") { + type = UnitOfMeasure::Type::TIME; + } else if (typeStr == "ParametricUnit") { + type = UnitOfMeasure::Type::PARAMETRIC; + } else if (typeStr == "Unit") { + type = UnitOfMeasure::Type::UNKNOWN; + } else { + throw ParsingException("Unsupported value of \"type\""); + } + auto nameStr = getName(v); + auto convFactor = getNumber(v, "conversion_factor"); + std::string authorityStr; + std::string codeStr; + if (v.contains("authority") && v.contains("code")) { + authorityStr = getString(v, "authority"); + auto code = v["code"]; + if (code.is_string()) { + codeStr = code.get<std::string>(); + } else if (code.is_number_integer()) { + codeStr = internal::toString(code.get<int>()); + } else { + throw ParsingException("Unexpected type for value of \"code\""); + } + } + return UnitOfMeasure(nameStr, convFactor, type, authorityStr, codeStr); +} + +// --------------------------------------------------------------------------- + +std::string JSONParser::getName(const json &j) { return getString(j, "name"); } + +// --------------------------------------------------------------------------- + +std::string JSONParser::getType(const json &j) { return getString(j, "type"); } + +// --------------------------------------------------------------------------- + +Length JSONParser::getLength(const json &j, const char *key) { + if (!j.contains(key)) { + throw ParsingException(std::string("Missing \"") + key + "\" key"); + } + auto v = j[key]; + if (v.is_number()) { + return Length(v.get<double>(), UnitOfMeasure::METRE); + } + if (v.is_object()) { + return Length(getMeasure(v)); + } + throw ParsingException(std::string("The value of \"") + key + + "\" should be a number or an object"); +} + +// --------------------------------------------------------------------------- + +Measure JSONParser::getMeasure(const json &j) { + return Measure(getNumber(j, "value"), getUnit(j, "unit")); +} + +// --------------------------------------------------------------------------- + +ObjectDomainPtr JSONParser::buildObjectDomain(const json &j) { + optional<std::string> scope; + if (j.contains("scope")) { + scope = getString(j, "scope"); + } + std::string area; + if (j.contains("area")) { + area = getString(j, "area"); + } + std::vector<GeographicExtentNNPtr> geogExtent; + if (j.contains("bbox")) { + auto bbox = getObject(j, "bbox"); + double south = getNumber(bbox, "south_latitude"); + double west = getNumber(bbox, "west_longitude"); + double north = getNumber(bbox, "north_latitude"); + double east = getNumber(bbox, "east_longitude"); + geogExtent.emplace_back( + GeographicBoundingBox::create(west, south, east, north)); + } + if (scope.has_value() || !area.empty() || !geogExtent.empty()) { + util::optional<std::string> description; + if (!area.empty()) + description = area; + ExtentPtr extent; + if (description.has_value() || !geogExtent.empty()) { + extent = + Extent::create(description, geogExtent, {}, {}).as_nullable(); + } + return ObjectDomain::create(scope, extent).as_nullable(); + } + return nullptr; +} + +// --------------------------------------------------------------------------- + +IdentifierNNPtr JSONParser::buildId(const json &j, bool removeInverseOf) { + + PropertyMap propertiesId; + auto codeSpace(getString(j, "authority")); + if (removeInverseOf && starts_with(codeSpace, "INVERSE(") && + codeSpace.back() == ')') { + codeSpace = codeSpace.substr(strlen("INVERSE(")); + codeSpace.resize(codeSpace.size() - 1); + } + propertiesId.set(metadata::Identifier::CODESPACE_KEY, codeSpace); + propertiesId.set(metadata::Identifier::AUTHORITY_KEY, codeSpace); + if (!j.contains("code")) { + throw ParsingException("Missing \"code\" key"); + } + std::string code; + auto codeJ = j["code"]; + if (codeJ.is_string()) { + code = codeJ.get<std::string>(); + } else if (codeJ.is_number_integer()) { + code = internal::toString(codeJ.get<int>()); + } else { + throw ParsingException("Unexpected type for value of \"code\""); + } + return Identifier::create(code, propertiesId); +} + +// --------------------------------------------------------------------------- + +PropertyMap JSONParser::buildProperties(const json &j, bool removeInverseOf) { + PropertyMap map; + std::string name(getName(j)); + if (removeInverseOf && starts_with(name, "Inverse of ")) { + name = name.substr(strlen("Inverse of ")); + } + map.set(IdentifiedObject::NAME_KEY, name); + + if (j.contains("ids")) { + auto idsJ = getArray(j, "ids"); + auto identifiers = ArrayOfBaseObject::create(); + for (const auto &idJ : idsJ) { + if (!idJ.is_object()) { + throw ParsingException( + "Unexpected type for value of \"ids\" child"); + } + identifiers->add(buildId(idJ, removeInverseOf)); + } + map.set(IdentifiedObject::IDENTIFIERS_KEY, identifiers); + } else if (j.contains("id")) { + auto idJ = getObject(j, "id"); + auto identifiers = ArrayOfBaseObject::create(); + identifiers->add(buildId(idJ, removeInverseOf)); + map.set(IdentifiedObject::IDENTIFIERS_KEY, identifiers); + } + + if (j.contains("remarks")) { + map.set(IdentifiedObject::REMARKS_KEY, getString(j, "remarks")); + } + + if (j.contains("usages")) { + ArrayOfBaseObjectNNPtr array = ArrayOfBaseObject::create(); + auto usages = j["usages"]; + if (!usages.is_array()) { + throw ParsingException("Unexpected type for value of \"usages\""); + } + for (const auto &usage : usages) { + if (!usage.is_object()) { + throw ParsingException( + "Unexpected type for value of \"usages\" child"); + } + auto objectDomain = buildObjectDomain(usage); + if (!objectDomain) { + throw ParsingException("missing children in \"usages\" child"); + } + array->add(NN_NO_CHECK(objectDomain)); + } + if (!array->empty()) { + map.set(ObjectUsage::OBJECT_DOMAIN_KEY, array); + } + } else { + auto objectDomain = buildObjectDomain(j); + if (objectDomain) { + map.set(ObjectUsage::OBJECT_DOMAIN_KEY, NN_NO_CHECK(objectDomain)); + } + } + + return map; +} + +// --------------------------------------------------------------------------- + +BaseObjectNNPtr JSONParser::create(const json &j) + +{ + if (!j.is_object()) { + throw ParsingException("JSON object expected"); + } + auto type = getString(j, "type"); + if (type == "GeographicCRS") { + return buildGeographicCRS(j); + } + if (type == "GeodeticCRS") { + return buildGeodeticCRS(j); + } + if (type == "ProjectedCRS") { + return buildProjectedCRS(j); + } + if (type == "VerticalCRS") { + return buildVerticalCRS(j); + } + if (type == "CompoundCRS") { + return buildCompoundCRS(j); + } + if (type == "BoundCRS") { + return buildBoundCRS(j); + } + if (type == "EngineeringCRS") { + return buildCRS<EngineeringCRS>(j, &JSONParser::buildEngineeringDatum); + } + if (type == "ParametricCRS") { + return buildCRS<ParametricCRS, + decltype(&JSONParser::buildParametricDatum), + ParametricCS>(j, &JSONParser::buildParametricDatum); + } + if (type == "TemporalCRS") { + return buildCRS<TemporalCRS, decltype(&JSONParser::buildTemporalDatum), + TemporalCS>(j, &JSONParser::buildTemporalDatum); + } + if (type == "DerivedGeodeticCRS") { + auto baseCRSObj = create(getObject(j, "base_crs")); + auto baseCRS = util::nn_dynamic_pointer_cast<GeodeticCRS>(baseCRSObj); + if (!baseCRS) { + throw ParsingException("base_crs not of expected type"); + } + auto cs = buildCS(getObject(j, "coordinate_system")); + auto conv = buildConversion(getObject(j, "conversion")); + auto csCartesian = util::nn_dynamic_pointer_cast<CartesianCS>(cs); + if (csCartesian) + return DerivedGeodeticCRS::create(buildProperties(j), + NN_NO_CHECK(baseCRS), conv, + NN_NO_CHECK(csCartesian)); + auto csSpherical = util::nn_dynamic_pointer_cast<SphericalCS>(cs); + if (csSpherical) + return DerivedGeodeticCRS::create(buildProperties(j), + NN_NO_CHECK(baseCRS), conv, + NN_NO_CHECK(csSpherical)); + throw ParsingException("coordinate_system not of expected type"); + } + if (type == "DerivedGeographicCRS") { + return buildDerivedCRS<DerivedGeographicCRS, GeodeticCRS, + EllipsoidalCS>(j); + } + if (type == "DerivedProjectedCRS") { + return buildDerivedCRS<DerivedProjectedCRS, ProjectedCRS>(j); + } + if (type == "DerivedVerticalCRS") { + return buildDerivedCRS<DerivedVerticalCRS, VerticalCRS, VerticalCS>(j); + } + if (type == "DerivedEngineeringCRS") { + return buildDerivedCRS<DerivedEngineeringCRS, EngineeringCRS>(j); + } + if (type == "DerivedParametricCRS") { + return buildDerivedCRS<DerivedParametricCRS, ParametricCRS, + ParametricCS>(j); + } + if (type == "DerivedTemporalCRS") { + return buildDerivedCRS<DerivedTemporalCRS, TemporalCRS, TemporalCS>(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 == "EngineeringDatum") { + return buildEngineeringDatum(j); + } + if (type == "ParametricDatum") { + return buildParametricDatum(j); + } + if (type == "TemporalDatum") { + return buildTemporalDatum(j); + } + if (type == "Ellipsoid") { + return buildEllipsoid(j); + } + if (type == "PrimeMeridian") { + return buildPrimeMeridian(j); + } + if (type == "CoordinateSystem") { + return buildCS(j); + } + if (type == "Conversion") { + return buildConversion(j); + } + if (type == "Transformation") { + return buildTransformation(j); + } + if (type == "ConcatenatedOperation") { + return buildConcatenatedOperation(j); + } + throw ParsingException("Unsupported value of \"type\""); +} + +// --------------------------------------------------------------------------- + +GeographicCRSNNPtr JSONParser::buildGeographicCRS(const json &j) { + GeodeticReferenceFramePtr datum; + DatumEnsemblePtr datumEnsemble; + if (j.contains("datum")) { + auto datumJ = getObject(j, "datum"); + datum = util::nn_dynamic_pointer_cast<GeodeticReferenceFrame>( + 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<EllipsoidalCS>(buildCS(csJ)); + if (!ellipsoidalCS) { + throw ParsingException("expected an ellipsoidal CS"); + } + return GeographicCRS::create(buildProperties(j), datum, datumEnsemble, + NN_NO_CHECK(ellipsoidalCS)); +} + +// --------------------------------------------------------------------------- + +GeodeticCRSNNPtr JSONParser::buildGeodeticCRS(const json &j) { + auto datumJ = getObject(j, "datum"); + if (getType(datumJ) != "GeodeticReferenceFrame") { + throw ParsingException("Unsupported type for datum."); + } + auto datum = buildGeodeticReferenceFrame(datumJ); + DatumEnsemblePtr datumEnsemble; + auto csJ = getObject(j, "coordinate_system"); + auto cs = buildCS(csJ); + auto props = buildProperties(j); + auto cartesianCS = nn_dynamic_pointer_cast<CartesianCS>(cs); + if (cartesianCS) { + if (cartesianCS->axisList().size() != 3) { + throw ParsingException( + "Cartesian CS for a GeodeticCRS should have 3 axis"); + } + try { + return GeodeticCRS::create(props, datum, datumEnsemble, + NN_NO_CHECK(cartesianCS)); + } catch (const util::Exception &e) { + throw ParsingException(std::string("buildGeodeticCRS: ") + + e.what()); + } + } + + auto sphericalCS = nn_dynamic_pointer_cast<SphericalCS>(cs); + if (sphericalCS) { + try { + return GeodeticCRS::create(props, datum, datumEnsemble, + NN_NO_CHECK(sphericalCS)); + } catch (const util::Exception &e) { + throw ParsingException(std::string("buildGeodeticCRS: ") + + e.what()); + } + } + throw ParsingException("expected a Cartesian or spherical CS"); +} + +// --------------------------------------------------------------------------- + +ProjectedCRSNNPtr JSONParser::buildProjectedCRS(const json &j) { + auto baseCRS = buildGeographicCRS(getObject(j, "base_crs")); + auto csJ = getObject(j, "coordinate_system"); + auto cartesianCS = util::nn_dynamic_pointer_cast<CartesianCS>(buildCS(csJ)); + if (!cartesianCS) { + throw ParsingException("expected a Cartesian CS"); + } + auto conv = buildConversion(getObject(j, "conversion")); + return ProjectedCRS::create(buildProperties(j), baseCRS, conv, + NN_NO_CHECK(cartesianCS)); +} + +// --------------------------------------------------------------------------- + +VerticalCRSNNPtr JSONParser::buildVerticalCRS(const json &j) { + VerticalReferenceFramePtr datum; + DatumEnsemblePtr datumEnsemble; + if (j.contains("datum")) { + auto datumJ = getObject(j, "datum"); + datum = util::nn_dynamic_pointer_cast<VerticalReferenceFrame>( + 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 verticalCS = util::nn_dynamic_pointer_cast<VerticalCS>(buildCS(csJ)); + if (!verticalCS) { + throw ParsingException("expected a vertical CS"); + } + return VerticalCRS::create(buildProperties(j), datum, datumEnsemble, + NN_NO_CHECK(verticalCS)); +} + +// --------------------------------------------------------------------------- + +CRSNNPtr JSONParser::buildCRS(const json &j) { + auto crs = util::nn_dynamic_pointer_cast<CRS>(create(j)); + if (crs) { + return NN_NO_CHECK(crs); + } + throw ParsingException("Object is not a CRS"); +} + +// --------------------------------------------------------------------------- + +CompoundCRSNNPtr JSONParser::buildCompoundCRS(const json &j) { + auto componentsJ = getArray(j, "components"); + std::vector<CRSNNPtr> components; + for (const auto &componentJ : componentsJ) { + if (!componentJ.is_object()) { + throw ParsingException( + "Unexpected type for a \"components\" child"); + } + components.push_back(buildCRS(componentJ)); + } + return CompoundCRS::create(buildProperties(j), components); +} + +// --------------------------------------------------------------------------- + +ConversionNNPtr JSONParser::buildConversion(const json &j) { + auto methodJ = getObject(j, "method"); + auto convProps = buildProperties(j); + auto methodProps = buildProperties(methodJ); + if (!j.contains("parameters")) { + return Conversion::create(convProps, methodProps, {}, {}); + } + + auto parametersJ = getArray(j, "parameters"); + std::vector<OperationParameterNNPtr> parameters; + std::vector<ParameterValueNNPtr> values; + for (const auto ¶m : parametersJ) { + if (!param.is_object()) { + throw ParsingException( + "Unexpected type for a \"parameters\" child"); + } + parameters.emplace_back( + OperationParameter::create(buildProperties(param))); + values.emplace_back(ParameterValue::create(getMeasure(param))); + } + + std::string convName; + std::string methodName; + if (convProps.getStringValue(IdentifiedObject::NAME_KEY, convName) && + methodProps.getStringValue(IdentifiedObject::NAME_KEY, methodName) && + starts_with(convName, "Inverse of ") && + starts_with(methodName, "Inverse of ")) { + + auto invConvProps = buildProperties(j, true); + auto invMethodProps = buildProperties(methodJ, true); + return NN_NO_CHECK(util::nn_dynamic_pointer_cast<Conversion>( + Conversion::create(invConvProps, invMethodProps, parameters, values) + ->inverse())); + } + return Conversion::create(convProps, methodProps, parameters, values); +} + +// --------------------------------------------------------------------------- + +BoundCRSNNPtr JSONParser::buildBoundCRS(const json &j) { + + auto sourceCRS = buildCRS(getObject(j, "source_crs")); + auto targetCRS = buildCRS(getObject(j, "target_crs")); + auto transformationJ = getObject(j, "transformation"); + auto methodJ = getObject(transformationJ, "method"); + auto parametersJ = getArray(transformationJ, "parameters"); + std::vector<OperationParameterNNPtr> parameters; + std::vector<ParameterValueNNPtr> values; + for (const auto ¶m : parametersJ) { + if (!param.is_object()) { + throw ParsingException( + "Unexpected type for a \"parameters\" child"); + } + parameters.emplace_back( + OperationParameter::create(buildProperties(param))); + if (param.contains("value")) { + auto v = param["value"]; + if (v.is_string()) { + values.emplace_back( + ParameterValue::createFilename(v.get<std::string>())); + continue; + } + } + values.emplace_back(ParameterValue::create(getMeasure(param))); + } + + CRSPtr sourceTransformationCRS; + if (dynamic_cast<GeographicCRS *>(targetCRS.get())) { + sourceTransformationCRS = sourceCRS->extractGeographicCRS(); + if (!sourceTransformationCRS) { + sourceTransformationCRS = + std::dynamic_pointer_cast<VerticalCRS>(sourceCRS.as_nullable()); + if (!sourceTransformationCRS) { + throw ParsingException( + "Cannot find GeographicCRS or VerticalCRS in sourceCRS"); + } + } + } else { + sourceTransformationCRS = sourceCRS; + } + + auto transformation = Transformation::create( + buildProperties(transformationJ), NN_NO_CHECK(sourceTransformationCRS), + targetCRS, nullptr, buildProperties(methodJ), parameters, values, + std::vector<PositionalAccuracyNNPtr>()); + + return BoundCRS::create(sourceCRS, targetCRS, transformation); +} + +// --------------------------------------------------------------------------- + +TransformationNNPtr JSONParser::buildTransformation(const json &j) { + + auto sourceCRS = buildCRS(getObject(j, "source_crs")); + auto targetCRS = buildCRS(getObject(j, "target_crs")); + auto methodJ = getObject(j, "method"); + auto parametersJ = getArray(j, "parameters"); + std::vector<OperationParameterNNPtr> parameters; + std::vector<ParameterValueNNPtr> values; + for (const auto ¶m : parametersJ) { + if (!param.is_object()) { + throw ParsingException( + "Unexpected type for a \"parameters\" child"); + } + parameters.emplace_back( + OperationParameter::create(buildProperties(param))); + if (param.contains("value")) { + auto v = param["value"]; + if (v.is_string()) { + values.emplace_back( + ParameterValue::createFilename(v.get<std::string>())); + continue; + } + } + values.emplace_back(ParameterValue::create(getMeasure(param))); + } + CRSPtr interpolationCRS; + if (j.contains("interpolation_crs")) { + interpolationCRS = + buildCRS(getObject(j, "interpolation_crs")).as_nullable(); + } + std::vector<PositionalAccuracyNNPtr> accuracies; + if (j.contains("accuracy")) { + accuracies.push_back( + PositionalAccuracy::create(getString(j, "accuracy"))); + } + + return Transformation::create(buildProperties(j), sourceCRS, targetCRS, + interpolationCRS, buildProperties(methodJ), + parameters, values, accuracies); +} + +// --------------------------------------------------------------------------- + +ConcatenatedOperationNNPtr +JSONParser::buildConcatenatedOperation(const json &j) { + + auto sourceCRS = buildCRS(getObject(j, "source_crs")); + auto targetCRS = buildCRS(getObject(j, "target_crs")); + auto stepsJ = getArray(j, "steps"); + std::vector<CoordinateOperationNNPtr> operations; + for (const auto &stepJ : stepsJ) { + if (!stepJ.is_object()) { + throw ParsingException("Unexpected type for a \"steps\" child"); + } + auto op = nn_dynamic_pointer_cast<CoordinateOperation>(create(stepJ)); + if (!op) { + throw ParsingException("Invalid content in a \"steps\" child"); + } + operations.emplace_back(NN_NO_CHECK(op)); + } + + ConcatenatedOperation::fixStepsDirection(sourceCRS, targetCRS, operations); + + try { + return ConcatenatedOperation::create( + buildProperties(j), operations, + std::vector<PositionalAccuracyNNPtr>()); + } catch (const InvalidOperation &e) { + throw ParsingException( + std::string("Cannot build concatenated operation: ") + e.what()); + } +} + +// --------------------------------------------------------------------------- + +CoordinateSystemAxisNNPtr JSONParser::buildAxis(const json &j) { + auto dirString = getString(j, "direction"); + auto abbreviation = getString(j, "abbreviation"); + auto unit = j.contains("unit") ? getUnit(j, "unit") + : UnitOfMeasure(std::string(), 1.0, + UnitOfMeasure::Type::NONE); + auto direction = AxisDirection::valueOf(dirString); + if (!direction) { + throw ParsingException(concat("unhandled axis direction: ", dirString)); + } + return CoordinateSystemAxis::create(buildProperties(j), abbreviation, + *direction, unit, + nullptr /* meridian */); +} + +// --------------------------------------------------------------------------- + +CoordinateSystemNNPtr JSONParser::buildCS(const json &j) { + auto subtype = getString(j, "subtype"); + if (!j.contains("axis")) { + throw ParsingException("Missing \"axis\" key"); + } + auto jAxisList = j["axis"]; + if (!jAxisList.is_array()) { + throw ParsingException("Unexpected type for value of \"axis\""); + } + std::vector<CoordinateSystemAxisNNPtr> axisList; + for (const auto &axis : jAxisList) { + if (!axis.is_object()) { + throw ParsingException( + "Unexpected type for value of a \"axis\" member"); + } + axisList.emplace_back(buildAxis(axis)); + } + const PropertyMap &csMap = emptyPropertyMap; + if (subtype == "ellipsoidal") { + if (axisList.size() == 2) { + return EllipsoidalCS::create(csMap, axisList[0], axisList[1]); + } + if (axisList.size() == 3) { + return EllipsoidalCS::create(csMap, axisList[0], axisList[1], + axisList[2]); + } + throw ParsingException("Expected 2 or 3 axis"); + } + if (subtype == "Cartesian") { + if (axisList.size() == 2) { + return CartesianCS::create(csMap, axisList[0], axisList[1]); + } + if (axisList.size() == 3) { + return CartesianCS::create(csMap, axisList[0], axisList[1], + axisList[2]); + } + throw ParsingException("Expected 2 or 3 axis"); + } + if (subtype == "vertical") { + if (axisList.size() == 1) { + return VerticalCS::create(csMap, axisList[0]); + } + throw ParsingException("Expected 1 axis"); + } + if (subtype == "spherical") { + if (axisList.size() == 3) { + return SphericalCS::create(csMap, axisList[0], axisList[1], + axisList[2]); + } + throw ParsingException("Expected 3 axis"); + } + if (subtype == "ordinal") { + return OrdinalCS::create(csMap, axisList); + } + if (subtype == "parametric") { + if (axisList.size() == 1) { + return ParametricCS::create(csMap, axisList[0]); + } + throw ParsingException("Expected 1 axis"); + } + if (subtype == "TemporalDateTime") { + if (axisList.size() == 1) { + return DateTimeTemporalCS::create(csMap, axisList[0]); + } + throw ParsingException("Expected 1 axis"); + } + if (subtype == "TemporalCount") { + if (axisList.size() == 1) { + return TemporalCountCS::create(csMap, axisList[0]); + } + throw ParsingException("Expected 1 axis"); + } + if (subtype == "TemporalMeasure") { + if (axisList.size() == 1) { + return TemporalMeasureCS::create(csMap, axisList[0]); + } + throw ParsingException("Expected 1 axis"); + } + throw ParsingException("Unhandled value for subtype"); +} + +// --------------------------------------------------------------------------- + +DatumEnsembleNNPtr JSONParser::buildDatumEnsemble(const json &j) { + auto membersJ = getArray(j, "members"); + std::vector<DatumNNPtr> 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<std::string>(); + } else if (code.is_number_integer()) { + codeStr = internal::toString(code.get<int>()); + } 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<Datum>(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<std::string>(), 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"); + auto pm = j.contains("prime_meridian") + ? buildPrimeMeridian(getObject(j, "prime_meridian")) + : PrimeMeridian::GREENWICH; + return GeodeticReferenceFrame::create( + buildProperties(j), buildEllipsoid(ellipsoidJ), getAnchor(j), pm); +} + +// --------------------------------------------------------------------------- + +DynamicGeodeticReferenceFrameNNPtr +JSONParser::buildDynamicGeodeticReferenceFrame(const json &j) { + auto ellipsoidJ = getObject(j, "ellipsoid"); + auto pm = j.contains("prime_meridian") + ? buildPrimeMeridian(getObject(j, "prime_meridian")) + : PrimeMeridian::GREENWICH; + Measure frameReferenceEpoch(getNumber(j, "frame_reference_epoch"), + UnitOfMeasure::YEAR); + optional<std::string> deformationModel; + if (j.contains("deformation_model")) { + deformationModel = getString(j, "deformation_model"); + } + return DynamicGeodeticReferenceFrame::create( + buildProperties(j), buildEllipsoid(ellipsoidJ), getAnchor(j), pm, + frameReferenceEpoch, deformationModel); +} + +// --------------------------------------------------------------------------- + +VerticalReferenceFrameNNPtr +JSONParser::buildVerticalReferenceFrame(const json &j) { + return VerticalReferenceFrame::create(buildProperties(j), getAnchor(j)); +} + +// --------------------------------------------------------------------------- + +DynamicVerticalReferenceFrameNNPtr +JSONParser::buildDynamicVerticalReferenceFrame(const json &j) { + Measure frameReferenceEpoch(getNumber(j, "frame_reference_epoch"), + UnitOfMeasure::YEAR); + optional<std::string> deformationModel; + if (j.contains("deformation_model")) { + deformationModel = getString(j, "deformation_model"); + } + return DynamicVerticalReferenceFrame::create( + buildProperties(j), getAnchor(j), util::optional<RealizationMethod>(), + frameReferenceEpoch, deformationModel); +} + +// --------------------------------------------------------------------------- + +PrimeMeridianNNPtr JSONParser::buildPrimeMeridian(const json &j) { + if (!j.contains("longitude")) { + throw ParsingException("Missing \"longitude\" key"); + } + auto longitude = j["longitude"]; + if (longitude.is_number()) { + return PrimeMeridian::create( + buildProperties(j), + Angle(longitude.get<double>(), UnitOfMeasure::DEGREE)); + } else if (longitude.is_object()) { + return PrimeMeridian::create(buildProperties(j), + Angle(getMeasure(longitude))); + } + throw ParsingException("Unexpected type for value of \"longitude\""); +} + +// --------------------------------------------------------------------------- + +EllipsoidNNPtr JSONParser::buildEllipsoid(const json &j) { + if (j.contains("semi_major_axis")) { + auto semiMajorAxis = getLength(j, "semi_major_axis"); + const auto celestialBody( + Ellipsoid::guessBodyName(dbContext_, semiMajorAxis.getSIValue())); + if (j.contains("semi_minor_axis")) { + return Ellipsoid::createTwoAxis(buildProperties(j), semiMajorAxis, + getLength(j, "semi_minor_axis"), + celestialBody); + } else if (j.contains("inverse_flattening")) { + return Ellipsoid::createFlattenedSphere( + buildProperties(j), semiMajorAxis, + Scale(getNumber(j, "inverse_flattening")), celestialBody); + } else { + throw ParsingException( + "Missing semi_minor_axis or inverse_flattening"); + } + } else if (j.contains("radius")) { + auto radius = getLength(j, "radius"); + const auto celestialBody( + Ellipsoid::guessBodyName(dbContext_, radius.getSIValue())); + return Ellipsoid::createSphere(buildProperties(j), radius, + celestialBody); + } + throw ParsingException("Missing semi_major_axis or radius"); +} + +// --------------------------------------------------------------------------- + static BaseObjectNNPtr createFromUserInput(const std::string &text, const DatabaseContextPtr &dbContext, bool usePROJ4InitRules, PJ_CONTEXT *ctx) { + if (!text.empty() && text[0] == '{') { + json j; + try { + j = json::parse(text); + } catch (const std::exception &e) { + throw ParsingException(e.what()); + } + return JSONParser().attachDatabaseContext(dbContext).create(j); + } if (!ci_starts_with(text, "step proj=") && !ci_starts_with(text, "step +proj=")) { @@ -4747,6 +5824,7 @@ static BaseObjectNNPtr createFromUserInput(const std::string &text, * <li>an Object name. e.g "WGS 84", "WGS 84 / UTM zone 31N". In that case as * uniqueness is not guaranteed, the function may apply heuristics to * determine the appropriate best match.</li> + * <li>PROJJSON string</li> * </ul> * * @param text One of the above mentioned text format @@ -4795,6 +5873,7 @@ BaseObjectNNPtr createFromUserInput(const std::string &text, * <li>an Object name. e.g "WGS 84", "WGS 84 / UTM zone 31N". In that case as * uniqueness is not guaranteed, the function may apply heuristics to * determine the appropriate best match.</li> + * <li>PROJJSON string</li> * </ul> * * @param text One of the above mentioned text format @@ -8016,5 +9095,182 @@ PROJStringParser::createFromPROJString(const std::string &projString) { nullptr, nullptr, {}); } +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +struct JSONFormatter::Private { + CPLJSonStreamingWriter writer_{nullptr, nullptr}; + DatabaseContextPtr dbContext_{}; + + std::vector<bool> stackHasId_{false}; + std::vector<bool> outputIdStack_{true}; + bool allowIDInImmediateChild_ = false; + bool omitTypeInImmediateChild_ = false; + bool abridgedTransformation_ = false; + std::string schema_ = PROJJSON_CURRENT_VERSION; + + std::string result_{}; + + // cppcheck-suppress functionStatic + void pushOutputId(bool outputIdIn) { outputIdStack_.push_back(outputIdIn); } + + // cppcheck-suppress functionStatic + void popOutputId() { outputIdStack_.pop_back(); } +}; +//! @endcond + +// --------------------------------------------------------------------------- + +/** \brief Constructs a new formatter. + * + * A formatter can be used only once (its internal state is mutated) + * + * @return new formatter. + */ +JSONFormatterNNPtr JSONFormatter::create( // cppcheck-suppress passedByValue + DatabaseContextPtr dbContext) { + auto ret = NN_NO_CHECK(JSONFormatter::make_unique<JSONFormatter>()); + ret->d->dbContext_ = dbContext; + return ret; +} + +// --------------------------------------------------------------------------- + +/** \brief Whether to use multi line output or not. */ +JSONFormatter &JSONFormatter::setMultiLine(bool multiLine) noexcept { + d->writer_.SetPrettyFormatting(multiLine); + return *this; +} + +// --------------------------------------------------------------------------- + +/** \brief Set number of spaces for each indentation level (defaults to 4). + */ +JSONFormatter &JSONFormatter::setIndentationWidth(int width) noexcept { + d->writer_.SetIndentationSize(width); + return *this; +} + +// --------------------------------------------------------------------------- + +/** \brief Set the value of the "$schema" key in the top level object. + * + * If set to empty string, it will not be written. + */ +JSONFormatter &JSONFormatter::setSchema(const std::string &schema) noexcept { + d->schema_ = schema; + return *this; +} + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress + +JSONFormatter::JSONFormatter() : d(internal::make_unique<Private>()) {} + +// --------------------------------------------------------------------------- + +JSONFormatter::~JSONFormatter() = default; + +// --------------------------------------------------------------------------- + +CPLJSonStreamingWriter &JSONFormatter::writer() const { return d->writer_; } + +// --------------------------------------------------------------------------- + +bool JSONFormatter::outputId() const { return d->outputIdStack_.back(); } + +// --------------------------------------------------------------------------- + +bool JSONFormatter::outputUsage() const { + return outputId() && d->outputIdStack_.size() == 2; +} + +// --------------------------------------------------------------------------- + +void JSONFormatter::setAllowIDInImmediateChild() { + d->allowIDInImmediateChild_ = true; +} + +// --------------------------------------------------------------------------- + +void JSONFormatter::setOmitTypeInImmediateChild() { + d->omitTypeInImmediateChild_ = true; +} + +// --------------------------------------------------------------------------- + +JSONFormatter::ObjectContext::ObjectContext(JSONFormatter &formatter, + const char *objectType, bool hasId) + : m_formatter(formatter) { + m_formatter.d->writer_.StartObj(); + if (m_formatter.d->outputIdStack_.size() == 1 && + !m_formatter.d->schema_.empty()) { + m_formatter.d->writer_.AddObjKey("$schema"); + m_formatter.d->writer_.Add(m_formatter.d->schema_); + } + if (objectType && !m_formatter.d->omitTypeInImmediateChild_) { + m_formatter.d->writer_.AddObjKey("type"); + m_formatter.d->writer_.Add(objectType); + } + m_formatter.d->omitTypeInImmediateChild_ = false; + // All intermediate nodes shouldn't have ID if a parent has an ID + // unless explicitly enabled. + if (m_formatter.d->allowIDInImmediateChild_) { + m_formatter.d->pushOutputId(m_formatter.d->outputIdStack_[0]); + m_formatter.d->allowIDInImmediateChild_ = false; + } else { + m_formatter.d->pushOutputId(m_formatter.d->outputIdStack_[0] && + !m_formatter.d->stackHasId_.back()); + } + + m_formatter.d->stackHasId_.push_back(hasId || + m_formatter.d->stackHasId_.back()); +} + +// --------------------------------------------------------------------------- + +JSONFormatter::ObjectContext::~ObjectContext() { + m_formatter.d->writer_.EndObj(); + m_formatter.d->stackHasId_.pop_back(); + m_formatter.d->popOutputId(); +} + +// --------------------------------------------------------------------------- + +void JSONFormatter::setAbridgedTransformation(bool outputIn) { + d->abridgedTransformation_ = outputIn; +} + +// --------------------------------------------------------------------------- + +bool JSONFormatter::abridgedTransformation() const { + return d->abridgedTransformation_; +} + +//! @endcond + +// --------------------------------------------------------------------------- + +/** \brief Return the serialized JSON. + */ +const std::string &JSONFormatter::toString() const { + return d->writer_.GetString(); +} + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +IJSONExportable::~IJSONExportable() = default; + +// --------------------------------------------------------------------------- + +std::string IJSONExportable::exportToJSON(JSONFormatter *formatter) const { + _exportToJSON(formatter); + return formatter->toString(); +} + +//! @endcond + } // namespace io NS_PROJ_END |
