aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/Makefile.am6
-rw-r--r--src/apps/projinfo.cpp38
-rw-r--r--src/iso19111/c_api.cpp70
-rw-r--r--src/iso19111/common.cpp141
-rw-r--r--src/iso19111/coordinateoperation.cpp254
-rw-r--r--src/iso19111/coordinatesystem.cpp67
-rw-r--r--src/iso19111/crs.cpp329
-rw-r--r--src/iso19111/datum.cpp317
-rw-r--r--src/iso19111/io.cpp1256
-rw-r--r--src/iso19111/metadata.cpp20
-rw-r--r--src/lib_proj.cmake2
-rw-r--r--src/proj.h4
-rw-r--r--src/proj_internal.h1
-rw-r--r--src/proj_json_streaming_writer.cpp296
-rw-r--r--src/proj_json_streaming_writer.hpp155
15 files changed, 2944 insertions, 12 deletions
diff --git a/src/Makefile.am b/src/Makefile.am
index aed5a393..e50a5638 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -11,7 +11,7 @@ AM_CPPFLAGS = -DPROJ_LIB=\"$(pkgdatadir)\" \
AM_CXXFLAGS = @CXX_WFLAGS@ @FLTO_FLAG@
include_HEADERS = proj.h proj_experimental.h proj_constants.h proj_api.h geodesic.h \
- org_proj4_PJ.h proj_symbol_rename.h
+ org_proj4_PJ.h proj_symbol_rename.h proj_json_streaming_writer.hpp
EXTRA_DIST = bin_cct.cmake bin_gie.cmake bin_cs2cs.cmake \
bin_geod.cmake bin_proj.cmake bin_projinfo.cmake \
@@ -208,7 +208,9 @@ libproj_la_SOURCES = \
wkt1_parser.h wkt1_parser.cpp \
wkt1_generated_parser.h wkt1_generated_parser.c \
wkt2_parser.h wkt2_parser.cpp \
- wkt2_generated_parser.h wkt2_generated_parser.c
+ wkt2_generated_parser.h wkt2_generated_parser.c \
+ \
+ proj_json_streaming_writer.cpp
# The sed hack is to please MSVC
diff --git a/src/apps/projinfo.cpp b/src/apps/projinfo.cpp
index 9d724522..1d75efa6 100644
--- a/src/apps/projinfo.cpp
+++ b/src/apps/projinfo.cpp
@@ -67,6 +67,7 @@ struct OutputOptions {
bool WKT2_2015_SIMPLIFIED = false;
bool WKT1_GDAL = false;
bool WKT1_ESRI = false;
+ bool PROJJSON = false;
bool c_ify = false;
bool singleLine = false;
bool strict = true;
@@ -101,7 +102,7 @@ static void usage() {
std::cerr << std::endl;
std::cerr << "-o: formats is a comma separated combination of: "
"all,default,PROJ,WKT_ALL,WKT2_2015,WKT2_2018,WKT1_GDAL,"
- "WKT1_ESRI"
+ "WKT1_ESRI,PROJJSON"
<< std::endl;
std::cerr << " Except 'all' and 'default', other format can be preceded "
"by '-' to disable them"
@@ -467,6 +468,34 @@ static void outputObject(
std::cerr << "Error when exporting to WKT1_ESRI: " << e.what()
<< std::endl;
}
+ alreadyOutputed = true;
+ }
+ }
+
+ auto JSONExportable = nn_dynamic_pointer_cast<IJSONExportable>(obj);
+ if (JSONExportable) {
+ if (outputOpt.PROJJSON) {
+ try {
+ if (alreadyOutputed) {
+ std::cout << std::endl;
+ }
+ if (!outputOpt.quiet) {
+ std::cout << "PROJJSON:" << std::endl;
+ }
+ auto formatter(JSONFormatter::create(dbContext));
+ if (outputOpt.singleLine) {
+ formatter->setMultiLine(false);
+ }
+ auto jsonString(JSONExportable->exportToJSON(formatter.get()));
+ if (outputOpt.c_ify) {
+ jsonString = c_ify_string(jsonString);
+ }
+ std::cout << jsonString << std::endl;
+ } catch (const std::exception &e) {
+ std::cerr << "Error when exporting to PROJJSON: " << e.what()
+ << std::endl;
+ }
+ // alreadyOutputed = true;
}
}
@@ -720,6 +749,7 @@ int main(int argc, char **argv) {
outputOpt.WKT2_2015 = true;
outputOpt.WKT1_GDAL = true;
outputOpt.WKT1_ESRI = true;
+ outputOpt.PROJJSON = true;
} else if (ci_equal(format, "default")) {
outputOpt.PROJ5 = true;
outputOpt.WKT2_2018 = true;
@@ -779,6 +809,10 @@ int main(int argc, char **argv) {
ci_equal(format, "-WKT1-ESRI") ||
ci_equal(format, "-WKT1:ESRI")) {
outputOpt.WKT1_ESRI = false;
+ } else if (ci_equal(format, "PROJJSON")) {
+ outputOpt.PROJJSON = true;
+ } else if (ci_equal(format, "-PROJJSON")) {
+ outputOpt.PROJJSON = false;
} else {
std::cerr << "Unrecognized value for option -o: " << format
<< std::endl;
@@ -998,7 +1032,7 @@ int main(int argc, char **argv) {
if (outputOpt.quiet &&
(outputOpt.PROJ5 + outputOpt.WKT2_2018 + outputOpt.WKT2_2015 +
- outputOpt.WKT1_GDAL) != 1) {
+ outputOpt.WKT1_GDAL + outputOpt.PROJJSON) != 1) {
std::cerr << "-q can only be used with a single output format"
<< std::endl;
usage();
diff --git a/src/iso19111/c_api.cpp b/src/iso19111/c_api.cpp
index a125261a..f40cf241 100644
--- a/src/iso19111/c_api.cpp
+++ b/src/iso19111/c_api.cpp
@@ -328,9 +328,9 @@ PJ *proj_clone(PJ_CONTEXT *ctx, const PJ *obj) {
// ---------------------------------------------------------------------------
-/** \brief Instantiate an object from a WKT string, PROJ string or object code
+/** \brief Instantiate an object from a WKT string, PROJ string, object code
* (like "EPSG:4326", "urn:ogc:def:crs:EPSG::4326",
- * "urn:ogc:def:coordinateOperation:EPSG::1671").
+ * "urn:ogc:def:coordinateOperation:EPSG::1671") or PROJJSON string.
*
* This function calls osgeo::proj::io::createFromUserInput()
*
@@ -1314,6 +1314,72 @@ const char *proj_as_proj_string(PJ_CONTEXT *ctx, const PJ *obj,
// ---------------------------------------------------------------------------
+/** \brief Get a PROJJSON string representation of an object.
+ *
+ * The returned string is valid while the input obj parameter is valid,
+ * and until a next call to proj_as_proj_string() with the same input
+ * object.
+ *
+ * This function calls
+ * osgeo::proj::io::IJSONExportable::exportToJSON().
+ *
+ * This function may return NULL if the object is not compatible with an
+ * export to the requested type.
+ *
+ * @param ctx PROJ context, or NULL for default context
+ * @param obj Object (must not be NULL)
+ * @param options NULL-terminated list of strings with "KEY=VALUE" format. or
+ * NULL. Currently
+ * supported options are:
+ * <ul>
+ * <li>MULTILINE=YES/NO. Defaults to YES</li>
+ * <li>INDENTATION_WIDTH=number. Defauls to 2 (when multiline output is
+ * on).</li>
+ * <li>SCHEMA=string. URL to PROJJSON schema. Can be set to empty string to
+ * disable it.</li>
+ * </ul>
+ * @return a string, or NULL in case of error.
+ *
+ * @since 6.2
+ */
+const char *proj_as_projjson(PJ_CONTEXT *ctx, const PJ *obj,
+ const char *const *options) {
+ SANITIZE_CTX(ctx);
+ assert(obj);
+ auto exportable = dynamic_cast<const IJSONExportable *>(obj->iso_obj.get());
+ if (!exportable) {
+ proj_log_error(ctx, __FUNCTION__, "Object type not exportable to JSON");
+ return nullptr;
+ }
+
+ auto dbContext = getDBcontextNoException(ctx, __FUNCTION__);
+ try {
+ auto formatter = JSONFormatter::create(dbContext);
+ for (auto iter = options; iter && iter[0]; ++iter) {
+ const char *value;
+ if ((value = getOptionValue(*iter, "MULTILINE="))) {
+ formatter->setMultiLine(ci_equal(value, "YES"));
+ } else if ((value = getOptionValue(*iter, "INDENTATION_WIDTH="))) {
+ formatter->setIndentationWidth(std::atoi(value));
+ } else if ((value = getOptionValue(*iter, "SCHEMA="))) {
+ formatter->setSchema(value);
+ } else {
+ std::string msg("Unknown option :");
+ msg += *iter;
+ proj_log_error(ctx, __FUNCTION__, msg.c_str());
+ return nullptr;
+ }
+ }
+ obj->lastJSONString = exportable->exportToJSON(formatter.get());
+ return obj->lastJSONString.c_str();
+ } catch (const std::exception &e) {
+ proj_log_error(ctx, __FUNCTION__, e.what());
+ return nullptr;
+ }
+}
+
+// ---------------------------------------------------------------------------
+
/** \brief Get the scope of an object.
*
* In case of multiple usages, this will be the one of first usage.
diff --git a/src/iso19111/common.cpp b/src/iso19111/common.cpp
index d46da0da..f375ea0a 100644
--- a/src/iso19111/common.cpp
+++ b/src/iso19111/common.cpp
@@ -236,6 +236,55 @@ void UnitOfMeasure::_exportToWKT(
}
formatter->endNode();
}
+
+// ---------------------------------------------------------------------------
+
+void UnitOfMeasure::_exportToJSON(
+ JSONFormatter *formatter) const // throw(FormattingException)
+{
+ auto &writer = formatter->writer();
+ const auto &l_codeSpace = codeSpace();
+ auto objContext(
+ formatter->MakeObjectContext(nullptr, !l_codeSpace.empty()));
+ writer.AddObjKey("type");
+ const auto l_type = type();
+ if (l_type == Type::LINEAR) {
+ writer.Add("LinearUnit");
+ } else if (l_type == Type::ANGULAR) {
+ writer.Add("AngularUnit");
+ } else if (l_type == Type::SCALE) {
+ writer.Add("ScaleUnit");
+ } else if (l_type == Type::TIME) {
+ writer.Add("TimeUnit");
+ } else if (l_type == Type::PARAMETRIC) {
+ writer.Add("ParametricUnit");
+ } else {
+ writer.Add("Unit");
+ }
+
+ writer.AddObjKey("name");
+ const auto &l_name = name();
+ writer.Add(l_name);
+
+ const auto &factor = conversionToSI();
+ writer.AddObjKey("conversion_factor");
+ writer.Add(factor, 15);
+
+ if (!l_codeSpace.empty() && formatter->outputId()) {
+ writer.AddObjKey("id");
+ auto idContext(formatter->MakeObjectContext(nullptr, false));
+ writer.AddObjKey("authority");
+ writer.Add(l_codeSpace);
+ writer.AddObjKey("code");
+ const auto &l_code = code();
+ try {
+ writer.Add(std::stoi(l_code));
+ } catch (const std::exception &) {
+ writer.Add(l_code);
+ }
+ }
+}
+
//! @endcond
// ---------------------------------------------------------------------------
@@ -815,6 +864,33 @@ void IdentifiedObject::formatRemarks(WKTFormatter *formatter) const {
// ---------------------------------------------------------------------------
+void IdentifiedObject::formatID(JSONFormatter *formatter) const {
+ const auto &ids(identifiers());
+ auto &writer = formatter->writer();
+ if (ids.size() == 1) {
+ writer.AddObjKey("id");
+ ids.front()->_exportToJSON(formatter);
+ } else if (!ids.empty()) {
+ writer.AddObjKey("ids");
+ auto arrayContext(writer.MakeArrayContext());
+ for (const auto &id : ids) {
+ id->_exportToJSON(formatter);
+ }
+ }
+}
+
+// ---------------------------------------------------------------------------
+
+void IdentifiedObject::formatRemarks(JSONFormatter *formatter) const {
+ if (!remarks().empty()) {
+ auto &writer = formatter->writer();
+ writer.AddObjKey("remarks");
+ writer.Add(remarks());
+ }
+}
+
+// ---------------------------------------------------------------------------
+
bool IdentifiedObject::_isEquivalentTo(
const util::IComparable *other,
util::IComparable::Criterion criterion) const {
@@ -966,6 +1042,46 @@ void ObjectDomain::_exportToWKT(WKTFormatter *formatter) const {
// ---------------------------------------------------------------------------
//! @cond Doxygen_Suppress
+void ObjectDomain::_exportToJSON(JSONFormatter *formatter) const {
+ auto &writer = formatter->writer();
+ if (d->scope_.has_value()) {
+ writer.AddObjKey("scope");
+ writer.Add(*(d->scope_));
+ }
+ if (d->domainOfValidity_) {
+ if (d->domainOfValidity_->description().has_value()) {
+ writer.AddObjKey("area");
+ writer.Add(*(d->domainOfValidity_->description()));
+ }
+ if (d->domainOfValidity_->geographicElements().size() == 1) {
+ const auto bbox = dynamic_cast<const GeographicBoundingBox *>(
+ d->domainOfValidity_->geographicElements()[0].get());
+ if (bbox) {
+ writer.AddObjKey("bbox");
+ auto bboxContext(writer.MakeObjectContext());
+ writer.AddObjKey("south_latitude");
+ writer.Add(bbox->southBoundLatitude(), 15);
+ writer.AddObjKey("west_longitude");
+ writer.Add(bbox->westBoundLongitude(), 15);
+ writer.AddObjKey("north_latitude");
+ writer.Add(bbox->northBoundLatitude(), 15);
+ writer.AddObjKey("east_longitude");
+ writer.Add(bbox->eastBoundLongitude(), 15);
+ }
+ }
+ if (d->domainOfValidity_->verticalElements().size() == 1) {
+ // TODO
+ }
+ if (d->domainOfValidity_->temporalElements().size() == 1) {
+ // TODO
+ }
+ }
+}
+//! @endcond
+
+// ---------------------------------------------------------------------------
+
+//! @cond Doxygen_Suppress
bool ObjectDomain::_isEquivalentTo(
const util::IComparable *other,
util::IComparable::Criterion criterion) const {
@@ -1097,6 +1213,31 @@ void ObjectUsage::baseExportToWKT(WKTFormatter *formatter) const {
// ---------------------------------------------------------------------------
+void ObjectUsage::baseExportToJSON(JSONFormatter *formatter) const {
+
+ auto &writer = formatter->writer();
+ if (formatter->outputUsage()) {
+ const auto &l_domains = domains();
+ if (l_domains.size() == 1) {
+ l_domains[0]->_exportToJSON(formatter);
+ } else if (!l_domains.empty()) {
+ writer.AddObjKey("usages");
+ auto arrayContext(writer.MakeArrayContext(false));
+ for (const auto &domain : l_domains) {
+ auto objContext(writer.MakeObjectContext());
+ domain->_exportToJSON(formatter);
+ }
+ }
+ }
+
+ if (formatter->outputId()) {
+ formatID(formatter);
+ }
+ formatRemarks(formatter);
+}
+
+// ---------------------------------------------------------------------------
+
//! @cond Doxygen_Suppress
bool ObjectUsage::_isEquivalentTo(
const util::IComparable *other,
diff --git a/src/iso19111/coordinateoperation.cpp b/src/iso19111/coordinateoperation.cpp
index 822f5822..2dd77f1c 100644
--- a/src/iso19111/coordinateoperation.cpp
+++ b/src/iso19111/coordinateoperation.cpp
@@ -991,6 +991,24 @@ void OperationMethod::_exportToWKT(io::WKTFormatter *formatter) const {
// ---------------------------------------------------------------------------
//! @cond Doxygen_Suppress
+void OperationMethod::_exportToJSON(
+ io::JSONFormatter *formatter) const // throw(FormattingException)
+{
+ auto &writer = formatter->writer();
+ auto objectContext(formatter->MakeObjectContext("OperationMethod",
+ !identifiers().empty()));
+
+ writer.AddObjKey("name");
+ writer.Add(nameStr());
+
+ if (formatter->outputId()) {
+ formatID(formatter);
+ }
+}
+
+// ---------------------------------------------------------------------------
+
+//! @cond Doxygen_Suppress
bool OperationMethod::_isEquivalentTo(
const util::IComparable *other,
util::IComparable::Criterion criterion) const {
@@ -1168,6 +1186,42 @@ void OperationParameterValue::_exportToWKT(io::WKTFormatter *formatter,
// ---------------------------------------------------------------------------
//! @cond Doxygen_Suppress
+void OperationParameterValue::_exportToJSON(
+ io::JSONFormatter *formatter) const {
+ auto &writer = formatter->writer();
+ auto objectContext(formatter->MakeObjectContext(
+ "ParameterValue", !parameter()->identifiers().empty()));
+
+ writer.AddObjKey("name");
+ writer.Add(parameter()->nameStr());
+
+ const auto &l_value(parameterValue());
+ if (l_value->type() == ParameterValue::Type::MEASURE) {
+ writer.AddObjKey("value");
+ writer.Add(l_value->value().value(), 15);
+ writer.AddObjKey("unit");
+ const auto &l_unit(l_value->value().unit());
+ if (l_unit == common::UnitOfMeasure::METRE ||
+ l_unit == common::UnitOfMeasure::DEGREE ||
+ l_unit == common::UnitOfMeasure::SCALE_UNITY) {
+ writer.Add(l_unit.name());
+ } else {
+ l_unit._exportToJSON(formatter);
+ }
+ } else if (l_value->type() == ParameterValue::Type::FILENAME) {
+ writer.AddObjKey("value");
+ writer.Add(l_value->valueFile());
+ }
+
+ if (formatter->outputId()) {
+ parameter()->formatID(formatter);
+ }
+}
+//! @endcond
+
+// ---------------------------------------------------------------------------
+
+//! @cond Doxygen_Suppress
/** Utility method used on WKT2 import to convert from abridged transformation
* to "normal" transformation parameters.
@@ -5410,6 +5464,49 @@ void Conversion::_exportToWKT(io::WKTFormatter *formatter) const {
// ---------------------------------------------------------------------------
//! @cond Doxygen_Suppress
+void Conversion::_exportToJSON(
+ io::JSONFormatter *formatter) const // throw(FormattingException)
+{
+ auto &writer = formatter->writer();
+ auto objectContext(
+ formatter->MakeObjectContext("Conversion", !identifiers().empty()));
+
+ writer.AddObjKey("name");
+ auto l_name = nameStr();
+ if (l_name.empty()) {
+ writer.Add("unnamed");
+ } else {
+ writer.Add(l_name);
+ }
+
+ writer.AddObjKey("method");
+ formatter->setOmitTypeInImmediateChild();
+ formatter->setAllowIDInImmediateChild();
+ method()->_exportToJSON(formatter);
+
+ const auto &l_parameterValues = parameterValues();
+ if (!l_parameterValues.empty()) {
+ writer.AddObjKey("parameters");
+ {
+ auto parametersContext(writer.MakeArrayContext(false));
+ for (const auto &genOpParamvalue : l_parameterValues) {
+ formatter->setAllowIDInImmediateChild();
+ formatter->setOmitTypeInImmediateChild();
+ genOpParamvalue->_exportToJSON(formatter);
+ }
+ }
+ }
+
+ if (formatter->outputId()) {
+ formatID(formatter);
+ }
+}
+
+//! @endcond
+
+// ---------------------------------------------------------------------------
+
+//! @cond Doxygen_Suppress
static bool createPROJ4WebMercator(const Conversion *conv,
io::PROJStringFormatter *formatter) {
const double centralMeridian = conv->parameterValueNumeric(
@@ -7768,6 +7865,76 @@ void Transformation::_exportToWKT(io::WKTFormatter *formatter) const {
// ---------------------------------------------------------------------------
+//! @cond Doxygen_Suppress
+void Transformation::_exportToJSON(
+ io::JSONFormatter *formatter) const // throw(FormattingException)
+{
+ auto &writer = formatter->writer();
+ auto objectContext(formatter->MakeObjectContext(
+ formatter->abridgedTransformation() ? "AbridgedTransformation"
+ : "Transformation",
+ !identifiers().empty()));
+
+ writer.AddObjKey("name");
+ auto l_name = nameStr();
+ if (l_name.empty()) {
+ writer.Add("unnamed");
+ } else {
+ writer.Add(l_name);
+ }
+
+ if (!formatter->abridgedTransformation()) {
+ writer.AddObjKey("source_crs");
+ formatter->setAllowIDInImmediateChild();
+ sourceCRS()->_exportToJSON(formatter);
+
+ writer.AddObjKey("target_crs");
+ formatter->setAllowIDInImmediateChild();
+ targetCRS()->_exportToJSON(formatter);
+
+ const auto &l_interpolationCRS = interpolationCRS();
+ if (l_interpolationCRS) {
+ writer.AddObjKey("interpolation_crs");
+ formatter->setAllowIDInImmediateChild();
+ l_interpolationCRS->_exportToJSON(formatter);
+ }
+ }
+
+ writer.AddObjKey("method");
+ formatter->setOmitTypeInImmediateChild();
+ formatter->setAllowIDInImmediateChild();
+ method()->_exportToJSON(formatter);
+
+ writer.AddObjKey("parameters");
+ {
+ auto parametersContext(writer.MakeArrayContext(false));
+ for (const auto &genOpParamvalue : parameterValues()) {
+ formatter->setAllowIDInImmediateChild();
+ formatter->setOmitTypeInImmediateChild();
+ genOpParamvalue->_exportToJSON(formatter);
+ }
+ }
+
+ if (!formatter->abridgedTransformation()) {
+ if (!coordinateOperationAccuracies().empty()) {
+ writer.AddObjKey("accuracy");
+ writer.Add(coordinateOperationAccuracies()[0]->value());
+ }
+ }
+
+ if (formatter->abridgedTransformation()) {
+ if (formatter->outputId()) {
+ formatID(formatter);
+ }
+ } else {
+ ObjectUsage::baseExportToJSON(formatter);
+ }
+}
+
+//! @endcond
+
+// ---------------------------------------------------------------------------
+
static void exportSourceCRSAndTargetCRSToWKT(const CoordinateOperation *co,
io::WKTFormatter *formatter) {
auto l_sourceCRS = co->sourceCRS();
@@ -9584,6 +9751,46 @@ void ConcatenatedOperation::_exportToWKT(io::WKTFormatter *formatter) const {
// ---------------------------------------------------------------------------
//! @cond Doxygen_Suppress
+void ConcatenatedOperation::_exportToJSON(
+ io::JSONFormatter *formatter) const // throw(FormattingException)
+{
+ auto &writer = formatter->writer();
+ auto objectContext(formatter->MakeObjectContext("ConcatenatedOperation",
+ !identifiers().empty()));
+
+ writer.AddObjKey("name");
+ auto l_name = nameStr();
+ if (l_name.empty()) {
+ writer.Add("unnamed");
+ } else {
+ writer.Add(l_name);
+ }
+
+ writer.AddObjKey("source_crs");
+ formatter->setAllowIDInImmediateChild();
+ sourceCRS()->_exportToJSON(formatter);
+
+ writer.AddObjKey("target_crs");
+ formatter->setAllowIDInImmediateChild();
+ targetCRS()->_exportToJSON(formatter);
+
+ writer.AddObjKey("steps");
+ {
+ auto parametersContext(writer.MakeArrayContext(false));
+ for (const auto &operation : operations()) {
+ formatter->setAllowIDInImmediateChild();
+ operation->_exportToJSON(formatter);
+ }
+ }
+
+ ObjectUsage::baseExportToJSON(formatter);
+}
+
+//! @endcond
+
+// ---------------------------------------------------------------------------
+
+//! @cond Doxygen_Suppress
CoordinateOperationNNPtr ConcatenatedOperation::_shallowClone() const {
auto op =
ConcatenatedOperation::nn_make_shared<ConcatenatedOperation>(*this);
@@ -13055,6 +13262,53 @@ void PROJBasedOperation::_exportToWKT(io::WKTFormatter *formatter) const {
// ---------------------------------------------------------------------------
+void PROJBasedOperation::_exportToJSON(
+ io::JSONFormatter *formatter) const // throw(FormattingException)
+{
+ auto &writer = formatter->writer();
+ auto objectContext(formatter->MakeObjectContext(
+ (sourceCRS() && targetCRS()) ? "Transformation" : "Conversion",
+ !identifiers().empty()));
+
+ writer.AddObjKey("name");
+ auto l_name = nameStr();
+ if (l_name.empty()) {
+ writer.Add("unnamed");
+ } else {
+ writer.Add(l_name);
+ }
+
+ if (sourceCRS() && targetCRS()) {
+ writer.AddObjKey("source_crs");
+ formatter->setAllowIDInImmediateChild();
+ sourceCRS()->_exportToJSON(formatter);
+
+ writer.AddObjKey("target_crs");
+ formatter->setAllowIDInImmediateChild();
+ targetCRS()->_exportToJSON(formatter);
+ }
+
+ writer.AddObjKey("method");
+ formatter->setOmitTypeInImmediateChild();
+ formatter->setAllowIDInImmediateChild();
+ method()->_exportToJSON(formatter);
+
+ const auto &l_parameterValues = parameterValues();
+ if (!l_parameterValues.empty()) {
+ writer.AddObjKey("parameters");
+ {
+ auto parametersContext(writer.MakeArrayContext(false));
+ for (const auto &genOpParamvalue : l_parameterValues) {
+ formatter->setAllowIDInImmediateChild();
+ formatter->setOmitTypeInImmediateChild();
+ genOpParamvalue->_exportToJSON(formatter);
+ }
+ }
+ }
+}
+
+// ---------------------------------------------------------------------------
+
void PROJBasedOperation::_exportToPROJString(
io::PROJStringFormatter *formatter) const {
if (projStringExportable_) {
diff --git a/src/iso19111/coordinatesystem.cpp b/src/iso19111/coordinatesystem.cpp
index ef9cac93..5a852b0d 100644
--- a/src/iso19111/coordinatesystem.cpp
+++ b/src/iso19111/coordinatesystem.cpp
@@ -311,8 +311,8 @@ void CoordinateSystemAxis::_exportToWKT(io::WKTFormatter *formatter, int order,
bool disableAbbrev) const {
const bool isWKT2 = formatter->version() == io::WKTFormatter::Version::WKT2;
formatter->startNode(io::WKTConstants::AXIS, !identifiers().empty());
- std::string axisName = *(name()->description());
- std::string abbrev = abbreviation();
+ const std::string &axisName = nameStr();
+ const std::string &abbrev = abbreviation();
std::string parenthesizedAbbrev = "(" + abbrev + ")";
std::string dir = direction().toString();
std::string axisDesignation;
@@ -393,6 +393,41 @@ void CoordinateSystemAxis::_exportToWKT(io::WKTFormatter *formatter, int order,
// ---------------------------------------------------------------------------
//! @cond Doxygen_Suppress
+void CoordinateSystemAxis::_exportToJSON(
+ io::JSONFormatter *formatter) const // throw(FormattingException)
+{
+ auto &writer = formatter->writer();
+ auto objectContext(
+ formatter->MakeObjectContext("Axis", !identifiers().empty()));
+
+ writer.AddObjKey("name");
+ writer.Add(nameStr());
+
+ writer.AddObjKey("abbreviation");
+ writer.Add(abbreviation());
+
+ writer.AddObjKey("direction");
+ writer.Add(direction().toString());
+
+ const auto &l_unit(unit());
+ if (l_unit == common::UnitOfMeasure::METRE ||
+ l_unit == common::UnitOfMeasure::DEGREE) {
+ writer.AddObjKey("unit");
+ writer.Add(l_unit.name());
+ } else if (l_unit.type() != common::UnitOfMeasure::Type::NONE) {
+ writer.AddObjKey("unit");
+ l_unit._exportToJSON(formatter);
+ }
+
+ if (formatter->outputId()) {
+ formatID(formatter);
+ }
+}
+//! @endcond
+
+// ---------------------------------------------------------------------------
+
+//! @cond Doxygen_Suppress
bool CoordinateSystemAxis::_isEquivalentTo(
const util::IComparable *other,
util::IComparable::Criterion criterion) const {
@@ -535,6 +570,34 @@ void CoordinateSystem::_exportToWKT(
// ---------------------------------------------------------------------------
//! @cond Doxygen_Suppress
+void CoordinateSystem::_exportToJSON(
+ io::JSONFormatter *formatter) const // throw(FormattingException)
+{
+ auto &writer = formatter->writer();
+ auto objectContext(formatter->MakeObjectContext("CoordinateSystem",
+ !identifiers().empty()));
+
+ writer.AddObjKey("subtype");
+ writer.Add(getWKT2Type(true));
+
+ writer.AddObjKey("axis");
+ auto axisContext(writer.MakeArrayContext(false));
+ const auto &l_axisList = axisList();
+ for (auto &axis : l_axisList) {
+ formatter->setOmitTypeInImmediateChild();
+ axis->_exportToJSON(formatter);
+ }
+
+ if (formatter->outputId()) {
+ formatID(formatter);
+ }
+}
+
+//! @endcond
+
+// ---------------------------------------------------------------------------
+
+//! @cond Doxygen_Suppress
bool CoordinateSystem::_isEquivalentTo(
const util::IComparable *other,
util::IComparable::Criterion criterion) const {
diff --git a/src/iso19111/crs.cpp b/src/iso19111/crs.cpp
index 476bc72b..b9694ba2 100644
--- a/src/iso19111/crs.cpp
+++ b/src/iso19111/crs.cpp
@@ -1342,6 +1342,42 @@ void GeodeticCRS::addDatumInfoToPROJString(
// ---------------------------------------------------------------------------
//! @cond Doxygen_Suppress
+void GeodeticCRS::_exportToJSON(
+ io::JSONFormatter *formatter) const // throw(io::FormattingException)
+{
+ auto &writer = formatter->writer();
+ auto objectContext(
+ formatter->MakeObjectContext("GeodeticCRS", !identifiers().empty()));
+
+ writer.AddObjKey("name");
+ auto l_name = nameStr();
+ if (l_name.empty()) {
+ writer.Add("unnamed");
+ } else {
+ writer.Add(l_name);
+ }
+
+ 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();
+ coordinateSystem()->_exportToJSON(formatter);
+
+ ObjectUsage::baseExportToJSON(formatter);
+}
+//! @endcond
+
+// ---------------------------------------------------------------------------
+
+//! @cond Doxygen_Suppress
static util::IComparable::Criterion
getStandardCriterion(util::IComparable::Criterion criterion) {
return criterion == util::IComparable::Criterion::
@@ -2059,6 +2095,42 @@ void GeographicCRS::_exportToPROJString(
// ---------------------------------------------------------------------------
//! @cond Doxygen_Suppress
+void GeographicCRS::_exportToJSON(
+ io::JSONFormatter *formatter) const // throw(io::FormattingException)
+{
+ auto &writer = formatter->writer();
+ auto objectContext(
+ formatter->MakeObjectContext("GeographicCRS", !identifiers().empty()));
+
+ writer.AddObjKey("name");
+ auto l_name = nameStr();
+ if (l_name.empty()) {
+ writer.Add("unnamed");
+ } else {
+ writer.Add(l_name);
+ }
+
+ 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();
+ coordinateSystem()->_exportToJSON(formatter);
+
+ ObjectUsage::baseExportToJSON(formatter);
+}
+//! @endcond
+
+// ---------------------------------------------------------------------------
+
+//! @cond Doxygen_Suppress
struct VerticalCRS::Private {
std::vector<operation::TransformationNNPtr> geoidModel{};
std::vector<operation::PointMotionOperationNNPtr> velocityModel{};
@@ -2197,6 +2269,7 @@ void VerticalCRS::_exportToWKT(io::WKTFormatter *formatter) const {
// ---------------------------------------------------------------------------
+//! @cond Doxygen_Suppress
void VerticalCRS::_exportToPROJString(
io::PROJStringFormatter *formatter) const // throw(io::FormattingException)
{
@@ -2216,6 +2289,43 @@ void VerticalCRS::_exportToPROJString(
}
}
}
+//! @endcond
+
+// ---------------------------------------------------------------------------
+
+//! @cond Doxygen_Suppress
+void VerticalCRS::_exportToJSON(
+ io::JSONFormatter *formatter) const // throw(io::FormattingException)
+{
+ auto &writer = formatter->writer();
+ auto objectContext(
+ formatter->MakeObjectContext("VerticalCRS", !identifiers().empty()));
+
+ writer.AddObjKey("name");
+ auto l_name = nameStr();
+ if (l_name.empty()) {
+ writer.Add("unnamed");
+ } else {
+ writer.Add(l_name);
+ }
+
+ 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();
+ coordinateSystem()->_exportToJSON(formatter);
+
+ ObjectUsage::baseExportToJSON(formatter);
+}
+//! @endcond
// ---------------------------------------------------------------------------
@@ -2582,6 +2692,39 @@ void DerivedCRS::baseExportToWKT(io::WKTFormatter *formatter,
// ---------------------------------------------------------------------------
//! @cond Doxygen_Suppress
+void DerivedCRS::_exportToJSON(
+ io::JSONFormatter *formatter) const // throw(io::FormattingException)
+{
+ auto &writer = formatter->writer();
+ auto objectContext(
+ formatter->MakeObjectContext(className(), !identifiers().empty()));
+
+ writer.AddObjKey("name");
+ auto l_name = nameStr();
+ if (l_name.empty()) {
+ writer.Add("unnamed");
+ } else {
+ writer.Add(l_name);
+ }
+
+ writer.AddObjKey("base_crs");
+ baseCRS()->_exportToJSON(formatter);
+
+ writer.AddObjKey("conversion");
+ formatter->setOmitTypeInImmediateChild();
+ derivingConversionRef()->_exportToJSON(formatter);
+
+ writer.AddObjKey("coordinate_system");
+ formatter->setOmitTypeInImmediateChild();
+ coordinateSystem()->_exportToJSON(formatter);
+
+ ObjectUsage::baseExportToJSON(formatter);
+}
+//! @endcond
+
+// ---------------------------------------------------------------------------
+
+//! @cond Doxygen_Suppress
struct ProjectedCRS::Private {
GeodeticCRSNNPtr baseCRS_;
cs::CartesianCSNNPtr cs_;
@@ -2856,6 +2999,41 @@ void ProjectedCRS::_exportToWKT(io::WKTFormatter *formatter) const {
// ---------------------------------------------------------------------------
+//! @cond Doxygen_Suppress
+void ProjectedCRS::_exportToJSON(
+ io::JSONFormatter *formatter) const // throw(io::FormattingException)
+{
+ auto &writer = formatter->writer();
+ auto objectContext(
+ formatter->MakeObjectContext("ProjectedCRS", !identifiers().empty()));
+
+ writer.AddObjKey("name");
+ auto l_name = nameStr();
+ if (l_name.empty()) {
+ writer.Add("unnamed");
+ } else {
+ writer.Add(l_name);
+ }
+
+ writer.AddObjKey("base_crs");
+ formatter->setAllowIDInImmediateChild();
+ formatter->setOmitTypeInImmediateChild();
+ baseCRS()->_exportToJSON(formatter);
+
+ writer.AddObjKey("conversion");
+ formatter->setOmitTypeInImmediateChild();
+ derivingConversionRef()->_exportToJSON(formatter);
+
+ writer.AddObjKey("coordinate_system");
+ formatter->setOmitTypeInImmediateChild();
+ coordinateSystem()->_exportToJSON(formatter);
+
+ ObjectUsage::baseExportToJSON(formatter);
+}
+//! @endcond
+
+// ---------------------------------------------------------------------------
+
void ProjectedCRS::_exportToPROJString(
io::PROJStringFormatter *formatter) const // throw(io::FormattingException)
{
@@ -3411,6 +3589,36 @@ void CompoundCRS::_exportToWKT(io::WKTFormatter *formatter) const {
// ---------------------------------------------------------------------------
+//! @cond Doxygen_Suppress
+void CompoundCRS::_exportToJSON(
+ io::JSONFormatter *formatter) const // throw(io::FormattingException)
+{
+ auto &writer = formatter->writer();
+ auto objectContext(
+ formatter->MakeObjectContext("CompoundCRS", !identifiers().empty()));
+
+ writer.AddObjKey("name");
+ auto l_name = nameStr();
+ if (l_name.empty()) {
+ writer.Add("unnamed");
+ } else {
+ writer.Add(l_name);
+ }
+
+ writer.AddObjKey("components");
+ {
+ auto componentsContext(writer.MakeArrayContext(false));
+ for (const auto &crs : componentReferenceSystems()) {
+ crs->_exportToJSON(formatter);
+ }
+ }
+
+ ObjectUsage::baseExportToJSON(formatter);
+}
+//! @endcond
+
+// ---------------------------------------------------------------------------
+
void CompoundCRS::_exportToPROJString(
io::PROJStringFormatter *formatter) const // throw(io::FormattingException)
{
@@ -3901,6 +4109,30 @@ void BoundCRS::_exportToWKT(io::WKTFormatter *formatter) const {
// ---------------------------------------------------------------------------
+//! @cond Doxygen_Suppress
+void BoundCRS::_exportToJSON(
+ io::JSONFormatter *formatter) const // throw(io::FormattingException)
+{
+ auto &writer = formatter->writer();
+ auto objectContext(
+ formatter->MakeObjectContext("BoundCRS", !identifiers().empty()));
+
+ writer.AddObjKey("source_crs");
+ d->baseCRS()->_exportToJSON(formatter);
+
+ writer.AddObjKey("target_crs");
+ d->hubCRS()->_exportToJSON(formatter);
+
+ writer.AddObjKey("transformation");
+ formatter->setOmitTypeInImmediateChild();
+ formatter->setAbridgedTransformation(true);
+ d->transformation()->_exportToJSON(formatter);
+ formatter->setAbridgedTransformation(false);
+}
+//! @endcond
+
+// ---------------------------------------------------------------------------
+
void BoundCRS::_exportToPROJString(
io::PROJStringFormatter *formatter) const // throw(io::FormattingException)
{
@@ -4589,6 +4821,36 @@ void TemporalCRS::_exportToWKT(io::WKTFormatter *formatter) const {
// ---------------------------------------------------------------------------
+//! @cond Doxygen_Suppress
+void TemporalCRS::_exportToJSON(
+ io::JSONFormatter *formatter) const // throw(io::FormattingException)
+{
+ auto &writer = formatter->writer();
+ auto objectContext(
+ formatter->MakeObjectContext("TemporalCRS", !identifiers().empty()));
+
+ writer.AddObjKey("name");
+ auto l_name = nameStr();
+ if (l_name.empty()) {
+ writer.Add("unnamed");
+ } else {
+ writer.Add(l_name);
+ }
+
+ writer.AddObjKey("datum");
+ formatter->setOmitTypeInImmediateChild();
+ datum()->_exportToJSON(formatter);
+
+ writer.AddObjKey("coordinate_system");
+ formatter->setOmitTypeInImmediateChild();
+ coordinateSystem()->_exportToJSON(formatter);
+
+ ObjectUsage::baseExportToJSON(formatter);
+}
+//! @endcond
+
+// ---------------------------------------------------------------------------
+
bool TemporalCRS::_isEquivalentTo(
const util::IComparable *other,
util::IComparable::Criterion criterion) const {
@@ -4697,6 +4959,36 @@ void EngineeringCRS::_exportToWKT(io::WKTFormatter *formatter) const {
// ---------------------------------------------------------------------------
+//! @cond Doxygen_Suppress
+void EngineeringCRS::_exportToJSON(
+ io::JSONFormatter *formatter) const // throw(io::FormattingException)
+{
+ auto &writer = formatter->writer();
+ auto objectContext(
+ formatter->MakeObjectContext("EngineeringCRS", !identifiers().empty()));
+
+ writer.AddObjKey("name");
+ auto l_name = nameStr();
+ if (l_name.empty()) {
+ writer.Add("unnamed");
+ } else {
+ writer.Add(l_name);
+ }
+
+ writer.AddObjKey("datum");
+ formatter->setOmitTypeInImmediateChild();
+ datum()->_exportToJSON(formatter);
+
+ writer.AddObjKey("coordinate_system");
+ formatter->setOmitTypeInImmediateChild();
+ coordinateSystem()->_exportToJSON(formatter);
+
+ ObjectUsage::baseExportToJSON(formatter);
+}
+//! @endcond
+
+// ---------------------------------------------------------------------------
+
bool EngineeringCRS::_isEquivalentTo(
const util::IComparable *other,
util::IComparable::Criterion criterion) const {
@@ -4799,6 +5091,36 @@ void ParametricCRS::_exportToWKT(io::WKTFormatter *formatter) const {
// ---------------------------------------------------------------------------
+//! @cond Doxygen_Suppress
+void ParametricCRS::_exportToJSON(
+ io::JSONFormatter *formatter) const // throw(io::FormattingException)
+{
+ auto &writer = formatter->writer();
+ auto objectContext(
+ formatter->MakeObjectContext("ParametricCRS", !identifiers().empty()));
+
+ writer.AddObjKey("name");
+ auto l_name = nameStr();
+ if (l_name.empty()) {
+ writer.Add("unnamed");
+ } else {
+ writer.Add(l_name);
+ }
+
+ writer.AddObjKey("datum");
+ formatter->setOmitTypeInImmediateChild();
+ datum()->_exportToJSON(formatter);
+
+ writer.AddObjKey("coordinate_system");
+ formatter->setOmitTypeInImmediateChild();
+ coordinateSystem()->_exportToJSON(formatter);
+
+ ObjectUsage::baseExportToJSON(formatter);
+}
+//! @endcond
+
+// ---------------------------------------------------------------------------
+
bool ParametricCRS::_isEquivalentTo(
const util::IComparable *other,
util::IComparable::Criterion criterion) const {
@@ -4993,6 +5315,13 @@ DerivedCRSTemplate<DerivedCRSTraits>::create(
// ---------------------------------------------------------------------------
+template <class DerivedCRSTraits>
+const char *DerivedCRSTemplate<DerivedCRSTraits>::className() const {
+ return DerivedCRSTraits::CRSName().c_str();
+}
+
+// ---------------------------------------------------------------------------
+
static void DerivedCRSTemplateCheckExportToWKT(io::WKTFormatter *formatter,
const std::string &crsName,
bool wkt2_2018_only) {
diff --git a/src/iso19111/datum.cpp b/src/iso19111/datum.cpp
index bf3092c1..65905ca9 100644
--- a/src/iso19111/datum.cpp
+++ b/src/iso19111/datum.cpp
@@ -93,6 +93,9 @@ struct Datum::Private {
// cppcheck-suppress functionStatic
void exportAnchorDefinition(io::WKTFormatter *formatter) const;
+
+ // cppcheck-suppress functionStatic
+ void exportAnchorDefinition(io::JSONFormatter *formatter) const;
};
// ---------------------------------------------------------------------------
@@ -105,6 +108,17 @@ void Datum::Private::exportAnchorDefinition(io::WKTFormatter *formatter) const {
}
}
+// ---------------------------------------------------------------------------
+
+void Datum::Private::exportAnchorDefinition(
+ io::JSONFormatter *formatter) const {
+ if (anchorDefinition) {
+ auto &writer = formatter->writer();
+ writer.AddObjKey("anchor");
+ writer.Add(*anchorDefinition);
+ }
+}
+
//! @endcond
// ---------------------------------------------------------------------------
@@ -348,6 +362,40 @@ void PrimeMeridian::_exportToWKT(
// ---------------------------------------------------------------------------
//! @cond Doxygen_Suppress
+void PrimeMeridian::_exportToJSON(
+ io::JSONFormatter *formatter) const // throw(FormattingException)
+{
+ auto &writer = formatter->writer();
+ auto objectContext(
+ formatter->MakeObjectContext("PrimeMeridian", !identifiers().empty()));
+
+ writer.AddObjKey("name");
+ std::string l_name =
+ name()->description().has_value() ? nameStr() : "Greenwich";
+ writer.Add(l_name);
+
+ const auto &l_long = longitude();
+ writer.AddObjKey("longitude");
+ const auto &unit = l_long.unit();
+ if (unit == common::UnitOfMeasure::DEGREE) {
+ writer.Add(l_long.value(), 15);
+ } else {
+ auto longitudeContext(formatter->MakeObjectContext(nullptr, false));
+ writer.AddObjKey("value");
+ writer.Add(l_long.value(), 15);
+ writer.AddObjKey("unit");
+ unit._exportToJSON(formatter);
+ }
+
+ if (formatter->outputId()) {
+ formatID(formatter);
+ }
+}
+//! @endcond
+
+// ---------------------------------------------------------------------------
+
+//! @cond Doxygen_Suppress
std::string
PrimeMeridian::getPROJStringWellKnownName(const common::Angle &angle) {
const double valRad = angle.getSIValue();
@@ -768,6 +816,66 @@ void Ellipsoid::_exportToWKT(
// ---------------------------------------------------------------------------
+//! @cond Doxygen_Suppress
+void Ellipsoid::_exportToJSON(
+ io::JSONFormatter *formatter) const // throw(FormattingException)
+{
+ auto &writer = formatter->writer();
+ auto objectContext(
+ formatter->MakeObjectContext("Ellipsoid", !identifiers().empty()));
+
+ writer.AddObjKey("name");
+ auto l_name = nameStr();
+ if (l_name.empty()) {
+ writer.Add("unnamed");
+ } else {
+ writer.Add(l_name);
+ }
+
+ const auto &semiMajor = semiMajorAxis();
+ const auto &semiMajorUnit = semiMajor.unit();
+ writer.AddObjKey(isSphere() ? "radius" : "semi_major_axis");
+ if (semiMajorUnit == common::UnitOfMeasure::METRE) {
+ writer.Add(semiMajor.value(), 15);
+ } else {
+ auto objContext(formatter->MakeObjectContext(nullptr, false));
+ writer.AddObjKey("value");
+ writer.Add(semiMajor.value(), 15);
+
+ writer.AddObjKey("unit");
+ semiMajorUnit._exportToJSON(formatter);
+ }
+
+ if (!isSphere()) {
+ const auto &l_inverseFlattening = inverseFlattening();
+ if (l_inverseFlattening.has_value()) {
+ writer.AddObjKey("inverse_flattening");
+ writer.Add(l_inverseFlattening->getSIValue(), 15);
+ } else {
+ writer.AddObjKey("semi_minor_axis");
+ const auto &l_semiMinorAxis(semiMinorAxis());
+ const auto &semiMinorAxisUnit(l_semiMinorAxis->unit());
+ if (semiMinorAxisUnit == common::UnitOfMeasure::METRE) {
+ writer.Add(l_semiMinorAxis->value(), 15);
+ } else {
+ auto objContext(formatter->MakeObjectContext(nullptr, false));
+ writer.AddObjKey("value");
+ writer.Add(l_semiMinorAxis->value(), 15);
+
+ writer.AddObjKey("unit");
+ semiMinorAxisUnit._exportToJSON(formatter);
+ }
+ }
+ }
+
+ if (formatter->outputId()) {
+ formatID(formatter);
+ }
+}
+//! @endcond
+
+// ---------------------------------------------------------------------------
+
bool Ellipsoid::lookForProjWellKnownEllps(std::string &projEllpsName,
std::string &ellpsName) const {
const double a = semiMajorAxis().getSIValue();
@@ -1153,6 +1261,55 @@ void GeodeticReferenceFrame::_exportToWKT(
// ---------------------------------------------------------------------------
//! @cond Doxygen_Suppress
+void GeodeticReferenceFrame::_exportToJSON(
+ io::JSONFormatter *formatter) const // throw(FormattingException)
+{
+ auto dynamicGRF = dynamic_cast<const DynamicGeodeticReferenceFrame *>(this);
+
+ auto objectContext(formatter->MakeObjectContext(
+ dynamicGRF ? "DynamicGeodeticReferenceFrame" : "GeodeticReferenceFrame",
+ !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);
+ }
+
+ 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);
+
+ const auto &l_primeMeridian(primeMeridian());
+ if (l_primeMeridian->nameStr() != "Greenwich") {
+ writer.AddObjKey("prime_meridian");
+ formatter->setOmitTypeInImmediateChild();
+ primeMeridian()->_exportToJSON(formatter);
+ }
+
+ ObjectUsage::baseExportToJSON(formatter);
+}
+//! @endcond
+
+// ---------------------------------------------------------------------------
+
+//! @cond Doxygen_Suppress
bool GeodeticReferenceFrame::_isEquivalentTo(
const util::IComparable *other,
util::IComparable::Criterion criterion) const {
@@ -1279,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.
@@ -1288,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,
@@ -1419,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<GeodeticReferenceFrame>(
+ 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.
@@ -1583,6 +1790,44 @@ void VerticalReferenceFrame::_exportToWKT(
// ---------------------------------------------------------------------------
//! @cond Doxygen_Suppress
+void VerticalReferenceFrame::_exportToJSON(
+ io::JSONFormatter *formatter) const // throw(FormattingException)
+{
+ auto dynamicGRF = dynamic_cast<const DynamicVerticalReferenceFrame *>(this);
+
+ auto objectContext(formatter->MakeObjectContext(
+ dynamicGRF ? "DynamicVerticalReferenceFrame" : "VerticalReferenceFrame",
+ !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);
+ }
+
+ 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
+
+// ---------------------------------------------------------------------------
+
+//! @cond Doxygen_Suppress
bool VerticalReferenceFrame::_isEquivalentTo(
const util::IComparable *other,
util::IComparable::Criterion criterion) const {
@@ -1715,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.
@@ -1723,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,
@@ -1846,6 +2091,32 @@ void TemporalDatum::_exportToWKT(
// ---------------------------------------------------------------------------
//! @cond Doxygen_Suppress
+void TemporalDatum::_exportToJSON(
+ io::JSONFormatter *formatter) const // throw(FormattingException)
+{
+ auto objectContext(
+ formatter->MakeObjectContext("TemporalDatum", !identifiers().empty()));
+ auto &writer = formatter->writer();
+
+ writer.AddObjKey("name");
+ writer.Add(nameStr());
+
+ writer.AddObjKey("calendar");
+ writer.Add(calendar());
+
+ const auto &timeOriginStr = temporalOrigin().toString();
+ if (!timeOriginStr.empty()) {
+ writer.AddObjKey("time_origin");
+ writer.Add(timeOriginStr);
+ }
+
+ ObjectUsage::baseExportToJSON(formatter);
+}
+//! @endcond
+
+// ---------------------------------------------------------------------------
+
+//! @cond Doxygen_Suppress
bool TemporalDatum::_isEquivalentTo(
const util::IComparable *other,
util::IComparable::Criterion criterion) const {
@@ -1919,6 +2190,25 @@ void EngineeringDatum::_exportToWKT(
// ---------------------------------------------------------------------------
//! @cond Doxygen_Suppress
+void EngineeringDatum::_exportToJSON(
+ io::JSONFormatter *formatter) const // throw(FormattingException)
+{
+ auto objectContext(formatter->MakeObjectContext("EngineeringDatum",
+ !identifiers().empty()));
+ auto &writer = formatter->writer();
+
+ writer.AddObjKey("name");
+ writer.Add(nameStr());
+
+ Datum::getPrivate()->exportAnchorDefinition(formatter);
+
+ ObjectUsage::baseExportToJSON(formatter);
+}
+//! @endcond
+
+// ---------------------------------------------------------------------------
+
+//! @cond Doxygen_Suppress
bool EngineeringDatum::_isEquivalentTo(
const util::IComparable *other,
util::IComparable::Criterion criterion) const {
@@ -1985,6 +2275,25 @@ void ParametricDatum::_exportToWKT(
// ---------------------------------------------------------------------------
//! @cond Doxygen_Suppress
+void ParametricDatum::_exportToJSON(
+ io::JSONFormatter *formatter) const // throw(FormattingException)
+{
+ auto objectContext(formatter->MakeObjectContext("ParametricDatum",
+ !identifiers().empty()));
+ auto &writer = formatter->writer();
+
+ writer.AddObjKey("name");
+ writer.Add(nameStr());
+
+ Datum::getPrivate()->exportAnchorDefinition(formatter);
+
+ ObjectUsage::baseExportToJSON(formatter);
+}
+//! @endcond
+
+// ---------------------------------------------------------------------------
+
+//! @cond Doxygen_Suppress
bool ParametricDatum::_isEquivalentTo(
const util::IComparable *other,
util::IComparable::Criterion criterion) const {
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
diff --git a/src/iso19111/metadata.cpp b/src/iso19111/metadata.cpp
index 3725b072..41653b32 100644
--- a/src/iso19111/metadata.cpp
+++ b/src/iso19111/metadata.cpp
@@ -1088,6 +1088,26 @@ void Identifier::_exportToWKT(WKTFormatter *formatter) const {
}
}
}
+
+// ---------------------------------------------------------------------------
+
+void Identifier::_exportToJSON(JSONFormatter *formatter) const {
+ const std::string &l_code = code();
+ const std::string &l_codeSpace = *codeSpace();
+ if (!l_codeSpace.empty() && !l_code.empty()) {
+ auto &writer = formatter->writer();
+ auto objContext(formatter->MakeObjectContext(nullptr, false));
+ writer.AddObjKey("authority");
+ writer.Add(l_codeSpace);
+ writer.AddObjKey("code");
+ try {
+ writer.Add(std::stoi(l_code));
+ } catch (const std::exception &) {
+ writer.Add(l_code);
+ }
+ }
+}
+
//! @endcond
// ---------------------------------------------------------------------------
diff --git a/src/lib_proj.cmake b/src/lib_proj.cmake
index 5a0a8070..bad60324 100644
--- a/src/lib_proj.cmake
+++ b/src/lib_proj.cmake
@@ -285,6 +285,7 @@ set(SRC_LIBPROJ_CORE
wkt_parser.cpp
wkt_parser.hpp
zpoly1.cpp
+ proj_json_streaming_writer.cpp
${CMAKE_CURRENT_BINARY_DIR}/proj_config.h
)
@@ -293,6 +294,7 @@ set(HEADERS_LIBPROJ
proj.h
proj_experimental.h
proj_constants.h
+ proj_json_streaming_writer.hpp
geodesic.h
)
diff --git a/src/proj.h b/src/proj.h
index 4328e7ef..4024ddfc 100644
--- a/src/proj.h
+++ b/src/proj.h
@@ -847,6 +847,10 @@ const char PROJ_DLL* proj_as_proj_string(PJ_CONTEXT *ctx,
PJ_PROJ_STRING_TYPE type,
const char* const *options);
+const char PROJ_DLL* proj_as_projjson(PJ_CONTEXT *ctx,
+ const PJ *obj,
+ const char* const *options);
+
PJ PROJ_DLL *proj_get_source_crs(PJ_CONTEXT *ctx,
const PJ *obj);
diff --git a/src/proj_internal.h b/src/proj_internal.h
index 8c365793..761746c1 100644
--- a/src/proj_internal.h
+++ b/src/proj_internal.h
@@ -517,6 +517,7 @@ struct PJconsts {
// cached results
mutable std::string lastWKT{};
mutable std::string lastPROJString{};
+ mutable std::string lastJSONString{};
mutable bool gridsNeededAsked = false;
mutable std::vector<NS_PROJ::operation::GridDescription> gridsNeeded{};
diff --git a/src/proj_json_streaming_writer.cpp b/src/proj_json_streaming_writer.cpp
new file mode 100644
index 00000000..4a9b9bd2
--- /dev/null
+++ b/src/proj_json_streaming_writer.cpp
@@ -0,0 +1,296 @@
+/******************************************************************************
+ *
+ * Project: CPL - Common Portability Library
+ * Purpose: JSon streaming writer
+ * Author: Even Rouault, even.rouault at spatialys.com
+ *
+ ******************************************************************************
+ * Copyright (c) 2019, Even Rouault <even.rouault at spatialys.com>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included
+ * in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+ * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ ****************************************************************************/
+
+/*! @cond Doxygen_Suppress */
+
+#include <vector>
+#include <string>
+
+#include "proj_json_streaming_writer.hpp"
+
+#include <string.h>
+#include <sqlite3.h>
+#include <stdarg.h>
+#include <cmath>
+#define CPLAssert(x) do {} while(0)
+#define CPLIsNan std::isnan
+#define CPLIsInf std::isinf
+#define CPL_FRMT_GIB "%lld"
+#define CPL_FRMT_GUIB "%llu"
+typedef std::uint64_t GUIntBig;
+
+static std::string CPLSPrintf(const char* fmt, ...)
+{
+ std::string res;
+ res.resize(256);
+ va_list list;
+ va_start(list, fmt);
+ sqlite3_vsnprintf(256, &res[0], fmt, list);
+ va_end(list);
+ res.resize(strlen(&res[0]));
+ return res;
+}
+
+NS_PROJ_START
+
+CPLJSonStreamingWriter::CPLJSonStreamingWriter(
+ SerializationFuncType pfnSerializationFunc,
+ void* pUserData):
+ m_pfnSerializationFunc(pfnSerializationFunc),
+ m_pUserData(pUserData)
+{}
+
+CPLJSonStreamingWriter::~CPLJSonStreamingWriter()
+{
+ CPLAssert( m_nLevel == 0 );
+ CPLAssert( m_states.empty() );
+}
+
+void CPLJSonStreamingWriter::Print(const std::string& text)
+{
+ if( m_pfnSerializationFunc )
+ {
+ m_pfnSerializationFunc(text.c_str(), m_pUserData);
+ }
+ else
+ {
+ m_osStr += text;
+ }
+}
+
+void CPLJSonStreamingWriter::SetIndentationSize(int nSpaces)
+{
+ CPLAssert( m_nLevel == 0 );
+ m_osIndent.clear();
+ m_osIndent.resize(nSpaces, ' ');
+}
+
+void CPLJSonStreamingWriter::IncIndent()
+{
+ m_nLevel ++;
+ if( m_bPretty )
+ m_osIndentAcc += m_osIndent;
+}
+
+void CPLJSonStreamingWriter::DecIndent()
+{
+ CPLAssert(m_nLevel > 0);
+ m_nLevel --;
+ if( m_bPretty )
+ m_osIndentAcc.resize(m_osIndentAcc.size() - m_osIndent.size());
+}
+
+std::string CPLJSonStreamingWriter::FormatString(const std::string& str)
+{
+ std::string ret;
+ ret += '"';
+ for( char ch: str )
+ {
+ switch(ch)
+ {
+ case '"' : ret += "\\\""; break;
+ case '\\': ret += "\\\\"; break;
+ case '\b': ret += "\\b"; break;
+ case '\f': ret += "\\f"; break;
+ case '\n': ret += "\\n"; break;
+ case '\r': ret += "\\r"; break;
+ case '\t': ret += "\\t"; break;
+ default:
+ if( static_cast<unsigned char>(ch) < ' ' )
+ ret += CPLSPrintf("\\u%04X", ch);
+ else
+ ret += ch;
+ break;
+ }
+ }
+ ret += '"';
+ return ret;
+}
+
+void CPLJSonStreamingWriter::EmitCommaIfNeeded()
+{
+ if( m_bWaitForValue )
+ {
+ m_bWaitForValue = false;
+ }
+ else if( !m_states.empty() )
+ {
+ if( !m_states.back().bFirstChild )
+ {
+ Print(",");
+ if( m_bPretty && !m_bNewLineEnabled )
+ Print(" ");
+ }
+ if( m_bPretty && m_bNewLineEnabled )
+ {
+ Print("\n");
+ Print(m_osIndentAcc);
+ }
+ m_states.back().bFirstChild = false;
+ }
+}
+
+void CPLJSonStreamingWriter::StartObj()
+{
+ EmitCommaIfNeeded();
+ Print("{");
+ IncIndent();
+ m_states.emplace_back(State(true));
+}
+
+void CPLJSonStreamingWriter::EndObj()
+{
+ CPLAssert(!m_bWaitForValue);
+ CPLAssert( !m_states.empty() );
+ CPLAssert( m_states.back().bIsObj );
+ DecIndent();
+ if( !m_states.back().bFirstChild )
+ {
+ if( m_bPretty && m_bNewLineEnabled )
+ {
+ Print("\n");
+ Print(m_osIndentAcc);
+ }
+ }
+ m_states.pop_back();
+ Print("}");
+}
+
+void CPLJSonStreamingWriter::StartArray()
+{
+ EmitCommaIfNeeded();
+ Print("[");
+ IncIndent();
+ m_states.emplace_back(State(false));
+}
+
+void CPLJSonStreamingWriter::EndArray()
+{
+ CPLAssert( !m_states.empty() );
+ CPLAssert( !m_states.back().bIsObj );
+ DecIndent();
+ if( !m_states.back().bFirstChild )
+ {
+ if( m_bPretty && m_bNewLineEnabled )
+ {
+ Print("\n");
+ Print(m_osIndentAcc);
+ }
+ }
+ m_states.pop_back();
+ Print("]");
+}
+
+void CPLJSonStreamingWriter::AddObjKey(const std::string& key)
+{
+ CPLAssert( !m_states.empty() );
+ CPLAssert( m_states.back().bIsObj );
+ CPLAssert(!m_bWaitForValue);
+ EmitCommaIfNeeded();
+ Print(FormatString(key));
+ Print(m_bPretty ? ": " : ":");
+ m_bWaitForValue = true;
+}
+
+void CPLJSonStreamingWriter::Add(bool bVal)
+{
+ EmitCommaIfNeeded();
+ Print(bVal ? "true" : "false");
+}
+
+void CPLJSonStreamingWriter::Add(const std::string& str)
+{
+ EmitCommaIfNeeded();
+ Print(FormatString(str));
+}
+
+void CPLJSonStreamingWriter::Add(const char* pszStr)
+{
+ EmitCommaIfNeeded();
+ Print(FormatString(pszStr));
+}
+
+void CPLJSonStreamingWriter::Add(GIntBig nVal)
+{
+ EmitCommaIfNeeded();
+ Print(CPLSPrintf(CPL_FRMT_GIB, nVal));
+}
+
+void CPLJSonStreamingWriter::Add(GUInt64 nVal)
+{
+ EmitCommaIfNeeded();
+ Print(CPLSPrintf(CPL_FRMT_GUIB, static_cast<GUIntBig>(nVal)));
+}
+
+void CPLJSonStreamingWriter::Add(float fVal, int nPrecision)
+{
+ EmitCommaIfNeeded();
+ if( CPLIsNan(fVal) )
+ {
+ Print("\"NaN\"");
+ }
+ else if( CPLIsInf(fVal) )
+ {
+ Print( fVal > 0 ? "\"Infinity\"" : "\"-Infinity\"" );
+ }
+ else
+ {
+ char szFormatting[10];
+ snprintf(szFormatting, sizeof(szFormatting), "%%.%dg", nPrecision);
+ Print(CPLSPrintf(szFormatting, fVal));
+ }
+}
+
+void CPLJSonStreamingWriter::Add(double dfVal, int nPrecision)
+{
+ EmitCommaIfNeeded();
+ if( CPLIsNan(dfVal) )
+ {
+ Print("\"NaN\"");
+ }
+ else if( CPLIsInf(dfVal) )
+ {
+ Print( dfVal > 0 ? "\"Infinity\"" : "\"-Infinity\"" );
+ }
+ else
+ {
+ char szFormatting[10];
+ snprintf(szFormatting, sizeof(szFormatting), "%%.%dg", nPrecision);
+ Print(CPLSPrintf(szFormatting, dfVal));
+ }
+}
+
+void CPLJSonStreamingWriter::AddNull()
+{
+ EmitCommaIfNeeded();
+ Print("null");
+}
+
+NS_PROJ_END
+
+/*! @endcond */
diff --git a/src/proj_json_streaming_writer.hpp b/src/proj_json_streaming_writer.hpp
new file mode 100644
index 00000000..62676842
--- /dev/null
+++ b/src/proj_json_streaming_writer.hpp
@@ -0,0 +1,155 @@
+/******************************************************************************
+ *
+ * Project: CPL - Common Portability Library
+ * Purpose: JSon streaming writer
+ * Author: Even Rouault, even.rouault at spatialys.com
+ *
+ ******************************************************************************
+ * Copyright (c) 2019, Even Rouault <even.rouault at spatialys.com>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included
+ * in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+ * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ ****************************************************************************/
+
+#ifndef PROJ_JSON_STREAMING_WRITER_H
+#define PROJ_JSON_STREAMING_WRITER_H
+
+/*! @cond Doxygen_Suppress */
+
+#include <vector>
+#include <string>
+
+#define CPL_DLL
+
+#include "proj/util.hpp"
+NS_PROJ_START
+
+typedef std::int64_t GIntBig;
+typedef std::uint64_t GUInt64;
+
+class CPL_DLL CPLJSonStreamingWriter
+{
+public:
+ typedef void (*SerializationFuncType)(const char* pszTxt, void* pUserData);
+
+private:
+ CPLJSonStreamingWriter(const CPLJSonStreamingWriter&) = delete;
+ CPLJSonStreamingWriter& operator=(const CPLJSonStreamingWriter&) = delete;
+
+ std::string m_osStr{};
+ SerializationFuncType m_pfnSerializationFunc = nullptr;
+ void* m_pUserData = nullptr;
+ bool m_bPretty = true;
+ std::string m_osIndent = std::string(" ");
+ std::string m_osIndentAcc{};
+ int m_nLevel = 0;
+ bool m_bNewLineEnabled = true;
+ struct State
+ {
+ bool bIsObj = false;
+ bool bFirstChild = true;
+ explicit State(bool bIsObjIn): bIsObj(bIsObjIn) {}
+ };
+ std::vector<State> m_states{};
+ bool m_bWaitForValue = false;
+
+ void Print(const std::string& text);
+ void IncIndent();
+ void DecIndent();
+ static std::string FormatString(const std::string& str);
+ void EmitCommaIfNeeded();
+
+public:
+ CPLJSonStreamingWriter(SerializationFuncType pfnSerializationFunc,
+ void* pUserData);
+ ~CPLJSonStreamingWriter();
+
+ void SetPrettyFormatting(bool bPretty) { m_bPretty = bPretty; }
+ void SetIndentationSize(int nSpaces);
+
+ // cppcheck-suppress functionStatic
+ const std::string& GetString() const { return m_osStr; }
+
+ void Add(const std::string& str);
+ void Add(const char* pszStr);
+ void Add(bool bVal);
+ void Add(int nVal) { Add(static_cast<GIntBig>(nVal)); }
+ void Add(unsigned int nVal) { Add(static_cast<GIntBig>(nVal)); }
+ void Add(GIntBig nVal);
+ void Add(GUInt64 nVal);
+ void Add(float fVal, int nPrecision = 9);
+ void Add(double dfVal, int nPrecision = 18);
+ void AddNull();
+
+ void StartObj();
+ void EndObj();
+ void AddObjKey(const std::string& key);
+ struct CPL_DLL ObjectContext
+ {
+ CPLJSonStreamingWriter& m_serializer;
+
+ ObjectContext(const ObjectContext &) = delete;
+ ObjectContext(ObjectContext&&) = default;
+
+ explicit inline ObjectContext(CPLJSonStreamingWriter& serializer):
+ m_serializer(serializer) { m_serializer.StartObj(); }
+ ~ObjectContext() { m_serializer.EndObj(); }
+ };
+ inline ObjectContext MakeObjectContext() { return ObjectContext(*this); }
+
+ void StartArray();
+ void EndArray();
+ struct CPL_DLL ArrayContext
+ {
+ CPLJSonStreamingWriter& m_serializer;
+ bool m_bForceSingleLine;
+ bool m_bNewLineEnabledBackup;
+
+ ArrayContext(const ArrayContext &) = delete;
+ ArrayContext(ArrayContext&&) = default;
+
+ inline explicit ArrayContext(CPLJSonStreamingWriter& serializer,
+ bool bForceSingleLine = false):
+ m_serializer(serializer),
+ m_bForceSingleLine(bForceSingleLine),
+ m_bNewLineEnabledBackup(serializer.GetNewLine())
+ {
+ if( m_bForceSingleLine )
+ serializer.SetNewline(false);
+ m_serializer.StartArray();
+
+ }
+ ~ArrayContext()
+ {
+ m_serializer.EndArray();
+ if( m_bForceSingleLine )
+ m_serializer.SetNewline(m_bNewLineEnabledBackup);
+ }
+ };
+ inline ArrayContext MakeArrayContext(bool bForceSingleLine = false)
+ { return ArrayContext(*this, bForceSingleLine); }
+
+ bool GetNewLine() const { return m_bNewLineEnabled; }
+ void SetNewline(bool bEnabled) { m_bNewLineEnabled = bEnabled; }
+};
+
+NS_PROJ_END
+
+/*! @endcond */
+
+#endif // PROJ_JSON_STREAMING_WRITER_H