aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorEven Rouault <even.rouault@spatialys.com>2019-07-06 02:03:50 +0200
committerEven Rouault <even.rouault@spatialys.com>2019-07-06 02:27:46 +0200
commit17f0b0b3bc65ffba39bf6f22a12b2cc7fcb9bafd (patch)
tree5993d701145e2117fb8598faa186312b98d54f00
parent1da55c8be619a21153845607a553c9d1206bc792 (diff)
downloadPROJ-17f0b0b3bc65ffba39bf6f22a12b2cc7fcb9bafd.tar.gz
PROJ-17f0b0b3bc65ffba39bf6f22a12b2cc7fcb9bafd.zip
Proof-of-concept of JSON export limited to PrimeMeridian (refs #1545)
-rw-r--r--include/proj/common.hpp5
-rw-r--r--include/proj/datum.hpp6
-rw-r--r--include/proj/io.hpp72
-rw-r--r--include/proj/metadata.hpp6
-rw-r--r--scripts/reference_exported_symbols.txt7
-rw-r--r--src/Makefile.am6
-rw-r--r--src/iso19111/common.cpp65
-rw-r--r--src/iso19111/datum.cpp35
-rw-r--r--src/iso19111/io.cpp87
-rw-r--r--src/iso19111/metadata.cpp20
-rw-r--r--src/lib_proj.cmake2
-rw-r--r--src/proj_json_streaming_writer.cpp297
-rw-r--r--src/proj_json_streaming_writer.hpp145
-rw-r--r--test/unit/test_datum.cpp28
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"),