From 17f0b0b3bc65ffba39bf6f22a12b2cc7fcb9bafd Mon Sep 17 00:00:00 2001 From: Even Rouault Date: Sat, 6 Jul 2019 02:03:50 +0200 Subject: Proof-of-concept of JSON export limited to PrimeMeridian (refs #1545) --- src/Makefile.am | 6 +- src/iso19111/common.cpp | 65 ++++++++ src/iso19111/datum.cpp | 35 +++++ src/iso19111/io.cpp | 87 +++++++++++ src/iso19111/metadata.cpp | 20 +++ src/lib_proj.cmake | 2 + src/proj_json_streaming_writer.cpp | 297 +++++++++++++++++++++++++++++++++++++ src/proj_json_streaming_writer.hpp | 145 ++++++++++++++++++ 8 files changed, 655 insertions(+), 2 deletions(-) create mode 100644 src/proj_json_streaming_writer.cpp create mode 100644 src/proj_json_streaming_writer.hpp (limited to 'src') 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/iso19111/common.cpp b/src/iso19111/common.cpp index d46da0da..f2a51032 100644 --- a/src/iso19111/common.cpp +++ b/src/iso19111/common.cpp @@ -236,6 +236,54 @@ void UnitOfMeasure::_exportToWKT( } formatter->endNode(); } + +// --------------------------------------------------------------------------- + +void UnitOfMeasure::_exportToJSON( + JSONFormatter *formatter) const // throw(FormattingException) +{ + auto &writer = formatter->writer(); + PROJ::CPLJSonStreamingWriter::ObjectContext objContext(writer); + 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("ParametericUnit"); + } 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); + + const auto &l_codeSpace = codeSpace(); + if (!l_codeSpace.empty() && formatter->outputId()) { + writer.AddObjKey("id"); + PROJ::CPLJSonStreamingWriter::ObjectContext idContext(writer); + 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 +863,23 @@ 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"); + PROJ::CPLJSonStreamingWriter::ArrayContext arrayContext(writer); + for (const auto &id : ids) { + id->_exportToJSON(formatter); + } + } +} + +// --------------------------------------------------------------------------- + bool IdentifiedObject::_isEquivalentTo( const util::IComparable *other, util::IComparable::Criterion criterion) const { diff --git a/src/iso19111/datum.cpp b/src/iso19111/datum.cpp index bf3092c1..6ba219d4 100644 --- a/src/iso19111/datum.cpp +++ b/src/iso19111/datum.cpp @@ -347,6 +347,41 @@ void PrimeMeridian::_exportToWKT( // --------------------------------------------------------------------------- +//! @cond Doxygen_Suppress +void PrimeMeridian::_exportToJSON( + io::JSONFormatter *formatter) const // throw(FormattingException) +{ + auto &writer = formatter->writer(); + PROJ::CPLJSonStreamingWriter::ObjectContext objectContext(writer); + + writer.AddObjKey("type"); + writer.Add("PrimeMeridian"); + + 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"); + { + PROJ::CPLJSonStreamingWriter::ObjectContext longitudeContext(writer); + writer.AddObjKey("value"); + writer.Add(l_long.value(), 15); + + const auto &unit = l_long.unit(); + writer.AddObjKey("unit"); + unit._exportToJSON(formatter); + } + + if (formatter->outputId()) { + formatID(formatter); + } +} +//! @endcond + +// --------------------------------------------------------------------------- + //! @cond Doxygen_Suppress std::string PrimeMeridian::getPROJStringWellKnownName(const common::Angle &angle) { diff --git a/src/iso19111/io.cpp b/src/iso19111/io.cpp index 0db97d0b..0c9f8a0c 100644 --- a/src/iso19111/io.cpp +++ b/src/iso19111/io.cpp @@ -60,6 +60,7 @@ #include "proj_constants.h" +#include "proj_json_streaming_writer.hpp" #include "wkt1_parser.h" #include "wkt2_parser.h" @@ -8016,5 +8017,91 @@ PROJStringParser::createFromPROJString(const std::string &projString) { nullptr, nullptr, {}); } +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +struct JSONFormatter::Private { + PROJ::CPLJSonStreamingWriter writer_{nullptr, nullptr}; + DatabaseContextPtr dbContext_{}; + std::string result_{}; +}; +//! @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()); + 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; +} + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress + +JSONFormatter::JSONFormatter() : d(internal::make_unique()) {} + +// --------------------------------------------------------------------------- + +JSONFormatter::~JSONFormatter() = default; + +// --------------------------------------------------------------------------- + +PROJ::CPLJSonStreamingWriter &JSONFormatter::writer() const { + return d->writer_; +} + +// --------------------------------------------------------------------------- + +bool JSONFormatter::outputId() const { return true; } + +//! @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..761b909b 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(); + PROJ::CPLJSonStreamingWriter::ObjectContext objContext(writer); + writer.AddObjKey("authority"); + writer.Add(l_codeSpace); + writer.AddObjKey("code"); + try { + writer.Add(std::stoi(l_code)); + } catch (const std::exception &) { + writer.Add(l_code); + } + } +} + //! @endcond // --------------------------------------------------------------------------- diff --git a/src/lib_proj.cmake b/src/lib_proj.cmake index 5a0a8070..bad60324 100644 --- a/src/lib_proj.cmake +++ b/src/lib_proj.cmake @@ -285,6 +285,7 @@ set(SRC_LIBPROJ_CORE wkt_parser.cpp wkt_parser.hpp zpoly1.cpp + proj_json_streaming_writer.cpp ${CMAKE_CURRENT_BINARY_DIR}/proj_config.h ) @@ -293,6 +294,7 @@ set(HEADERS_LIBPROJ proj.h proj_experimental.h proj_constants.h + proj_json_streaming_writer.hpp geodesic.h ) diff --git a/src/proj_json_streaming_writer.cpp b/src/proj_json_streaming_writer.cpp new file mode 100644 index 00000000..3198308b --- /dev/null +++ b/src/proj_json_streaming_writer.cpp @@ -0,0 +1,297 @@ +/****************************************************************************** + * + * Project: CPL - Common Portability Library + * Purpose: JSon streaming writer + * Author: Even Rouault, even.rouault at spatialys.com + * + ****************************************************************************** + * Copyright (c) 2019, Even Rouault + * + * 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 +#include + +#include "proj_json_streaming_writer.hpp" + +#include +#include +#include +#include +#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; +} + +namespace PROJ +{ + +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(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(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"); +} + +} // namespace PROJ + +/*! @endcond */ diff --git a/src/proj_json_streaming_writer.hpp b/src/proj_json_streaming_writer.hpp new file mode 100644 index 00000000..d1510449 --- /dev/null +++ b/src/proj_json_streaming_writer.hpp @@ -0,0 +1,145 @@ +/****************************************************************************** + * + * Project: CPL - Common Portability Library + * Purpose: JSon streaming writer + * Author: Even Rouault, even.rouault at spatialys.com + * + ****************************************************************************** + * Copyright (c) 2019, Even Rouault + * + * 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 +#include + +#define CPL_DLL + +namespace PROJ +{ + +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 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(nVal)); } + void Add(unsigned int nVal) { Add(static_cast(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; + explicit ObjectContext(CPLJSonStreamingWriter& serializer): + m_serializer(serializer) { m_serializer.StartObj(); } + ~ObjectContext() { m_serializer.EndObj(); } + }; + + void StartArray(); + void EndArray(); + struct CPL_DLL ArrayContext + { + CPLJSonStreamingWriter& m_serializer; + bool m_bForceSingleLine; + bool m_bNewLineEnabledBackup; + + 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); + } + }; + + bool GetNewLine() const { return m_bNewLineEnabled; } + void SetNewline(bool bEnabled) { m_bNewLineEnabled = bEnabled; } +}; + +} // namespace PROJ + +/*! @endcond */ + +#endif // PROJ_JSON_STREAMING_WRITER_H -- cgit v1.2.3 From ece151aed1b4bf2634de5759f37fd7bc005e8313 Mon Sep 17 00:00:00 2001 From: Even Rouault Date: Sat, 6 Jul 2019 17:06:23 +0200 Subject: CRS JSON: export GeographicCRS and Projected CRS --- src/apps/projinfo.cpp | 33 +++++++- src/iso19111/common.cpp | 84 +++++++++++++++++- src/iso19111/coordinateoperation.cpp | 85 +++++++++++++++++++ src/iso19111/coordinatesystem.cpp | 59 ++++++++++++- src/iso19111/crs.cpp | 160 +++++++++++++++++++++++++++++++++++ src/iso19111/datum.cpp | 106 +++++++++++++++++++++-- src/iso19111/io.cpp | 61 ++++++++++++- src/iso19111/metadata.cpp | 2 +- src/proj_json_streaming_writer.cpp | 5 +- src/proj_json_streaming_writer.hpp | 20 +++-- 10 files changed, 589 insertions(+), 26 deletions(-) (limited to 'src') diff --git a/src/apps/projinfo.cpp b/src/apps/projinfo.cpp index 9d724522..6b1267e0 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 JSON = 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,JSON" << std::endl; std::cerr << " Except 'all' and 'default', other format can be preceded " "by '-' to disable them" @@ -467,6 +468,29 @@ static void outputObject( std::cerr << "Error when exporting to WKT1_ESRI: " << e.what() << std::endl; } + alreadyOutputed = true; + } + } + + auto JSONExportable = nn_dynamic_pointer_cast(obj); + if (JSONExportable) { + if (outputOpt.JSON) { + try { + if (alreadyOutputed) { + std::cout << std::endl; + } + if (!outputOpt.quiet) { + std::cout << "JSON:" << std::endl; + } + + std::cout << JSONExportable->exportToJSON( + JSONFormatter::create(dbContext).get()) + << std::endl; + } catch (const std::exception &e) { + std::cerr << "Error when exporting to JSON: " << e.what() + << std::endl; + } + // alreadyOutputed = true; } } @@ -720,6 +744,7 @@ int main(int argc, char **argv) { outputOpt.WKT2_2015 = true; outputOpt.WKT1_GDAL = true; outputOpt.WKT1_ESRI = true; + outputOpt.JSON = true; } else if (ci_equal(format, "default")) { outputOpt.PROJ5 = true; outputOpt.WKT2_2018 = true; @@ -779,6 +804,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, "JSON")) { + outputOpt.JSON = true; + } else if (ci_equal(format, "-JSON")) { + outputOpt.JSON = false; } else { std::cerr << "Unrecognized value for option -o: " << format << std::endl; @@ -998,7 +1027,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.JSON) != 1) { std::cerr << "-q can only be used with a single output format" << std::endl; usage(); diff --git a/src/iso19111/common.cpp b/src/iso19111/common.cpp index f2a51032..fb7a63c0 100644 --- a/src/iso19111/common.cpp +++ b/src/iso19111/common.cpp @@ -243,7 +243,9 @@ void UnitOfMeasure::_exportToJSON( JSONFormatter *formatter) const // throw(FormattingException) { auto &writer = formatter->writer(); - PROJ::CPLJSonStreamingWriter::ObjectContext objContext(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) { @@ -268,10 +270,9 @@ void UnitOfMeasure::_exportToJSON( writer.AddObjKey("conversion_factor"); writer.Add(factor, 15); - const auto &l_codeSpace = codeSpace(); if (!l_codeSpace.empty() && formatter->outputId()) { writer.AddObjKey("id"); - PROJ::CPLJSonStreamingWriter::ObjectContext idContext(writer); + auto idContext(formatter->MakeObjectContext(nullptr, false)); writer.AddObjKey("authority"); writer.Add(l_codeSpace); writer.AddObjKey("code"); @@ -871,7 +872,7 @@ void IdentifiedObject::formatID(JSONFormatter *formatter) const { ids.front()->_exportToJSON(formatter); } else if (!ids.empty()) { writer.AddObjKey("ids"); - PROJ::CPLJSonStreamingWriter::ArrayContext arrayContext(writer); + auto arrayContext(writer.MakeArrayContext()); for (const auto &id : ids) { id->_exportToJSON(formatter); } @@ -880,6 +881,16 @@ void IdentifiedObject::formatID(JSONFormatter *formatter) const { // --------------------------------------------------------------------------- +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 { @@ -1030,6 +1041,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( + 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, @@ -1162,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 7f3a2137..a2a31edd 100644 --- a/src/iso19111/coordinateoperation.cpp +++ b/src/iso19111/coordinateoperation.cpp @@ -990,6 +990,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, @@ -1167,6 +1185,35 @@ 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"); + l_value->value().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 @@ -5409,6 +5456,44 @@ 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->setAllowIDInImmediateChild(); + method()->_exportToJSON(formatter); + + writer.AddObjKey("parameters"); + { + auto parametersContext(writer.MakeArrayContext(false)); + for (const auto &genOpParamvalue : parameterValues()) { + formatter->setAllowIDInImmediateChild(); + genOpParamvalue->_exportToJSON(formatter); + } + } + + if (formatter->outputId()) { + formatID(formatter); + } +} + +//! @endcond + +// --------------------------------------------------------------------------- + //! @cond Doxygen_Suppress static bool createPROJ4WebMercator(const Conversion *conv, io::PROJStringFormatter *formatter) { diff --git a/src/iso19111/coordinatesystem.cpp b/src/iso19111/coordinatesystem.cpp index ef9cac93..f96a962f 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; @@ -392,6 +392,34 @@ 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()); + + writer.AddObjKey("unit"); + unit()._exportToJSON(formatter); + + if (formatter->outputId()) { + formatID(formatter); + } +} +//! @endcond + +// --------------------------------------------------------------------------- + //! @cond Doxygen_Suppress bool CoordinateSystemAxis::_isEquivalentTo( const util::IComparable *other, @@ -534,6 +562,33 @@ 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) { + axis->_exportToJSON(formatter); + } + + if (formatter->outputId()) { + formatID(formatter); + } +} + +//! @endcond + +// --------------------------------------------------------------------------- + //! @cond Doxygen_Suppress bool CoordinateSystem::_isEquivalentTo( const util::IComparable *other, diff --git a/src/iso19111/crs.cpp b/src/iso19111/crs.cpp index 476bc72b..2ca2786d 100644 --- a/src/iso19111/crs.cpp +++ b/src/iso19111/crs.cpp @@ -1341,6 +1341,39 @@ 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 { + // TODO DatumEnsemble + } + + writer.AddObjKey("coordinate_system"); + coordinateSystem()->_exportToJSON(formatter); + + ObjectUsage::baseExportToJSON(formatter); +} +//! @endcond + +// --------------------------------------------------------------------------- + //! @cond Doxygen_Suppress static util::IComparable::Criterion getStandardCriterion(util::IComparable::Criterion criterion) { @@ -2058,6 +2091,39 @@ 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 { + // TODO DatumEnsemble + } + + writer.AddObjKey("coordinate_system"); + coordinateSystem()->_exportToJSON(formatter); + + ObjectUsage::baseExportToJSON(formatter); +} +//! @endcond + +// --------------------------------------------------------------------------- + //! @cond Doxygen_Suppress struct VerticalCRS::Private { std::vector geoidModel{}; @@ -2856,6 +2922,38 @@ 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(); + baseCRS()->_exportToJSON(formatter); + + writer.AddObjKey("conversion"); + derivingConversionRef()->_exportToJSON(formatter); + + writer.AddObjKey("coordinate_system"); + coordinateSystem()->_exportToJSON(formatter); + + ObjectUsage::baseExportToJSON(formatter); +} +//! @endcond + +// --------------------------------------------------------------------------- + void ProjectedCRS::_exportToPROJString( io::PROJStringFormatter *formatter) const // throw(io::FormattingException) { @@ -4195,6 +4293,37 @@ void DerivedGeodeticCRS::_exportToWKT(io::WKTFormatter *formatter) const { // --------------------------------------------------------------------------- +//! @cond Doxygen_Suppress +void DerivedGeodeticCRS::_exportToJSON( + io::JSONFormatter *formatter) const // throw(io::FormattingException) +{ + auto &writer = formatter->writer(); + auto objectContext(formatter->MakeObjectContext("DerivedGeodeticCRS", + !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"); + derivingConversionRef()->_exportToJSON(formatter); + + writer.AddObjKey("coordinate_system"); + coordinateSystem()->_exportToJSON(formatter); + + ObjectUsage::baseExportToJSON(formatter); +} +//! @endcond + +// --------------------------------------------------------------------------- + void DerivedGeodeticCRS::_exportToPROJString( io::PROJStringFormatter *) const // throw(io::FormattingException) { @@ -4333,6 +4462,37 @@ void DerivedGeographicCRS::_exportToWKT(io::WKTFormatter *formatter) const { // --------------------------------------------------------------------------- +//! @cond Doxygen_Suppress +void DerivedGeographicCRS::_exportToJSON( + io::JSONFormatter *formatter) const // throw(io::FormattingException) +{ + auto &writer = formatter->writer(); + auto objectContext(formatter->MakeObjectContext("DerivedGeographicCRS", + !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"); + derivingConversionRef()->_exportToJSON(formatter); + + writer.AddObjKey("coordinate_system"); + coordinateSystem()->_exportToJSON(formatter); + + ObjectUsage::baseExportToJSON(formatter); +} +//! @endcond + +// --------------------------------------------------------------------------- + void DerivedGeographicCRS::_exportToPROJString( io::PROJStringFormatter *) const // throw(io::FormattingException) { diff --git a/src/iso19111/datum.cpp b/src/iso19111/datum.cpp index 6ba219d4..89ecc755 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 // --------------------------------------------------------------------------- @@ -352,10 +366,8 @@ void PrimeMeridian::_exportToJSON( io::JSONFormatter *formatter) const // throw(FormattingException) { auto &writer = formatter->writer(); - PROJ::CPLJSonStreamingWriter::ObjectContext objectContext(writer); - - writer.AddObjKey("type"); - writer.Add("PrimeMeridian"); + auto objectContext( + formatter->MakeObjectContext("PrimeMeridian", !identifiers().empty())); writer.AddObjKey("name"); std::string l_name = @@ -365,7 +377,7 @@ void PrimeMeridian::_exportToJSON( const auto &l_long = longitude(); writer.AddObjKey("longitude"); { - PROJ::CPLJSonStreamingWriter::ObjectContext longitudeContext(writer); + auto longitudeContext(formatter->MakeObjectContext(nullptr, false)); writer.AddObjKey("value"); writer.Add(l_long.value(), 15); @@ -803,6 +815,60 @@ 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"); + { + 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"); + { + auto objContext(formatter->MakeObjectContext(nullptr, false)); + writer.AddObjKey("value"); + writer.Add(semiMinorAxis()->value(), 15); + + writer.AddObjKey("unit"); + semiMinorAxis()->unit()._exportToJSON(formatter); + } + } + } + + if (formatter->outputId()) { + formatID(formatter); + } +} +//! @endcond + +// --------------------------------------------------------------------------- + bool Ellipsoid::lookForProjWellKnownEllps(std::string &projEllpsName, std::string &ellpsName) const { const double a = semiMajorAxis().getSIValue(); @@ -1187,6 +1253,36 @@ void GeodeticReferenceFrame::_exportToWKT( // --------------------------------------------------------------------------- +//! @cond Doxygen_Suppress +void GeodeticReferenceFrame::_exportToJSON( + io::JSONFormatter *formatter) const // throw(FormattingException) +{ + auto objectContext(formatter->MakeObjectContext("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); + + writer.AddObjKey("ellipsoid"); + ellipsoid()->_exportToJSON(formatter); + + writer.AddObjKey("prime_meridian"); + primeMeridian()->_exportToJSON(formatter); + + ObjectUsage::baseExportToJSON(formatter); +} +//! @endcond + +// --------------------------------------------------------------------------- + //! @cond Doxygen_Suppress bool GeodeticReferenceFrame::_isEquivalentTo( const util::IComparable *other, diff --git a/src/iso19111/io.cpp b/src/iso19111/io.cpp index 0c9f8a0c..35798d9d 100644 --- a/src/iso19111/io.cpp +++ b/src/iso19111/io.cpp @@ -8021,9 +8021,20 @@ PROJStringParser::createFromPROJString(const std::string &projString) { //! @cond Doxygen_Suppress struct JSONFormatter::Private { - PROJ::CPLJSonStreamingWriter writer_{nullptr, nullptr}; + CPLJSonStreamingWriter writer_{nullptr, nullptr}; DatabaseContextPtr dbContext_{}; + + std::vector stackHasId_{false}; + std::vector outputIdStack_{true}; + bool allowIDInImmediateChild_ = false; + std::string result_{}; + + // cppcheck-suppress functionStatic + void pushOutputId(bool outputIdIn) { outputIdStack_.push_back(outputIdIn); } + + // cppcheck-suppress functionStatic + void popOutputId() { outputIdStack_.pop_back(); } }; //! @endcond @@ -8071,13 +8082,55 @@ JSONFormatter::~JSONFormatter() = default; // --------------------------------------------------------------------------- -PROJ::CPLJSonStreamingWriter &JSONFormatter::writer() const { - return d->writer_; +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; } // --------------------------------------------------------------------------- -bool JSONFormatter::outputId() const { return true; } +void JSONFormatter::setAllowIDInImmediateChild() { + d->allowIDInImmediateChild_ = true; +} + +// --------------------------------------------------------------------------- + +JSONFormatter::ObjectContext::ObjectContext(JSONFormatter &formatter, + const char *objectType, bool hasId) + : m_formatter(formatter) { + m_formatter.d->writer_.StartObj(); + if (objectType) { + m_formatter.d->writer_.AddObjKey("type"); + m_formatter.d->writer_.Add(objectType); + } + // 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(); +} //! @endcond diff --git a/src/iso19111/metadata.cpp b/src/iso19111/metadata.cpp index 761b909b..41653b32 100644 --- a/src/iso19111/metadata.cpp +++ b/src/iso19111/metadata.cpp @@ -1096,7 +1096,7 @@ void Identifier::_exportToJSON(JSONFormatter *formatter) const { const std::string &l_codeSpace = *codeSpace(); if (!l_codeSpace.empty() && !l_code.empty()) { auto &writer = formatter->writer(); - PROJ::CPLJSonStreamingWriter::ObjectContext objContext(writer); + auto objContext(formatter->MakeObjectContext(nullptr, false)); writer.AddObjKey("authority"); writer.Add(l_codeSpace); writer.AddObjKey("code"); diff --git a/src/proj_json_streaming_writer.cpp b/src/proj_json_streaming_writer.cpp index 3198308b..4a9b9bd2 100644 --- a/src/proj_json_streaming_writer.cpp +++ b/src/proj_json_streaming_writer.cpp @@ -56,8 +56,7 @@ static std::string CPLSPrintf(const char* fmt, ...) return res; } -namespace PROJ -{ +NS_PROJ_START CPLJSonStreamingWriter::CPLJSonStreamingWriter( SerializationFuncType pfnSerializationFunc, @@ -292,6 +291,6 @@ void CPLJSonStreamingWriter::AddNull() Print("null"); } -} // namespace PROJ +NS_PROJ_END /*! @endcond */ diff --git a/src/proj_json_streaming_writer.hpp b/src/proj_json_streaming_writer.hpp index d1510449..62676842 100644 --- a/src/proj_json_streaming_writer.hpp +++ b/src/proj_json_streaming_writer.hpp @@ -36,8 +36,8 @@ #define CPL_DLL -namespace PROJ -{ +#include "proj/util.hpp" +NS_PROJ_START typedef std::int64_t GIntBig; typedef std::uint64_t GUInt64; @@ -102,10 +102,15 @@ public: struct CPL_DLL ObjectContext { CPLJSonStreamingWriter& m_serializer; - explicit ObjectContext(CPLJSonStreamingWriter& 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(); @@ -115,7 +120,10 @@ public: bool m_bForceSingleLine; bool m_bNewLineEnabledBackup; - ArrayContext(CPLJSonStreamingWriter& serializer, + ArrayContext(const ArrayContext &) = delete; + ArrayContext(ArrayContext&&) = default; + + inline explicit ArrayContext(CPLJSonStreamingWriter& serializer, bool bForceSingleLine = false): m_serializer(serializer), m_bForceSingleLine(bForceSingleLine), @@ -133,12 +141,14 @@ public: 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; } }; -} // namespace PROJ +NS_PROJ_END /*! @endcond */ -- cgit v1.2.3 From ec49c9cd0aa9de24623920b8de226daf05a0e90f Mon Sep 17 00:00:00 2001 From: Even Rouault Date: Thu, 8 Aug 2019 19:18:26 +0200 Subject: projinfo: rename JSON to PROJJSON --- src/apps/projinfo.cpp | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) (limited to 'src') diff --git a/src/apps/projinfo.cpp b/src/apps/projinfo.cpp index 6b1267e0..6cacdd66 100644 --- a/src/apps/projinfo.cpp +++ b/src/apps/projinfo.cpp @@ -67,7 +67,7 @@ struct OutputOptions { bool WKT2_2015_SIMPLIFIED = false; bool WKT1_GDAL = false; bool WKT1_ESRI = false; - bool JSON = false; + bool PROJJSON = false; bool c_ify = false; bool singleLine = false; bool strict = true; @@ -102,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,JSON" + "WKT1_ESRI,PROJJSON" << std::endl; std::cerr << " Except 'all' and 'default', other format can be preceded " "by '-' to disable them" @@ -474,20 +474,20 @@ static void outputObject( auto JSONExportable = nn_dynamic_pointer_cast(obj); if (JSONExportable) { - if (outputOpt.JSON) { + if (outputOpt.PROJJSON) { try { if (alreadyOutputed) { std::cout << std::endl; } if (!outputOpt.quiet) { - std::cout << "JSON:" << std::endl; + std::cout << "PROJJSON:" << std::endl; } std::cout << JSONExportable->exportToJSON( JSONFormatter::create(dbContext).get()) << std::endl; } catch (const std::exception &e) { - std::cerr << "Error when exporting to JSON: " << e.what() + std::cerr << "Error when exporting to PROJJSON: " << e.what() << std::endl; } // alreadyOutputed = true; @@ -744,7 +744,7 @@ int main(int argc, char **argv) { outputOpt.WKT2_2015 = true; outputOpt.WKT1_GDAL = true; outputOpt.WKT1_ESRI = true; - outputOpt.JSON = true; + outputOpt.PROJJSON = true; } else if (ci_equal(format, "default")) { outputOpt.PROJ5 = true; outputOpt.WKT2_2018 = true; @@ -804,10 +804,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, "JSON")) { - outputOpt.JSON = true; - } else if (ci_equal(format, "-JSON")) { - outputOpt.JSON = 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; @@ -1027,7 +1027,7 @@ int main(int argc, char **argv) { if (outputOpt.quiet && (outputOpt.PROJ5 + outputOpt.WKT2_2018 + outputOpt.WKT2_2015 + - outputOpt.WKT1_GDAL + outputOpt.JSON) != 1) { + outputOpt.WKT1_GDAL + outputOpt.PROJJSON) != 1) { std::cerr << "-q can only be used with a single output format" << std::endl; usage(); -- cgit v1.2.3 From 89f2cc7ec4178a369e73e9cd115a2552a55d870a Mon Sep 17 00:00:00 2001 From: Even Rouault Date: Thu, 8 Aug 2019 20:04:20 +0200 Subject: PROJJSON export: use more compact form --- src/iso19111/coordinateoperation.cpp | 11 ++++++++++- src/iso19111/coordinatesystem.cpp | 9 ++++++++- src/iso19111/crs.cpp | 7 +++++++ src/iso19111/datum.cpp | 30 +++++++++++++++++++++--------- src/iso19111/io.cpp | 10 +++++++++- 5 files changed, 55 insertions(+), 12 deletions(-) (limited to 'src') diff --git a/src/iso19111/coordinateoperation.cpp b/src/iso19111/coordinateoperation.cpp index a2a31edd..f4e90fe8 100644 --- a/src/iso19111/coordinateoperation.cpp +++ b/src/iso19111/coordinateoperation.cpp @@ -1200,7 +1200,14 @@ void OperationParameterValue::_exportToJSON( writer.AddObjKey("value"); writer.Add(l_value->value().value(), 15); writer.AddObjKey("unit"); - l_value->value().unit()._exportToJSON(formatter); + 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()); @@ -5473,6 +5480,7 @@ void Conversion::_exportToJSON( } writer.AddObjKey("method"); + formatter->setOmitTypeInImmediateChild(); formatter->setAllowIDInImmediateChild(); method()->_exportToJSON(formatter); @@ -5481,6 +5489,7 @@ void Conversion::_exportToJSON( auto parametersContext(writer.MakeArrayContext(false)); for (const auto &genOpParamvalue : parameterValues()) { formatter->setAllowIDInImmediateChild(); + formatter->setOmitTypeInImmediateChild(); genOpParamvalue->_exportToJSON(formatter); } } diff --git a/src/iso19111/coordinatesystem.cpp b/src/iso19111/coordinatesystem.cpp index f96a962f..ef53dd57 100644 --- a/src/iso19111/coordinatesystem.cpp +++ b/src/iso19111/coordinatesystem.cpp @@ -410,7 +410,13 @@ void CoordinateSystemAxis::_exportToJSON( writer.Add(direction().toString()); writer.AddObjKey("unit"); - unit()._exportToJSON(formatter); + const auto &l_unit(unit()); + if (l_unit == common::UnitOfMeasure::METRE || + l_unit == common::UnitOfMeasure::DEGREE) { + writer.Add(l_unit.name()); + } else { + l_unit._exportToJSON(formatter); + } if (formatter->outputId()) { formatID(formatter); @@ -577,6 +583,7 @@ void CoordinateSystem::_exportToJSON( auto axisContext(writer.MakeArrayContext(false)); const auto &l_axisList = axisList(); for (auto &axis : l_axisList) { + formatter->setOmitTypeInImmediateChild(); axis->_exportToJSON(formatter); } diff --git a/src/iso19111/crs.cpp b/src/iso19111/crs.cpp index 2ca2786d..670d0c1a 100644 --- a/src/iso19111/crs.cpp +++ b/src/iso19111/crs.cpp @@ -1366,6 +1366,7 @@ void GeodeticCRS::_exportToJSON( } writer.AddObjKey("coordinate_system"); + formatter->setOmitTypeInImmediateChild(); coordinateSystem()->_exportToJSON(formatter); ObjectUsage::baseExportToJSON(formatter); @@ -2116,6 +2117,7 @@ void GeographicCRS::_exportToJSON( } writer.AddObjKey("coordinate_system"); + formatter->setOmitTypeInImmediateChild(); coordinateSystem()->_exportToJSON(formatter); ObjectUsage::baseExportToJSON(formatter); @@ -2940,12 +2942,15 @@ void ProjectedCRS::_exportToJSON( 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); @@ -4316,6 +4321,7 @@ void DerivedGeodeticCRS::_exportToJSON( derivingConversionRef()->_exportToJSON(formatter); writer.AddObjKey("coordinate_system"); + formatter->setOmitTypeInImmediateChild(); coordinateSystem()->_exportToJSON(formatter); ObjectUsage::baseExportToJSON(formatter); @@ -4485,6 +4491,7 @@ void DerivedGeographicCRS::_exportToJSON( derivingConversionRef()->_exportToJSON(formatter); writer.AddObjKey("coordinate_system"); + formatter->setOmitTypeInImmediateChild(); coordinateSystem()->_exportToJSON(formatter); ObjectUsage::baseExportToJSON(formatter); diff --git a/src/iso19111/datum.cpp b/src/iso19111/datum.cpp index 89ecc755..2944da1e 100644 --- a/src/iso19111/datum.cpp +++ b/src/iso19111/datum.cpp @@ -376,12 +376,13 @@ void PrimeMeridian::_exportToJSON( 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); - - const auto &unit = l_long.unit(); writer.AddObjKey("unit"); unit._exportToJSON(formatter); } @@ -834,7 +835,9 @@ void Ellipsoid::_exportToJSON( 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); @@ -850,13 +853,17 @@ void Ellipsoid::_exportToJSON( 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(semiMinorAxis()->value(), 15); + writer.Add(l_semiMinorAxis->value(), 15); writer.AddObjKey("unit"); - semiMinorAxis()->unit()._exportToJSON(formatter); + semiMinorAxisUnit._exportToJSON(formatter); } } } @@ -1272,10 +1279,15 @@ void GeodeticReferenceFrame::_exportToJSON( Datum::getPrivate()->exportAnchorDefinition(formatter); writer.AddObjKey("ellipsoid"); + formatter->setOmitTypeInImmediateChild(); ellipsoid()->_exportToJSON(formatter); - writer.AddObjKey("prime_meridian"); - primeMeridian()->_exportToJSON(formatter); + const auto &l_primeMeridian(primeMeridian()); + if (l_primeMeridian->nameStr() != "Greenwich") { + writer.AddObjKey("prime_meridian"); + formatter->setOmitTypeInImmediateChild(); + primeMeridian()->_exportToJSON(formatter); + } ObjectUsage::baseExportToJSON(formatter); } diff --git a/src/iso19111/io.cpp b/src/iso19111/io.cpp index 35798d9d..bb15c80a 100644 --- a/src/iso19111/io.cpp +++ b/src/iso19111/io.cpp @@ -8027,6 +8027,7 @@ struct JSONFormatter::Private { std::vector stackHasId_{false}; std::vector outputIdStack_{true}; bool allowIDInImmediateChild_ = false; + bool omitTypeInImmediateChild_ = false; std::string result_{}; @@ -8102,14 +8103,21 @@ void JSONFormatter::setAllowIDInImmediateChild() { // --------------------------------------------------------------------------- +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 (objectType) { + 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_) { -- cgit v1.2.3 From 81bd57dfd8cfae609288643d1b09a4805f1bcded Mon Sep 17 00:00:00 2001 From: Even Rouault Date: Fri, 9 Aug 2019 12:59:49 +0200 Subject: createFromUserInput(): add capability to import PROJJSON --- src/apps/projinfo.cpp | 10 +- src/iso19111/io.cpp | 577 ++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 583 insertions(+), 4 deletions(-) (limited to 'src') diff --git a/src/apps/projinfo.cpp b/src/apps/projinfo.cpp index 6cacdd66..8f3f09b8 100644 --- a/src/apps/projinfo.cpp +++ b/src/apps/projinfo.cpp @@ -482,10 +482,12 @@ static void outputObject( if (!outputOpt.quiet) { std::cout << "PROJJSON:" << std::endl; } - - std::cout << JSONExportable->exportToJSON( - JSONFormatter::create(dbContext).get()) - << std::endl; + auto jsonString(JSONExportable->exportToJSON( + JSONFormatter::create(dbContext).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; diff --git a/src/iso19111/io.cpp b/src/iso19111/io.cpp index bb15c80a..bcd14429 100644 --- a/src/iso19111/io.cpp +++ b/src/iso19111/io.cpp @@ -58,6 +58,8 @@ #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" @@ -80,6 +82,8 @@ 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{}; //! @endcond @@ -4372,10 +4376,581 @@ 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); + + ObjectDomainPtr buildObjectDomain(const json &j); + PropertyMap buildProperties(const json &j); + + GeographicCRSNNPtr buildGeographicCRS(const json &j); + GeodeticCRSNNPtr buildGeodeticCRS(const json &j); + ProjectedCRSNNPtr buildProjectedCRS(const json &j); + ConversionNNPtr buildConversion(const json &j); + GeodeticReferenceFrameNNPtr buildGeodeticReferenceFrame(const json &j); + EllipsoidNNPtr buildEllipsoid(const json &j); + PrimeMeridianNNPtr buildPrimeMeridian(const json &j); + CoordinateSystemNNPtr buildCS(const json &j); + CoordinateSystemAxisNNPtr buildAxis(const json &j); + + 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(); +} + +// --------------------------------------------------------------------------- + +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 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(); +} + +// --------------------------------------------------------------------------- + +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(); +} + +// --------------------------------------------------------------------------- + +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(); + 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 == "ParametericUnit") { + 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(); + } else if (code.is_number_integer()) { + codeStr = internal::toString(code.get()); + } 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(), 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 scope; + if (j.contains("scope")) { + scope = getString(j, "scope"); + } + std::string area; + if (j.contains("area")) { + area = getString(j, "area"); + } + std::vector 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 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; +} + +// --------------------------------------------------------------------------- + +PropertyMap JSONParser::buildProperties(const json &j) { + PropertyMap map; + map.set(IdentifiedObject::NAME_KEY, getName(j)); + + if (j.contains("id")) { + auto id = getObject(j, "id"); + map.set(metadata::Identifier::CODESPACE_KEY, + getString(id, "authority")); + if (!id.contains("code")) { + throw ParsingException("Missing \"code\" key"); + } + auto code = id["code"]; + if (code.is_string()) { + map.set(metadata::Identifier::CODE_KEY, code.get()); + } else if (code.is_number_integer()) { + map.set(metadata::Identifier::CODE_KEY, code.get()); + } else { + throw ParsingException("Unexpected type for value of \"code\""); + } + } + + 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.type() != json::value_t::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 == "GeodeticReferenceFrame") { + return buildGeodeticReferenceFrame(j); + } + if (type == "Ellipsoid") { + return buildEllipsoid(j); + } + if (type == "PrimeMeridian") { + return buildPrimeMeridian(j); + } + if (type == "CoordinateSystem") { + return buildCS(j); + } + throw ParsingException("Unsupported value of \"type\""); +} + +// --------------------------------------------------------------------------- + +GeographicCRSNNPtr JSONParser::buildGeographicCRS(const json &j) { + auto datumJ = getObject(j, "datum"); + if (getType(datumJ) != "GeodeticReferenceFrame") { + throw ParsingException("Unsupported type for datum."); + } + auto datum = buildGeodeticReferenceFrame(datumJ); + DatumEnsemblePtr datumEnsemble; + auto csJ = getObject(j, "coordinate_system"); + auto ellipsoidalCS = + util::nn_dynamic_pointer_cast(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(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(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(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)); +} + +// --------------------------------------------------------------------------- + +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 parameters; + std::vector 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))); + } + return Conversion::create(convProps, methodProps, parameters, values); +} + +// --------------------------------------------------------------------------- + +CoordinateSystemAxisNNPtr JSONParser::buildAxis(const json &j) { + auto dirString = getString(j, "direction"); + auto abbreviation = getString(j, "abbreviation"); + auto unit = getUnit(j, "unit"); + 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 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"); +} + +// --------------------------------------------------------------------------- + +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), + optional() /* anchor */, pm); +} + +// --------------------------------------------------------------------------- + +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(), 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=")) { @@ -4748,6 +5323,7 @@ static BaseObjectNNPtr createFromUserInput(const std::string &text, *
  • 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.
  • + *
  • PROJJSON string
  • * * * @param text One of the above mentioned text format @@ -4796,6 +5372,7 @@ BaseObjectNNPtr createFromUserInput(const std::string &text, *
  • 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.
  • + *
  • PROJJSON string
  • * * * @param text One of the above mentioned text format -- cgit v1.2.3 From f0d766e45640800412c08a7cb9ab4f8823d88d05 Mon Sep 17 00:00:00 2001 From: Even Rouault Date: Fri, 9 Aug 2019 17:10:20 +0200 Subject: PROJSJON: add import/export of VerticalCRS, CompoundCRS, BoundCRS, Transformation and ConcatenatedOperation --- src/iso19111/coordinateoperation.cpp | 174 +++++++++++++++++++++- src/iso19111/crs.cpp | 275 +++++++++++++++++++++++++++-------- src/iso19111/datum.cpp | 88 +++++++++++ src/iso19111/io.cpp | 263 ++++++++++++++++++++++++++++++++- 4 files changed, 721 insertions(+), 79 deletions(-) (limited to 'src') diff --git a/src/iso19111/coordinateoperation.cpp b/src/iso19111/coordinateoperation.cpp index f4e90fe8..27a22b51 100644 --- a/src/iso19111/coordinateoperation.cpp +++ b/src/iso19111/coordinateoperation.cpp @@ -5484,13 +5484,16 @@ void Conversion::_exportToJSON( 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); + 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); + } } } @@ -7862,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(); @@ -9677,6 +9750,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 = @@ -13148,6 +13261,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/crs.cpp b/src/iso19111/crs.cpp index 670d0c1a..4f2ab7c6 100644 --- a/src/iso19111/crs.cpp +++ b/src/iso19111/crs.cpp @@ -2265,6 +2265,7 @@ void VerticalCRS::_exportToWKT(io::WKTFormatter *formatter) const { // --------------------------------------------------------------------------- +//! @cond Doxygen_Suppress void VerticalCRS::_exportToPROJString( io::PROJStringFormatter *formatter) const // throw(io::FormattingException) { @@ -2284,6 +2285,36 @@ 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); + } + + writer.AddObjKey("datum"); + datum()->_exportToJSON(formatter); + + writer.AddObjKey("coordinate_system"); + formatter->setOmitTypeInImmediateChild(); + coordinateSystem()->_exportToJSON(formatter); + + ObjectUsage::baseExportToJSON(formatter); +} +//! @endcond // --------------------------------------------------------------------------- @@ -2649,6 +2680,38 @@ 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"); + derivingConversionRef()->_exportToJSON(formatter); + + writer.AddObjKey("coordinate_system"); + formatter->setOmitTypeInImmediateChild(); + coordinateSystem()->_exportToJSON(formatter); + + ObjectUsage::baseExportToJSON(formatter); +} +//! @endcond + +// --------------------------------------------------------------------------- + //! @cond Doxygen_Suppress struct ProjectedCRS::Private { GeodeticCRSNNPtr baseCRS_; @@ -3514,6 +3577,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) { @@ -4004,6 +4097,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) { @@ -4298,38 +4415,6 @@ void DerivedGeodeticCRS::_exportToWKT(io::WKTFormatter *formatter) const { // --------------------------------------------------------------------------- -//! @cond Doxygen_Suppress -void DerivedGeodeticCRS::_exportToJSON( - io::JSONFormatter *formatter) const // throw(io::FormattingException) -{ - auto &writer = formatter->writer(); - auto objectContext(formatter->MakeObjectContext("DerivedGeodeticCRS", - !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"); - derivingConversionRef()->_exportToJSON(formatter); - - writer.AddObjKey("coordinate_system"); - formatter->setOmitTypeInImmediateChild(); - coordinateSystem()->_exportToJSON(formatter); - - ObjectUsage::baseExportToJSON(formatter); -} -//! @endcond - -// --------------------------------------------------------------------------- - void DerivedGeodeticCRS::_exportToPROJString( io::PROJStringFormatter *) const // throw(io::FormattingException) { @@ -4468,38 +4553,6 @@ void DerivedGeographicCRS::_exportToWKT(io::WKTFormatter *formatter) const { // --------------------------------------------------------------------------- -//! @cond Doxygen_Suppress -void DerivedGeographicCRS::_exportToJSON( - io::JSONFormatter *formatter) const // throw(io::FormattingException) -{ - auto &writer = formatter->writer(); - auto objectContext(formatter->MakeObjectContext("DerivedGeographicCRS", - !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"); - derivingConversionRef()->_exportToJSON(formatter); - - writer.AddObjKey("coordinate_system"); - formatter->setOmitTypeInImmediateChild(); - coordinateSystem()->_exportToJSON(formatter); - - ObjectUsage::baseExportToJSON(formatter); -} -//! @endcond - -// --------------------------------------------------------------------------- - void DerivedGeographicCRS::_exportToPROJString( io::PROJStringFormatter *) const // throw(io::FormattingException) { @@ -4756,6 +4809,35 @@ 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"); + 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 { @@ -4864,6 +4946,35 @@ 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"); + 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 { @@ -4966,6 +5077,35 @@ 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"); + 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 { @@ -5160,6 +5300,13 @@ DerivedCRSTemplate::create( // --------------------------------------------------------------------------- +template +const char *DerivedCRSTemplate::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 2944da1e..d6e6bc21 100644 --- a/src/iso19111/datum.cpp +++ b/src/iso19111/datum.cpp @@ -1725,6 +1725,30 @@ void VerticalReferenceFrame::_exportToWKT( // --------------------------------------------------------------------------- +//! @cond Doxygen_Suppress +void VerticalReferenceFrame::_exportToJSON( + io::JSONFormatter *formatter) const // throw(FormattingException) +{ + auto objectContext(formatter->MakeObjectContext("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); + + ObjectUsage::baseExportToJSON(formatter); +} +//! @endcond + +// --------------------------------------------------------------------------- + //! @cond Doxygen_Suppress bool VerticalReferenceFrame::_isEquivalentTo( const util::IComparable *other, @@ -1988,6 +2012,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, @@ -2061,6 +2111,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, @@ -2127,6 +2196,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, diff --git a/src/iso19111/io.cpp b/src/iso19111/io.cpp index bcd14429..eadc54cc 100644 --- a/src/iso19111/io.cpp +++ b/src/iso19111/io.cpp @@ -4390,17 +4390,24 @@ class JSONParser { static Measure getMeasure(const json &j); ObjectDomainPtr buildObjectDomain(const json &j); - PropertyMap buildProperties(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); GeodeticReferenceFrameNNPtr buildGeodeticReferenceFrame(const json &j); + VerticalReferenceFrameNNPtr buildVerticalReferenceFrame(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); public: JSONParser() = default; @@ -4592,14 +4599,23 @@ ObjectDomainPtr JSONParser::buildObjectDomain(const json &j) { // --------------------------------------------------------------------------- -PropertyMap JSONParser::buildProperties(const json &j) { +PropertyMap JSONParser::buildProperties(const json &j, bool removeInverseOf) { PropertyMap map; - map.set(IdentifiedObject::NAME_KEY, getName(j)); + 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("id")) { auto id = getObject(j, "id"); - map.set(metadata::Identifier::CODESPACE_KEY, - getString(id, "authority")); + auto codeSpace(getString(id, "authority")); + if (removeInverseOf && starts_with(codeSpace, "INVERSE(") && + codeSpace.back() == ')') { + codeSpace = codeSpace.substr(strlen("INVERSE(")); + codeSpace.resize(codeSpace.size() - 1); + } + map.set(metadata::Identifier::CODESPACE_KEY, codeSpace); if (!id.contains("code")) { throw ParsingException("Missing \"code\" key"); } @@ -4652,7 +4668,7 @@ PropertyMap JSONParser::buildProperties(const json &j) { BaseObjectNNPtr JSONParser::create(const json &j) { - if (j.type() != json::value_t::object) { + if (!j.is_object()) { throw ParsingException("JSON object expected"); } auto type = getString(j, "type"); @@ -4665,9 +4681,21 @@ BaseObjectNNPtr JSONParser::create(const json &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 == "GeodeticReferenceFrame") { return buildGeodeticReferenceFrame(j); } + if (type == "VerticalReferenceFrame") { + return buildVerticalReferenceFrame(j); + } if (type == "Ellipsoid") { return buildEllipsoid(j); } @@ -4677,6 +4705,15 @@ BaseObjectNNPtr JSONParser::create(const json &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\""); } @@ -4755,6 +4792,48 @@ ProjectedCRSNNPtr JSONParser::buildProjectedCRS(const json &j) { // --------------------------------------------------------------------------- +VerticalCRSNNPtr JSONParser::buildVerticalCRS(const json &j) { + auto datumJ = getObject(j, "datum"); + if (getType(datumJ) != "VerticalReferenceFrame") { + throw ParsingException("Unsupported type for datum."); + } + auto datum = buildVerticalReferenceFrame(datumJ); + auto csJ = getObject(j, "coordinate_system"); + auto verticalCS = util::nn_dynamic_pointer_cast(buildCS(csJ)); + if (!verticalCS) { + throw ParsingException("expected a vertical CS"); + } + return VerticalCRS::create(buildProperties(j), datum, + NN_NO_CHECK(verticalCS)); +} + +// --------------------------------------------------------------------------- + +CRSNNPtr JSONParser::buildCRS(const json &j) { + auto crs = util::nn_dynamic_pointer_cast(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 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); @@ -4775,11 +4854,152 @@ ConversionNNPtr JSONParser::buildConversion(const json &j) { 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::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 parameters; + std::vector 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())); + continue; + } + } + values.emplace_back(ParameterValue::create(getMeasure(param))); + } + + CRSPtr sourceTransformationCRS; + if (dynamic_cast(targetCRS.get())) { + sourceTransformationCRS = sourceCRS->extractGeographicCRS(); + if (!sourceTransformationCRS) { + sourceTransformationCRS = + std::dynamic_pointer_cast(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()); + + 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 parameters; + std::vector 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())); + 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 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 operations; + for (const auto &stepJ : stepsJ) { + if (!stepJ.is_object()) { + throw ParsingException("Unexpected type for a \"steps\" child"); + } + auto op = nn_dynamic_pointer_cast(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()); + } 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"); @@ -4884,9 +5104,23 @@ JSONParser::buildGeodeticReferenceFrame(const json &j) { auto pm = j.contains("prime_meridian") ? buildPrimeMeridian(getObject(j, "prime_meridian")) : PrimeMeridian::GREENWICH; + optional anchor; + if (j.contains("anchor")) { + anchor = getString(j, "anchor"); + } return GeodeticReferenceFrame::create( - buildProperties(j), buildEllipsoid(ellipsoidJ), - optional() /* anchor */, pm); + buildProperties(j), buildEllipsoid(ellipsoidJ), anchor, pm); +} + +// --------------------------------------------------------------------------- + +VerticalReferenceFrameNNPtr +JSONParser::buildVerticalReferenceFrame(const json &j) { + optional anchor; + if (j.contains("anchor")) { + anchor = getString(j, "anchor"); + } + return VerticalReferenceFrame::create(buildProperties(j), anchor); } // --------------------------------------------------------------------------- @@ -8605,6 +8839,7 @@ struct JSONFormatter::Private { std::vector outputIdStack_{true}; bool allowIDInImmediateChild_ = false; bool omitTypeInImmediateChild_ = false; + bool abridgedTransformation_ = false; std::string result_{}; @@ -8717,6 +8952,18 @@ JSONFormatter::ObjectContext::~ObjectContext() { m_formatter.d->popOutputId(); } +// --------------------------------------------------------------------------- + +void JSONFormatter::setAbridgedTransformation(bool outputIn) { + d->abridgedTransformation_ = outputIn; +} + +// --------------------------------------------------------------------------- + +bool JSONFormatter::abridgedTransformation() const { + return d->abridgedTransformation_; +} + //! @endcond // --------------------------------------------------------------------------- -- cgit v1.2.3 From 124eb228c464eaf170c03b70fd461fd1a06b82f2 Mon Sep 17 00:00:00 2001 From: Even Rouault Date: Fri, 9 Aug 2019 17:26:47 +0200 Subject: projinfo: document PROJJSON output, and add single-line output capability --- src/apps/projinfo.cpp | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) (limited to 'src') diff --git a/src/apps/projinfo.cpp b/src/apps/projinfo.cpp index 8f3f09b8..1d75efa6 100644 --- a/src/apps/projinfo.cpp +++ b/src/apps/projinfo.cpp @@ -482,8 +482,11 @@ static void outputObject( if (!outputOpt.quiet) { std::cout << "PROJJSON:" << std::endl; } - auto jsonString(JSONExportable->exportToJSON( - JSONFormatter::create(dbContext).get())); + 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); } -- cgit v1.2.3 From 99b25fcf9009a6cc098cf2e0703f994d74968985 Mon Sep 17 00:00:00 2001 From: Even Rouault Date: Fri, 9 Aug 2019 17:50:35 +0200 Subject: C API: add proj_as_projjson() --- src/iso19111/c_api.cpp | 66 ++++++++++++++++++++++++++++++++++++++++++++++++-- src/proj.h | 4 +++ src/proj_internal.h | 1 + 3 files changed, 69 insertions(+), 2 deletions(-) (limited to 'src') diff --git a/src/iso19111/c_api.cpp b/src/iso19111/c_api.cpp index 7a77ccfb..d4813091 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,68 @@ 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: + *
      + *
    • MULTILINE=YES/NO. Defaults to YES
    • + *
    • INDENTATION_WIDTH=number. Defauls to 2 (when multiline output is + * on).
    • + *
    + * @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(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 { + 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/proj.h b/src/proj.h index 25cd981c..0cb3c1c5 100644 --- a/src/proj.h +++ b/src/proj.h @@ -847,6 +847,10 @@ const char PROJ_DLL* proj_as_proj_string(PJ_CONTEXT *ctx, PJ_PROJ_STRING_TYPE type, const char* const *options); +const char PROJ_DLL* proj_as_projjson(PJ_CONTEXT *ctx, + const PJ *obj, + const char* const *options); + PJ PROJ_DLL *proj_get_source_crs(PJ_CONTEXT *ctx, const PJ *obj); diff --git a/src/proj_internal.h b/src/proj_internal.h index 8c365793..761746c1 100644 --- a/src/proj_internal.h +++ b/src/proj_internal.h @@ -517,6 +517,7 @@ struct PJconsts { // cached results mutable std::string lastWKT{}; mutable std::string lastPROJString{}; + mutable std::string lastJSONString{}; mutable bool gridsNeededAsked = false; mutable std::vector gridsNeeded{}; -- cgit v1.2.3 From 63981af418d84749cd4d70752f83fd551100389f Mon Sep 17 00:00:00 2001 From: Even Rouault Date: Sat, 10 Aug 2019 15:15:44 +0200 Subject: PROJJSON: add support for DatumEnsemble and Dynamic[Geodetic|Vertical]ReferenceFrame --- src/iso19111/crs.cpp | 19 ++++-- src/iso19111/datum.cpp | 94 ++++++++++++++++++++++++++--- src/iso19111/io.cpp | 156 +++++++++++++++++++++++++++++++++++++++++++++---- 3 files changed, 247 insertions(+), 22 deletions(-) (limited to 'src') diff --git a/src/iso19111/crs.cpp b/src/iso19111/crs.cpp index 4f2ab7c6..b03ece23 100644 --- a/src/iso19111/crs.cpp +++ b/src/iso19111/crs.cpp @@ -1362,7 +1362,9 @@ void GeodeticCRS::_exportToJSON( writer.AddObjKey("datum"); l_datum->_exportToJSON(formatter); } else { - // TODO DatumEnsemble + writer.AddObjKey("datum_ensemble"); + formatter->setOmitTypeInImmediateChild(); + datumEnsemble()->_exportToJSON(formatter); } writer.AddObjKey("coordinate_system"); @@ -2113,7 +2115,9 @@ void GeographicCRS::_exportToJSON( writer.AddObjKey("datum"); l_datum->_exportToJSON(formatter); } else { - // TODO DatumEnsemble + writer.AddObjKey("datum_ensemble"); + formatter->setOmitTypeInImmediateChild(); + datumEnsemble()->_exportToJSON(formatter); } writer.AddObjKey("coordinate_system"); @@ -2305,8 +2309,15 @@ void VerticalCRS::_exportToJSON( writer.Add(l_name); } - writer.AddObjKey("datum"); - datum()->_exportToJSON(formatter); + const auto &l_datum(datum()); + if (l_datum) { + writer.AddObjKey("datum"); + l_datum->_exportToJSON(formatter); + } else { + writer.AddObjKey("datum_ensemble"); + formatter->setOmitTypeInImmediateChild(); + datumEnsemble()->_exportToJSON(formatter); + } writer.AddObjKey("coordinate_system"); formatter->setOmitTypeInImmediateChild(); diff --git a/src/iso19111/datum.cpp b/src/iso19111/datum.cpp index d6e6bc21..65905ca9 100644 --- a/src/iso19111/datum.cpp +++ b/src/iso19111/datum.cpp @@ -1264,8 +1264,11 @@ void GeodeticReferenceFrame::_exportToWKT( void GeodeticReferenceFrame::_exportToJSON( io::JSONFormatter *formatter) const // throw(FormattingException) { - auto objectContext(formatter->MakeObjectContext("GeodeticReferenceFrame", - !identifiers().empty())); + auto dynamicGRF = dynamic_cast(this); + + auto objectContext(formatter->MakeObjectContext( + dynamicGRF ? "DynamicGeodeticReferenceFrame" : "GeodeticReferenceFrame", + !identifiers().empty())); auto &writer = formatter->writer(); writer.AddObjKey("name"); @@ -1278,6 +1281,17 @@ void GeodeticReferenceFrame::_exportToJSON( Datum::getPrivate()->exportAnchorDefinition(formatter); + if (dynamicGRF) { + writer.AddObjKey("frame_reference_epoch"); + writer.Add(dynamicGRF->frameReferenceEpoch().value()); + + const auto &deformationModel = dynamicGRF->deformationModelName(); + if (deformationModel.has_value()) { + writer.AddObjKey("deformation_model"); + writer.Add(*deformationModel); + } + } + writer.AddObjKey("ellipsoid"); formatter->setOmitTypeInImmediateChild(); ellipsoid()->_exportToJSON(formatter); @@ -1422,7 +1436,7 @@ void DynamicGeodeticReferenceFrame::_exportToWKT( // --------------------------------------------------------------------------- -/** \brief Instantiate a DyanmicGeodeticReferenceFrame +/** \brief Instantiate a DynamicGeodeticReferenceFrame * * @param properties See \ref general_properties. * At minimum the name should be defined. @@ -1431,7 +1445,7 @@ void DynamicGeodeticReferenceFrame::_exportToWKT( * @param primeMeridian the PrimeMeridian. * @param frameReferenceEpochIn the frame reference epoch. * @param deformationModelNameIn deformation model name, or empty - * @return new DyanmicGeodeticReferenceFrame. + * @return new DynamicGeodeticReferenceFrame. */ DynamicGeodeticReferenceFrameNNPtr DynamicGeodeticReferenceFrame::create( const util::PropertyMap &properties, const EllipsoidNNPtr &ellipsoid, @@ -1562,6 +1576,56 @@ void DatumEnsemble::_exportToWKT( // --------------------------------------------------------------------------- +//! @cond Doxygen_Suppress +void DatumEnsemble::_exportToJSON( + io::JSONFormatter *formatter) const // throw(FormattingException) +{ + auto objectContext( + formatter->MakeObjectContext("DatumEnsemble", !identifiers().empty())); + auto &writer = formatter->writer(); + + writer.AddObjKey("name"); + auto l_name = nameStr(); + if (l_name.empty()) { + writer.Add("unnamed"); + } else { + writer.Add(l_name); + } + + auto l_datums = datums(); + writer.AddObjKey("members"); + { + auto membersContext(writer.MakeArrayContext(false)); + for (const auto &datum : l_datums) { + auto memberContext(writer.MakeObjectContext()); + writer.AddObjKey("name"); + const auto &l_datum_name = datum->nameStr(); + if (!l_datum_name.empty()) { + writer.Add(l_datum_name); + } else { + writer.Add("unnamed"); + } + datum->formatID(formatter); + } + } + + auto grfFirst = std::dynamic_pointer_cast( + l_datums[0].as_nullable()); + if (grfFirst) { + writer.AddObjKey("ellipsoid"); + formatter->setOmitTypeInImmediateChild(); + grfFirst->ellipsoid()->_exportToJSON(formatter); + } + + writer.AddObjKey("accuracy"); + writer.Add(positionalAccuracy()->value()); + + formatID(formatter); +} +//! @endcond + +// --------------------------------------------------------------------------- + /** \brief Instantiate a DatumEnsemble. * * @param properties See \ref general_properties. @@ -1729,8 +1793,11 @@ void VerticalReferenceFrame::_exportToWKT( void VerticalReferenceFrame::_exportToJSON( io::JSONFormatter *formatter) const // throw(FormattingException) { - auto objectContext(formatter->MakeObjectContext("VerticalReferenceFrame", - !identifiers().empty())); + auto dynamicGRF = dynamic_cast(this); + + auto objectContext(formatter->MakeObjectContext( + dynamicGRF ? "DynamicVerticalReferenceFrame" : "VerticalReferenceFrame", + !identifiers().empty())); auto &writer = formatter->writer(); writer.AddObjKey("name"); @@ -1743,6 +1810,17 @@ void VerticalReferenceFrame::_exportToJSON( Datum::getPrivate()->exportAnchorDefinition(formatter); + if (dynamicGRF) { + writer.AddObjKey("frame_reference_epoch"); + writer.Add(dynamicGRF->frameReferenceEpoch().value()); + + const auto &deformationModel = dynamicGRF->deformationModelName(); + if (deformationModel.has_value()) { + writer.AddObjKey("deformation_model"); + writer.Add(*deformationModel); + } + } + ObjectUsage::baseExportToJSON(formatter); } //! @endcond @@ -1882,7 +1960,7 @@ void DynamicVerticalReferenceFrame::_exportToWKT( // --------------------------------------------------------------------------- -/** \brief Instantiate a DyanmicVerticalReferenceFrame +/** \brief Instantiate a DynamicVerticalReferenceFrame * * @param properties See \ref general_properties. * At minimum the name should be defined. @@ -1890,7 +1968,7 @@ void DynamicVerticalReferenceFrame::_exportToWKT( * @param realizationMethodIn the realization method, or empty. * @param frameReferenceEpochIn the frame reference epoch. * @param deformationModelNameIn deformation model name, or empty - * @return new DyanmicVerticalReferenceFrame. + * @return new DynamicVerticalReferenceFrame. */ DynamicVerticalReferenceFrameNNPtr DynamicVerticalReferenceFrame::create( const util::PropertyMap &properties, diff --git a/src/iso19111/io.cpp b/src/iso19111/io.cpp index eadc54cc..8467a9e1 100644 --- a/src/iso19111/io.cpp +++ b/src/iso19111/io.cpp @@ -4396,8 +4396,13 @@ class JSONParser { GeodeticCRSNNPtr buildGeodeticCRS(const json &j); ProjectedCRSNNPtr buildProjectedCRS(const json &j); ConversionNNPtr buildConversion(const json &j); + DatumEnsembleNNPtr buildDatumEnsemble(const json &j); GeodeticReferenceFrameNNPtr buildGeodeticReferenceFrame(const json &j); VerticalReferenceFrameNNPtr buildVerticalReferenceFrame(const json &j); + DynamicGeodeticReferenceFrameNNPtr + buildDynamicGeodeticReferenceFrame(const json &j); + DynamicVerticalReferenceFrameNNPtr + buildDynamicVerticalReferenceFrame(const json &j); EllipsoidNNPtr buildEllipsoid(const json &j); PrimeMeridianNNPtr buildPrimeMeridian(const json &j); CoordinateSystemNNPtr buildCS(const json &j); @@ -4690,12 +4695,21 @@ BaseObjectNNPtr JSONParser::create(const json &j) if (type == "BoundCRS") { return buildBoundCRS(j); } + if (type == "DatumEnsemble") { + return buildDatumEnsemble(j); + } if (type == "GeodeticReferenceFrame") { return buildGeodeticReferenceFrame(j); } if (type == "VerticalReferenceFrame") { return buildVerticalReferenceFrame(j); } + if (type == "DynamicGeodeticReferenceFrame") { + return buildDynamicGeodeticReferenceFrame(j); + } + if (type == "DynamicVerticalReferenceFrame") { + return buildDynamicVerticalReferenceFrame(j); + } if (type == "Ellipsoid") { return buildEllipsoid(j); } @@ -4720,12 +4734,20 @@ BaseObjectNNPtr JSONParser::create(const json &j) // --------------------------------------------------------------------------- GeographicCRSNNPtr JSONParser::buildGeographicCRS(const json &j) { - auto datumJ = getObject(j, "datum"); - if (getType(datumJ) != "GeodeticReferenceFrame") { - throw ParsingException("Unsupported type for datum."); - } - auto datum = buildGeodeticReferenceFrame(datumJ); + GeodeticReferenceFramePtr datum; DatumEnsemblePtr datumEnsemble; + if (j.contains("datum")) { + auto datumJ = getObject(j, "datum"); + datum = util::nn_dynamic_pointer_cast( + create(datumJ)); + if (!datum) { + throw ParsingException("datum of wrong type"); + } + + } else { + datumEnsemble = + buildDatumEnsemble(getObject(j, "datum_ensemble")).as_nullable(); + } auto csJ = getObject(j, "coordinate_system"); auto ellipsoidalCS = util::nn_dynamic_pointer_cast(buildCS(csJ)); @@ -4793,17 +4815,25 @@ ProjectedCRSNNPtr JSONParser::buildProjectedCRS(const json &j) { // --------------------------------------------------------------------------- VerticalCRSNNPtr JSONParser::buildVerticalCRS(const json &j) { - auto datumJ = getObject(j, "datum"); - if (getType(datumJ) != "VerticalReferenceFrame") { - throw ParsingException("Unsupported type for datum."); + VerticalReferenceFramePtr datum; + DatumEnsemblePtr datumEnsemble; + if (j.contains("datum")) { + auto datumJ = getObject(j, "datum"); + datum = util::nn_dynamic_pointer_cast( + create(datumJ)); + if (!datum) { + throw ParsingException("datum of wrong type"); + } + } else { + datumEnsemble = + buildDatumEnsemble(getObject(j, "datum_ensemble")).as_nullable(); } - auto datum = buildVerticalReferenceFrame(datumJ); auto csJ = getObject(j, "coordinate_system"); auto verticalCS = util::nn_dynamic_pointer_cast(buildCS(csJ)); if (!verticalCS) { throw ParsingException("expected a vertical CS"); } - return VerticalCRS::create(buildProperties(j), datum, + return VerticalCRS::create(buildProperties(j), datum, datumEnsemble, NN_NO_CHECK(verticalCS)); } @@ -5098,6 +5128,70 @@ CoordinateSystemNNPtr JSONParser::buildCS(const json &j) { // --------------------------------------------------------------------------- +DatumEnsembleNNPtr JSONParser::buildDatumEnsemble(const json &j) { + auto membersJ = getArray(j, "members"); + std::vector datums; + const bool hasEllipsoid(j.contains("ellipsoid")); + for (const auto &memberJ : membersJ) { + if (!memberJ.is_object()) { + throw ParsingException( + "Unexpected type for value of a \"members\" member"); + } + auto datumName(getName(memberJ)); + if (dbContext_ && memberJ.contains("id")) { + auto id = getObject(memberJ, "id"); + auto authority = getString(id, "authority"); + auto authFactory = + AuthorityFactory::create(NN_NO_CHECK(dbContext_), authority); + auto code = id["code"]; + std::string codeStr; + if (code.is_string()) { + codeStr = code.get(); + } else if (code.is_number_integer()) { + codeStr = internal::toString(code.get()); + } else { + throw ParsingException("Unexpected type for value of \"code\""); + } + try { + datums.push_back(authFactory->createDatum(codeStr)); + } catch (const std::exception &) { + throw ParsingException("No Datum of code " + codeStr); + } + continue; + } else if (dbContext_) { + auto authFactory = AuthorityFactory::create(NN_NO_CHECK(dbContext_), + std::string()); + auto list = authFactory->createObjectsFromName( + datumName, {AuthorityFactory::ObjectType::DATUM}, + false /* approximate=false*/); + if (!list.empty()) { + auto datum = util::nn_dynamic_pointer_cast(list.front()); + if (!datum) + throw ParsingException( + "DatumEnsemble member is not a datum"); + datums.push_back(NN_NO_CHECK(datum)); + continue; + } + } + + // Fallback if no db match + if (hasEllipsoid) { + datums.emplace_back(GeodeticReferenceFrame::create( + buildProperties(memberJ), + buildEllipsoid(getObject(j, "ellipsoid")), + optional(), PrimeMeridian::GREENWICH)); + } else { + datums.emplace_back( + VerticalReferenceFrame::create(buildProperties(memberJ))); + } + } + return DatumEnsemble::create( + buildProperties(j), datums, + PositionalAccuracy::create(getString(j, "accuracy"))); +} + +// --------------------------------------------------------------------------- + GeodeticReferenceFrameNNPtr JSONParser::buildGeodeticReferenceFrame(const json &j) { auto ellipsoidJ = getObject(j, "ellipsoid"); @@ -5114,6 +5208,29 @@ JSONParser::buildGeodeticReferenceFrame(const json &j) { // --------------------------------------------------------------------------- +DynamicGeodeticReferenceFrameNNPtr +JSONParser::buildDynamicGeodeticReferenceFrame(const json &j) { + auto ellipsoidJ = getObject(j, "ellipsoid"); + auto pm = j.contains("prime_meridian") + ? buildPrimeMeridian(getObject(j, "prime_meridian")) + : PrimeMeridian::GREENWICH; + optional anchor; + if (j.contains("anchor")) { + anchor = getString(j, "anchor"); + } + Measure frameReferenceEpoch(getNumber(j, "frame_reference_epoch"), + UnitOfMeasure::YEAR); + optional deformationModel; + if (j.contains("deformation_model")) { + deformationModel = getString(j, "deformation_model"); + } + return DynamicGeodeticReferenceFrame::create( + buildProperties(j), buildEllipsoid(ellipsoidJ), anchor, pm, + frameReferenceEpoch, deformationModel); +} + +// --------------------------------------------------------------------------- + VerticalReferenceFrameNNPtr JSONParser::buildVerticalReferenceFrame(const json &j) { optional anchor; @@ -5125,6 +5242,25 @@ JSONParser::buildVerticalReferenceFrame(const json &j) { // --------------------------------------------------------------------------- +DynamicVerticalReferenceFrameNNPtr +JSONParser::buildDynamicVerticalReferenceFrame(const json &j) { + optional anchor; + if (j.contains("anchor")) { + anchor = getString(j, "anchor"); + } + Measure frameReferenceEpoch(getNumber(j, "frame_reference_epoch"), + UnitOfMeasure::YEAR); + optional deformationModel; + if (j.contains("deformation_model")) { + deformationModel = getString(j, "deformation_model"); + } + return DynamicVerticalReferenceFrame::create( + buildProperties(j), anchor, util::optional(), + frameReferenceEpoch, deformationModel); +} + +// --------------------------------------------------------------------------- + PrimeMeridianNNPtr JSONParser::buildPrimeMeridian(const json &j) { if (!j.contains("longitude")) { throw ParsingException("Missing \"longitude\" key"); -- cgit v1.2.3 From 0a1261781de96d2bb8c76fbd905ebf8b0121d3a6 Mon Sep 17 00:00:00 2001 From: Even Rouault Date: Sat, 10 Aug 2019 17:44:53 +0200 Subject: PROJJSON: a few fixes, and add import of DerivedCRS, EngineeringCRS, ParametricCRS and TemporalCRS --- src/iso19111/common.cpp | 2 +- src/iso19111/coordinatesystem.cpp | 5 +- src/iso19111/crs.cpp | 4 ++ src/iso19111/io.cpp | 146 ++++++++++++++++++++++++++++++++------ 4 files changed, 132 insertions(+), 25 deletions(-) (limited to 'src') diff --git a/src/iso19111/common.cpp b/src/iso19111/common.cpp index fb7a63c0..f375ea0a 100644 --- a/src/iso19111/common.cpp +++ b/src/iso19111/common.cpp @@ -257,7 +257,7 @@ void UnitOfMeasure::_exportToJSON( } else if (l_type == Type::TIME) { writer.Add("TimeUnit"); } else if (l_type == Type::PARAMETRIC) { - writer.Add("ParametericUnit"); + writer.Add("ParametricUnit"); } else { writer.Add("Unit"); } diff --git a/src/iso19111/coordinatesystem.cpp b/src/iso19111/coordinatesystem.cpp index ef53dd57..5a852b0d 100644 --- a/src/iso19111/coordinatesystem.cpp +++ b/src/iso19111/coordinatesystem.cpp @@ -409,12 +409,13 @@ void CoordinateSystemAxis::_exportToJSON( writer.AddObjKey("direction"); writer.Add(direction().toString()); - writer.AddObjKey("unit"); 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 { + } else if (l_unit.type() != common::UnitOfMeasure::Type::NONE) { + writer.AddObjKey("unit"); l_unit._exportToJSON(formatter); } diff --git a/src/iso19111/crs.cpp b/src/iso19111/crs.cpp index b03ece23..b9694ba2 100644 --- a/src/iso19111/crs.cpp +++ b/src/iso19111/crs.cpp @@ -2711,6 +2711,7 @@ void DerivedCRS::_exportToJSON( baseCRS()->_exportToJSON(formatter); writer.AddObjKey("conversion"); + formatter->setOmitTypeInImmediateChild(); derivingConversionRef()->_exportToJSON(formatter); writer.AddObjKey("coordinate_system"); @@ -4837,6 +4838,7 @@ void TemporalCRS::_exportToJSON( } writer.AddObjKey("datum"); + formatter->setOmitTypeInImmediateChild(); datum()->_exportToJSON(formatter); writer.AddObjKey("coordinate_system"); @@ -4974,6 +4976,7 @@ void EngineeringCRS::_exportToJSON( } writer.AddObjKey("datum"); + formatter->setOmitTypeInImmediateChild(); datum()->_exportToJSON(formatter); writer.AddObjKey("coordinate_system"); @@ -5105,6 +5108,7 @@ void ParametricCRS::_exportToJSON( } writer.AddObjKey("datum"); + formatter->setOmitTypeInImmediateChild(); datum()->_exportToJSON(formatter); writer.AddObjKey("coordinate_system"); diff --git a/src/iso19111/io.cpp b/src/iso19111/io.cpp index 8467a9e1..cd54f4ea 100644 --- a/src/iso19111/io.cpp +++ b/src/iso19111/io.cpp @@ -4414,6 +4414,61 @@ class JSONParser { TransformationNNPtr buildTransformation(const json &j); ConcatenatedOperationNNPtr buildConcatenatedOperation(const json &j); + static util::optional getAnchor(const json &j) { + util::optional 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 + util::nn> 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); + if (!csCast) { + throw ParsingException("coordinate_system not of expected type"); + } + return TargetCRS::create(buildProperties(j), datum, + NN_NO_CHECK(csCast)); + } + + template + util::nn> buildDerivedCRS(const json &j) { + auto baseCRSObj = create(getObject(j, "base_crs")); + auto baseCRS = util::nn_dynamic_pointer_cast(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); + 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; @@ -4511,7 +4566,7 @@ UnitOfMeasure JSONParser::getUnit(const json &j, const char *key) { type = UnitOfMeasure::Type::SCALE; } else if (typeStr == "TimeUnit") { type = UnitOfMeasure::Type::TIME; - } else if (typeStr == "ParametericUnit") { + } else if (typeStr == "ParametricUnit") { type = UnitOfMeasure::Type::PARAMETRIC; } else if (typeStr == "Unit") { type = UnitOfMeasure::Type::UNKNOWN; @@ -4695,6 +4750,58 @@ BaseObjectNNPtr JSONParser::create(const json &j) if (type == "BoundCRS") { return buildBoundCRS(j); } + if (type == "EngineeringCRS") { + return buildCRS(j, &JSONParser::buildEngineeringDatum); + } + if (type == "ParametricCRS") { + return buildCRS(j, &JSONParser::buildParametricDatum); + } + if (type == "TemporalCRS") { + return buildCRS(j, &JSONParser::buildTemporalDatum); + } + if (type == "DerivedGeodeticCRS") { + auto baseCRSObj = create(getObject(j, "base_crs")); + auto baseCRS = util::nn_dynamic_pointer_cast(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(cs); + if (csCartesian) + return DerivedGeodeticCRS::create(buildProperties(j), + NN_NO_CHECK(baseCRS), conv, + NN_NO_CHECK(csCartesian)); + auto csSpherical = util::nn_dynamic_pointer_cast(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(j); + } + if (type == "DerivedProjectedCRS") { + return buildDerivedCRS(j); + } + if (type == "DerivedVerticalCRS") { + return buildDerivedCRS(j); + } + if (type == "DerivedEngineeringCRS") { + return buildDerivedCRS(j); + } + if (type == "DerivedParametricCRS") { + return buildDerivedCRS(j); + } + if (type == "DerivedTemporalCRS") { + return buildDerivedCRS(j); + } if (type == "DatumEnsemble") { return buildDatumEnsemble(j); } @@ -4710,6 +4817,15 @@ BaseObjectNNPtr JSONParser::create(const json &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); } @@ -5033,7 +5149,9 @@ JSONParser::buildConcatenatedOperation(const json &j) { CoordinateSystemAxisNNPtr JSONParser::buildAxis(const json &j) { auto dirString = getString(j, "direction"); auto abbreviation = getString(j, "abbreviation"); - auto unit = getUnit(j, "unit"); + 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)); @@ -5198,12 +5316,8 @@ JSONParser::buildGeodeticReferenceFrame(const json &j) { auto pm = j.contains("prime_meridian") ? buildPrimeMeridian(getObject(j, "prime_meridian")) : PrimeMeridian::GREENWICH; - optional anchor; - if (j.contains("anchor")) { - anchor = getString(j, "anchor"); - } return GeodeticReferenceFrame::create( - buildProperties(j), buildEllipsoid(ellipsoidJ), anchor, pm); + buildProperties(j), buildEllipsoid(ellipsoidJ), getAnchor(j), pm); } // --------------------------------------------------------------------------- @@ -5214,10 +5328,6 @@ JSONParser::buildDynamicGeodeticReferenceFrame(const json &j) { auto pm = j.contains("prime_meridian") ? buildPrimeMeridian(getObject(j, "prime_meridian")) : PrimeMeridian::GREENWICH; - optional anchor; - if (j.contains("anchor")) { - anchor = getString(j, "anchor"); - } Measure frameReferenceEpoch(getNumber(j, "frame_reference_epoch"), UnitOfMeasure::YEAR); optional deformationModel; @@ -5225,7 +5335,7 @@ JSONParser::buildDynamicGeodeticReferenceFrame(const json &j) { deformationModel = getString(j, "deformation_model"); } return DynamicGeodeticReferenceFrame::create( - buildProperties(j), buildEllipsoid(ellipsoidJ), anchor, pm, + buildProperties(j), buildEllipsoid(ellipsoidJ), getAnchor(j), pm, frameReferenceEpoch, deformationModel); } @@ -5233,21 +5343,13 @@ JSONParser::buildDynamicGeodeticReferenceFrame(const json &j) { VerticalReferenceFrameNNPtr JSONParser::buildVerticalReferenceFrame(const json &j) { - optional anchor; - if (j.contains("anchor")) { - anchor = getString(j, "anchor"); - } - return VerticalReferenceFrame::create(buildProperties(j), anchor); + return VerticalReferenceFrame::create(buildProperties(j), getAnchor(j)); } // --------------------------------------------------------------------------- DynamicVerticalReferenceFrameNNPtr JSONParser::buildDynamicVerticalReferenceFrame(const json &j) { - optional anchor; - if (j.contains("anchor")) { - anchor = getString(j, "anchor"); - } Measure frameReferenceEpoch(getNumber(j, "frame_reference_epoch"), UnitOfMeasure::YEAR); optional deformationModel; @@ -5255,7 +5357,7 @@ JSONParser::buildDynamicVerticalReferenceFrame(const json &j) { deformationModel = getString(j, "deformation_model"); } return DynamicVerticalReferenceFrame::create( - buildProperties(j), anchor, util::optional(), + buildProperties(j), getAnchor(j), util::optional(), frameReferenceEpoch, deformationModel); } -- cgit v1.2.3 From 8d0500b325d12b047797a60e3c13d4b473fae987 Mon Sep 17 00:00:00 2001 From: Even Rouault Date: Mon, 12 Aug 2019 13:45:17 +0200 Subject: PROJJSON: add support for importing 'ids' --- src/iso19111/io.cpp | 63 +++++++++++++++++++++++++++++++++++++---------------- 1 file changed, 44 insertions(+), 19 deletions(-) (limited to 'src') diff --git a/src/iso19111/io.cpp b/src/iso19111/io.cpp index cd54f4ea..9f4f6061 100644 --- a/src/iso19111/io.cpp +++ b/src/iso19111/io.cpp @@ -4389,6 +4389,7 @@ class JSONParser { 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); @@ -4659,6 +4660,34 @@ ObjectDomainPtr JSONParser::buildObjectDomain(const json &j) { // --------------------------------------------------------------------------- +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(); + } else if (codeJ.is_number_integer()) { + code = internal::toString(codeJ.get()); + } 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)); @@ -4667,26 +4696,22 @@ PropertyMap JSONParser::buildProperties(const json &j, bool removeInverseOf) { } map.set(IdentifiedObject::NAME_KEY, name); - if (j.contains("id")) { - auto id = getObject(j, "id"); - auto codeSpace(getString(id, "authority")); - if (removeInverseOf && starts_with(codeSpace, "INVERSE(") && - codeSpace.back() == ')') { - codeSpace = codeSpace.substr(strlen("INVERSE(")); - codeSpace.resize(codeSpace.size() - 1); - } - map.set(metadata::Identifier::CODESPACE_KEY, codeSpace); - if (!id.contains("code")) { - throw ParsingException("Missing \"code\" key"); - } - auto code = id["code"]; - if (code.is_string()) { - map.set(metadata::Identifier::CODE_KEY, code.get()); - } else if (code.is_number_integer()) { - map.set(metadata::Identifier::CODE_KEY, code.get()); - } else { - throw ParsingException("Unexpected type for value of \"code\""); + 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")) { -- cgit v1.2.3 From cad1c5cf61fc00759bf4ad17b0b34f57f4945de6 Mon Sep 17 00:00:00 2001 From: Even Rouault Date: Sat, 17 Aug 2019 10:58:08 +0200 Subject: PROJJSON: rename file as projjson.schema.json, and add versionning to it and to exported PROJJSON strings --- src/iso19111/c_api.cpp | 4 ++++ src/iso19111/io.cpp | 21 +++++++++++++++++++++ 2 files changed, 25 insertions(+) (limited to 'src') diff --git a/src/iso19111/c_api.cpp b/src/iso19111/c_api.cpp index d4813091..d27b8800 100644 --- a/src/iso19111/c_api.cpp +++ b/src/iso19111/c_api.cpp @@ -1335,6 +1335,8 @@ const char *proj_as_proj_string(PJ_CONTEXT *ctx, const PJ *obj, *
  • MULTILINE=YES/NO. Defaults to YES
  • *
  • INDENTATION_WIDTH=number. Defauls to 2 (when multiline output is * on).
  • + *
  • SCHEMA=string. URL to PROJJSON schema. Can be set to empty string to + * disable it.
  • * * @return a string, or NULL in case of error. * @@ -1359,6 +1361,8 @@ const char *proj_as_projjson(PJ_CONTEXT *ctx, const PJ *obj, 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; diff --git a/src/iso19111/io.cpp b/src/iso19111/io.cpp index 9f4f6061..5ccd9642 100644 --- a/src/iso19111/io.cpp +++ b/src/iso19111/io.cpp @@ -86,6 +86,10 @@ 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 @@ -9103,6 +9107,7 @@ struct JSONFormatter::Private { bool allowIDInImmediateChild_ = false; bool omitTypeInImmediateChild_ = false; bool abridgedTransformation_ = false; + std::string schema_ = PROJJSON_CURRENT_VERSION; std::string result_{}; @@ -9148,6 +9153,17 @@ JSONFormatter &JSONFormatter::setIndentationWidth(int width) noexcept { // --------------------------------------------------------------------------- +/** \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()) {} @@ -9188,6 +9204,11 @@ 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); -- cgit v1.2.3