aboutsummaryrefslogtreecommitdiff
path: root/src/iso19111/io.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/iso19111/io.cpp')
-rw-r--r--src/iso19111/io.cpp1256
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 &param : 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 &param : 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 &param : 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