diff options
| author | Even Rouault <even.rouault@spatialys.com> | 2019-08-20 13:22:01 +0200 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2019-08-20 13:22:01 +0200 |
| commit | 2c9c015a6529548f5a5d448c78bc9b565d751590 (patch) | |
| tree | 2597c59e8270f1480785c97f7441ee0b295c29b3 /src | |
| parent | e52fc2aa58504e6f0658da821bdd543d7a39df34 (diff) | |
| parent | cad1c5cf61fc00759bf4ad17b0b34f57f4945de6 (diff) | |
| download | PROJ-2c9c015a6529548f5a5d448c78bc9b565d751590.tar.gz PROJ-2c9c015a6529548f5a5d448c78bc9b565d751590.zip | |
Merge pull request #1547 from rouault/json_export
Add CRS JSON export (refs #1545)
Diffstat (limited to 'src')
| -rw-r--r-- | src/Makefile.am | 6 | ||||
| -rw-r--r-- | src/apps/projinfo.cpp | 38 | ||||
| -rw-r--r-- | src/iso19111/c_api.cpp | 70 | ||||
| -rw-r--r-- | src/iso19111/common.cpp | 141 | ||||
| -rw-r--r-- | src/iso19111/coordinateoperation.cpp | 254 | ||||
| -rw-r--r-- | src/iso19111/coordinatesystem.cpp | 67 | ||||
| -rw-r--r-- | src/iso19111/crs.cpp | 329 | ||||
| -rw-r--r-- | src/iso19111/datum.cpp | 317 | ||||
| -rw-r--r-- | src/iso19111/io.cpp | 1256 | ||||
| -rw-r--r-- | src/iso19111/metadata.cpp | 20 | ||||
| -rw-r--r-- | src/lib_proj.cmake | 2 | ||||
| -rw-r--r-- | src/proj.h | 4 | ||||
| -rw-r--r-- | src/proj_internal.h | 1 | ||||
| -rw-r--r-- | src/proj_json_streaming_writer.cpp | 296 | ||||
| -rw-r--r-- | src/proj_json_streaming_writer.hpp | 155 |
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 ¶m : parametersJ) { + if (!param.is_object()) { + throw ParsingException( + "Unexpected type for a \"parameters\" child"); + } + parameters.emplace_back( + OperationParameter::create(buildProperties(param))); + values.emplace_back(ParameterValue::create(getMeasure(param))); + } + + std::string convName; + std::string methodName; + if (convProps.getStringValue(IdentifiedObject::NAME_KEY, convName) && + methodProps.getStringValue(IdentifiedObject::NAME_KEY, methodName) && + starts_with(convName, "Inverse of ") && + starts_with(methodName, "Inverse of ")) { + + auto invConvProps = buildProperties(j, true); + auto invMethodProps = buildProperties(methodJ, true); + return NN_NO_CHECK(util::nn_dynamic_pointer_cast<Conversion>( + Conversion::create(invConvProps, invMethodProps, parameters, values) + ->inverse())); + } + return Conversion::create(convProps, methodProps, parameters, values); +} + +// --------------------------------------------------------------------------- + +BoundCRSNNPtr JSONParser::buildBoundCRS(const json &j) { + + auto sourceCRS = buildCRS(getObject(j, "source_crs")); + auto targetCRS = buildCRS(getObject(j, "target_crs")); + auto transformationJ = getObject(j, "transformation"); + auto methodJ = getObject(transformationJ, "method"); + auto parametersJ = getArray(transformationJ, "parameters"); + std::vector<OperationParameterNNPtr> parameters; + std::vector<ParameterValueNNPtr> values; + for (const auto ¶m : parametersJ) { + if (!param.is_object()) { + throw ParsingException( + "Unexpected type for a \"parameters\" child"); + } + parameters.emplace_back( + OperationParameter::create(buildProperties(param))); + if (param.contains("value")) { + auto v = param["value"]; + if (v.is_string()) { + values.emplace_back( + ParameterValue::createFilename(v.get<std::string>())); + continue; + } + } + values.emplace_back(ParameterValue::create(getMeasure(param))); + } + + CRSPtr sourceTransformationCRS; + if (dynamic_cast<GeographicCRS *>(targetCRS.get())) { + sourceTransformationCRS = sourceCRS->extractGeographicCRS(); + if (!sourceTransformationCRS) { + sourceTransformationCRS = + std::dynamic_pointer_cast<VerticalCRS>(sourceCRS.as_nullable()); + if (!sourceTransformationCRS) { + throw ParsingException( + "Cannot find GeographicCRS or VerticalCRS in sourceCRS"); + } + } + } else { + sourceTransformationCRS = sourceCRS; + } + + auto transformation = Transformation::create( + buildProperties(transformationJ), NN_NO_CHECK(sourceTransformationCRS), + targetCRS, nullptr, buildProperties(methodJ), parameters, values, + std::vector<PositionalAccuracyNNPtr>()); + + return BoundCRS::create(sourceCRS, targetCRS, transformation); +} + +// --------------------------------------------------------------------------- + +TransformationNNPtr JSONParser::buildTransformation(const json &j) { + + auto sourceCRS = buildCRS(getObject(j, "source_crs")); + auto targetCRS = buildCRS(getObject(j, "target_crs")); + auto methodJ = getObject(j, "method"); + auto parametersJ = getArray(j, "parameters"); + std::vector<OperationParameterNNPtr> parameters; + std::vector<ParameterValueNNPtr> values; + for (const auto ¶m : parametersJ) { + if (!param.is_object()) { + throw ParsingException( + "Unexpected type for a \"parameters\" child"); + } + parameters.emplace_back( + OperationParameter::create(buildProperties(param))); + if (param.contains("value")) { + auto v = param["value"]; + if (v.is_string()) { + values.emplace_back( + ParameterValue::createFilename(v.get<std::string>())); + continue; + } + } + values.emplace_back(ParameterValue::create(getMeasure(param))); + } + CRSPtr interpolationCRS; + if (j.contains("interpolation_crs")) { + interpolationCRS = + buildCRS(getObject(j, "interpolation_crs")).as_nullable(); + } + std::vector<PositionalAccuracyNNPtr> accuracies; + if (j.contains("accuracy")) { + accuracies.push_back( + PositionalAccuracy::create(getString(j, "accuracy"))); + } + + return Transformation::create(buildProperties(j), sourceCRS, targetCRS, + interpolationCRS, buildProperties(methodJ), + parameters, values, accuracies); +} + +// --------------------------------------------------------------------------- + +ConcatenatedOperationNNPtr +JSONParser::buildConcatenatedOperation(const json &j) { + + auto sourceCRS = buildCRS(getObject(j, "source_crs")); + auto targetCRS = buildCRS(getObject(j, "target_crs")); + auto stepsJ = getArray(j, "steps"); + std::vector<CoordinateOperationNNPtr> operations; + for (const auto &stepJ : stepsJ) { + if (!stepJ.is_object()) { + throw ParsingException("Unexpected type for a \"steps\" child"); + } + auto op = nn_dynamic_pointer_cast<CoordinateOperation>(create(stepJ)); + if (!op) { + throw ParsingException("Invalid content in a \"steps\" child"); + } + operations.emplace_back(NN_NO_CHECK(op)); + } + + ConcatenatedOperation::fixStepsDirection(sourceCRS, targetCRS, operations); + + try { + return ConcatenatedOperation::create( + buildProperties(j), operations, + std::vector<PositionalAccuracyNNPtr>()); + } catch (const InvalidOperation &e) { + throw ParsingException( + std::string("Cannot build concatenated operation: ") + e.what()); + } +} + +// --------------------------------------------------------------------------- + +CoordinateSystemAxisNNPtr JSONParser::buildAxis(const json &j) { + auto dirString = getString(j, "direction"); + auto abbreviation = getString(j, "abbreviation"); + auto unit = j.contains("unit") ? getUnit(j, "unit") + : UnitOfMeasure(std::string(), 1.0, + UnitOfMeasure::Type::NONE); + auto direction = AxisDirection::valueOf(dirString); + if (!direction) { + throw ParsingException(concat("unhandled axis direction: ", dirString)); + } + return CoordinateSystemAxis::create(buildProperties(j), abbreviation, + *direction, unit, + nullptr /* meridian */); +} + +// --------------------------------------------------------------------------- + +CoordinateSystemNNPtr JSONParser::buildCS(const json &j) { + auto subtype = getString(j, "subtype"); + if (!j.contains("axis")) { + throw ParsingException("Missing \"axis\" key"); + } + auto jAxisList = j["axis"]; + if (!jAxisList.is_array()) { + throw ParsingException("Unexpected type for value of \"axis\""); + } + std::vector<CoordinateSystemAxisNNPtr> axisList; + for (const auto &axis : jAxisList) { + if (!axis.is_object()) { + throw ParsingException( + "Unexpected type for value of a \"axis\" member"); + } + axisList.emplace_back(buildAxis(axis)); + } + const PropertyMap &csMap = emptyPropertyMap; + if (subtype == "ellipsoidal") { + if (axisList.size() == 2) { + return EllipsoidalCS::create(csMap, axisList[0], axisList[1]); + } + if (axisList.size() == 3) { + return EllipsoidalCS::create(csMap, axisList[0], axisList[1], + axisList[2]); + } + throw ParsingException("Expected 2 or 3 axis"); + } + if (subtype == "Cartesian") { + if (axisList.size() == 2) { + return CartesianCS::create(csMap, axisList[0], axisList[1]); + } + if (axisList.size() == 3) { + return CartesianCS::create(csMap, axisList[0], axisList[1], + axisList[2]); + } + throw ParsingException("Expected 2 or 3 axis"); + } + if (subtype == "vertical") { + if (axisList.size() == 1) { + return VerticalCS::create(csMap, axisList[0]); + } + throw ParsingException("Expected 1 axis"); + } + if (subtype == "spherical") { + if (axisList.size() == 3) { + return SphericalCS::create(csMap, axisList[0], axisList[1], + axisList[2]); + } + throw ParsingException("Expected 3 axis"); + } + if (subtype == "ordinal") { + return OrdinalCS::create(csMap, axisList); + } + if (subtype == "parametric") { + if (axisList.size() == 1) { + return ParametricCS::create(csMap, axisList[0]); + } + throw ParsingException("Expected 1 axis"); + } + if (subtype == "TemporalDateTime") { + if (axisList.size() == 1) { + return DateTimeTemporalCS::create(csMap, axisList[0]); + } + throw ParsingException("Expected 1 axis"); + } + if (subtype == "TemporalCount") { + if (axisList.size() == 1) { + return TemporalCountCS::create(csMap, axisList[0]); + } + throw ParsingException("Expected 1 axis"); + } + if (subtype == "TemporalMeasure") { + if (axisList.size() == 1) { + return TemporalMeasureCS::create(csMap, axisList[0]); + } + throw ParsingException("Expected 1 axis"); + } + throw ParsingException("Unhandled value for subtype"); +} + +// --------------------------------------------------------------------------- + +DatumEnsembleNNPtr JSONParser::buildDatumEnsemble(const json &j) { + auto membersJ = getArray(j, "members"); + std::vector<DatumNNPtr> datums; + const bool hasEllipsoid(j.contains("ellipsoid")); + for (const auto &memberJ : membersJ) { + if (!memberJ.is_object()) { + throw ParsingException( + "Unexpected type for value of a \"members\" member"); + } + auto datumName(getName(memberJ)); + if (dbContext_ && memberJ.contains("id")) { + auto id = getObject(memberJ, "id"); + auto authority = getString(id, "authority"); + auto authFactory = + AuthorityFactory::create(NN_NO_CHECK(dbContext_), authority); + auto code = id["code"]; + std::string codeStr; + if (code.is_string()) { + codeStr = code.get<std::string>(); + } else if (code.is_number_integer()) { + codeStr = internal::toString(code.get<int>()); + } else { + throw ParsingException("Unexpected type for value of \"code\""); + } + try { + datums.push_back(authFactory->createDatum(codeStr)); + } catch (const std::exception &) { + throw ParsingException("No Datum of code " + codeStr); + } + continue; + } else if (dbContext_) { + auto authFactory = AuthorityFactory::create(NN_NO_CHECK(dbContext_), + std::string()); + auto list = authFactory->createObjectsFromName( + datumName, {AuthorityFactory::ObjectType::DATUM}, + false /* approximate=false*/); + if (!list.empty()) { + auto datum = util::nn_dynamic_pointer_cast<Datum>(list.front()); + if (!datum) + throw ParsingException( + "DatumEnsemble member is not a datum"); + datums.push_back(NN_NO_CHECK(datum)); + continue; + } + } + + // Fallback if no db match + if (hasEllipsoid) { + datums.emplace_back(GeodeticReferenceFrame::create( + buildProperties(memberJ), + buildEllipsoid(getObject(j, "ellipsoid")), + optional<std::string>(), PrimeMeridian::GREENWICH)); + } else { + datums.emplace_back( + VerticalReferenceFrame::create(buildProperties(memberJ))); + } + } + return DatumEnsemble::create( + buildProperties(j), datums, + PositionalAccuracy::create(getString(j, "accuracy"))); +} + +// --------------------------------------------------------------------------- + +GeodeticReferenceFrameNNPtr +JSONParser::buildGeodeticReferenceFrame(const json &j) { + auto ellipsoidJ = getObject(j, "ellipsoid"); + auto pm = j.contains("prime_meridian") + ? buildPrimeMeridian(getObject(j, "prime_meridian")) + : PrimeMeridian::GREENWICH; + return GeodeticReferenceFrame::create( + buildProperties(j), buildEllipsoid(ellipsoidJ), getAnchor(j), pm); +} + +// --------------------------------------------------------------------------- + +DynamicGeodeticReferenceFrameNNPtr +JSONParser::buildDynamicGeodeticReferenceFrame(const json &j) { + auto ellipsoidJ = getObject(j, "ellipsoid"); + auto pm = j.contains("prime_meridian") + ? buildPrimeMeridian(getObject(j, "prime_meridian")) + : PrimeMeridian::GREENWICH; + Measure frameReferenceEpoch(getNumber(j, "frame_reference_epoch"), + UnitOfMeasure::YEAR); + optional<std::string> deformationModel; + if (j.contains("deformation_model")) { + deformationModel = getString(j, "deformation_model"); + } + return DynamicGeodeticReferenceFrame::create( + buildProperties(j), buildEllipsoid(ellipsoidJ), getAnchor(j), pm, + frameReferenceEpoch, deformationModel); +} + +// --------------------------------------------------------------------------- + +VerticalReferenceFrameNNPtr +JSONParser::buildVerticalReferenceFrame(const json &j) { + return VerticalReferenceFrame::create(buildProperties(j), getAnchor(j)); +} + +// --------------------------------------------------------------------------- + +DynamicVerticalReferenceFrameNNPtr +JSONParser::buildDynamicVerticalReferenceFrame(const json &j) { + Measure frameReferenceEpoch(getNumber(j, "frame_reference_epoch"), + UnitOfMeasure::YEAR); + optional<std::string> deformationModel; + if (j.contains("deformation_model")) { + deformationModel = getString(j, "deformation_model"); + } + return DynamicVerticalReferenceFrame::create( + buildProperties(j), getAnchor(j), util::optional<RealizationMethod>(), + frameReferenceEpoch, deformationModel); +} + +// --------------------------------------------------------------------------- + +PrimeMeridianNNPtr JSONParser::buildPrimeMeridian(const json &j) { + if (!j.contains("longitude")) { + throw ParsingException("Missing \"longitude\" key"); + } + auto longitude = j["longitude"]; + if (longitude.is_number()) { + return PrimeMeridian::create( + buildProperties(j), + Angle(longitude.get<double>(), UnitOfMeasure::DEGREE)); + } else if (longitude.is_object()) { + return PrimeMeridian::create(buildProperties(j), + Angle(getMeasure(longitude))); + } + throw ParsingException("Unexpected type for value of \"longitude\""); +} + +// --------------------------------------------------------------------------- + +EllipsoidNNPtr JSONParser::buildEllipsoid(const json &j) { + if (j.contains("semi_major_axis")) { + auto semiMajorAxis = getLength(j, "semi_major_axis"); + const auto celestialBody( + Ellipsoid::guessBodyName(dbContext_, semiMajorAxis.getSIValue())); + if (j.contains("semi_minor_axis")) { + return Ellipsoid::createTwoAxis(buildProperties(j), semiMajorAxis, + getLength(j, "semi_minor_axis"), + celestialBody); + } else if (j.contains("inverse_flattening")) { + return Ellipsoid::createFlattenedSphere( + buildProperties(j), semiMajorAxis, + Scale(getNumber(j, "inverse_flattening")), celestialBody); + } else { + throw ParsingException( + "Missing semi_minor_axis or inverse_flattening"); + } + } else if (j.contains("radius")) { + auto radius = getLength(j, "radius"); + const auto celestialBody( + Ellipsoid::guessBodyName(dbContext_, radius.getSIValue())); + return Ellipsoid::createSphere(buildProperties(j), radius, + celestialBody); + } + throw ParsingException("Missing semi_major_axis or radius"); +} + +// --------------------------------------------------------------------------- + static BaseObjectNNPtr createFromUserInput(const std::string &text, const DatabaseContextPtr &dbContext, bool usePROJ4InitRules, PJ_CONTEXT *ctx) { + if (!text.empty() && text[0] == '{') { + json j; + try { + j = json::parse(text); + } catch (const std::exception &e) { + throw ParsingException(e.what()); + } + return JSONParser().attachDatabaseContext(dbContext).create(j); + } if (!ci_starts_with(text, "step proj=") && !ci_starts_with(text, "step +proj=")) { @@ -4747,6 +5824,7 @@ static BaseObjectNNPtr createFromUserInput(const std::string &text, * <li>an Object name. e.g "WGS 84", "WGS 84 / UTM zone 31N". In that case as * uniqueness is not guaranteed, the function may apply heuristics to * determine the appropriate best match.</li> + * <li>PROJJSON string</li> * </ul> * * @param text One of the above mentioned text format @@ -4795,6 +5873,7 @@ BaseObjectNNPtr createFromUserInput(const std::string &text, * <li>an Object name. e.g "WGS 84", "WGS 84 / UTM zone 31N". In that case as * uniqueness is not guaranteed, the function may apply heuristics to * determine the appropriate best match.</li> + * <li>PROJJSON string</li> * </ul> * * @param text One of the above mentioned text format @@ -8016,5 +9095,182 @@ PROJStringParser::createFromPROJString(const std::string &projString) { nullptr, nullptr, {}); } +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +struct JSONFormatter::Private { + CPLJSonStreamingWriter writer_{nullptr, nullptr}; + DatabaseContextPtr dbContext_{}; + + std::vector<bool> stackHasId_{false}; + std::vector<bool> outputIdStack_{true}; + bool allowIDInImmediateChild_ = false; + bool omitTypeInImmediateChild_ = false; + bool abridgedTransformation_ = false; + std::string schema_ = PROJJSON_CURRENT_VERSION; + + std::string result_{}; + + // cppcheck-suppress functionStatic + void pushOutputId(bool outputIdIn) { outputIdStack_.push_back(outputIdIn); } + + // cppcheck-suppress functionStatic + void popOutputId() { outputIdStack_.pop_back(); } +}; +//! @endcond + +// --------------------------------------------------------------------------- + +/** \brief Constructs a new formatter. + * + * A formatter can be used only once (its internal state is mutated) + * + * @return new formatter. + */ +JSONFormatterNNPtr JSONFormatter::create( // cppcheck-suppress passedByValue + DatabaseContextPtr dbContext) { + auto ret = NN_NO_CHECK(JSONFormatter::make_unique<JSONFormatter>()); + ret->d->dbContext_ = dbContext; + return ret; +} + +// --------------------------------------------------------------------------- + +/** \brief Whether to use multi line output or not. */ +JSONFormatter &JSONFormatter::setMultiLine(bool multiLine) noexcept { + d->writer_.SetPrettyFormatting(multiLine); + return *this; +} + +// --------------------------------------------------------------------------- + +/** \brief Set number of spaces for each indentation level (defaults to 4). + */ +JSONFormatter &JSONFormatter::setIndentationWidth(int width) noexcept { + d->writer_.SetIndentationSize(width); + return *this; +} + +// --------------------------------------------------------------------------- + +/** \brief Set the value of the "$schema" key in the top level object. + * + * If set to empty string, it will not be written. + */ +JSONFormatter &JSONFormatter::setSchema(const std::string &schema) noexcept { + d->schema_ = schema; + return *this; +} + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress + +JSONFormatter::JSONFormatter() : d(internal::make_unique<Private>()) {} + +// --------------------------------------------------------------------------- + +JSONFormatter::~JSONFormatter() = default; + +// --------------------------------------------------------------------------- + +CPLJSonStreamingWriter &JSONFormatter::writer() const { return d->writer_; } + +// --------------------------------------------------------------------------- + +bool JSONFormatter::outputId() const { return d->outputIdStack_.back(); } + +// --------------------------------------------------------------------------- + +bool JSONFormatter::outputUsage() const { + return outputId() && d->outputIdStack_.size() == 2; +} + +// --------------------------------------------------------------------------- + +void JSONFormatter::setAllowIDInImmediateChild() { + d->allowIDInImmediateChild_ = true; +} + +// --------------------------------------------------------------------------- + +void JSONFormatter::setOmitTypeInImmediateChild() { + d->omitTypeInImmediateChild_ = true; +} + +// --------------------------------------------------------------------------- + +JSONFormatter::ObjectContext::ObjectContext(JSONFormatter &formatter, + const char *objectType, bool hasId) + : m_formatter(formatter) { + m_formatter.d->writer_.StartObj(); + if (m_formatter.d->outputIdStack_.size() == 1 && + !m_formatter.d->schema_.empty()) { + m_formatter.d->writer_.AddObjKey("$schema"); + m_formatter.d->writer_.Add(m_formatter.d->schema_); + } + if (objectType && !m_formatter.d->omitTypeInImmediateChild_) { + m_formatter.d->writer_.AddObjKey("type"); + m_formatter.d->writer_.Add(objectType); + } + m_formatter.d->omitTypeInImmediateChild_ = false; + // All intermediate nodes shouldn't have ID if a parent has an ID + // unless explicitly enabled. + if (m_formatter.d->allowIDInImmediateChild_) { + m_formatter.d->pushOutputId(m_formatter.d->outputIdStack_[0]); + m_formatter.d->allowIDInImmediateChild_ = false; + } else { + m_formatter.d->pushOutputId(m_formatter.d->outputIdStack_[0] && + !m_formatter.d->stackHasId_.back()); + } + + m_formatter.d->stackHasId_.push_back(hasId || + m_formatter.d->stackHasId_.back()); +} + +// --------------------------------------------------------------------------- + +JSONFormatter::ObjectContext::~ObjectContext() { + m_formatter.d->writer_.EndObj(); + m_formatter.d->stackHasId_.pop_back(); + m_formatter.d->popOutputId(); +} + +// --------------------------------------------------------------------------- + +void JSONFormatter::setAbridgedTransformation(bool outputIn) { + d->abridgedTransformation_ = outputIn; +} + +// --------------------------------------------------------------------------- + +bool JSONFormatter::abridgedTransformation() const { + return d->abridgedTransformation_; +} + +//! @endcond + +// --------------------------------------------------------------------------- + +/** \brief Return the serialized JSON. + */ +const std::string &JSONFormatter::toString() const { + return d->writer_.GetString(); +} + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +IJSONExportable::~IJSONExportable() = default; + +// --------------------------------------------------------------------------- + +std::string IJSONExportable::exportToJSON(JSONFormatter *formatter) const { + _exportToJSON(formatter); + return formatter->toString(); +} + +//! @endcond + } // namespace io NS_PROJ_END 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 ) @@ -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 |
