diff options
| author | Even Rouault <even.rouault@spatialys.com> | 2019-07-06 02:03:50 +0200 |
|---|---|---|
| committer | Even Rouault <even.rouault@spatialys.com> | 2019-07-06 02:27:46 +0200 |
| commit | 17f0b0b3bc65ffba39bf6f22a12b2cc7fcb9bafd (patch) | |
| tree | 5993d701145e2117fb8598faa186312b98d54f00 | |
| parent | 1da55c8be619a21153845607a553c9d1206bc792 (diff) | |
| download | PROJ-17f0b0b3bc65ffba39bf6f22a12b2cc7fcb9bafd.tar.gz PROJ-17f0b0b3bc65ffba39bf6f22a12b2cc7fcb9bafd.zip | |
Proof-of-concept of JSON export limited to PrimeMeridian (refs #1545)
| -rw-r--r-- | include/proj/common.hpp | 5 | ||||
| -rw-r--r-- | include/proj/datum.hpp | 6 | ||||
| -rw-r--r-- | include/proj/io.hpp | 72 | ||||
| -rw-r--r-- | include/proj/metadata.hpp | 6 | ||||
| -rw-r--r-- | scripts/reference_exported_symbols.txt | 7 | ||||
| -rw-r--r-- | src/Makefile.am | 6 | ||||
| -rw-r--r-- | src/iso19111/common.cpp | 65 | ||||
| -rw-r--r-- | src/iso19111/datum.cpp | 35 | ||||
| -rw-r--r-- | src/iso19111/io.cpp | 87 | ||||
| -rw-r--r-- | src/iso19111/metadata.cpp | 20 | ||||
| -rw-r--r-- | src/lib_proj.cmake | 2 | ||||
| -rw-r--r-- | src/proj_json_streaming_writer.cpp | 297 | ||||
| -rw-r--r-- | src/proj_json_streaming_writer.hpp | 145 | ||||
| -rw-r--r-- | test/unit/test_datum.cpp | 28 |
14 files changed, 777 insertions, 4 deletions
diff --git a/include/proj/common.hpp b/include/proj/common.hpp index 7940f4fc..d857dafd 100644 --- a/include/proj/common.hpp +++ b/include/proj/common.hpp @@ -104,6 +104,9 @@ class PROJ_GCC_DLL UnitOfMeasure : public util::BaseObject { const std::string &unitType = std::string()) const; // throw(io::FormattingException) + PROJ_INTERNAL void _exportToJSON( + io::JSONFormatter *formatter) const; // throw(io::FormattingException) + PROJ_INTERNAL std::string exportToPROJString() const; PROJ_INTERNAL bool @@ -329,6 +332,8 @@ class PROJ_GCC_DLL IdentifiedObject : public util::BaseObject, formatID(io::WKTFormatter *formatter) const; PROJ_INTERNAL void formatRemarks(io::WKTFormatter *formatter) const; + PROJ_INTERNAL void formatID(io::JSONFormatter *formatter) const; + PROJ_INTERNAL bool _isEquivalentTo(const util::IComparable *other, util::IComparable::Criterion criterion = diff --git a/include/proj/datum.hpp b/include/proj/datum.hpp index b7416497..47bab4a0 100644 --- a/include/proj/datum.hpp +++ b/include/proj/datum.hpp @@ -171,7 +171,8 @@ using PrimeMeridianNNPtr = util::nn<PrimeMeridianPtr>; * \remark Implements PrimeMeridian from \ref ISO_19111_2019 */ class PROJ_GCC_DLL PrimeMeridian final : public common::IdentifiedObject, - public io::IPROJStringExportable { + public io::IPROJStringExportable, + public io::IJSONExportable { public: //! @cond Doxygen_Suppress PROJ_DLL ~PrimeMeridian() override; @@ -198,6 +199,9 @@ class PROJ_GCC_DLL PrimeMeridian final : public common::IdentifiedObject, PROJ_INTERNAL void _exportToWKT(io::WKTFormatter *formatter) const override; // throw(io::FormattingException) + PROJ_INTERNAL void _exportToJSON(io::JSONFormatter *formatter) + const override; // throw(io::FormattingException) + PROJ_INTERNAL bool _isEquivalentTo(const util::IComparable *other, util::IComparable::Criterion criterion = diff --git a/include/proj/io.hpp b/include/proj/io.hpp index c553598d..71a6430d 100644 --- a/include/proj/io.hpp +++ b/include/proj/io.hpp @@ -38,6 +38,7 @@ #include "proj.h" +#include "proj_json_streaming_writer.hpp" #include "util.hpp" NS_PROJ_START @@ -455,6 +456,77 @@ class PROJ_GCC_DLL PROJStringFormatter { // --------------------------------------------------------------------------- +class JSONFormatter; +/** JSONFormatter unique pointer. */ +using JSONFormatterPtr = std::unique_ptr<JSONFormatter>; +/** Non-null JSONFormatter unique pointer. */ +using JSONFormatterNNPtr = util::nn<JSONFormatterPtr>; + +/** \brief Formatter to JSON strings. + * + * An instance of this class can only be used by a single + * thread at a time. + */ +class PROJ_GCC_DLL JSONFormatter { + public: + PROJ_DLL static JSONFormatterNNPtr + create(DatabaseContextPtr dbContext = nullptr); + //! @cond Doxygen_Suppress + PROJ_DLL ~JSONFormatter(); + //! @endcond + + PROJ_DLL JSONFormatter &setMultiLine(bool multiLine) noexcept; + PROJ_DLL JSONFormatter &setIndentationWidth(int width) noexcept; + + PROJ_DLL const std::string &toString() const; + + PROJ_PRIVATE : + + //! @cond Doxygen_Suppress + PROJ_INTERNAL PROJ::CPLJSonStreamingWriter & + writer() const; + + // cppcheck-suppress functionStatic + PROJ_INTERNAL bool outputId() const; + + //! @endcond + + protected: + //! @cond Doxygen_Suppress + PROJ_INTERNAL explicit JSONFormatter(); + JSONFormatter(const JSONFormatter &other) = delete; + + INLINED_MAKE_UNIQUE + //! @endcond + + private: + PROJ_OPAQUE_PRIVATE_DATA +}; + +// --------------------------------------------------------------------------- + +/** \brief Interface for an object that can be exported to JSON. */ +class PROJ_GCC_DLL IJSONExportable { + public: + //! @cond Doxygen_Suppress + PROJ_DLL virtual ~IJSONExportable(); + //! @endcond + + /** Builds a JSON representation. May throw a FormattingException */ + PROJ_DLL std::string + exportToJSON(JSONFormatter *formatter) const; // throw(FormattingException) + + PROJ_PRIVATE : + + //! @cond Doxygen_Suppress + PROJ_INTERNAL virtual void + _exportToJSON( + JSONFormatter *formatter) const = 0; // throw(FormattingException) + //! @endcond +}; + +// --------------------------------------------------------------------------- + /** \brief Exception possibly thrown by IWKTExportable::exportToWKT() or * IPROJStringExportable::exportToPROJString(). */ class PROJ_GCC_DLL FormattingException : public util::Exception { diff --git a/include/proj/metadata.hpp b/include/proj/metadata.hpp index d32996fb..37241e6f 100644 --- a/include/proj/metadata.hpp +++ b/include/proj/metadata.hpp @@ -361,7 +361,8 @@ using IdentifierNNPtr = util::nn<IdentifierPtr>; * originates from \ref ISO_19115 */ class PROJ_GCC_DLL Identifier : public util::BaseObject, - public io::IWKTExportable { + public io::IWKTExportable, + public io::IJSONExportable { public: //! @cond Doxygen_Suppress PROJ_DLL Identifier(const Identifier &other); @@ -401,6 +402,9 @@ class PROJ_GCC_DLL Identifier : public util::BaseObject, PROJ_INTERNAL void _exportToWKT(io::WKTFormatter *formatter) const override; // throw(io::FormattingException) + PROJ_INTERNAL void _exportToJSON(io::JSONFormatter *formatter) + const override; // throw(io::FormattingException) + //! @endcond protected: diff --git a/scripts/reference_exported_symbols.txt b/scripts/reference_exported_symbols.txt index f3d13bd7..51ef425a 100644 --- a/scripts/reference_exported_symbols.txt +++ b/scripts/reference_exported_symbols.txt @@ -331,10 +331,17 @@ osgeo::proj::io::FactoryException::FactoryException(osgeo::proj::io::FactoryExce osgeo::proj::io::FactoryException::FactoryException(std::string const&) osgeo::proj::io::FormattingException::~FormattingException() osgeo::proj::io::FormattingException::FormattingException(osgeo::proj::io::FormattingException const&) +osgeo::proj::io::IJSONExportable::exportToJSON(osgeo::proj::io::JSONFormatter*) const +osgeo::proj::io::IJSONExportable::~IJSONExportable() osgeo::proj::io::IPROJStringExportable::exportToPROJString(osgeo::proj::io::PROJStringFormatter*) const osgeo::proj::io::IPROJStringExportable::~IPROJStringExportable() osgeo::proj::io::IWKTExportable::exportToWKT(osgeo::proj::io::WKTFormatter*) const osgeo::proj::io::IWKTExportable::~IWKTExportable() +osgeo::proj::io::JSONFormatter::create(std::shared_ptr<osgeo::proj::io::DatabaseContext>) +osgeo::proj::io::JSONFormatter::~JSONFormatter() +osgeo::proj::io::JSONFormatter::setIndentationWidth(int) +osgeo::proj::io::JSONFormatter::setMultiLine(bool) +osgeo::proj::io::JSONFormatter::toString() const osgeo::proj::io::NoSuchAuthorityCodeException::getAuthorityCode() const osgeo::proj::io::NoSuchAuthorityCodeException::getAuthority() const osgeo::proj::io::NoSuchAuthorityCodeException::~NoSuchAuthorityCodeException() 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 @@ -348,6 +348,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) { const double valRad = angle.getSIValue(); 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<JSONFormatter>()); + ret->d->dbContext_ = dbContext; + return ret; +} + +// --------------------------------------------------------------------------- + +/** \brief Whether to use multi line output or not. */ +JSONFormatter &JSONFormatter::setMultiLine(bool multiLine) noexcept { + d->writer_.SetPrettyFormatting(multiLine); + return *this; +} + +// --------------------------------------------------------------------------- + +/** \brief Set number of spaces for each indentation level (defaults to 4). + */ +JSONFormatter &JSONFormatter::setIndentationWidth(int width) noexcept { + d->writer_.SetIndentationSize(width); + return *this; +} + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress + +JSONFormatter::JSONFormatter() : d(internal::make_unique<Private>()) {} + +// --------------------------------------------------------------------------- + +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 <even.rouault at spatialys.com> + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + ****************************************************************************/ + +/*! @cond Doxygen_Suppress */ + +#include <vector> +#include <string> + +#include "proj_json_streaming_writer.hpp" + +#include <string.h> +#include <sqlite3.h> +#include <stdarg.h> +#include <cmath> +#define CPLAssert(x) do {} while(0) +#define CPLIsNan std::isnan +#define CPLIsInf std::isinf +#define CPL_FRMT_GIB "%lld" +#define CPL_FRMT_GUIB "%llu" +typedef std::uint64_t GUIntBig; + +static std::string CPLSPrintf(const char* fmt, ...) +{ + std::string res; + res.resize(256); + va_list list; + va_start(list, fmt); + sqlite3_vsnprintf(256, &res[0], fmt, list); + va_end(list); + res.resize(strlen(&res[0])); + return res; +} + +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<unsigned char>(ch) < ' ' ) + ret += CPLSPrintf("\\u%04X", ch); + else + ret += ch; + break; + } + } + ret += '"'; + return ret; +} + +void CPLJSonStreamingWriter::EmitCommaIfNeeded() +{ + if( m_bWaitForValue ) + { + m_bWaitForValue = false; + } + else if( !m_states.empty() ) + { + if( !m_states.back().bFirstChild ) + { + Print(","); + if( m_bPretty && !m_bNewLineEnabled ) + Print(" "); + } + if( m_bPretty && m_bNewLineEnabled ) + { + Print("\n"); + Print(m_osIndentAcc); + } + m_states.back().bFirstChild = false; + } +} + +void CPLJSonStreamingWriter::StartObj() +{ + EmitCommaIfNeeded(); + Print("{"); + IncIndent(); + m_states.emplace_back(State(true)); +} + +void CPLJSonStreamingWriter::EndObj() +{ + CPLAssert(!m_bWaitForValue); + CPLAssert( !m_states.empty() ); + CPLAssert( m_states.back().bIsObj ); + DecIndent(); + if( !m_states.back().bFirstChild ) + { + if( m_bPretty && m_bNewLineEnabled ) + { + Print("\n"); + Print(m_osIndentAcc); + } + } + m_states.pop_back(); + Print("}"); +} + +void CPLJSonStreamingWriter::StartArray() +{ + EmitCommaIfNeeded(); + Print("["); + IncIndent(); + m_states.emplace_back(State(false)); +} + +void CPLJSonStreamingWriter::EndArray() +{ + CPLAssert( !m_states.empty() ); + CPLAssert( !m_states.back().bIsObj ); + DecIndent(); + if( !m_states.back().bFirstChild ) + { + if( m_bPretty && m_bNewLineEnabled ) + { + Print("\n"); + Print(m_osIndentAcc); + } + } + m_states.pop_back(); + Print("]"); +} + +void CPLJSonStreamingWriter::AddObjKey(const std::string& key) +{ + CPLAssert( !m_states.empty() ); + CPLAssert( m_states.back().bIsObj ); + CPLAssert(!m_bWaitForValue); + EmitCommaIfNeeded(); + Print(FormatString(key)); + Print(m_bPretty ? ": " : ":"); + m_bWaitForValue = true; +} + +void CPLJSonStreamingWriter::Add(bool bVal) +{ + EmitCommaIfNeeded(); + Print(bVal ? "true" : "false"); +} + +void CPLJSonStreamingWriter::Add(const std::string& str) +{ + EmitCommaIfNeeded(); + Print(FormatString(str)); +} + +void CPLJSonStreamingWriter::Add(const char* pszStr) +{ + EmitCommaIfNeeded(); + Print(FormatString(pszStr)); +} + +void CPLJSonStreamingWriter::Add(GIntBig nVal) +{ + EmitCommaIfNeeded(); + Print(CPLSPrintf(CPL_FRMT_GIB, nVal)); +} + +void CPLJSonStreamingWriter::Add(GUInt64 nVal) +{ + EmitCommaIfNeeded(); + Print(CPLSPrintf(CPL_FRMT_GUIB, static_cast<GUIntBig>(nVal))); +} + +void CPLJSonStreamingWriter::Add(float fVal, int nPrecision) +{ + EmitCommaIfNeeded(); + if( CPLIsNan(fVal) ) + { + Print("\"NaN\""); + } + else if( CPLIsInf(fVal) ) + { + Print( fVal > 0 ? "\"Infinity\"" : "\"-Infinity\"" ); + } + else + { + char szFormatting[10]; + snprintf(szFormatting, sizeof(szFormatting), "%%.%dg", nPrecision); + Print(CPLSPrintf(szFormatting, fVal)); + } +} + +void CPLJSonStreamingWriter::Add(double dfVal, int nPrecision) +{ + EmitCommaIfNeeded(); + if( CPLIsNan(dfVal) ) + { + Print("\"NaN\""); + } + else if( CPLIsInf(dfVal) ) + { + Print( dfVal > 0 ? "\"Infinity\"" : "\"-Infinity\"" ); + } + else + { + char szFormatting[10]; + snprintf(szFormatting, sizeof(szFormatting), "%%.%dg", nPrecision); + Print(CPLSPrintf(szFormatting, dfVal)); + } +} + +void CPLJSonStreamingWriter::AddNull() +{ + EmitCommaIfNeeded(); + Print("null"); +} + +} // 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 <even.rouault at spatialys.com> + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + ****************************************************************************/ + +#ifndef PROJ_JSON_STREAMING_WRITER_H +#define PROJ_JSON_STREAMING_WRITER_H + +/*! @cond Doxygen_Suppress */ + +#include <vector> +#include <string> + +#define CPL_DLL + +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<State> m_states{}; + bool m_bWaitForValue = false; + + void Print(const std::string& text); + void IncIndent(); + void DecIndent(); + static std::string FormatString(const std::string& str); + void EmitCommaIfNeeded(); + +public: + CPLJSonStreamingWriter(SerializationFuncType pfnSerializationFunc, + void* pUserData); + ~CPLJSonStreamingWriter(); + + void SetPrettyFormatting(bool bPretty) { m_bPretty = bPretty; } + void SetIndentationSize(int nSpaces); + + // cppcheck-suppress functionStatic + const std::string& GetString() const { return m_osStr; } + + void Add(const std::string& str); + void Add(const char* pszStr); + void Add(bool bVal); + void Add(int nVal) { Add(static_cast<GIntBig>(nVal)); } + void Add(unsigned int nVal) { Add(static_cast<GIntBig>(nVal)); } + void Add(GIntBig nVal); + void Add(GUInt64 nVal); + void Add(float fVal, int nPrecision = 9); + void Add(double dfVal, int nPrecision = 18); + void AddNull(); + + void StartObj(); + void EndObj(); + void AddObjKey(const std::string& key); + struct CPL_DLL ObjectContext + { + CPLJSonStreamingWriter& m_serializer; + 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 diff --git a/test/unit/test_datum.cpp b/test/unit/test_datum.cpp index fa53ff85..121b7c88 100644 --- a/test/unit/test_datum.cpp +++ b/test/unit/test_datum.cpp @@ -205,6 +205,34 @@ TEST(datum, prime_meridian_to_PROJString) { // --------------------------------------------------------------------------- +TEST(datum, prime_meridian_to_JSON) { + + EXPECT_EQ( + PrimeMeridian::GREENWICH->exportToJSON(JSONFormatter::create().get()), + "{\n" + " \"type\": \"PrimeMeridian\",\n" + " \"name\": \"Greenwich\",\n" + " \"longitude\": {\n" + " \"value\": 0,\n" + " \"unit\": {\n" + " \"type\": \"AngularUnit\",\n" + " \"name\": \"degree\",\n" + " \"conversion_factor\": 0.0174532925199433,\n" + " \"id\": {\n" + " \"authority\": \"EPSG\",\n" + " \"code\": 9122\n" + " }\n" + " }\n" + " },\n" + " \"id\": {\n" + " \"authority\": \"EPSG\",\n" + " \"code\": 8901\n" + " }\n" + "}"); +} + +// --------------------------------------------------------------------------- + TEST(datum, datum_with_ANCHOR) { auto datum = GeodeticReferenceFrame::create( PropertyMap().set(IdentifiedObject::NAME_KEY, "WGS_1984 with anchor"), |
