aboutsummaryrefslogtreecommitdiff
path: root/test
diff options
context:
space:
mode:
authorEven Rouault <even.rouault@spatialys.com>2018-11-14 17:40:42 +0100
committerEven Rouault <even.rouault@spatialys.com>2018-11-14 22:48:29 +0100
commitd928db15d53805d9b728b440079756081961c536 (patch)
treee862a961d26bedb34c58e4f28ef0bdeedb5f3225 /test
parent330e8bf686f9c4524075ca1ff50cbca6c9e091da (diff)
downloadPROJ-d928db15d53805d9b728b440079756081961c536.tar.gz
PROJ-d928db15d53805d9b728b440079756081961c536.zip
Implement RFC 2: Initial integration of "GDAL SRS barn" work
This work mostly consists of: - a C++ implementation of the ISO-19111:2018 / OGC Topic 2 "Referencing by coordinates" classes to represent Datums, Coordinate systems, CRSs (Coordinate Reference Systems) and Coordinate Operations. - methods to convert between this C++ modeling and WKT1, WKT2 and PROJ string representations of those objects - management and query of a SQLite3 database of CRS and Coordinate Operation definition - a C API binding part of those capabilities This is all-in-one squashed commit of the work of https://github.com/OSGeo/proj.4/pull/1040
Diffstat (limited to 'test')
-rw-r--r--test/gie/builtins.gie15
-rw-r--r--test/unit/CMakeLists.txt25
-rw-r--r--test/unit/Makefile.am13
-rw-r--r--test/unit/main.cpp5
-rw-r--r--test/unit/test_c_api.cpp1925
-rw-r--r--test/unit/test_common.cpp191
-rw-r--r--test/unit/test_crs.cpp4647
-rw-r--r--test/unit/test_datum.cpp482
-rw-r--r--test/unit/test_factory.cpp2732
-rw-r--r--test/unit/test_io.cpp7148
-rw-r--r--test/unit/test_metadata.cpp388
-rw-r--r--test/unit/test_operation.cpp6271
-rw-r--r--test/unit/test_util.cpp63
13 files changed, 23904 insertions, 1 deletions
diff --git a/test/gie/builtins.gie b/test/gie/builtins.gie
index f4e15a77..1154febc 100644
--- a/test/gie/builtins.gie
+++ b/test/gie/builtins.gie
@@ -124,6 +124,21 @@ expect 3_860_398.3783 5_430_089.0490
roundtrip 100
-------------------------------------------------------------------------------
+# Test the Modified Azimuthal Equidistant / EPSG 9832. Test data from the EPSG
+Guidance Note 7 part 2, April 2018, p. 85
+-------------------------------------------------------------------------------
+operation +proj=aeqd +ellps=clrk66 +lat_0=9.546708325068591 +lon_0=138.1687444500492 +x_0=40000.00 +y_0=60000.00
+-------------------------------------------------------------------------------
+tolerance 1 cm
+accept 138.19303001104092 9.596525859439623
+expect 42665.90 65509.82
+roundtrip 100
+
+direction inverse
+accept 42665.90 65509.82
+expect 138.19303001104092 9.596525859439623
+
+-------------------------------------------------------------------------------
# Test the azimuthal equidistant modified for Guam. Test data from the EPSG
Guidance Note 7 part 2, September 2016, p. 85
-------------------------------------------------------------------------------
diff --git a/test/unit/CMakeLists.txt b/test/unit/CMakeLists.txt
index d6f6b068..5138dafc 100644
--- a/test/unit/CMakeLists.txt
+++ b/test/unit/CMakeLists.txt
@@ -50,6 +50,14 @@ unset(_save_cxx_flags)
#
# Build PROJ unit tests
#
+
+if("${CMAKE_C_COMPILER_ID}" STREQUAL "MSVC" AND BUILD_LIBPROJ_SHARED)
+add_definitions(-DPROJ_MSVC_DLL_IMPORT=1)
+endif()
+
+include_directories(${CMAKE_SOURCE_DIR}/include)
+include_directories(${SQLITE3_INCLUDE_DIR})
+
add_executable(proj_test_unit
main.cpp
basic_test.cpp)
@@ -78,3 +86,20 @@ target_link_libraries(pj_phi2_test
${PROJ_LIBRARIES})
add_test(NAME pj_phi2_test COMMAND pj_phi2_test)
endif()
+
+add_executable(proj_test_cpp_api
+ main.cpp
+ test_util.cpp
+ test_common.cpp
+ test_crs.cpp
+ test_metadata.cpp
+ test_io.cpp
+ test_operation.cpp
+ test_datum.cpp
+ test_factory.cpp
+ test_c_api.cpp)
+target_link_libraries(proj_test_cpp_api
+ gtest
+ ${PROJ_LIBRARIES}
+ ${SQLITE3_LIBRARY})
+add_test(NAME proj_test_cpp_api COMMAND proj_test_cpp_api)
diff --git a/test/unit/Makefile.am b/test/unit/Makefile.am
index 65132be7..77525f9a 100644
--- a/test/unit/Makefile.am
+++ b/test/unit/Makefile.am
@@ -4,16 +4,22 @@ EXTRA_DIST = CMakeLists.txt
noinst_HEADERS = gtest_include.h
-AM_CPPFLAGS = -I$(top_srcdir)/src -I$(top_srcdir)/test -I$(top_srcdir)/test/googletest/include
+AM_CPPFLAGS = -I$(top_srcdir)/src -I$(top_srcdir)/include -I$(top_srcdir)/test -I$(top_srcdir)/test/googletest/include @SQLITE3_FLAGS@
AM_CXXFLAGS = @CXX_WFLAGS@ @NO_ZERO_AS_NULL_POINTER_CONSTANT_FLAG@
+PROJ_LIB ?= ../../data
+
noinst_PROGRAMS = basic_test
noinst_PROGRAMS += pj_phi2_test
noinst_PROGRAMS += proj_errno_string_test
+noinst_PROGRAMS += test_cpp_api
basic_test_SOURCES = basic_test.cpp main.cpp
basic_test_LDADD = ../../src/libproj.la ../../test/googletest/libgtest.la
+test_cpp_api_SOURCES = test_util.cpp test_common.cpp test_crs.cpp test_metadata.cpp test_io.cpp test_operation.cpp test_datum.cpp test_factory.cpp test_c_api.cpp main.cpp
+test_cpp_api_LDADD = ../../src/libproj.la ../../test/googletest/libgtest.la @SQLITE3_LDFLAGS@
+
basic_test-check: basic_test
./basic_test
@@ -31,3 +37,8 @@ proj_errno_string_test-check: proj_errno_string_test
check-local: basic_test-check
check-local: pj_phi2_test-check proj_errno_string_test-check
+
+test_cpp_api-check: test_cpp_api
+ PROJ_LIB=$(PROJ_LIB) ./test_cpp_api
+
+check-local: basic_test-check test_cpp_api-check
diff --git a/test/unit/main.cpp b/test/unit/main.cpp
index ffa95e03..f24a7aa6 100644
--- a/test/unit/main.cpp
+++ b/test/unit/main.cpp
@@ -26,9 +26,14 @@
* DEALINGS IN THE SOFTWARE.
****************************************************************************/
+#include <locale>
+
#include "gtest_include.h"
GTEST_API_ int main(int argc, char **argv) {
+ // Use a potentially non-C locale to make sure we are robust
+ setlocale(LC_ALL, "");
+
testing::InitGoogleTest(&argc, argv);
return RUN_ALL_TESTS();
}
diff --git a/test/unit/test_c_api.cpp b/test/unit/test_c_api.cpp
new file mode 100644
index 00000000..207a8cd1
--- /dev/null
+++ b/test/unit/test_c_api.cpp
@@ -0,0 +1,1925 @@
+/******************************************************************************
+ *
+ * Project: PROJ
+ * Purpose: Test ISO19111:2018 implementation
+ * Author: Even Rouault <even dot rouault at spatialys dot com>
+ *
+ ******************************************************************************
+ * Copyright (c) 2018, Even Rouault <even dot rouault at spatialys dot 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.
+ ****************************************************************************/
+
+#include "gtest_include.h"
+
+#include "proj.h"
+
+#include "proj/common.hpp"
+#include "proj/coordinateoperation.hpp"
+#include "proj/coordinatesystem.hpp"
+#include "proj/crs.hpp"
+#include "proj/datum.hpp"
+#include "proj/io.hpp"
+#include "proj/metadata.hpp"
+#include "proj/util.hpp"
+
+using namespace osgeo::proj::common;
+using namespace osgeo::proj::crs;
+using namespace osgeo::proj::cs;
+using namespace osgeo::proj::datum;
+using namespace osgeo::proj::io;
+using namespace osgeo::proj::metadata;
+using namespace osgeo::proj::operation;
+using namespace osgeo::proj::util;
+
+namespace {
+
+class CApi : public ::testing::Test {
+
+ static void DummyLogFunction(void *, int, const char *) {}
+
+ protected:
+ void SetUp() override {
+ m_ctxt = proj_context_create();
+ proj_log_func(m_ctxt, nullptr, DummyLogFunction);
+ }
+
+ void TearDown() override { proj_context_destroy(m_ctxt); }
+
+ static BoundCRSNNPtr createBoundCRS() {
+ return BoundCRS::create(
+ GeographicCRS::EPSG_4807, GeographicCRS::EPSG_4326,
+ Transformation::create(PropertyMap(), GeographicCRS::EPSG_4807,
+ GeographicCRS::EPSG_4326, nullptr,
+ PropertyMap(), {}, {}, {}));
+ }
+
+ static ProjectedCRSNNPtr createProjectedCRS() {
+ PropertyMap propertiesCRS;
+ propertiesCRS.set(Identifier::CODESPACE_KEY, "EPSG")
+ .set(Identifier::CODE_KEY, 32631)
+ .set(IdentifiedObject::NAME_KEY, "WGS 84 / UTM zone 31N");
+ return ProjectedCRS::create(
+ propertiesCRS, GeographicCRS::EPSG_4326,
+ Conversion::createUTM(PropertyMap(), 31, true),
+ CartesianCS::createEastingNorthing(UnitOfMeasure::METRE));
+ }
+
+ static VerticalCRSNNPtr createVerticalCRS() {
+ PropertyMap propertiesVDatum;
+ propertiesVDatum.set(Identifier::CODESPACE_KEY, "EPSG")
+ .set(Identifier::CODE_KEY, 5101)
+ .set(IdentifiedObject::NAME_KEY, "Ordnance Datum Newlyn");
+ auto vdatum = VerticalReferenceFrame::create(propertiesVDatum);
+ PropertyMap propertiesCRS;
+ propertiesCRS.set(Identifier::CODESPACE_KEY, "EPSG")
+ .set(Identifier::CODE_KEY, 5701)
+ .set(IdentifiedObject::NAME_KEY, "ODN height");
+ return VerticalCRS::create(
+ propertiesCRS, vdatum,
+ VerticalCS::createGravityRelatedHeight(UnitOfMeasure::METRE));
+ }
+
+ static CompoundCRSNNPtr createCompoundCRS() {
+ PropertyMap properties;
+ properties.set(Identifier::CODESPACE_KEY, "codespace")
+ .set(Identifier::CODE_KEY, "code")
+ .set(IdentifiedObject::NAME_KEY, "horizontal + vertical");
+ return CompoundCRS::create(
+ properties,
+ std::vector<CRSNNPtr>{createProjectedCRS(), createVerticalCRS()});
+ }
+
+ PJ_CONTEXT *m_ctxt = nullptr;
+
+ struct ObjectKeeper {
+ PJ_OBJ *m_obj = nullptr;
+ explicit ObjectKeeper(PJ_OBJ *obj) : m_obj(obj) {}
+ ~ObjectKeeper() { proj_obj_unref(m_obj); }
+
+ ObjectKeeper(const ObjectKeeper &) = delete;
+ ObjectKeeper &operator=(const ObjectKeeper &) = delete;
+ };
+
+ struct ContextKeeper {
+ PJ_OPERATION_FACTORY_CONTEXT *m_op_ctxt = nullptr;
+ explicit ContextKeeper(PJ_OPERATION_FACTORY_CONTEXT *op_ctxt)
+ : m_op_ctxt(op_ctxt) {}
+ ~ContextKeeper() { proj_operation_factory_context_unref(m_op_ctxt); }
+
+ ContextKeeper(const ContextKeeper &) = delete;
+ ContextKeeper &operator=(const ContextKeeper &) = delete;
+ };
+
+ struct ObjListKeeper {
+ PJ_OBJ_LIST *m_res = nullptr;
+ explicit ObjListKeeper(PJ_OBJ_LIST *res) : m_res(res) {}
+ ~ObjListKeeper() { proj_obj_list_unref(m_res); }
+
+ ObjListKeeper(const ObjListKeeper &) = delete;
+ ObjListKeeper &operator=(const ObjListKeeper &) = delete;
+ };
+};
+
+// ---------------------------------------------------------------------------
+
+TEST_F(CApi, proj_obj_create_from_user_input) {
+ proj_obj_unref(nullptr);
+ EXPECT_EQ(proj_obj_create_from_user_input(m_ctxt, "invalid", nullptr),
+ nullptr);
+ {
+ auto obj = proj_obj_create_from_user_input(
+ m_ctxt,
+ GeographicCRS::EPSG_4326->exportToWKT(WKTFormatter::create().get())
+ .c_str(),
+ nullptr);
+ ObjectKeeper keeper(obj);
+ EXPECT_NE(obj, nullptr);
+ }
+ {
+ auto obj =
+ proj_obj_create_from_user_input(m_ctxt, "EPSG:4326", nullptr);
+ ObjectKeeper keeper(obj);
+ EXPECT_NE(obj, nullptr);
+ }
+}
+
+// ---------------------------------------------------------------------------
+
+TEST_F(CApi, proj_obj_create_from_wkt) {
+ proj_obj_unref(nullptr);
+ EXPECT_EQ(proj_obj_create_from_wkt(m_ctxt, "invalid", nullptr), nullptr);
+ auto obj = proj_obj_create_from_wkt(
+ m_ctxt,
+ GeographicCRS::EPSG_4326->exportToWKT(WKTFormatter::create().get())
+ .c_str(),
+ nullptr);
+ ObjectKeeper keeper(obj);
+ EXPECT_NE(obj, nullptr);
+}
+
+// ---------------------------------------------------------------------------
+
+TEST_F(CApi, proj_obj_create_from_proj_string) {
+ proj_obj_unref(nullptr);
+ EXPECT_EQ(proj_obj_create_from_proj_string(m_ctxt, "invalid", nullptr),
+ nullptr);
+ auto obj =
+ proj_obj_create_from_proj_string(m_ctxt, "+proj=longlat", nullptr);
+ ObjectKeeper keeper(obj);
+ EXPECT_NE(obj, nullptr);
+}
+
+// ---------------------------------------------------------------------------
+
+TEST_F(CApi, proj_obj_as_wkt) {
+ auto obj = proj_obj_create_from_wkt(
+ m_ctxt,
+ GeographicCRS::EPSG_4326->exportToWKT(WKTFormatter::create().get())
+ .c_str(),
+ nullptr);
+ ObjectKeeper keeper(obj);
+ ASSERT_NE(obj, nullptr);
+
+ {
+ auto wkt = proj_obj_as_wkt(obj, PJ_WKT2_2018, nullptr);
+ ASSERT_NE(wkt, nullptr);
+ EXPECT_TRUE(std::string(wkt).find("GEOGCRS[") == 0) << wkt;
+ }
+
+ {
+ auto wkt = proj_obj_as_wkt(obj, PJ_WKT2_2018_SIMPLIFIED, nullptr);
+ ASSERT_NE(wkt, nullptr);
+ EXPECT_TRUE(std::string(wkt).find("GEOGCRS[") == 0) << wkt;
+ EXPECT_TRUE(std::string(wkt).find("ANGULARUNIT[") == std::string::npos)
+ << wkt;
+ }
+
+ {
+ auto wkt = proj_obj_as_wkt(obj, PJ_WKT2_2015, nullptr);
+ ASSERT_NE(wkt, nullptr);
+ EXPECT_TRUE(std::string(wkt).find("GEODCRS[") == 0) << wkt;
+ }
+
+ {
+ auto wkt = proj_obj_as_wkt(obj, PJ_WKT2_2015_SIMPLIFIED, nullptr);
+ ASSERT_NE(wkt, nullptr);
+ EXPECT_TRUE(std::string(wkt).find("GEODCRS[") == 0) << wkt;
+ EXPECT_TRUE(std::string(wkt).find("ANGULARUNIT[") == std::string::npos)
+ << wkt;
+ }
+
+ {
+ auto wkt = proj_obj_as_wkt(obj, PJ_WKT1_GDAL, nullptr);
+ ASSERT_NE(wkt, nullptr);
+ EXPECT_TRUE(std::string(wkt).find("GEOGCS[\"WGS 84\"") == 0) << wkt;
+ }
+
+ {
+ auto wkt = proj_obj_as_wkt(obj, PJ_WKT1_ESRI, nullptr);
+ ASSERT_NE(wkt, nullptr);
+ EXPECT_TRUE(std::string(wkt).find("GEOGCS[\"GCS_WGS_1984\"") == 0)
+ << wkt;
+ }
+
+ // MULTILINE=NO
+ {
+ const char *const options[] = {"MULTILINE=NO", nullptr};
+ auto wkt = proj_obj_as_wkt(obj, PJ_WKT1_GDAL, options);
+ ASSERT_NE(wkt, nullptr);
+ EXPECT_TRUE(std::string(wkt).find("\n") == std::string::npos) << wkt;
+ }
+
+ // INDENTATION_WIDTH=2
+ {
+ const char *const options[] = {"INDENTATION_WIDTH=2", nullptr};
+ auto wkt = proj_obj_as_wkt(obj, PJ_WKT1_GDAL, options);
+ ASSERT_NE(wkt, nullptr);
+ EXPECT_TRUE(std::string(wkt).find("\n DATUM") != std::string::npos)
+ << wkt;
+ }
+
+ // OUTPUT_AXIS=NO
+ {
+ const char *const options[] = {"OUTPUT_AXIS=NO", nullptr};
+ auto wkt = proj_obj_as_wkt(obj, PJ_WKT1_GDAL, options);
+ ASSERT_NE(wkt, nullptr);
+ EXPECT_TRUE(std::string(wkt).find("AXIS") == std::string::npos) << wkt;
+ }
+
+ // unsupported option
+ {
+ const char *const options[] = {"unsupported=yes", nullptr};
+ auto wkt = proj_obj_as_wkt(obj, PJ_WKT2_2018, options);
+ EXPECT_EQ(wkt, nullptr);
+ }
+}
+
+// ---------------------------------------------------------------------------
+
+TEST_F(CApi, proj_obj_as_wkt_incompatible_WKT1) {
+ auto obj = proj_obj_create_from_wkt(
+ m_ctxt,
+ createBoundCRS()->exportToWKT(WKTFormatter::create().get()).c_str(),
+ nullptr);
+ ObjectKeeper keeper(obj);
+ ASSERT_NE(obj, nullptr);
+
+ auto wkt1_GDAL = proj_obj_as_wkt(obj, PJ_WKT1_GDAL, nullptr);
+ ASSERT_EQ(wkt1_GDAL, nullptr);
+}
+
+// ---------------------------------------------------------------------------
+
+TEST_F(CApi, proj_obj_as_proj_string) {
+ auto obj = proj_obj_create_from_wkt(
+ m_ctxt,
+ GeographicCRS::EPSG_4326->exportToWKT(WKTFormatter::create().get())
+ .c_str(),
+ nullptr);
+ ObjectKeeper keeper(obj);
+ ASSERT_NE(obj, nullptr);
+
+ {
+ auto proj_5 = proj_obj_as_proj_string(obj, PJ_PROJ_5, nullptr);
+ ASSERT_NE(proj_5, nullptr);
+ EXPECT_EQ(std::string(proj_5), "+proj=pipeline +step +proj=longlat "
+ "+ellps=WGS84 +step +proj=unitconvert "
+ "+xy_in=rad +xy_out=deg +step "
+ "+proj=axisswap +order=2,1");
+ }
+ {
+ auto proj_4 = proj_obj_as_proj_string(obj, PJ_PROJ_4, nullptr);
+ ASSERT_NE(proj_4, nullptr);
+ EXPECT_EQ(std::string(proj_4), "+proj=longlat +datum=WGS84");
+ }
+}
+
+// ---------------------------------------------------------------------------
+
+TEST_F(CApi, proj_obj_as_proj_string_incompatible_WKT1) {
+ auto obj = proj_obj_create_from_wkt(
+ m_ctxt,
+ createBoundCRS()->exportToWKT(WKTFormatter::create().get()).c_str(),
+ nullptr);
+ ObjectKeeper keeper(obj);
+ ASSERT_NE(obj, nullptr);
+
+ auto str = proj_obj_as_proj_string(obj, PJ_PROJ_5, nullptr);
+ ASSERT_EQ(str, nullptr);
+}
+
+// ---------------------------------------------------------------------------
+
+TEST_F(CApi, proj_obj_as_proj_string_etmerc_option) {
+ auto obj = proj_obj_create_from_proj_string(m_ctxt, "+proj=tmerc", nullptr);
+ ObjectKeeper keeper(obj);
+ ASSERT_NE(obj, nullptr);
+
+ const char *options[] = {"USE_ETMERC=YES", nullptr};
+ auto str = proj_obj_as_proj_string(obj, PJ_PROJ_4, options);
+ ASSERT_NE(str, nullptr);
+ EXPECT_EQ(str, std::string("+proj=etmerc +lat_0=0 +lon_0=0 +k_0=1 +x_0=0 "
+ "+y_0=0 +datum=WGS84"));
+}
+
+// ---------------------------------------------------------------------------
+
+TEST_F(CApi, proj_obj_crs_create_bound_crs_to_WGS84) {
+ auto crs = proj_obj_create_from_database(
+ m_ctxt, "EPSG", "3844", PJ_OBJ_CATEGORY_CRS, false, nullptr);
+ ObjectKeeper keeper(crs);
+ ASSERT_NE(crs, nullptr);
+
+ auto res = proj_obj_crs_create_bound_crs_to_WGS84(crs);
+ ObjectKeeper keeper_res(res);
+ ASSERT_NE(res, nullptr);
+
+ auto proj_4 = proj_obj_as_proj_string(res, PJ_PROJ_4, nullptr);
+ ASSERT_NE(proj_4, nullptr);
+ EXPECT_EQ(std::string(proj_4),
+ "+proj=sterea +lat_0=46 +lon_0=25 +k=0.99975 +x_0=500000 "
+ "+y_0=500000 +ellps=krass "
+ "+towgs84=2.329,-147.042,-92.08,-0.309,0.325,0.497,5.69");
+}
+
+// ---------------------------------------------------------------------------
+
+TEST_F(CApi, proj_obj_crs_create_bound_crs_to_WGS84_on_invalid_type) {
+ auto obj = proj_obj_create_from_wkt(
+ m_ctxt, createProjectedCRS()
+ ->derivingConversion()
+ ->exportToWKT(WKTFormatter::create().get())
+ .c_str(),
+ nullptr);
+ ObjectKeeper keeper(obj);
+ ASSERT_NE(obj, nullptr);
+
+ auto res = proj_obj_crs_create_bound_crs_to_WGS84(obj);
+ ASSERT_EQ(res, nullptr);
+}
+
+// ---------------------------------------------------------------------------
+
+TEST_F(CApi, proj_obj_get_name) {
+ auto obj = proj_obj_create_from_wkt(
+ m_ctxt,
+ GeographicCRS::EPSG_4326->exportToWKT(WKTFormatter::create().get())
+ .c_str(),
+ nullptr);
+ ObjectKeeper keeper(obj);
+ ASSERT_NE(obj, nullptr);
+ auto name = proj_obj_get_name(obj);
+ ASSERT_TRUE(name != nullptr);
+ EXPECT_EQ(name, std::string("WGS 84"));
+ EXPECT_EQ(name, proj_obj_get_name(obj));
+}
+
+// ---------------------------------------------------------------------------
+
+TEST_F(CApi, proj_obj_get_id_auth_name) {
+ auto obj = proj_obj_create_from_wkt(
+ m_ctxt,
+ GeographicCRS::EPSG_4326->exportToWKT(WKTFormatter::create().get())
+ .c_str(),
+ nullptr);
+ ObjectKeeper keeper(obj);
+ ASSERT_NE(obj, nullptr);
+ auto auth = proj_obj_get_id_auth_name(obj, 0);
+ ASSERT_TRUE(auth != nullptr);
+ EXPECT_EQ(auth, std::string("EPSG"));
+ EXPECT_EQ(auth, proj_obj_get_id_auth_name(obj, 0));
+ EXPECT_EQ(proj_obj_get_id_auth_name(obj, -1), nullptr);
+ EXPECT_EQ(proj_obj_get_id_auth_name(obj, 1), nullptr);
+}
+
+// ---------------------------------------------------------------------------
+
+TEST_F(CApi, proj_obj_get_id_code) {
+ auto obj = proj_obj_create_from_wkt(
+ m_ctxt,
+ GeographicCRS::EPSG_4326->exportToWKT(WKTFormatter::create().get())
+ .c_str(),
+ nullptr);
+ ObjectKeeper keeper(obj);
+ ASSERT_NE(obj, nullptr);
+ auto code = proj_obj_get_id_code(obj, 0);
+ ASSERT_TRUE(code != nullptr);
+ EXPECT_EQ(code, std::string("4326"));
+ EXPECT_EQ(code, proj_obj_get_id_code(obj, 0));
+ EXPECT_EQ(proj_obj_get_id_code(obj, -1), nullptr);
+ EXPECT_EQ(proj_obj_get_id_code(obj, 1), nullptr);
+}
+
+// ---------------------------------------------------------------------------
+
+TEST_F(CApi, proj_obj_get_type) {
+ {
+ auto obj = proj_obj_create_from_wkt(
+ m_ctxt,
+ GeographicCRS::EPSG_4326->exportToWKT(WKTFormatter::create().get())
+ .c_str(),
+ nullptr);
+ ObjectKeeper keeper(obj);
+ ASSERT_NE(obj, nullptr);
+ EXPECT_EQ(proj_obj_get_type(obj), PJ_OBJ_TYPE_GEOGRAPHIC_2D_CRS);
+ }
+ {
+ auto obj = proj_obj_create_from_wkt(
+ m_ctxt,
+ GeographicCRS::EPSG_4979->exportToWKT(WKTFormatter::create().get())
+ .c_str(),
+ nullptr);
+ ObjectKeeper keeper(obj);
+ ASSERT_NE(obj, nullptr);
+ EXPECT_EQ(proj_obj_get_type(obj), PJ_OBJ_TYPE_GEOGRAPHIC_3D_CRS);
+ }
+ {
+ auto obj = proj_obj_create_from_wkt(
+ m_ctxt,
+ GeographicCRS::EPSG_4978->exportToWKT(WKTFormatter::create().get())
+ .c_str(),
+ nullptr);
+ ObjectKeeper keeper(obj);
+ ASSERT_NE(obj, nullptr);
+ EXPECT_EQ(proj_obj_get_type(obj), PJ_OBJ_TYPE_GEOCENTRIC_CRS);
+ }
+ {
+ auto obj = proj_obj_create_from_wkt(
+ m_ctxt, GeographicCRS::EPSG_4326->datum()
+ ->exportToWKT(WKTFormatter::create().get())
+ .c_str(),
+ nullptr);
+ ObjectKeeper keeper(obj);
+ ASSERT_NE(obj, nullptr);
+ EXPECT_EQ(proj_obj_get_type(obj), PJ_OBJ_TYPE_GEODETIC_REFERENCE_FRAME);
+ }
+ {
+ auto obj = proj_obj_create_from_wkt(
+ m_ctxt, GeographicCRS::EPSG_4326->ellipsoid()
+ ->exportToWKT(WKTFormatter::create().get())
+ .c_str(),
+ nullptr);
+ ObjectKeeper keeper(obj);
+ ASSERT_NE(obj, nullptr);
+ EXPECT_EQ(proj_obj_get_type(obj), PJ_OBJ_TYPE_ELLIPSOID);
+ }
+ {
+ auto obj = proj_obj_create_from_wkt(
+ m_ctxt, createProjectedCRS()
+ ->exportToWKT(WKTFormatter::create().get())
+ .c_str(),
+ nullptr);
+ ObjectKeeper keeper(obj);
+ ASSERT_NE(obj, nullptr);
+ EXPECT_EQ(proj_obj_get_type(obj), PJ_OBJ_TYPE_PROJECTED_CRS);
+ }
+ {
+ auto obj = proj_obj_create_from_wkt(
+ m_ctxt, createVerticalCRS()
+ ->exportToWKT(WKTFormatter::create().get())
+ .c_str(),
+ nullptr);
+ ObjectKeeper keeper(obj);
+ ASSERT_NE(obj, nullptr);
+ EXPECT_EQ(proj_obj_get_type(obj), PJ_OBJ_TYPE_VERTICAL_CRS);
+ }
+ {
+ auto obj = proj_obj_create_from_wkt(
+ m_ctxt, createVerticalCRS()
+ ->datum()
+ ->exportToWKT(WKTFormatter::create().get())
+ .c_str(),
+ nullptr);
+ ObjectKeeper keeper(obj);
+ ASSERT_NE(obj, nullptr);
+ EXPECT_EQ(proj_obj_get_type(obj), PJ_OBJ_TYPE_VERTICAL_REFERENCE_FRAME);
+ }
+ {
+ auto obj = proj_obj_create_from_wkt(
+ m_ctxt, createProjectedCRS()
+ ->derivingConversion()
+ ->exportToWKT(WKTFormatter::create().get())
+ .c_str(),
+ nullptr);
+ ObjectKeeper keeper(obj);
+ ASSERT_NE(obj, nullptr);
+ EXPECT_EQ(proj_obj_get_type(obj), PJ_OBJ_TYPE_CONVERSION);
+ }
+ {
+ auto obj = proj_obj_create_from_wkt(
+ m_ctxt,
+ createBoundCRS()->exportToWKT(WKTFormatter::create().get()).c_str(),
+ nullptr);
+ ObjectKeeper keeper(obj);
+ ASSERT_NE(obj, nullptr);
+ EXPECT_EQ(proj_obj_get_type(obj), PJ_OBJ_TYPE_BOUND_CRS);
+ }
+ {
+ auto obj = proj_obj_create_from_wkt(
+ m_ctxt, createBoundCRS()
+ ->transformation()
+ ->exportToWKT(WKTFormatter::create().get())
+ .c_str(),
+ nullptr);
+ ObjectKeeper keeper(obj);
+ ASSERT_NE(obj, nullptr);
+ EXPECT_EQ(proj_obj_get_type(obj), PJ_OBJ_TYPE_TRANSFORMATION);
+ }
+ {
+ auto obj = proj_obj_create_from_wkt(m_ctxt, "AUTHORITY[\"EPSG\", 4326]",
+ nullptr);
+ ObjectKeeper keeper(obj);
+ ASSERT_EQ(obj, nullptr);
+ }
+}
+
+// ---------------------------------------------------------------------------
+
+TEST_F(CApi, proj_obj_create_from_database) {
+ {
+ auto crs = proj_obj_create_from_database(
+ m_ctxt, "EPSG", "-1", PJ_OBJ_CATEGORY_CRS, false, nullptr);
+ ASSERT_EQ(crs, nullptr);
+ }
+ {
+ auto crs = proj_obj_create_from_database(
+ m_ctxt, "EPSG", "4326", PJ_OBJ_CATEGORY_CRS, false, nullptr);
+ ASSERT_NE(crs, nullptr);
+ ObjectKeeper keeper(crs);
+ EXPECT_TRUE(proj_obj_is_crs(crs));
+ EXPECT_FALSE(proj_obj_is_deprecated(crs));
+ EXPECT_EQ(proj_obj_get_type(crs), PJ_OBJ_TYPE_GEOGRAPHIC_2D_CRS);
+ }
+ {
+ auto crs = proj_obj_create_from_database(
+ m_ctxt, "EPSG", "6871", PJ_OBJ_CATEGORY_CRS, false, nullptr);
+ ASSERT_NE(crs, nullptr);
+ ObjectKeeper keeper(crs);
+ EXPECT_TRUE(proj_obj_is_crs(crs));
+ EXPECT_EQ(proj_obj_get_type(crs), PJ_OBJ_TYPE_COMPOUND_CRS);
+ }
+ {
+ auto ellipsoid = proj_obj_create_from_database(
+ m_ctxt, "EPSG", "7030", PJ_OBJ_CATEGORY_ELLIPSOID, false, nullptr);
+ ASSERT_NE(ellipsoid, nullptr);
+ ObjectKeeper keeper(ellipsoid);
+ EXPECT_EQ(proj_obj_get_type(ellipsoid), PJ_OBJ_TYPE_ELLIPSOID);
+ }
+ {
+ auto datum = proj_obj_create_from_database(
+ m_ctxt, "EPSG", "6326", PJ_OBJ_CATEGORY_DATUM, false, nullptr);
+ ASSERT_NE(datum, nullptr);
+ ObjectKeeper keeper(datum);
+ EXPECT_EQ(proj_obj_get_type(datum),
+ PJ_OBJ_TYPE_GEODETIC_REFERENCE_FRAME);
+ }
+ {
+ auto op = proj_obj_create_from_database(
+ m_ctxt, "EPSG", "16031", PJ_OBJ_CATEGORY_COORDINATE_OPERATION,
+ false, nullptr);
+ ASSERT_NE(op, nullptr);
+ ObjectKeeper keeper(op);
+ EXPECT_EQ(proj_obj_get_type(op), PJ_OBJ_TYPE_CONVERSION);
+ }
+}
+
+// ---------------------------------------------------------------------------
+
+TEST_F(CApi, proj_crs) {
+ auto crs = proj_obj_create_from_wkt(
+ m_ctxt,
+ createProjectedCRS()
+ ->exportToWKT(
+ WKTFormatter::create(WKTFormatter::Convention::WKT1_GDAL).get())
+ .c_str(),
+ nullptr);
+ ASSERT_NE(crs, nullptr);
+ ObjectKeeper keeper(crs);
+ EXPECT_TRUE(proj_obj_is_crs(crs));
+
+ auto geodCRS = proj_obj_crs_get_geodetic_crs(crs);
+ ASSERT_NE(geodCRS, nullptr);
+ ObjectKeeper keeper_geogCRS(geodCRS);
+ EXPECT_TRUE(proj_obj_is_crs(geodCRS));
+ auto geogCRS_name = proj_obj_get_name(geodCRS);
+ ASSERT_TRUE(geogCRS_name != nullptr);
+ EXPECT_EQ(geogCRS_name, std::string("WGS 84"));
+
+ auto datum = proj_obj_crs_get_horizontal_datum(crs);
+ ASSERT_NE(datum, nullptr);
+ ObjectKeeper keeper_datum(datum);
+ auto datum_name = proj_obj_get_name(datum);
+ ASSERT_TRUE(datum_name != nullptr);
+ EXPECT_EQ(datum_name, std::string("World Geodetic System 1984"));
+
+ auto ellipsoid = proj_obj_get_ellipsoid(crs);
+ ASSERT_NE(ellipsoid, nullptr);
+ ObjectKeeper keeper_ellipsoid(ellipsoid);
+ auto ellipsoid_name = proj_obj_get_name(ellipsoid);
+ ASSERT_TRUE(ellipsoid_name != nullptr);
+ EXPECT_EQ(ellipsoid_name, std::string("WGS 84"));
+
+ auto ellipsoid_from_datum = proj_obj_get_ellipsoid(datum);
+ ASSERT_NE(ellipsoid_from_datum, nullptr);
+ ObjectKeeper keeper_ellipsoid_from_datum(ellipsoid_from_datum);
+
+ EXPECT_EQ(proj_obj_get_ellipsoid(ellipsoid), nullptr);
+ EXPECT_FALSE(proj_obj_is_crs(ellipsoid));
+
+ double a;
+ double b;
+ int b_is_computed;
+ double rf;
+ EXPECT_TRUE(proj_obj_ellipsoid_get_parameters(ellipsoid, nullptr, nullptr,
+ nullptr, nullptr));
+ EXPECT_TRUE(proj_obj_ellipsoid_get_parameters(ellipsoid, &a, &b,
+ &b_is_computed, &rf));
+ EXPECT_FALSE(
+ proj_obj_ellipsoid_get_parameters(crs, &a, &b, &b_is_computed, &rf));
+ EXPECT_EQ(a, 6378137);
+ EXPECT_NEAR(b, 6356752.31424518, 1e-9);
+ EXPECT_EQ(b_is_computed, 1);
+ EXPECT_EQ(rf, 298.257223563);
+ auto id = proj_obj_get_id_code(ellipsoid, 0);
+ ASSERT_TRUE(id != nullptr);
+ EXPECT_EQ(id, std::string("7030"));
+}
+
+// ---------------------------------------------------------------------------
+
+TEST_F(CApi, proj_obj_get_prime_meridian) {
+ auto crs = proj_obj_create_from_wkt(
+ m_ctxt,
+ createProjectedCRS()
+ ->exportToWKT(
+ WKTFormatter::create(WKTFormatter::Convention::WKT1_GDAL).get())
+ .c_str(),
+ nullptr);
+ ASSERT_NE(crs, nullptr);
+ ObjectKeeper keeper(crs);
+
+ auto pm = proj_obj_get_prime_meridian(crs);
+ ASSERT_NE(pm, nullptr);
+ ObjectKeeper keeper_pm(pm);
+ auto pm_name = proj_obj_get_name(pm);
+ ASSERT_TRUE(pm_name != nullptr);
+ EXPECT_EQ(pm_name, std::string("Greenwich"));
+
+ EXPECT_EQ(proj_obj_get_prime_meridian(pm), nullptr);
+
+ EXPECT_TRUE(
+ proj_obj_prime_meridian_get_parameters(pm, nullptr, nullptr, nullptr));
+ double longitude = -1;
+ double longitude_unit = 0;
+ const char *longitude_unit_name = nullptr;
+ EXPECT_TRUE(proj_obj_prime_meridian_get_parameters(
+ pm, &longitude, &longitude_unit, &longitude_unit_name));
+ EXPECT_EQ(longitude, 0);
+ EXPECT_NEAR(longitude_unit, UnitOfMeasure::DEGREE.conversionToSI(), 1e-10);
+ ASSERT_TRUE(longitude_unit_name != nullptr);
+ EXPECT_EQ(longitude_unit_name, std::string("degree"));
+
+ auto datum = proj_obj_crs_get_horizontal_datum(crs);
+ ASSERT_NE(datum, nullptr);
+ ObjectKeeper keeper_datum(datum);
+ auto pm_from_datum = proj_obj_get_prime_meridian(datum);
+ ASSERT_NE(pm_from_datum, nullptr);
+ ObjectKeeper keeper_pm_from_datum(pm_from_datum);
+}
+
+// ---------------------------------------------------------------------------
+
+TEST_F(CApi, proj_crs_compound) {
+ auto crs = proj_obj_create_from_wkt(
+ m_ctxt,
+ createCompoundCRS()->exportToWKT(WKTFormatter::create().get()).c_str(),
+ nullptr);
+ ASSERT_NE(crs, nullptr);
+ ObjectKeeper keeper(crs);
+ EXPECT_EQ(proj_obj_get_type(crs), PJ_OBJ_TYPE_COMPOUND_CRS);
+
+ EXPECT_EQ(proj_obj_crs_get_sub_crs(crs, -1), nullptr);
+ EXPECT_EQ(proj_obj_crs_get_sub_crs(crs, 2), nullptr);
+
+ auto subcrs_horiz = proj_obj_crs_get_sub_crs(crs, 0);
+ ASSERT_NE(subcrs_horiz, nullptr);
+ ObjectKeeper keeper_subcrs_horiz(subcrs_horiz);
+ EXPECT_EQ(proj_obj_get_type(subcrs_horiz), PJ_OBJ_TYPE_PROJECTED_CRS);
+ EXPECT_EQ(proj_obj_crs_get_sub_crs(subcrs_horiz, 0), nullptr);
+
+ auto subcrs_vertical = proj_obj_crs_get_sub_crs(crs, 1);
+ ASSERT_NE(subcrs_vertical, nullptr);
+ ObjectKeeper keeper_subcrs_vertical(subcrs_vertical);
+ EXPECT_EQ(proj_obj_get_type(subcrs_vertical), PJ_OBJ_TYPE_VERTICAL_CRS);
+}
+
+// ---------------------------------------------------------------------------
+
+TEST_F(CApi, proj_obj_get_source_target_crs_bound_crs) {
+ auto crs = proj_obj_create_from_wkt(
+ m_ctxt,
+ createBoundCRS()->exportToWKT(WKTFormatter::create().get()).c_str(),
+ nullptr);
+ ASSERT_NE(crs, nullptr);
+ ObjectKeeper keeper(crs);
+
+ auto sourceCRS = proj_obj_get_source_crs(crs);
+ ASSERT_NE(sourceCRS, nullptr);
+ ObjectKeeper keeper_sourceCRS(sourceCRS);
+ EXPECT_EQ(std::string(proj_obj_get_name(sourceCRS)), "NTF (Paris)");
+
+ auto targetCRS = proj_obj_get_target_crs(crs);
+ ASSERT_NE(targetCRS, nullptr);
+ ObjectKeeper keeper_targetCRS(targetCRS);
+ EXPECT_EQ(std::string(proj_obj_get_name(targetCRS)), "WGS 84");
+}
+
+// ---------------------------------------------------------------------------
+
+TEST_F(CApi, proj_obj_get_source_target_crs_transformation) {
+ auto obj = proj_obj_create_from_wkt(
+ m_ctxt, createBoundCRS()
+ ->transformation()
+ ->exportToWKT(WKTFormatter::create().get())
+ .c_str(),
+ nullptr);
+ ASSERT_NE(obj, nullptr);
+ ObjectKeeper keeper(obj);
+
+ auto sourceCRS = proj_obj_get_source_crs(obj);
+ ASSERT_NE(sourceCRS, nullptr);
+ ObjectKeeper keeper_sourceCRS(sourceCRS);
+ EXPECT_EQ(std::string(proj_obj_get_name(sourceCRS)), "NTF (Paris)");
+
+ auto targetCRS = proj_obj_get_target_crs(obj);
+ ASSERT_NE(targetCRS, nullptr);
+ ObjectKeeper keeper_targetCRS(targetCRS);
+ EXPECT_EQ(std::string(proj_obj_get_name(targetCRS)), "WGS 84");
+}
+
+// ---------------------------------------------------------------------------
+
+TEST_F(CApi, proj_obj_get_source_target_crs_conversion_without_crs) {
+ auto obj = proj_obj_create_from_database(
+ m_ctxt, "EPSG", "16031", PJ_OBJ_CATEGORY_COORDINATE_OPERATION, false,
+ nullptr);
+ ASSERT_NE(obj, nullptr);
+ ObjectKeeper keeper(obj);
+
+ auto sourceCRS = proj_obj_get_source_crs(obj);
+ ASSERT_EQ(sourceCRS, nullptr);
+
+ auto targetCRS = proj_obj_get_target_crs(obj);
+ ASSERT_EQ(targetCRS, nullptr);
+}
+
+// ---------------------------------------------------------------------------
+
+TEST_F(CApi, proj_obj_get_source_target_crs_invalid_object) {
+ auto obj = proj_obj_create_from_wkt(
+ m_ctxt, "ELLIPSOID[\"WGS 84\",6378137,298.257223563]", nullptr);
+ ASSERT_NE(obj, nullptr);
+ ObjectKeeper keeper(obj);
+
+ auto sourceCRS = proj_obj_get_source_crs(obj);
+ ASSERT_EQ(sourceCRS, nullptr);
+
+ auto targetCRS = proj_obj_get_target_crs(obj);
+ ASSERT_EQ(targetCRS, nullptr);
+}
+
+// ---------------------------------------------------------------------------
+
+struct ListFreer {
+ PROJ_STRING_LIST list;
+ ListFreer(PROJ_STRING_LIST ptrIn) : list(ptrIn) {}
+ ~ListFreer() { proj_free_string_list(list); }
+ ListFreer(const ListFreer &) = delete;
+ ListFreer &operator=(const ListFreer &) = delete;
+};
+
+// ---------------------------------------------------------------------------
+
+TEST_F(CApi, proj_get_authorities_from_database) {
+ auto list = proj_get_authorities_from_database(m_ctxt);
+ ListFreer feer(list);
+ ASSERT_NE(list, nullptr);
+ ASSERT_TRUE(list[0] != nullptr);
+ EXPECT_EQ(list[0], std::string("EPSG"));
+ ASSERT_TRUE(list[1] != nullptr);
+ EXPECT_EQ(list[1], std::string("ESRI"));
+ ASSERT_TRUE(list[2] != nullptr);
+ EXPECT_EQ(list[2], std::string("IGNF"));
+ ASSERT_TRUE(list[3] != nullptr);
+ EXPECT_EQ(list[3], std::string("OGC"));
+ ASSERT_TRUE(list[4] != nullptr);
+ EXPECT_EQ(list[4], std::string("PROJ"));
+ EXPECT_EQ(list[5], nullptr);
+}
+
+// ---------------------------------------------------------------------------
+
+TEST_F(CApi, proj_get_codes_from_database) {
+
+ auto listTypes =
+ std::vector<PJ_OBJ_TYPE>{PJ_OBJ_TYPE_ELLIPSOID,
+
+ PJ_OBJ_TYPE_GEODETIC_REFERENCE_FRAME,
+ PJ_OBJ_TYPE_DYNAMIC_GEODETIC_REFERENCE_FRAME,
+ PJ_OBJ_TYPE_VERTICAL_REFERENCE_FRAME,
+ PJ_OBJ_TYPE_DYNAMIC_VERTICAL_REFERENCE_FRAME,
+ PJ_OBJ_TYPE_DATUM_ENSEMBLE,
+
+ PJ_OBJ_TYPE_CRS,
+ PJ_OBJ_TYPE_GEODETIC_CRS,
+ PJ_OBJ_TYPE_GEOCENTRIC_CRS,
+ PJ_OBJ_TYPE_GEOGRAPHIC_CRS,
+ PJ_OBJ_TYPE_GEOGRAPHIC_2D_CRS,
+ PJ_OBJ_TYPE_GEOGRAPHIC_3D_CRS,
+ PJ_OBJ_TYPE_VERTICAL_CRS,
+ PJ_OBJ_TYPE_PROJECTED_CRS,
+ PJ_OBJ_TYPE_COMPOUND_CRS,
+ PJ_OBJ_TYPE_TEMPORAL_CRS,
+ PJ_OBJ_TYPE_BOUND_CRS,
+ PJ_OBJ_TYPE_OTHER_CRS,
+
+ PJ_OBJ_TYPE_CONVERSION,
+ PJ_OBJ_TYPE_TRANSFORMATION,
+ PJ_OBJ_TYPE_CONCATENATED_OPERATION,
+ PJ_OBJ_TYPE_OTHER_COORDINATE_OPERATION,
+
+ PJ_OBJ_TYPE_UNKNOWN};
+ for (const auto &type : listTypes) {
+ auto list = proj_get_codes_from_database(m_ctxt, "EPSG", type, true);
+ ListFreer feer(list);
+ if (type == PJ_OBJ_TYPE_TEMPORAL_CRS || type == PJ_OBJ_TYPE_BOUND_CRS ||
+ type == PJ_OBJ_TYPE_UNKNOWN) {
+ EXPECT_EQ(list, nullptr) << type;
+ } else {
+ ASSERT_NE(list, nullptr) << type;
+ ASSERT_NE(list[0], nullptr) << type;
+ }
+ }
+}
+
+// ---------------------------------------------------------------------------
+
+TEST_F(CApi, conversion) {
+ auto crs = proj_obj_create_from_wkt(
+ m_ctxt,
+ createProjectedCRS()->exportToWKT(WKTFormatter::create().get()).c_str(),
+ nullptr);
+ ASSERT_NE(crs, nullptr);
+ ObjectKeeper keeper(crs);
+
+ {
+ auto conv =
+ proj_obj_crs_get_coordoperation(crs, nullptr, nullptr, nullptr);
+ ASSERT_NE(conv, nullptr);
+ ObjectKeeper keeper_conv(conv);
+
+ ASSERT_EQ(
+ proj_obj_crs_get_coordoperation(conv, nullptr, nullptr, nullptr),
+ nullptr);
+ }
+
+ const char *methodName = nullptr;
+ const char *methodAuthorityName = nullptr;
+ const char *methodCode = nullptr;
+ auto conv = proj_obj_crs_get_coordoperation(
+ crs, &methodName, &methodAuthorityName, &methodCode);
+ ASSERT_NE(conv, nullptr);
+ ObjectKeeper keeper_conv(conv);
+
+ ASSERT_NE(methodName, nullptr);
+ ASSERT_NE(methodAuthorityName, nullptr);
+ ASSERT_NE(methodCode, nullptr);
+ EXPECT_EQ(methodName, std::string("Transverse Mercator"));
+ EXPECT_EQ(methodAuthorityName, std::string("EPSG"));
+ EXPECT_EQ(methodCode, std::string("9807"));
+
+ EXPECT_EQ(proj_coordoperation_get_param_count(conv), 5);
+ EXPECT_EQ(proj_coordoperation_get_param_index(conv, "foo"), -1);
+ EXPECT_EQ(proj_coordoperation_get_param_index(conv, "False easting"), 3);
+
+ EXPECT_FALSE(proj_coordoperation_get_param(conv, -1, nullptr, nullptr,
+ nullptr, nullptr, nullptr,
+ nullptr, nullptr));
+ EXPECT_FALSE(proj_coordoperation_get_param(conv, 5, nullptr, nullptr,
+ nullptr, nullptr, nullptr,
+ nullptr, nullptr));
+
+ const char *name = nullptr;
+ const char *nameAuthorityName = nullptr;
+ const char *nameCode = nullptr;
+ double value = 0;
+ const char *valueString = nullptr;
+ double valueUnitConvFactor = 0;
+ const char *valueUnitName = nullptr;
+ EXPECT_TRUE(proj_coordoperation_get_param(
+ conv, 3, &name, &nameAuthorityName, &nameCode, &value, &valueString,
+ &valueUnitConvFactor, &valueUnitName));
+ ASSERT_NE(name, nullptr);
+ ASSERT_NE(nameAuthorityName, nullptr);
+ ASSERT_NE(nameCode, nullptr);
+ EXPECT_EQ(valueString, nullptr);
+ ASSERT_NE(valueUnitName, nullptr);
+ EXPECT_EQ(name, std::string("False easting"));
+ EXPECT_EQ(nameAuthorityName, std::string("EPSG"));
+ EXPECT_EQ(nameCode, std::string("8806"));
+ EXPECT_EQ(value, 500000.0);
+ EXPECT_EQ(valueUnitConvFactor, 1.0);
+ EXPECT_EQ(valueUnitName, std::string("metre"));
+}
+
+// ---------------------------------------------------------------------------
+
+TEST_F(CApi, transformation_from_boundCRS) {
+ auto crs = proj_obj_create_from_wkt(
+ m_ctxt,
+ createBoundCRS()->exportToWKT(WKTFormatter::create().get()).c_str(),
+ nullptr);
+ ASSERT_NE(crs, nullptr);
+ ObjectKeeper keeper(crs);
+
+ auto transf =
+ proj_obj_crs_get_coordoperation(crs, nullptr, nullptr, nullptr);
+ ASSERT_NE(transf, nullptr);
+ ObjectKeeper keeper_transf(transf);
+}
+
+// ---------------------------------------------------------------------------
+
+TEST_F(CApi, proj_coordoperation_get_grid_used) {
+ auto op = proj_obj_create_from_database(
+ m_ctxt, "EPSG", "1312", PJ_OBJ_CATEGORY_COORDINATE_OPERATION, true,
+ nullptr);
+ ASSERT_NE(op, nullptr);
+ ObjectKeeper keeper(op);
+
+ EXPECT_EQ(proj_coordoperation_get_grid_used_count(op), 1);
+ const char *shortName = nullptr;
+ const char *fullName = nullptr;
+ const char *packageName = nullptr;
+ const char *url = nullptr;
+ int directDownload = 0;
+ int openLicense = 0;
+ int available = 0;
+ EXPECT_EQ(proj_coordoperation_get_grid_used(op, -1, nullptr, nullptr,
+ nullptr, nullptr, nullptr,
+ nullptr, nullptr),
+ 0);
+ EXPECT_EQ(proj_coordoperation_get_grid_used(op, 1, nullptr, nullptr,
+ nullptr, nullptr, nullptr,
+ nullptr, nullptr),
+ 0);
+ EXPECT_EQ(proj_coordoperation_get_grid_used(
+ op, 0, &shortName, &fullName, &packageName, &url,
+ &directDownload, &openLicense, &available),
+ 1);
+ ASSERT_NE(shortName, nullptr);
+ ASSERT_NE(fullName, nullptr);
+ ASSERT_NE(packageName, nullptr);
+ ASSERT_NE(url, nullptr);
+ EXPECT_EQ(shortName, std::string("ntv1_can.dat"));
+ // EXPECT_EQ(fullName, std::string(""));
+ EXPECT_EQ(packageName, std::string("proj-datumgrid"));
+ EXPECT_TRUE(std::string(url).find(
+ "https://download.osgeo.org/proj/proj-datumgrid-") == 0)
+ << std::string(url);
+ EXPECT_EQ(directDownload, 1);
+ EXPECT_EQ(openLicense, 1);
+}
+
+// ---------------------------------------------------------------------------
+
+TEST_F(CApi, proj_coordoperation_is_instanciable) {
+ auto op = proj_obj_create_from_database(
+ m_ctxt, "EPSG", "1671", PJ_OBJ_CATEGORY_COORDINATE_OPERATION, true,
+ nullptr);
+ ASSERT_NE(op, nullptr);
+ ObjectKeeper keeper(op);
+ EXPECT_EQ(proj_coordoperation_is_instanciable(op), 1);
+}
+
+// ---------------------------------------------------------------------------
+
+TEST_F(CApi, proj_obj_create_operations) {
+ auto ctxt = proj_create_operation_factory_context(m_ctxt, nullptr);
+ ASSERT_NE(ctxt, nullptr);
+ ContextKeeper keeper_ctxt(ctxt);
+
+ auto source_crs = proj_obj_create_from_database(
+ m_ctxt, "EPSG", "4267", PJ_OBJ_CATEGORY_CRS, false, nullptr); // NAD27
+ ASSERT_NE(source_crs, nullptr);
+ ObjectKeeper keeper_source_crs(source_crs);
+
+ auto target_crs = proj_obj_create_from_database(
+ m_ctxt, "EPSG", "4269", PJ_OBJ_CATEGORY_CRS, false, nullptr); // NAD83
+ ASSERT_NE(target_crs, nullptr);
+ ObjectKeeper keeper_target_crs(target_crs);
+
+ proj_operation_factory_context_set_spatial_criterion(
+ ctxt, PROJ_SPATIAL_CRITERION_PARTIAL_INTERSECTION);
+
+ proj_operation_factory_context_set_grid_availability_use(
+ ctxt, PROJ_GRID_AVAILABILITY_IGNORED);
+
+ auto res = proj_obj_create_operations(source_crs, target_crs, ctxt);
+ ASSERT_NE(res, nullptr);
+ ObjListKeeper keeper_res(res);
+
+ EXPECT_EQ(proj_obj_list_get_count(res), 7);
+
+ EXPECT_EQ(proj_obj_list_get(res, -1), nullptr);
+ EXPECT_EQ(proj_obj_list_get(res, proj_obj_list_get_count(res)), nullptr);
+ auto op = proj_obj_list_get(res, 0);
+ ASSERT_NE(op, nullptr);
+ ObjectKeeper keeper_op(op);
+
+ EXPECT_EQ(proj_obj_get_name(op), std::string("NAD27 to NAD83 (3)"));
+}
+
+// ---------------------------------------------------------------------------
+
+TEST_F(CApi, proj_obj_create_operations_with_pivot) {
+
+ auto source_crs = proj_obj_create_from_database(
+ m_ctxt, "EPSG", "4326", PJ_OBJ_CATEGORY_CRS, false, nullptr); // WGS84
+ ASSERT_NE(source_crs, nullptr);
+ ObjectKeeper keeper_source_crs(source_crs);
+
+ auto target_crs = proj_obj_create_from_database(
+ m_ctxt, "EPSG", "6668", PJ_OBJ_CATEGORY_CRS, false, nullptr); // JGD2011
+ ASSERT_NE(target_crs, nullptr);
+ ObjectKeeper keeper_target_crs(target_crs);
+
+ // There is no direct transformations between both
+
+ // Default behaviour: allow any pivot
+ {
+ auto ctxt = proj_create_operation_factory_context(m_ctxt, nullptr);
+ ASSERT_NE(ctxt, nullptr);
+ ContextKeeper keeper_ctxt(ctxt);
+
+ auto res = proj_obj_create_operations(source_crs, target_crs, ctxt);
+ ASSERT_NE(res, nullptr);
+ ObjListKeeper keeper_res(res);
+ EXPECT_EQ(proj_obj_list_get_count(res), 1);
+ auto op = proj_obj_list_get(res, 0);
+ ASSERT_NE(op, nullptr);
+ ObjectKeeper keeper_op(op);
+
+ EXPECT_EQ(
+ proj_obj_get_name(op),
+ std::string(
+ "Inverse of JGD2000 to WGS 84 (1) + JGD2000 to JGD2011 (2)"));
+ }
+
+ // Disallow pivots
+ {
+ auto ctxt = proj_create_operation_factory_context(m_ctxt, nullptr);
+ ASSERT_NE(ctxt, nullptr);
+ ContextKeeper keeper_ctxt(ctxt);
+ proj_operation_factory_context_set_allow_use_intermediate_crs(ctxt,
+ false);
+
+ auto res = proj_obj_create_operations(source_crs, target_crs, ctxt);
+ ASSERT_NE(res, nullptr);
+ ObjListKeeper keeper_res(res);
+ EXPECT_EQ(proj_obj_list_get_count(res), 1);
+ auto op = proj_obj_list_get(res, 0);
+ ASSERT_NE(op, nullptr);
+ ObjectKeeper keeper_op(op);
+
+ EXPECT_EQ(proj_obj_get_name(op),
+ std::string("Null geographic offset from WGS 84 to JGD2011"));
+ }
+
+ // Restrict pivot to Tokyo CRS
+ {
+ auto ctxt = proj_create_operation_factory_context(m_ctxt, "EPSG");
+ ASSERT_NE(ctxt, nullptr);
+ ContextKeeper keeper_ctxt(ctxt);
+
+ const char *pivots[] = {"EPSG", "4301", nullptr};
+ proj_operation_factory_context_set_allowed_intermediate_crs(ctxt,
+ pivots);
+ proj_operation_factory_context_set_spatial_criterion(
+ ctxt, PROJ_SPATIAL_CRITERION_PARTIAL_INTERSECTION);
+ proj_operation_factory_context_set_grid_availability_use(
+ ctxt, PROJ_GRID_AVAILABILITY_IGNORED);
+
+ auto res = proj_obj_create_operations(source_crs, target_crs, ctxt);
+ ASSERT_NE(res, nullptr);
+ ObjListKeeper keeper_res(res);
+ EXPECT_EQ(proj_obj_list_get_count(res), 6);
+ auto op = proj_obj_list_get(res, 0);
+ ASSERT_NE(op, nullptr);
+ ObjectKeeper keeper_op(op);
+
+ EXPECT_EQ(
+ proj_obj_get_name(op),
+ std::string(
+ "Inverse of Tokyo to WGS 84 (108) + Tokyo to JGD2011 (2)"));
+ }
+
+ // Restrict pivot to JGD2000
+ {
+ auto ctxt = proj_create_operation_factory_context(m_ctxt, nullptr);
+ ASSERT_NE(ctxt, nullptr);
+ ContextKeeper keeper_ctxt(ctxt);
+
+ const char *pivots[] = {"EPSG", "4612", nullptr};
+ proj_operation_factory_context_set_allowed_intermediate_crs(ctxt,
+ pivots);
+ proj_operation_factory_context_set_spatial_criterion(
+ ctxt, PROJ_SPATIAL_CRITERION_PARTIAL_INTERSECTION);
+ proj_operation_factory_context_set_grid_availability_use(
+ ctxt, PROJ_GRID_AVAILABILITY_IGNORED);
+
+ auto res = proj_obj_create_operations(source_crs, target_crs, ctxt);
+ ASSERT_NE(res, nullptr);
+ ObjListKeeper keeper_res(res);
+ // includes 2 results from ESRI
+ EXPECT_EQ(proj_obj_list_get_count(res), 4);
+ auto op = proj_obj_list_get(res, 0);
+ ASSERT_NE(op, nullptr);
+ ObjectKeeper keeper_op(op);
+
+ EXPECT_EQ(
+ proj_obj_get_name(op),
+ std::string(
+ "Inverse of JGD2000 to WGS 84 (1) + JGD2000 to JGD2011 (2)"));
+ }
+}
+
+// ---------------------------------------------------------------------------
+
+TEST_F(CApi, proj_context_set_database_path_null) {
+
+ EXPECT_TRUE(
+ proj_context_set_database_path(m_ctxt, nullptr, nullptr, nullptr));
+ auto source_crs = proj_obj_create_from_database(m_ctxt, "EPSG", "4326",
+ PJ_OBJ_CATEGORY_CRS, false,
+ nullptr); // WGS84
+ ASSERT_NE(source_crs, nullptr);
+ ObjectKeeper keeper_source_crs(source_crs);
+}
+
+// ---------------------------------------------------------------------------
+
+TEST_F(CApi, proj_context_set_database_path_main_memory_one_aux) {
+
+ auto c_path = proj_context_get_database_path(m_ctxt);
+ ASSERT_TRUE(c_path != nullptr);
+ std::string path(c_path);
+ const char *aux_db_list[] = {path.c_str(), nullptr};
+
+ // This is super exotic and a miracle that it works. :memory: as the
+ // main DB is empty. The real stuff is in the aux_db_list. No view
+ // is created in the ':memory:' internal DB, but as there's only one
+ // aux DB its tables and views can be directly queried...
+ // If that breaks at some point, that wouldn't be a big issue.
+ // Keeping that one as I had a hard time figuring out why it worked !
+ // The real thing is tested by the C++
+ // factory::attachExtraDatabases_auxiliary
+ EXPECT_TRUE(proj_context_set_database_path(m_ctxt, ":memory:", aux_db_list,
+ nullptr));
+
+ auto source_crs = proj_obj_create_from_database(m_ctxt, "EPSG", "4326",
+ PJ_OBJ_CATEGORY_CRS, false,
+ nullptr); // WGS84
+ ASSERT_NE(source_crs, nullptr);
+ ObjectKeeper keeper_source_crs(source_crs);
+}
+
+// ---------------------------------------------------------------------------
+
+TEST_F(CApi, proj_context_set_database_path_error_1) {
+
+ EXPECT_FALSE(proj_context_set_database_path(m_ctxt, "i_do_not_exist.db",
+ nullptr, nullptr));
+
+ // We will eventually re-open on the default DB
+ auto source_crs = proj_obj_create_from_database(m_ctxt, "EPSG", "4326",
+ PJ_OBJ_CATEGORY_CRS, false,
+ nullptr); // WGS84
+ ASSERT_NE(source_crs, nullptr);
+ ObjectKeeper keeper_source_crs(source_crs);
+}
+
+// ---------------------------------------------------------------------------
+
+TEST_F(CApi, proj_context_set_database_path_error_2) {
+
+ const char *aux_db_list[] = {"i_do_not_exist.db", nullptr};
+ EXPECT_FALSE(
+ proj_context_set_database_path(m_ctxt, nullptr, aux_db_list, nullptr));
+
+ // We will eventually re-open on the default DB
+ auto source_crs = proj_obj_create_from_database(m_ctxt, "EPSG", "4326",
+ PJ_OBJ_CATEGORY_CRS, false,
+ nullptr); // WGS84
+ ASSERT_NE(source_crs, nullptr);
+ ObjectKeeper keeper_source_crs(source_crs);
+}
+
+// ---------------------------------------------------------------------------
+
+TEST_F(CApi, proj_context_guess_wkt_dialect) {
+
+ EXPECT_EQ(proj_context_guess_wkt_dialect(nullptr, "LOCAL_CS[\"foo\"]"),
+ PJ_GUESSED_WKT1_GDAL);
+
+ EXPECT_EQ(proj_context_guess_wkt_dialect(
+ nullptr,
+ "GEOGCS[\"GCS_WGS_1984\",DATUM[\"D_WGS_1984\",SPHEROID[\"WGS_"
+ "1984\",6378137.0,298.257223563]],PRIMEM[\"Greenwich\",0.0],"
+ "UNIT[\"Degree\",0.0174532925199433]]"),
+ PJ_GUESSED_WKT1_ESRI);
+
+ EXPECT_EQ(proj_context_guess_wkt_dialect(
+ nullptr,
+ "GEOGCRS[\"WGS 84\",\n"
+ " DATUM[\"World Geodetic System 1984\",\n"
+ " ELLIPSOID[\"WGS 84\",6378137,298.257223563]],\n"
+ " CS[ellipsoidal,2],\n"
+ " AXIS[\"geodetic latitude (Lat)\",north],\n"
+ " AXIS[\"geodetic longitude (Lon)\",east],\n"
+ " UNIT[\"degree\",0.0174532925199433]]"),
+ PJ_GUESSED_WKT2_2018);
+
+ EXPECT_EQ(proj_context_guess_wkt_dialect(
+ nullptr,
+ "GEODCRS[\"WGS 84\",\n"
+ " DATUM[\"World Geodetic System 1984\",\n"
+ " ELLIPSOID[\"WGS 84\",6378137,298.257223563]],\n"
+ " CS[ellipsoidal,2],\n"
+ " AXIS[\"geodetic latitude (Lat)\",north],\n"
+ " AXIS[\"geodetic longitude (Lon)\",east],\n"
+ " UNIT[\"degree\",0.0174532925199433]]"),
+ PJ_GUESSED_WKT2_2015);
+
+ EXPECT_EQ(proj_context_guess_wkt_dialect(nullptr, "foo"),
+ PJ_GUESSED_NOT_WKT);
+}
+
+// ---------------------------------------------------------------------------
+
+TEST_F(CApi, proj_obj_create_from_name) {
+ /*
+ PJ_OBJ_LIST PROJ_DLL *proj_obj_create_from_name(
+ PJ_CONTEXT *ctx,
+ const char *auth_name,
+ const char *searchedName,
+ const PJ_OBJ_TYPE* types,
+ size_t typesCount,
+ int approximateMatch,
+ size_t limitResultCount,
+ const char* const *options); */
+ {
+ auto res = proj_obj_create_from_name(m_ctxt, nullptr, "WGS 84", nullptr,
+ 0, false, 0, nullptr);
+ ASSERT_NE(res, nullptr);
+ ObjListKeeper keeper_res(res);
+ EXPECT_EQ(proj_obj_list_get_count(res), 4);
+ }
+ {
+ auto res = proj_obj_create_from_name(m_ctxt, "xx", "WGS 84", nullptr, 0,
+ false, 0, nullptr);
+ ASSERT_NE(res, nullptr);
+ ObjListKeeper keeper_res(res);
+ EXPECT_EQ(proj_obj_list_get_count(res), 0);
+ }
+ {
+ const PJ_OBJ_TYPE types[] = {PJ_OBJ_TYPE_GEODETIC_CRS,
+ PJ_OBJ_TYPE_PROJECTED_CRS};
+ auto res = proj_obj_create_from_name(m_ctxt, nullptr, "WGS 84", types,
+ 2, true, 10, nullptr);
+ ASSERT_NE(res, nullptr);
+ ObjListKeeper keeper_res(res);
+ EXPECT_EQ(proj_obj_list_get_count(res), 10);
+ }
+}
+
+// ---------------------------------------------------------------------------
+
+TEST_F(CApi, proj_obj_identify) {
+ auto obj = proj_obj_create_from_wkt(
+ m_ctxt,
+ GeographicCRS::EPSG_4807->exportToWKT(WKTFormatter::create().get())
+ .c_str(),
+ nullptr);
+ ObjectKeeper keeper(obj);
+ ASSERT_NE(obj, nullptr);
+ {
+ auto res = proj_obj_identify(obj, nullptr, nullptr, nullptr);
+ ObjListKeeper keeper_res(res);
+ EXPECT_EQ(proj_obj_list_get_count(res), 1);
+ }
+ {
+ int *confidence = nullptr;
+ auto res = proj_obj_identify(obj, "EPSG", nullptr, &confidence);
+ ObjListKeeper keeper_res(res);
+ EXPECT_EQ(proj_obj_list_get_count(res), 1);
+ EXPECT_EQ(confidence[0], 100);
+ proj_free_int_list(confidence);
+ }
+ {
+ auto objEllps = proj_obj_create_from_wkt(
+ m_ctxt,
+ Ellipsoid::GRS1980->exportToWKT(WKTFormatter::create().get())
+ .c_str(),
+ nullptr);
+ ObjectKeeper keeperEllps(objEllps);
+ auto res = proj_obj_identify(objEllps, nullptr, nullptr, nullptr);
+ ObjListKeeper keeper_res(res);
+ EXPECT_EQ(res, nullptr);
+ }
+}
+
+// ---------------------------------------------------------------------------
+
+TEST_F(CApi, proj_obj_get_area_of_use) {
+ {
+ auto crs = proj_obj_create_from_database(
+ m_ctxt, "EPSG", "4326", PJ_OBJ_CATEGORY_CRS, false, nullptr);
+ ASSERT_NE(crs, nullptr);
+ ObjectKeeper keeper(crs);
+ EXPECT_TRUE(proj_obj_get_area_of_use(crs, nullptr, nullptr, nullptr,
+ nullptr, nullptr));
+ const char *name = nullptr;
+ double w;
+ double s;
+ double e;
+ double n;
+ EXPECT_TRUE(proj_obj_get_area_of_use(crs, &w, &s, &e, &n, &name));
+ EXPECT_EQ(w, -180);
+ EXPECT_EQ(s, -90);
+ EXPECT_EQ(e, 180);
+ EXPECT_EQ(n, 90);
+ ASSERT_TRUE(name != nullptr);
+ EXPECT_EQ(std::string(name), "World");
+ }
+ {
+ auto obj =
+ proj_obj_create_from_user_input(m_ctxt, "+proj=longlat", nullptr);
+ ObjectKeeper keeper(obj);
+ ASSERT_NE(obj, nullptr);
+ EXPECT_FALSE(proj_obj_get_area_of_use(obj, nullptr, nullptr, nullptr,
+ nullptr, nullptr));
+ }
+}
+
+// ---------------------------------------------------------------------------
+
+TEST_F(CApi, proj_coordoperation_get_accuracy) {
+ {
+ auto crs = proj_obj_create_from_database(
+ m_ctxt, "EPSG", "4326", PJ_OBJ_CATEGORY_CRS, false, nullptr);
+ ASSERT_NE(crs, nullptr);
+ ObjectKeeper keeper(crs);
+ EXPECT_EQ(proj_coordoperation_get_accuracy(crs), -1.0);
+ }
+ {
+ auto obj = proj_obj_create_from_database(
+ m_ctxt, "EPSG", "1170", PJ_OBJ_CATEGORY_COORDINATE_OPERATION, false,
+ nullptr);
+ ASSERT_NE(obj, nullptr);
+ ObjectKeeper keeper(obj);
+ EXPECT_EQ(proj_coordoperation_get_accuracy(obj), 16.0);
+ }
+ {
+ auto obj =
+ proj_obj_create_from_user_input(m_ctxt, "+proj=helmert", nullptr);
+ ObjectKeeper keeper(obj);
+ ASSERT_NE(obj, nullptr);
+ EXPECT_EQ(proj_coordoperation_get_accuracy(obj), -1.0);
+ }
+}
+
+// ---------------------------------------------------------------------------
+
+TEST_F(CApi, proj_obj_create_geographic_crs) {
+ {
+ auto obj = proj_obj_create_geographic_crs(
+ m_ctxt, "WGS 84", "World Geodetic System 1984", "WGS 84", 6378137,
+ 298.257223563, "Greenwich", 0.0, "Degree", 0.0174532925199433,
+ true);
+ ObjectKeeper keeper(obj);
+ ASSERT_NE(obj, nullptr);
+
+ auto objRef = proj_obj_create_from_user_input(
+ m_ctxt,
+ GeographicCRS::EPSG_4326->exportToWKT(WKTFormatter::create().get())
+ .c_str(),
+ nullptr);
+ ObjectKeeper keeperobjRef(objRef);
+ EXPECT_NE(objRef, nullptr);
+
+ EXPECT_TRUE(proj_obj_is_equivalent_to(obj, objRef, PJ_COMP_EQUIVALENT));
+ }
+ {
+ auto obj = proj_obj_create_geographic_crs(m_ctxt, nullptr, nullptr,
+ nullptr, 1.0, 0.0, nullptr,
+ 0.0, nullptr, 0.0, false);
+ ObjectKeeper keeper(obj);
+ ASSERT_NE(obj, nullptr);
+ }
+}
+
+// ---------------------------------------------------------------------------
+
+TEST_F(CApi, proj_obj_create_projections) {
+
+ auto geogCRS = proj_obj_create_geographic_crs(
+ m_ctxt, "WGS 84", "World Geodetic System 1984", "WGS 84", 6378137,
+ 298.257223563, "Greenwich", 0.0, "Degree", 0.0174532925199433, true);
+ ObjectKeeper keepergeogCRS(geogCRS);
+ ASSERT_NE(geogCRS, nullptr);
+
+ /* BEGIN: Generated by scripts/create_c_api_projections.py*/
+ {
+ auto projCRS =
+ proj_obj_create_projected_crs_UTM(geogCRS, nullptr, 0, 0);
+ ObjectKeeper keeper_projCRS(projCRS);
+ ASSERT_NE(projCRS, nullptr);
+ }
+ {
+ auto projCRS = proj_obj_create_projected_crs_TransverseMercator(
+ geogCRS, nullptr, 0, 0, 0, 0, 0, "Degree", 0.0174532925199433,
+ "Metre", 1.0);
+ ObjectKeeper keeper_projCRS(projCRS);
+ ASSERT_NE(projCRS, nullptr);
+ }
+ {
+ auto projCRS =
+ proj_obj_create_projected_crs_GaussSchreiberTransverseMercator(
+ geogCRS, nullptr, 0, 0, 0, 0, 0, "Degree", 0.0174532925199433,
+ "Metre", 1.0);
+ ObjectKeeper keeper_projCRS(projCRS);
+ ASSERT_NE(projCRS, nullptr);
+ }
+ {
+ auto projCRS =
+ proj_obj_create_projected_crs_TransverseMercatorSouthOriented(
+ geogCRS, nullptr, 0, 0, 0, 0, 0, "Degree", 0.0174532925199433,
+ "Metre", 1.0);
+ ObjectKeeper keeper_projCRS(projCRS);
+ ASSERT_NE(projCRS, nullptr);
+ }
+ {
+ auto projCRS = proj_obj_create_projected_crs_TwoPointEquidistant(
+ geogCRS, nullptr, 0, 0, 0, 0, 0, 0, "Degree", 0.0174532925199433,
+ "Metre", 1.0);
+ ObjectKeeper keeper_projCRS(projCRS);
+ ASSERT_NE(projCRS, nullptr);
+ }
+ {
+ auto projCRS = proj_obj_create_projected_crs_TunisiaMappingGrid(
+ geogCRS, nullptr, 0, 0, 0, 0, "Degree", 0.0174532925199433, "Metre",
+ 1.0);
+ ObjectKeeper keeper_projCRS(projCRS);
+ ASSERT_NE(projCRS, nullptr);
+ }
+ {
+ auto projCRS = proj_obj_create_projected_crs_AlbersEqualArea(
+ geogCRS, nullptr, 0, 0, 0, 0, 0, 0, "Degree", 0.0174532925199433,
+ "Metre", 1.0);
+ ObjectKeeper keeper_projCRS(projCRS);
+ ASSERT_NE(projCRS, nullptr);
+ }
+ {
+ auto projCRS = proj_obj_create_projected_crs_LambertConicConformal_1SP(
+ geogCRS, nullptr, 0, 0, 0, 0, 0, "Degree", 0.0174532925199433,
+ "Metre", 1.0);
+ ObjectKeeper keeper_projCRS(projCRS);
+ ASSERT_NE(projCRS, nullptr);
+ }
+ {
+ auto projCRS = proj_obj_create_projected_crs_LambertConicConformal_2SP(
+ geogCRS, nullptr, 0, 0, 0, 0, 0, 0, "Degree", 0.0174532925199433,
+ "Metre", 1.0);
+ ObjectKeeper keeper_projCRS(projCRS);
+ ASSERT_NE(projCRS, nullptr);
+ }
+ {
+ auto projCRS =
+ proj_obj_create_projected_crs_LambertConicConformal_2SP_Michigan(
+ geogCRS, nullptr, 0, 0, 0, 0, 0, 0, 0, "Degree",
+ 0.0174532925199433, "Metre", 1.0);
+ ObjectKeeper keeper_projCRS(projCRS);
+ ASSERT_NE(projCRS, nullptr);
+ }
+ {
+ auto projCRS =
+ proj_obj_create_projected_crs_LambertConicConformal_2SP_Belgium(
+ geogCRS, nullptr, 0, 0, 0, 0, 0, 0, "Degree",
+ 0.0174532925199433, "Metre", 1.0);
+ ObjectKeeper keeper_projCRS(projCRS);
+ ASSERT_NE(projCRS, nullptr);
+ }
+ {
+ auto projCRS = proj_obj_create_projected_crs_AzimuthalEquidistant(
+ geogCRS, nullptr, 0, 0, 0, 0, "Degree", 0.0174532925199433, "Metre",
+ 1.0);
+ ObjectKeeper keeper_projCRS(projCRS);
+ ASSERT_NE(projCRS, nullptr);
+ }
+ {
+ auto projCRS = proj_obj_create_projected_crs_GuamProjection(
+ geogCRS, nullptr, 0, 0, 0, 0, "Degree", 0.0174532925199433, "Metre",
+ 1.0);
+ ObjectKeeper keeper_projCRS(projCRS);
+ ASSERT_NE(projCRS, nullptr);
+ }
+ {
+ auto projCRS = proj_obj_create_projected_crs_Bonne(
+ geogCRS, nullptr, 0, 0, 0, 0, "Degree", 0.0174532925199433, "Metre",
+ 1.0);
+ ObjectKeeper keeper_projCRS(projCRS);
+ ASSERT_NE(projCRS, nullptr);
+ }
+ {
+ auto projCRS =
+ proj_obj_create_projected_crs_LambertCylindricalEqualAreaSpherical(
+ geogCRS, nullptr, 0, 0, 0, 0, "Degree", 0.0174532925199433,
+ "Metre", 1.0);
+ ObjectKeeper keeper_projCRS(projCRS);
+ ASSERT_NE(projCRS, nullptr);
+ }
+ {
+ auto projCRS =
+ proj_obj_create_projected_crs_LambertCylindricalEqualArea(
+ geogCRS, nullptr, 0, 0, 0, 0, "Degree", 0.0174532925199433,
+ "Metre", 1.0);
+ ObjectKeeper keeper_projCRS(projCRS);
+ ASSERT_NE(projCRS, nullptr);
+ }
+ {
+ auto projCRS = proj_obj_create_projected_crs_CassiniSoldner(
+ geogCRS, nullptr, 0, 0, 0, 0, "Degree", 0.0174532925199433, "Metre",
+ 1.0);
+ ObjectKeeper keeper_projCRS(projCRS);
+ ASSERT_NE(projCRS, nullptr);
+ }
+ {
+ auto projCRS = proj_obj_create_projected_crs_EquidistantConic(
+ geogCRS, nullptr, 0, 0, 0, 0, 0, 0, "Degree", 0.0174532925199433,
+ "Metre", 1.0);
+ ObjectKeeper keeper_projCRS(projCRS);
+ ASSERT_NE(projCRS, nullptr);
+ }
+ {
+ auto projCRS = proj_obj_create_projected_crs_EckertI(
+ geogCRS, nullptr, 0, 0, 0, "Degree", 0.0174532925199433, "Metre",
+ 1.0);
+ ObjectKeeper keeper_projCRS(projCRS);
+ ASSERT_NE(projCRS, nullptr);
+ }
+ {
+ auto projCRS = proj_obj_create_projected_crs_EckertII(
+ geogCRS, nullptr, 0, 0, 0, "Degree", 0.0174532925199433, "Metre",
+ 1.0);
+ ObjectKeeper keeper_projCRS(projCRS);
+ ASSERT_NE(projCRS, nullptr);
+ }
+ {
+ auto projCRS = proj_obj_create_projected_crs_EckertIII(
+ geogCRS, nullptr, 0, 0, 0, "Degree", 0.0174532925199433, "Metre",
+ 1.0);
+ ObjectKeeper keeper_projCRS(projCRS);
+ ASSERT_NE(projCRS, nullptr);
+ }
+ {
+ auto projCRS = proj_obj_create_projected_crs_EckertIV(
+ geogCRS, nullptr, 0, 0, 0, "Degree", 0.0174532925199433, "Metre",
+ 1.0);
+ ObjectKeeper keeper_projCRS(projCRS);
+ ASSERT_NE(projCRS, nullptr);
+ }
+ {
+ auto projCRS = proj_obj_create_projected_crs_EckertV(
+ geogCRS, nullptr, 0, 0, 0, "Degree", 0.0174532925199433, "Metre",
+ 1.0);
+ ObjectKeeper keeper_projCRS(projCRS);
+ ASSERT_NE(projCRS, nullptr);
+ }
+ {
+ auto projCRS = proj_obj_create_projected_crs_EckertVI(
+ geogCRS, nullptr, 0, 0, 0, "Degree", 0.0174532925199433, "Metre",
+ 1.0);
+ ObjectKeeper keeper_projCRS(projCRS);
+ ASSERT_NE(projCRS, nullptr);
+ }
+ {
+ auto projCRS = proj_obj_create_projected_crs_EquidistantCylindrical(
+ geogCRS, nullptr, 0, 0, 0, 0, "Degree", 0.0174532925199433, "Metre",
+ 1.0);
+ ObjectKeeper keeper_projCRS(projCRS);
+ ASSERT_NE(projCRS, nullptr);
+ }
+ {
+ auto projCRS =
+ proj_obj_create_projected_crs_EquidistantCylindricalSpherical(
+ geogCRS, nullptr, 0, 0, 0, 0, "Degree", 0.0174532925199433,
+ "Metre", 1.0);
+ ObjectKeeper keeper_projCRS(projCRS);
+ ASSERT_NE(projCRS, nullptr);
+ }
+ {
+ auto projCRS = proj_obj_create_projected_crs_Gall(
+ geogCRS, nullptr, 0, 0, 0, "Degree", 0.0174532925199433, "Metre",
+ 1.0);
+ ObjectKeeper keeper_projCRS(projCRS);
+ ASSERT_NE(projCRS, nullptr);
+ }
+ {
+ auto projCRS = proj_obj_create_projected_crs_GoodeHomolosine(
+ geogCRS, nullptr, 0, 0, 0, "Degree", 0.0174532925199433, "Metre",
+ 1.0);
+ ObjectKeeper keeper_projCRS(projCRS);
+ ASSERT_NE(projCRS, nullptr);
+ }
+ {
+ auto projCRS = proj_obj_create_projected_crs_InterruptedGoodeHomolosine(
+ geogCRS, nullptr, 0, 0, 0, "Degree", 0.0174532925199433, "Metre",
+ 1.0);
+ ObjectKeeper keeper_projCRS(projCRS);
+ ASSERT_NE(projCRS, nullptr);
+ }
+ {
+ auto projCRS =
+ proj_obj_create_projected_crs_GeostationarySatelliteSweepX(
+ geogCRS, nullptr, 0, 0, 0, 0, "Degree", 0.0174532925199433,
+ "Metre", 1.0);
+ ObjectKeeper keeper_projCRS(projCRS);
+ ASSERT_NE(projCRS, nullptr);
+ }
+ {
+ auto projCRS =
+ proj_obj_create_projected_crs_GeostationarySatelliteSweepY(
+ geogCRS, nullptr, 0, 0, 0, 0, "Degree", 0.0174532925199433,
+ "Metre", 1.0);
+ ObjectKeeper keeper_projCRS(projCRS);
+ ASSERT_NE(projCRS, nullptr);
+ }
+ {
+ auto projCRS = proj_obj_create_projected_crs_Gnomonic(
+ geogCRS, nullptr, 0, 0, 0, 0, "Degree", 0.0174532925199433, "Metre",
+ 1.0);
+ ObjectKeeper keeper_projCRS(projCRS);
+ ASSERT_NE(projCRS, nullptr);
+ }
+ {
+ auto projCRS =
+ proj_obj_create_projected_crs_HotineObliqueMercatorVariantA(
+ geogCRS, nullptr, 0, 0, 0, 0, 0, 0, 0, "Degree",
+ 0.0174532925199433, "Metre", 1.0);
+ ObjectKeeper keeper_projCRS(projCRS);
+ ASSERT_NE(projCRS, nullptr);
+ }
+ {
+ auto projCRS =
+ proj_obj_create_projected_crs_HotineObliqueMercatorVariantB(
+ geogCRS, nullptr, 0, 0, 0, 0, 0, 0, 0, "Degree",
+ 0.0174532925199433, "Metre", 1.0);
+ ObjectKeeper keeper_projCRS(projCRS);
+ ASSERT_NE(projCRS, nullptr);
+ }
+ {
+ auto projCRS =
+ proj_obj_create_projected_crs_HotineObliqueMercatorTwoPointNaturalOrigin(
+ geogCRS, nullptr, 0, 0, 0, 0, 0, 0, 0, 0, "Degree",
+ 0.0174532925199433, "Metre", 1.0);
+ ObjectKeeper keeper_projCRS(projCRS);
+ ASSERT_NE(projCRS, nullptr);
+ }
+ {
+ auto projCRS =
+ proj_obj_create_projected_crs_InternationalMapWorldPolyconic(
+ geogCRS, nullptr, 0, 0, 0, 0, 0, "Degree", 0.0174532925199433,
+ "Metre", 1.0);
+ ObjectKeeper keeper_projCRS(projCRS);
+ ASSERT_NE(projCRS, nullptr);
+ }
+ {
+ auto projCRS = proj_obj_create_projected_crs_KrovakNorthOriented(
+ geogCRS, nullptr, 0, 0, 0, 0, 0, 0, 0, "Degree", 0.0174532925199433,
+ "Metre", 1.0);
+ ObjectKeeper keeper_projCRS(projCRS);
+ ASSERT_NE(projCRS, nullptr);
+ }
+ {
+ auto projCRS = proj_obj_create_projected_crs_Krovak(
+ geogCRS, nullptr, 0, 0, 0, 0, 0, 0, 0, "Degree", 0.0174532925199433,
+ "Metre", 1.0);
+ ObjectKeeper keeper_projCRS(projCRS);
+ ASSERT_NE(projCRS, nullptr);
+ }
+ {
+ auto projCRS = proj_obj_create_projected_crs_LambertAzimuthalEqualArea(
+ geogCRS, nullptr, 0, 0, 0, 0, "Degree", 0.0174532925199433, "Metre",
+ 1.0);
+ ObjectKeeper keeper_projCRS(projCRS);
+ ASSERT_NE(projCRS, nullptr);
+ }
+ {
+ auto projCRS = proj_obj_create_projected_crs_MillerCylindrical(
+ geogCRS, nullptr, 0, 0, 0, "Degree", 0.0174532925199433, "Metre",
+ 1.0);
+ ObjectKeeper keeper_projCRS(projCRS);
+ ASSERT_NE(projCRS, nullptr);
+ }
+ {
+ auto projCRS = proj_obj_create_projected_crs_MercatorVariantA(
+ geogCRS, nullptr, 0, 0, 0, 0, 0, "Degree", 0.0174532925199433,
+ "Metre", 1.0);
+ ObjectKeeper keeper_projCRS(projCRS);
+ ASSERT_NE(projCRS, nullptr);
+ }
+ {
+ auto projCRS = proj_obj_create_projected_crs_MercatorVariantB(
+ geogCRS, nullptr, 0, 0, 0, 0, "Degree", 0.0174532925199433, "Metre",
+ 1.0);
+ ObjectKeeper keeper_projCRS(projCRS);
+ ASSERT_NE(projCRS, nullptr);
+ }
+ {
+ auto projCRS =
+ proj_obj_create_projected_crs_PopularVisualisationPseudoMercator(
+ geogCRS, nullptr, 0, 0, 0, 0, "Degree", 0.0174532925199433,
+ "Metre", 1.0);
+ ObjectKeeper keeper_projCRS(projCRS);
+ ASSERT_NE(projCRS, nullptr);
+ }
+ {
+ auto projCRS = proj_obj_create_projected_crs_Mollweide(
+ geogCRS, nullptr, 0, 0, 0, "Degree", 0.0174532925199433, "Metre",
+ 1.0);
+ ObjectKeeper keeper_projCRS(projCRS);
+ ASSERT_NE(projCRS, nullptr);
+ }
+ {
+ auto projCRS = proj_obj_create_projected_crs_NewZealandMappingGrid(
+ geogCRS, nullptr, 0, 0, 0, 0, "Degree", 0.0174532925199433, "Metre",
+ 1.0);
+ ObjectKeeper keeper_projCRS(projCRS);
+ ASSERT_NE(projCRS, nullptr);
+ }
+ {
+ auto projCRS = proj_obj_create_projected_crs_ObliqueStereographic(
+ geogCRS, nullptr, 0, 0, 0, 0, 0, "Degree", 0.0174532925199433,
+ "Metre", 1.0);
+ ObjectKeeper keeper_projCRS(projCRS);
+ ASSERT_NE(projCRS, nullptr);
+ }
+ {
+ auto projCRS = proj_obj_create_projected_crs_Orthographic(
+ geogCRS, nullptr, 0, 0, 0, 0, "Degree", 0.0174532925199433, "Metre",
+ 1.0);
+ ObjectKeeper keeper_projCRS(projCRS);
+ ASSERT_NE(projCRS, nullptr);
+ }
+ {
+ auto projCRS = proj_obj_create_projected_crs_AmericanPolyconic(
+ geogCRS, nullptr, 0, 0, 0, 0, "Degree", 0.0174532925199433, "Metre",
+ 1.0);
+ ObjectKeeper keeper_projCRS(projCRS);
+ ASSERT_NE(projCRS, nullptr);
+ }
+ {
+ auto projCRS = proj_obj_create_projected_crs_PolarStereographicVariantA(
+ geogCRS, nullptr, 0, 0, 0, 0, 0, "Degree", 0.0174532925199433,
+ "Metre", 1.0);
+ ObjectKeeper keeper_projCRS(projCRS);
+ ASSERT_NE(projCRS, nullptr);
+ }
+ {
+ auto projCRS = proj_obj_create_projected_crs_PolarStereographicVariantB(
+ geogCRS, nullptr, 0, 0, 0, 0, "Degree", 0.0174532925199433, "Metre",
+ 1.0);
+ ObjectKeeper keeper_projCRS(projCRS);
+ ASSERT_NE(projCRS, nullptr);
+ }
+ {
+ auto projCRS = proj_obj_create_projected_crs_Robinson(
+ geogCRS, nullptr, 0, 0, 0, "Degree", 0.0174532925199433, "Metre",
+ 1.0);
+ ObjectKeeper keeper_projCRS(projCRS);
+ ASSERT_NE(projCRS, nullptr);
+ }
+ {
+ auto projCRS = proj_obj_create_projected_crs_Sinusoidal(
+ geogCRS, nullptr, 0, 0, 0, "Degree", 0.0174532925199433, "Metre",
+ 1.0);
+ ObjectKeeper keeper_projCRS(projCRS);
+ ASSERT_NE(projCRS, nullptr);
+ }
+ {
+ auto projCRS = proj_obj_create_projected_crs_Stereographic(
+ geogCRS, nullptr, 0, 0, 0, 0, 0, "Degree", 0.0174532925199433,
+ "Metre", 1.0);
+ ObjectKeeper keeper_projCRS(projCRS);
+ ASSERT_NE(projCRS, nullptr);
+ }
+ {
+ auto projCRS = proj_obj_create_projected_crs_VanDerGrinten(
+ geogCRS, nullptr, 0, 0, 0, "Degree", 0.0174532925199433, "Metre",
+ 1.0);
+ ObjectKeeper keeper_projCRS(projCRS);
+ ASSERT_NE(projCRS, nullptr);
+ }
+ {
+ auto projCRS = proj_obj_create_projected_crs_WagnerI(
+ geogCRS, nullptr, 0, 0, 0, "Degree", 0.0174532925199433, "Metre",
+ 1.0);
+ ObjectKeeper keeper_projCRS(projCRS);
+ ASSERT_NE(projCRS, nullptr);
+ }
+ {
+ auto projCRS = proj_obj_create_projected_crs_WagnerII(
+ geogCRS, nullptr, 0, 0, 0, "Degree", 0.0174532925199433, "Metre",
+ 1.0);
+ ObjectKeeper keeper_projCRS(projCRS);
+ ASSERT_NE(projCRS, nullptr);
+ }
+ {
+ auto projCRS = proj_obj_create_projected_crs_WagnerIII(
+ geogCRS, nullptr, 0, 0, 0, 0, "Degree", 0.0174532925199433, "Metre",
+ 1.0);
+ ObjectKeeper keeper_projCRS(projCRS);
+ ASSERT_NE(projCRS, nullptr);
+ }
+ {
+ auto projCRS = proj_obj_create_projected_crs_WagnerIV(
+ geogCRS, nullptr, 0, 0, 0, "Degree", 0.0174532925199433, "Metre",
+ 1.0);
+ ObjectKeeper keeper_projCRS(projCRS);
+ ASSERT_NE(projCRS, nullptr);
+ }
+ {
+ auto projCRS = proj_obj_create_projected_crs_WagnerV(
+ geogCRS, nullptr, 0, 0, 0, "Degree", 0.0174532925199433, "Metre",
+ 1.0);
+ ObjectKeeper keeper_projCRS(projCRS);
+ ASSERT_NE(projCRS, nullptr);
+ }
+ {
+ auto projCRS = proj_obj_create_projected_crs_WagnerVI(
+ geogCRS, nullptr, 0, 0, 0, "Degree", 0.0174532925199433, "Metre",
+ 1.0);
+ ObjectKeeper keeper_projCRS(projCRS);
+ ASSERT_NE(projCRS, nullptr);
+ }
+ {
+ auto projCRS = proj_obj_create_projected_crs_WagnerVII(
+ geogCRS, nullptr, 0, 0, 0, "Degree", 0.0174532925199433, "Metre",
+ 1.0);
+ ObjectKeeper keeper_projCRS(projCRS);
+ ASSERT_NE(projCRS, nullptr);
+ }
+ {
+ auto projCRS =
+ proj_obj_create_projected_crs_QuadrilateralizedSphericalCube(
+ geogCRS, nullptr, 0, 0, 0, 0, "Degree", 0.0174532925199433,
+ "Metre", 1.0);
+ ObjectKeeper keeper_projCRS(projCRS);
+ ASSERT_NE(projCRS, nullptr);
+ }
+ {
+ auto projCRS = proj_obj_create_projected_crs_SphericalCrossTrackHeight(
+ geogCRS, nullptr, 0, 0, 0, 0, "Degree", 0.0174532925199433, "Metre",
+ 1.0);
+ ObjectKeeper keeper_projCRS(projCRS);
+ ASSERT_NE(projCRS, nullptr);
+ }
+ {
+ auto projCRS = proj_obj_create_projected_crs_EqualEarth(
+ geogCRS, nullptr, 0, 0, 0, "Degree", 0.0174532925199433, "Metre",
+ 1.0);
+ ObjectKeeper keeper_projCRS(projCRS);
+ ASSERT_NE(projCRS, nullptr);
+ }
+ /* END: Generated by scripts/create_c_api_projections.py*/
+}
+
+} // namespace
diff --git a/test/unit/test_common.cpp b/test/unit/test_common.cpp
new file mode 100644
index 00000000..4de4654d
--- /dev/null
+++ b/test/unit/test_common.cpp
@@ -0,0 +1,191 @@
+/******************************************************************************
+ *
+ * Project: PROJ
+ * Purpose: Test ISO19111:2018 implementation
+ * Author: Even Rouault <even dot rouault at spatialys dot com>
+ *
+ ******************************************************************************
+ * Copyright (c) 2018, Even Rouault <even dot rouault at spatialys dot 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.
+ ****************************************************************************/
+
+#include "gtest_include.h"
+
+#include "proj/common.hpp"
+#include "proj/coordinateoperation.hpp"
+#include "proj/metadata.hpp"
+#include "proj/util.hpp"
+
+using namespace osgeo::proj::common;
+using namespace osgeo::proj::metadata;
+using namespace osgeo::proj::operation;
+using namespace osgeo::proj::util;
+
+// ---------------------------------------------------------------------------
+
+TEST(common, unit_of_measure) {
+ EXPECT_EQ(UnitOfMeasure::METRE.name(), "metre");
+
+ EXPECT_EQ(UnitOfMeasure::METRE.conversionToSI(), 1.0);
+ EXPECT_EQ(UnitOfMeasure::METRE.type(), UnitOfMeasure::Type::LINEAR);
+
+ EXPECT_EQ(UnitOfMeasure::DEGREE.name(), "degree");
+ EXPECT_EQ(UnitOfMeasure::DEGREE.conversionToSI(), 0.017453292519943295);
+ EXPECT_EQ(UnitOfMeasure::DEGREE.type(), UnitOfMeasure::Type::ANGULAR);
+
+ EXPECT_EQ(UnitOfMeasure::RADIAN.name(), "radian");
+ EXPECT_EQ(UnitOfMeasure::RADIAN.conversionToSI(), 1.0);
+ EXPECT_EQ(UnitOfMeasure::RADIAN.type(), UnitOfMeasure::Type::ANGULAR);
+
+ EXPECT_EQ(Length(2.0, UnitOfMeasure("km", 1000.0))
+ .convertToUnit(UnitOfMeasure::METRE),
+ 2000.0);
+
+ EXPECT_EQ(
+ Angle(2.0, UnitOfMeasure::DEGREE).convertToUnit(UnitOfMeasure::RADIAN),
+ 2 * 0.017453292519943295);
+
+ EXPECT_EQ(Angle(2.5969213, UnitOfMeasure::GRAD)
+ .convertToUnit(UnitOfMeasure::DEGREE),
+ 2.5969213 / 100.0 * 90.0);
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(common, measure) { EXPECT_TRUE(Measure(1.0) == Measure(1.0)); }
+
+// ---------------------------------------------------------------------------
+
+TEST(common, identifiedobject_empty) {
+ PropertyMap properties;
+ auto obj = OperationParameter::create(properties);
+ EXPECT_TRUE(obj->name()->code().empty());
+ EXPECT_TRUE(obj->identifiers().empty());
+ EXPECT_TRUE(obj->aliases().empty());
+ EXPECT_TRUE(obj->remarks().empty());
+ EXPECT_TRUE(!obj->isDeprecated());
+ EXPECT_TRUE(obj->alias().empty());
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(common, identifiedobject) {
+ PropertyMap properties;
+ properties.set(IdentifiedObject::NAME_KEY, "name");
+ properties.set(IdentifiedObject::IDENTIFIERS_KEY,
+ Identifier::create("identifier_code"));
+ properties.set(IdentifiedObject::ALIAS_KEY, "alias");
+ properties.set(IdentifiedObject::REMARKS_KEY, "remarks");
+ properties.set(IdentifiedObject::DEPRECATED_KEY, true);
+ auto obj = OperationParameter::create(properties);
+ EXPECT_EQ(*(obj->name()->description()), "name");
+ ASSERT_EQ(obj->identifiers().size(), 1);
+ EXPECT_EQ(obj->identifiers()[0]->code(), "identifier_code");
+ ASSERT_EQ(obj->aliases().size(), 1);
+ EXPECT_EQ(obj->aliases()[0]->toString(), "alias");
+ EXPECT_EQ(obj->remarks(), "remarks");
+ EXPECT_TRUE(obj->isDeprecated());
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(common, identifiedobject_name_invalid_type_integer) {
+ PropertyMap properties;
+ properties.set(IdentifiedObject::NAME_KEY, 123);
+ ASSERT_THROW(OperationParameter::create(properties),
+ InvalidValueTypeException);
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(common, identifiedobject_name_invalid_type_citation) {
+ PropertyMap properties;
+ properties.set(IdentifiedObject::NAME_KEY,
+ nn_make_shared<Citation>("invalid"));
+ ASSERT_THROW(OperationParameter::create(properties),
+ InvalidValueTypeException);
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(common, identifiedobject_identifier_invalid_type) {
+ PropertyMap properties;
+ properties.set(IdentifiedObject::IDENTIFIERS_KEY, "string not allowed");
+ ASSERT_THROW(OperationParameter::create(properties),
+ InvalidValueTypeException);
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(common, identifiedobject_identifier_array_of_identifier) {
+ PropertyMap properties;
+ auto array = ArrayOfBaseObject::create();
+ array->add(Identifier::create("identifier_code1"));
+ array->add(Identifier::create("identifier_code2"));
+ properties.set(IdentifiedObject::IDENTIFIERS_KEY, array);
+ auto obj = OperationParameter::create(properties);
+ ASSERT_EQ(obj->identifiers().size(), 2);
+ EXPECT_EQ(obj->identifiers()[0]->code(), "identifier_code1");
+ EXPECT_EQ(obj->identifiers()[1]->code(), "identifier_code2");
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(common, identifiedobject_identifier_array_of_invalid_type) {
+ PropertyMap properties;
+ auto array = ArrayOfBaseObject::create();
+ array->add(nn_make_shared<Citation>("unexpected type"));
+ properties.set(IdentifiedObject::IDENTIFIERS_KEY, array);
+ ASSERT_THROW(OperationParameter::create(properties),
+ InvalidValueTypeException);
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(common, identifiedobject_alias_array_of_string) {
+ PropertyMap properties;
+ properties.set(IdentifiedObject::ALIAS_KEY,
+ std::vector<std::string>{"alias1", "alias2"});
+ auto obj = OperationParameter::create(properties);
+ ASSERT_EQ(obj->aliases().size(), 2);
+ EXPECT_EQ(obj->aliases()[0]->toString(), "alias1");
+ EXPECT_EQ(obj->aliases()[1]->toString(), "alias2");
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(common, identifiedobject_alias_invalid_type) {
+ PropertyMap properties;
+ properties.set(IdentifiedObject::ALIAS_KEY,
+ nn_make_shared<Citation>("unexpected type"));
+ ASSERT_THROW(OperationParameter::create(properties),
+ InvalidValueTypeException);
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(common, identifiedobject_alias_array_of_invalid_type) {
+ PropertyMap properties;
+ auto array = ArrayOfBaseObject::create();
+ array->add(nn_make_shared<Citation>("unexpected type"));
+ properties.set(IdentifiedObject::ALIAS_KEY, array);
+ ASSERT_THROW(OperationParameter::create(properties),
+ InvalidValueTypeException);
+}
diff --git a/test/unit/test_crs.cpp b/test/unit/test_crs.cpp
new file mode 100644
index 00000000..43698252
--- /dev/null
+++ b/test/unit/test_crs.cpp
@@ -0,0 +1,4647 @@
+/******************************************************************************
+ *
+ * Project: PROJ
+ * Purpose: Test ISO19111:2018 implementation
+ * Author: Even Rouault <even dot rouault at spatialys dot com>
+ *
+ ******************************************************************************
+ * Copyright (c) 2018, Even Rouault <even dot rouault at spatialys dot 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.
+ ****************************************************************************/
+
+#include "gtest_include.h"
+
+// to be able to use internal::replaceAll
+#define FROM_PROJ_CPP
+
+#include "proj/common.hpp"
+#include "proj/coordinateoperation.hpp"
+#include "proj/coordinatesystem.hpp"
+#include "proj/crs.hpp"
+#include "proj/datum.hpp"
+#include "proj/io.hpp"
+#include "proj/metadata.hpp"
+#include "proj/util.hpp"
+
+#include "proj/internal/internal.hpp"
+#include "proj/internal/io_internal.hpp"
+
+using namespace osgeo::proj::common;
+using namespace osgeo::proj::crs;
+using namespace osgeo::proj::cs;
+using namespace osgeo::proj::datum;
+using namespace osgeo::proj::io;
+using namespace osgeo::proj::internal;
+using namespace osgeo::proj::metadata;
+using namespace osgeo::proj::operation;
+using namespace osgeo::proj::util;
+
+namespace {
+struct UnrelatedObject : public IComparable {
+ UnrelatedObject() = default;
+
+ bool _isEquivalentTo(const IComparable *, Criterion) const override {
+ assert(false);
+ return false;
+ }
+};
+
+static nn<std::shared_ptr<UnrelatedObject>> createUnrelatedObject() {
+ return nn_make_shared<UnrelatedObject>();
+}
+} // namespace
+
+// ---------------------------------------------------------------------------
+
+TEST(crs, EPSG_4326_get_components) {
+ auto crs = GeographicCRS::EPSG_4326;
+ ASSERT_EQ(crs->identifiers().size(), 1);
+ EXPECT_EQ(crs->identifiers()[0]->code(), "4326");
+ EXPECT_EQ(*(crs->identifiers()[0]->codeSpace()), "EPSG");
+ EXPECT_EQ(crs->nameStr(), "WGS 84");
+
+ auto datum = crs->datum();
+ ASSERT_EQ(datum->identifiers().size(), 1);
+ EXPECT_EQ(datum->identifiers()[0]->code(), "6326");
+ EXPECT_EQ(*(datum->identifiers()[0]->codeSpace()), "EPSG");
+ EXPECT_EQ(datum->nameStr(), "World Geodetic System 1984");
+
+ auto ellipsoid = datum->ellipsoid();
+ EXPECT_EQ(ellipsoid->semiMajorAxis().value(), 6378137.0);
+ EXPECT_EQ(ellipsoid->semiMajorAxis().unit(), UnitOfMeasure::METRE);
+ EXPECT_EQ(ellipsoid->inverseFlattening()->value(), 298.257223563);
+ ASSERT_EQ(ellipsoid->identifiers().size(), 1);
+ EXPECT_EQ(ellipsoid->identifiers()[0]->code(), "7030");
+ EXPECT_EQ(*(ellipsoid->identifiers()[0]->codeSpace()), "EPSG");
+ EXPECT_EQ(ellipsoid->nameStr(), "WGS 84");
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(crs, GeographicCRS_isEquivalentTo) {
+ auto crs = GeographicCRS::EPSG_4326;
+ EXPECT_TRUE(crs->isEquivalentTo(crs.get()));
+ EXPECT_TRUE(
+ crs->isEquivalentTo(crs.get(), IComparable::Criterion::EQUIVALENT));
+ EXPECT_TRUE(crs->isEquivalentTo(
+ crs.get(),
+ IComparable::Criterion::EQUIVALENT_EXCEPT_AXIS_ORDER_GEOGCRS));
+ EXPECT_TRUE(crs->shallowClone()->isEquivalentTo(crs.get()));
+
+ EXPECT_FALSE(crs->isEquivalentTo(createUnrelatedObject().get()));
+ EXPECT_FALSE(crs->isEquivalentTo(GeographicCRS::EPSG_4979.get()));
+ EXPECT_FALSE(crs->isEquivalentTo(GeographicCRS::EPSG_4979.get(),
+ IComparable::Criterion::EQUIVALENT));
+
+ EXPECT_FALSE(crs->isEquivalentTo(GeographicCRS::OGC_CRS84.get(),
+ IComparable::Criterion::EQUIVALENT));
+ EXPECT_TRUE(crs->isEquivalentTo(
+ GeographicCRS::OGC_CRS84.get(),
+ IComparable::Criterion::EQUIVALENT_EXCEPT_AXIS_ORDER_GEOGCRS));
+ EXPECT_TRUE(GeographicCRS::OGC_CRS84->isEquivalentTo(
+ crs.get(),
+ IComparable::Criterion::EQUIVALENT_EXCEPT_AXIS_ORDER_GEOGCRS));
+
+ EXPECT_FALSE(
+ GeographicCRS::create(
+ PropertyMap(), GeodeticReferenceFrame::EPSG_6326,
+ EllipsoidalCS::createLatitudeLongitude(UnitOfMeasure::DEGREE))
+ ->isEquivalentTo(crs.get()));
+
+ EXPECT_FALSE(
+ GeographicCRS::create(
+ PropertyMap(), GeodeticReferenceFrame::EPSG_6326,
+ EllipsoidalCS::createLatitudeLongitude(UnitOfMeasure::DEGREE))
+ ->isEquivalentTo(
+ GeographicCRS::create(PropertyMap(),
+ GeodeticReferenceFrame::create(
+ PropertyMap(), Ellipsoid::WGS84,
+ optional<std::string>(),
+ PrimeMeridian::GREENWICH),
+ EllipsoidalCS::createLatitudeLongitude(
+ UnitOfMeasure::DEGREE))
+ .get()));
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(crs, GeographicCRS_datum_ensemble) {
+ auto ensemble_vdatum = DatumEnsemble::create(
+ PropertyMap(),
+ std::vector<DatumNNPtr>{GeodeticReferenceFrame::EPSG_6326,
+ GeodeticReferenceFrame::EPSG_6326},
+ PositionalAccuracy::create("100"));
+ auto crs = GeographicCRS::create(
+ PropertyMap().set(IdentifiedObject::NAME_KEY, "unnamed"), nullptr,
+ ensemble_vdatum,
+ EllipsoidalCS::createLatitudeLongitude(UnitOfMeasure::DEGREE));
+ WKTFormatterNNPtr f(
+ WKTFormatter::create(WKTFormatter::Convention::WKT2_2018));
+ f->simulCurNodeHasId();
+ crs->exportToWKT(f.get());
+ auto expected = "GEOGCRS[\"unnamed\",\n"
+ " ENSEMBLE[\"unnamed\",\n"
+ " MEMBER[\"World Geodetic System 1984\"],\n"
+ " MEMBER[\"World Geodetic System 1984\"],\n"
+ " ELLIPSOID[\"WGS 84\",6378137,298.257223563,\n"
+ " LENGTHUNIT[\"metre\",1]],\n"
+ " ENSEMBLEACCURACY[100]],\n"
+ " PRIMEM[\"Greenwich\",0,\n"
+ " ANGLEUNIT[\"degree\",0.0174532925199433]],\n"
+ " CS[ellipsoidal,2],\n"
+ " AXIS[\"latitude\",north,\n"
+ " ORDER[1],\n"
+ " ANGLEUNIT[\"degree\",0.0174532925199433]],\n"
+ " AXIS[\"longitude\",east,\n"
+ " ORDER[2],\n"
+ " ANGLEUNIT[\"degree\",0.0174532925199433]]]";
+
+ EXPECT_EQ(f->toString(), expected);
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(crs, GeographicCRS_ensemble_exception_in_create) {
+ EXPECT_THROW(GeographicCRS::create(PropertyMap(), nullptr, nullptr,
+ EllipsoidalCS::createLatitudeLongitude(
+ UnitOfMeasure::DEGREE)),
+ Exception);
+
+ auto ensemble_vdatum = DatumEnsemble::create(
+ PropertyMap(),
+ std::vector<DatumNNPtr>{
+ VerticalReferenceFrame::create(
+ PropertyMap().set(IdentifiedObject::NAME_KEY, "vdatum1")),
+ VerticalReferenceFrame::create(
+ PropertyMap().set(IdentifiedObject::NAME_KEY, "vdatum2"))},
+ PositionalAccuracy::create("100"));
+ EXPECT_THROW(GeographicCRS::create(PropertyMap(), nullptr, ensemble_vdatum,
+ EllipsoidalCS::createLatitudeLongitude(
+ UnitOfMeasure::DEGREE)),
+ Exception);
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(crs, EPSG_4326_as_WKT2) {
+ auto crs = GeographicCRS::EPSG_4326;
+ WKTFormatterNNPtr f(WKTFormatter::create());
+ crs->exportToWKT(f.get());
+ EXPECT_EQ(f->toString(),
+ "GEODCRS[\"WGS 84\",\n"
+ " DATUM[\"World Geodetic System 1984\",\n"
+ " ELLIPSOID[\"WGS 84\",6378137,298.257223563,\n"
+ " LENGTHUNIT[\"metre\",1]]],\n"
+ " PRIMEM[\"Greenwich\",0,\n"
+ " ANGLEUNIT[\"degree\",0.0174532925199433]],\n"
+ " CS[ellipsoidal,2],\n"
+ " AXIS[\"latitude\",north,\n"
+ " ORDER[1],\n"
+ " ANGLEUNIT[\"degree\",0.0174532925199433]],\n"
+ " AXIS[\"longitude\",east,\n"
+ " ORDER[2],\n"
+ " ANGLEUNIT[\"degree\",0.0174532925199433]],\n"
+ " ID[\"EPSG\",4326]]");
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(crs, EPSG_4326_as_WKT2_2018) {
+ auto crs = GeographicCRS::EPSG_4326;
+ WKTFormatterNNPtr f(
+ WKTFormatter::create(WKTFormatter::Convention::WKT2_2018));
+ crs->exportToWKT(f.get());
+ EXPECT_EQ(f->toString(),
+ "GEOGCRS[\"WGS 84\",\n"
+ " DATUM[\"World Geodetic System 1984\",\n"
+ " ELLIPSOID[\"WGS 84\",6378137,298.257223563,\n"
+ " LENGTHUNIT[\"metre\",1]]],\n"
+ " PRIMEM[\"Greenwich\",0,\n"
+ " ANGLEUNIT[\"degree\",0.0174532925199433]],\n"
+ " CS[ellipsoidal,2],\n"
+ " AXIS[\"latitude\",north,\n"
+ " ORDER[1],\n"
+ " ANGLEUNIT[\"degree\",0.0174532925199433]],\n"
+ " AXIS[\"longitude\",east,\n"
+ " ORDER[2],\n"
+ " ANGLEUNIT[\"degree\",0.0174532925199433]],\n"
+ " ID[\"EPSG\",4326]]");
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(crs, EPSG_4326_as_WKT2_SIMPLIFIED) {
+ auto crs = GeographicCRS::EPSG_4326;
+ WKTFormatterNNPtr f(
+ WKTFormatter::create(WKTFormatter::Convention::WKT2_SIMPLIFIED));
+ crs->exportToWKT(f.get());
+ EXPECT_EQ(f->toString(),
+ "GEODCRS[\"WGS 84\",\n"
+ " DATUM[\"World Geodetic System 1984\",\n"
+ " ELLIPSOID[\"WGS 84\",6378137,298.257223563]],\n"
+ " CS[ellipsoidal,2],\n"
+ " AXIS[\"latitude\",north],\n"
+ " AXIS[\"longitude\",east],\n"
+ " UNIT[\"degree\",0.0174532925199433],\n"
+ " ID[\"EPSG\",4326]]");
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(crs, EPSG_4326_as_WKT2_SIMPLIFIED_single_line) {
+ auto crs = GeographicCRS::EPSG_4326;
+ WKTFormatterNNPtr f(
+ WKTFormatter::create(WKTFormatter::Convention::WKT2_SIMPLIFIED));
+ f->setMultiLine(false);
+ crs->exportToWKT(f.get());
+ EXPECT_EQ(
+ f->toString(),
+ "GEODCRS[\"WGS 84\",DATUM[\"World Geodetic System "
+ "1984\",ELLIPSOID[\"WGS "
+ "84\",6378137,298.257223563]],"
+ "CS[ellipsoidal,2],AXIS[\"latitude\",north],AXIS[\"longitude\",east],"
+ "UNIT[\"degree\",0.0174532925199433],ID[\"EPSG\",4326]]");
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(crs, EPSG_4326_as_WKT2_2018_SIMPLIFIED) {
+ auto crs = GeographicCRS::EPSG_4326;
+ WKTFormatterNNPtr f(
+ WKTFormatter::create(WKTFormatter::Convention::WKT2_2018_SIMPLIFIED));
+ crs->exportToWKT(f.get());
+ EXPECT_EQ(f->toString(),
+ "GEOGCRS[\"WGS 84\",\n"
+ " DATUM[\"World Geodetic System 1984\",\n"
+ " ELLIPSOID[\"WGS 84\",6378137,298.257223563]],\n"
+ " CS[ellipsoidal,2],\n"
+ " AXIS[\"latitude\",north],\n"
+ " AXIS[\"longitude\",east],\n"
+ " UNIT[\"degree\",0.0174532925199433],\n"
+ " ID[\"EPSG\",4326]]");
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(crs, EPSG_4326_as_WKT1_GDAL) {
+ auto crs = GeographicCRS::EPSG_4326;
+ WKTFormatterNNPtr f(
+ WKTFormatter::create(WKTFormatter::Convention::WKT1_GDAL));
+ crs->exportToWKT(f.get());
+ EXPECT_EQ(f->toString(),
+ "GEOGCS[\"WGS 84\",\n"
+ " DATUM[\"WGS_1984\",\n"
+ " SPHEROID[\"WGS 84\",6378137,298.257223563,\n"
+ " AUTHORITY[\"EPSG\",\"7030\"]],\n"
+ " AUTHORITY[\"EPSG\",\"6326\"]],\n"
+ " PRIMEM[\"Greenwich\",0,\n"
+ " AUTHORITY[\"EPSG\",\"8901\"]],\n"
+ " UNIT[\"degree\",0.0174532925199433,\n"
+ " AUTHORITY[\"EPSG\",\"9122\"]],\n"
+ " AXIS[\"Latitude\",NORTH],\n"
+ " AXIS[\"Longitude\",EAST],\n"
+ " AUTHORITY[\"EPSG\",\"4326\"]]");
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(crs, EPSG_4326_as_WKT1_ESRI_with_database) {
+ auto crs = GeographicCRS::EPSG_4326;
+ WKTFormatterNNPtr f(WKTFormatter::create(
+ WKTFormatter::Convention::WKT1_ESRI, DatabaseContext::create()));
+ EXPECT_EQ(crs->exportToWKT(f.get()),
+ "GEOGCS[\"GCS_WGS_1984\",DATUM[\"D_WGS_1984\",SPHEROID[\"WGS_"
+ "1984\",6378137.0,298.257223563]],PRIMEM[\"Greenwich\",0.0],"
+ "UNIT[\"Degree\",0.0174532925199433]]");
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(crs, EPSG_4326_as_WKT1_ESRI_without_database) {
+ auto crs = GeographicCRS::EPSG_4326;
+ WKTFormatterNNPtr f(
+ WKTFormatter::create(WKTFormatter::Convention::WKT1_ESRI));
+ EXPECT_EQ(crs->exportToWKT(f.get()),
+ "GEOGCS[\"GCS_WGS_1984\",DATUM[\"D_WGS_1984\",SPHEROID[\"WGS_"
+ "1984\",6378137.0,298.257223563]],PRIMEM[\"Greenwich\",0.0],"
+ "UNIT[\"Degree\",0.0174532925199433]]");
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(crs, EPSG_4326_as_PROJ_string) {
+ auto crs = GeographicCRS::EPSG_4326;
+ EXPECT_EQ(crs->exportToPROJString(PROJStringFormatter::create().get()),
+ "+proj=pipeline +step +proj=longlat +ellps=WGS84 +step "
+ "+proj=unitconvert +xy_in=rad +xy_out=deg +step +proj=axisswap "
+ "+order=2,1");
+ EXPECT_EQ(
+ crs->exportToPROJString(
+ PROJStringFormatter::create(PROJStringFormatter::Convention::PROJ_4)
+ .get()),
+ "+proj=longlat +datum=WGS84");
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(crs, EPSG_4979_as_WKT2_SIMPLIFIED) {
+ auto crs = GeographicCRS::EPSG_4979;
+ WKTFormatterNNPtr f(
+ WKTFormatter::create(WKTFormatter::Convention::WKT2_SIMPLIFIED));
+ crs->exportToWKT(f.get());
+ EXPECT_EQ(f->toString(),
+ "GEODCRS[\"WGS 84\",\n"
+ " DATUM[\"World Geodetic System 1984\",\n"
+ " ELLIPSOID[\"WGS 84\",6378137,298.257223563]],\n"
+ " CS[ellipsoidal,3],\n"
+ " AXIS[\"latitude\",north,\n"
+ " UNIT[\"degree\",0.0174532925199433]],\n"
+ " AXIS[\"longitude\",east,\n"
+ " UNIT[\"degree\",0.0174532925199433]],\n"
+ " AXIS[\"ellipsoidal height\",up,\n"
+ " UNIT[\"metre\",1]],\n"
+ " ID[\"EPSG\",4979]]");
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(crs, EPSG_4979_as_WKT2_2018_SIMPLIFIED) {
+ auto crs = GeographicCRS::EPSG_4979;
+ WKTFormatterNNPtr f(
+ WKTFormatter::create(WKTFormatter::Convention::WKT2_2018_SIMPLIFIED));
+ crs->exportToWKT(f.get());
+ EXPECT_EQ(f->toString(),
+ "GEOGCRS[\"WGS 84\",\n"
+ " DATUM[\"World Geodetic System 1984\",\n"
+ " ELLIPSOID[\"WGS 84\",6378137,298.257223563]],\n"
+ " CS[ellipsoidal,3],\n"
+ " AXIS[\"latitude\",north,\n"
+ " UNIT[\"degree\",0.0174532925199433]],\n"
+ " AXIS[\"longitude\",east,\n"
+ " UNIT[\"degree\",0.0174532925199433]],\n"
+ " AXIS[\"ellipsoidal height\",up,\n"
+ " UNIT[\"metre\",1]],\n"
+ " ID[\"EPSG\",4979]]");
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(crs, EPSG_4979_as_WKT1_GDAL) {
+ auto crs = GeographicCRS::EPSG_4979;
+ WKTFormatterNNPtr f(
+ WKTFormatter::create(WKTFormatter::Convention::WKT1_GDAL));
+ crs->exportToWKT(f.get());
+ // FIXME? WKT1 only supports 2 axis for GEOGCS. So this is an extension of
+ // WKT1 as it
+ // and GDAL doesn't really export such as beast, although it can import it
+ EXPECT_EQ(f->toString(),
+ "GEOGCS[\"WGS 84\",\n"
+ " DATUM[\"WGS_1984\",\n"
+ " SPHEROID[\"WGS 84\",6378137,298.257223563,\n"
+ " AUTHORITY[\"EPSG\",\"7030\"]],\n"
+ " AUTHORITY[\"EPSG\",\"6326\"]],\n"
+ " PRIMEM[\"Greenwich\",0,\n"
+ " AUTHORITY[\"EPSG\",\"8901\"]],\n"
+ " UNIT[\"degree\",0.0174532925199433,\n"
+ " AUTHORITY[\"EPSG\",\"9122\"]],\n"
+ " AXIS[\"Latitude\",NORTH],\n"
+ " AXIS[\"Longitude\",EAST],\n"
+ " AXIS[\"Ellipsoidal height\",UP],\n"
+ " AUTHORITY[\"EPSG\",\"4979\"]]");
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(crs, EPSG_4979_as_WKT1_ESRI) {
+ auto crs = GeographicCRS::EPSG_4979;
+ WKTFormatterNNPtr f(
+ WKTFormatter::create(WKTFormatter::Convention::WKT1_ESRI));
+ EXPECT_THROW(crs->exportToWKT(f.get()), FormattingException);
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(crs, GeographicCRS_is2DPartOf3D) {
+ EXPECT_TRUE(GeographicCRS::EPSG_4326->is2DPartOf3D(
+ NN_NO_CHECK(GeographicCRS::EPSG_4979.get())));
+ EXPECT_FALSE(GeographicCRS::EPSG_4326->is2DPartOf3D(
+ NN_NO_CHECK(GeographicCRS::EPSG_4326.get())));
+ EXPECT_FALSE(GeographicCRS::EPSG_4979->is2DPartOf3D(
+ NN_NO_CHECK(GeographicCRS::EPSG_4326.get())));
+ EXPECT_FALSE(GeographicCRS::EPSG_4979->is2DPartOf3D(
+ NN_NO_CHECK(GeographicCRS::EPSG_4979.get())));
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(crs, EPSG_4807_as_WKT2) {
+ auto crs = GeographicCRS::EPSG_4807;
+ WKTFormatterNNPtr f(WKTFormatter::create(WKTFormatter::Convention::WKT2));
+ crs->exportToWKT(f.get());
+ EXPECT_EQ(
+ f->toString(),
+ "GEODCRS[\"NTF (Paris)\",\n"
+ " DATUM[\"Nouvelle Triangulation Francaise (Paris)\",\n"
+ " ELLIPSOID[\"Clarke 1880 (IGN)\",6378249.2,293.466021293627,\n"
+ " LENGTHUNIT[\"metre\",1]]],\n"
+ " PRIMEM[\"Paris\",2.5969213,\n"
+ " ANGLEUNIT[\"grad\",0.015707963267949]],\n"
+ " CS[ellipsoidal,2],\n"
+ " AXIS[\"latitude\",north,\n"
+ " ORDER[1],\n"
+ " ANGLEUNIT[\"grad\",0.015707963267949]],\n"
+ " AXIS[\"longitude\",east,\n"
+ " ORDER[2],\n"
+ " ANGLEUNIT[\"grad\",0.015707963267949]],\n"
+ " ID[\"EPSG\",4807]]");
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(crs, EPSG_4807_as_WKT2_SIMPLIFIED) {
+ auto crs = GeographicCRS::EPSG_4807;
+ WKTFormatterNNPtr f(
+ WKTFormatter::create(WKTFormatter::Convention::WKT2_SIMPLIFIED));
+ crs->exportToWKT(f.get());
+ EXPECT_EQ(f->toString(),
+ "GEODCRS[\"NTF (Paris)\",\n"
+ " DATUM[\"Nouvelle Triangulation Francaise (Paris)\",\n"
+ " ELLIPSOID[\"Clarke 1880 "
+ "(IGN)\",6378249.2,293.466021293627]],\n"
+ " PRIMEM[\"Paris\",2.5969213],\n"
+ " CS[ellipsoidal,2],\n"
+ " AXIS[\"latitude\",north],\n"
+ " AXIS[\"longitude\",east],\n"
+ " UNIT[\"grad\",0.015707963267949],\n"
+ " ID[\"EPSG\",4807]]");
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(crs, EPSG_4807_as_WKT1_GDAL) {
+ auto crs = GeographicCRS::EPSG_4807;
+ WKTFormatterNNPtr f(
+ WKTFormatter::create(WKTFormatter::Convention::WKT1_GDAL));
+ crs->exportToWKT(f.get());
+ EXPECT_EQ(
+ f->toString(),
+ "GEOGCS[\"NTF (Paris)\",\n"
+ " DATUM[\"Nouvelle_Triangulation_Francaise_Paris\",\n"
+ " SPHEROID[\"Clarke 1880 (IGN)\",6378249.2,293.466021293627,\n"
+ " AUTHORITY[\"EPSG\",\"7011\"]],\n"
+ " AUTHORITY[\"EPSG\",\"6807\"]],\n"
+ " PRIMEM[\"Paris\",2.33722917,\n" /* WKT1_GDAL weirdness: PRIMEM is
+ converted to degree */
+ " AUTHORITY[\"EPSG\",\"8903\"]],\n"
+ " UNIT[\"grad\",0.015707963267949,\n"
+ " AUTHORITY[\"EPSG\",\"9105\"]],\n"
+ " AXIS[\"Latitude\",NORTH],\n"
+ " AXIS[\"Longitude\",EAST],\n"
+ " AUTHORITY[\"EPSG\",\"4807\"]]");
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(crs, EPSG_4807_as_WKT1_ESRI_with_database) {
+ auto crs = GeographicCRS::EPSG_4807;
+ WKTFormatterNNPtr f(WKTFormatter::create(
+ WKTFormatter::Convention::WKT1_ESRI, DatabaseContext::create()));
+ EXPECT_EQ(crs->exportToWKT(f.get()),
+ "GEOGCS[\"GCS_NTF_Paris\",DATUM[\"D_NTF\",SPHEROID[\"Clarke_1880_"
+ "IGN\",6378249.2,293.466021293627]],PRIMEM[\"Paris\",2.33722917],"
+ "UNIT[\"Grad\",0.015707963267949]]");
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(crs, EPSG_4807_as_WKT1_ESRI_without_database) {
+ auto crs = GeographicCRS::EPSG_4807;
+ WKTFormatterNNPtr f(
+ WKTFormatter::create(WKTFormatter::Convention::WKT1_ESRI));
+ EXPECT_EQ(crs->exportToWKT(f.get()),
+ "GEOGCS[\"GCS_NTF_Paris\",DATUM[\"D_Nouvelle_Triangulation_"
+ "Francaise_Paris\",SPHEROID[\"Clarke_1880_IGN\",6378249.2,293."
+ "466021293627]],PRIMEM[\"Paris\",2.33722917],UNIT[\"Grad\",0."
+ "015707963267949]]");
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(crs, EPSG_4807_as_PROJ_string) {
+ auto crs = GeographicCRS::EPSG_4807;
+ EXPECT_EQ(crs->exportToPROJString(PROJStringFormatter::create().get()),
+ "+proj=pipeline +step +proj=longlat +ellps=clrk80ign "
+ "+pm=paris +step +proj=unitconvert +xy_in=rad +xy_out=grad +step "
+ "+proj=axisswap +order=2,1");
+ EXPECT_EQ(
+ crs->exportToPROJString(
+ PROJStringFormatter::create(PROJStringFormatter::Convention::PROJ_4)
+ .get()),
+ "+proj=longlat +ellps=clrk80ign +pm=paris");
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(crs, EPSG_4267) {
+ auto crs = GeographicCRS::EPSG_4267;
+ EXPECT_EQ(crs->exportToWKT(WKTFormatter::create().get()),
+ "GEODCRS[\"NAD27\",\n"
+ " DATUM[\"North American Datum 1927\",\n"
+ " ELLIPSOID[\"Clarke 1866\",6378206.4,294.978698213898,\n"
+ " LENGTHUNIT[\"metre\",1]]],\n"
+ " PRIMEM[\"Greenwich\",0,\n"
+ " ANGLEUNIT[\"degree\",0.0174532925199433]],\n"
+ " CS[ellipsoidal,2],\n"
+ " AXIS[\"latitude\",north,\n"
+ " ORDER[1],\n"
+ " ANGLEUNIT[\"degree\",0.0174532925199433]],\n"
+ " AXIS[\"longitude\",east,\n"
+ " ORDER[2],\n"
+ " ANGLEUNIT[\"degree\",0.0174532925199433]],\n"
+ " ID[\"EPSG\",4267]]");
+ EXPECT_EQ(
+ crs->exportToPROJString(
+ PROJStringFormatter::create(PROJStringFormatter::Convention::PROJ_4)
+ .get()),
+ "+proj=longlat +datum=NAD27");
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(crs, EPSG_4267_as_WKT1_ESRI_with_database) {
+ auto crs = GeographicCRS::EPSG_4267;
+ WKTFormatterNNPtr f(WKTFormatter::create(
+ WKTFormatter::Convention::WKT1_ESRI, DatabaseContext::create()));
+ EXPECT_EQ(crs->exportToWKT(f.get()),
+ "GEOGCS[\"GCS_North_American_1927\","
+ "DATUM[\"D_North_American_1927\",SPHEROID[\"Clarke_1866\","
+ "6378206.4,294.978698213898]],PRIMEM[\"Greenwich\",0.0],"
+ "UNIT[\"Degree\",0.0174532925199433]]");
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(crs, EPSG_4269) {
+ auto crs = GeographicCRS::EPSG_4269;
+ EXPECT_EQ(crs->exportToWKT(WKTFormatter::create().get()),
+ "GEODCRS[\"NAD83\",\n"
+ " DATUM[\"North American Datum 1983\",\n"
+ " ELLIPSOID[\"GRS 1980\",6378137,298.257222101,\n"
+ " LENGTHUNIT[\"metre\",1]]],\n"
+ " PRIMEM[\"Greenwich\",0,\n"
+ " ANGLEUNIT[\"degree\",0.0174532925199433]],\n"
+ " CS[ellipsoidal,2],\n"
+ " AXIS[\"latitude\",north,\n"
+ " ORDER[1],\n"
+ " ANGLEUNIT[\"degree\",0.0174532925199433]],\n"
+ " AXIS[\"longitude\",east,\n"
+ " ORDER[2],\n"
+ " ANGLEUNIT[\"degree\",0.0174532925199433]],\n"
+ " ID[\"EPSG\",4269]]");
+ EXPECT_EQ(
+ crs->exportToPROJString(
+ PROJStringFormatter::create(PROJStringFormatter::Convention::PROJ_4)
+ .get()),
+ "+proj=longlat +datum=NAD83");
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(crs, EPSG_4268_geogcrs_deprecated_as_WKT1_GDAL) {
+ auto dbContext = DatabaseContext::create();
+ auto factory = AuthorityFactory::create(dbContext, "EPSG");
+ auto crs = factory->createCoordinateReferenceSystem("4268");
+ WKTFormatterNNPtr f(
+ WKTFormatter::create(WKTFormatter::Convention::WKT1_GDAL));
+ auto wkt = crs->exportToWKT(f.get());
+ EXPECT_TRUE(wkt.find("GEOGCS[\"NAD27 Michigan (deprecated)\"") == 0) << wkt;
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(crs, EPSG_2008_projcrs_deprecated_as_WKT1_GDAL) {
+ auto dbContext = DatabaseContext::create();
+ auto factory = AuthorityFactory::create(dbContext, "EPSG");
+ auto crs = factory->createCoordinateReferenceSystem("2008");
+ WKTFormatterNNPtr f(
+ WKTFormatter::create(WKTFormatter::Convention::WKT1_GDAL));
+ auto wkt = crs->exportToWKT(f.get());
+ EXPECT_TRUE(
+ wkt.find("PROJCS[\"NAD27(CGQ77) / SCoPQ zone 2 (deprecated)\"") == 0)
+ << wkt;
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(crs, EPSG_27561_projected_with_geodetic_in_grad_as_PROJ_string_and_WKT1) {
+ auto obj = WKTParser().createFromWKT(
+ "PROJCRS[\"NTF (Paris) / Lambert Nord France\",\n"
+ " BASEGEODCRS[\"NTF (Paris)\",\n"
+ " DATUM[\"Nouvelle Triangulation Francaise (Paris)\",\n"
+ " ELLIPSOID[\"Clarke 1880 "
+ "(IGN)\",6378249.2,293.4660213,LENGTHUNIT[\"metre\",1.0]]],\n"
+ " PRIMEM[\"Paris\",2.5969213,ANGLEUNIT[\"grad\",0.015707963268]]],\n"
+ " CONVERSION[\"Lambert Nord France\",\n"
+ " METHOD[\"Lambert Conic Conformal (1SP)\",ID[\"EPSG\",9801]],\n"
+ " PARAMETER[\"Latitude of natural "
+ "origin\",55,ANGLEUNIT[\"grad\",0.015707963268]],\n"
+ " PARAMETER[\"Longitude of natural "
+ "origin\",0,ANGLEUNIT[\"grad\",0.015707963268]],\n"
+ " PARAMETER[\"Scale factor at natural "
+ "origin\",0.999877341,SCALEUNIT[\"unity\",1.0]],\n"
+ " PARAMETER[\"False easting\",600000,LENGTHUNIT[\"metre\",1.0]],\n"
+ " PARAMETER[\"False northing\",200000,LENGTHUNIT[\"metre\",1.0]]],\n"
+ " CS[cartesian,2],\n"
+ " AXIS[\"easting (X)\",east,ORDER[1]],\n"
+ " AXIS[\"northing (Y)\",north,ORDER[2]],\n"
+ " LENGTHUNIT[\"metre\",1.0],\n"
+ " ID[\"EPSG\",27561]]");
+ auto crs = nn_dynamic_pointer_cast<ProjectedCRS>(obj);
+ ASSERT_TRUE(crs != nullptr);
+ EXPECT_EQ(
+ crs->exportToPROJString(PROJStringFormatter::create().get()),
+ "+proj=pipeline +step +proj=axisswap +order=2,1 +step "
+ "+proj=unitconvert +xy_in=grad +xy_out=rad +step +inv +proj=longlat "
+ "+ellps=clrk80ign +pm=paris +step +proj=lcc +lat_1=49.5 "
+ "+lat_0=49.5 +lon_0=0 +k_0=0.999877341 +x_0=600000 +y_0=200000 "
+ "+ellps=clrk80ign +pm=paris");
+ EXPECT_EQ(
+ crs->exportToPROJString(
+ PROJStringFormatter::create(PROJStringFormatter::Convention::PROJ_4)
+ .get()),
+ "+proj=lcc +lat_1=49.5 +lat_0=49.5 +lon_0=0 +k_0=0.999877341 "
+ "+x_0=600000 +y_0=200000 +ellps=clrk80ign +pm=paris");
+
+ auto nn_crs = NN_CHECK_ASSERT(crs);
+ EXPECT_TRUE(nn_crs->isEquivalentTo(nn_crs.get()));
+ EXPECT_FALSE(nn_crs->isEquivalentTo(createUnrelatedObject().get()));
+ EXPECT_FALSE(
+ nn_crs->DerivedCRS::isEquivalentTo(createUnrelatedObject().get()));
+
+ auto wkt1 = crs->exportToWKT(
+ WKTFormatter::create(WKTFormatter::Convention::WKT1_GDAL).get());
+ EXPECT_EQ(
+ wkt1,
+ "PROJCS[\"NTF (Paris) / Lambert Nord France\",\n"
+ " GEOGCS[\"NTF (Paris)\",\n"
+ " DATUM[\"Nouvelle_Triangulation_Francaise_Paris\",\n"
+ " SPHEROID[\"Clarke 1880 (IGN)\",6378249.2,293.4660213]],\n"
+ " PRIMEM[\"Paris\",2.33722917000759],\n"
+ " UNIT[\"grad\",0.015707963268],\n"
+ " AXIS[\"Latitude\",NORTH],\n"
+ " AXIS[\"Longitude\",EAST]],\n"
+ " PROJECTION[\"Lambert_Conformal_Conic_1SP\"],\n"
+ " PARAMETER[\"latitude_of_origin\",55],\n"
+ " PARAMETER[\"central_meridian\",0],\n"
+ " PARAMETER[\"scale_factor\",0.999877341],\n"
+ " PARAMETER[\"false_easting\",600000],\n"
+ " PARAMETER[\"false_northing\",200000],\n"
+ " UNIT[\"metre\",1],\n"
+ " AXIS[\"Easting\",EAST],\n"
+ " AXIS[\"Northing\",NORTH],\n"
+ " AUTHORITY[\"EPSG\",\"27561\"]]");
+
+ auto wkt1_esri = crs->exportToWKT(
+ WKTFormatter::create(WKTFormatter::Convention::WKT1_ESRI,
+ DatabaseContext::create())
+ .get());
+ EXPECT_EQ(
+ wkt1_esri,
+ "PROJCS[\"NTF_Paris_Lambert_Nord_France\",GEOGCS[\"GCS_NTF_Paris\","
+ "DATUM[\"D_NTF\",SPHEROID[\"Clarke_1880_IGN\",6378249.2,"
+ "293.4660213]],PRIMEM[\"Paris\",2.33722917000759],"
+ "UNIT[\"Grad\",0.015707963268]],"
+ "PROJECTION[\"Lambert_Conformal_Conic\"],"
+ "PARAMETER[\"False_Easting\",600000.0],"
+ "PARAMETER[\"False_Northing\",200000.0],"
+ "PARAMETER[\"Central_Meridian\",0.0],"
+ "PARAMETER[\"Standard_Parallel_1\",55.0],"
+ "PARAMETER[\"Scale_Factor\",0.999877341],"
+ "PARAMETER[\"Latitude_Of_Origin\",55.0],"
+ "UNIT[\"Meter\",1.0]]");
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(crs, EPSG_3040_projected_northing_easting_as_PROJ_string) {
+ auto obj = WKTParser().createFromWKT(
+ "PROJCRS[\"ETRS89 / UTM zone 28N (N-E)\",\n"
+ " BASEGEODCRS[\"ETRS89\",\n"
+ " DATUM[\"European Terrestrial Reference System 1989\",\n"
+ " ELLIPSOID[\"GRS "
+ "1980\",6378137,298.257222101,LENGTHUNIT[\"metre\",1.0]]]],\n"
+ " CONVERSION[\"UTM zone 28N\",\n"
+ " METHOD[\"Transverse Mercator\",ID[\"EPSG\",9807]],\n"
+ " PARAMETER[\"Latitude of natural "
+ "origin\",0,ANGLEUNIT[\"degree\",0.01745329252]],\n"
+ " PARAMETER[\"Longitude of natural "
+ "origin\",-15,ANGLEUNIT[\"degree\",0.01745329252]],\n"
+ " PARAMETER[\"Scale factor at natural "
+ "origin\",0.9996,SCALEUNIT[\"unity\",1.0]],\n"
+ " PARAMETER[\"False easting\",500000,LENGTHUNIT[\"metre\",1.0]],\n"
+ " PARAMETER[\"False northing\",0,LENGTHUNIT[\"metre\",1.0]]],\n"
+ " CS[cartesian,2],\n"
+ " AXIS[\"northing (N)\",north,ORDER[1]],\n"
+ " AXIS[\"easting (E)\",east,ORDER[2]],\n"
+ " LENGTHUNIT[\"metre\",1.0],\n"
+ " ID[\"EPSG\",3040]]");
+ auto crs = nn_dynamic_pointer_cast<ProjectedCRS>(obj);
+ ASSERT_TRUE(crs != nullptr);
+ EXPECT_EQ(crs->exportToPROJString(PROJStringFormatter::create().get()),
+ "+proj=pipeline +step +proj=axisswap +order=2,1 +step "
+ "+proj=unitconvert +xy_in=deg +xy_out=rad +step +proj=utm "
+ "+zone=28 +ellps=GRS80 +step +proj=axisswap +order=2,1");
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(crs, EPSG_2222_projected_unit_foot_as_PROJ_string_and_WKT1) {
+ auto obj = WKTParser().createFromWKT(
+ "PROJCRS[\"NAD83 / Arizona East (ft)\",\n"
+ " BASEGEODCRS[\"NAD83\",\n"
+ " DATUM[\"North American Datum 1983\",\n"
+ " ELLIPSOID[\"GRS "
+ "1980\",6378137,298.257222101,LENGTHUNIT[\"metre\",1.0]]]],\n"
+ " CONVERSION[\"SPCS83 Arizona East zone (International feet)\",\n"
+ " METHOD[\"Transverse Mercator\",ID[\"EPSG\",9807]],\n"
+ " PARAMETER[\"Latitude of natural "
+ "origin\",31,ANGLEUNIT[\"degree\",0.01745329252]],\n"
+ " PARAMETER[\"Longitude of natural "
+ "origin\",-110.166666666667,ANGLEUNIT[\"degree\",0.01745329252]],\n"
+ " PARAMETER[\"Scale factor at natural "
+ "origin\",0.9999,SCALEUNIT[\"unity\",1.0]],\n"
+ " PARAMETER[\"False easting\",700000,LENGTHUNIT[\"foot\",0.3048]],\n"
+ " PARAMETER[\"False northing\",0,LENGTHUNIT[\"foot\",0.3048]]],\n"
+ " CS[cartesian,2],\n"
+ " AXIS[\"easting (X)\",east,ORDER[1]],\n"
+ " AXIS[\"northing (Y)\",north,ORDER[2]],\n"
+ " LENGTHUNIT[\"foot\",0.3048],\n"
+ " ID[\"EPSG\",2222]]");
+ auto crs = nn_dynamic_pointer_cast<ProjectedCRS>(obj);
+ ASSERT_TRUE(crs != nullptr);
+ EXPECT_EQ(crs->exportToPROJString(PROJStringFormatter::create().get()),
+ "+proj=pipeline +step +proj=axisswap +order=2,1 +step "
+ "+proj=unitconvert +xy_in=deg +xy_out=rad +step +proj=tmerc "
+ "+lat_0=31 +lon_0=-110.166666666667 +k_0=0.9999 +x_0=213360 "
+ "+y_0=0 +ellps=GRS80 +step +proj=unitconvert +xy_in=m +z_in=m "
+ "+xy_out=ft +z_out=ft");
+ EXPECT_EQ(
+ crs->exportToPROJString(
+ PROJStringFormatter::create(PROJStringFormatter::Convention::PROJ_4)
+ .get()),
+ "+proj=tmerc +lat_0=31 +lon_0=-110.166666666667 +k_0=0.9999 "
+ "+x_0=213360 +y_0=0 +datum=NAD83 +units=ft");
+
+ auto wkt1 = crs->exportToWKT(
+ WKTFormatter::create(WKTFormatter::Convention::WKT1_GDAL).get());
+ EXPECT_EQ(wkt1,
+ "PROJCS[\"NAD83 / Arizona East (ft)\",\n"
+ " GEOGCS[\"NAD83\",\n"
+ " DATUM[\"North_American_Datum_1983\",\n"
+ " SPHEROID[\"GRS 1980\",6378137,298.257222101]],\n"
+ " PRIMEM[\"Greenwich\",0,\n"
+ " AUTHORITY[\"EPSG\",\"8901\"]],\n"
+ " UNIT[\"degree\",0.0174532925199433,\n"
+ " AUTHORITY[\"EPSG\",\"9122\"]],\n"
+ " AXIS[\"Latitude\",NORTH],\n"
+ " AXIS[\"Longitude\",EAST]],\n"
+ " PROJECTION[\"Transverse_Mercator\"],\n"
+ " PARAMETER[\"latitude_of_origin\",31],\n"
+ " PARAMETER[\"central_meridian\",-110.166666666667],\n"
+ " PARAMETER[\"scale_factor\",0.9999],\n"
+ " PARAMETER[\"false_easting\",700000],\n"
+ " PARAMETER[\"false_northing\",0],\n"
+ " UNIT[\"foot\",0.3048],\n"
+ " AXIS[\"Easting\",EAST],\n"
+ " AXIS[\"Northing\",NORTH],\n"
+ " AUTHORITY[\"EPSG\",\"2222\"]]");
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(crs, projected_with_parameter_unit_different_than_cs_unit_as_WKT1) {
+ auto obj = WKTParser().createFromWKT(
+ "PROJCRS[\"unknown\","
+ " BASEGEODCRS[\"unknown\","
+ " DATUM[\"Unknown based on GRS80 ellipsoid\","
+ " ELLIPSOID[\"GRS 1980\",6378137,298.257222101,"
+ " LENGTHUNIT[\"metre\",1]]],"
+ " PRIMEM[\"Greenwich\",0]],"
+ " CONVERSION[\"UTM zone 32N\","
+ " METHOD[\"Transverse Mercator\"],"
+ " PARAMETER[\"Latitude of natural origin\",0],"
+ " PARAMETER[\"Longitude of natural origin\",9],"
+ " PARAMETER[\"Scale factor at natural origin\",0.9996],"
+ " PARAMETER[\"False easting\",500000,LENGTHUNIT[\"metre\",1]],"
+ " PARAMETER[\"False northing\",0,LENGTHUNIT[\"metre\",1]]],"
+ " CS[Cartesian,2],"
+ " AXIS[\"(E)\",east],"
+ " AXIS[\"(N)\",north],"
+ " LENGTHUNIT[\"US survey foot\",0.304800609601219]]");
+ auto crs = nn_dynamic_pointer_cast<ProjectedCRS>(obj);
+ ASSERT_TRUE(crs != nullptr);
+
+ auto wkt1 = crs->exportToWKT(
+ WKTFormatter::create(WKTFormatter::Convention::WKT1_GDAL).get());
+ EXPECT_EQ(wkt1,
+ "PROJCS[\"unknown\",\n"
+ " GEOGCS[\"unknown\",\n"
+ " DATUM[\"Unknown_based_on_GRS80_ellipsoid\",\n"
+ " SPHEROID[\"GRS 1980\",6378137,298.257222101]],\n"
+ " PRIMEM[\"Greenwich\",0],\n"
+ " UNIT[\"degree\",0.0174532925199433,\n"
+ " AUTHORITY[\"EPSG\",\"9122\"]],\n"
+ " AXIS[\"Latitude\",NORTH],\n"
+ " AXIS[\"Longitude\",EAST]],\n"
+ " PROJECTION[\"Transverse_Mercator\"],\n"
+ " PARAMETER[\"latitude_of_origin\",0],\n"
+ " PARAMETER[\"central_meridian\",9],\n"
+ " PARAMETER[\"scale_factor\",0.9996],\n"
+ " PARAMETER[\"false_easting\",1640416.66666667],\n"
+ " PARAMETER[\"false_northing\",0],\n"
+ " UNIT[\"US survey foot\",0.304800609601219],\n"
+ " AXIS[\"Easting\",EAST],\n"
+ " AXIS[\"Northing\",NORTH]]");
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(crs, EPSG_32661_projected_north_pole_north_east) {
+ auto dbContext = DatabaseContext::create();
+ auto factory = AuthorityFactory::create(dbContext, "EPSG");
+ auto crs = factory->createCoordinateReferenceSystem("32661");
+ auto proj_crs = nn_dynamic_pointer_cast<ProjectedCRS>(crs);
+ ASSERT_TRUE(proj_crs != nullptr);
+ auto proj_string =
+ "+proj=pipeline +step +proj=axisswap +order=2,1 +step "
+ "+proj=unitconvert +xy_in=deg +xy_out=rad +step +proj=stere "
+ "+lat_0=90 +lon_0=0 +k=0.994 +x_0=2000000 +y_0=2000000 "
+ "+ellps=WGS84 +step +proj=axisswap +order=2,1";
+ EXPECT_EQ(proj_crs->exportToPROJString(PROJStringFormatter::create().get()),
+ proj_string);
+
+ auto obj_from_proj = PROJStringParser().createFromPROJString(proj_string);
+ auto crs_from_proj = nn_dynamic_pointer_cast<ProjectedCRS>(obj_from_proj);
+ ASSERT_TRUE(crs_from_proj != nullptr);
+ EXPECT_EQ(
+ crs_from_proj->exportToPROJString(PROJStringFormatter::create().get()),
+ proj_string);
+ EXPECT_TRUE(crs_from_proj->coordinateSystem()->isEquivalentTo(
+ proj_crs->coordinateSystem().get()));
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(crs, EPSG_5041_projected_north_pole_east_north) {
+ auto dbContext = DatabaseContext::create();
+ auto factory = AuthorityFactory::create(dbContext, "EPSG");
+ auto crs = factory->createCoordinateReferenceSystem("5041");
+ auto proj_crs = nn_dynamic_pointer_cast<ProjectedCRS>(crs);
+ ASSERT_TRUE(proj_crs != nullptr);
+ auto proj_string =
+ "+proj=pipeline +step +proj=axisswap +order=2,1 +step "
+ "+proj=unitconvert +xy_in=deg +xy_out=rad +step +proj=stere "
+ "+lat_0=90 +lon_0=0 +k=0.994 +x_0=2000000 +y_0=2000000 "
+ "+ellps=WGS84";
+ EXPECT_EQ(proj_crs->exportToPROJString(PROJStringFormatter::create().get()),
+ proj_string);
+
+ auto obj_from_proj = PROJStringParser().createFromPROJString(proj_string);
+ auto crs_from_proj = nn_dynamic_pointer_cast<ProjectedCRS>(obj_from_proj);
+ ASSERT_TRUE(crs_from_proj != nullptr);
+ EXPECT_EQ(
+ crs_from_proj->exportToPROJString(PROJStringFormatter::create().get()),
+ proj_string);
+ EXPECT_TRUE(crs_from_proj->coordinateSystem()->isEquivalentTo(
+ proj_crs->coordinateSystem().get()));
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(crs, EPSG_32761_projected_south_pole_north_east) {
+ auto dbContext = DatabaseContext::create();
+ auto factory = AuthorityFactory::create(dbContext, "EPSG");
+ auto crs = factory->createCoordinateReferenceSystem("32761");
+ auto proj_crs = nn_dynamic_pointer_cast<ProjectedCRS>(crs);
+ ASSERT_TRUE(proj_crs != nullptr);
+ auto proj_string =
+ "+proj=pipeline +step +proj=axisswap +order=2,1 +step "
+ "+proj=unitconvert +xy_in=deg +xy_out=rad +step +proj=stere "
+ "+lat_0=-90 +lon_0=0 +k=0.994 +x_0=2000000 +y_0=2000000 "
+ "+ellps=WGS84 +step +proj=axisswap +order=2,1";
+ EXPECT_EQ(proj_crs->exportToPROJString(PROJStringFormatter::create().get()),
+ proj_string);
+
+ auto obj_from_proj = PROJStringParser().createFromPROJString(proj_string);
+ auto crs_from_proj = nn_dynamic_pointer_cast<ProjectedCRS>(obj_from_proj);
+ ASSERT_TRUE(crs_from_proj != nullptr);
+ EXPECT_EQ(
+ crs_from_proj->exportToPROJString(PROJStringFormatter::create().get()),
+ proj_string);
+ EXPECT_TRUE(crs_from_proj->coordinateSystem()->isEquivalentTo(
+ proj_crs->coordinateSystem().get()));
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(crs, EPSG_5042_projected_south_pole_east_north) {
+ auto dbContext = DatabaseContext::create();
+ auto factory = AuthorityFactory::create(dbContext, "EPSG");
+ auto crs = factory->createCoordinateReferenceSystem("5042");
+ auto proj_crs = nn_dynamic_pointer_cast<ProjectedCRS>(crs);
+ ASSERT_TRUE(proj_crs != nullptr);
+ EXPECT_EQ(proj_crs->exportToPROJString(PROJStringFormatter::create().get()),
+ "+proj=pipeline +step +proj=axisswap +order=2,1 +step "
+ "+proj=unitconvert +xy_in=deg +xy_out=rad +step +proj=stere "
+ "+lat_0=-90 +lon_0=0 +k=0.994 +x_0=2000000 +y_0=2000000 "
+ "+ellps=WGS84");
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(crs, geodetic_crs_both_datum_datum_ensemble_null) {
+ EXPECT_THROW(GeodeticCRS::create(
+ PropertyMap(), nullptr, nullptr,
+ CartesianCS::createGeocentric(UnitOfMeasure::METRE)),
+ Exception);
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(crs, geodetic_crs_both_datum_datum_ensemble_non_null) {
+ auto ensemble = DatumEnsemble::create(
+ PropertyMap(),
+ std::vector<DatumNNPtr>{GeodeticReferenceFrame::EPSG_6326,
+ GeodeticReferenceFrame::EPSG_6326},
+ PositionalAccuracy::create("100"));
+ EXPECT_THROW(GeodeticCRS::create(
+ PropertyMap(), GeodeticReferenceFrame::EPSG_6326, ensemble,
+ CartesianCS::createGeocentric(UnitOfMeasure::METRE)),
+ Exception);
+}
+
+// ---------------------------------------------------------------------------
+
+static GeodeticCRSNNPtr createGeocentric() {
+ PropertyMap propertiesCRS;
+ propertiesCRS.set(Identifier::CODESPACE_KEY, "EPSG")
+ .set(Identifier::CODE_KEY, 4328)
+ .set(IdentifiedObject::NAME_KEY, "WGS 84");
+ return GeodeticCRS::create(
+ propertiesCRS, GeodeticReferenceFrame::EPSG_6326,
+ CartesianCS::createGeocentric(UnitOfMeasure::METRE));
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(crs, geocentricCRS_as_WKT2) {
+ auto crs = createGeocentric();
+
+ auto expected = "GEODCRS[\"WGS 84\",\n"
+ " DATUM[\"World Geodetic System 1984\",\n"
+ " ELLIPSOID[\"WGS 84\",6378137,298.257223563,\n"
+ " LENGTHUNIT[\"metre\",1]]],\n"
+ " PRIMEM[\"Greenwich\",0,\n"
+ " ANGLEUNIT[\"degree\",0.0174532925199433]],\n"
+ " CS[Cartesian,3],\n"
+ " AXIS[\"(X)\",geocentricX,\n"
+ " ORDER[1],\n"
+ " LENGTHUNIT[\"metre\",1]],\n"
+ " AXIS[\"(Y)\",geocentricY,\n"
+ " ORDER[2],\n"
+ " LENGTHUNIT[\"metre\",1]],\n"
+ " AXIS[\"(Z)\",geocentricZ,\n"
+ " ORDER[3],\n"
+ " LENGTHUNIT[\"metre\",1]],\n"
+ " ID[\"EPSG\",4328]]";
+
+ EXPECT_EQ(crs->exportToWKT(WKTFormatter::create().get()), expected);
+ EXPECT_EQ(
+ crs->exportToWKT(
+ WKTFormatter::create(WKTFormatter::Convention::WKT2_2018).get()),
+ expected);
+
+ EXPECT_TRUE(crs->isEquivalentTo(crs.get()));
+ EXPECT_TRUE(crs->shallowClone()->isEquivalentTo(crs.get()));
+ EXPECT_FALSE(crs->isEquivalentTo(createUnrelatedObject().get()));
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(crs, geocentricCRS_as_WKT2_simplified) {
+ auto crs = createGeocentric();
+
+ auto expected = "GEODCRS[\"WGS 84\",\n"
+ " DATUM[\"World Geodetic System 1984\",\n"
+ " ELLIPSOID[\"WGS 84\",6378137,298.257223563]],\n"
+ " CS[Cartesian,3],\n"
+ " AXIS[\"(X)\",geocentricX],\n"
+ " AXIS[\"(Y)\",geocentricY],\n"
+ " AXIS[\"(Z)\",geocentricZ],\n"
+ " UNIT[\"metre\",1],\n"
+ " ID[\"EPSG\",4328]]";
+
+ EXPECT_EQ(crs->exportToWKT(WKTFormatter::create(
+ WKTFormatter::Convention::WKT2_SIMPLIFIED)
+ .get()),
+ expected);
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(crs, geocentricCRS_as_WKT1_GDAL) {
+ auto crs = createGeocentric();
+ WKTFormatterNNPtr f(
+ WKTFormatter::create(WKTFormatter::Convention::WKT1_GDAL));
+ crs->exportToWKT(f.get());
+ EXPECT_EQ(f->toString(),
+ "GEOCCS[\"WGS 84\",\n"
+ " DATUM[\"WGS_1984\",\n"
+ " SPHEROID[\"WGS 84\",6378137,298.257223563,\n"
+ " AUTHORITY[\"EPSG\",\"7030\"]],\n"
+ " AUTHORITY[\"EPSG\",\"6326\"]],\n"
+ " PRIMEM[\"Greenwich\",0,\n"
+ " AUTHORITY[\"EPSG\",\"8901\"]],\n"
+ " UNIT[\"metre\",1,\n"
+ " AUTHORITY[\"EPSG\",\"9001\"]],\n"
+ " AXIS[\"Geocentric X\",OTHER],\n"
+ " AXIS[\"Geocentric Y\",OTHER],\n"
+ " AXIS[\"Geocentric Z\",NORTH],\n"
+ " AUTHORITY[\"EPSG\",\"4328\"]]");
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(crs, geocentricCRS_as_PROJ_string) {
+ auto crs = createGeocentric();
+ EXPECT_EQ(crs->exportToPROJString(PROJStringFormatter::create().get()),
+ "+proj=cart +ellps=WGS84");
+ EXPECT_EQ(
+ crs->exportToPROJString(
+ PROJStringFormatter::create(PROJStringFormatter::Convention::PROJ_4)
+ .get()),
+ "+proj=geocent +ellps=WGS84");
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(crs, geocentricCRS_non_meter_unit_as_PROJ_string) {
+ auto crs = GeodeticCRS::create(
+ PropertyMap(), GeodeticReferenceFrame::EPSG_6326,
+ CartesianCS::createGeocentric(
+ UnitOfMeasure("kilometre", 1000.0, UnitOfMeasure::Type::LINEAR)));
+
+ EXPECT_EQ(crs->exportToPROJString(PROJStringFormatter::create().get()),
+ "+proj=pipeline +step +proj=cart +ellps=WGS84 +step "
+ "+proj=unitconvert +xy_in=m +z_in=m +xy_out=km +z_out=km");
+ EXPECT_THROW(
+ crs->exportToPROJString(
+ PROJStringFormatter::create(PROJStringFormatter::Convention::PROJ_4)
+ .get()),
+ FormattingException);
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(crs, geocentricCRS_unsupported_unit_as_PROJ_string) {
+ auto crs = GeodeticCRS::create(
+ PropertyMap(), GeodeticReferenceFrame::EPSG_6326,
+ CartesianCS::createGeocentric(
+ UnitOfMeasure("my unit", 500.0, UnitOfMeasure::Type::LINEAR)));
+
+ EXPECT_EQ(crs->exportToPROJString(PROJStringFormatter::create().get()),
+ "+proj=pipeline +step +proj=cart +ellps=WGS84 +step "
+ "+proj=unitconvert +xy_in=m +z_in=m +xy_out=500 +z_out=500");
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(crs, geodeticcrs_identify_no_db) {
+ {
+ auto res =
+ GeodeticCRS::create(
+ PropertyMap(), GeodeticReferenceFrame::EPSG_6326, nullptr,
+ CartesianCS::createGeocentric(UnitOfMeasure::METRE))
+ ->identify(nullptr);
+ ASSERT_EQ(res.size(), 0);
+ }
+ {
+ auto res = GeographicCRS::EPSG_4326->identify(nullptr);
+ ASSERT_EQ(res.size(), 1);
+ EXPECT_EQ(res.front().first.get(), GeographicCRS::EPSG_4326.get());
+ EXPECT_EQ(res.front().second, 100);
+ }
+ {
+ // Using virtual method
+ auto res = static_cast<const CRS *>(GeographicCRS::EPSG_4326.get())
+ ->identify(nullptr);
+ ASSERT_EQ(res.size(), 1);
+ EXPECT_EQ(res.front().first.get(), GeographicCRS::EPSG_4326.get());
+ EXPECT_EQ(res.front().second, 100);
+ }
+ {
+ auto res =
+ GeographicCRS::create(
+ PropertyMap().set(IdentifiedObject::NAME_KEY, "WGS 84"),
+ GeodeticReferenceFrame::EPSG_6326, nullptr,
+ EllipsoidalCS::createLatitudeLongitude(UnitOfMeasure::DEGREE))
+ ->identify(nullptr);
+ ASSERT_EQ(res.size(), 1);
+ EXPECT_EQ(res.front().first.get(), GeographicCRS::EPSG_4326.get());
+ EXPECT_EQ(res.front().second, 100);
+ }
+ {
+ auto res =
+ GeographicCRS::create(
+ PropertyMap().set(IdentifiedObject::NAME_KEY, "WGS84"),
+ GeodeticReferenceFrame::EPSG_6326, nullptr,
+ EllipsoidalCS::createLatitudeLongitude(UnitOfMeasure::DEGREE))
+ ->identify(nullptr);
+ ASSERT_EQ(res.size(), 1);
+ EXPECT_EQ(res.front().first.get(), GeographicCRS::EPSG_4326.get());
+ EXPECT_EQ(res.front().second, 90);
+ }
+ {
+ // Long Lat order
+ auto res =
+ GeographicCRS::create(
+ PropertyMap().set(IdentifiedObject::NAME_KEY, "WGS 84"),
+ GeodeticReferenceFrame::EPSG_6326, nullptr,
+ EllipsoidalCS::createLongitudeLatitude(UnitOfMeasure::DEGREE))
+ ->identify(nullptr);
+ ASSERT_EQ(res.size(), 1);
+ EXPECT_EQ(res.front().first.get(), GeographicCRS::EPSG_4326.get());
+ EXPECT_EQ(res.front().second, 25);
+ }
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(crs, geodeticcrs_identify_db) {
+ auto dbContext = DatabaseContext::create();
+ auto factory = AuthorityFactory::create(dbContext, "EPSG");
+ {
+ // No match
+ auto res =
+ GeographicCRS::create(
+ PropertyMap(),
+ GeodeticReferenceFrame::create(
+ PropertyMap(),
+ Ellipsoid::createFlattenedSphere(
+ PropertyMap(), Length(6378137), Scale(10)),
+ optional<std::string>(), PrimeMeridian::GREENWICH),
+ EllipsoidalCS::createLatitudeLongitude(UnitOfMeasure::DEGREE))
+ ->identify(nullptr);
+ ASSERT_EQ(res.size(), 0);
+ }
+ {
+ // Identify by datum code
+ auto res =
+ GeodeticCRS::create(
+ PropertyMap(), GeodeticReferenceFrame::EPSG_6326, nullptr,
+ CartesianCS::createGeocentric(UnitOfMeasure::METRE))
+ ->identify(factory);
+ ASSERT_EQ(res.size(), 1);
+ EXPECT_EQ(res.front().first->getEPSGCode(), 4978);
+ EXPECT_EQ(res.front().second, 70);
+ }
+ {
+ // Identify by datum code
+ auto res =
+ GeographicCRS::create(
+ PropertyMap().set(IdentifiedObject::NAME_KEY, "unnamed"),
+ GeodeticReferenceFrame::EPSG_6326, nullptr,
+ EllipsoidalCS::createLatitudeLongitude(UnitOfMeasure::DEGREE))
+ ->identify(factory);
+ ASSERT_EQ(res.size(), 1);
+ EXPECT_EQ(res.front().first->getEPSGCode(), 4326);
+ EXPECT_EQ(res.front().second, 70);
+ }
+ {
+ // Identify by datum code (as a fallback)
+ auto res =
+ GeographicCRS::create(
+ PropertyMap().set(IdentifiedObject::NAME_KEY, "foobar"),
+ GeodeticReferenceFrame::EPSG_6326, nullptr,
+ EllipsoidalCS::createLatitudeLongitude(UnitOfMeasure::DEGREE))
+ ->identify(factory);
+ ASSERT_EQ(res.size(), 1);
+ EXPECT_EQ(res.front().first->getEPSGCode(), 4326);
+ EXPECT_EQ(res.front().second, 70);
+ }
+ {
+ // Perfect match, and ID available. Hardcoded case
+ auto res = GeographicCRS::EPSG_4326->identify(factory);
+ ASSERT_EQ(res.size(), 1);
+ EXPECT_EQ(res.front().first.get(), GeographicCRS::EPSG_4326.get());
+ EXPECT_EQ(res.front().second, 100);
+ }
+ {
+ // Perfect match, but no ID available. Hardcoded case
+ auto res =
+ GeographicCRS::create(
+ PropertyMap().set(IdentifiedObject::NAME_KEY, "WGS 84"),
+ GeodeticReferenceFrame::EPSG_6326, nullptr,
+ EllipsoidalCS::createLatitudeLongitude(UnitOfMeasure::DEGREE))
+ ->identify(factory);
+ ASSERT_EQ(res.size(), 1);
+ EXPECT_EQ(res.front().first.get(), GeographicCRS::EPSG_4326.get());
+ EXPECT_EQ(res.front().second, 100);
+ }
+ {
+ // Perfect match, but no ID available
+ auto res =
+ GeographicCRS::create(
+ PropertyMap().set(IdentifiedObject::NAME_KEY, "NTF (Paris)"),
+ GeographicCRS::EPSG_4807->datum(), nullptr,
+ EllipsoidalCS::createLatitudeLongitude(UnitOfMeasure::GRAD))
+ ->identify(factory);
+ ASSERT_EQ(res.size(), 1);
+ EXPECT_EQ(res.front().first->getEPSGCode(), 4807);
+ EXPECT_EQ(res.front().second, 100);
+ }
+ {
+ // Perfect match, and ID available
+ auto res = GeographicCRS::EPSG_4807->identify(factory);
+ ASSERT_EQ(res.size(), 1);
+ EXPECT_EQ(res.front().first->getEPSGCode(), 4807);
+ EXPECT_EQ(res.front().second, 100);
+ }
+ {
+ // The object has an unexisting ID
+ auto res =
+ GeographicCRS::create(
+ PropertyMap()
+ .set(IdentifiedObject::NAME_KEY, "NTF (Paris)")
+ .set(Identifier::CODESPACE_KEY, "EPSG")
+ .set(Identifier::CODE_KEY, 1),
+ GeographicCRS::EPSG_4807->datum(), nullptr,
+ EllipsoidalCS::createLatitudeLongitude(UnitOfMeasure::GRAD))
+ ->identify(factory);
+ ASSERT_EQ(res.size(), 0);
+ }
+ {
+ // The object has a unreliable ID
+ auto res =
+ GeographicCRS::create(
+ PropertyMap()
+ .set(IdentifiedObject::NAME_KEY, "NTF (Paris)")
+ .set(Identifier::CODESPACE_KEY, "EPSG")
+ .set(Identifier::CODE_KEY, 4326),
+ GeographicCRS::EPSG_4807->datum(), nullptr,
+ EllipsoidalCS::createLatitudeLongitude(UnitOfMeasure::GRAD))
+ ->identify(factory);
+ ASSERT_EQ(res.size(), 1);
+ EXPECT_EQ(res.front().first->getEPSGCode(), 4326);
+ EXPECT_EQ(res.front().second, 25);
+ }
+ {
+ // Approximate match by name
+ auto res =
+ GeographicCRS::create(
+ PropertyMap().set(IdentifiedObject::NAME_KEY, "WGS84"),
+ GeodeticReferenceFrame::EPSG_6326, nullptr,
+ EllipsoidalCS::createLatitudeLongitude(UnitOfMeasure::DEGREE))
+ ->identify(factory);
+ ASSERT_EQ(res.size(), 1);
+ EXPECT_EQ(res.front().first->getEPSGCode(), 4326);
+ EXPECT_EQ(res.front().second, 90);
+ }
+ {
+ // Long Lat order
+ auto res =
+ GeographicCRS::create(
+ PropertyMap().set(IdentifiedObject::NAME_KEY, "WGS 84"),
+ GeodeticReferenceFrame::EPSG_6326, nullptr,
+ EllipsoidalCS::createLongitudeLatitude(UnitOfMeasure::DEGREE))
+ ->identify(factory);
+ ASSERT_EQ(res.size(), 3);
+ EXPECT_EQ(res.front().first->getEPSGCode(), 4326);
+ EXPECT_EQ(res.front().second, 25);
+ }
+ {
+ // Identify by ellipsoid code
+ auto res =
+ GeographicCRS::create(
+ PropertyMap(),
+ GeodeticReferenceFrame::create(PropertyMap(), Ellipsoid::WGS84,
+ optional<std::string>(),
+ PrimeMeridian::GREENWICH),
+ EllipsoidalCS::createLatitudeLongitude(UnitOfMeasure::DEGREE))
+ ->identify(factory);
+ ASSERT_GT(res.size(), 1U);
+ EXPECT_EQ(res.front().first->getEPSGCode(), 4326);
+ EXPECT_EQ(res.front().second, 60.0);
+ }
+ {
+ // Identify by ellipsoid code (as a fallback)
+ auto res =
+ GeographicCRS::create(
+ PropertyMap().set(IdentifiedObject::NAME_KEY, "unnamed"),
+ GeodeticReferenceFrame::create(PropertyMap(), Ellipsoid::WGS84,
+ optional<std::string>(),
+ PrimeMeridian::GREENWICH),
+ EllipsoidalCS::createLatitudeLongitude(UnitOfMeasure::DEGREE))
+ ->identify(factory);
+ ASSERT_GT(res.size(), 1U);
+ EXPECT_EQ(res.front().first->getEPSGCode(), 4326);
+ EXPECT_EQ(res.front().second, 60.0);
+ }
+ {
+ // Identify by ellipsoid description
+ auto obj = PROJStringParser().createFromPROJString(
+ "+proj=longlat +a=6378521.049 +rf=298.257222100883 +axis=neu");
+ auto crs = nn_dynamic_pointer_cast<GeodeticCRS>(obj);
+ ASSERT_TRUE(crs != nullptr);
+ auto factoryAll = AuthorityFactory::create(dbContext, std::string());
+ auto res = crs->identify(factoryAll);
+ EXPECT_EQ(res.size(), 5);
+ }
+ {
+ // Identify by name, without any code
+ auto wkt =
+ "GEODCRS[\"GCS_Datum_Lisboa_Bessel\",\n"
+ " DATUM[\"D_Datum_Lisboa_Bessel\",\n"
+ " ELLIPSOID[\"Bessel 1841\",6377397.155,299.1528128,\n"
+ " LENGTHUNIT[\"metre\",1]]],\n"
+ " PRIMEM[\"Greenwich\",0,\n"
+ " ANGLEUNIT[\"degree\",0.0174532925199433]],\n"
+ " CS[ellipsoidal,2],\n"
+ " AXIS[\"geodetic latitude (Lat)\",north,\n"
+ " ORDER[1],\n"
+ " ANGLEUNIT[\"degree\",0.0174532925199433]],\n"
+ " AXIS[\"geodetic longitude (Lon)\",east,\n"
+ " ORDER[2],\n"
+ " ANGLEUNIT[\"degree\",0.0174532925199433]]]";
+ auto obj = WKTParser().createFromWKT(wkt);
+ auto crs = nn_dynamic_pointer_cast<GeodeticCRS>(obj);
+ ASSERT_TRUE(crs != nullptr);
+
+ auto allFactory = AuthorityFactory::create(dbContext, std::string());
+ auto res = crs->identify(allFactory);
+ ASSERT_EQ(res.size(), 1U);
+ ASSERT_TRUE(!res.front().first->identifiers().empty());
+ EXPECT_EQ(*res.front().first->identifiers()[0]->codeSpace(), "ESRI");
+ EXPECT_EQ(res.front().first->identifiers()[0]->code(), "104105");
+ EXPECT_EQ(res.front().second, 100);
+ }
+ {
+ // Identification by non-existing code
+ auto res =
+ GeographicCRS::create(
+ PropertyMap()
+ .set(IdentifiedObject::NAME_KEY, "foobar")
+ .set(Identifier::CODESPACE_KEY, "EPSG")
+ .set(Identifier::CODE_KEY, "i_dont_exist"),
+ GeodeticReferenceFrame::create(
+ PropertyMap(),
+ Ellipsoid::createFlattenedSphere(
+ PropertyMap(), Length(6378137), Scale(10)),
+ optional<std::string>(), PrimeMeridian::GREENWICH),
+ nullptr,
+ EllipsoidalCS::createLatitudeLongitude(UnitOfMeasure::DEGREE))
+ ->identify(factory);
+ ASSERT_EQ(res.size(), 0);
+ }
+}
+
+// ---------------------------------------------------------------------------
+
+static ProjectedCRSNNPtr createProjected() {
+ PropertyMap propertiesCRS;
+ propertiesCRS.set(Identifier::CODESPACE_KEY, "EPSG")
+ .set(Identifier::CODE_KEY, 32631)
+ .set(IdentifiedObject::NAME_KEY, "WGS 84 / UTM zone 31N");
+ return ProjectedCRS::create(
+ propertiesCRS, GeographicCRS::EPSG_4326,
+ Conversion::createUTM(PropertyMap(), 31, true),
+ CartesianCS::createEastingNorthing(UnitOfMeasure::METRE));
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(crs, projectedCRS_derivingConversion) {
+ auto crs = createProjected();
+ auto conv = crs->derivingConversion();
+ EXPECT_TRUE(conv->sourceCRS() != nullptr);
+ ASSERT_TRUE(conv->targetCRS() != nullptr);
+ EXPECT_EQ(conv->targetCRS().get(), crs.get());
+
+ // derivingConversion() returns a copy of the internal conversion
+ auto targetCRSAsProjCRS =
+ std::dynamic_pointer_cast<ProjectedCRS>(conv->targetCRS());
+ ASSERT_TRUE(targetCRSAsProjCRS != nullptr);
+ EXPECT_NE(targetCRSAsProjCRS->derivingConversion(), conv);
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(crs, projectedCRS_shallowClone) {
+ {
+ auto crs = createProjected();
+ EXPECT_TRUE(crs->isEquivalentTo(crs.get()));
+ EXPECT_TRUE(!crs->isEquivalentTo(createUnrelatedObject().get()));
+ auto clone = nn_dynamic_pointer_cast<ProjectedCRS>(crs->shallowClone());
+ EXPECT_TRUE(clone->isEquivalentTo(crs.get()));
+ EXPECT_EQ(clone->derivingConversion()->targetCRS().get(), clone.get());
+ }
+
+ {
+ ProjectedCRSPtr clone;
+ {
+ auto crs = ProjectedCRS::create(
+ PropertyMap(), createGeocentric(),
+ Conversion::createUTM(PropertyMap(), 31, true),
+ CartesianCS::createEastingNorthing(UnitOfMeasure::METRE));
+ clone = nn_dynamic_pointer_cast<ProjectedCRS>(crs->shallowClone());
+ }
+ EXPECT_EQ(clone->baseCRS()->exportToPROJString(
+ PROJStringFormatter::create().get()),
+ "+proj=cart +ellps=WGS84");
+ }
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(crs, projectedCRS_as_WKT2) {
+ auto crs = createProjected();
+
+ auto expected =
+ "PROJCRS[\"WGS 84 / UTM zone 31N\",\n"
+ " BASEGEODCRS[\"WGS 84\",\n"
+ " DATUM[\"World Geodetic System 1984\",\n"
+ " ELLIPSOID[\"WGS 84\",6378137,298.257223563,\n"
+ " LENGTHUNIT[\"metre\",1]]],\n"
+ " PRIMEM[\"Greenwich\",0,\n"
+ " ANGLEUNIT[\"degree\",0.0174532925199433]]],\n"
+ " CONVERSION[\"UTM zone 31N\",\n"
+ " METHOD[\"Transverse Mercator\",\n"
+ " ID[\"EPSG\",9807]],\n"
+ " PARAMETER[\"Latitude of natural origin\",0,\n"
+ " ANGLEUNIT[\"degree\",0.0174532925199433],\n"
+ " ID[\"EPSG\",8801]],\n"
+ " PARAMETER[\"Longitude of natural origin\",3,\n"
+ " ANGLEUNIT[\"degree\",0.0174532925199433],\n"
+ " ID[\"EPSG\",8802]],\n"
+ " PARAMETER[\"Scale factor at natural origin\",0.9996,\n"
+ " SCALEUNIT[\"unity\",1],\n"
+ " ID[\"EPSG\",8805]],\n"
+ " PARAMETER[\"False easting\",500000,\n"
+ " LENGTHUNIT[\"metre\",1],\n"
+ " ID[\"EPSG\",8806]],\n"
+ " PARAMETER[\"False northing\",0,\n"
+ " LENGTHUNIT[\"metre\",1],\n"
+ " ID[\"EPSG\",8807]]],\n"
+ " CS[Cartesian,2],\n"
+ " AXIS[\"(E)\",east,\n"
+ " ORDER[1],\n"
+ " LENGTHUNIT[\"metre\",1]],\n"
+ " AXIS[\"(N)\",north,\n"
+ " ORDER[2],\n"
+ " LENGTHUNIT[\"metre\",1]],\n"
+ " ID[\"EPSG\",32631]]";
+
+ EXPECT_EQ(crs->exportToWKT(WKTFormatter::create().get()), expected);
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(crs, projectedCRS_as_WKT2_simplified) {
+ auto crs = createProjected();
+
+ auto expected =
+ "PROJCRS[\"WGS 84 / UTM zone 31N\",\n"
+ " BASEGEODCRS[\"WGS 84\",\n"
+ " DATUM[\"World Geodetic System 1984\",\n"
+ " ELLIPSOID[\"WGS 84\",6378137,298.257223563]],\n"
+ " UNIT[\"degree\",0.0174532925199433]],\n"
+ " CONVERSION[\"UTM zone 31N\",\n"
+ " METHOD[\"Transverse Mercator\"],\n"
+ " PARAMETER[\"Latitude of natural origin\",0],\n"
+ " PARAMETER[\"Longitude of natural origin\",3],\n"
+ " PARAMETER[\"Scale factor at natural origin\",0.9996],\n"
+ " PARAMETER[\"False easting\",500000],\n"
+ " PARAMETER[\"False northing\",0]],\n"
+ " CS[Cartesian,2],\n"
+ " AXIS[\"(E)\",east],\n"
+ " AXIS[\"(N)\",north],\n"
+ " UNIT[\"metre\",1],\n"
+ " ID[\"EPSG\",32631]]";
+
+ EXPECT_EQ(crs->exportToWKT(WKTFormatter::create(
+ WKTFormatter::Convention::WKT2_SIMPLIFIED)
+ .get()),
+ expected);
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(crs, projectedCRS_as_WKT2_2018_simplified) {
+ auto crs = createProjected();
+
+ auto expected =
+ "PROJCRS[\"WGS 84 / UTM zone 31N\",\n"
+ " BASEGEOGCRS[\"WGS 84\",\n"
+ " DATUM[\"World Geodetic System 1984\",\n"
+ " ELLIPSOID[\"WGS 84\",6378137,298.257223563]],\n"
+ " UNIT[\"degree\",0.0174532925199433]],\n"
+ " CONVERSION[\"UTM zone 31N\",\n"
+ " METHOD[\"Transverse Mercator\"],\n"
+ " PARAMETER[\"Latitude of natural origin\",0],\n"
+ " PARAMETER[\"Longitude of natural origin\",3],\n"
+ " PARAMETER[\"Scale factor at natural origin\",0.9996],\n"
+ " PARAMETER[\"False easting\",500000],\n"
+ " PARAMETER[\"False northing\",0]],\n"
+ " CS[Cartesian,2],\n"
+ " AXIS[\"(E)\",east],\n"
+ " AXIS[\"(N)\",north],\n"
+ " UNIT[\"metre\",1],\n"
+ " ID[\"EPSG\",32631]]";
+
+ EXPECT_EQ(
+ crs->exportToWKT(
+ WKTFormatter::create(WKTFormatter::Convention::WKT2_2018_SIMPLIFIED)
+ .get()),
+ expected);
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(crs, projectedCRS_as_WKT1_GDAL) {
+ auto crs = createProjected();
+
+ auto expected = "PROJCS[\"WGS 84 / UTM zone 31N\",\n"
+ " GEOGCS[\"WGS 84\",\n"
+ " DATUM[\"WGS_1984\",\n"
+ " SPHEROID[\"WGS 84\",6378137,298.257223563,\n"
+ " AUTHORITY[\"EPSG\",\"7030\"]],\n"
+ " AUTHORITY[\"EPSG\",\"6326\"]],\n"
+ " PRIMEM[\"Greenwich\",0,\n"
+ " AUTHORITY[\"EPSG\",\"8901\"]],\n"
+ " UNIT[\"degree\",0.0174532925199433,\n"
+ " AUTHORITY[\"EPSG\",\"9122\"]],\n"
+ " AXIS[\"Latitude\",NORTH],\n"
+ " AXIS[\"Longitude\",EAST],\n"
+ " AUTHORITY[\"EPSG\",\"4326\"]],\n"
+ " PROJECTION[\"Transverse_Mercator\"],\n"
+ " PARAMETER[\"latitude_of_origin\",0],\n"
+ " PARAMETER[\"central_meridian\",3],\n"
+ " PARAMETER[\"scale_factor\",0.9996],\n"
+ " PARAMETER[\"false_easting\",500000],\n"
+ " PARAMETER[\"false_northing\",0],\n"
+ " UNIT[\"metre\",1,\n"
+ " AUTHORITY[\"EPSG\",\"9001\"]],\n"
+ " AXIS[\"Easting\",EAST],\n"
+ " AXIS[\"Northing\",NORTH],\n"
+ " AUTHORITY[\"EPSG\",\"32631\"]]";
+
+ EXPECT_EQ(
+ crs->exportToWKT(
+ WKTFormatter::create(WKTFormatter::Convention::WKT1_GDAL).get()),
+ expected);
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(crs, projectedCRS_as_WKT1_ESRI) {
+ auto crs = createProjected();
+
+ auto expected = "PROJCS[\"WGS_1984_UTM_Zone_31N\",GEOGCS[\"GCS_WGS_1984\","
+ "DATUM[\"D_WGS_1984\",SPHEROID[\"WGS_1984\",6378137.0,"
+ "298.257223563]],PRIMEM[\"Greenwich\",0.0],"
+ "UNIT[\"Degree\",0.0174532925199433]],"
+ "PROJECTION[\"Transverse_Mercator\"],"
+ "PARAMETER[\"False_Easting\",500000.0],"
+ "PARAMETER[\"False_Northing\",0.0],"
+ "PARAMETER[\"Central_Meridian\",3.0],"
+ "PARAMETER[\"Scale_Factor\",0.9996],"
+ "PARAMETER[\"Latitude_Of_Origin\",0.0],"
+ "UNIT[\"Meter\",1.0]]";
+
+ EXPECT_EQ(crs->exportToWKT(
+ WKTFormatter::create(WKTFormatter::Convention::WKT1_ESRI,
+ DatabaseContext::create())
+ .get()),
+ expected);
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(crs, projectedCRS_as_PROJ_string) {
+ auto crs = createProjected();
+ EXPECT_EQ(crs->exportToPROJString(PROJStringFormatter::create().get()),
+ "+proj=pipeline +step +proj=axisswap +order=2,1 +step "
+ "+proj=unitconvert +xy_in=deg +xy_out=rad +step +proj=utm "
+ "+zone=31 +ellps=WGS84");
+ EXPECT_EQ(
+ crs->exportToPROJString(
+ PROJStringFormatter::create(PROJStringFormatter::Convention::PROJ_4)
+ .get()),
+ "+proj=utm +zone=31 +datum=WGS84");
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(crs, projectedCRS_identify_no_db) {
+ {
+ // Hard-coded case: WGS 84 / UTM. No name
+ auto res = ProjectedCRS::create(
+ PropertyMap(), GeographicCRS::EPSG_4326,
+ Conversion::createUTM(PropertyMap(), 1, true),
+ CartesianCS::createEastingNorthing(UnitOfMeasure::METRE))
+ ->identify(nullptr);
+ ASSERT_EQ(res.size(), 1);
+ EXPECT_EQ(res.front().first->getEPSGCode(), 32601);
+ EXPECT_EQ(res.front().second, 70);
+ EXPECT_TRUE(res.front().first->isEquivalentTo(
+ AuthorityFactory::create(DatabaseContext::create(), "EPSG")
+ ->createProjectedCRS("32601")
+ .get(),
+ IComparable::Criterion::EQUIVALENT));
+ }
+ {
+ // Hard-coded case: WGS 84 / UTM (south). Exact name.
+ // Using virtual method
+ auto crs = ProjectedCRS::create(
+ PropertyMap().set(IdentifiedObject::NAME_KEY,
+ "WGS 84 / UTM zone 60S"),
+ GeographicCRS::EPSG_4326,
+ Conversion::createUTM(PropertyMap(), 60, false),
+ CartesianCS::createEastingNorthing(UnitOfMeasure::METRE));
+ auto res = static_cast<const CRS *>(crs.get())->identify(nullptr);
+ ASSERT_EQ(res.size(), 1);
+ EXPECT_EQ(res.front().first->getEPSGCode(), 32760);
+ EXPECT_EQ(res.front().second, 100);
+ EXPECT_TRUE(res.front().first->isEquivalentTo(
+ AuthorityFactory::create(DatabaseContext::create(), "EPSG")
+ ->createProjectedCRS("32760")
+ .get(),
+ IComparable::Criterion::EQUIVALENT));
+ }
+ {
+ // Hard-coded case: NAD27 / UTM. Approximate name.
+ auto res = ProjectedCRS::create(
+ PropertyMap().set(IdentifiedObject::NAME_KEY,
+ "NAD27_UTM_zone_11N"),
+ GeographicCRS::EPSG_4267,
+ Conversion::createUTM(PropertyMap(), 11, true),
+ CartesianCS::createEastingNorthing(UnitOfMeasure::METRE))
+ ->identify(nullptr);
+ ASSERT_EQ(res.size(), 1);
+ EXPECT_EQ(res.front().first->getEPSGCode(), 26711);
+ EXPECT_EQ(res.front().second, 90);
+ EXPECT_TRUE(res.front().first->isEquivalentTo(
+ AuthorityFactory::create(DatabaseContext::create(), "EPSG")
+ ->createProjectedCRS("26711")
+ .get(),
+ IComparable::Criterion::EQUIVALENT));
+ }
+ {
+ // Hard-coded case: NAD83 / UTM
+ auto res = ProjectedCRS::create(
+ PropertyMap(), GeographicCRS::EPSG_4269,
+ Conversion::createUTM(PropertyMap(), 11, true),
+ CartesianCS::createEastingNorthing(UnitOfMeasure::METRE))
+ ->identify(nullptr);
+ ASSERT_EQ(res.size(), 1);
+ EXPECT_EQ(res.front().first->getEPSGCode(), 26911);
+ EXPECT_EQ(res.front().second, 70);
+ EXPECT_TRUE(res.front().first->isEquivalentTo(
+ AuthorityFactory::create(DatabaseContext::create(), "EPSG")
+ ->createProjectedCRS("26911")
+ .get(),
+ IComparable::Criterion::EQUIVALENT));
+ }
+ {
+ // Tolerance on axis order
+ auto obj = PROJStringParser().createFromPROJString(
+ "+proj=utm +zone=31 +datum=WGS84");
+ auto crs = nn_dynamic_pointer_cast<ProjectedCRS>(obj);
+ ASSERT_TRUE(crs != nullptr);
+ auto res = crs->identify(nullptr);
+ ASSERT_EQ(res.size(), 1);
+ EXPECT_EQ(res.front().first->getEPSGCode(), 32631);
+ EXPECT_EQ(res.front().second, 70);
+ EXPECT_TRUE(res.front().first->isEquivalentTo(
+ crs.get(),
+ IComparable::Criterion::EQUIVALENT_EXCEPT_AXIS_ORDER_GEOGCRS));
+ }
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(crs, projectedCRS_identify_db) {
+ auto dbContext = DatabaseContext::create();
+ auto factoryEPSG = AuthorityFactory::create(dbContext, "EPSG");
+ {
+ // Identify by existing code
+ auto res =
+ factoryEPSG->createProjectedCRS("2172")->identify(factoryEPSG);
+ ASSERT_EQ(res.size(), 1);
+ EXPECT_EQ(res.front().first->getEPSGCode(), 2172);
+ EXPECT_EQ(res.front().second, 100);
+ }
+ {
+ // Non-existing code
+ auto sourceCRS = factoryEPSG->createProjectedCRS("2172");
+ auto crs = ProjectedCRS::create(
+ PropertyMap()
+ .set(IdentifiedObject::NAME_KEY,
+ "Pulkovo 1942(58) / Poland zone II")
+ .set(Identifier::CODESPACE_KEY, "EPSG")
+ .set(Identifier::CODE_KEY, 1),
+ sourceCRS->baseCRS(), sourceCRS->derivingConversion(),
+ sourceCRS->coordinateSystem());
+ auto res = crs->identify(factoryEPSG);
+ EXPECT_EQ(res.size(), 0);
+ }
+ {
+ // Existing code, but not matching content
+ auto sourceCRS = factoryEPSG->createProjectedCRS("2172");
+ auto crs = ProjectedCRS::create(
+ PropertyMap()
+ .set(IdentifiedObject::NAME_KEY,
+ "Pulkovo 1942(58) / Poland zone II")
+ .set(Identifier::CODESPACE_KEY, "EPSG")
+ .set(Identifier::CODE_KEY, 32631),
+ sourceCRS->baseCRS(), sourceCRS->derivingConversion(),
+ sourceCRS->coordinateSystem());
+ auto res = crs->identify(factoryEPSG);
+ ASSERT_EQ(res.size(), 1);
+ EXPECT_EQ(res.front().first->getEPSGCode(), 32631);
+ EXPECT_EQ(res.front().second, 25);
+ }
+ {
+ // Identify by exact name
+ auto sourceCRS = factoryEPSG->createProjectedCRS("2172");
+ auto crs = ProjectedCRS::create(
+ PropertyMap().set(IdentifiedObject::NAME_KEY,
+ "Pulkovo 1942(58) / Poland zone II"),
+ sourceCRS->baseCRS(), sourceCRS->derivingConversion(),
+ sourceCRS->coordinateSystem());
+ auto res = crs->identify(factoryEPSG);
+ ASSERT_EQ(res.size(), 1);
+ EXPECT_EQ(res.front().first->getEPSGCode(), 2172);
+ EXPECT_EQ(res.front().second, 100);
+ }
+ {
+ // Identify by equivalent name
+ auto sourceCRS = factoryEPSG->createProjectedCRS("2172");
+ auto crs = ProjectedCRS::create(
+ PropertyMap().set(IdentifiedObject::NAME_KEY,
+ "Pulkovo_1942_58_Poland_zone_II"),
+ sourceCRS->baseCRS(), sourceCRS->derivingConversion(),
+ sourceCRS->coordinateSystem());
+ auto res = crs->identify(factoryEPSG);
+ ASSERT_EQ(res.size(), 1);
+ EXPECT_EQ(res.front().first->getEPSGCode(), 2172);
+ EXPECT_EQ(res.front().second, 90);
+ }
+ {
+ // Identify by properties
+ auto sourceCRS = factoryEPSG->createProjectedCRS("2172");
+ auto crs = ProjectedCRS::create(
+ PropertyMap().set(IdentifiedObject::NAME_KEY, "i am a faked name"),
+ sourceCRS->baseCRS(), sourceCRS->derivingConversion(),
+ sourceCRS->coordinateSystem());
+ auto res = crs->identify(factoryEPSG);
+ ASSERT_EQ(res.size(), 1);
+ EXPECT_EQ(res.front().first->getEPSGCode(), 2172);
+ EXPECT_EQ(res.front().second, 70);
+ }
+ {
+ // Identify by name, but objects aren't equivalent
+ auto sourceCRS = factoryEPSG->createProjectedCRS("3375");
+ auto crs = ProjectedCRS::create(
+ PropertyMap().set(IdentifiedObject::NAME_KEY,
+ "Pulkovo 1942(58) / Poland zone II"),
+ sourceCRS->baseCRS(), sourceCRS->derivingConversion(),
+ sourceCRS->coordinateSystem());
+ auto res = crs->identify(factoryEPSG);
+ ASSERT_EQ(res.size(), 1);
+ EXPECT_EQ(res.front().first->getEPSGCode(), 2172);
+ EXPECT_EQ(res.front().second, 25);
+ }
+ {
+ // Identify from a PROJ string
+ auto obj = PROJStringParser().createFromPROJString(
+ "+proj=pipeline +step +proj=axisswap +order=2,1 +step "
+ "+proj=unitconvert +xy_in=deg +xy_out=rad +step +proj=omerc "
+ "+no_uoff +lat_0=4 +lonc=102.25 +alpha=323.025796466667 "
+ "+gamma=323.130102361111 +k=0.99984 +x_0=804671 +y_0=0 "
+ "+ellps=GRS80");
+ auto crs = nn_dynamic_pointer_cast<ProjectedCRS>(obj);
+ ASSERT_TRUE(crs != nullptr);
+ auto res = crs->identify(factoryEPSG);
+ ASSERT_EQ(res.size(), 1);
+ EXPECT_EQ(res.front().first->getEPSGCode(), 3375);
+ EXPECT_EQ(res.front().second, 70);
+ }
+ {
+ // Identify from a PROJ string (with "wrong" axis order for the geodetic
+ // part)
+ auto obj = PROJStringParser().createFromPROJString(
+ "+proj=omerc +no_uoff +lat_0=4 +lonc=102.25 "
+ "+alpha=323.025796466667 +gamma=323.130102361111 +k=0.99984 "
+ "+x_0=804671 +y_0=0 +ellps=GRS80");
+ auto crs = nn_dynamic_pointer_cast<ProjectedCRS>(obj);
+ ASSERT_TRUE(crs != nullptr);
+ auto res = crs->identify(factoryEPSG);
+ ASSERT_EQ(res.size(), 1);
+ EXPECT_EQ(res.front().first->getEPSGCode(), 3375);
+ EXPECT_EQ(res.front().second, 70);
+ }
+ {
+ // No equivalent CRS to input one in result set
+ auto obj =
+ PROJStringParser().createFromPROJString("+proj=tmerc +datum=WGS84");
+ auto crs = nn_dynamic_pointer_cast<ProjectedCRS>(obj);
+ ASSERT_TRUE(crs != nullptr);
+ auto res = crs->identify(factoryEPSG);
+ ASSERT_EQ(res.size(), 0);
+ }
+ {
+ // ESRI:103729 definition as a PROJ string
+ auto obj =
+ PROJStringParser()
+ .attachDatabaseContext(dbContext)
+ .createFromPROJString(
+ "+proj=lcc +lat_0=43.5 +lon_0=-93.95 "
+ "+lat_1=43.5666666666667 "
+ "+lat_2=43.8 +x_0=152400.30480061 +y_0=30480.0609601219 "
+ "+a=6378521.049 +rf=298.257222100883 +units=us-ft");
+ auto crs = nn_dynamic_pointer_cast<ProjectedCRS>(obj);
+ ASSERT_TRUE(crs != nullptr);
+ auto factoryAll = AuthorityFactory::create(dbContext, std::string());
+ auto res = crs->identify(factoryAll);
+ EXPECT_GE(res.size(), 1U);
+ bool found = false;
+ for (const auto &pair : res) {
+ if (pair.first->identifiers()[0]->code() == "103729") {
+ found = true;
+ EXPECT_EQ(pair.second, 70);
+ break;
+ }
+ }
+ EXPECT_TRUE(found);
+ }
+ {
+ // EPSG:2327 as PROJ.4 string (so with easting, northing order whereas
+ // official CRS is northing, easting)
+ auto obj =
+ PROJStringParser()
+ .attachDatabaseContext(dbContext)
+ .createFromPROJString(
+ "+proj=tmerc +lat_0=0 +lon_0=75 +k=1 +x_0=13500000 +y_0=0 "
+ "+a=6378140 +b=6356755.288157528 +units=m");
+ auto crs = nn_dynamic_pointer_cast<ProjectedCRS>(obj);
+ ASSERT_TRUE(crs != nullptr);
+ auto res = crs->identify(factoryEPSG);
+ EXPECT_EQ(res.size(), 1);
+ EXPECT_EQ(res.front().first->getEPSGCode(), 2327);
+ EXPECT_EQ(res.front().second, 50);
+ }
+ {
+ // EPSG:6646 as PROJ.4 string, using clrk80 which is pretty generic
+ auto obj =
+ PROJStringParser()
+ .attachDatabaseContext(dbContext)
+ .createFromPROJString(
+ "+proj=tmerc +lat_0=29.02626833333333 +lon_0=46.5 "
+ "+k=0.9994 +x_0=800000 +y_0=0 +ellps=clrk80 +units=m");
+ auto crs = nn_dynamic_pointer_cast<ProjectedCRS>(obj);
+ ASSERT_TRUE(crs != nullptr);
+ auto res = crs->identify(factoryEPSG);
+ EXPECT_EQ(res.size(), 1);
+ EXPECT_EQ(res.front().first->getEPSGCode(), 6646);
+ EXPECT_EQ(res.front().second, 70);
+ }
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(crs, mercator_1SP_as_WKT1_ESRI) {
+
+ auto obj = PROJStringParser().createFromPROJString(
+ "+proj=merc +lon_0=110 +k=0.997 +x_0=3900000 +y_0=900000 "
+ "+ellps=bessel");
+ auto crs = nn_dynamic_pointer_cast<ProjectedCRS>(obj);
+ ASSERT_TRUE(crs != nullptr);
+
+ auto expected = "PROJCS[\"unknown\",GEOGCS[\"GCS_unknown\","
+ "DATUM[\"D_Unknown_based_on_Bessel_1841_ellipsoid\","
+ "SPHEROID[\"Bessel_1841\",6377397.155,299.1528128]],"
+ "PRIMEM[\"Greenwich\",0.0],"
+ "UNIT[\"Degree\",0.0174532925199433]],"
+ "PROJECTION[\"Mercator\"],"
+ "PARAMETER[\"False_Easting\",3900000.0],"
+ "PARAMETER[\"False_Northing\",900000.0],"
+ "PARAMETER[\"Central_Meridian\",110.0],"
+ "PARAMETER[\"Standard_Parallel_1\",4.45405154589748],"
+ "UNIT[\"Meter\",1.0]]";
+ EXPECT_EQ(crs->exportToWKT(
+ WKTFormatter::create(WKTFormatter::Convention::WKT1_ESRI,
+ DatabaseContext::create())
+ .get()),
+ expected);
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(crs, Plate_Carree_as_WKT1_ESRI) {
+
+ auto obj = PROJStringParser().createFromPROJString(
+ "+title=my Plate carree +proj=eqc");
+ auto crs = nn_dynamic_pointer_cast<ProjectedCRS>(obj);
+ ASSERT_TRUE(crs != nullptr);
+
+ auto expected = "PROJCS[\"my_Plate_carree\",GEOGCS[\"GCS_unknown\","
+ "DATUM[\"D_WGS_1984\",SPHEROID[\"WGS_1984\","
+ "6378137.0,298.257223563]],PRIMEM[\"Greenwich\",0.0],"
+ "UNIT[\"Degree\",0.0174532925199433]],"
+ "PROJECTION[\"Plate_Carree\"],"
+ "PARAMETER[\"False_Easting\",0.0],"
+ "PARAMETER[\"False_Northing\",0.0],"
+ "PARAMETER[\"Central_Meridian\",0.0],"
+ "UNIT[\"Meter\",1.0]]";
+ EXPECT_EQ(crs->exportToWKT(
+ WKTFormatter::create(WKTFormatter::Convention::WKT1_ESRI,
+ DatabaseContext::create())
+ .get()),
+ expected);
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(crs, Equidistant_Cylindrical_as_WKT1_ESRI) {
+
+ auto obj = PROJStringParser().createFromPROJString("+proj=eqc");
+ auto crs = nn_dynamic_pointer_cast<ProjectedCRS>(obj);
+ ASSERT_TRUE(crs != nullptr);
+
+ auto expected = "PROJCS[\"unknown\",GEOGCS[\"GCS_unknown\","
+ "DATUM[\"D_WGS_1984\",SPHEROID[\"WGS_1984\","
+ "6378137.0,298.257223563]],PRIMEM[\"Greenwich\",0.0],"
+ "UNIT[\"Degree\",0.0174532925199433]],"
+ "PROJECTION[\"Equidistant_Cylindrical\"],"
+ "PARAMETER[\"False_Easting\",0.0],"
+ "PARAMETER[\"False_Northing\",0.0],"
+ "PARAMETER[\"Central_Meridian\",0.0],"
+ "PARAMETER[\"Standard_Parallel_1\",0.0],"
+ "UNIT[\"Meter\",1.0]]";
+ EXPECT_EQ(crs->exportToWKT(
+ WKTFormatter::create(WKTFormatter::Convention::WKT1_ESRI,
+ DatabaseContext::create())
+ .get()),
+ expected);
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(crs, Hotine_Oblique_Mercator_Azimuth_Natural_Origin_as_WKT1_ESRI) {
+
+ auto obj = PROJStringParser().createFromPROJString(
+ "+proj=omerc +no_uoff +gamma=295 +alpha=295");
+ auto crs = nn_dynamic_pointer_cast<ProjectedCRS>(obj);
+ ASSERT_TRUE(crs != nullptr);
+
+ auto expected = "PROJCS[\"unknown\",GEOGCS[\"GCS_unknown\","
+ "DATUM[\"D_WGS_1984\",SPHEROID[\"WGS_1984\","
+ "6378137.0,298.257223563]],PRIMEM[\"Greenwich\",0.0],"
+ "UNIT[\"Degree\",0.0174532925199433]],"
+ "PROJECTION["
+ "\"Hotine_Oblique_Mercator_Azimuth_Natural_Origin\"],"
+ "PARAMETER[\"False_Easting\",0.0],"
+ "PARAMETER[\"False_Northing\",0.0],"
+ "PARAMETER[\"Scale_Factor\",1.0],"
+ // we renormalize angles to [-180,180]
+ "PARAMETER[\"Azimuth\",-65.0],"
+ "PARAMETER[\"Longitude_Of_Center\",0.0],"
+ "PARAMETER[\"Latitude_Of_Center\",0.0],"
+ "UNIT[\"Meter\",1.0]]";
+ EXPECT_EQ(crs->exportToWKT(
+ WKTFormatter::create(WKTFormatter::Convention::WKT1_ESRI,
+ DatabaseContext::create())
+ .get()),
+ expected);
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(crs, Rectified_Skew_Orthomorphic_Natural_Origin_as_WKT1_ESRI) {
+
+ auto obj = PROJStringParser().createFromPROJString(
+ "+proj=omerc +no_uoff +gamma=3 +alpha=2");
+ auto crs = nn_dynamic_pointer_cast<ProjectedCRS>(obj);
+ ASSERT_TRUE(crs != nullptr);
+
+ auto expected = "PROJCS[\"unknown\",GEOGCS[\"GCS_unknown\","
+ "DATUM[\"D_WGS_1984\",SPHEROID[\"WGS_1984\","
+ "6378137.0,298.257223563]],PRIMEM[\"Greenwich\",0.0],"
+ "UNIT[\"Degree\",0.0174532925199433]],"
+ "PROJECTION["
+ "\"Rectified_Skew_Orthomorphic_Natural_Origin\"],"
+ "PARAMETER[\"False_Easting\",0.0],"
+ "PARAMETER[\"False_Northing\",0.0],"
+ "PARAMETER[\"Scale_Factor\",1.0],"
+ "PARAMETER[\"Azimuth\",2.0],"
+ "PARAMETER[\"Longitude_Of_Center\",0.0],"
+ "PARAMETER[\"Latitude_Of_Center\",0.0],"
+ "PARAMETER[\"XY_Plane_Rotation\",3.0],"
+ "UNIT[\"Meter\",1.0]]";
+ EXPECT_EQ(crs->exportToWKT(
+ WKTFormatter::create(WKTFormatter::Convention::WKT1_ESRI,
+ DatabaseContext::create())
+ .get()),
+ expected);
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(crs, Hotine_Oblique_Mercator_Azimuth_Center_as_WKT1_ESRI) {
+
+ auto obj = PROJStringParser().createFromPROJString(
+ "+proj=omerc +gamma=2 +alpha=2");
+ auto crs = nn_dynamic_pointer_cast<ProjectedCRS>(obj);
+ ASSERT_TRUE(crs != nullptr);
+
+ auto expected = "PROJCS[\"unknown\",GEOGCS[\"GCS_unknown\","
+ "DATUM[\"D_WGS_1984\",SPHEROID[\"WGS_1984\","
+ "6378137.0,298.257223563]],PRIMEM[\"Greenwich\",0.0],"
+ "UNIT[\"Degree\",0.0174532925199433]],"
+ "PROJECTION["
+ "\"Hotine_Oblique_Mercator_Azimuth_Center\"],"
+ "PARAMETER[\"False_Easting\",0.0],"
+ "PARAMETER[\"False_Northing\",0.0],"
+ "PARAMETER[\"Scale_Factor\",1.0],"
+ "PARAMETER[\"Azimuth\",2.0],"
+ "PARAMETER[\"Longitude_Of_Center\",0.0],"
+ "PARAMETER[\"Latitude_Of_Center\",0.0],"
+ "UNIT[\"Meter\",1.0]]";
+ EXPECT_EQ(crs->exportToWKT(
+ WKTFormatter::create(WKTFormatter::Convention::WKT1_ESRI,
+ DatabaseContext::create())
+ .get()),
+ expected);
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(crs, Rectified_Skew_Orthomorphic_Center_as_WKT1_ESRI) {
+
+ auto obj = PROJStringParser().createFromPROJString(
+ "+proj=omerc +gamma=3 +alpha=2");
+ auto crs = nn_dynamic_pointer_cast<ProjectedCRS>(obj);
+ ASSERT_TRUE(crs != nullptr);
+
+ auto expected = "PROJCS[\"unknown\",GEOGCS[\"GCS_unknown\","
+ "DATUM[\"D_WGS_1984\",SPHEROID[\"WGS_1984\","
+ "6378137.0,298.257223563]],PRIMEM[\"Greenwich\",0.0],"
+ "UNIT[\"Degree\",0.0174532925199433]],"
+ "PROJECTION["
+ "\"Rectified_Skew_Orthomorphic_Center\"],"
+ "PARAMETER[\"False_Easting\",0.0],"
+ "PARAMETER[\"False_Northing\",0.0],"
+ "PARAMETER[\"Scale_Factor\",1.0],"
+ "PARAMETER[\"Azimuth\",2.0],"
+ "PARAMETER[\"Longitude_Of_Center\",0.0],"
+ "PARAMETER[\"Latitude_Of_Center\",0.0],"
+ "PARAMETER[\"XY_Plane_Rotation\",3.0],"
+ "UNIT[\"Meter\",1.0]]";
+ EXPECT_EQ(crs->exportToWKT(
+ WKTFormatter::create(WKTFormatter::Convention::WKT1_ESRI,
+ DatabaseContext::create())
+ .get()),
+ expected);
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(crs, Gauss_Kruger_as_WKT1_ESRI) {
+
+ auto obj = PROJStringParser().createFromPROJString(
+ "+title=my Gauss Kruger +proj=tmerc");
+ auto crs = nn_dynamic_pointer_cast<ProjectedCRS>(obj);
+ ASSERT_TRUE(crs != nullptr);
+
+ auto expected = "PROJCS[\"my_Gauss_Kruger\",GEOGCS[\"GCS_unknown\","
+ "DATUM[\"D_WGS_1984\",SPHEROID[\"WGS_1984\",6378137.0,"
+ "298.257223563]],PRIMEM[\"Greenwich\",0.0],"
+ "UNIT[\"Degree\",0.0174532925199433]],"
+ "PROJECTION[\"Gauss_Kruger\"],"
+ "PARAMETER[\"False_Easting\",0.0],"
+ "PARAMETER[\"False_Northing\",0.0],"
+ "PARAMETER[\"Central_Meridian\",0.0],"
+ "PARAMETER[\"Scale_Factor\",1.0],"
+ "PARAMETER[\"Latitude_Of_Origin\",0.0],"
+ "UNIT[\"Meter\",1.0]]";
+ EXPECT_EQ(crs->exportToWKT(
+ WKTFormatter::create(WKTFormatter::Convention::WKT1_ESRI,
+ DatabaseContext::create())
+ .get()),
+ expected);
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(crs, Stereographic_North_Pole_as_WKT1_ESRI) {
+
+ auto obj = PROJStringParser().createFromPROJString(
+ "+proj=stere +lat_0=90 +lat_ts=70");
+ auto crs = nn_dynamic_pointer_cast<ProjectedCRS>(obj);
+ ASSERT_TRUE(crs != nullptr);
+
+ auto expected = "PROJCS[\"unknown\",GEOGCS[\"GCS_unknown\","
+ "DATUM[\"D_WGS_1984\",SPHEROID[\"WGS_1984\","
+ "6378137.0,298.257223563]],PRIMEM[\"Greenwich\",0.0],"
+ "UNIT[\"Degree\",0.0174532925199433]],"
+ "PROJECTION[\"Stereographic_North_Pole\"],"
+ "PARAMETER[\"False_Easting\",0.0],"
+ "PARAMETER[\"False_Northing\",0.0],"
+ "PARAMETER[\"Central_Meridian\",0.0],"
+ "PARAMETER[\"Standard_Parallel_1\",70.0],"
+ "UNIT[\"Meter\",1.0]]";
+ EXPECT_EQ(crs->exportToWKT(
+ WKTFormatter::create(WKTFormatter::Convention::WKT1_ESRI,
+ DatabaseContext::create())
+ .get()),
+ expected);
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(crs, Stereographic_South_Pole_as_WKT1_ESRI) {
+
+ auto obj = PROJStringParser().createFromPROJString(
+ "+proj=stere +lat_0=-90 +lat_ts=-70");
+ auto crs = nn_dynamic_pointer_cast<ProjectedCRS>(obj);
+ ASSERT_TRUE(crs != nullptr);
+
+ auto expected = "PROJCS[\"unknown\",GEOGCS[\"GCS_unknown\","
+ "DATUM[\"D_WGS_1984\",SPHEROID[\"WGS_1984\","
+ "6378137.0,298.257223563]],PRIMEM[\"Greenwich\",0.0],"
+ "UNIT[\"Degree\",0.0174532925199433]],"
+ "PROJECTION[\"Stereographic_South_Pole\"],"
+ "PARAMETER[\"False_Easting\",0.0],"
+ "PARAMETER[\"False_Northing\",0.0],"
+ "PARAMETER[\"Central_Meridian\",0.0],"
+ "PARAMETER[\"Standard_Parallel_1\",-70.0],"
+ "UNIT[\"Meter\",1.0]]";
+ EXPECT_EQ(crs->exportToWKT(
+ WKTFormatter::create(WKTFormatter::Convention::WKT1_ESRI,
+ DatabaseContext::create())
+ .get()),
+ expected);
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(crs, Krovak_North_Orientated_as_WKT1_ESRI) {
+
+ auto obj = PROJStringParser().createFromPROJString("+proj=krovak");
+ auto crs = nn_dynamic_pointer_cast<ProjectedCRS>(obj);
+ ASSERT_TRUE(crs != nullptr);
+
+ auto expected = "PROJCS[\"unknown\",GEOGCS[\"GCS_unknown\","
+ "DATUM[\"D_WGS_1984\",SPHEROID[\"WGS_1984\","
+ "6378137.0,298.257223563]],PRIMEM[\"Greenwich\",0.0],"
+ "UNIT[\"Degree\",0.0174532925199433]],"
+ "PROJECTION[\"Krovak\"],"
+ "PARAMETER[\"False_Easting\",0.0],"
+ "PARAMETER[\"False_Northing\",0.0],"
+ "PARAMETER[\"Pseudo_Standard_Parallel_1\",78.5],"
+ "PARAMETER[\"Scale_Factor\",1.0],"
+ "PARAMETER[\"Azimuth\",30.2881397222222],"
+ "PARAMETER[\"Longitude_Of_Center\",0.0],"
+ "PARAMETER[\"Latitude_Of_Center\",0.0],"
+ "PARAMETER[\"X_Scale\",-1.0],"
+ "PARAMETER[\"Y_Scale\",1.0],"
+ "PARAMETER[\"XY_Plane_Rotation\",90.0],"
+ "UNIT[\"Meter\",1.0]]";
+ EXPECT_EQ(crs->exportToWKT(
+ WKTFormatter::create(WKTFormatter::Convention::WKT1_ESRI,
+ DatabaseContext::create())
+ .get()),
+ expected);
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(crs, Krovak_as_WKT1_ESRI) {
+
+ auto obj =
+ PROJStringParser().createFromPROJString("+proj=krovak +axis=swu");
+ auto crs = nn_dynamic_pointer_cast<ProjectedCRS>(obj);
+ ASSERT_TRUE(crs != nullptr);
+
+ auto expected = "PROJCS[\"unknown\",GEOGCS[\"GCS_unknown\","
+ "DATUM[\"D_WGS_1984\",SPHEROID[\"WGS_1984\","
+ "6378137.0,298.257223563]],PRIMEM[\"Greenwich\",0.0],"
+ "UNIT[\"Degree\",0.0174532925199433]],"
+ "PROJECTION[\"Krovak\"],"
+ "PARAMETER[\"False_Easting\",0.0],"
+ "PARAMETER[\"False_Northing\",0.0],"
+ "PARAMETER[\"Pseudo_Standard_Parallel_1\",78.5],"
+ "PARAMETER[\"Scale_Factor\",1.0],"
+ "PARAMETER[\"Azimuth\",30.2881397222222],"
+ "PARAMETER[\"Longitude_Of_Center\",0.0],"
+ "PARAMETER[\"Latitude_Of_Center\",0.0],"
+ "PARAMETER[\"X_Scale\",1.0],"
+ "PARAMETER[\"Y_Scale\",1.0],"
+ "PARAMETER[\"XY_Plane_Rotation\",0.0],"
+ "UNIT[\"Meter\",1.0]]";
+ EXPECT_EQ(crs->exportToWKT(
+ WKTFormatter::create(WKTFormatter::Convention::WKT1_ESRI,
+ DatabaseContext::create())
+ .get()),
+ expected);
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(crs, LCC_1SP_as_WKT1_ESRI) {
+
+ auto obj = PROJStringParser().createFromPROJString(
+ "+proj=lcc +lat_1=1 +lat_0=1 +k=0.9");
+ auto crs = nn_dynamic_pointer_cast<ProjectedCRS>(obj);
+ ASSERT_TRUE(crs != nullptr);
+
+ auto expected = "PROJCS[\"unknown\",GEOGCS[\"GCS_unknown\","
+ "DATUM[\"D_WGS_1984\",SPHEROID[\"WGS_1984\","
+ "6378137.0,298.257223563]],PRIMEM[\"Greenwich\",0.0],"
+ "UNIT[\"Degree\",0.0174532925199433]],"
+ "PROJECTION[\"Lambert_Conformal_Conic\"],"
+ "PARAMETER[\"False_Easting\",0.0],"
+ "PARAMETER[\"False_Northing\",0.0],"
+ "PARAMETER[\"Central_Meridian\",0.0],"
+ "PARAMETER[\"Standard_Parallel_1\",1.0],"
+ "PARAMETER[\"Scale_Factor\",0.9],"
+ "PARAMETER[\"Latitude_Of_Origin\",1.0],"
+ "UNIT[\"Meter\",1.0]]";
+ EXPECT_EQ(crs->exportToWKT(
+ WKTFormatter::create(WKTFormatter::Convention::WKT1_ESRI,
+ DatabaseContext::create())
+ .get()),
+ expected);
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(crs, LCC_2SP_as_WKT1_ESRI) {
+
+ auto obj = PROJStringParser().createFromPROJString(
+ "+proj=lcc +lat_0=1.5 +lat_1=1 +lat_2=2");
+ auto crs = nn_dynamic_pointer_cast<ProjectedCRS>(obj);
+ ASSERT_TRUE(crs != nullptr);
+
+ auto expected = "PROJCS[\"unknown\",GEOGCS[\"GCS_unknown\","
+ "DATUM[\"D_WGS_1984\",SPHEROID[\"WGS_1984\","
+ "6378137.0,298.257223563]],PRIMEM[\"Greenwich\",0.0],"
+ "UNIT[\"Degree\",0.0174532925199433]],"
+ "PROJECTION[\"Lambert_Conformal_Conic\"],"
+ "PARAMETER[\"False_Easting\",0.0],"
+ "PARAMETER[\"False_Northing\",0.0],"
+ "PARAMETER[\"Central_Meridian\",0.0],"
+ "PARAMETER[\"Standard_Parallel_1\",1.0],"
+ "PARAMETER[\"Standard_Parallel_2\",2.0],"
+ "PARAMETER[\"Latitude_Of_Origin\",1.5],"
+ "UNIT[\"Meter\",1.0]]";
+ EXPECT_EQ(crs->exportToWKT(
+ WKTFormatter::create(WKTFormatter::Convention::WKT1_ESRI,
+ DatabaseContext::create())
+ .get()),
+ expected);
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(crs, ESRI_WKT1_to_ESRI_WKT1) {
+
+ auto in_wkt =
+ "PROJCS[\"NAD_1983_CORS96_StatePlane_North_Carolina_FIPS_3200_Ft_US\","
+ "GEOGCS[\"GCS_NAD_1983_CORS96\",DATUM[\"D_NAD_1983_CORS96\","
+ "SPHEROID[\"GRS_1980\",6378137.0,298.257222101]],"
+ "PRIMEM[\"Greenwich\",0.0],UNIT[\"Degree\",0.0174532925199433]],"
+ "PROJECTION[\"Lambert_Conformal_Conic\"],"
+ "PARAMETER[\"False_Easting\",2000000.0],"
+ "PARAMETER[\"False_Northing\",0.0],"
+ "PARAMETER[\"Central_Meridian\",-79.0],"
+ "PARAMETER[\"Standard_Parallel_1\",34.33333333333334],"
+ "PARAMETER[\"Standard_Parallel_2\",36.16666666666666],"
+ "PARAMETER[\"Latitude_Of_Origin\",33.75],"
+ "UNIT[\"Foot_US\",0.3048006096012192]]";
+
+ auto obj = WKTParser().createFromWKT(in_wkt);
+ auto crs = nn_dynamic_pointer_cast<ProjectedCRS>(obj);
+ ASSERT_TRUE(crs != nullptr);
+
+ auto expected =
+ "PROJCS[\"NAD_1983_CORS96_StatePlane_North_Carolina_FIPS_3200_Ft_US\","
+ "GEOGCS[\"GCS_NAD_1983_CORS96\",DATUM[\"D_NAD_1983_CORS96\","
+ "SPHEROID[\"GRS_1980\",6378137.0,298.257222101]],"
+ "PRIMEM[\"Greenwich\",0.0],UNIT[\"Degree\",0.0174532925199433]],"
+ "PROJECTION[\"Lambert_Conformal_Conic\"],"
+ "PARAMETER[\"False_Easting\",2000000.0],"
+ "PARAMETER[\"False_Northing\",0.0],"
+ "PARAMETER[\"Central_Meridian\",-79.0],"
+ "PARAMETER[\"Standard_Parallel_1\",34.3333333333333],"
+ "PARAMETER[\"Standard_Parallel_2\",36.1666666666667],"
+ "PARAMETER[\"Latitude_Of_Origin\",33.75],"
+ "UNIT[\"Foot_US\",0.304800609601219]]";
+
+ EXPECT_EQ(crs->exportToWKT(
+ WKTFormatter::create(WKTFormatter::Convention::WKT1_ESRI,
+ DatabaseContext::create())
+ .get()),
+ expected);
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(datum, cs_with_MERIDIAN) {
+ std::vector<CoordinateSystemAxisNNPtr> axis{
+ CoordinateSystemAxis::create(
+ PropertyMap().set(IdentifiedObject::NAME_KEY, "Easting"), "X",
+ AxisDirection::SOUTH, UnitOfMeasure::METRE,
+ Meridian::create(Angle(90))),
+ CoordinateSystemAxis::create(
+ PropertyMap().set(IdentifiedObject::NAME_KEY, "Northing"), "Y",
+ AxisDirection::SOUTH, UnitOfMeasure::METRE,
+ Meridian::create(Angle(180.0)))};
+ auto cs(CartesianCS::create(PropertyMap(), axis[0], axis[1]));
+
+ auto expected = "CS[Cartesian,2],\n"
+ " AXIS[\"easting (X)\",south,\n"
+ " MERIDIAN[90,\n"
+ " ANGLEUNIT[\"degree\",0.0174532925199433]],\n"
+ " ORDER[1],\n"
+ " LENGTHUNIT[\"metre\",1]],\n"
+ " AXIS[\"northing (Y)\",south,\n"
+ " MERIDIAN[180,\n"
+ " ANGLEUNIT[\"degree\",0.0174532925199433]],\n"
+ " ORDER[2],\n"
+ " LENGTHUNIT[\"metre\",1]]";
+
+ auto formatter = WKTFormatter::create();
+ formatter->setOutputId(false);
+ EXPECT_EQ(cs->exportToWKT(formatter.get()), expected);
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(crs, scope_area_bbox_remark) {
+ auto in_wkt = "GEODETICCRS[\"JGD2000\","
+ "DATUM[\"Japanese Geodetic Datum 2000\","
+ " ELLIPSOID[\"GRS 1980\",6378137,298.257222101]],"
+ "CS[Cartesian,3],"
+ " AXIS[\"(X)\",geocentricX],"
+ " AXIS[\"(Y)\",geocentricY],"
+ " AXIS[\"(Z)\",geocentricZ],"
+ " LENGTHUNIT[\"metre\",1.0],"
+ "SCOPE[\"Geodesy, topographic mapping and cadastre\"],"
+ "AREA[\"Japan\"],"
+ "BBOX[17.09,122.38,46.05,157.64],"
+ "VERTICALEXTENT[-10000,10000],"
+ "TIMEEXTENT[2002-04-01,2011-10-21],"
+ "ID[\"EPSG\",4946],\n"
+ "REMARK[\"some_remark\"]]";
+ auto crs =
+ nn_dynamic_pointer_cast<GeodeticCRS>(WKTParser().createFromWKT(in_wkt));
+ ASSERT_TRUE(crs != nullptr);
+
+ ASSERT_EQ(crs->domains().size(), 1);
+ auto domain = crs->domains()[0];
+ EXPECT_TRUE(domain->scope().has_value());
+ EXPECT_EQ(*(domain->scope()), "Geodesy, topographic mapping and cadastre");
+ ASSERT_TRUE(domain->domainOfValidity() != nullptr);
+ EXPECT_TRUE(domain->domainOfValidity()->description().has_value());
+ EXPECT_EQ(*(domain->domainOfValidity()->description()), "Japan");
+ ASSERT_EQ(domain->domainOfValidity()->geographicElements().size(), 1);
+ auto geogElement = domain->domainOfValidity()->geographicElements()[0];
+ auto bbox = nn_dynamic_pointer_cast<GeographicBoundingBox>(geogElement);
+ ASSERT_TRUE(bbox != nullptr);
+ EXPECT_EQ(bbox->southBoundLatitude(), 17.09);
+ EXPECT_EQ(bbox->westBoundLongitude(), 122.38);
+ EXPECT_EQ(bbox->northBoundLatitude(), 46.05);
+ EXPECT_EQ(bbox->eastBoundLongitude(), 157.64);
+
+ ASSERT_EQ(domain->domainOfValidity()->verticalElements().size(), 1);
+ auto verticalElement = domain->domainOfValidity()->verticalElements()[0];
+ EXPECT_EQ(verticalElement->minimumValue(), -10000);
+ EXPECT_EQ(verticalElement->maximumValue(), 10000);
+ EXPECT_EQ(*(verticalElement->unit()), UnitOfMeasure::METRE);
+
+ ASSERT_EQ(domain->domainOfValidity()->temporalElements().size(), 1);
+ auto temporalElement = domain->domainOfValidity()->temporalElements()[0];
+ EXPECT_EQ(temporalElement->start(), "2002-04-01");
+ EXPECT_EQ(temporalElement->stop(), "2011-10-21");
+
+ auto got_wkt = crs->exportToWKT(WKTFormatter::create().get());
+ auto expected =
+ "GEODCRS[\"JGD2000\",\n"
+ " DATUM[\"Japanese Geodetic Datum 2000\",\n"
+ " ELLIPSOID[\"GRS 1980\",6378137,298.257222101,\n"
+ " LENGTHUNIT[\"metre\",1]]],\n"
+ " PRIMEM[\"Greenwich\",0,\n"
+ " ANGLEUNIT[\"degree\",0.0174532925199433]],\n"
+ " CS[Cartesian,3],\n"
+ " AXIS[\"(X)\",geocentricX,\n"
+ " ORDER[1],\n"
+ " LENGTHUNIT[\"metre\",1]],\n"
+ " AXIS[\"(Y)\",geocentricY,\n"
+ " ORDER[2],\n"
+ " LENGTHUNIT[\"metre\",1]],\n"
+ " AXIS[\"(Z)\",geocentricZ,\n"
+ " ORDER[3],\n"
+ " LENGTHUNIT[\"metre\",1]],\n"
+ " SCOPE[\"Geodesy, topographic mapping and cadastre\"],\n"
+ " AREA[\"Japan\"],\n"
+ " BBOX[17.09,122.38,46.05,157.64],\n"
+ " VERTICALEXTENT[-10000,10000,\n"
+ " LENGTHUNIT[\"metre\",1]],\n"
+ " TIMEEXTENT[2002-04-01,2011-10-21],\n"
+ " ID[\"EPSG\",4946],\n"
+ " REMARK[\"some_remark\"]]";
+
+ EXPECT_EQ(got_wkt, expected);
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(crs, usage) {
+ auto in_wkt = "GEODETICCRS[\"JGD2000\","
+ "DATUM[\"Japanese Geodetic Datum 2000\","
+ " ELLIPSOID[\"GRS 1980\",6378137,298.257222101]],"
+ "CS[Cartesian,3],"
+ " AXIS[\"(X)\",geocentricX],"
+ " AXIS[\"(Y)\",geocentricY],"
+ " AXIS[\"(Z)\",geocentricZ],"
+ " LENGTHUNIT[\"metre\",1.0],"
+ "USAGE[SCOPE[\"scope\"],AREA[\"area.\"]]]";
+ auto crs =
+ nn_dynamic_pointer_cast<GeodeticCRS>(WKTParser().createFromWKT(in_wkt));
+ ASSERT_TRUE(crs != nullptr);
+
+ auto got_wkt = crs->exportToWKT(
+ WKTFormatter::create(WKTFormatter::Convention::WKT2_2018).get());
+ auto expected = "GEODCRS[\"JGD2000\",\n"
+ " DATUM[\"Japanese Geodetic Datum 2000\",\n"
+ " ELLIPSOID[\"GRS 1980\",6378137,298.257222101,\n"
+ " LENGTHUNIT[\"metre\",1,\n"
+ " ID[\"EPSG\",9001]]]],\n"
+ " PRIMEM[\"Greenwich\",0,\n"
+ " ANGLEUNIT[\"degree\",0.0174532925199433],\n"
+ " ID[\"EPSG\",8901]],\n"
+ " CS[Cartesian,3],\n"
+ " AXIS[\"(X)\",geocentricX,\n"
+ " ORDER[1],\n"
+ " LENGTHUNIT[\"metre\",1]],\n"
+ " AXIS[\"(Y)\",geocentricY,\n"
+ " ORDER[2],\n"
+ " LENGTHUNIT[\"metre\",1]],\n"
+ " AXIS[\"(Z)\",geocentricZ,\n"
+ " ORDER[3],\n"
+ " LENGTHUNIT[\"metre\",1]],\n"
+ " USAGE[\n"
+ " SCOPE[\"scope\"],\n"
+ " AREA[\"area.\"]]]";
+ EXPECT_EQ(got_wkt, expected);
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(crs, multiple_ID) {
+
+ PropertyMap propertiesCRS;
+ propertiesCRS.set(IdentifiedObject::NAME_KEY, "WGS 84");
+ auto identifiers = ArrayOfBaseObject::create();
+ identifiers->add(Identifier::create(
+ "codeA", PropertyMap().set(Identifier::CODESPACE_KEY, "authorityA")));
+ identifiers->add(Identifier::create(
+ "codeB", PropertyMap().set(Identifier::CODESPACE_KEY, "authorityB")));
+ propertiesCRS.set(IdentifiedObject::IDENTIFIERS_KEY, identifiers);
+ auto crs = GeodeticCRS::create(
+ propertiesCRS, GeodeticReferenceFrame::EPSG_6326,
+ CartesianCS::createGeocentric(UnitOfMeasure::METRE));
+
+ auto got_wkt = crs->exportToWKT(
+ WKTFormatter::create(WKTFormatter::Convention::WKT2_SIMPLIFIED).get());
+ auto expected = "GEODCRS[\"WGS 84\",\n"
+ " DATUM[\"World Geodetic System 1984\",\n"
+ " ELLIPSOID[\"WGS 84\",6378137,298.257223563]],\n"
+ " CS[Cartesian,3],\n"
+ " AXIS[\"(X)\",geocentricX],\n"
+ " AXIS[\"(Y)\",geocentricY],\n"
+ " AXIS[\"(Z)\",geocentricZ],\n"
+ " UNIT[\"metre\",1],\n"
+ " ID[\"authorityA\",\"codeA\"],\n"
+ " ID[\"authorityB\",\"codeB\"]]";
+
+ EXPECT_EQ(got_wkt, expected);
+}
+
+// ---------------------------------------------------------------------------
+
+static VerticalCRSNNPtr createVerticalCRS() {
+ PropertyMap propertiesVDatum;
+ propertiesVDatum.set(Identifier::CODESPACE_KEY, "EPSG")
+ .set(Identifier::CODE_KEY, 5101)
+ .set(IdentifiedObject::NAME_KEY, "Ordnance Datum Newlyn");
+ auto vdatum = VerticalReferenceFrame::create(propertiesVDatum);
+ PropertyMap propertiesCRS;
+ propertiesCRS.set(Identifier::CODESPACE_KEY, "EPSG")
+ .set(Identifier::CODE_KEY, 5701)
+ .set(IdentifiedObject::NAME_KEY, "ODN height");
+ return VerticalCRS::create(
+ propertiesCRS, vdatum,
+ VerticalCS::createGravityRelatedHeight(UnitOfMeasure::METRE));
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(crs, verticalCRS_as_WKT2) {
+ auto crs = createVerticalCRS();
+ auto expected = "VERTCRS[\"ODN height\",\n"
+ " VDATUM[\"Ordnance Datum Newlyn\"],\n"
+ " CS[vertical,1],\n"
+ " AXIS[\"gravity-related height (H)\",up,\n"
+ " LENGTHUNIT[\"metre\",1]],\n"
+ " ID[\"EPSG\",5701]]";
+
+ EXPECT_TRUE(crs->isEquivalentTo(crs.get()));
+ EXPECT_TRUE(crs->shallowClone()->isEquivalentTo(crs.get()));
+ EXPECT_FALSE(crs->isEquivalentTo(createUnrelatedObject().get()));
+
+ EXPECT_EQ(crs->exportToWKT(WKTFormatter::create().get()), expected);
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(crs, verticalCRS_as_WKT1_GDAL) {
+ auto crs = createVerticalCRS();
+ auto expected = "VERT_CS[\"ODN height\",\n"
+ " VERT_DATUM[\"Ordnance Datum Newlyn\",2005,\n"
+ " AUTHORITY[\"EPSG\",\"5101\"]],\n"
+ " UNIT[\"metre\",1,\n"
+ " AUTHORITY[\"EPSG\",\"9001\"]],\n"
+ " AXIS[\"Gravity-related height\",UP],\n"
+ " AUTHORITY[\"EPSG\",\"5701\"]]";
+
+ EXPECT_EQ(
+ crs->exportToWKT(
+ WKTFormatter::create(WKTFormatter::Convention::WKT1_GDAL).get()),
+ expected);
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(crs, verticalCRS_identify_db) {
+ auto dbContext = DatabaseContext::create();
+ auto factory = AuthorityFactory::create(dbContext, "EPSG");
+ {
+ // Identify by existing code
+ auto res = factory->createVerticalCRS("7651")->identify(factory);
+ ASSERT_EQ(res.size(), 1);
+ EXPECT_EQ(res.front().first->getEPSGCode(), 7651);
+ EXPECT_EQ(res.front().second, 100);
+
+ // Test with null
+ EXPECT_TRUE(
+ factory->createVerticalCRS("7651")->identify(nullptr).empty());
+ }
+ {
+ // Non-existing code
+ auto sourceCRS = factory->createVerticalCRS("7651");
+ auto crs = VerticalCRS::create(
+ PropertyMap()
+ .set(IdentifiedObject::NAME_KEY, sourceCRS->nameStr())
+ .set(Identifier::CODESPACE_KEY, "EPSG")
+ .set(Identifier::CODE_KEY, 1),
+ sourceCRS->datum(), sourceCRS->datumEnsemble(),
+ sourceCRS->coordinateSystem());
+ auto res = crs->identify(factory);
+ EXPECT_EQ(res.size(), 0);
+ }
+ {
+ // Existing code, but not matching content
+ auto sourceCRS = factory->createVerticalCRS("7651");
+ auto crs = VerticalCRS::create(
+ PropertyMap()
+ .set(IdentifiedObject::NAME_KEY, sourceCRS->nameStr())
+ .set(Identifier::CODESPACE_KEY, "EPSG")
+ .set(Identifier::CODE_KEY, 7652),
+ sourceCRS->datum(), sourceCRS->datumEnsemble(),
+ sourceCRS->coordinateSystem());
+ auto res = crs->identify(factory);
+ ASSERT_EQ(res.size(), 1);
+ EXPECT_EQ(res.front().first->getEPSGCode(), 7652);
+ EXPECT_EQ(res.front().second, 25);
+ }
+ {
+ // Identify by exact name
+ auto sourceCRS = factory->createVerticalCRS("7651");
+ auto crs = VerticalCRS::create(
+ PropertyMap().set(IdentifiedObject::NAME_KEY, sourceCRS->nameStr()),
+ sourceCRS->datum(), sourceCRS->datumEnsemble(),
+ sourceCRS->coordinateSystem());
+ auto res = static_cast<const CRS *>(crs.get())->identify(factory);
+ ASSERT_EQ(res.size(), 1);
+ EXPECT_EQ(res.front().first->getEPSGCode(), 7651);
+ EXPECT_EQ(res.front().second, 100);
+ }
+ {
+ // Identify by equivalent name
+ auto sourceCRS = factory->createVerticalCRS("7651");
+ auto crs = VerticalCRS::create(
+ PropertyMap().set(IdentifiedObject::NAME_KEY, "Kumul_34_height"),
+ sourceCRS->datum(), sourceCRS->datumEnsemble(),
+ sourceCRS->coordinateSystem());
+ auto res = crs->identify(factory);
+ ASSERT_EQ(res.size(), 1);
+ EXPECT_EQ(res.front().first->getEPSGCode(), 7651);
+ EXPECT_EQ(res.front().second, 90);
+ }
+ {
+ // Identify by name, but objects aren't equivalent
+ auto sourceCRS = factory->createVerticalCRS("7652");
+ auto crs = VerticalCRS::create(
+ PropertyMap().set(IdentifiedObject::NAME_KEY, "Kumul_34_height"),
+ sourceCRS->datum(), sourceCRS->datumEnsemble(),
+ sourceCRS->coordinateSystem());
+ auto res = crs->identify(factory);
+ ASSERT_EQ(res.size(), 1);
+ EXPECT_EQ(res.front().first->getEPSGCode(), 7651);
+ EXPECT_EQ(res.front().second, 25);
+ }
+ {
+ auto sourceCRS = factory->createVerticalCRS("7651");
+ auto crs = VerticalCRS::create(
+ PropertyMap().set(IdentifiedObject::NAME_KEY, "no match"),
+ sourceCRS->datum(), sourceCRS->datumEnsemble(),
+ sourceCRS->coordinateSystem());
+ auto res = crs->identify(factory);
+ ASSERT_EQ(res.size(), 0);
+ }
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(crs, verticalCRS_datum_ensemble) {
+ auto ensemble = DatumEnsemble::create(
+ PropertyMap(),
+ std::vector<DatumNNPtr>{
+ VerticalReferenceFrame::create(
+ PropertyMap().set(IdentifiedObject::NAME_KEY, "vdatum1")),
+ VerticalReferenceFrame::create(
+ PropertyMap().set(IdentifiedObject::NAME_KEY, "vdatum2"))},
+ PositionalAccuracy::create("100"));
+ auto crs = VerticalCRS::create(
+ PropertyMap().set(IdentifiedObject::NAME_KEY, "unnamed"), nullptr,
+ ensemble, VerticalCS::createGravityRelatedHeight(UnitOfMeasure::METRE));
+ WKTFormatterNNPtr f(
+ WKTFormatter::create(WKTFormatter::Convention::WKT2_2018));
+ f->simulCurNodeHasId();
+ crs->exportToWKT(f.get());
+ auto expected = "VERTCRS[\"unnamed\",\n"
+ " ENSEMBLE[\"unnamed\",\n"
+ " MEMBER[\"vdatum1\"],\n"
+ " MEMBER[\"vdatum2\"],\n"
+ " ENSEMBLEACCURACY[100]],\n"
+ " CS[vertical,1],\n"
+ " AXIS[\"gravity-related height (H)\",up,\n"
+ " LENGTHUNIT[\"metre\",1]]]";
+ EXPECT_EQ(f->toString(), expected);
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(crs, VerticalCRS_ensemble_exception_in_create) {
+ EXPECT_THROW(VerticalCRS::create(PropertyMap(), nullptr, nullptr,
+ VerticalCS::createGravityRelatedHeight(
+ UnitOfMeasure::METRE)),
+ Exception);
+
+ auto ensemble_hdatum = DatumEnsemble::create(
+ PropertyMap(),
+ std::vector<DatumNNPtr>{GeodeticReferenceFrame::EPSG_6326,
+ GeodeticReferenceFrame::EPSG_6326},
+ PositionalAccuracy::create("100"));
+ EXPECT_THROW(VerticalCRS::create(PropertyMap(), nullptr, ensemble_hdatum,
+ VerticalCS::createGravityRelatedHeight(
+ UnitOfMeasure::METRE)),
+ Exception);
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(datum, vdatum_with_anchor) {
+ PropertyMap propertiesVDatum;
+ propertiesVDatum.set(Identifier::CODESPACE_KEY, "EPSG")
+ .set(Identifier::CODE_KEY, 5101)
+ .set(IdentifiedObject::NAME_KEY, "Ordnance Datum Newlyn");
+ auto vdatum = VerticalReferenceFrame::create(
+ propertiesVDatum, optional<std::string>("my anchor"),
+ optional<RealizationMethod>(RealizationMethod::LEVELLING));
+ EXPECT_TRUE(vdatum->realizationMethod().has_value());
+ EXPECT_EQ(*(vdatum->realizationMethod()), RealizationMethod::LEVELLING);
+
+ auto expected = "VDATUM[\"Ordnance Datum Newlyn\",\n"
+ " ANCHOR[\"my anchor\"],\n"
+ " ID[\"EPSG\",5101]]";
+
+ EXPECT_EQ(vdatum->exportToWKT(WKTFormatter::create().get()), expected);
+
+ EXPECT_TRUE(vdatum->isEquivalentTo(vdatum.get()));
+ EXPECT_FALSE(vdatum->isEquivalentTo(createUnrelatedObject().get()));
+}
+
+// ---------------------------------------------------------------------------
+
+static CompoundCRSNNPtr createCompoundCRS() {
+ PropertyMap properties;
+ properties.set(Identifier::CODESPACE_KEY, "codespace")
+ .set(Identifier::CODE_KEY, "code")
+ .set(IdentifiedObject::NAME_KEY, "horizontal + vertical");
+ return CompoundCRS::create(
+ properties,
+ std::vector<CRSNNPtr>{createProjected(), createVerticalCRS()});
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(crs, compoundCRS_as_WKT2) {
+ auto crs = createCompoundCRS();
+ auto expected =
+ "COMPOUNDCRS[\"horizontal + vertical\",\n"
+ " PROJCRS[\"WGS 84 / UTM zone 31N\",\n"
+ " BASEGEODCRS[\"WGS 84\",\n"
+ " DATUM[\"World Geodetic System 1984\",\n"
+ " ELLIPSOID[\"WGS 84\",6378137,298.257223563,\n"
+ " LENGTHUNIT[\"metre\",1]]],\n"
+ " PRIMEM[\"Greenwich\",0,\n"
+ " ANGLEUNIT[\"degree\",0.0174532925199433]]],\n"
+ " CONVERSION[\"UTM zone 31N\",\n"
+ " METHOD[\"Transverse Mercator\",\n"
+ " ID[\"EPSG\",9807]],\n"
+ " PARAMETER[\"Latitude of natural origin\",0,\n"
+ " ANGLEUNIT[\"degree\",0.0174532925199433],\n"
+ " ID[\"EPSG\",8801]],\n"
+ " PARAMETER[\"Longitude of natural origin\",3,\n"
+ " ANGLEUNIT[\"degree\",0.0174532925199433],\n"
+ " ID[\"EPSG\",8802]],\n"
+ " PARAMETER[\"Scale factor at natural origin\",0.9996,\n"
+ " SCALEUNIT[\"unity\",1],\n"
+ " ID[\"EPSG\",8805]],\n"
+ " PARAMETER[\"False easting\",500000,\n"
+ " LENGTHUNIT[\"metre\",1],\n"
+ " ID[\"EPSG\",8806]],\n"
+ " PARAMETER[\"False northing\",0,\n"
+ " LENGTHUNIT[\"metre\",1],\n"
+ " ID[\"EPSG\",8807]]],\n"
+ " CS[Cartesian,2],\n"
+ " AXIS[\"(E)\",east,\n"
+ " ORDER[1],\n"
+ " LENGTHUNIT[\"metre\",1]],\n"
+ " AXIS[\"(N)\",north,\n"
+ " ORDER[2],\n"
+ " LENGTHUNIT[\"metre\",1]]],\n"
+ " VERTCRS[\"ODN height\",\n"
+ " VDATUM[\"Ordnance Datum Newlyn\"],\n"
+ " CS[vertical,1],\n"
+ " AXIS[\"gravity-related height (H)\",up,\n"
+ " LENGTHUNIT[\"metre\",1]]],\n"
+ " ID[\"codespace\",\"code\"]]";
+
+ EXPECT_EQ(crs->exportToWKT(WKTFormatter::create().get()), expected);
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(crs, compoundCRS_isEquivalentTo) {
+
+ auto crs = createCompoundCRS();
+ EXPECT_TRUE(crs->isEquivalentTo(crs.get()));
+ EXPECT_TRUE(crs->shallowClone()->isEquivalentTo(crs.get()));
+ EXPECT_FALSE(crs->isEquivalentTo(createUnrelatedObject().get()));
+ auto compoundCRSOfProjCRS =
+ CompoundCRS::create(PropertyMap().set(IdentifiedObject::NAME_KEY, ""),
+ std::vector<CRSNNPtr>{createProjected()});
+ auto emptyCompoundCRS =
+ CompoundCRS::create(PropertyMap().set(IdentifiedObject::NAME_KEY, ""),
+ std::vector<CRSNNPtr>{});
+ EXPECT_FALSE(compoundCRSOfProjCRS->isEquivalentTo(emptyCompoundCRS.get()));
+ auto compoundCRSOfGeogCRS =
+ CompoundCRS::create(PropertyMap().set(IdentifiedObject::NAME_KEY, ""),
+ std::vector<CRSNNPtr>{GeographicCRS::EPSG_4326});
+ EXPECT_FALSE(
+ compoundCRSOfProjCRS->isEquivalentTo(compoundCRSOfGeogCRS.get()));
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(crs, compoundCRS_as_WKT1_GDAL) {
+ auto crs = createCompoundCRS();
+ auto expected =
+ "COMPD_CS[\"horizontal + vertical\",\n"
+ " PROJCS[\"WGS 84 / UTM zone 31N\",\n"
+ " GEOGCS[\"WGS 84\",\n"
+ " DATUM[\"WGS_1984\",\n"
+ " SPHEROID[\"WGS 84\",6378137,298.257223563,\n"
+ " AUTHORITY[\"EPSG\",\"7030\"]],\n"
+ " AUTHORITY[\"EPSG\",\"6326\"]],\n"
+ " PRIMEM[\"Greenwich\",0,\n"
+ " AUTHORITY[\"EPSG\",\"8901\"]],\n"
+ " UNIT[\"degree\",0.0174532925199433,\n"
+ " AUTHORITY[\"EPSG\",\"9122\"]],\n"
+ " AXIS[\"Latitude\",NORTH],\n"
+ " AXIS[\"Longitude\",EAST],\n"
+ " AUTHORITY[\"EPSG\",\"4326\"]],\n"
+ " PROJECTION[\"Transverse_Mercator\"],\n"
+ " PARAMETER[\"latitude_of_origin\",0],\n"
+ " PARAMETER[\"central_meridian\",3],\n"
+ " PARAMETER[\"scale_factor\",0.9996],\n"
+ " PARAMETER[\"false_easting\",500000],\n"
+ " PARAMETER[\"false_northing\",0],\n"
+ " UNIT[\"metre\",1,\n"
+ " AUTHORITY[\"EPSG\",\"9001\"]],\n"
+ " AXIS[\"Easting\",EAST],\n"
+ " AXIS[\"Northing\",NORTH],\n"
+ " AUTHORITY[\"EPSG\",\"32631\"]],\n"
+ " VERT_CS[\"ODN height\",\n"
+ " VERT_DATUM[\"Ordnance Datum Newlyn\",2005,\n"
+ " AUTHORITY[\"EPSG\",\"5101\"]],\n"
+ " UNIT[\"metre\",1,\n"
+ " AUTHORITY[\"EPSG\",\"9001\"]],\n"
+ " AXIS[\"Gravity-related height\",UP],\n"
+ " AUTHORITY[\"EPSG\",\"5701\"]],\n"
+ " AUTHORITY[\"codespace\",\"code\"]]";
+
+ EXPECT_EQ(
+ crs->exportToWKT(
+ WKTFormatter::create(WKTFormatter::Convention::WKT1_GDAL).get()),
+ expected);
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(crs, compoundCRS_as_PROJ_string) {
+ auto crs = createCompoundCRS();
+ auto expected = "+proj=pipeline +step +proj=axisswap +order=2,1 +step "
+ "+proj=unitconvert +xy_in=deg +xy_out=rad +step +proj=utm "
+ "+zone=31 +ellps=WGS84 +vunits=m";
+
+ EXPECT_EQ(crs->exportToPROJString(PROJStringFormatter::create().get()),
+ expected);
+ EXPECT_EQ(
+ crs->exportToPROJString(
+ PROJStringFormatter::create(PROJStringFormatter::Convention::PROJ_4)
+ .get()),
+ "+proj=utm +zone=31 +datum=WGS84 +vunits=m");
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(crs, compoundCRS_no_name_provided) {
+ auto crs = CompoundCRS::create(
+ PropertyMap(),
+ std::vector<CRSNNPtr>{createProjected(), createVerticalCRS()});
+ EXPECT_EQ(crs->nameStr(), "WGS 84 / UTM zone 31N + ODN height");
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(crs, compoundCRS_identify_db) {
+ auto dbContext = DatabaseContext::create();
+ auto factory = AuthorityFactory::create(dbContext, "EPSG");
+ {
+ // Identify by existing code
+ auto res = factory->createCompoundCRS("8769")->identify(factory);
+ ASSERT_EQ(res.size(), 1);
+ EXPECT_EQ(res.front().first->getEPSGCode(), 8769);
+ EXPECT_EQ(res.front().second, 100);
+
+ // Test with null
+ EXPECT_TRUE(
+ factory->createCompoundCRS("8769")->identify(nullptr).empty());
+ }
+ {
+ // Non-existing code
+ auto sourceCRS = factory->createCompoundCRS("8769");
+ auto crs = CompoundCRS::create(
+ PropertyMap()
+ .set(IdentifiedObject::NAME_KEY, sourceCRS->nameStr())
+ .set(Identifier::CODESPACE_KEY, "EPSG")
+ .set(Identifier::CODE_KEY, 1),
+ sourceCRS->componentReferenceSystems());
+ auto res = crs->identify(factory);
+ EXPECT_EQ(res.size(), 0);
+ }
+ {
+ // Existing code, but not matching content
+ auto sourceCRS = factory->createCompoundCRS("8769");
+ auto crs = CompoundCRS::create(
+ PropertyMap()
+ .set(IdentifiedObject::NAME_KEY, sourceCRS->nameStr())
+ .set(Identifier::CODESPACE_KEY, "EPSG")
+ .set(Identifier::CODE_KEY, 8770),
+ sourceCRS->componentReferenceSystems());
+ auto res = crs->identify(factory);
+ ASSERT_EQ(res.size(), 1);
+ EXPECT_EQ(res.front().first->getEPSGCode(), 8770);
+ EXPECT_EQ(res.front().second, 25);
+ }
+ {
+ // Identify by exact name
+ auto sourceCRS = factory->createCompoundCRS("8769");
+ auto crs = CompoundCRS::create(
+ PropertyMap().set(IdentifiedObject::NAME_KEY, sourceCRS->nameStr()),
+ sourceCRS->componentReferenceSystems());
+ auto res = static_cast<const CRS *>(crs.get())->identify(factory);
+ ASSERT_EQ(res.size(), 1);
+ EXPECT_EQ(res.front().first->getEPSGCode(), 8769);
+ EXPECT_EQ(res.front().second, 100);
+ }
+ {
+ EXPECT_TRUE(Identifier::isEquivalentName(
+ "NAD83_Ohio_North_ftUS_NAVD88_height_ftUS",
+ "NAD83 / Ohio North (ftUS) + NAVD88 height (ftUS)"));
+
+ // Identify by equivalent name
+ auto sourceCRS = factory->createCompoundCRS("8769");
+ auto crs = CompoundCRS::create(
+ PropertyMap().set(IdentifiedObject::NAME_KEY,
+ "NAD83_Ohio_North_ftUS_NAVD88_height_ftUS"),
+ sourceCRS->componentReferenceSystems());
+ auto res = crs->identify(factory);
+ ASSERT_EQ(res.size(), 1);
+ EXPECT_EQ(res.front().first->getEPSGCode(), 8769);
+ EXPECT_EQ(res.front().second, 90);
+ }
+ {
+ // Identify by name, but objects aren't equivalent
+ auto sourceCRS = factory->createCompoundCRS("8770");
+ auto crs = CompoundCRS::create(
+ PropertyMap().set(IdentifiedObject::NAME_KEY,
+ "NAD83_Ohio_North_ftUS_NAVD88_height_ftUS"),
+ sourceCRS->componentReferenceSystems());
+ auto res = crs->identify(factory);
+ ASSERT_EQ(res.size(), 1);
+ EXPECT_EQ(res.front().first->getEPSGCode(), 8769);
+ EXPECT_EQ(res.front().second, 25);
+ }
+ {
+ auto sourceCRS = factory->createCompoundCRS("8769");
+ auto crs = CompoundCRS::create(
+ PropertyMap().set(IdentifiedObject::NAME_KEY, "unrelated name"),
+ sourceCRS->componentReferenceSystems());
+ auto res = crs->identify(factory);
+ ASSERT_EQ(res.size(), 1);
+ EXPECT_EQ(res.front().first->getEPSGCode(), 8769);
+ EXPECT_EQ(res.front().second, 70);
+ }
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(crs, boundCRS_to_WKT2) {
+
+ auto projcrs = ProjectedCRS::create(
+ PropertyMap().set(IdentifiedObject::NAME_KEY, "my PROJCRS"),
+ GeographicCRS::create(
+ PropertyMap().set(IdentifiedObject::NAME_KEY, "my GEOGCRS"),
+ GeodeticReferenceFrame::EPSG_6326,
+ EllipsoidalCS::createLatitudeLongitude(UnitOfMeasure::DEGREE)),
+ Conversion::createUTM(PropertyMap(), 31, true),
+ CartesianCS::createEastingNorthing(UnitOfMeasure::METRE));
+
+ auto crs = BoundCRS::createFromTOWGS84(
+ projcrs, std::vector<double>{1, 2, 3, 4, 5, 6, 7});
+
+ EXPECT_EQ(crs->baseCRS()->nameStr(), projcrs->nameStr());
+
+ EXPECT_EQ(crs->hubCRS()->nameStr(), GeographicCRS::EPSG_4326->nameStr());
+
+ ASSERT_TRUE(crs->transformation()->sourceCRS() != nullptr);
+ EXPECT_EQ(crs->transformation()->sourceCRS()->nameStr(),
+ projcrs->baseCRS()->nameStr());
+
+ ASSERT_TRUE(crs->transformation()->targetCRS() != nullptr);
+ EXPECT_EQ(crs->transformation()->targetCRS()->nameStr(),
+ GeographicCRS::EPSG_4326->nameStr());
+
+ auto values = crs->transformation()->parameterValues();
+ ASSERT_EQ(values.size(), 7);
+ {
+ const auto &opParamvalue =
+ nn_dynamic_pointer_cast<OperationParameterValue>(values[0]);
+ ASSERT_TRUE(opParamvalue);
+ const auto &paramName = opParamvalue->parameter()->nameStr();
+ const auto &parameterValue = opParamvalue->parameterValue();
+ EXPECT_TRUE(opParamvalue->parameter()->getEPSGCode() == 8605);
+ EXPECT_EQ(paramName, "X-axis translation");
+ EXPECT_EQ(parameterValue->type(), ParameterValue::Type::MEASURE);
+ auto measure = parameterValue->value();
+ EXPECT_EQ(measure.unit(), UnitOfMeasure::METRE);
+ EXPECT_EQ(measure.value(), 1.0);
+ }
+ {
+ const auto &opParamvalue =
+ nn_dynamic_pointer_cast<OperationParameterValue>(values[1]);
+ ASSERT_TRUE(opParamvalue);
+ const auto &paramName = opParamvalue->parameter()->nameStr();
+ const auto &parameterValue = opParamvalue->parameterValue();
+ EXPECT_TRUE(opParamvalue->parameter()->getEPSGCode() == 8606);
+ EXPECT_EQ(paramName, "Y-axis translation");
+ EXPECT_EQ(parameterValue->type(), ParameterValue::Type::MEASURE);
+ auto measure = parameterValue->value();
+ EXPECT_EQ(measure.unit(), UnitOfMeasure::METRE);
+ EXPECT_EQ(measure.value(), 2.0);
+ }
+ {
+ const auto &opParamvalue =
+ nn_dynamic_pointer_cast<OperationParameterValue>(values[2]);
+ ASSERT_TRUE(opParamvalue);
+ const auto &paramName = opParamvalue->parameter()->nameStr();
+ const auto &parameterValue = opParamvalue->parameterValue();
+ EXPECT_TRUE(opParamvalue->parameter()->getEPSGCode() == 8607);
+ EXPECT_EQ(paramName, "Z-axis translation");
+ EXPECT_EQ(parameterValue->type(), ParameterValue::Type::MEASURE);
+ auto measure = parameterValue->value();
+ EXPECT_EQ(measure.unit(), UnitOfMeasure::METRE);
+ EXPECT_EQ(measure.value(), 3.0);
+ }
+ {
+ const auto &opParamvalue =
+ nn_dynamic_pointer_cast<OperationParameterValue>(values[3]);
+ ASSERT_TRUE(opParamvalue);
+ const auto &paramName = opParamvalue->parameter()->nameStr();
+ const auto &parameterValue = opParamvalue->parameterValue();
+ EXPECT_TRUE(opParamvalue->parameter()->getEPSGCode() == 8608);
+ EXPECT_EQ(paramName, "X-axis rotation");
+ EXPECT_EQ(parameterValue->type(), ParameterValue::Type::MEASURE);
+ auto measure = parameterValue->value();
+ EXPECT_EQ(measure.unit(), UnitOfMeasure::ARC_SECOND);
+ EXPECT_EQ(measure.value(), 4.0);
+ }
+ {
+ const auto &opParamvalue =
+ nn_dynamic_pointer_cast<OperationParameterValue>(values[4]);
+ ASSERT_TRUE(opParamvalue);
+ const auto &paramName = opParamvalue->parameter()->nameStr();
+ const auto &parameterValue = opParamvalue->parameterValue();
+ EXPECT_TRUE(opParamvalue->parameter()->getEPSGCode() == 8609);
+ EXPECT_EQ(paramName, "Y-axis rotation");
+ EXPECT_EQ(parameterValue->type(), ParameterValue::Type::MEASURE);
+ auto measure = parameterValue->value();
+ EXPECT_EQ(measure.unit(), UnitOfMeasure::ARC_SECOND);
+ EXPECT_EQ(measure.value(), 5.0);
+ }
+ {
+ const auto &opParamvalue =
+ nn_dynamic_pointer_cast<OperationParameterValue>(values[5]);
+ ASSERT_TRUE(opParamvalue);
+ const auto &paramName = opParamvalue->parameter()->nameStr();
+ const auto &parameterValue = opParamvalue->parameterValue();
+ EXPECT_TRUE(opParamvalue->parameter()->getEPSGCode() == 8610);
+ EXPECT_EQ(paramName, "Z-axis rotation");
+ EXPECT_EQ(parameterValue->type(), ParameterValue::Type::MEASURE);
+ auto measure = parameterValue->value();
+ EXPECT_EQ(measure.unit(), UnitOfMeasure::ARC_SECOND);
+ EXPECT_EQ(measure.value(), 6.0);
+ }
+ {
+ const auto &opParamvalue =
+ nn_dynamic_pointer_cast<OperationParameterValue>(values[6]);
+ ASSERT_TRUE(opParamvalue);
+ const auto &paramName = opParamvalue->parameter()->nameStr();
+ const auto &parameterValue = opParamvalue->parameterValue();
+ EXPECT_TRUE(opParamvalue->parameter()->getEPSGCode() == 8611);
+ EXPECT_EQ(paramName, "Scale difference");
+ EXPECT_EQ(parameterValue->type(), ParameterValue::Type::MEASURE);
+ auto measure = parameterValue->value();
+ EXPECT_EQ(measure.unit(), UnitOfMeasure::PARTS_PER_MILLION);
+ EXPECT_EQ(measure.value(), 7.0);
+ }
+
+ auto expected =
+ "BOUNDCRS[SOURCECRS[" +
+ projcrs->exportToWKT(WKTFormatter::create().get()) + "],\n" +
+ "TARGETCRS[" +
+ GeographicCRS::EPSG_4326->exportToWKT(WKTFormatter::create().get()) +
+ "],\n"
+ " ABRIDGEDTRANSFORMATION[\"Transformation from myGEOGCRS to "
+ "WGS84\",\n"
+ " METHOD[\"Position Vector transformation (geog2D "
+ "domain)\",\n"
+ " ID[\"EPSG\",9606]],\n"
+ " PARAMETER[\"X-axis translation\",1,\n"
+ " ID[\"EPSG\",8605]],\n"
+ " PARAMETER[\"Y-axis translation\",2,\n"
+ " ID[\"EPSG\",8606]],\n"
+ " PARAMETER[\"Z-axis translation\",3,\n"
+ " ID[\"EPSG\",8607]],\n"
+ " PARAMETER[\"X-axis rotation\",4,\n"
+ " ID[\"EPSG\",8608]],\n"
+ " PARAMETER[\"Y-axis rotation\",5,\n"
+ " ID[\"EPSG\",8609]],\n"
+ " PARAMETER[\"Z-axis rotation\",6,\n"
+ " ID[\"EPSG\",8610]],\n"
+ " PARAMETER[\"Scale difference\",1.000007,\n"
+ " ID[\"EPSG\",8611]]]]";
+
+ EXPECT_EQ(
+ replaceAll(
+ replaceAll(crs->exportToWKT(WKTFormatter::create().get()), " ", ""),
+ "\n", ""),
+ replaceAll(replaceAll(expected, " ", ""), "\n", ""));
+
+ EXPECT_TRUE(crs->isEquivalentTo(crs.get()));
+ EXPECT_TRUE(crs->shallowClone()->isEquivalentTo(crs.get()));
+ EXPECT_FALSE(crs->isEquivalentTo(createUnrelatedObject().get()));
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(crs, boundCRS_crs_link) {
+
+ {
+ std::weak_ptr<CRS> oriBaseCRS;
+ {
+ auto baseCRSIn = GeographicCRS::EPSG_4267->shallowClone();
+ oriBaseCRS = baseCRSIn.as_nullable();
+ EXPECT_EQ(oriBaseCRS.use_count(), 1);
+ {
+ auto boundCRS = BoundCRS::createFromTOWGS84(
+ baseCRSIn, std::vector<double>{1, 2, 3, 4, 5, 6, 7});
+ EXPECT_EQ(oriBaseCRS.use_count(), 3);
+ }
+ EXPECT_EQ(oriBaseCRS.use_count(), 1);
+ }
+ EXPECT_TRUE(oriBaseCRS.expired());
+ }
+
+ {
+ CRSPtr baseCRS;
+ {
+ auto baseCRSIn = GeographicCRS::EPSG_4267->shallowClone();
+ CRS *baseCRSPtr = baseCRSIn.get();
+ auto boundCRS = BoundCRS::createFromTOWGS84(
+ baseCRSIn, std::vector<double>{1, 2, 3, 4, 5, 6, 7});
+ baseCRS = boundCRS->baseCRS().as_nullable();
+ EXPECT_TRUE(baseCRS.get() == baseCRSPtr);
+ }
+ EXPECT_TRUE(baseCRS->isEquivalentTo(GeographicCRS::EPSG_4267.get()));
+ EXPECT_TRUE(baseCRS->canonicalBoundCRS() == nullptr);
+ }
+
+ {
+ CRSPtr baseCRS;
+ {
+ auto boundCRS = BoundCRS::createFromTOWGS84(
+ GeographicCRS::EPSG_4267->shallowClone(),
+ std::vector<double>{1, 2, 3, 4, 5, 6, 7});
+ baseCRS = boundCRS->baseCRSWithCanonicalBoundCRS().as_nullable();
+ }
+ EXPECT_TRUE(baseCRS->isEquivalentTo(GeographicCRS::EPSG_4267.get()));
+ EXPECT_TRUE(baseCRS->canonicalBoundCRS() != nullptr);
+
+ EXPECT_TRUE(
+ baseCRS->createBoundCRSToWGS84IfPossible(nullptr)->isEquivalentTo(
+ baseCRS->canonicalBoundCRS().get()));
+ }
+
+ {
+ std::weak_ptr<CRS> oriBaseCRS;
+ {
+ BoundCRSPtr boundCRSExterior;
+ {
+ auto baseCRS = GeographicCRS::EPSG_4267->shallowClone();
+ oriBaseCRS = baseCRS.as_nullable();
+ EXPECT_EQ(oriBaseCRS.use_count(), 1);
+ auto boundCRS = BoundCRS::createFromTOWGS84(
+ baseCRS, std::vector<double>{1, 2, 3, 4, 5, 6, 7});
+ EXPECT_EQ(oriBaseCRS.use_count(), 3);
+ boundCRSExterior = boundCRS->baseCRSWithCanonicalBoundCRS()
+ ->canonicalBoundCRS();
+ EXPECT_EQ(oriBaseCRS.use_count(), 4);
+ }
+ EXPECT_EQ(oriBaseCRS.use_count(), 2);
+ EXPECT_TRUE(!oriBaseCRS.expired());
+ EXPECT_TRUE(boundCRSExterior->baseCRS()->isEquivalentTo(
+ GeographicCRS::EPSG_4267.get()));
+ }
+ EXPECT_EQ(oriBaseCRS.use_count(), 0);
+ EXPECT_TRUE(oriBaseCRS.expired());
+ }
+
+ {
+ std::weak_ptr<CRS> oriBaseCRS;
+ {
+ BoundCRSPtr boundCRSExterior;
+ {
+ auto baseCRS = createProjected();
+ oriBaseCRS = baseCRS.as_nullable();
+ EXPECT_EQ(oriBaseCRS.use_count(), 1);
+ auto boundCRS = BoundCRS::createFromTOWGS84(
+ baseCRS, std::vector<double>{1, 2, 3, 4, 5, 6, 7});
+ EXPECT_EQ(oriBaseCRS.use_count(), 2);
+ boundCRSExterior = boundCRS->baseCRSWithCanonicalBoundCRS()
+ ->canonicalBoundCRS();
+ EXPECT_EQ(oriBaseCRS.use_count(), 3);
+ }
+ EXPECT_EQ(oriBaseCRS.use_count(), 1);
+ EXPECT_TRUE(!oriBaseCRS.expired());
+ EXPECT_TRUE(boundCRSExterior->baseCRS()->isEquivalentTo(
+ createProjected().get()));
+ }
+ EXPECT_EQ(oriBaseCRS.use_count(), 0);
+ EXPECT_TRUE(oriBaseCRS.expired());
+ }
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(crs, boundCRS_to_WKT1) {
+
+ auto projcrs = ProjectedCRS::create(
+ PropertyMap().set(IdentifiedObject::NAME_KEY, "my PROJCRS"),
+ GeographicCRS::create(
+ PropertyMap().set(IdentifiedObject::NAME_KEY, "my GEOGCRS"),
+ GeodeticReferenceFrame::EPSG_6326,
+ EllipsoidalCS::createLatitudeLongitude(UnitOfMeasure::DEGREE)),
+ Conversion::createUTM(PropertyMap(), 31, true),
+ CartesianCS::createEastingNorthing(UnitOfMeasure::METRE));
+
+ auto crs = BoundCRS::createFromTOWGS84(
+ projcrs, std::vector<double>{1, 2, 3, 4, 5, 6, 7});
+ auto expected = "PROJCS[\"my PROJCRS\",\n"
+ " GEOGCS[\"my GEOGCRS\",\n"
+ " DATUM[\"WGS_1984\",\n"
+ " SPHEROID[\"WGS 84\",6378137,298.257223563,\n"
+ " AUTHORITY[\"EPSG\",\"7030\"]],\n"
+ " TOWGS84[1,2,3,4,5,6,7],\n"
+ " AUTHORITY[\"EPSG\",\"6326\"]],\n"
+ " PRIMEM[\"Greenwich\",0,\n"
+ " AUTHORITY[\"EPSG\",\"8901\"]],\n"
+ " UNIT[\"degree\",0.0174532925199433,\n"
+ " AUTHORITY[\"EPSG\",\"9122\"]],\n"
+ " AXIS[\"Latitude\",NORTH],\n"
+ " AXIS[\"Longitude\",EAST]],\n"
+ " PROJECTION[\"Transverse_Mercator\"],\n"
+ " PARAMETER[\"latitude_of_origin\",0],\n"
+ " PARAMETER[\"central_meridian\",3],\n"
+ " PARAMETER[\"scale_factor\",0.9996],\n"
+ " PARAMETER[\"false_easting\",500000],\n"
+ " PARAMETER[\"false_northing\",0],\n"
+ " UNIT[\"metre\",1,\n"
+ " AUTHORITY[\"EPSG\",\"9001\"]],\n"
+ " AXIS[\"Easting\",EAST],\n"
+ " AXIS[\"Northing\",NORTH]]";
+
+ EXPECT_EQ(
+ crs->exportToWKT(
+ WKTFormatter::create(WKTFormatter::Convention::WKT1_GDAL).get()),
+ expected);
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(crs, boundCRS_geographicCRS_to_PROJ_string) {
+
+ auto basecrs = GeographicCRS::create(
+ PropertyMap().set(IdentifiedObject::NAME_KEY, "my GEOGCRS"),
+ GeodeticReferenceFrame::EPSG_6326,
+ EllipsoidalCS::createLatitudeLongitude(UnitOfMeasure::DEGREE));
+
+ auto crs = BoundCRS::createFromTOWGS84(
+ basecrs, std::vector<double>{1, 2, 3, 4, 5, 6, 7});
+
+ EXPECT_THROW(crs->exportToPROJString(PROJStringFormatter::create().get()),
+ FormattingException);
+ EXPECT_EQ(
+ crs->exportToPROJString(
+ PROJStringFormatter::create(PROJStringFormatter::Convention::PROJ_4)
+ .get()),
+ "+proj=longlat +ellps=WGS84 +towgs84=1,2,3,4,5,6,7");
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(crs, boundCRS_projectedCRS_to_PROJ_string) {
+
+ auto projcrs = ProjectedCRS::create(
+ PropertyMap().set(IdentifiedObject::NAME_KEY, "my PROJCRS"),
+ GeographicCRS::create(
+ PropertyMap().set(IdentifiedObject::NAME_KEY, "my GEOGCRS"),
+ GeodeticReferenceFrame::EPSG_6326,
+ EllipsoidalCS::createLatitudeLongitude(UnitOfMeasure::DEGREE)),
+ Conversion::createUTM(PropertyMap(), 31, true),
+ CartesianCS::createEastingNorthing(UnitOfMeasure::METRE));
+
+ auto crs = BoundCRS::createFromTOWGS84(
+ projcrs, std::vector<double>{1, 2, 3, 4, 5, 6, 7});
+
+ EXPECT_EQ(
+ crs->exportToPROJString(
+ PROJStringFormatter::create(PROJStringFormatter::Convention::PROJ_4)
+ .get()),
+ "+proj=utm +zone=31 +ellps=WGS84 +towgs84=1,2,3,4,5,6,7");
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(crs, boundCRS_identify_db) {
+ auto dbContext = DatabaseContext::create();
+ auto factoryEPSG = AuthorityFactory::create(dbContext, "EPSG");
+ {
+ auto obj =
+ PROJStringParser()
+ .attachDatabaseContext(dbContext)
+ .createFromPROJString("+proj=tmerc +lat_0=-37.76111111111111 "
+ "+lon_0=176.4661111111111 +k=1 "
+ "+x_0=400000 +y_0=800000 +ellps=GRS80 "
+ "+towgs84=0,0,0,0,0,0,0 +units=m");
+ auto crs = nn_dynamic_pointer_cast<BoundCRS>(obj);
+ ASSERT_TRUE(crs != nullptr);
+ auto res = crs->identify(factoryEPSG);
+ ASSERT_EQ(res.size(), 1);
+ auto boundCRS = dynamic_cast<const BoundCRS *>(res.front().first.get());
+ ASSERT_TRUE(boundCRS != nullptr);
+ EXPECT_EQ(boundCRS->baseCRS()->getEPSGCode(), 2106);
+ EXPECT_EQ(boundCRS->transformation()->nameStr(),
+ "NZGD2000 to WGS 84 (1)");
+ EXPECT_EQ(res.front().second, 50);
+ }
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(crs, incompatible_boundCRS_hubCRS_to_WKT1) {
+
+ auto crs = BoundCRS::create(
+ GeographicCRS::EPSG_4326, GeographicCRS::EPSG_4807,
+ Transformation::createGeocentricTranslations(
+ PropertyMap(), GeographicCRS::EPSG_4326, GeographicCRS::EPSG_4807,
+ 1.0, 2.0, 3.0, std::vector<PositionalAccuracyNNPtr>()));
+
+ EXPECT_THROW(
+ crs->exportToWKT(
+ WKTFormatter::create(WKTFormatter::Convention::WKT1_GDAL).get()),
+ FormattingException);
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(crs, incompatible_boundCRS_transformation_to_WKT1) {
+
+ auto crs = BoundCRS::create(
+ GeographicCRS::EPSG_4807, GeographicCRS::EPSG_4326,
+ Transformation::create(PropertyMap(), GeographicCRS::EPSG_4807,
+ GeographicCRS::EPSG_4326, nullptr, PropertyMap(),
+ std::vector<OperationParameterNNPtr>(),
+ std::vector<ParameterValueNNPtr>(),
+ std::vector<PositionalAccuracyNNPtr>()));
+
+ EXPECT_THROW(
+ crs->exportToWKT(
+ WKTFormatter::create(WKTFormatter::Convention::WKT1_GDAL).get()),
+ FormattingException);
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(crs, WKT1_DATUM_EXTENSION_to_WKT1_and_PROJ_string) {
+ auto wkt =
+ "PROJCS[\"unnamed\",\n"
+ " GEOGCS[\"International 1909 (Hayford)\",\n"
+ " DATUM[\"unknown\",\n"
+ " SPHEROID[\"intl\",6378388,297],\n"
+ " EXTENSION[\"PROJ4_GRIDS\",\"nzgd2kgrid0005.gsb\"]],\n"
+ " PRIMEM[\"Greenwich\",0],\n"
+ " UNIT[\"degree\",0.0174532925199433],\n"
+ " AXIS[\"Longitude\",EAST],\n"
+ " AXIS[\"Latitude\",NORTH]],\n"
+ " PROJECTION[\"New_Zealand_Map_Grid\"],\n"
+ " PARAMETER[\"latitude_of_origin\",-41],\n"
+ " PARAMETER[\"central_meridian\",173],\n"
+ " PARAMETER[\"false_easting\",2510000],\n"
+ " PARAMETER[\"false_northing\",6023150],\n"
+ " UNIT[\"Meter\",1],\n"
+ " AXIS[\"Easting\",EAST],\n"
+ " AXIS[\"Northing\",NORTH]]";
+
+ auto obj = WKTParser().createFromWKT(wkt);
+ auto crs = nn_dynamic_pointer_cast<BoundCRS>(obj);
+ ASSERT_TRUE(crs != nullptr);
+
+ EXPECT_EQ(
+ crs->exportToWKT(
+ WKTFormatter::create(WKTFormatter::Convention::WKT1_GDAL).get()),
+ wkt);
+
+ EXPECT_EQ(
+ crs->exportToPROJString(
+ PROJStringFormatter::create(PROJStringFormatter::Convention::PROJ_4)
+ .get()),
+ "+proj=nzmg +lat_0=-41 +lon_0=173 +x_0=2510000 +y_0=6023150 "
+ "+ellps=intl +nadgrids=nzgd2kgrid0005.gsb +units=m");
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(crs, WKT1_VERT_DATUM_EXTENSION_to_WKT1) {
+ auto wkt = "VERT_CS[\"EGM2008 geoid height\",\n"
+ " VERT_DATUM[\"EGM2008 geoid\",2005,\n"
+ " EXTENSION[\"PROJ4_GRIDS\",\"egm08_25.gtx\"],\n"
+ " AUTHORITY[\"EPSG\",\"1027\"]],\n"
+ " UNIT[\"metre\",1,\n"
+ " AUTHORITY[\"EPSG\",\"9001\"]],\n"
+ " AXIS[\"Up\",UP],\n"
+ " AUTHORITY[\"EPSG\",\"3855\"]]";
+
+ auto obj = WKTParser().createFromWKT(wkt);
+ auto crs = nn_dynamic_pointer_cast<BoundCRS>(obj);
+ ASSERT_TRUE(crs != nullptr);
+
+ EXPECT_EQ(
+ crs->exportToWKT(
+ WKTFormatter::create(WKTFormatter::Convention::WKT1_GDAL).get()),
+ wkt);
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(crs, WKT1_VERT_DATUM_EXTENSION_to_WKT2) {
+ auto wkt = "VERT_CS[\"EGM2008 geoid height\",\n"
+ " VERT_DATUM[\"EGM2008 geoid\",2005,\n"
+ " EXTENSION[\"PROJ4_GRIDS\",\"egm08_25.gtx\"],\n"
+ " AUTHORITY[\"EPSG\",\"1027\"]],\n"
+ " UNIT[\"metre\",1,\n"
+ " AUTHORITY[\"EPSG\",\"9001\"]],\n"
+ " AXIS[\"Up\",UP],\n"
+ " AUTHORITY[\"EPSG\",\"3855\"]]";
+
+ auto obj = WKTParser().createFromWKT(wkt);
+ auto crs = nn_dynamic_pointer_cast<BoundCRS>(obj);
+ ASSERT_TRUE(crs != nullptr);
+
+ auto wkt2 =
+ "BOUNDCRS[\n"
+ " SOURCECRS[\n"
+ " VERTCRS[\"EGM2008 geoid height\",\n"
+ " VDATUM[\"EGM2008 geoid\"],\n"
+ " CS[vertical,1],\n"
+ " AXIS[\"up\",up,\n"
+ " LENGTHUNIT[\"metre\",1]],\n"
+ " ID[\"EPSG\",3855]]],\n"
+ " TARGETCRS[\n"
+ " GEODCRS[\"WGS 84\",\n"
+ " DATUM[\"World Geodetic System 1984\",\n"
+ " ELLIPSOID[\"WGS 84\",6378137,298.257223563,\n"
+ " LENGTHUNIT[\"metre\",1]]],\n"
+ " PRIMEM[\"Greenwich\",0,\n"
+ " ANGLEUNIT[\"degree\",0.0174532925199433]],\n"
+ " CS[ellipsoidal,3],\n"
+ " AXIS[\"latitude\",north,\n"
+ " ORDER[1],\n"
+ " ANGLEUNIT[\"degree\",0.0174532925199433]],\n"
+ " AXIS[\"longitude\",east,\n"
+ " ORDER[2],\n"
+ " ANGLEUNIT[\"degree\",0.0174532925199433]],\n"
+ " AXIS[\"ellipsoidal height\",up,\n"
+ " ORDER[3],\n"
+ " LENGTHUNIT[\"metre\",1]],\n"
+ " ID[\"EPSG\",4979]]],\n"
+ " ABRIDGEDTRANSFORMATION[\"EGM2008 geoid height to WGS84 "
+ "ellipsoidal height\",\n"
+ " METHOD[\"GravityRelatedHeight to Geographic3D\"],\n"
+ " PARAMETERFILE[\"Geoid (height correction) model "
+ "file\",\"egm08_25.gtx\",\n"
+ " ID[\"EPSG\",8666]]]]";
+
+ EXPECT_EQ(crs->exportToWKT(
+ WKTFormatter::create(WKTFormatter::Convention::WKT2).get()),
+ wkt2);
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(crs, WKT1_VERT_DATUM_EXTENSION_to_PROJ_string) {
+ auto wkt = "VERT_CS[\"EGM2008 geoid height\",\n"
+ " VERT_DATUM[\"EGM2008 geoid\",2005,\n"
+ " EXTENSION[\"PROJ4_GRIDS\",\"egm08_25.gtx\"],\n"
+ " AUTHORITY[\"EPSG\",\"1027\"]],\n"
+ " UNIT[\"metre\",1,\n"
+ " AUTHORITY[\"EPSG\",\"9001\"]],\n"
+ " AXIS[\"Up\",UP],\n"
+ " AUTHORITY[\"EPSG\",\"3855\"]]";
+
+ auto obj = WKTParser().createFromWKT(wkt);
+ auto crs = nn_dynamic_pointer_cast<BoundCRS>(obj);
+ ASSERT_TRUE(crs != nullptr);
+
+ EXPECT_EQ(
+ crs->exportToPROJString(
+ PROJStringFormatter::create(PROJStringFormatter::Convention::PROJ_4)
+ .get()),
+ "+geoidgrids=egm08_25.gtx +vunits=m");
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(crs, extractGeographicCRS) {
+ EXPECT_EQ(GeographicCRS::EPSG_4326->extractGeographicCRS(),
+ GeographicCRS::EPSG_4326);
+ EXPECT_EQ(createProjected()->extractGeographicCRS(),
+ GeographicCRS::EPSG_4326);
+ EXPECT_EQ(
+ CompoundCRS::create(PropertyMap(),
+ std::vector<CRSNNPtr>{GeographicCRS::EPSG_4326})
+ ->extractGeographicCRS(),
+ GeographicCRS::EPSG_4326);
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(crs, extractVerticalCRS) {
+ EXPECT_EQ(GeographicCRS::EPSG_4326->extractVerticalCRS(), nullptr);
+ {
+ auto vertcrs = createCompoundCRS()->extractVerticalCRS();
+ ASSERT_TRUE(vertcrs != nullptr);
+ EXPECT_TRUE(vertcrs->isEquivalentTo(createVerticalCRS().get()));
+ }
+}
+
+// ---------------------------------------------------------------------------
+
+static DerivedGeographicCRSNNPtr createDerivedGeographicCRS() {
+
+ auto derivingConversion = Conversion::create(
+ PropertyMap().set(IdentifiedObject::NAME_KEY, "Atlantic pole"),
+ PropertyMap().set(IdentifiedObject::NAME_KEY, "Pole rotation"),
+ std::vector<OperationParameterNNPtr>{
+ OperationParameter::create(PropertyMap().set(
+ IdentifiedObject::NAME_KEY, "Latitude of rotated pole")),
+ OperationParameter::create(PropertyMap().set(
+ IdentifiedObject::NAME_KEY, "Longitude of rotated pole")),
+ OperationParameter::create(
+ PropertyMap().set(IdentifiedObject::NAME_KEY, "Axis rotation")),
+ },
+ std::vector<ParameterValueNNPtr>{
+ ParameterValue::create(Angle(52.0)),
+ ParameterValue::create(Angle(-30.0)),
+ ParameterValue::create(Angle(-25)),
+ });
+
+ return DerivedGeographicCRS::create(
+ PropertyMap().set(IdentifiedObject::NAME_KEY, "WMO Atlantic Pole"),
+ GeographicCRS::EPSG_4326, derivingConversion,
+ EllipsoidalCS::createLatitudeLongitude(UnitOfMeasure::DEGREE));
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(crs, derivedGeographicCRS_WKT2) {
+
+ auto expected = "GEODCRS[\"WMO Atlantic Pole\",\n"
+ " BASEGEODCRS[\"WGS 84\",\n"
+ " DATUM[\"World Geodetic System 1984\",\n"
+ " ELLIPSOID[\"WGS 84\",6378137,298.257223563,\n"
+ " LENGTHUNIT[\"metre\",1]]],\n"
+ " PRIMEM[\"Greenwich\",0,\n"
+ " ANGLEUNIT[\"degree\",0.0174532925199433]]],\n"
+ " DERIVINGCONVERSION[\"Atlantic pole\",\n"
+ " METHOD[\"Pole rotation\"],\n"
+ " PARAMETER[\"Latitude of rotated pole\",52,\n"
+ " ANGLEUNIT[\"degree\",0.0174532925199433,\n"
+ " ID[\"EPSG\",9122]]],\n"
+ " PARAMETER[\"Longitude of rotated pole\",-30,\n"
+ " ANGLEUNIT[\"degree\",0.0174532925199433,\n"
+ " ID[\"EPSG\",9122]]],\n"
+ " PARAMETER[\"Axis rotation\",-25,\n"
+ " ANGLEUNIT[\"degree\",0.0174532925199433,\n"
+ " ID[\"EPSG\",9122]]]],\n"
+ " CS[ellipsoidal,2],\n"
+ " AXIS[\"latitude\",north,\n"
+ " ORDER[1],\n"
+ " ANGLEUNIT[\"degree\",0.0174532925199433,\n"
+ " ID[\"EPSG\",9122]]],\n"
+ " AXIS[\"longitude\",east,\n"
+ " ORDER[2],\n"
+ " ANGLEUNIT[\"degree\",0.0174532925199433,\n"
+ " ID[\"EPSG\",9122]]]]";
+
+ auto crs = createDerivedGeographicCRS();
+ EXPECT_EQ(crs->exportToWKT(
+ WKTFormatter::create(WKTFormatter::Convention::WKT2).get()),
+ expected);
+
+ EXPECT_TRUE(crs->isEquivalentTo(crs.get()));
+ EXPECT_TRUE(crs->shallowClone()->isEquivalentTo(crs.get()));
+ EXPECT_FALSE(crs->isEquivalentTo(createUnrelatedObject().get()));
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(crs, derivedGeographicCRS_WKT2_2018) {
+
+ auto expected = "GEOGCRS[\"WMO Atlantic Pole\",\n"
+ " BASEGEOGCRS[\"WGS 84\",\n"
+ " DATUM[\"World Geodetic System 1984\",\n"
+ " ELLIPSOID[\"WGS 84\",6378137,298.257223563,\n"
+ " LENGTHUNIT[\"metre\",1]]],\n"
+ " PRIMEM[\"Greenwich\",0,\n"
+ " ANGLEUNIT[\"degree\",0.0174532925199433]]],\n"
+ " DERIVINGCONVERSION[\"Atlantic pole\",\n"
+ " METHOD[\"Pole rotation\"],\n"
+ " PARAMETER[\"Latitude of rotated pole\",52,\n"
+ " ANGLEUNIT[\"degree\",0.0174532925199433,\n"
+ " ID[\"EPSG\",9122]]],\n"
+ " PARAMETER[\"Longitude of rotated pole\",-30,\n"
+ " ANGLEUNIT[\"degree\",0.0174532925199433,\n"
+ " ID[\"EPSG\",9122]]],\n"
+ " PARAMETER[\"Axis rotation\",-25,\n"
+ " ANGLEUNIT[\"degree\",0.0174532925199433,\n"
+ " ID[\"EPSG\",9122]]]],\n"
+ " CS[ellipsoidal,2],\n"
+ " AXIS[\"latitude\",north,\n"
+ " ORDER[1],\n"
+ " ANGLEUNIT[\"degree\",0.0174532925199433,\n"
+ " ID[\"EPSG\",9122]]],\n"
+ " AXIS[\"longitude\",east,\n"
+ " ORDER[2],\n"
+ " ANGLEUNIT[\"degree\",0.0174532925199433,\n"
+ " ID[\"EPSG\",9122]]]]";
+
+ EXPECT_EQ(
+ createDerivedGeographicCRS()->exportToWKT(
+ WKTFormatter::create(WKTFormatter::Convention::WKT2_2018).get()),
+ expected);
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(crs, derivedGeographicCRS_WKT1) {
+
+ EXPECT_THROW(
+ createDerivedGeographicCRS()->exportToWKT(
+ WKTFormatter::create(WKTFormatter::Convention::WKT1_GDAL).get()),
+ FormattingException);
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(crs, derivedGeographicCRS_to_PROJ) {
+
+ auto wkt = "GEODCRS[\"WMO Atlantic Pole\",\n"
+ " BASEGEODCRS[\"WGS 84\",\n"
+ " DATUM[\"World Geodetic System 1984\",\n"
+ " ELLIPSOID[\"WGS 84\",6378137,298.257223563,\n"
+ " LENGTHUNIT[\"metre\",1]]],\n"
+ " PRIMEM[\"Greenwich\",0,\n"
+ " ANGLEUNIT[\"degree\",0.0174532925199433]]],\n"
+ " DERIVINGCONVERSION[\"unnamed\",\n"
+ " METHOD[\"PROJ ob_tran o_proj=longlat\"],\n"
+ " PARAMETER[\"o_lat_p\",52],\n"
+ " PARAMETER[\"o_lon_p\",-30],\n"
+ " PARAMETER[\"lon_0\",-25]],\n"
+ " CS[ellipsoidal,2],\n"
+ " AXIS[\"latitude\",north,\n"
+ " ORDER[1],\n"
+ " ANGLEUNIT[\"degree\",0.0174532925199433]],\n"
+ " AXIS[\"longitude\",east,\n"
+ " ORDER[2],\n"
+ " ANGLEUNIT[\"degree\",0.0174532925199433]]]";
+
+ auto obj = WKTParser().createFromWKT(wkt);
+ auto crs = nn_dynamic_pointer_cast<DerivedGeographicCRS>(obj);
+ ASSERT_TRUE(crs != nullptr);
+ EXPECT_EQ(
+ crs->exportToPROJString(PROJStringFormatter::create().get()),
+ "+proj=pipeline +step +proj=axisswap +order=2,1 +step "
+ "+proj=unitconvert +xy_in=deg +xy_out=rad +step +proj=ob_tran "
+ "+o_proj=longlat +o_lat_p=52 +o_lon_p=-30 +lon_0=-25 +ellps=WGS84 "
+ "+step +proj=unitconvert +xy_in=rad +xy_out=deg +step "
+ "+proj=axisswap +order=2,1");
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(crs, derivedGeographicCRS_with_affine_transform_to_PROJ) {
+
+ auto wkt = "GEODCRS[\"WGS 84 Translated\",\n"
+ " BASEGEODCRS[\"WGS 84\",\n"
+ " DATUM[\"World Geodetic System 1984\",\n"
+ " ELLIPSOID[\"WGS 84\",6378137,298.257223563,\n"
+ " LENGTHUNIT[\"metre\",1]]],\n"
+ " PRIMEM[\"Greenwich\",0]],\n"
+ " DERIVINGCONVERSION[\"Translation\",\n"
+ " METHOD[\"Affine parametric transformation\",\n"
+ " ID[\"EPSG\",9624]],\n"
+ " PARAMETER[\"A0\",0.5,\n"
+ " ANGLEUNIT[\"degree\",0.0174532925199433],\n"
+ " ID[\"EPSG\",8623]],\n"
+ " PARAMETER[\"A1\",1,\n"
+ " SCALEUNIT[\"coefficient\",1],\n"
+ " ID[\"EPSG\",8624]],\n"
+ " PARAMETER[\"A2\",0,\n"
+ " SCALEUNIT[\"coefficient\",1],\n"
+ " ID[\"EPSG\",8625]],\n"
+ " PARAMETER[\"B0\",2.5,\n"
+ " ANGLEUNIT[\"degree\",0.0174532925199433],\n"
+ " ID[\"EPSG\",8639]],\n"
+ " PARAMETER[\"B1\",0,\n"
+ " SCALEUNIT[\"coefficient\",1],\n"
+ " ID[\"EPSG\",8640]],\n"
+ " PARAMETER[\"B2\",1,\n"
+ " SCALEUNIT[\"coefficient\",1],\n"
+ " ID[\"EPSG\",8641]]],\n"
+ " CS[ellipsoidal,2],\n"
+ " AXIS[\"latitude\",north],\n"
+ " AXIS[\"longitude\",east],\n"
+ " ANGLEUNIT[\"degree\",0.0174532925199433]]";
+
+ auto obj = WKTParser().createFromWKT(wkt);
+ auto crs = nn_dynamic_pointer_cast<DerivedGeographicCRS>(obj);
+ ASSERT_TRUE(crs != nullptr);
+ EXPECT_EQ(crs->exportToPROJString(PROJStringFormatter::create().get()),
+ "+proj=affine +xoff=0.5 +s11=1 +s12=0 +yoff=2.5 +s21=0 +s22=1");
+}
+
+// ---------------------------------------------------------------------------
+
+static DerivedGeodeticCRSNNPtr createDerivedGeodeticCRS() {
+
+ auto derivingConversion = Conversion::create(
+ PropertyMap().set(IdentifiedObject::NAME_KEY, "Some conversion"),
+ PropertyMap().set(IdentifiedObject::NAME_KEY, "Some method"),
+ std::vector<OperationParameterNNPtr>{},
+ std::vector<ParameterValueNNPtr>{});
+
+ return DerivedGeodeticCRS::create(
+ PropertyMap().set(IdentifiedObject::NAME_KEY, "Derived geodetic CRS"),
+ GeographicCRS::EPSG_4326, derivingConversion,
+ CartesianCS::createGeocentric(UnitOfMeasure::METRE));
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(crs, derivedGeodeticCRS_WKT2) {
+
+ auto expected = "GEODCRS[\"Derived geodetic CRS\",\n"
+ " BASEGEODCRS[\"WGS 84\",\n"
+ " DATUM[\"World Geodetic System 1984\",\n"
+ " ELLIPSOID[\"WGS 84\",6378137,298.257223563,\n"
+ " LENGTHUNIT[\"metre\",1]]],\n"
+ " PRIMEM[\"Greenwich\",0,\n"
+ " ANGLEUNIT[\"degree\",0.0174532925199433]]],\n"
+ " DERIVINGCONVERSION[\"Some conversion\",\n"
+ " METHOD[\"Some method\"]],\n"
+ " CS[Cartesian,3],\n"
+ " AXIS[\"(X)\",geocentricX,\n"
+ " ORDER[1],\n"
+ " LENGTHUNIT[\"metre\",1,\n"
+ " ID[\"EPSG\",9001]]],\n"
+ " AXIS[\"(Y)\",geocentricY,\n"
+ " ORDER[2],\n"
+ " LENGTHUNIT[\"metre\",1,\n"
+ " ID[\"EPSG\",9001]]],\n"
+ " AXIS[\"(Z)\",geocentricZ,\n"
+ " ORDER[3],\n"
+ " LENGTHUNIT[\"metre\",1,\n"
+ " ID[\"EPSG\",9001]]]]";
+
+ auto crs = createDerivedGeodeticCRS();
+ EXPECT_EQ(crs->exportToWKT(
+ WKTFormatter::create(WKTFormatter::Convention::WKT2).get()),
+ expected);
+
+ EXPECT_TRUE(crs->isEquivalentTo(crs.get()));
+ EXPECT_TRUE(crs->shallowClone()->isEquivalentTo(crs.get()));
+ EXPECT_FALSE(crs->isEquivalentTo(createUnrelatedObject().get()));
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(crs, derivedGeodeticCRS_WKT2_2018) {
+
+ auto expected = "GEODCRS[\"Derived geodetic CRS\",\n"
+ " BASEGEOGCRS[\"WGS 84\",\n"
+ " DATUM[\"World Geodetic System 1984\",\n"
+ " ELLIPSOID[\"WGS 84\",6378137,298.257223563,\n"
+ " LENGTHUNIT[\"metre\",1]]],\n"
+ " PRIMEM[\"Greenwich\",0,\n"
+ " ANGLEUNIT[\"degree\",0.0174532925199433]]],\n"
+ " DERIVINGCONVERSION[\"Some conversion\",\n"
+ " METHOD[\"Some method\"]],\n"
+ " CS[Cartesian,3],\n"
+ " AXIS[\"(X)\",geocentricX,\n"
+ " ORDER[1],\n"
+ " LENGTHUNIT[\"metre\",1,\n"
+ " ID[\"EPSG\",9001]]],\n"
+ " AXIS[\"(Y)\",geocentricY,\n"
+ " ORDER[2],\n"
+ " LENGTHUNIT[\"metre\",1,\n"
+ " ID[\"EPSG\",9001]]],\n"
+ " AXIS[\"(Z)\",geocentricZ,\n"
+ " ORDER[3],\n"
+ " LENGTHUNIT[\"metre\",1,\n"
+ " ID[\"EPSG\",9001]]]]";
+
+ EXPECT_EQ(
+ createDerivedGeodeticCRS()->exportToWKT(
+ WKTFormatter::create(WKTFormatter::Convention::WKT2_2018).get()),
+ expected);
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(crs, derivedGeodeticCRS_WKT1) {
+
+ EXPECT_THROW(
+ createDerivedGeodeticCRS()->exportToWKT(
+ WKTFormatter::create(WKTFormatter::Convention::WKT1_GDAL).get()),
+ FormattingException);
+}
+
+// ---------------------------------------------------------------------------
+
+static DerivedProjectedCRSNNPtr createDerivedProjectedCRS() {
+
+ auto derivingConversion = Conversion::create(
+ PropertyMap().set(IdentifiedObject::NAME_KEY, "unnamed"),
+ PropertyMap().set(IdentifiedObject::NAME_KEY, "PROJ unimplemented"),
+ std::vector<OperationParameterNNPtr>{},
+ std::vector<ParameterValueNNPtr>{});
+
+ return DerivedProjectedCRS::create(
+ PropertyMap().set(IdentifiedObject::NAME_KEY, "derived projectedCRS"),
+ createProjected(), derivingConversion,
+ CartesianCS::createEastingNorthing(UnitOfMeasure::METRE));
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(crs, derivedProjectedCRS_WKT2_2018) {
+
+ auto expected =
+ "DERIVEDPROJCRS[\"derived projectedCRS\",\n"
+ " BASEPROJCRS[\"WGS 84 / UTM zone 31N\",\n"
+ " BASEGEOGCRS[\"WGS 84\",\n"
+ " DATUM[\"World Geodetic System 1984\",\n"
+ " ELLIPSOID[\"WGS 84\",6378137,298.257223563,\n"
+ " LENGTHUNIT[\"metre\",1]]],\n"
+ " PRIMEM[\"Greenwich\",0,\n"
+ " ANGLEUNIT[\"degree\",0.0174532925199433]]],\n"
+ " CONVERSION[\"UTM zone 31N\",\n"
+ " METHOD[\"Transverse Mercator\",\n"
+ " ID[\"EPSG\",9807]],\n"
+ " PARAMETER[\"Latitude of natural origin\",0,\n"
+ " ANGLEUNIT[\"degree\",0.0174532925199433],\n"
+ " ID[\"EPSG\",8801]],\n"
+ " PARAMETER[\"Longitude of natural origin\",3,\n"
+ " ANGLEUNIT[\"degree\",0.0174532925199433],\n"
+ " ID[\"EPSG\",8802]],\n"
+ " PARAMETER[\"Scale factor at natural origin\",0.9996,\n"
+ " SCALEUNIT[\"unity\",1],\n"
+ " ID[\"EPSG\",8805]],\n"
+ " PARAMETER[\"False easting\",500000,\n"
+ " LENGTHUNIT[\"metre\",1],\n"
+ " ID[\"EPSG\",8806]],\n"
+ " PARAMETER[\"False northing\",0,\n"
+ " LENGTHUNIT[\"metre\",1],\n"
+ " ID[\"EPSG\",8807]]]],\n"
+ " DERIVINGCONVERSION[\"unnamed\",\n"
+ " METHOD[\"PROJ unimplemented\"]],\n"
+ " CS[Cartesian,2],\n"
+ " AXIS[\"(E)\",east,\n"
+ " ORDER[1],\n"
+ " LENGTHUNIT[\"metre\",1,\n"
+ " ID[\"EPSG\",9001]]],\n"
+ " AXIS[\"(N)\",north,\n"
+ " ORDER[2],\n"
+ " LENGTHUNIT[\"metre\",1,\n"
+ " ID[\"EPSG\",9001]]]]";
+
+ auto crs = createDerivedProjectedCRS();
+ EXPECT_EQ(
+ crs->exportToWKT(
+ WKTFormatter::create(WKTFormatter::Convention::WKT2_2018).get()),
+ expected);
+
+ EXPECT_TRUE(crs->isEquivalentTo(crs.get()));
+ EXPECT_TRUE(crs->shallowClone()->isEquivalentTo(crs.get()));
+ EXPECT_FALSE(crs->isEquivalentTo(createUnrelatedObject().get()));
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(crs, derivedProjectedCRS_WKT2_2015) {
+
+ auto crs = createDerivedProjectedCRS();
+ EXPECT_THROW(
+ crs->exportToWKT(
+ WKTFormatter::create(WKTFormatter::Convention::WKT2_2015).get()),
+ FormattingException);
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(crs, derivedProjectedCRS_to_PROJ) {
+
+ auto crs = createDerivedProjectedCRS();
+ EXPECT_EQ(crs->exportToPROJString(PROJStringFormatter::create().get()),
+ "+proj=unimplemented");
+}
+
+// ---------------------------------------------------------------------------
+
+static DateTimeTemporalCSNNPtr createDateTimeTemporalCS() {
+ return DateTimeTemporalCS::create(
+ PropertyMap(),
+ CoordinateSystemAxis::create(
+ PropertyMap().set(IdentifiedObject::NAME_KEY, "Time"), "T",
+ AxisDirection::FUTURE, UnitOfMeasure::NONE));
+ ;
+}
+
+// ---------------------------------------------------------------------------
+
+static TemporalCRSNNPtr createDateTimeTemporalCRS() {
+
+ auto datum = TemporalDatum::create(
+ PropertyMap().set(IdentifiedObject::NAME_KEY, "Gregorian calendar"),
+ DateTime::create("0000-01-01"),
+ TemporalDatum::CALENDAR_PROLEPTIC_GREGORIAN);
+
+ return TemporalCRS::create(
+ PropertyMap().set(IdentifiedObject::NAME_KEY, "Temporal CRS"), datum,
+ createDateTimeTemporalCS());
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(crs, dateTimeTemporalCRS_WKT2) {
+
+ auto expected = "TIMECRS[\"Temporal CRS\",\n"
+ " TDATUM[\"Gregorian calendar\",\n"
+ " TIMEORIGIN[0000-01-01]],\n"
+ " CS[temporal,1],\n"
+ " AXIS[\"time (T)\",future]]";
+
+ auto crs = createDateTimeTemporalCRS();
+ EXPECT_EQ(crs->exportToWKT(
+ WKTFormatter::create(WKTFormatter::Convention::WKT2).get()),
+ expected);
+
+ EXPECT_THROW(
+ crs->exportToWKT(
+ WKTFormatter::create(WKTFormatter::Convention::WKT1_GDAL).get()),
+ FormattingException);
+
+ EXPECT_TRUE(crs->isEquivalentTo(crs.get()));
+ EXPECT_TRUE(crs->shallowClone()->isEquivalentTo(crs.get()));
+ EXPECT_TRUE(!crs->isEquivalentTo(createUnrelatedObject().get()));
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(crs, dateTimeTemporalCRS_WKT2_2018) {
+
+ auto expected = "TIMECRS[\"Temporal CRS\",\n"
+ " TDATUM[\"Gregorian calendar\",\n"
+ " CALENDAR[\"proleptic Gregorian\"],\n"
+ " TIMEORIGIN[0000-01-01]],\n"
+ " CS[TemporalDateTime,1],\n"
+ " AXIS[\"time (T)\",future]]";
+
+ EXPECT_EQ(
+ createDateTimeTemporalCRS()->exportToWKT(
+ WKTFormatter::create(WKTFormatter::Convention::WKT2_2018).get()),
+ expected);
+}
+
+// ---------------------------------------------------------------------------
+
+static TemporalCRSNNPtr createTemporalCountCRSWithConvFactor() {
+
+ auto datum = TemporalDatum::create(
+ PropertyMap().set(IdentifiedObject::NAME_KEY, "GPS time origin"),
+ DateTime::create("1980-01-01T00:00:00.0Z"),
+ TemporalDatum::CALENDAR_PROLEPTIC_GREGORIAN);
+
+ auto cs = TemporalCountCS::create(
+ PropertyMap(),
+ CoordinateSystemAxis::create(PropertyMap(), "T", AxisDirection::FUTURE,
+ UnitOfMeasure("milliseconds (ms)", 0.001,
+ UnitOfMeasure::Type::TIME)));
+
+ return TemporalCRS::create(
+ PropertyMap().set(IdentifiedObject::NAME_KEY, "GPS milliseconds"),
+ datum, cs);
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(crs, temporalCountCRSWithConvFactor_WKT2_2018) {
+
+ auto expected = "TIMECRS[\"GPS milliseconds\",\n"
+ " TDATUM[\"GPS time origin\",\n"
+ " CALENDAR[\"proleptic Gregorian\"],\n"
+ " TIMEORIGIN[1980-01-01T00:00:00.0Z]],\n"
+ " CS[TemporalCount,1],\n"
+ " AXIS[\"(T)\",future,\n"
+ " TIMEUNIT[\"milliseconds (ms)\",0.001]]]";
+
+ EXPECT_EQ(
+ createTemporalCountCRSWithConvFactor()->exportToWKT(
+ WKTFormatter::create(WKTFormatter::Convention::WKT2_2018).get()),
+ expected);
+}
+
+// ---------------------------------------------------------------------------
+
+static TemporalCRSNNPtr createTemporalCountCRSWithoutConvFactor() {
+
+ auto datum = TemporalDatum::create(
+ PropertyMap().set(IdentifiedObject::NAME_KEY, "29 December 1979"),
+ DateTime::create("1979-12-29T00"),
+ TemporalDatum::CALENDAR_PROLEPTIC_GREGORIAN);
+
+ auto cs = TemporalCountCS::create(
+ PropertyMap(),
+ CoordinateSystemAxis::create(
+ PropertyMap().set(IdentifiedObject::NAME_KEY, "Time"), "",
+ AxisDirection::FUTURE,
+ UnitOfMeasure("hour", 0, UnitOfMeasure::Type::TIME)));
+
+ return TemporalCRS::create(
+ PropertyMap().set(IdentifiedObject::NAME_KEY,
+ "Calendar hours from 1979-12-29"),
+ datum, cs);
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(crs, temporalCountCRSWithoutConvFactor_WKT2_2018) {
+
+ auto expected = "TIMECRS[\"Calendar hours from 1979-12-29\",\n"
+ " TDATUM[\"29 December 1979\",\n"
+ " CALENDAR[\"proleptic Gregorian\"],\n"
+ " TIMEORIGIN[1979-12-29T00]],\n"
+ " CS[TemporalCount,1],\n"
+ " AXIS[\"time\",future,\n"
+ " TIMEUNIT[\"hour\"]]]";
+
+ EXPECT_EQ(
+ createTemporalCountCRSWithoutConvFactor()->exportToWKT(
+ WKTFormatter::create(WKTFormatter::Convention::WKT2_2018).get()),
+ expected);
+}
+
+// ---------------------------------------------------------------------------
+
+static TemporalCRSNNPtr createTemporalMeasureCRSWithoutConvFactor() {
+
+ auto datum = TemporalDatum::create(
+ PropertyMap().set(IdentifiedObject::NAME_KEY, "Common Era"),
+ DateTime::create("0000"), TemporalDatum::CALENDAR_PROLEPTIC_GREGORIAN);
+
+ auto cs = TemporalMeasureCS::create(
+ PropertyMap(),
+ CoordinateSystemAxis::create(
+ PropertyMap().set(IdentifiedObject::NAME_KEY, "Decimal years"), "a",
+ AxisDirection::FUTURE,
+ UnitOfMeasure("year", 0, UnitOfMeasure::Type::TIME)));
+
+ return TemporalCRS::create(
+ PropertyMap().set(IdentifiedObject::NAME_KEY, "Decimal Years CE"),
+ datum, cs);
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(crs, temporalMeasureCRSWithoutConvFactor_WKT2_2018) {
+
+ auto expected = "TIMECRS[\"Decimal Years CE\",\n"
+ " TDATUM[\"Common Era\",\n"
+ " CALENDAR[\"proleptic Gregorian\"],\n"
+ " TIMEORIGIN[0000]],\n"
+ " CS[TemporalMeasure,1],\n"
+ " AXIS[\"decimal years (a)\",future,\n"
+ " TIMEUNIT[\"year\"]]]";
+
+ EXPECT_EQ(
+ createTemporalMeasureCRSWithoutConvFactor()->exportToWKT(
+ WKTFormatter::create(WKTFormatter::Convention::WKT2_2018).get()),
+ expected);
+}
+
+// ---------------------------------------------------------------------------
+
+static EngineeringCRSNNPtr createEngineeringCRS() {
+
+ auto datum = EngineeringDatum::create(
+ PropertyMap().set(IdentifiedObject::NAME_KEY, "Engineering datum"));
+
+ return EngineeringCRS::create(
+ PropertyMap().set(IdentifiedObject::NAME_KEY, "Engineering CRS"), datum,
+ CartesianCS::createEastingNorthing(UnitOfMeasure::METRE));
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(crs, engineeringCRS_WKT2) {
+
+ auto expected = "ENGCRS[\"Engineering CRS\",\n"
+ " EDATUM[\"Engineering datum\"],\n"
+ " CS[Cartesian,2],\n"
+ " AXIS[\"(E)\",east,\n"
+ " ORDER[1],\n"
+ " LENGTHUNIT[\"metre\",1,\n"
+ " ID[\"EPSG\",9001]]],\n"
+ " AXIS[\"(N)\",north,\n"
+ " ORDER[2],\n"
+ " LENGTHUNIT[\"metre\",1,\n"
+ " ID[\"EPSG\",9001]]]]";
+
+ auto crs = createEngineeringCRS();
+ EXPECT_TRUE(crs->isEquivalentTo(crs.get()));
+ EXPECT_TRUE(crs->shallowClone()->isEquivalentTo(crs.get()));
+ EXPECT_TRUE(!crs->isEquivalentTo(createUnrelatedObject().get()));
+
+ EXPECT_EQ(crs->exportToWKT(
+ WKTFormatter::create(WKTFormatter::Convention::WKT2).get()),
+ expected);
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(crs, engineeringCRS_WKT1) {
+
+ auto expected = "LOCAL_CS[\"Engineering CRS\",\n"
+ " LOCAL_DATUM[\"Engineering datum\",32767],\n"
+ " AXIS[\"Easting\",EAST],\n"
+ " AXIS[\"Northing\",NORTH]]";
+ EXPECT_EQ(
+ createEngineeringCRS()->exportToWKT(
+ WKTFormatter::create(WKTFormatter::Convention::WKT1_GDAL).get()),
+ expected);
+}
+
+// ---------------------------------------------------------------------------
+
+static ParametricCSNNPtr createParametricCS() {
+
+ return ParametricCS::create(
+ PropertyMap(),
+ CoordinateSystemAxis::create(
+ PropertyMap().set(IdentifiedObject::NAME_KEY, "pressure"), "hPa",
+ AxisDirection::UP, UnitOfMeasure("HectoPascal", 100,
+ UnitOfMeasure::Type::PARAMETRIC)));
+}
+
+// ---------------------------------------------------------------------------
+
+static ParametricCRSNNPtr createParametricCRS() {
+
+ auto datum = ParametricDatum::create(
+ PropertyMap().set(IdentifiedObject::NAME_KEY, "Parametric datum"));
+
+ return ParametricCRS::create(
+ PropertyMap().set(IdentifiedObject::NAME_KEY, "Parametric CRS"), datum,
+ createParametricCS());
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(crs, default_identify_method) {
+ EXPECT_TRUE(createParametricCRS()->identify(nullptr).empty());
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(crs, parametricCRS_WKT2) {
+
+ auto expected = "PARAMETRICCRS[\"Parametric CRS\",\n"
+ " PDATUM[\"Parametric datum\"],\n"
+ " CS[parametric,1],\n"
+ " AXIS[\"pressure (hPa)\",up,\n"
+ " PARAMETRICUNIT[\"HectoPascal\",100]]]";
+ auto crs = createParametricCRS();
+ EXPECT_TRUE(crs->isEquivalentTo(crs.get()));
+ EXPECT_TRUE(crs->shallowClone()->isEquivalentTo(crs.get()));
+ EXPECT_TRUE(!crs->isEquivalentTo(createUnrelatedObject().get()));
+
+ EXPECT_EQ(crs->exportToWKT(
+ WKTFormatter::create(WKTFormatter::Convention::WKT2).get()),
+ expected);
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(crs, parametricCRS_WKT1) {
+
+ EXPECT_THROW(
+ createParametricCRS()->exportToWKT(
+ WKTFormatter::create(WKTFormatter::Convention::WKT1_GDAL).get()),
+ FormattingException);
+}
+
+// ---------------------------------------------------------------------------
+
+static DerivedVerticalCRSNNPtr createDerivedVerticalCRS() {
+
+ auto derivingConversion = Conversion::create(
+ PropertyMap().set(IdentifiedObject::NAME_KEY, "unnamed"),
+ PropertyMap().set(IdentifiedObject::NAME_KEY, "PROJ unimplemented"),
+ std::vector<OperationParameterNNPtr>{},
+ std::vector<ParameterValueNNPtr>{});
+
+ return DerivedVerticalCRS::create(
+ PropertyMap().set(IdentifiedObject::NAME_KEY, "Derived vertCRS"),
+ createVerticalCRS(), derivingConversion,
+ VerticalCS::createGravityRelatedHeight(UnitOfMeasure::METRE));
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(crs, DerivedVerticalCRS_WKT2) {
+
+ auto expected = "VERTCRS[\"Derived vertCRS\",\n"
+ " BASEVERTCRS[\"ODN height\",\n"
+ " VDATUM[\"Ordnance Datum Newlyn\"]],\n"
+ " DERIVINGCONVERSION[\"unnamed\",\n"
+ " METHOD[\"PROJ unimplemented\"]],\n"
+ " CS[vertical,1],\n"
+ " AXIS[\"gravity-related height (H)\",up,\n"
+ " LENGTHUNIT[\"metre\",1,\n"
+ " ID[\"EPSG\",9001]]]]";
+
+ auto crs = createDerivedVerticalCRS();
+ EXPECT_TRUE(crs->isEquivalentTo(crs.get()));
+ EXPECT_TRUE(crs->shallowClone()->isEquivalentTo(crs.get()));
+ EXPECT_TRUE(!crs->isEquivalentTo(createUnrelatedObject().get()));
+
+ EXPECT_EQ(crs->exportToWKT(
+ WKTFormatter::create(WKTFormatter::Convention::WKT2).get()),
+ expected);
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(crs, DerivedVerticalCRS_WKT1) {
+
+ EXPECT_THROW(
+ createDerivedVerticalCRS()->exportToWKT(
+ WKTFormatter::create(WKTFormatter::Convention::WKT1_GDAL).get()),
+ FormattingException);
+}
+
+// ---------------------------------------------------------------------------
+
+static DerivedEngineeringCRSNNPtr createDerivedEngineeringCRS() {
+
+ auto derivingConversion = Conversion::create(
+ PropertyMap().set(IdentifiedObject::NAME_KEY, "unnamed"),
+ PropertyMap().set(IdentifiedObject::NAME_KEY, "PROJ unimplemented"),
+ std::vector<OperationParameterNNPtr>{},
+ std::vector<ParameterValueNNPtr>{});
+
+ return DerivedEngineeringCRS::create(
+ PropertyMap().set(IdentifiedObject::NAME_KEY, "Derived EngineeringCRS"),
+ createEngineeringCRS(), derivingConversion,
+ CartesianCS::createEastingNorthing(UnitOfMeasure::METRE));
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(crs, DerivedEngineeringCRS_WKT2) {
+
+ auto expected = "ENGCRS[\"Derived EngineeringCRS\",\n"
+ " BASEENGCRS[\"Engineering CRS\",\n"
+ " EDATUM[\"Engineering datum\"]],\n"
+ " DERIVINGCONVERSION[\"unnamed\",\n"
+ " METHOD[\"PROJ unimplemented\"]],\n"
+ " CS[Cartesian,2],\n"
+ " AXIS[\"(E)\",east,\n"
+ " ORDER[1],\n"
+ " LENGTHUNIT[\"metre\",1,\n"
+ " ID[\"EPSG\",9001]]],\n"
+ " AXIS[\"(N)\",north,\n"
+ " ORDER[2],\n"
+ " LENGTHUNIT[\"metre\",1,\n"
+ " ID[\"EPSG\",9001]]]]";
+
+ auto crs = createDerivedEngineeringCRS();
+ EXPECT_TRUE(crs->isEquivalentTo(crs.get()));
+ EXPECT_TRUE(crs->shallowClone()->isEquivalentTo(crs.get()));
+ EXPECT_TRUE(!crs->isEquivalentTo(createUnrelatedObject().get()));
+ EXPECT_TRUE(crs->coordinateSystem()->isEquivalentTo(
+ CartesianCS::createEastingNorthing(UnitOfMeasure::METRE).get()));
+ EXPECT_TRUE(
+ crs->datum()->isEquivalentTo(createEngineeringCRS()->datum().get()));
+
+ EXPECT_EQ(
+ crs->exportToWKT(
+ WKTFormatter::create(WKTFormatter::Convention::WKT2_2018).get()),
+ expected);
+ EXPECT_THROW(
+ createDerivedEngineeringCRS()->exportToWKT(
+ WKTFormatter::create(WKTFormatter::Convention::WKT2_2015).get()),
+ FormattingException);
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(crs, DerivedEngineeringCRS_WKT1) {
+
+ EXPECT_THROW(
+ createDerivedEngineeringCRS()->exportToWKT(
+ WKTFormatter::create(WKTFormatter::Convention::WKT1_GDAL).get()),
+ FormattingException);
+}
+
+// ---------------------------------------------------------------------------
+
+static DerivedParametricCRSNNPtr createDerivedParametricCRS() {
+
+ auto derivingConversion = Conversion::create(
+ PropertyMap().set(IdentifiedObject::NAME_KEY, "unnamed"),
+ PropertyMap().set(IdentifiedObject::NAME_KEY, "PROJ unimplemented"),
+ std::vector<OperationParameterNNPtr>{},
+ std::vector<ParameterValueNNPtr>{});
+
+ return DerivedParametricCRS::create(
+ PropertyMap().set(IdentifiedObject::NAME_KEY, "Derived ParametricCRS"),
+ createParametricCRS(), derivingConversion, createParametricCS());
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(crs, DerivedParametricCRS_WKT2) {
+
+ auto expected = "PARAMETRICCRS[\"Derived ParametricCRS\",\n"
+ " BASEPARAMCRS[\"Parametric CRS\",\n"
+ " PDATUM[\"Parametric datum\"]],\n"
+ " DERIVINGCONVERSION[\"unnamed\",\n"
+ " METHOD[\"PROJ unimplemented\"]],\n"
+ " CS[parametric,1],\n"
+ " AXIS[\"pressure (hPa)\",up,\n"
+ " PARAMETRICUNIT[\"HectoPascal\",100]]]";
+
+ auto crs = createDerivedParametricCRS();
+ EXPECT_TRUE(crs->isEquivalentTo(crs.get()));
+ EXPECT_TRUE(crs->shallowClone()->isEquivalentTo(crs.get()));
+ EXPECT_TRUE(!crs->isEquivalentTo(createUnrelatedObject().get()));
+ EXPECT_TRUE(
+ crs->coordinateSystem()->isEquivalentTo(createParametricCS().get()));
+ EXPECT_TRUE(
+ crs->datum()->isEquivalentTo(createParametricCRS()->datum().get()));
+
+ EXPECT_EQ(
+ crs->exportToWKT(
+ WKTFormatter::create(WKTFormatter::Convention::WKT2_2018).get()),
+ expected);
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(crs, DerivedParametricCRS_WKT1) {
+
+ EXPECT_THROW(
+ createDerivedParametricCRS()->exportToWKT(
+ WKTFormatter::create(WKTFormatter::Convention::WKT1_GDAL).get()),
+ FormattingException);
+}
+
+// ---------------------------------------------------------------------------
+
+static DerivedTemporalCRSNNPtr createDerivedTemporalCRS() {
+
+ auto derivingConversion = Conversion::create(
+ PropertyMap().set(IdentifiedObject::NAME_KEY, "unnamed"),
+ PropertyMap().set(IdentifiedObject::NAME_KEY, "PROJ unimplemented"),
+ std::vector<OperationParameterNNPtr>{},
+ std::vector<ParameterValueNNPtr>{});
+
+ return DerivedTemporalCRS::create(
+ PropertyMap().set(IdentifiedObject::NAME_KEY, "Derived TemporalCRS"),
+ createDateTimeTemporalCRS(), derivingConversion,
+ createDateTimeTemporalCS());
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(crs, DeriveTemporalCRS_WKT2) {
+
+ auto expected = "TIMECRS[\"Derived TemporalCRS\",\n"
+ " BASETIMECRS[\"Temporal CRS\",\n"
+ " TDATUM[\"Gregorian calendar\",\n"
+ " CALENDAR[\"proleptic Gregorian\"],\n"
+ " TIMEORIGIN[0000-01-01]]],\n"
+ " DERIVINGCONVERSION[\"unnamed\",\n"
+ " METHOD[\"PROJ unimplemented\"]],\n"
+ " CS[TemporalDateTime,1],\n"
+ " AXIS[\"time (T)\",future]]";
+
+ auto crs = createDerivedTemporalCRS();
+ EXPECT_TRUE(crs->isEquivalentTo(crs.get()));
+ EXPECT_TRUE(crs->shallowClone()->isEquivalentTo(crs.get()));
+ EXPECT_TRUE(!crs->isEquivalentTo(createUnrelatedObject().get()));
+ EXPECT_TRUE(crs->coordinateSystem()->isEquivalentTo(
+ createDateTimeTemporalCS().get()));
+ EXPECT_TRUE(crs->datum()->isEquivalentTo(
+ createDateTimeTemporalCRS()->datum().get()));
+
+ EXPECT_EQ(
+ crs->exportToWKT(
+ WKTFormatter::create(WKTFormatter::Convention::WKT2_2018).get()),
+ expected);
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(crs, DeriveTemporalCRS_WKT1) {
+
+ EXPECT_THROW(
+ createDerivedTemporalCRS()->exportToWKT(
+ WKTFormatter::create(WKTFormatter::Convention::WKT1_GDAL).get()),
+ FormattingException);
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(crs, crs_createBoundCRSToWGS84IfPossible) {
+ auto dbContext = DatabaseContext::create();
+ auto factory = AuthorityFactory::create(dbContext, "EPSG");
+ {
+ auto crs_4326 = factory->createCoordinateReferenceSystem("4326");
+ EXPECT_EQ(crs_4326->createBoundCRSToWGS84IfPossible(dbContext),
+ crs_4326);
+ }
+ {
+ auto crs_32631 = factory->createCoordinateReferenceSystem("32631");
+ EXPECT_EQ(crs_32631->createBoundCRSToWGS84IfPossible(dbContext),
+ crs_32631);
+ }
+ {
+ // Pulkovo 42 East Germany
+ auto crs_5670 = factory->createCoordinateReferenceSystem("5670");
+ EXPECT_EQ(crs_5670->createBoundCRSToWGS84IfPossible(dbContext),
+ crs_5670);
+ }
+ {
+ // Pulkovo 42 Romania
+ auto crs_3844 = factory->createCoordinateReferenceSystem("3844");
+ auto bound = crs_3844->createBoundCRSToWGS84IfPossible(dbContext);
+ EXPECT_NE(bound, crs_3844);
+ EXPECT_EQ(bound->createBoundCRSToWGS84IfPossible(dbContext), bound);
+ auto boundCRS = nn_dynamic_pointer_cast<BoundCRS>(bound);
+ ASSERT_TRUE(boundCRS != nullptr);
+ EXPECT_EQ(boundCRS->exportToPROJString(
+ PROJStringFormatter::create(
+ PROJStringFormatter::Convention::PROJ_4)
+ .get()),
+ "+proj=sterea +lat_0=46 +lon_0=25 +k=0.99975 +x_0=500000 "
+ "+y_0=500000 +ellps=krass "
+ "+towgs84=2.329,-147.042,-92.08,-0.309,0.325,0.497,5.69");
+ }
+ {
+ // Pulkovo 42 Poland
+ auto crs_2171 = factory->createCoordinateReferenceSystem("2171");
+ auto bound = crs_2171->createBoundCRSToWGS84IfPossible(dbContext);
+ EXPECT_NE(bound, crs_2171);
+ EXPECT_EQ(bound->createBoundCRSToWGS84IfPossible(dbContext), bound);
+ auto boundCRS = nn_dynamic_pointer_cast<BoundCRS>(bound);
+ ASSERT_TRUE(boundCRS != nullptr);
+ EXPECT_EQ(boundCRS->exportToPROJString(
+ PROJStringFormatter::create(
+ PROJStringFormatter::Convention::PROJ_4)
+ .get()),
+ "+proj=sterea +lat_0=50.625 +lon_0=21.0833333333333 "
+ "+k=0.9998 +x_0=4637000 +y_0=5647000 +ellps=krass "
+ "+towgs84=33.4,-146.6,-76.3,-0.359,-0.053,0.844,-0.84");
+ }
+ {
+ // NTF (Paris)
+ auto crs_4807 = factory->createCoordinateReferenceSystem("4807");
+ auto bound = crs_4807->createBoundCRSToWGS84IfPossible(dbContext);
+ EXPECT_NE(bound, crs_4807);
+ EXPECT_EQ(bound->createBoundCRSToWGS84IfPossible(dbContext), bound);
+ auto boundCRS = nn_dynamic_pointer_cast<BoundCRS>(bound);
+ ASSERT_TRUE(boundCRS != nullptr);
+ EXPECT_EQ(boundCRS->exportToPROJString(
+ PROJStringFormatter::create(
+ PROJStringFormatter::Convention::PROJ_4)
+ .get()),
+ "+proj=longlat +ellps=clrk80ign +pm=paris "
+ "+towgs84=-168,-60,320,0,0,0,0");
+ }
+ {
+ // NTF (Paris) / Lambert zone II + NGF-IGN69 height
+ auto crs_7421 = factory->createCoordinateReferenceSystem("7421");
+ auto bound = crs_7421->createBoundCRSToWGS84IfPossible(dbContext);
+ EXPECT_NE(bound, crs_7421);
+ EXPECT_EQ(bound->createBoundCRSToWGS84IfPossible(dbContext), bound);
+ auto boundCRS = nn_dynamic_pointer_cast<BoundCRS>(bound);
+ ASSERT_TRUE(boundCRS != nullptr);
+ EXPECT_EQ(boundCRS->exportToPROJString(
+ PROJStringFormatter::create(
+ PROJStringFormatter::Convention::PROJ_4)
+ .get()),
+ "+proj=lcc +lat_1=46.8 +lat_0=46.8 +lon_0=0 +k_0=0.99987742 "
+ "+x_0=600000 +y_0=2200000 +ellps=clrk80ign +pm=paris "
+ "+towgs84=-168,-60,320,0,0,0,0 +vunits=m");
+ }
+ {
+ auto crs = createVerticalCRS();
+ EXPECT_EQ(crs->createBoundCRSToWGS84IfPossible(dbContext), crs);
+ }
+ {
+ auto factoryIGNF =
+ AuthorityFactory::create(DatabaseContext::create(), "IGNF");
+ auto crs = factoryIGNF->createCoordinateReferenceSystem("TERA50STEREO");
+ auto bound = crs->createBoundCRSToWGS84IfPossible(dbContext);
+ EXPECT_NE(bound, crs);
+ auto boundCRS = nn_dynamic_pointer_cast<BoundCRS>(bound);
+ ASSERT_TRUE(boundCRS != nullptr);
+ EXPECT_EQ(boundCRS->exportToPROJString(
+ PROJStringFormatter::create(
+ PROJStringFormatter::Convention::PROJ_4)
+ .get()),
+ "+proj=stere +lat_0=-90 +lat_ts=-67 +lon_0=140 +x_0=300000 "
+ "+y_0=-2299363.482 +ellps=intl "
+ "+towgs84=324.912,153.282,172.026,0,0,0,0");
+ }
+ {
+ auto factoryIGNF =
+ AuthorityFactory::create(DatabaseContext::create(), "IGNF");
+ auto crs = factoryIGNF->createCoordinateReferenceSystem("AMST63");
+ auto bound = crs->createBoundCRSToWGS84IfPossible(dbContext);
+ EXPECT_NE(bound, crs);
+ auto boundCRS = nn_dynamic_pointer_cast<BoundCRS>(bound);
+ ASSERT_TRUE(boundCRS != nullptr);
+ EXPECT_EQ(boundCRS->exportToPROJString(
+ PROJStringFormatter::create(
+ PROJStringFormatter::Convention::PROJ_4)
+ .get()),
+ "+proj=geocent +ellps=intl "
+ "+towgs84=109.753,-528.133,-362.244,0,0,0,0");
+ }
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(crs, crs_stripVerticalComponent) {
+
+ {
+ auto crs = GeographicCRS::EPSG_4979->stripVerticalComponent();
+ auto geogCRS = nn_dynamic_pointer_cast<GeographicCRS>(crs);
+ ASSERT_TRUE(geogCRS != nullptr);
+ EXPECT_EQ(geogCRS->coordinateSystem()->axisList().size(), 2);
+ }
+
+ {
+ auto crs = GeographicCRS::EPSG_4326->stripVerticalComponent();
+ EXPECT_TRUE(crs->isEquivalentTo(GeographicCRS::EPSG_4326.get()));
+ }
+
+ {
+ std::vector<CoordinateSystemAxisNNPtr> axis{
+ CoordinateSystemAxis::create(
+ PropertyMap().set(IdentifiedObject::NAME_KEY, "Easting"), "E",
+ AxisDirection::EAST, UnitOfMeasure::METRE),
+ CoordinateSystemAxis::create(
+ PropertyMap().set(IdentifiedObject::NAME_KEY, "Northing"), "N",
+ AxisDirection::NORTH, UnitOfMeasure::METRE),
+ CoordinateSystemAxis::create(
+ PropertyMap().set(IdentifiedObject::NAME_KEY, "Height"), "z",
+ AxisDirection::UP, UnitOfMeasure::METRE)};
+ auto cs(CartesianCS::create(PropertyMap(), axis[0], axis[1], axis[2]));
+ auto projected3DCrs = ProjectedCRS::create(
+ PropertyMap(), GeographicCRS::EPSG_4326,
+ Conversion::createUTM(PropertyMap(), 31, true), cs);
+ auto projCRS = nn_dynamic_pointer_cast<ProjectedCRS>(
+ projected3DCrs->stripVerticalComponent());
+ ASSERT_TRUE(projCRS != nullptr);
+ EXPECT_EQ(projCRS->coordinateSystem()->axisList().size(), 2);
+ }
+}
diff --git a/test/unit/test_datum.cpp b/test/unit/test_datum.cpp
new file mode 100644
index 00000000..18cf244a
--- /dev/null
+++ b/test/unit/test_datum.cpp
@@ -0,0 +1,482 @@
+/******************************************************************************
+ *
+ * Project: PROJ
+ * Purpose: Test ISO19111:2018 implementation
+ * Author: Even Rouault <even dot rouault at spatialys dot com>
+ *
+ ******************************************************************************
+ * Copyright (c) 2018, Even Rouault <even dot rouault at spatialys dot 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.
+ ****************************************************************************/
+
+#include "gtest_include.h"
+
+#include "proj/common.hpp"
+#include "proj/datum.hpp"
+#include "proj/io.hpp"
+#include "proj/metadata.hpp"
+#include "proj/util.hpp"
+
+using namespace osgeo::proj::common;
+using namespace osgeo::proj::datum;
+using namespace osgeo::proj::io;
+using namespace osgeo::proj::metadata;
+using namespace osgeo::proj::util;
+
+namespace {
+struct UnrelatedObject : public IComparable {
+ UnrelatedObject() = default;
+
+ bool _isEquivalentTo(const IComparable *, Criterion) const override {
+ assert(false);
+ return false;
+ }
+};
+
+static nn<std::shared_ptr<UnrelatedObject>> createUnrelatedObject() {
+ return nn_make_shared<UnrelatedObject>();
+}
+} // namespace
+
+// ---------------------------------------------------------------------------
+
+TEST(datum, ellipsoid_from_sphere) {
+
+ auto ellipsoid = Ellipsoid::createSphere(PropertyMap(), Length(6378137));
+ EXPECT_FALSE(ellipsoid->inverseFlattening().has_value());
+ EXPECT_FALSE(ellipsoid->semiMinorAxis().has_value());
+ EXPECT_FALSE(ellipsoid->semiMedianAxis().has_value());
+ EXPECT_TRUE(ellipsoid->isSphere());
+ EXPECT_EQ(ellipsoid->semiMajorAxis(), Length(6378137));
+ EXPECT_EQ(ellipsoid->celestialBody(), "Earth");
+
+ EXPECT_EQ(ellipsoid->computeSemiMinorAxis(), Length(6378137));
+ EXPECT_EQ(ellipsoid->computedInverseFlattening(), 0);
+
+ EXPECT_EQ(
+ ellipsoid->exportToPROJString(PROJStringFormatter::create().get()),
+ "+R=6378137");
+
+ EXPECT_TRUE(ellipsoid->isEquivalentTo(ellipsoid.get()));
+ EXPECT_FALSE(ellipsoid->isEquivalentTo(createUnrelatedObject().get()));
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(datum, ellipsoid_non_earth) {
+
+ auto ellipsoid =
+ Ellipsoid::createSphere(PropertyMap(), Length(1), "Unity sphere");
+ EXPECT_EQ(ellipsoid->celestialBody(), "Unity sphere");
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(datum, ellipsoid_from_inverse_flattening) {
+
+ auto ellipsoid = Ellipsoid::createFlattenedSphere(
+ PropertyMap(), Length(6378137), Scale(298.257223563));
+ EXPECT_TRUE(ellipsoid->inverseFlattening().has_value());
+ EXPECT_FALSE(ellipsoid->semiMinorAxis().has_value());
+ EXPECT_FALSE(ellipsoid->semiMedianAxis().has_value());
+ EXPECT_FALSE(ellipsoid->isSphere());
+ EXPECT_EQ(ellipsoid->semiMajorAxis(), Length(6378137));
+ EXPECT_EQ(*ellipsoid->inverseFlattening(), Scale(298.257223563));
+
+ EXPECT_EQ(ellipsoid->computeSemiMinorAxis().unit(),
+ ellipsoid->semiMajorAxis().unit());
+ EXPECT_NEAR(ellipsoid->computeSemiMinorAxis().value(),
+ Length(6356752.31424518).value(), 1e-9);
+ EXPECT_EQ(ellipsoid->computedInverseFlattening(), 298.257223563);
+
+ EXPECT_EQ(
+ ellipsoid->exportToPROJString(PROJStringFormatter::create().get()),
+ "+ellps=WGS84");
+
+ EXPECT_TRUE(ellipsoid->isEquivalentTo(ellipsoid.get()));
+ EXPECT_FALSE(ellipsoid->isEquivalentTo(
+ Ellipsoid::createTwoAxis(PropertyMap(), Length(6378137),
+ Length(6356752.31424518))
+ .get()));
+ EXPECT_TRUE(ellipsoid->isEquivalentTo(
+ Ellipsoid::createTwoAxis(PropertyMap(), Length(6378137),
+ Length(6356752.31424518))
+ .get(),
+ IComparable::Criterion::EQUIVALENT));
+
+ EXPECT_FALSE(Ellipsoid::WGS84->isEquivalentTo(
+ Ellipsoid::GRS1980.get(), IComparable::Criterion::EQUIVALENT));
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(datum, ellipsoid_from_semi_minor_axis) {
+
+ auto ellipsoid = Ellipsoid::createTwoAxis(PropertyMap(), Length(6378137),
+ Length(6356752.31424518));
+ EXPECT_FALSE(ellipsoid->inverseFlattening().has_value());
+ EXPECT_TRUE(ellipsoid->semiMinorAxis().has_value());
+ EXPECT_FALSE(ellipsoid->semiMedianAxis().has_value());
+ EXPECT_FALSE(ellipsoid->isSphere());
+ EXPECT_EQ(ellipsoid->semiMajorAxis(), Length(6378137));
+ EXPECT_EQ(*ellipsoid->semiMinorAxis(), Length(6356752.31424518));
+
+ EXPECT_EQ(ellipsoid->computeSemiMinorAxis(), Length(6356752.31424518));
+ EXPECT_NEAR(ellipsoid->computedInverseFlattening(), 298.257223563, 1e-10);
+
+ EXPECT_EQ(
+ ellipsoid->exportToPROJString(PROJStringFormatter::create().get()),
+ "+ellps=WGS84");
+
+ EXPECT_TRUE(ellipsoid->isEquivalentTo(ellipsoid.get()));
+ EXPECT_FALSE(ellipsoid->isEquivalentTo(
+ Ellipsoid::createFlattenedSphere(PropertyMap(), Length(6378137),
+ Scale(298.257223563))
+ .get()));
+ EXPECT_TRUE(ellipsoid->isEquivalentTo(
+ Ellipsoid::createFlattenedSphere(PropertyMap(), Length(6378137),
+ Scale(298.257223563))
+ .get(),
+ IComparable::Criterion::EQUIVALENT));
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(datum, prime_meridian_to_PROJString) {
+
+ EXPECT_EQ(PrimeMeridian::GREENWICH->exportToPROJString(
+ PROJStringFormatter::create().get()),
+ "");
+
+ EXPECT_EQ(PrimeMeridian::PARIS->exportToPROJString(
+ PROJStringFormatter::create().get()),
+ "+pm=paris");
+
+ EXPECT_EQ(PrimeMeridian::create(PropertyMap(), Angle(3.5))
+ ->exportToPROJString(PROJStringFormatter::create().get()),
+ "+pm=3.5");
+
+ EXPECT_EQ(
+ PrimeMeridian::create(PropertyMap(), Angle(100, UnitOfMeasure::GRAD))
+ ->exportToPROJString(PROJStringFormatter::create().get()),
+ "+pm=90");
+
+ EXPECT_EQ(
+ PrimeMeridian::create(
+ PropertyMap().set(IdentifiedObject::NAME_KEY, "Origin meridian"),
+ Angle(0))
+ ->exportToPROJString(PROJStringFormatter::create().get()),
+ "");
+
+ EXPECT_TRUE(PrimeMeridian::GREENWICH->isEquivalentTo(
+ PrimeMeridian::GREENWICH.get()));
+ EXPECT_FALSE(PrimeMeridian::GREENWICH->isEquivalentTo(
+ createUnrelatedObject().get()));
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(datum, datum_with_ANCHOR) {
+ auto datum = GeodeticReferenceFrame::create(
+ PropertyMap().set(IdentifiedObject::NAME_KEY, "WGS_1984 with anchor"),
+ Ellipsoid::WGS84, optional<std::string>("My anchor"),
+ PrimeMeridian::GREENWICH);
+
+ auto expected = "DATUM[\"WGS_1984 with anchor\",\n"
+ " ELLIPSOID[\"WGS 84\",6378137,298.257223563,\n"
+ " LENGTHUNIT[\"metre\",1],\n"
+ " ID[\"EPSG\",7030]],\n"
+ " ANCHOR[\"My anchor\"]]";
+
+ EXPECT_EQ(datum->exportToWKT(WKTFormatter::create().get()), expected);
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(datum, dynamic_geodetic_reference_frame) {
+ auto drf = DynamicGeodeticReferenceFrame::create(
+ PropertyMap().set(IdentifiedObject::NAME_KEY, "test"), Ellipsoid::WGS84,
+ optional<std::string>("My anchor"), PrimeMeridian::GREENWICH,
+ Measure(2018.5, UnitOfMeasure::YEAR),
+ optional<std::string>("My model"));
+
+ auto expected = "DATUM[\"test\",\n"
+ " ELLIPSOID[\"WGS 84\",6378137,298.257223563,\n"
+ " LENGTHUNIT[\"metre\",1],\n"
+ " ID[\"EPSG\",7030]],\n"
+ " ANCHOR[\"My anchor\"]]";
+
+ EXPECT_EQ(drf->exportToWKT(WKTFormatter::create().get()), expected);
+
+ auto expected_wtk2_2018 =
+ "DYNAMIC[\n"
+ " FRAMEEPOCH[2018.5],\n"
+ " MODEL[\"My model\"]],\n"
+ "DATUM[\"test\",\n"
+ " ELLIPSOID[\"WGS 84\",6378137,298.257223563,\n"
+ " LENGTHUNIT[\"metre\",1],\n"
+ " ID[\"EPSG\",7030]],\n"
+ " ANCHOR[\"My anchor\"]]";
+ EXPECT_EQ(
+ drf->exportToWKT(
+ WKTFormatter::create(WKTFormatter::Convention::WKT2_2018).get()),
+ expected_wtk2_2018);
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(datum, ellipsoid_to_PROJString) {
+
+ EXPECT_EQ(Ellipsoid::WGS84->exportToPROJString(
+ PROJStringFormatter::create().get()),
+ "+ellps=WGS84");
+
+ EXPECT_EQ(Ellipsoid::GRS1980->exportToPROJString(
+ PROJStringFormatter::create().get()),
+ "+ellps=GRS80");
+
+ EXPECT_EQ(
+ Ellipsoid::createFlattenedSphere(
+ PropertyMap(), Length(10, UnitOfMeasure("km", 1000)), Scale(0.5))
+ ->exportToPROJString(PROJStringFormatter::create().get()),
+ "+a=10000 +rf=0.5");
+
+ EXPECT_EQ(Ellipsoid::createTwoAxis(PropertyMap(),
+ Length(10, UnitOfMeasure("km", 1000)),
+ Length(5, UnitOfMeasure("km", 1000)))
+ ->exportToPROJString(PROJStringFormatter::create().get()),
+ "+a=10000 +b=5000");
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(datum, temporal_datum_WKT2) {
+ auto datum = TemporalDatum::create(
+ PropertyMap().set(IdentifiedObject::NAME_KEY, "Gregorian calendar"),
+ DateTime::create("0000-01-01"),
+ TemporalDatum::CALENDAR_PROLEPTIC_GREGORIAN);
+
+ auto expected = "TDATUM[\"Gregorian calendar\",\n"
+ " TIMEORIGIN[0000-01-01]]";
+
+ EXPECT_EQ(datum->exportToWKT(
+ WKTFormatter::create(WKTFormatter::Convention::WKT2).get()),
+ expected);
+
+ EXPECT_THROW(
+ datum->exportToWKT(
+ WKTFormatter::create(WKTFormatter::Convention::WKT1_GDAL).get()),
+ FormattingException);
+
+ EXPECT_TRUE(datum->isEquivalentTo(datum.get()));
+ EXPECT_FALSE(datum->isEquivalentTo(createUnrelatedObject().get()));
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(datum, temporal_datum_time_origin_non_ISO8601) {
+ auto datum = TemporalDatum::create(
+ PropertyMap().set(IdentifiedObject::NAME_KEY, "Gregorian calendar"),
+ DateTime::create("0001 January 1st"),
+ TemporalDatum::CALENDAR_PROLEPTIC_GREGORIAN);
+
+ auto expected = "TDATUM[\"Gregorian calendar\",\n"
+ " TIMEORIGIN[\"0001 January 1st\"]]";
+
+ EXPECT_EQ(datum->exportToWKT(
+ WKTFormatter::create(WKTFormatter::Convention::WKT2).get()),
+ expected);
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(datum, temporal_datum_WKT2_2018) {
+ auto datum = TemporalDatum::create(
+ PropertyMap().set(IdentifiedObject::NAME_KEY, "Gregorian calendar"),
+ DateTime::create("0000-01-01"),
+ TemporalDatum::CALENDAR_PROLEPTIC_GREGORIAN);
+
+ auto expected = "TDATUM[\"Gregorian calendar\",\n"
+ " CALENDAR[\"proleptic Gregorian\"],\n"
+ " TIMEORIGIN[0000-01-01]]";
+
+ EXPECT_EQ(
+ datum->exportToWKT(
+ WKTFormatter::create(WKTFormatter::Convention::WKT2_2018).get()),
+ expected);
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(datum, dynamic_vertical_reference_frame) {
+ auto drf = DynamicVerticalReferenceFrame::create(
+ PropertyMap().set(IdentifiedObject::NAME_KEY, "test"),
+ optional<std::string>("My anchor"), optional<RealizationMethod>(),
+ Measure(2018.5, UnitOfMeasure::YEAR),
+ optional<std::string>("My model"));
+
+ auto expected = "VDATUM[\"test\",\n"
+ " ANCHOR[\"My anchor\"]]";
+
+ EXPECT_EQ(drf->exportToWKT(WKTFormatter::create().get()), expected);
+
+ auto expected_wtk2_2018 = "DYNAMIC[\n"
+ " FRAMEEPOCH[2018.5],\n"
+ " MODEL[\"My model\"]],\n"
+ "VDATUM[\"test\",\n"
+ " ANCHOR[\"My anchor\"]]";
+ EXPECT_EQ(
+ drf->exportToWKT(
+ WKTFormatter::create(WKTFormatter::Convention::WKT2_2018).get()),
+ expected_wtk2_2018);
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(datum, datum_ensemble) {
+ auto otherDatum = GeodeticReferenceFrame::create(
+ PropertyMap().set(IdentifiedObject::NAME_KEY, "other datum"),
+ Ellipsoid::WGS84, optional<std::string>(), PrimeMeridian::GREENWICH);
+ auto ensemble = DatumEnsemble::create(
+ PropertyMap().set(IdentifiedObject::NAME_KEY, "test"),
+ std::vector<DatumNNPtr>{GeodeticReferenceFrame::EPSG_6326, otherDatum},
+ PositionalAccuracy::create("100"));
+ EXPECT_EQ(ensemble->datums().size(), 2);
+ EXPECT_EQ(ensemble->positionalAccuracy()->value(), "100");
+ EXPECT_THROW(ensemble->exportToWKT(WKTFormatter::create().get()),
+ FormattingException);
+ EXPECT_EQ(
+ ensemble->exportToWKT(
+ WKTFormatter::create(WKTFormatter::Convention::WKT2_2018).get()),
+ "ENSEMBLE[\"test\",\n"
+ " MEMBER[\"World Geodetic System 1984\",\n"
+ " ID[\"EPSG\",6326]],\n"
+ " MEMBER[\"other datum\"],\n"
+ " ELLIPSOID[\"WGS 84\",6378137,298.257223563,\n"
+ " LENGTHUNIT[\"metre\",1],\n"
+ " ID[\"EPSG\",7030]],\n"
+ " ENSEMBLEACCURACY[100]]");
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(datum, datum_ensemble_vertical) {
+ auto ensemble = DatumEnsemble::create(
+ PropertyMap(),
+ std::vector<DatumNNPtr>{
+ VerticalReferenceFrame::create(
+ PropertyMap().set(IdentifiedObject::NAME_KEY, "vdatum1")),
+ VerticalReferenceFrame::create(
+ PropertyMap().set(IdentifiedObject::NAME_KEY, "vdatum2"))},
+ PositionalAccuracy::create("100"));
+ EXPECT_EQ(
+ ensemble->exportToWKT(
+ WKTFormatter::create(WKTFormatter::Convention::WKT2_2018).get()),
+ "ENSEMBLE[\"unnamed\",\n"
+ " MEMBER[\"vdatum1\"],\n"
+ " MEMBER[\"vdatum2\"],\n"
+ " ENSEMBLEACCURACY[100]]");
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(datum, datum_ensemble_exceptions) {
+ // No datum
+ EXPECT_THROW(DatumEnsemble::create(PropertyMap(), std::vector<DatumNNPtr>{},
+ PositionalAccuracy::create("100")),
+ Exception);
+
+ // Single datum
+ EXPECT_THROW(DatumEnsemble::create(
+ PropertyMap(),
+ std::vector<DatumNNPtr>{GeodeticReferenceFrame::EPSG_6326},
+ PositionalAccuracy::create("100")),
+ Exception);
+
+ auto vdatum = VerticalReferenceFrame::create(
+ PropertyMap().set(IdentifiedObject::NAME_KEY, "vdatum1"));
+
+ // Different datum type
+ EXPECT_THROW(
+ DatumEnsemble::create(
+ PropertyMap(),
+ std::vector<DatumNNPtr>{GeodeticReferenceFrame::EPSG_6326, vdatum},
+ PositionalAccuracy::create("100")),
+ Exception);
+
+ // Different datum type
+ EXPECT_THROW(
+ DatumEnsemble::create(
+ PropertyMap(),
+ std::vector<DatumNNPtr>{vdatum, GeodeticReferenceFrame::EPSG_6326},
+ PositionalAccuracy::create("100")),
+ Exception);
+
+ // Different ellipsoid
+ EXPECT_THROW(DatumEnsemble::create(
+ PropertyMap(),
+ std::vector<DatumNNPtr>{GeodeticReferenceFrame::EPSG_6326,
+ GeodeticReferenceFrame::EPSG_6267},
+ PositionalAccuracy::create("100")),
+ Exception);
+
+ // Different prime meridian
+ EXPECT_THROW(DatumEnsemble::create(
+ PropertyMap(),
+ std::vector<DatumNNPtr>{
+ GeodeticReferenceFrame::EPSG_6326,
+ GeodeticReferenceFrame::create(
+ PropertyMap().set(IdentifiedObject::NAME_KEY,
+ "other datum"),
+ Ellipsoid::WGS84, optional<std::string>(),
+ PrimeMeridian::PARIS)},
+ PositionalAccuracy::create("100")),
+ Exception);
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(datum, edatum) {
+ auto datum = EngineeringDatum::create(
+ PropertyMap().set(IdentifiedObject::NAME_KEY, "Engineering datum"),
+ optional<std::string>("my anchor"));
+
+ auto expected = "EDATUM[\"Engineering datum\",\n"
+ " ANCHOR[\"my anchor\"]]";
+
+ EXPECT_EQ(datum->exportToWKT(
+ WKTFormatter::create(WKTFormatter::Convention::WKT2).get()),
+ expected);
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(datum, pdatum) {
+ auto datum = ParametricDatum::create(
+ PropertyMap().set(IdentifiedObject::NAME_KEY, "Parametric datum"),
+ optional<std::string>("my anchor"));
+
+ auto expected = "PDATUM[\"Parametric datum\",\n"
+ " ANCHOR[\"my anchor\"]]";
+
+ EXPECT_EQ(datum->exportToWKT(
+ WKTFormatter::create(WKTFormatter::Convention::WKT2).get()),
+ expected);
+}
diff --git a/test/unit/test_factory.cpp b/test/unit/test_factory.cpp
new file mode 100644
index 00000000..739bb729
--- /dev/null
+++ b/test/unit/test_factory.cpp
@@ -0,0 +1,2732 @@
+/******************************************************************************
+ *
+ * Project: PROJ
+ * Purpose: Test ISO19111:2018 implementation
+ * Author: Even Rouault <even dot rouault at spatialys dot com>
+ *
+ ******************************************************************************
+ * Copyright (c) 2018, Even Rouault <even dot rouault at spatialys dot 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.
+ ****************************************************************************/
+
+#include "gtest_include.h"
+
+#include "proj/common.hpp"
+#include "proj/coordinateoperation.hpp"
+#include "proj/coordinatesystem.hpp"
+#include "proj/crs.hpp"
+#include "proj/datum.hpp"
+#include "proj/io.hpp"
+#include "proj/metadata.hpp"
+#include "proj/util.hpp"
+
+#include <sqlite3.h>
+
+#ifdef _MSC_VER
+#include <stdio.h>
+#else
+#include <unistd.h>
+#endif
+
+//#undef SQLITE_OPEN_URI
+
+using namespace osgeo::proj::common;
+using namespace osgeo::proj::crs;
+using namespace osgeo::proj::cs;
+using namespace osgeo::proj::datum;
+using namespace osgeo::proj::io;
+using namespace osgeo::proj::metadata;
+using namespace osgeo::proj::operation;
+using namespace osgeo::proj::util;
+
+namespace {
+
+// ---------------------------------------------------------------------------
+
+TEST(factory, databasecontext_create) {
+ DatabaseContext::create();
+#ifndef _WIN32
+ // For some reason, no exception is thrown on AppVeyor Windows
+ EXPECT_THROW(DatabaseContext::create("/i/dont/exist"), FactoryException);
+#endif
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(factory, AuthorityFactory_createObject) {
+ auto factory = AuthorityFactory::create(DatabaseContext::create(), "EPSG");
+ EXPECT_THROW(factory->createObject("-1"), NoSuchAuthorityCodeException);
+ EXPECT_THROW(factory->createObject("4326"),
+ FactoryException); // area and crs
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(factory, AuthorityFactory_createUnitOfMeasure_linear) {
+ auto factory = AuthorityFactory::create(DatabaseContext::create(), "EPSG");
+ EXPECT_THROW(factory->createUnitOfMeasure("-1"),
+ NoSuchAuthorityCodeException);
+ EXPECT_TRUE(nn_dynamic_pointer_cast<UnitOfMeasure>(
+ factory->createObject("9001")) != nullptr);
+ auto uom = factory->createUnitOfMeasure("9001");
+ EXPECT_EQ(uom->name(), "metre");
+ EXPECT_EQ(uom->type(), UnitOfMeasure::Type::LINEAR);
+ EXPECT_EQ(uom->conversionToSI(), 1.0);
+ EXPECT_EQ(uom->codeSpace(), "EPSG");
+ EXPECT_EQ(uom->code(), "9001");
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(factory, AuthorityFactory_createUnitOfMeasure_angular) {
+ auto factory = AuthorityFactory::create(DatabaseContext::create(), "EPSG");
+ auto uom = factory->createUnitOfMeasure("9102");
+ EXPECT_EQ(uom->name(), "degree");
+ EXPECT_EQ(uom->type(), UnitOfMeasure::Type::ANGULAR);
+ EXPECT_EQ(uom->conversionToSI(), UnitOfMeasure::DEGREE.conversionToSI());
+ EXPECT_EQ(uom->codeSpace(), "EPSG");
+ EXPECT_EQ(uom->code(), "9102");
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(factory, AuthorityFactory_createUnitOfMeasure_angular_9107) {
+ auto factory = AuthorityFactory::create(DatabaseContext::create(), "EPSG");
+ auto uom = factory->createUnitOfMeasure("9107");
+ EXPECT_EQ(uom->name(), "degree minute second");
+ EXPECT_EQ(uom->type(), UnitOfMeasure::Type::ANGULAR);
+ EXPECT_EQ(uom->conversionToSI(), UnitOfMeasure::DEGREE.conversionToSI());
+ EXPECT_EQ(uom->codeSpace(), "EPSG");
+ EXPECT_EQ(uom->code(), "9107");
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(factory, AuthorityFactory_createUnitOfMeasure_scale) {
+ auto factory = AuthorityFactory::create(DatabaseContext::create(), "EPSG");
+ auto uom = factory->createUnitOfMeasure("1028");
+ EXPECT_EQ(uom->name(), "parts per billion");
+ EXPECT_EQ(uom->type(), UnitOfMeasure::Type::SCALE);
+ EXPECT_EQ(uom->conversionToSI(), 1e-9);
+ EXPECT_EQ(uom->codeSpace(), "EPSG");
+ EXPECT_EQ(uom->code(), "1028");
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(factory, AuthorityFactory_createUnitOfMeasure_time) {
+ auto factory = AuthorityFactory::create(DatabaseContext::create(), "EPSG");
+ auto uom = factory->createUnitOfMeasure("1029");
+ EXPECT_EQ(uom->name(), "year");
+ EXPECT_EQ(uom->type(), UnitOfMeasure::Type::TIME);
+ EXPECT_EQ(uom->conversionToSI(), 31556925.445);
+ EXPECT_EQ(uom->codeSpace(), "EPSG");
+ EXPECT_EQ(uom->code(), "1029");
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(factory, AuthorityFactory_createPrimeMeridian) {
+ auto factory = AuthorityFactory::create(DatabaseContext::create(), "EPSG");
+ EXPECT_THROW(factory->createPrimeMeridian("-1"),
+ NoSuchAuthorityCodeException);
+ EXPECT_TRUE(nn_dynamic_pointer_cast<PrimeMeridian>(
+ factory->createObject("8903")) != nullptr);
+ auto pm = factory->createPrimeMeridian("8903");
+ ASSERT_EQ(pm->identifiers().size(), 1);
+ EXPECT_EQ(pm->identifiers()[0]->code(), "8903");
+ EXPECT_EQ(*(pm->identifiers()[0]->codeSpace()), "EPSG");
+ EXPECT_EQ(*(pm->name()->description()), "Paris");
+ EXPECT_EQ(pm->longitude(), Angle(2.5969213, UnitOfMeasure::GRAD));
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(factory, AuthorityFactory_identifyBodyFromSemiMajorAxis) {
+ auto factory = AuthorityFactory::create(DatabaseContext::create(), "EPSG");
+ EXPECT_EQ(factory->identifyBodyFromSemiMajorAxis(6378137, 1e-5), "Earth");
+ EXPECT_THROW(factory->identifyBodyFromSemiMajorAxis(1, 1e-5),
+ FactoryException);
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(factory, AuthorityFactory_createEllipsoid) {
+ auto factory = AuthorityFactory::create(DatabaseContext::create(), "EPSG");
+ EXPECT_THROW(factory->createEllipsoid("-1"), NoSuchAuthorityCodeException);
+ EXPECT_TRUE(nn_dynamic_pointer_cast<Ellipsoid>(
+ factory->createObject("7030")) != nullptr);
+ auto ellipsoid = factory->createEllipsoid("7030");
+ ASSERT_EQ(ellipsoid->identifiers().size(), 1);
+ EXPECT_EQ(ellipsoid->identifiers()[0]->code(), "7030");
+ EXPECT_EQ(*(ellipsoid->identifiers()[0]->codeSpace()), "EPSG");
+ EXPECT_EQ(*(ellipsoid->name()->description()), "WGS 84");
+ EXPECT_TRUE(ellipsoid->inverseFlattening().has_value());
+ EXPECT_EQ(ellipsoid->semiMajorAxis(), Length(6378137));
+ EXPECT_EQ(*ellipsoid->inverseFlattening(), Scale(298.257223563));
+ EXPECT_EQ(ellipsoid->celestialBody(), "Earth");
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(factory, AuthorityFactory_createEllipsoid_sphere) {
+ auto factory = AuthorityFactory::create(DatabaseContext::create(), "EPSG");
+ auto ellipsoid = factory->createEllipsoid("7035");
+ EXPECT_TRUE(ellipsoid->isSphere());
+ EXPECT_EQ(ellipsoid->semiMajorAxis(), Length(6371000));
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(factory, AuthorityFactory_createEllipsoid_with_semi_minor_axis) {
+ auto factory = AuthorityFactory::create(DatabaseContext::create(), "EPSG");
+ auto ellipsoid = factory->createEllipsoid("7011");
+ EXPECT_TRUE(ellipsoid->semiMinorAxis().has_value());
+ EXPECT_EQ(ellipsoid->semiMajorAxis(), Length(6378249.2));
+ EXPECT_EQ(*ellipsoid->semiMinorAxis(), Length(6356515.0));
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(factory, AuthorityFactory_createExtent) {
+ auto factory = AuthorityFactory::create(DatabaseContext::create(), "EPSG");
+ EXPECT_THROW(factory->createExtent("-1"), NoSuchAuthorityCodeException);
+ auto extent = factory->createExtent("1262");
+ EXPECT_EQ(*(extent->description()), "World");
+ const auto &geogElts = extent->geographicElements();
+ ASSERT_EQ(geogElts.size(), 1);
+ auto bbox = nn_dynamic_pointer_cast<GeographicBoundingBox>(geogElts[0]);
+ ASSERT_TRUE(bbox != nullptr);
+ EXPECT_EQ(bbox->westBoundLongitude(), -180);
+ EXPECT_EQ(bbox->eastBoundLongitude(), 180);
+ EXPECT_EQ(bbox->northBoundLatitude(), 90);
+ EXPECT_EQ(bbox->southBoundLatitude(), -90);
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(factory, AuthorityFactory_createGeodeticDatum) {
+ auto factory = AuthorityFactory::create(DatabaseContext::create(), "EPSG");
+ EXPECT_THROW(factory->createGeodeticDatum("-1"),
+ NoSuchAuthorityCodeException);
+ auto grf = factory->createGeodeticDatum("6326");
+ ASSERT_EQ(grf->identifiers().size(), 1);
+ EXPECT_EQ(grf->identifiers()[0]->code(), "6326");
+ EXPECT_EQ(*(grf->identifiers()[0]->codeSpace()), "EPSG");
+ EXPECT_EQ(*(grf->name()->description()), "World Geodetic System 1984");
+ EXPECT_TRUE(grf->ellipsoid()->isEquivalentTo(
+ factory->createEllipsoid("7030").get()));
+ EXPECT_TRUE(grf->primeMeridian()->isEquivalentTo(
+ factory->createPrimeMeridian("8901").get()));
+ ASSERT_EQ(grf->domains().size(), 1);
+ auto domain = grf->domains()[0];
+ auto extent = domain->domainOfValidity();
+ ASSERT_TRUE(extent != nullptr);
+ EXPECT_TRUE(extent->isEquivalentTo(factory->createExtent("1262").get()));
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(factory, AuthorityFactory_createVerticalDatum) {
+ auto factory = AuthorityFactory::create(DatabaseContext::create(), "EPSG");
+ EXPECT_THROW(factory->createVerticalDatum("-1"),
+ NoSuchAuthorityCodeException);
+ auto vrf = factory->createVerticalDatum("1027");
+ ASSERT_EQ(vrf->identifiers().size(), 1);
+ EXPECT_EQ(vrf->identifiers()[0]->code(), "1027");
+ EXPECT_EQ(*(vrf->identifiers()[0]->codeSpace()), "EPSG");
+ EXPECT_EQ(*(vrf->name()->description()), "EGM2008 geoid");
+ auto domain = vrf->domains()[0];
+ auto extent = domain->domainOfValidity();
+ ASSERT_TRUE(extent != nullptr);
+ EXPECT_TRUE(extent->isEquivalentTo(factory->createExtent("1262").get()));
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(factory, AuthorityFactory_createDatum) {
+ auto factory = AuthorityFactory::create(DatabaseContext::create(), "EPSG");
+ EXPECT_THROW(factory->createDatum("-1"), NoSuchAuthorityCodeException);
+ EXPECT_TRUE(factory->createDatum("6326")->isEquivalentTo(
+ factory->createGeodeticDatum("6326").get()));
+ EXPECT_TRUE(factory->createDatum("1027")->isEquivalentTo(
+ factory->createVerticalDatum("1027").get()));
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(factory, AuthorityFactory_createCoordinateSystem_ellipsoidal_2_axis) {
+ auto factory = AuthorityFactory::create(DatabaseContext::create(), "EPSG");
+ EXPECT_THROW(factory->createCoordinateSystem("-1"),
+ NoSuchAuthorityCodeException);
+ auto cs = factory->createCoordinateSystem("6422");
+ auto ellipsoidal_cs = nn_dynamic_pointer_cast<EllipsoidalCS>(cs);
+ ASSERT_TRUE(ellipsoidal_cs != nullptr);
+
+ ASSERT_EQ(ellipsoidal_cs->identifiers().size(), 1);
+ EXPECT_EQ(ellipsoidal_cs->identifiers()[0]->code(), "6422");
+ EXPECT_EQ(*(ellipsoidal_cs->identifiers()[0]->codeSpace()), "EPSG");
+
+ const auto &axisList = ellipsoidal_cs->axisList();
+ EXPECT_EQ(axisList.size(), 2);
+
+ EXPECT_EQ(*(axisList[0]->name()->description()), "Geodetic latitude");
+ EXPECT_EQ(axisList[0]->abbreviation(), "Lat");
+ EXPECT_EQ(axisList[0]->direction(), AxisDirection::NORTH);
+ EXPECT_EQ(axisList[0]->unit(), UnitOfMeasure::DEGREE);
+
+ EXPECT_EQ(*(axisList[1]->name()->description()), "Geodetic longitude");
+ EXPECT_EQ(axisList[1]->abbreviation(), "Lon");
+ EXPECT_EQ(axisList[1]->direction(), AxisDirection::EAST);
+ EXPECT_EQ(axisList[1]->unit(), UnitOfMeasure::DEGREE);
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(factory, AuthorityFactory_createCoordinateSystem_ellipsoidal_3_axis) {
+ auto factory = AuthorityFactory::create(DatabaseContext::create(), "EPSG");
+
+ auto cs = factory->createCoordinateSystem("6423");
+ auto ellipsoidal_cs = nn_dynamic_pointer_cast<EllipsoidalCS>(cs);
+ ASSERT_TRUE(ellipsoidal_cs != nullptr);
+
+ ASSERT_EQ(ellipsoidal_cs->identifiers().size(), 1);
+ EXPECT_EQ(ellipsoidal_cs->identifiers()[0]->code(), "6423");
+ EXPECT_EQ(*(ellipsoidal_cs->identifiers()[0]->codeSpace()), "EPSG");
+
+ const auto &axisList = ellipsoidal_cs->axisList();
+ EXPECT_EQ(axisList.size(), 3);
+
+ EXPECT_EQ(*(axisList[0]->name()->description()), "Geodetic latitude");
+ EXPECT_EQ(axisList[0]->abbreviation(), "Lat");
+ EXPECT_EQ(axisList[0]->direction(), AxisDirection::NORTH);
+ EXPECT_EQ(axisList[0]->unit(), UnitOfMeasure::DEGREE);
+
+ EXPECT_EQ(*(axisList[1]->name()->description()), "Geodetic longitude");
+ EXPECT_EQ(axisList[1]->abbreviation(), "Lon");
+ EXPECT_EQ(axisList[1]->direction(), AxisDirection::EAST);
+ EXPECT_EQ(axisList[1]->unit(), UnitOfMeasure::DEGREE);
+
+ EXPECT_EQ(*(axisList[2]->name()->description()), "Ellipsoidal height");
+ EXPECT_EQ(axisList[2]->abbreviation(), "h");
+ EXPECT_EQ(axisList[2]->direction(), AxisDirection::UP);
+ EXPECT_EQ(axisList[2]->unit(), UnitOfMeasure::METRE);
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(factory, AuthorityFactory_createCoordinateSystem_geocentric) {
+ auto factory = AuthorityFactory::create(DatabaseContext::create(), "EPSG");
+
+ auto cs = factory->createCoordinateSystem("6500");
+ auto cartesian_cs = nn_dynamic_pointer_cast<CartesianCS>(cs);
+ ASSERT_TRUE(cartesian_cs != nullptr);
+
+ ASSERT_EQ(cartesian_cs->identifiers().size(), 1);
+ EXPECT_EQ(cartesian_cs->identifiers()[0]->code(), "6500");
+ EXPECT_EQ(*(cartesian_cs->identifiers()[0]->codeSpace()), "EPSG");
+
+ const auto &axisList = cartesian_cs->axisList();
+ EXPECT_EQ(axisList.size(), 3);
+
+ EXPECT_EQ(*(axisList[0]->name()->description()), "Geocentric X");
+ EXPECT_EQ(axisList[0]->abbreviation(), "X");
+ EXPECT_EQ(axisList[0]->direction(), AxisDirection::GEOCENTRIC_X);
+ EXPECT_EQ(axisList[0]->unit(), UnitOfMeasure::METRE);
+
+ EXPECT_EQ(*(axisList[1]->name()->description()), "Geocentric Y");
+ EXPECT_EQ(axisList[1]->abbreviation(), "Y");
+ EXPECT_EQ(axisList[1]->direction(), AxisDirection::GEOCENTRIC_Y);
+ EXPECT_EQ(axisList[1]->unit(), UnitOfMeasure::METRE);
+
+ EXPECT_EQ(*(axisList[2]->name()->description()), "Geocentric Z");
+ EXPECT_EQ(axisList[2]->abbreviation(), "Z");
+ EXPECT_EQ(axisList[2]->direction(), AxisDirection::GEOCENTRIC_Z);
+ EXPECT_EQ(axisList[2]->unit(), UnitOfMeasure::METRE);
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(factory, AuthorityFactory_createCoordinateSystem_vertical) {
+ auto factory = AuthorityFactory::create(DatabaseContext::create(), "EPSG");
+ EXPECT_THROW(factory->createCoordinateSystem("-1"),
+ NoSuchAuthorityCodeException);
+
+ auto cs = factory->createCoordinateSystem("6499");
+ auto vertical_cs = nn_dynamic_pointer_cast<VerticalCS>(cs);
+ ASSERT_TRUE(vertical_cs != nullptr);
+
+ ASSERT_EQ(vertical_cs->identifiers().size(), 1);
+ EXPECT_EQ(vertical_cs->identifiers()[0]->code(), "6499");
+ EXPECT_EQ(*(vertical_cs->identifiers()[0]->codeSpace()), "EPSG");
+
+ const auto &axisList = vertical_cs->axisList();
+ EXPECT_EQ(axisList.size(), 1);
+
+ EXPECT_EQ(*(axisList[0]->name()->description()), "Gravity-related height");
+ EXPECT_EQ(axisList[0]->abbreviation(), "H");
+ EXPECT_EQ(axisList[0]->direction(), AxisDirection::UP);
+ EXPECT_EQ(axisList[0]->unit(), UnitOfMeasure::METRE);
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(factory, AuthorityFactory_createGeodeticCRS_geographic2D) {
+ auto factory = AuthorityFactory::create(DatabaseContext::create(), "EPSG");
+ EXPECT_THROW(factory->createGeodeticCRS("-1"),
+ NoSuchAuthorityCodeException);
+ auto crs = factory->createGeodeticCRS("4326");
+ auto gcrs = nn_dynamic_pointer_cast<GeographicCRS>(crs);
+ ASSERT_TRUE(gcrs != nullptr);
+ ASSERT_EQ(gcrs->identifiers().size(), 1);
+ EXPECT_EQ(gcrs->identifiers()[0]->code(), "4326");
+ EXPECT_EQ(*(gcrs->identifiers()[0]->codeSpace()), "EPSG");
+ EXPECT_EQ(*(gcrs->name()->description()), "WGS 84");
+ EXPECT_TRUE(
+ gcrs->datum()->isEquivalentTo(factory->createDatum("6326").get()));
+ EXPECT_TRUE(gcrs->coordinateSystem()->isEquivalentTo(
+ factory->createCoordinateSystem("6422").get()));
+ auto domain = crs->domains()[0];
+ auto extent = domain->domainOfValidity();
+ ASSERT_TRUE(extent != nullptr);
+ EXPECT_TRUE(extent->isEquivalentTo(factory->createExtent("1262").get()));
+
+ EXPECT_EQ(crs->exportToPROJString(PROJStringFormatter::create().get()),
+ "+proj=pipeline +step +proj=longlat +ellps=WGS84 +step "
+ "+proj=unitconvert +xy_in=rad +xy_out=deg +step +proj=axisswap "
+ "+order=2,1");
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(factory, AuthorityFactory_createGeodeticCRS_geographic3D) {
+ auto factory = AuthorityFactory::create(DatabaseContext::create(), "EPSG");
+ auto crs = factory->createGeodeticCRS("4979");
+ auto gcrs = nn_dynamic_pointer_cast<GeographicCRS>(crs);
+ ASSERT_TRUE(gcrs != nullptr);
+ ASSERT_EQ(gcrs->identifiers().size(), 1);
+ EXPECT_EQ(gcrs->identifiers()[0]->code(), "4979");
+ EXPECT_EQ(*(gcrs->identifiers()[0]->codeSpace()), "EPSG");
+ EXPECT_EQ(*(gcrs->name()->description()), "WGS 84");
+ EXPECT_TRUE(
+ gcrs->datum()->isEquivalentTo(factory->createDatum("6326").get()));
+ EXPECT_TRUE(gcrs->coordinateSystem()->isEquivalentTo(
+ factory->createCoordinateSystem("6423").get()));
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(factory, AuthorityFactory_createGeodeticCRS_geocentric) {
+ auto factory = AuthorityFactory::create(DatabaseContext::create(), "EPSG");
+ auto crs = factory->createGeodeticCRS("4978");
+ ASSERT_TRUE(nn_dynamic_pointer_cast<GeographicCRS>(crs) == nullptr);
+ ASSERT_EQ(crs->identifiers().size(), 1);
+ EXPECT_EQ(crs->identifiers()[0]->code(), "4978");
+ EXPECT_EQ(*(crs->identifiers()[0]->codeSpace()), "EPSG");
+ EXPECT_EQ(*(crs->name()->description()), "WGS 84");
+ EXPECT_TRUE(
+ crs->datum()->isEquivalentTo(factory->createDatum("6326").get()));
+ EXPECT_TRUE(crs->coordinateSystem()->isEquivalentTo(
+ factory->createCoordinateSystem("6500").get()));
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(factory, AuthorityFactory_createVerticalCRS) {
+ auto factory = AuthorityFactory::create(DatabaseContext::create(), "EPSG");
+ EXPECT_THROW(factory->createVerticalCRS("-1"),
+ NoSuchAuthorityCodeException);
+
+ auto crs = factory->createVerticalCRS("3855");
+ ASSERT_EQ(crs->identifiers().size(), 1);
+ EXPECT_EQ(crs->identifiers()[0]->code(), "3855");
+ EXPECT_EQ(*(crs->identifiers()[0]->codeSpace()), "EPSG");
+ EXPECT_EQ(*(crs->name()->description()), "EGM2008 height");
+ EXPECT_TRUE(
+ crs->datum()->isEquivalentTo(factory->createDatum("1027").get()));
+ EXPECT_TRUE(crs->coordinateSystem()->isEquivalentTo(
+ factory->createCoordinateSystem("6499").get()));
+
+ auto domain = crs->domains()[0];
+ auto extent = domain->domainOfValidity();
+ ASSERT_TRUE(extent != nullptr);
+ EXPECT_TRUE(extent->isEquivalentTo(factory->createExtent("1262").get()));
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(factory, AuthorityFactory_createConversion) {
+ auto factory = AuthorityFactory::create(DatabaseContext::create(), "EPSG");
+ EXPECT_THROW(factory->createConversion("-1"), NoSuchAuthorityCodeException);
+
+ auto conv = factory->createConversion("16031");
+ ASSERT_EQ(conv->identifiers().size(), 1);
+ EXPECT_EQ(conv->identifiers()[0]->code(), "16031");
+ EXPECT_EQ(*(conv->identifiers()[0]->codeSpace()), "EPSG");
+ EXPECT_EQ(*(conv->name()->description()), "UTM zone 31N");
+
+ auto method = conv->method();
+ ASSERT_EQ(method->identifiers().size(), 1);
+ EXPECT_EQ(method->identifiers()[0]->code(), "9807");
+ EXPECT_EQ(*(method->identifiers()[0]->codeSpace()), "EPSG");
+ EXPECT_EQ(*(method->name()->description()), "Transverse Mercator");
+
+ const auto &values = conv->parameterValues();
+ ASSERT_EQ(values.size(), 5);
+ {
+ const auto &opParamvalue =
+ nn_dynamic_pointer_cast<OperationParameterValue>(values[0]);
+ ASSERT_TRUE(opParamvalue);
+ const auto &paramName =
+ *(opParamvalue->parameter()->name()->description());
+ const auto &parameterValue = opParamvalue->parameterValue();
+ EXPECT_TRUE(opParamvalue->parameter()->getEPSGCode() == 8801);
+ EXPECT_EQ(paramName, "Latitude of natural origin");
+ EXPECT_EQ(parameterValue->type(), ParameterValue::Type::MEASURE);
+ auto measure = parameterValue->value();
+ EXPECT_EQ(measure.unit(), UnitOfMeasure::DEGREE);
+ EXPECT_EQ(measure.value(), 0.0);
+ }
+ {
+ const auto &opParamvalue =
+ nn_dynamic_pointer_cast<OperationParameterValue>(values[1]);
+ ASSERT_TRUE(opParamvalue);
+ const auto &paramName =
+ *(opParamvalue->parameter()->name()->description());
+ const auto &parameterValue = opParamvalue->parameterValue();
+ EXPECT_TRUE(opParamvalue->parameter()->getEPSGCode() == 8802);
+ EXPECT_EQ(paramName, "Longitude of natural origin");
+ EXPECT_EQ(parameterValue->type(), ParameterValue::Type::MEASURE);
+ auto measure = parameterValue->value();
+ EXPECT_EQ(measure.unit(), UnitOfMeasure::DEGREE);
+ EXPECT_EQ(measure.value(), 3.0);
+ }
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(factory, AuthorityFactory_createProjectedCRS) {
+ auto factory = AuthorityFactory::create(DatabaseContext::create(), "EPSG");
+ EXPECT_THROW(factory->createProjectedCRS("-1"),
+ NoSuchAuthorityCodeException);
+
+ auto crs = factory->createProjectedCRS("32631");
+ ASSERT_EQ(crs->identifiers().size(), 1);
+ EXPECT_EQ(crs->identifiers()[0]->code(), "32631");
+ EXPECT_EQ(*(crs->identifiers()[0]->codeSpace()), "EPSG");
+ EXPECT_EQ(*(crs->name()->description()), "WGS 84 / UTM zone 31N");
+ EXPECT_TRUE(crs->baseCRS()->isEquivalentTo(
+ factory->createGeodeticCRS("4326").get()));
+ EXPECT_TRUE(crs->coordinateSystem()->isEquivalentTo(
+ factory->createCoordinateSystem("4400").get()));
+ EXPECT_TRUE(crs->derivingConversion()->isEquivalentTo(
+ factory->createConversion("16031").get()));
+
+ auto domain = crs->domains()[0];
+ auto extent = domain->domainOfValidity();
+ ASSERT_TRUE(extent != nullptr);
+ EXPECT_TRUE(extent->isEquivalentTo(factory->createExtent("2060").get()));
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(factory, AuthorityFactory_createProjectedCRS_south_pole) {
+ auto factory = AuthorityFactory::create(DatabaseContext::create(), "EPSG");
+ EXPECT_THROW(factory->createProjectedCRS("-1"),
+ NoSuchAuthorityCodeException);
+
+ auto crs = factory->createProjectedCRS("32761");
+ auto csList = crs->coordinateSystem()->axisList();
+ ASSERT_EQ(csList.size(), 2);
+ EXPECT_TRUE(csList[0]->meridian() != nullptr);
+ EXPECT_EQ(csList[0]->direction(), AxisDirection::NORTH);
+ EXPECT_EQ(
+ csList[0]->meridian()->longitude().convertToUnit(UnitOfMeasure::DEGREE),
+ 0);
+ EXPECT_EQ(csList[1]->direction(), AxisDirection::NORTH);
+ EXPECT_EQ(
+ csList[1]->meridian()->longitude().convertToUnit(UnitOfMeasure::DEGREE),
+ 90);
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(factory, AuthorityFactory_createProjectedCRS_north_pole) {
+ auto factory = AuthorityFactory::create(DatabaseContext::create(), "EPSG");
+
+ auto crs = factory->createProjectedCRS("32661");
+ auto csList = crs->coordinateSystem()->axisList();
+ ASSERT_EQ(csList.size(), 2);
+ EXPECT_TRUE(csList[0]->meridian() != nullptr);
+ EXPECT_EQ(csList[0]->direction(), AxisDirection::SOUTH);
+ EXPECT_EQ(
+ csList[0]->meridian()->longitude().convertToUnit(UnitOfMeasure::DEGREE),
+ 180);
+ EXPECT_EQ(csList[1]->direction(), AxisDirection::SOUTH);
+ EXPECT_EQ(
+ csList[1]->meridian()->longitude().convertToUnit(UnitOfMeasure::DEGREE),
+ 90);
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(factory, AuthorityFactory_createCompoundCRS) {
+ auto factory = AuthorityFactory::create(DatabaseContext::create(), "EPSG");
+ EXPECT_THROW(factory->createCompoundCRS("-1"),
+ NoSuchAuthorityCodeException);
+
+ auto crs = factory->createCompoundCRS("6871");
+ ASSERT_EQ(crs->identifiers().size(), 1);
+ EXPECT_EQ(crs->identifiers()[0]->code(), "6871");
+ EXPECT_EQ(*(crs->identifiers()[0]->codeSpace()), "EPSG");
+ EXPECT_EQ(*(crs->name()->description()),
+ "WGS 84 / Pseudo-Mercator + EGM2008 geoid height");
+
+ auto components = crs->componentReferenceSystems();
+ ASSERT_EQ(components.size(), 2);
+ EXPECT_TRUE(components[0]->isEquivalentTo(
+ factory->createProjectedCRS("3857").get()));
+ EXPECT_TRUE(components[1]->isEquivalentTo(
+ factory->createVerticalCRS("3855").get()));
+
+ auto domain = crs->domains()[0];
+ auto extent = domain->domainOfValidity();
+ ASSERT_TRUE(extent != nullptr);
+ EXPECT_TRUE(extent->isEquivalentTo(factory->createExtent("1262").get()));
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(factory, AuthorityFactory_createCoordinateReferenceSystem) {
+ auto factory = AuthorityFactory::create(DatabaseContext::create(), "EPSG");
+ EXPECT_THROW(factory->createCoordinateReferenceSystem("-1"),
+ NoSuchAuthorityCodeException);
+ EXPECT_TRUE(nn_dynamic_pointer_cast<GeographicCRS>(
+ factory->createCoordinateReferenceSystem("4326")));
+ EXPECT_TRUE(nn_dynamic_pointer_cast<GeographicCRS>(
+ factory->createCoordinateReferenceSystem("4979")));
+ EXPECT_TRUE(nn_dynamic_pointer_cast<GeodeticCRS>(
+ factory->createCoordinateReferenceSystem("4978")));
+ EXPECT_TRUE(nn_dynamic_pointer_cast<ProjectedCRS>(
+ factory->createCoordinateReferenceSystem("32631")));
+ EXPECT_TRUE(nn_dynamic_pointer_cast<VerticalCRS>(
+ factory->createCoordinateReferenceSystem("3855")));
+ EXPECT_TRUE(nn_dynamic_pointer_cast<CompoundCRS>(
+ factory->createCoordinateReferenceSystem("6871")));
+}
+// ---------------------------------------------------------------------------
+
+TEST(factory, AuthorityFactory_createCoordinateOperation_helmert_3) {
+ auto factory = AuthorityFactory::create(DatabaseContext::create(), "EPSG");
+ EXPECT_THROW(factory->createCoordinateOperation("-1", false),
+ NoSuchAuthorityCodeException);
+ auto op = factory->createCoordinateOperation("1113", false);
+ EXPECT_EQ(op->exportToPROJString(PROJStringFormatter::create().get()),
+ "+proj=pipeline +step +proj=axisswap +order=2,1 +step "
+ "+proj=unitconvert +xy_in=deg +xy_out=rad +step +inv "
+ "+proj=longlat +a=6378249.145 +rf=293.4663077 +step +proj=cart "
+ "+a=6378249.145 +rf=293.4663077 +step +proj=helmert +x=-143 "
+ "+y=-90 +z=-294 +step +inv +proj=cart +ellps=WGS84 +step "
+ "+proj=unitconvert +xy_in=rad +xy_out=deg +step +proj=axisswap "
+ "+order=2,1");
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(factory, AuthorityFactory_createCoordinateOperation_helmert_7_CF) {
+ auto factory = AuthorityFactory::create(DatabaseContext::create(), "EPSG");
+ auto op = factory->createCoordinateOperation("7676", false);
+ EXPECT_EQ(op->exportToPROJString(PROJStringFormatter::create().get()),
+ "+proj=pipeline +step +proj=axisswap +order=2,1 +step "
+ "+proj=unitconvert +xy_in=deg +xy_out=rad +step +proj=cart "
+ "+ellps=bessel +step +proj=helmert +x=577.88891 +y=165.22205 "
+ "+z=391.18289 +rx=-4.9145 +ry=0.94729 +rz=13.05098 +s=7.78664 "
+ "+convention=coordinate_frame +step +inv +proj=cart +ellps=WGS84 "
+ "+step +proj=unitconvert +xy_in=rad +xy_out=deg +step "
+ "+proj=axisswap +order=2,1");
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(factory, AuthorityFactory_createCoordinateOperation_helmert_7_PV) {
+ auto factory = AuthorityFactory::create(DatabaseContext::create(), "EPSG");
+ auto op = factory->createCoordinateOperation("1074", false);
+ auto wkt = op->exportToPROJString(PROJStringFormatter::create().get());
+ EXPECT_TRUE(wkt.find("+proj=helmert +x=-275.7224 +y=94.7824 +z=340.8944 "
+ "+rx=-8.001 +ry=-4.42 +rz=-11.821 +s=1 "
+ "+convention=position_vector") != std::string::npos)
+ << wkt;
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(factory, AuthorityFactory_createCoordinateOperation_helmert_8_CF) {
+ auto factory = AuthorityFactory::create(DatabaseContext::create(), "EPSG");
+ auto op = factory->createCoordinateOperation("7702", false);
+ auto expected = " PARAMETER[\"Transformation reference epoch\",2002,\n"
+ " TIMEUNIT[\"year\",31556925.445],\n"
+ " ID[\"EPSG\",1049]],\n";
+
+ auto wkt = op->exportToWKT(
+ WKTFormatter::create(WKTFormatter::Convention::WKT2_2018).get());
+ EXPECT_TRUE(wkt.find(expected) != std::string::npos) << wkt;
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(factory, AuthorityFactory_createCoordinateOperation_helmert_15_CF) {
+ auto factory = AuthorityFactory::create(DatabaseContext::create(), "EPSG");
+ auto op = factory->createCoordinateOperation("6276", false);
+ auto expected =
+ "COORDINATEOPERATION[\"ITRF2008 to GDA94 (1)\",\n"
+ " SOURCECRS[\n"
+ " GEODCRS[\"ITRF2008\",\n"
+ " DATUM[\"International Terrestrial Reference Frame "
+ "2008\",\n"
+ " ELLIPSOID[\"GRS 1980\",6378137,298.257222101,\n"
+ " LENGTHUNIT[\"metre\",1]]],\n"
+ " PRIMEM[\"Greenwich\",0,\n"
+ " ANGLEUNIT[\"degree\",0.0174532925199433]],\n"
+ " CS[Cartesian,3],\n"
+ " AXIS[\"(X)\",geocentricX,\n"
+ " ORDER[1],\n"
+ " LENGTHUNIT[\"metre\",1]],\n"
+ " AXIS[\"(Y)\",geocentricY,\n"
+ " ORDER[2],\n"
+ " LENGTHUNIT[\"metre\",1]],\n"
+ " AXIS[\"(Z)\",geocentricZ,\n"
+ " ORDER[3],\n"
+ " LENGTHUNIT[\"metre\",1]]]],\n"
+ " TARGETCRS[\n"
+ " GEODCRS[\"GDA94\",\n"
+ " DATUM[\"Geocentric Datum of Australia 1994\",\n"
+ " ELLIPSOID[\"GRS 1980\",6378137,298.257222101,\n"
+ " LENGTHUNIT[\"metre\",1]]],\n"
+ " PRIMEM[\"Greenwich\",0,\n"
+ " ANGLEUNIT[\"degree\",0.0174532925199433]],\n"
+ " CS[Cartesian,3],\n"
+ " AXIS[\"(X)\",geocentricX,\n"
+ " ORDER[1],\n"
+ " LENGTHUNIT[\"metre\",1]],\n"
+ " AXIS[\"(Y)\",geocentricY,\n"
+ " ORDER[2],\n"
+ " LENGTHUNIT[\"metre\",1]],\n"
+ " AXIS[\"(Z)\",geocentricZ,\n"
+ " ORDER[3],\n"
+ " LENGTHUNIT[\"metre\",1]]]],\n"
+ " METHOD[\"Time-dependent Coordinate Frame rotation (geocen)\",\n"
+ " ID[\"EPSG\",1056]],\n"
+ " PARAMETER[\"X-axis translation\",-84.68,\n"
+ " LENGTHUNIT[\"millimetre\",0.001],\n"
+ " ID[\"EPSG\",8605]],\n"
+ " PARAMETER[\"Y-axis translation\",-19.42,\n"
+ " LENGTHUNIT[\"millimetre\",0.001],\n"
+ " ID[\"EPSG\",8606]],\n"
+ " PARAMETER[\"Z-axis translation\",32.01,\n"
+ " LENGTHUNIT[\"millimetre\",0.001],\n"
+ " ID[\"EPSG\",8607]],\n"
+ " PARAMETER[\"X-axis rotation\",-0.4254,\n"
+ " ANGLEUNIT[\"milliarc-second\",4.84813681109536e-09],\n"
+ " ID[\"EPSG\",8608]],\n"
+ " PARAMETER[\"Y-axis rotation\",2.2578,\n"
+ " ANGLEUNIT[\"milliarc-second\",4.84813681109536e-09],\n"
+ " ID[\"EPSG\",8609]],\n"
+ " PARAMETER[\"Z-axis rotation\",2.4015,\n"
+ " ANGLEUNIT[\"milliarc-second\",4.84813681109536e-09],\n"
+ " ID[\"EPSG\",8610]],\n"
+ " PARAMETER[\"Scale difference\",9.71,\n"
+ " SCALEUNIT[\"parts per billion\",1e-09],\n"
+ " ID[\"EPSG\",8611]],\n"
+ " PARAMETER[\"Rate of change of X-axis translation\",1.42,\n"
+ " LENGTHUNIT[\"millimetres per year\",3.16887651727315e-11],\n"
+ " ID[\"EPSG\",1040]],\n"
+ " PARAMETER[\"Rate of change of Y-axis translation\",1.34,\n"
+ " LENGTHUNIT[\"millimetres per year\",3.16887651727315e-11],\n"
+ " ID[\"EPSG\",1041]],\n"
+ " PARAMETER[\"Rate of change of Z-axis translation\",0.9,\n"
+ " LENGTHUNIT[\"millimetres per year\",3.16887651727315e-11],\n"
+ " ID[\"EPSG\",1042]],\n"
+ " PARAMETER[\"Rate of change of X-axis rotation\",1.5461,\n"
+ " ANGLEUNIT[\"milliarc-seconds per "
+ "year\",1.53631468932076e-16],\n"
+ " ID[\"EPSG\",1043]],\n"
+ " PARAMETER[\"Rate of change of Y-axis rotation\",1.182,\n"
+ " ANGLEUNIT[\"milliarc-seconds per "
+ "year\",1.53631468932076e-16],\n"
+ " ID[\"EPSG\",1044]],\n"
+ " PARAMETER[\"Rate of change of Z-axis rotation\",1.1551,\n"
+ " ANGLEUNIT[\"milliarc-seconds per "
+ "year\",1.53631468932076e-16],\n"
+ " ID[\"EPSG\",1045]],\n"
+ " PARAMETER[\"Rate of change of Scale difference\",0.109,\n"
+ " SCALEUNIT[\"parts per billion per "
+ "year\",3.16887651727315e-17],\n"
+ " ID[\"EPSG\",1046]],\n"
+ " PARAMETER[\"Parameter reference epoch\",1994,\n"
+ " TIMEUNIT[\"year\",31556925.445],\n"
+ " ID[\"EPSG\",1047]],\n"
+ " OPERATIONACCURACY[0.03],\n"
+ " USAGE[\n"
+ " SCOPE[\"unknown\"],\n"
+ " AREA[\"Australia - onshore and EEZ\"],\n"
+ " BBOX[-47.2,109.23,-8.88,163.2]],\n"
+ " ID[\"EPSG\",6276]]";
+
+ EXPECT_EQ(
+ op->exportToWKT(
+ WKTFormatter::create(WKTFormatter::Convention::WKT2_2018).get()),
+ expected);
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(factory, AuthorityFactory_createCoordinateOperation_helmert_15_PV) {
+ auto factory = AuthorityFactory::create(DatabaseContext::create(), "EPSG");
+ auto op = factory->createCoordinateOperation("8069", false);
+ EXPECT_EQ(op->exportToPROJString(PROJStringFormatter::create().get()),
+ "+proj=helmert +x=-0.0254 +y=0.0005 +z=0.1548 +rx=-0.0001 +ry=0 "
+ "+rz=-0.00026 +s=-0.01129 +dx=-0.0001 +dy=0.0005 +dz=0.0033 "
+ "+drx=0 +dry=0 +drz=-2e-05 +ds=-0.00012 +t_epoch=2010 "
+ "+convention=position_vector");
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(factory,
+ AuthorityFactory_createCoordinateOperation_helmert_15_PV_rounding_of_drz) {
+ auto factory = AuthorityFactory::create(DatabaseContext::create(), "EPSG");
+ auto op = factory->createCoordinateOperation("7932", false);
+ EXPECT_EQ(op->exportToPROJString(PROJStringFormatter::create().get()),
+ "+proj=helmert +x=0 +y=0 +z=0 +rx=0 +ry=0 +rz=0 +s=0 +dx=0 +dy=0 "
+ "+dz=0 +drx=0.00011 +dry=0.00057 +drz=-0.00071 +ds=0 "
+ "+t_epoch=1989 +convention=position_vector");
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(factory,
+ AuthorityFactory_createCoordinateOperation_molodensky_badekas_PV) {
+ auto factory = AuthorityFactory::create(DatabaseContext::create(), "EPSG");
+ auto op = factory->createCoordinateOperation("1066", false);
+ EXPECT_EQ(op->exportToPROJString(PROJStringFormatter::create().get()),
+ "+proj=pipeline +step +proj=axisswap +order=2,1 +step "
+ "+proj=unitconvert +xy_in=deg +xy_out=rad +step +proj=cart "
+ "+ellps=bessel +step +proj=molobadekas +x=593.032 +y=26 "
+ "+z=478.741 +rx=0.409394387439237 +ry=-0.359705195614311 "
+ "+rz=1.86849100035057 +s=4.0772 +px=3903453.148 +py=368135.313 "
+ "+pz=5012970.306 +convention=coordinate_frame +step +inv "
+ "+proj=cart +ellps=GRS80 +step +proj=unitconvert +xy_in=rad "
+ "+xy_out=deg +step +proj=axisswap +order=2,1");
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(
+ factory,
+ AuthorityFactory_createCoordinateOperation_grid_transformation_one_parameter) {
+ auto factory = AuthorityFactory::create(DatabaseContext::create(), "EPSG");
+ auto op = factory->createCoordinateOperation("1295", false);
+ auto expected =
+ "COORDINATEOPERATION[\"RGNC91-93 to NEA74 Noumea (4)\",\n"
+ " SOURCECRS[\n"
+ " GEOGCRS[\"RGNC91-93\",\n"
+ " DATUM[\"Reseau Geodesique de Nouvelle Caledonie 91-93\",\n"
+ " ELLIPSOID[\"GRS 1980\",6378137,298.257222101,\n"
+ " LENGTHUNIT[\"metre\",1]]],\n"
+ " PRIMEM[\"Greenwich\",0,\n"
+ " ANGLEUNIT[\"degree\",0.0174532925199433]],\n"
+ " CS[ellipsoidal,2],\n"
+ " AXIS[\"geodetic latitude (Lat)\",north,\n"
+ " ORDER[1],\n"
+ " ANGLEUNIT[\"degree\",0.0174532925199433]],\n"
+ " AXIS[\"geodetic longitude (Lon)\",east,\n"
+ " ORDER[2],\n"
+ " ANGLEUNIT[\"degree\",0.0174532925199433]]]],\n"
+ " TARGETCRS[\n"
+ " GEOGCRS[\"NEA74 Noumea\",\n"
+ " DATUM[\"NEA74 Noumea\",\n"
+ " ELLIPSOID[\"International 1924\",6378388,297,\n"
+ " LENGTHUNIT[\"metre\",1]]],\n"
+ " PRIMEM[\"Greenwich\",0,\n"
+ " ANGLEUNIT[\"degree\",0.0174532925199433]],\n"
+ " CS[ellipsoidal,2],\n"
+ " AXIS[\"geodetic latitude (Lat)\",north,\n"
+ " ORDER[1],\n"
+ " ANGLEUNIT[\"degree\",0.0174532925199433]],\n"
+ " AXIS[\"geodetic longitude (Lon)\",east,\n"
+ " ORDER[2],\n"
+ " ANGLEUNIT[\"degree\",0.0174532925199433]]]],\n"
+ " METHOD[\"NTv2\",\n"
+ " ID[\"EPSG\",9615]],\n"
+ " PARAMETERFILE[\"Latitude and longitude difference "
+ "file\",\"RGNC1991_NEA74Noumea.gsb\"],\n"
+ " OPERATIONACCURACY[0.05],\n"
+ " USAGE[\n"
+ " SCOPE[\"unknown\"],\n"
+ " AREA[\"New Caledonia - Grande Terre - Noumea\"],\n"
+ " BBOX[-22.37,166.35,-22.19,166.54]],\n"
+ " ID[\"EPSG\",1295]]";
+ EXPECT_EQ(
+ op->exportToWKT(
+ WKTFormatter::create(WKTFormatter::Convention::WKT2_2018).get()),
+ expected);
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(
+ factory,
+ AuthorityFactory_createCoordinateOperation_grid_transformation_two_parameter) {
+ auto factory = AuthorityFactory::create(DatabaseContext::create(), "EPSG");
+ auto op = factory->createCoordinateOperation("15864", false);
+ auto expected =
+ " PARAMETERFILE[\"Latitude difference file\",\"alaska.las\"],\n"
+ " PARAMETERFILE[\"Longitude difference file\",\"alaska.los\"],\n";
+
+ auto wkt = op->exportToWKT(
+ WKTFormatter::create(WKTFormatter::Convention::WKT2_2018).get());
+ EXPECT_TRUE(wkt.find(expected) != std::string::npos) << wkt;
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(factory, AuthorityFactory_createCoordinateOperation_other_transformation) {
+ auto factory = AuthorityFactory::create(DatabaseContext::create(), "EPSG");
+ auto op = factory->createCoordinateOperation("1884", false);
+ auto expected =
+ "COORDINATEOPERATION[\"S-JTSK (Ferro) to S-JTSK (1)\",\n"
+ " SOURCECRS[\n"
+ " GEOGCRS[\"S-JTSK (Ferro)\",\n"
+ " DATUM[\"System of the Unified Trigonometrical Cadastral "
+ "Network (Ferro)\",\n"
+ " ELLIPSOID[\"Bessel 1841\",6377397.155,299.1528128,\n"
+ " LENGTHUNIT[\"metre\",1]]],\n"
+ " PRIMEM[\"Ferro\",-17.6666666666667,\n"
+ " ANGLEUNIT[\"degree\",0.0174532925199433]],\n"
+ " CS[ellipsoidal,2],\n"
+ " AXIS[\"geodetic latitude (Lat)\",north,\n"
+ " ORDER[1],\n"
+ " ANGLEUNIT[\"degree\",0.0174532925199433]],\n"
+ " AXIS[\"geodetic longitude (Lon)\",east,\n"
+ " ORDER[2],\n"
+ " ANGLEUNIT[\"degree\",0.0174532925199433]]]],\n"
+ " TARGETCRS[\n"
+ " GEOGCRS[\"S-JTSK\",\n"
+ " DATUM[\"System of the Unified Trigonometrical Cadastral "
+ "Network\",\n"
+ " ELLIPSOID[\"Bessel 1841\",6377397.155,299.1528128,\n"
+ " LENGTHUNIT[\"metre\",1]]],\n"
+ " PRIMEM[\"Greenwich\",0,\n"
+ " ANGLEUNIT[\"degree\",0.0174532925199433]],\n"
+ " CS[ellipsoidal,2],\n"
+ " AXIS[\"geodetic latitude (Lat)\",north,\n"
+ " ORDER[1],\n"
+ " ANGLEUNIT[\"degree\",0.0174532925199433]],\n"
+ " AXIS[\"geodetic longitude (Lon)\",east,\n"
+ " ORDER[2],\n"
+ " ANGLEUNIT[\"degree\",0.0174532925199433]]]],\n"
+ " METHOD[\"Longitude rotation\",\n"
+ " ID[\"EPSG\",9601]],\n"
+ " PARAMETER[\"Longitude offset\",-17.6666666666667,\n"
+ " ANGLEUNIT[\"degree\",0.0174532925199433],\n"
+ " ID[\"EPSG\",8602]],\n"
+ " OPERATIONACCURACY[0.0],\n"
+ " USAGE[\n"
+ " SCOPE[\"unknown\"],\n"
+ " AREA[\"Europe - Czechoslovakia\"],\n"
+ " BBOX[47.73,12.09,51.06,22.56]],\n"
+ " ID[\"EPSG\",1884]]";
+
+ EXPECT_EQ(
+ op->exportToWKT(
+ WKTFormatter::create(WKTFormatter::Convention::WKT2_2018).get()),
+ expected);
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(factory, AuthorityFactory_test_uom_9110) {
+ auto factory = AuthorityFactory::create(DatabaseContext::create(), "EPSG");
+ // This tests conversion from unit of measure EPSG:9110 DDD.MMSSsss
+ auto crs = factory->createProjectedCRS("2172");
+ EXPECT_EQ(crs->exportToPROJString(PROJStringFormatter::create().get()),
+ "+proj=pipeline +step +proj=axisswap +order=2,1 +step "
+ "+proj=unitconvert +xy_in=deg +xy_out=rad +step +proj=sterea "
+ "+lat_0=53.0019444444444 +lon_0=21.5027777777778 +k=0.9998 "
+ "+x_0=4603000 +y_0=5806000 +ellps=krass +step +proj=axisswap "
+ "+order=2,1");
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(factory, AuthorityFactory_affine_parametric_transform) {
+ auto factory = AuthorityFactory::create(DatabaseContext::create(), "EPSG");
+ auto op = factory->createCoordinateOperation("10087", false);
+ // Do not do axis unit change
+ EXPECT_EQ(op->exportToPROJString(PROJStringFormatter::create().get()),
+ "+proj=affine +xoff=82357.457 +s11=0.304794369 "
+ "+s12=1.5417425e-05 +yoff=28091.324 +s21=-1.5417425e-05 "
+ "+s22=0.304794369");
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(factory,
+ AuthorityFactory_createCoordinateOperation_concatenated_operation) {
+ auto factory = AuthorityFactory::create(DatabaseContext::create(), "EPSG");
+ auto op = factory->createCoordinateOperation("3896", false);
+ auto concatenated = nn_dynamic_pointer_cast<ConcatenatedOperation>(op);
+ ASSERT_TRUE(concatenated != nullptr);
+ auto operations = concatenated->operations();
+ ASSERT_EQ(operations.size(), 2);
+ EXPECT_TRUE(operations[0]->isEquivalentTo(
+ factory->createCoordinateOperation("3895", false).get()));
+ EXPECT_TRUE(operations[1]->isEquivalentTo(
+ factory->createCoordinateOperation("1618", false).get()));
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(
+ factory,
+ AuthorityFactory_createCoordinateOperation_concatenated_operation_three_steps) {
+ auto factory = AuthorityFactory::create(DatabaseContext::create(), "EPSG");
+ auto op = factory->createCoordinateOperation("8647", false);
+ auto concatenated = nn_dynamic_pointer_cast<ConcatenatedOperation>(op);
+ ASSERT_TRUE(concatenated != nullptr);
+ auto operations = concatenated->operations();
+ ASSERT_EQ(operations.size(), 3);
+ EXPECT_TRUE(operations[0]->isEquivalentTo(
+ factory->createCoordinateOperation("1313", false).get()));
+ EXPECT_TRUE(operations[1]->isEquivalentTo(
+ factory->createCoordinateOperation("1950", false).get()));
+ EXPECT_TRUE(operations[2]->isEquivalentTo(
+ factory->createCoordinateOperation("1946", false).get()));
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(
+ factory,
+ AuthorityFactory_createCoordinateOperation_concatenated_operation_inverse_step1) {
+ auto factory = AuthorityFactory::create(DatabaseContext::create(), "EPSG");
+ auto op = factory->createCoordinateOperation("8443", false);
+ auto concatenated = nn_dynamic_pointer_cast<ConcatenatedOperation>(op);
+ ASSERT_TRUE(concatenated != nullptr);
+ auto operations = concatenated->operations();
+ ASSERT_EQ(operations.size(), 2);
+ EXPECT_TRUE(operations[0]->isEquivalentTo(
+ factory->createCoordinateOperation("8364", false)->inverse().get()));
+ EXPECT_TRUE(operations[1]->isEquivalentTo(
+ factory->createCoordinateOperation("8367", false).get()));
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(
+ factory,
+ AuthorityFactory_createCoordinateOperation_concatenated_operation_inverse_step2) {
+ auto factory = AuthorityFactory::create(DatabaseContext::create(), "EPSG");
+ auto op = factory->createCoordinateOperation("7811", false);
+ auto concatenated = nn_dynamic_pointer_cast<ConcatenatedOperation>(op);
+ ASSERT_TRUE(concatenated != nullptr);
+ auto operations = concatenated->operations();
+ ASSERT_EQ(operations.size(), 2);
+ EXPECT_TRUE(operations[0]->isEquivalentTo(
+ factory->createCoordinateOperation("1763", false).get()));
+ EXPECT_TRUE(operations[1]->isEquivalentTo(
+ factory->createCoordinateOperation("15958", false)->inverse().get()));
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(
+ factory,
+ AuthorityFactory_createCoordinateOperation_concatenated_operation_step1_is_conversion) {
+ auto factory = AuthorityFactory::create(DatabaseContext::create(), "EPSG");
+ auto op = factory->createCoordinateOperation("7973", false);
+ auto concatenated = nn_dynamic_pointer_cast<ConcatenatedOperation>(op);
+ ASSERT_TRUE(concatenated != nullptr);
+ auto operations = concatenated->operations();
+ ASSERT_EQ(operations.size(), 2);
+ EXPECT_TRUE(operations[0]->isEquivalentTo(
+ factory->createCoordinateOperation("7972", false).get()));
+ EXPECT_TRUE(operations[1]->isEquivalentTo(
+ factory->createCoordinateOperation("7969", false).get()));
+}
+
+// ---------------------------------------------------------------------------
+
+static bool in(const std::string &str, const std::vector<std::string> &list) {
+ for (const auto &listItem : list) {
+ if (str == listItem) {
+ return true;
+ }
+ }
+ return false;
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(factory, AuthorityFactory_build_all_concatenated) {
+ auto factory = AuthorityFactory::create(DatabaseContext::create(), "EPSG");
+ auto setConcatenated = factory->getAuthorityCodes(
+ AuthorityFactory::ObjectType::CONCATENATED_OPERATION);
+ auto setConcatenatedNoDeprecated = factory->getAuthorityCodes(
+ AuthorityFactory::ObjectType::CONCATENATED_OPERATION, false);
+ EXPECT_LT(setConcatenatedNoDeprecated.size(), setConcatenated.size());
+ for (const auto &code : setConcatenated) {
+ if (in(code, {"8422", "8481", "8482", "8565", "8566", "8572",
+ // the issue with 7987 is the chaining of two conversions
+ "7987"})) {
+ EXPECT_THROW(factory->createCoordinateOperation(code, false),
+ FactoryException);
+ } else {
+ factory->createCoordinateOperation(code, false);
+ }
+ }
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(factory, AuthorityFactory_createCoordinateOperation_conversion) {
+ auto factory = AuthorityFactory::create(DatabaseContext::create(), "EPSG");
+ auto op = factory->createCoordinateOperation("16031", false);
+ auto conversion = nn_dynamic_pointer_cast<Conversion>(op);
+ ASSERT_TRUE(conversion != nullptr);
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(factory, AuthorityFactory_getAuthorityCodes) {
+ auto factory = AuthorityFactory::create(DatabaseContext::create(), "EPSG");
+ {
+ auto set = factory->getAuthorityCodes(
+ AuthorityFactory::ObjectType::PRIME_MERIDIAN);
+ ASSERT_TRUE(!set.empty());
+ factory->createPrimeMeridian(*(set.begin()));
+ }
+ {
+ auto set =
+ factory->getAuthorityCodes(AuthorityFactory::ObjectType::ELLIPSOID);
+ ASSERT_TRUE(!set.empty());
+ factory->createEllipsoid(*(set.begin()));
+ }
+ {
+ auto setDatum =
+ factory->getAuthorityCodes(AuthorityFactory::ObjectType::DATUM);
+ ASSERT_TRUE(!setDatum.empty());
+ factory->createDatum(*(setDatum.begin()));
+
+ auto setGeodeticDatum = factory->getAuthorityCodes(
+ AuthorityFactory::ObjectType::GEODETIC_REFERENCE_FRAME);
+ ASSERT_TRUE(!setGeodeticDatum.empty());
+ factory->createGeodeticDatum(*(setGeodeticDatum.begin()));
+
+ auto setVerticalDatum = factory->getAuthorityCodes(
+ AuthorityFactory::ObjectType::VERTICAL_REFERENCE_FRAME);
+ ASSERT_TRUE(!setVerticalDatum.empty());
+ factory->createVerticalDatum(*(setVerticalDatum.begin()));
+
+ std::set<std::string> setMerged;
+ for (const auto &v : setGeodeticDatum) {
+ setMerged.insert(v);
+ }
+ for (const auto &v : setVerticalDatum) {
+ setMerged.insert(v);
+ }
+ EXPECT_EQ(setDatum, setMerged);
+ }
+ {
+ auto setCRS =
+ factory->getAuthorityCodes(AuthorityFactory::ObjectType::CRS);
+ ASSERT_TRUE(!setCRS.empty());
+ factory->createCoordinateReferenceSystem(*(setCRS.begin()));
+
+ auto setGeodeticCRS = factory->getAuthorityCodes(
+ AuthorityFactory::ObjectType::GEODETIC_CRS);
+ ASSERT_TRUE(!setGeodeticCRS.empty());
+ factory->createGeodeticCRS(*(setGeodeticCRS.begin()));
+
+ auto setGeocentricCRS = factory->getAuthorityCodes(
+ AuthorityFactory::ObjectType::GEOCENTRIC_CRS);
+ ASSERT_TRUE(!setGeocentricCRS.empty());
+ factory->createGeodeticCRS(*(setGeocentricCRS.begin()));
+ EXPECT_LT(setGeocentricCRS.size(), setGeodeticCRS.size());
+
+ auto setGeographicCRS = factory->getAuthorityCodes(
+ AuthorityFactory::ObjectType::GEOGRAPHIC_CRS);
+ ASSERT_TRUE(!setGeographicCRS.empty());
+ factory->createGeographicCRS(*(setGeographicCRS.begin()));
+ EXPECT_LT(setGeographicCRS.size(), setGeodeticCRS.size());
+ for (const auto &v : setGeographicCRS) {
+ EXPECT_TRUE(setGeodeticCRS.find(v) != setGeodeticCRS.end());
+ }
+
+ auto setGeographic2DCRS = factory->getAuthorityCodes(
+ AuthorityFactory::ObjectType::GEOGRAPHIC_2D_CRS);
+ ASSERT_TRUE(!setGeographic2DCRS.empty());
+ factory->createGeographicCRS(*(setGeographic2DCRS.begin()));
+
+ auto setGeographic3DCRS = factory->getAuthorityCodes(
+ AuthorityFactory::ObjectType::GEOGRAPHIC_3D_CRS);
+ ASSERT_TRUE(!setGeographic3DCRS.empty());
+ factory->createGeographicCRS(*(setGeographic3DCRS.begin()));
+
+ EXPECT_EQ(setGeographic2DCRS.size() + setGeographic3DCRS.size(),
+ setGeographicCRS.size());
+
+ EXPECT_EQ(setGeocentricCRS.size() + setGeographicCRS.size(),
+ setGeodeticCRS.size());
+
+ auto setVerticalCRS = factory->getAuthorityCodes(
+ AuthorityFactory::ObjectType::VERTICAL_CRS);
+ ASSERT_TRUE(!setVerticalCRS.empty());
+ factory->createVerticalCRS(*(setVerticalCRS.begin()));
+
+ auto setProjectedCRS = factory->getAuthorityCodes(
+ AuthorityFactory::ObjectType::PROJECTED_CRS);
+ ASSERT_TRUE(!setProjectedCRS.empty());
+ factory->createProjectedCRS(*(setProjectedCRS.begin()));
+
+ auto setCompoundCRS = factory->getAuthorityCodes(
+ AuthorityFactory::ObjectType::COMPOUND_CRS);
+ ASSERT_TRUE(!setCompoundCRS.empty());
+ factory->createCompoundCRS(*(setCompoundCRS.begin()));
+
+ std::set<std::string> setMerged;
+ for (const auto &v : setGeodeticCRS) {
+ setMerged.insert(v);
+ }
+ for (const auto &v : setVerticalCRS) {
+ setMerged.insert(v);
+ }
+ for (const auto &v : setProjectedCRS) {
+ setMerged.insert(v);
+ }
+ for (const auto &v : setCompoundCRS) {
+ setMerged.insert(v);
+ }
+ EXPECT_EQ(setCRS, setMerged);
+ }
+ {
+ auto setCO = factory->getAuthorityCodes(
+ AuthorityFactory::ObjectType::COORDINATE_OPERATION);
+ ASSERT_TRUE(!setCO.empty());
+ factory->createCoordinateOperation(*(setCO.begin()), false);
+
+ auto setConversion = factory->getAuthorityCodes(
+ AuthorityFactory::ObjectType::CONVERSION);
+ ASSERT_TRUE(!setConversion.empty());
+ factory->createConversion(*(setConversion.begin()));
+
+ auto setTransformation = factory->getAuthorityCodes(
+ AuthorityFactory::ObjectType::TRANSFORMATION);
+ ASSERT_TRUE(!setTransformation.empty());
+ ASSERT_TRUE(nn_dynamic_pointer_cast<Transformation>(
+ factory->createCoordinateOperation(
+ *(setTransformation.begin()), false)) != nullptr);
+
+ auto setConcatenated = factory->getAuthorityCodes(
+ AuthorityFactory::ObjectType::CONCATENATED_OPERATION);
+ ASSERT_TRUE(!setConcatenated.empty());
+ ASSERT_TRUE(nn_dynamic_pointer_cast<ConcatenatedOperation>(
+ factory->createCoordinateOperation(
+ *(setConcatenated.begin()), false)) != nullptr);
+
+ std::set<std::string> setMerged;
+ for (const auto &v : setConversion) {
+ setMerged.insert(v);
+ }
+ for (const auto &v : setTransformation) {
+ setMerged.insert(v);
+ }
+ for (const auto &v : setConcatenated) {
+ setMerged.insert(v);
+ }
+ EXPECT_EQ(setCO.size(), setMerged.size());
+ std::set<std::string> setMissing;
+ for (const auto &v : setCO) {
+ if (setMerged.find(v) == setMerged.end()) {
+ setMissing.insert(v);
+ }
+ }
+ EXPECT_EQ(setMissing, std::set<std::string>());
+ EXPECT_EQ(setCO, setMerged);
+ }
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(factory, AuthorityFactory_getDescriptionText) {
+ auto factory = AuthorityFactory::create(DatabaseContext::create(), "EPSG");
+ EXPECT_THROW(factory->getDescriptionText("-1"),
+ NoSuchAuthorityCodeException);
+ EXPECT_EQ(factory->getDescriptionText("10000"),
+ "RGF93 to NGF IGN69 height (1)");
+}
+
+// ---------------------------------------------------------------------------
+
+class FactoryWithTmpDatabase : public ::testing::Test {
+ protected:
+ void SetUp() override { sqlite3_open(":memory:", &m_ctxt); }
+
+ void TearDown() override {
+ sqlite3_free_table(m_papszResult);
+ sqlite3_close(m_ctxt);
+ }
+
+ void createStructure() {
+ auto referenceDb = DatabaseContext::create();
+ const auto dbStructure = referenceDb->getDatabaseStructure();
+ for (const auto &sql : dbStructure) {
+ ASSERT_TRUE(execute(sql)) << last_error();
+ }
+ ASSERT_TRUE(execute("PRAGMA foreign_keys = 1;")) << last_error();
+ }
+
+ void populateWithFakeEPSG() {
+
+ ASSERT_TRUE(execute("INSERT INTO unit_of_measure "
+ "VALUES('EPSG','9001','metre','length',1.0,0);"))
+ << last_error();
+ ASSERT_TRUE(execute("INSERT INTO unit_of_measure "
+ "VALUES('EPSG','9102','degree','angle',1."
+ "74532925199432781271e-02,0);"))
+ << last_error();
+ ASSERT_TRUE(execute(
+ "INSERT INTO unit_of_measure VALUES('EPSG','9122','degree "
+ "(supplier to "
+ "define representation)','angle',1.74532925199432781271e-02,0);"))
+ << last_error();
+ ASSERT_TRUE(
+ execute("INSERT INTO area "
+ "VALUES('EPSG','1262','World','World.',-90.0,90.0,-180."
+ "0,180.0,0);"))
+ << last_error();
+ ASSERT_TRUE(
+ execute("INSERT INTO prime_meridian "
+ "VALUES('EPSG','8901','Greenwich',0.0,'EPSG','9102',0);"))
+ << last_error();
+ ASSERT_TRUE(
+ execute("INSERT INTO celestial_body VALUES('PROJ','EARTH','Earth',"
+ "6378137.0);"))
+ << last_error();
+ ASSERT_TRUE(
+ execute("INSERT INTO ellipsoid VALUES('EPSG','7030','WGS 84','',"
+ "'PROJ','EARTH',6378137.0,'EPSG','9001',298.257223563,"
+ "NULL,0);"))
+ << last_error();
+ ASSERT_TRUE(
+ execute("INSERT INTO geodetic_datum "
+ "VALUES('EPSG','6326','World Geodetic System 1984','',NULL,"
+ "'EPSG','7030','EPSG','8901','EPSG','1262',0);"))
+ << last_error();
+ ASSERT_TRUE(
+ execute("INSERT INTO vertical_datum VALUES('EPSG','1027','EGM2008 "
+ "geoid',NULL,NULL,'EPSG','1262',0);"))
+ << last_error();
+ ASSERT_TRUE(execute("INSERT INTO coordinate_system "
+ "VALUES('EPSG','6422','ellipsoidal',2);"))
+ << last_error();
+ ASSERT_TRUE(
+ execute("INSERT INTO axis VALUES('EPSG','106','Geodetic "
+ "latitude','Lat','north','EPSG','6422',1,'EPSG','9122');"))
+ << last_error();
+ ASSERT_TRUE(
+ execute("INSERT INTO axis VALUES('EPSG','107','Geodetic "
+ "longitude','Lon','east','EPSG','6422',2,'EPSG','9122');"))
+ << last_error();
+ ASSERT_TRUE(
+ execute("INSERT INTO geodetic_crs VALUES('EPSG','4326','WGS "
+ "84',NULL,NULL,'geographic "
+ "2D','EPSG','6422','EPSG','6326','EPSG','1262',NULL,0);"))
+ << last_error();
+
+ ASSERT_TRUE(execute("INSERT INTO coordinate_system "
+ "VALUES('EPSG','6499','vertical',1);"))
+ << last_error();
+ ASSERT_TRUE(
+ execute("INSERT INTO axis VALUES('EPSG','114','Gravity-related "
+ "height','H','up','EPSG','6499',1,'EPSG','9001');"))
+ << last_error();
+ ASSERT_TRUE(
+ execute("INSERT INTO vertical_crs VALUES('EPSG','3855','EGM2008 "
+ "height',NULL,NULL,'EPSG','6499','EPSG','1027','EPSG',"
+ "'1262',0);"))
+ << last_error();
+
+ ASSERT_TRUE(execute("INSERT INTO unit_of_measure "
+ "VALUES('EPSG','9201','unity','scale',1.0,0);"))
+ << last_error();
+
+ ASSERT_TRUE(execute(
+ "INSERT INTO area VALUES('EPSG','1933','World - N hemisphere - "
+ "0°E to 6°E','',0.0,84.0,0.0,6.0,0);"))
+ << last_error();
+
+ ASSERT_TRUE(execute(
+ "INSERT INTO conversion VALUES('EPSG','16031','UTM zone "
+ "31N',NULL,NULL,'EPSG','1933','EPSG','9807','Transverse "
+ "Mercator','EPSG','8801','Latitude "
+ "of "
+ "natural origin',0.0,'EPSG','9102','EPSG','8802','Longitude of "
+ "natural "
+ "origin',3.0,'EPSG','9102','EPSG','8805','Scale factor at natural "
+ "origin',0.9996,'EPSG','9201','EPSG','8806','False "
+ "easting',500000.0,'EPSG','9001','EPSG','8807','False "
+ "northing',0.0,'EPSG','9001',NULL,NULL,NULL,NULL,NULL,NULL,NULL,"
+ "NULL,"
+ "NULL,NULL,NULL,NULL,0);"))
+ << last_error();
+
+ ASSERT_TRUE(execute(
+ "INSERT INTO area VALUES('EPSG','2060','World - N hemisphere - "
+ "0°E to 6°E - by country','',0.0,84.0,0.0,6.0,0);"))
+ << last_error();
+
+ ASSERT_TRUE(execute("INSERT INTO coordinate_system "
+ "VALUES('EPSG','4400','Cartesian',2);"))
+ << last_error();
+ ASSERT_TRUE(
+ execute("INSERT INTO axis "
+ "VALUES('EPSG','1','Easting','E','east','EPSG','4400',"
+ "1,'EPSG','9001');"))
+ << last_error();
+ ASSERT_TRUE(
+ execute("INSERT INTO axis "
+ "VALUES('EPSG','2','Northing','N','north','EPSG','4400'"
+ ",2,'EPSG','9001');"))
+ << last_error();
+
+ ASSERT_TRUE(execute("INSERT INTO projected_crs "
+ "VALUES('EPSG','32631','WGS 84 / UTM zone "
+ "31N',NULL,NULL,'EPSG','4400','EPSG','4326',"
+ "'EPSG','16031','"
+ "EPSG','2060',NULL,0);"))
+ << last_error();
+
+ ASSERT_TRUE(execute(
+ "INSERT INTO compound_crs VALUES('EPSG','MY_COMPOUND','WGS 84 + "
+ "EGM2008 geoid height',NULL,NULL,'EPSG','4326','EPSG','3855',"
+ "'EPSG','1262',0);"))
+ << last_error();
+
+ ASSERT_TRUE(execute(
+ "INSERT INTO helmert_transformation "
+ "VALUES('EPSG','DUMMY_HELMERT','name',NULL,NULL,'EPSG','9603','"
+ "Geocentric translations (geog2D domain)','EPSG','4326',"
+ "'EPSG','4326','EPSG','1262',44.0,-143."
+ "0,-90.0,-294.0,'EPSG','9001',NULL,NULL,NULL,NULL,NULL,NULL,"
+ "NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,"
+ "NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,0);"))
+ << last_error();
+
+ ASSERT_TRUE(execute(
+ "INSERT INTO grid_transformation "
+ "VALUES('EPSG','DUMMY_GRID_TRANSFORMATION','name',NULL,NULL,"
+ "'EPSG','9615'"
+ ",'NTv2','EPSG','4326','EPSG','4326','EPSG','1262',1.0,'EPSG','"
+ "8656','Latitude and longitude difference "
+ "file','nzgd2kgrid0005.gsb',NULL,NULL,NULL,NULL,NULL,NULL,0);"))
+ << last_error();
+
+ ASSERT_TRUE(execute(
+ "INSERT INTO unit_of_measure VALUES('EPSG','9110','sexagesimal "
+ "DMS','angle',NULL,0);"))
+ << last_error();
+
+ ASSERT_TRUE(execute(
+ "INSERT INTO other_transformation "
+ "VALUES('EPSG','DUMMY_OTHER_TRANSFORMATION','name',NULL,NULL,"
+ "'EPSG','9601','Longitude rotation',"
+ "'EPSG','4326','EPSG','4326','EPSG','1262',0.0,'EPSG'"
+ ",'8602','Longitude "
+ "offset',-17.4,'EPSG','9110',NULL,NULL,NULL,NULL,NULL,NULL,"
+ "NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,"
+ "NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,"
+ "NULL,NULL,NULL,NULL,NULL,NULL,0);"))
+ << last_error();
+
+ ASSERT_TRUE(execute(
+ "INSERT INTO concatenated_operation "
+ "VALUES('EPSG','DUMMY_CONCATENATED','name',NULL,NULL,"
+ "'EPSG','4326','EPSG'"
+ ",'4326','EPSG','1262',NULL,'EPSG','DUMMY_OTHER_TRANSFORMATION'"
+ ",'EPSG','DUMMY_OTHER_TRANSFORMATION',NULL,NULL,0);"))
+ << last_error();
+ }
+
+ void createSourceTargetPivotCRS() {
+ const auto vals = std::vector<std::string>{"SOURCE", "TARGET", "PIVOT"};
+ for (const auto &val : vals) {
+
+ ASSERT_TRUE(execute("INSERT INTO geodetic_crs "
+ "VALUES('NS_" +
+ val + "','" + val + "','" + val +
+ "',NULL,NULL,'geographic 2D','EPSG','6422',"
+ "'EPSG','6326',"
+ "'EPSG','1262',NULL,0);"))
+ << last_error();
+ }
+ }
+
+ void createTransformationForPivotTesting(const std::string &src,
+ const std::string &dst) {
+
+ ASSERT_TRUE(execute(
+ "INSERT INTO helmert_transformation "
+ "VALUES('OTHER','" +
+ src + "_" + dst + "','Transformation from " + src + " to " + dst +
+ "',NULL,NULL,'EPSG','9603','"
+ "Geocentric translations (geog2D domain)','NS_" +
+ src + "','" + src + "','NS_" + dst + "','" + dst +
+ "','EPSG'"
+ ",'1262',1.0,0,0,0,'EPSG','9001',NULL,NULL,NULL,NULL,NULL,NULL,"
+ "NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,"
+ "NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,0);"))
+ << last_error();
+ }
+
+ void checkSourceToOther() {
+ {
+ auto factoryOTHER = AuthorityFactory::create(
+ DatabaseContext::create(m_ctxt), "OTHER");
+ auto res = factoryOTHER->createFromCRSCodesWithIntermediates(
+ "NS_SOURCE", "SOURCE", "NS_TARGET", "TARGET", false, false, {});
+ EXPECT_EQ(res.size(), 1);
+ EXPECT_TRUE(res.empty() ||
+ nn_dynamic_pointer_cast<ConcatenatedOperation>(res[0]));
+
+ res = factoryOTHER->createFromCRSCodesWithIntermediates(
+ "NS_SOURCE", "SOURCE", "NS_TARGET", "TARGET", false, false,
+ {std::make_pair(std::string("NS_PIVOT"),
+ std::string("PIVOT"))});
+ EXPECT_EQ(res.size(), 1);
+ EXPECT_TRUE(res.empty() ||
+ nn_dynamic_pointer_cast<ConcatenatedOperation>(res[0]));
+
+ res = factoryOTHER->createFromCRSCodesWithIntermediates(
+ "NS_SOURCE", "SOURCE", "NS_TARGET", "TARGET", false, false,
+ {std::make_pair(std::string("NS_PIVOT"),
+ std::string("NOT_EXISTING"))});
+ EXPECT_EQ(res.size(), 0);
+
+ res = factoryOTHER->createFromCRSCodesWithIntermediates(
+ "NS_SOURCE", "SOURCE", "NS_TARGET", "TARGET", false, false,
+ {std::make_pair(std::string("BAD_NS"), std::string("PIVOT"))});
+ EXPECT_EQ(res.size(), 0);
+
+ res = factoryOTHER->createFromCRSCodesWithIntermediates(
+ "NS_TARGET", "TARGET", "NS_SOURCE", "SOURCE", false, false, {});
+ EXPECT_EQ(res.size(), 1);
+ EXPECT_TRUE(res.empty() ||
+ nn_dynamic_pointer_cast<ConcatenatedOperation>(res[0]));
+ }
+ {
+ auto factory = AuthorityFactory::create(
+ DatabaseContext::create(m_ctxt), std::string());
+ auto res = factory->createFromCRSCodesWithIntermediates(
+ "NS_SOURCE", "SOURCE", "NS_TARGET", "TARGET", false, false, {});
+ EXPECT_EQ(res.size(), 1);
+ EXPECT_TRUE(res.empty() ||
+ nn_dynamic_pointer_cast<ConcatenatedOperation>(res[0]));
+
+ auto srcCRS = AuthorityFactory::create(
+ DatabaseContext::create(m_ctxt), "NS_SOURCE")
+ ->createCoordinateReferenceSystem("SOURCE");
+ auto targetCRS = AuthorityFactory::create(
+ DatabaseContext::create(m_ctxt), "NS_TARGET")
+ ->createCoordinateReferenceSystem("TARGET");
+
+ {
+ auto ctxt =
+ CoordinateOperationContext::create(factory, nullptr, 0);
+ res = CoordinateOperationFactory::create()->createOperations(
+ srcCRS, targetCRS, ctxt);
+ EXPECT_EQ(res.size(), 1);
+ EXPECT_TRUE(
+ res.empty() ||
+ nn_dynamic_pointer_cast<ConcatenatedOperation>(res[0]));
+ }
+
+ {
+ auto ctxt =
+ CoordinateOperationContext::create(factory, nullptr, 0);
+ ctxt->setIntermediateCRS({std::make_pair(
+ std::string("NS_PIVOT"), std::string("PIVOT"))});
+ res = CoordinateOperationFactory::create()->createOperations(
+ srcCRS, targetCRS, ctxt);
+ EXPECT_EQ(res.size(), 1);
+ EXPECT_TRUE(
+ res.empty() ||
+ nn_dynamic_pointer_cast<ConcatenatedOperation>(res[0]));
+ }
+
+ {
+ auto ctxt =
+ CoordinateOperationContext::create(factory, nullptr, 0);
+ ctxt->setAllowUseIntermediateCRS(false);
+ res = CoordinateOperationFactory::create()->createOperations(
+ srcCRS, targetCRS, ctxt);
+ EXPECT_EQ(res.size(), 1);
+ EXPECT_TRUE(res.empty() ||
+ nn_dynamic_pointer_cast<Transformation>(res[0]));
+ }
+
+ {
+ auto ctxt =
+ CoordinateOperationContext::create(factory, nullptr, 0);
+ ctxt->setIntermediateCRS({std::make_pair(
+ std::string("NS_PIVOT"), std::string("NOT_EXISTING"))});
+ res = CoordinateOperationFactory::create()->createOperations(
+ srcCRS, targetCRS, ctxt);
+ EXPECT_EQ(res.size(), 1);
+ EXPECT_TRUE(res.empty() ||
+ nn_dynamic_pointer_cast<Transformation>(res[0]));
+ }
+ }
+ }
+
+ bool get_table(const char *sql, sqlite3 *db = nullptr) {
+ sqlite3_free_table(m_papszResult);
+ m_papszResult = nullptr;
+ m_nRows = 0;
+ m_nCols = 0;
+ return sqlite3_get_table(db ? db : m_ctxt, sql, &m_papszResult,
+ &m_nRows, &m_nCols, nullptr) == SQLITE_OK;
+ }
+
+ bool execute(const std::string &sql) {
+ return sqlite3_exec(m_ctxt, sql.c_str(), nullptr, nullptr, nullptr) ==
+ SQLITE_OK;
+ }
+
+ std::string last_error() {
+ const char *msg = sqlite3_errmsg(m_ctxt);
+ return msg ? msg : std::string();
+ }
+
+ int m_nRows = 0;
+ int m_nCols = 0;
+ char **m_papszResult = nullptr;
+ sqlite3 *m_ctxt = nullptr;
+};
+
+// ---------------------------------------------------------------------------
+
+TEST_F(FactoryWithTmpDatabase, AuthorityFactory_test_with_fake_EPSG_database) {
+ createStructure();
+ populateWithFakeEPSG();
+
+ auto factory =
+ AuthorityFactory::create(DatabaseContext::create(m_ctxt), "EPSG");
+
+ EXPECT_TRUE(nn_dynamic_pointer_cast<UnitOfMeasure>(
+ factory->createObject("9001")) != nullptr);
+
+ EXPECT_TRUE(nn_dynamic_pointer_cast<Extent>(
+ factory->createObject("1262")) != nullptr);
+
+ EXPECT_TRUE(nn_dynamic_pointer_cast<PrimeMeridian>(
+ factory->createObject("8901")) != nullptr);
+
+ EXPECT_TRUE(nn_dynamic_pointer_cast<Ellipsoid>(
+ factory->createObject("7030")) != nullptr);
+
+ EXPECT_TRUE(nn_dynamic_pointer_cast<GeodeticReferenceFrame>(
+ factory->createObject("6326")) != nullptr);
+
+ EXPECT_TRUE(nn_dynamic_pointer_cast<VerticalReferenceFrame>(
+ factory->createObject("1027")) != nullptr);
+
+ EXPECT_TRUE(nn_dynamic_pointer_cast<GeographicCRS>(
+ factory->createObject("4326")) != nullptr);
+
+ EXPECT_TRUE(nn_dynamic_pointer_cast<VerticalCRS>(
+ factory->createObject("3855")) != nullptr);
+
+ EXPECT_TRUE(nn_dynamic_pointer_cast<Conversion>(
+ factory->createObject("16031")) != nullptr);
+
+ EXPECT_TRUE(nn_dynamic_pointer_cast<ProjectedCRS>(
+ factory->createObject("32631")) != nullptr);
+
+ EXPECT_TRUE(nn_dynamic_pointer_cast<CompoundCRS>(
+ factory->createObject("MY_COMPOUND")) != nullptr);
+
+ EXPECT_TRUE(nn_dynamic_pointer_cast<Transformation>(
+ factory->createObject("DUMMY_HELMERT")) != nullptr);
+
+ EXPECT_TRUE(nn_dynamic_pointer_cast<Transformation>(factory->createObject(
+ "DUMMY_GRID_TRANSFORMATION")) != nullptr);
+
+ EXPECT_TRUE(nn_dynamic_pointer_cast<Transformation>(factory->createObject(
+ "DUMMY_OTHER_TRANSFORMATION")) != nullptr);
+
+ EXPECT_TRUE(nn_dynamic_pointer_cast<ConcatenatedOperation>(
+ factory->createObject("DUMMY_CONCATENATED")) != nullptr);
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(factory, AuthorityFactory_createFromCoordinateReferenceSystemCodes) {
+ auto factory = AuthorityFactory::create(DatabaseContext::create(), "EPSG");
+ EXPECT_TRUE(
+ factory->createFromCoordinateReferenceSystemCodes("-1", "-1").empty());
+ {
+ auto res =
+ factory->createFromCoordinateReferenceSystemCodes("4326", "32631");
+ ASSERT_EQ(res.size(), 1);
+ EXPECT_TRUE(res[0]->sourceCRS() != nullptr);
+ EXPECT_TRUE(res[0]->targetCRS() != nullptr);
+ EXPECT_TRUE(
+ res[0]->isEquivalentTo(factory->createConversion("16031").get()));
+ }
+ {
+ auto res =
+ factory->createFromCoordinateReferenceSystemCodes("4209", "4326");
+ EXPECT_TRUE(!res.empty());
+ for (const auto &conv : res) {
+ EXPECT_TRUE(conv->sourceCRS()->getEPSGCode() == 4209);
+ EXPECT_TRUE(conv->targetCRS()->getEPSGCode() == 4326);
+ EXPECT_FALSE(conv->isDeprecated());
+ }
+ }
+ {
+ auto list =
+ factory->createFromCoordinateReferenceSystemCodes("4179", "4258");
+ ASSERT_EQ(list.size(), 3);
+ // Romania has a larger area than Poland (given our approx formula)
+ EXPECT_EQ(list[0]->getEPSGCode(), 15994); // Romania - 3m
+ EXPECT_EQ(list[1]->getEPSGCode(), 15993); // Romania - 10m
+ EXPECT_EQ(list[2]->getEPSGCode(), 1644); // Poland - 1m
+ }
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(
+ factory,
+ AuthorityFactory_createFromCoordinateReferenceSystemCodes_anonymous_authority) {
+ auto factory =
+ AuthorityFactory::create(DatabaseContext::create(), std::string());
+
+ {
+ auto res = factory->createFromCoordinateReferenceSystemCodes(
+ "EPSG", "4326", "EPSG", "32631", false, false);
+ ASSERT_EQ(res.size(), 1);
+ }
+ {
+ auto res = factory->createFromCoordinateReferenceSystemCodes(
+ "EPSG", "4209", "EPSG", "4326", false, false);
+ EXPECT_TRUE(!res.empty());
+ for (const auto &conv : res) {
+ EXPECT_TRUE(conv->sourceCRS()->getEPSGCode() == 4209);
+ EXPECT_TRUE(conv->targetCRS()->getEPSGCode() == 4326);
+ EXPECT_FALSE(conv->isDeprecated());
+ }
+ }
+}
+
+// ---------------------------------------------------------------------------
+
+TEST_F(FactoryWithTmpDatabase,
+ AuthorityFactory_test_with_fake_EPSG_and_OTHER_database) {
+ createStructure();
+ populateWithFakeEPSG();
+
+ ASSERT_TRUE(
+ execute("INSERT INTO geodetic_crs VALUES('OTHER','OTHER_4326','WGS "
+ "84',NULL,NULL,'geographic "
+ "2D','EPSG','6422','EPSG','6326','EPSG','1262',NULL,0);"))
+ << last_error();
+
+ ASSERT_TRUE(execute("INSERT INTO projected_crs "
+ "VALUES('OTHER','OTHER_32631','WGS 84 / UTM zone "
+ "31N',NULL,NULL,'EPSG','4400','OTHER','OTHER_4326',"
+ "'EPSG','16031','EPSG','2060',NULL,0);"))
+ << last_error();
+
+ auto factoryGeneral = AuthorityFactory::create(
+ DatabaseContext::create(m_ctxt), std::string());
+ {
+ auto res = factoryGeneral->createFromCoordinateReferenceSystemCodes(
+ "OTHER", "OTHER_4326", "OTHER", "OTHER_32631", false, false);
+ ASSERT_EQ(res.size(), 1);
+ }
+
+ auto factoryEPSG =
+ AuthorityFactory::create(DatabaseContext::create(m_ctxt), "EPSG");
+ {
+ auto res = factoryEPSG->createFromCoordinateReferenceSystemCodes(
+ "OTHER", "OTHER_4326", "OTHER", "OTHER_32631", false, false);
+ ASSERT_EQ(res.size(), 1);
+ }
+
+ auto factoryOTHER =
+ AuthorityFactory::create(DatabaseContext::create(m_ctxt), "OTHER");
+ {
+ auto res = factoryOTHER->createFromCoordinateReferenceSystemCodes(
+ "OTHER_4326", "OTHER_32631");
+ ASSERT_EQ(res.size(), 0); // the conversion is in the EPSG space
+ }
+
+ ASSERT_TRUE(execute(
+ "INSERT INTO grid_transformation "
+ "VALUES('OTHER','OTHER_GRID_TRANSFORMATION','name',NULL,NULL,"
+ "'EPSG','9615'"
+ ",'NTv2','EPSG','4326','OTHER','OTHER_4326','EPSG','1262',1.0,'EPSG','"
+ "8656','Latitude and longitude difference "
+ "file','nzgd2kgrid0005.gsb',NULL,NULL,NULL,NULL,NULL,NULL,0);"))
+ << last_error();
+ {
+ auto res = factoryGeneral->createFromCoordinateReferenceSystemCodes(
+ "EPSG", "4326", "OTHER", "OTHER_4326", false, false);
+ ASSERT_EQ(res.size(), 1);
+ }
+ {
+ auto res = factoryEPSG->createFromCoordinateReferenceSystemCodes(
+ "EPSG", "4326", "OTHER", "OTHER_4326", false, false);
+ ASSERT_EQ(res.size(), 0);
+ }
+ {
+ auto res = factoryOTHER->createFromCoordinateReferenceSystemCodes(
+ "EPSG", "4326", "OTHER", "OTHER_4326", false, false);
+ ASSERT_EQ(res.size(), 1);
+ }
+}
+
+// ---------------------------------------------------------------------------
+
+TEST_F(FactoryWithTmpDatabase,
+ AuthorityFactory_test_sorting_of_coordinate_operations) {
+ createStructure();
+ populateWithFakeEPSG();
+
+ ASSERT_TRUE(execute(
+ "INSERT INTO grid_transformation "
+ "VALUES('OTHER','TRANSFORMATION_10M','TRANSFORMATION_10M',NULL,NULL,"
+ "'EPSG','9615'"
+ ",'NTv2','EPSG','4326','EPSG','4326','EPSG','1262',10.0,'EPSG','"
+ "8656','Latitude and longitude difference "
+ "file','nzgd2kgrid0005.gsb',NULL,NULL,NULL,NULL,NULL,NULL,0);"))
+ << last_error();
+
+ ASSERT_TRUE(
+ execute("INSERT INTO grid_transformation "
+ "VALUES('OTHER','TRANSFORMATION_1M_SMALL_EXTENT','"
+ "TRANSFORMATION_1M_SMALL_EXTENT',NULL,NULL,'EPSG','9615'"
+ ",'NTv2','EPSG','4326','EPSG','4326','EPSG','2060',1.0,'EPSG','"
+ "8656','Latitude and longitude difference "
+ "file','nzgd2kgrid0005.gsb',NULL,NULL,NULL,NULL,NULL,NULL,0);"))
+ << last_error();
+
+ ASSERT_TRUE(execute(
+ "INSERT INTO grid_transformation "
+ "VALUES('OTHER','TRANSFORMATION_1M','TRANSFORMATION_1M',NULL,NULL,"
+ "'EPSG','9615'"
+ ",'NTv2','EPSG','4326','EPSG','4326','EPSG','1262',1.0,'EPSG','"
+ "8656','Latitude and longitude difference "
+ "file','nzgd2kgrid0005.gsb',NULL,NULL,NULL,NULL,NULL,NULL,0);"))
+ << last_error();
+
+ ASSERT_TRUE(
+ execute("INSERT INTO grid_transformation "
+ "VALUES('OTHER','TRANSFORMATION_0.5M_DEPRECATED','"
+ "TRANSFORMATION_0.5M_DEPRECATED',NULL,NULL,'EPSG','9615'"
+ ",'NTv2','EPSG','4326','EPSG','4326','EPSG','1262',1.0,'EPSG','"
+ "8656','Latitude and longitude difference "
+ "file','nzgd2kgrid0005.gsb',NULL,NULL,NULL,NULL,NULL,NULL,1);"))
+ << last_error();
+
+ auto factoryOTHER =
+ AuthorityFactory::create(DatabaseContext::create(m_ctxt), "OTHER");
+ auto res = factoryOTHER->createFromCoordinateReferenceSystemCodes(
+ "EPSG", "4326", "EPSG", "4326", false, false);
+ ASSERT_EQ(res.size(), 3);
+ EXPECT_EQ(*(res[0]->name()->description()), "TRANSFORMATION_1M");
+ EXPECT_EQ(*(res[1]->name()->description()), "TRANSFORMATION_10M");
+ EXPECT_EQ(*(res[2]->name()->description()),
+ "TRANSFORMATION_1M_SMALL_EXTENT");
+}
+
+// ---------------------------------------------------------------------------
+
+TEST_F(
+ FactoryWithTmpDatabase,
+ AuthorityFactory_createFromCRSCodesWithIntermediates_source_equals_target) {
+ createStructure();
+ populateWithFakeEPSG();
+
+ auto factory = AuthorityFactory::create(DatabaseContext::create(m_ctxt),
+ std::string());
+ auto res = factory->createFromCRSCodesWithIntermediates(
+ "EPSG", "4326", "EPSG", "4326", false, false, {});
+ EXPECT_EQ(res.size(), 0);
+}
+
+// ---------------------------------------------------------------------------
+
+TEST_F(
+ FactoryWithTmpDatabase,
+ AuthorityFactory_createFromCRSCodesWithIntermediates_case_source_pivot_target_pivot) {
+ createStructure();
+ populateWithFakeEPSG();
+ createSourceTargetPivotCRS();
+
+ createTransformationForPivotTesting("SOURCE", "PIVOT");
+ createTransformationForPivotTesting("TARGET", "PIVOT");
+
+ checkSourceToOther();
+}
+
+// ---------------------------------------------------------------------------
+
+TEST_F(
+ FactoryWithTmpDatabase,
+ AuthorityFactory_createFromCRSCodesWithIntermediates_case_source_pivot_pivot_target) {
+ createStructure();
+ populateWithFakeEPSG();
+ createSourceTargetPivotCRS();
+
+ createTransformationForPivotTesting("SOURCE", "PIVOT");
+ createTransformationForPivotTesting("PIVOT", "TARGET");
+
+ checkSourceToOther();
+}
+
+// ---------------------------------------------------------------------------
+
+TEST_F(
+ FactoryWithTmpDatabase,
+ AuthorityFactory_createFromCRSCodesWithIntermediates_case_pivot_source_pivot_target) {
+ createStructure();
+ populateWithFakeEPSG();
+ createSourceTargetPivotCRS();
+
+ createTransformationForPivotTesting("PIVOT", "SOURCE");
+ createTransformationForPivotTesting("PIVOT", "TARGET");
+
+ checkSourceToOther();
+}
+
+// ---------------------------------------------------------------------------
+
+TEST_F(
+ FactoryWithTmpDatabase,
+ AuthorityFactory_createFromCRSCodesWithIntermediates_case_pivot_source_target_pivot) {
+ createStructure();
+ populateWithFakeEPSG();
+ createSourceTargetPivotCRS();
+
+ createTransformationForPivotTesting("PIVOT", "SOURCE");
+ createTransformationForPivotTesting("TARGET", "PIVOT");
+
+ checkSourceToOther();
+}
+
+// ---------------------------------------------------------------------------
+
+TEST_F(FactoryWithTmpDatabase, AuthorityFactory_proj_based_transformation) {
+ createStructure();
+ populateWithFakeEPSG();
+
+ ASSERT_TRUE(execute(
+ "INSERT INTO other_transformation "
+ "VALUES('OTHER','FOO','My PROJ string based op',NULL,NULL,'PROJ',"
+ "'PROJString','+proj=pipeline +ellps=WGS84 +step +proj=longlat',"
+ "'EPSG','4326','EPSG','4326','EPSG','1262',0.0,NULL,NULL,NULL,"
+ "NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,"
+ "NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,"
+ "NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,"
+ "NULL,NULL,NULL,NULL,NULL,NULL,0);"))
+ << last_error();
+
+ auto factoryOTHER =
+ AuthorityFactory::create(DatabaseContext::create(m_ctxt), "OTHER");
+ auto res = factoryOTHER->createFromCoordinateReferenceSystemCodes(
+ "EPSG", "4326", "EPSG", "4326", false, false);
+ ASSERT_EQ(res.size(), 1);
+ EXPECT_EQ(res[0]->nameStr(), "My PROJ string based op");
+ EXPECT_EQ(res[0]->exportToPROJString(PROJStringFormatter::create().get()),
+ "+proj=pipeline +ellps=WGS84 +step +proj=longlat");
+}
+
+// ---------------------------------------------------------------------------
+
+TEST_F(FactoryWithTmpDatabase, AuthorityFactory_wkt_based_transformation) {
+ createStructure();
+ populateWithFakeEPSG();
+
+ auto wkt = "COORDINATEOPERATION[\"My WKT string based op\",\n"
+ " SOURCECRS[\n"
+ " GEODCRS[\"unknown\",\n"
+ " DATUM[\"World Geodetic System 1984\",\n"
+ " ELLIPSOID[\"WGS 84\",6378137,298.257223563,\n"
+ " LENGTHUNIT[\"metre\",1]],\n"
+ " ID[\"EPSG\",6326]],\n"
+ " PRIMEM[\"Greenwich\",0,\n"
+ " ANGLEUNIT[\"degree\",0.0174532925199433],\n"
+ " ID[\"EPSG\",8901]],\n"
+ " CS[ellipsoidal,2],\n"
+ " AXIS[\"geodetic latitude (Lat)\",north],\n"
+ " AXIS[\"geodetic longitude (Lon)\",east],\n"
+ " ANGLEUNIT[\"degree\",0.0174532925199433]]],\n"
+ " TARGETCRS[\n"
+ " GEODCRS[\"unknown\",\n"
+ " DATUM[\"World Geodetic System 1984\",\n"
+ " ELLIPSOID[\"WGS 84\",6378137,298.257223563,\n"
+ " LENGTHUNIT[\"metre\",1]],\n"
+ " ID[\"EPSG\",6326]],\n"
+ " PRIMEM[\"Greenwich\",0,\n"
+ " ANGLEUNIT[\"degree\",0.0174532925199433],\n"
+ " ID[\"EPSG\",8901]],\n"
+ " CS[ellipsoidal,2],\n"
+ " AXIS[\"geodetic latitude (Lat)\",north],\n"
+ " AXIS[\"geodetic longitude (Lon)\",east],\n"
+ " ANGLEUNIT[\"degree\",0.0174532925199433]]],\n"
+ " METHOD[\"Geocentric translations (geog2D domain)\"],\n"
+ " PARAMETER[\"X-axis translation\",1],\n"
+ " PARAMETER[\"Y-axis translation\",2],\n"
+ " PARAMETER[\"Z-axis translation\",3]]";
+
+ ASSERT_TRUE(
+ execute("INSERT INTO other_transformation "
+ "VALUES('OTHER','FOO','My WKT string based op',NULL,NULL,"
+ "'PROJ','WKT','" +
+ std::string(wkt) +
+ "',"
+ "'EPSG','4326','EPSG','4326','EPSG','1262',0.0,NULL,NULL,NULL,"
+ "NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,"
+ "NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,"
+ "NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,"
+ "NULL,NULL,NULL,NULL,NULL,NULL,0);"))
+ << last_error();
+
+ auto factoryOTHER =
+ AuthorityFactory::create(DatabaseContext::create(m_ctxt), "OTHER");
+ auto res = factoryOTHER->createFromCoordinateReferenceSystemCodes(
+ "EPSG", "4326", "EPSG", "4326", false, false);
+ ASSERT_EQ(res.size(), 1);
+ EXPECT_EQ(res[0]->nameStr(), "My WKT string based op");
+ EXPECT_EQ(res[0]->exportToPROJString(PROJStringFormatter::create().get()),
+ "+proj=pipeline +step +proj=axisswap +order=2,1 +step "
+ "+proj=unitconvert +xy_in=deg +xy_out=rad +step +proj=cart "
+ "+ellps=WGS84 +step +proj=helmert +x=1 +y=2 +z=3 +step +inv "
+ "+proj=cart +ellps=WGS84 +step +proj=unitconvert +xy_in=rad "
+ "+xy_out=deg +step +proj=axisswap +order=2,1");
+}
+
+// ---------------------------------------------------------------------------
+
+TEST_F(FactoryWithTmpDatabase,
+ AuthorityFactory_wkt_based_transformation_not_wkt) {
+ createStructure();
+ populateWithFakeEPSG();
+
+ ASSERT_TRUE(
+ execute("INSERT INTO other_transformation "
+ "VALUES('OTHER','FOO','My WKT string based op',NULL,NULL,"
+ "'PROJ','WKT','" +
+ std::string("invalid_wkt") +
+ "',"
+ "'EPSG','4326','EPSG','4326','EPSG','1262',0.0,NULL,NULL,NULL,"
+ "NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,"
+ "NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,"
+ "NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,"
+ "NULL,NULL,NULL,NULL,NULL,NULL,0);"))
+ << last_error();
+
+ auto factoryOTHER =
+ AuthorityFactory::create(DatabaseContext::create(m_ctxt), "OTHER");
+ EXPECT_THROW(factoryOTHER->createFromCoordinateReferenceSystemCodes(
+ "EPSG", "4326", "EPSG", "4326", false, false),
+ FactoryException);
+}
+
+// ---------------------------------------------------------------------------
+
+TEST_F(FactoryWithTmpDatabase,
+ AuthorityFactory_wkt_based_transformation_not_co_wkt) {
+ createStructure();
+ populateWithFakeEPSG();
+
+ ASSERT_TRUE(
+ execute("INSERT INTO other_transformation "
+ "VALUES('OTHER','FOO','My WKT string based op',NULL,NULL,"
+ "'PROJ','WKT','" +
+ std::string("LOCAL_CS[\"foo\"]") +
+ "',"
+ "'EPSG','4326','EPSG','4326','EPSG','1262',0.0,NULL,NULL,NULL,"
+ "NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,"
+ "NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,"
+ "NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,"
+ "NULL,NULL,NULL,NULL,NULL,NULL,0);"))
+ << last_error();
+
+ auto factoryOTHER =
+ AuthorityFactory::create(DatabaseContext::create(m_ctxt), "OTHER");
+ EXPECT_THROW(factoryOTHER->createFromCoordinateReferenceSystemCodes(
+ "EPSG", "4326", "EPSG", "4326", false, false),
+ FactoryException);
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(factory, AuthorityFactory_EPSG_4326_approximate_equivalent_to_builtin) {
+ auto factory = AuthorityFactory::create(DatabaseContext::create(), "EPSG");
+ auto crs = nn_dynamic_pointer_cast<GeographicCRS>(
+ factory->createCoordinateReferenceSystem("4326"));
+ EXPECT_TRUE(crs->isEquivalentTo(GeographicCRS::EPSG_4326.get(),
+ IComparable::Criterion::EQUIVALENT));
+}
+
+// ---------------------------------------------------------------------------
+
+TEST_F(FactoryWithTmpDatabase, getAuthorities) {
+ createStructure();
+ populateWithFakeEPSG();
+
+ auto res = DatabaseContext::create(m_ctxt)->getAuthorities();
+ EXPECT_EQ(res.size(), 2);
+ EXPECT_TRUE(res.find("EPSG") != res.end());
+ EXPECT_TRUE(res.find("PROJ") != res.end());
+}
+
+// ---------------------------------------------------------------------------
+
+TEST_F(FactoryWithTmpDatabase, lookForGridInfo) {
+ createStructure();
+
+ ASSERT_TRUE(execute("INSERT INTO grid_alternatives(original_grid_name,"
+ "proj_grid_name, "
+ "proj_grid_format, "
+ "proj_method, "
+ "inverse_direction, "
+ "package_name, "
+ "url, direct_download, open_license, directory) "
+ "VALUES ('null', "
+ "'PROJ_fake_grid', "
+ "'CTable2', "
+ "'hgridshift', "
+ "0, "
+ "NULL, "
+ "'url', 1, 1, NULL);"))
+ << last_error();
+
+ std::string fullFilename;
+ std::string packageName;
+ std::string url;
+ bool directDownload = false;
+ bool openLicense = false;
+ bool gridAvailable = false;
+ EXPECT_TRUE(DatabaseContext::create(m_ctxt)->lookForGridInfo(
+ "PROJ_fake_grid", fullFilename, packageName, url, directDownload,
+ openLicense, gridAvailable));
+ EXPECT_TRUE(fullFilename.empty());
+ EXPECT_TRUE(packageName.empty());
+ EXPECT_EQ(url, "url");
+ EXPECT_EQ(directDownload, true);
+ EXPECT_EQ(openLicense, true);
+ EXPECT_EQ(gridAvailable, false);
+}
+
+// ---------------------------------------------------------------------------
+
+TEST_F(FactoryWithTmpDatabase, custom_geodetic_crs) {
+ createStructure();
+ populateWithFakeEPSG();
+
+ ASSERT_TRUE(execute("INSERT INTO geodetic_crs VALUES('TEST_NS','TEST','my "
+ "name TEST',NULL,NULL,'geographic 2D',"
+ "NULL,NULL,NULL,NULL,NULL,NULL,'+proj=longlat +a=2 "
+ "+rf=300',0);"))
+ << last_error();
+
+ ASSERT_TRUE(execute("INSERT INTO geodetic_crs VALUES"
+ "('TEST_NS','TEST_BOUND',"
+ "'my name TEST',NULL,NULL,'geographic 2D',"
+ "NULL,NULL,NULL,NULL,NULL,NULL,'+proj=longlat +a=2 "
+ "+rf=300 +towgs84=1,2,3',0);"))
+ << last_error();
+
+ ASSERT_TRUE(execute("INSERT INTO geodetic_crs VALUES('TEST_NS','TEST_GC',"
+ "'my name',NULL,NULL,'geocentric',NULL,NULL,NULL,NULL,"
+ "NULL,NULL,'+proj=geocent +a=2 +rf=300',0);"))
+ << last_error();
+
+ ASSERT_TRUE(execute(
+ "INSERT INTO geodetic_crs "
+ "VALUES('TEST_NS','TEST_REF_ANOTHER','my name TEST_REF_ANOTHER',"
+ "NULL,NULL,"
+ "'geographic 2D',NULL,NULL,NULL,NULL,NULL,NULL,'TEST_NS:TEST',0);"))
+ << last_error();
+
+ ASSERT_TRUE(execute("INSERT INTO geodetic_crs "
+ "VALUES('TEST_NS','TEST_WRONG','my name',NULL,NULL,"
+ "'geographic 2D',NULL,NULL,NULL,NULL,NULL,NULL,"
+ "'+proj=merc',0);"))
+ << last_error();
+
+ ASSERT_TRUE(execute(
+ "INSERT INTO geodetic_crs "
+ "VALUES('TEST_NS','TEST_RECURSIVE','my name',NULL,NULL,'geographic 2D',"
+ "NULL,NULL,NULL,NULL,NULL,NULL,'TEST_NS:TEST_RECURSIVE',0);"))
+ << last_error();
+
+ auto factory =
+ AuthorityFactory::create(DatabaseContext::create(m_ctxt), "TEST_NS");
+ {
+ auto crs = factory->createGeodeticCRS("TEST");
+ EXPECT_TRUE(nn_dynamic_pointer_cast<GeographicCRS>(crs) != nullptr);
+ EXPECT_EQ(*(crs->name()->description()), "my name TEST");
+ EXPECT_EQ(crs->identifiers().size(), 1);
+ EXPECT_EQ(crs->ellipsoid()->semiMajorAxis(), Length(2));
+ EXPECT_EQ(*(crs->ellipsoid()->inverseFlattening()), Scale(300));
+ EXPECT_TRUE(crs->canonicalBoundCRS() == nullptr);
+ }
+ {
+ auto crs = factory->createGeodeticCRS("TEST_BOUND");
+ EXPECT_TRUE(nn_dynamic_pointer_cast<GeographicCRS>(crs) != nullptr);
+ EXPECT_EQ(*(crs->name()->description()), "my name TEST");
+ EXPECT_EQ(crs->identifiers().size(), 1);
+ EXPECT_EQ(crs->ellipsoid()->semiMajorAxis(), Length(2));
+ EXPECT_EQ(*(crs->ellipsoid()->inverseFlattening()), Scale(300));
+ EXPECT_TRUE(crs->canonicalBoundCRS() != nullptr);
+ }
+ {
+ auto crs = factory->createGeodeticCRS("TEST_GC");
+ EXPECT_TRUE(nn_dynamic_pointer_cast<GeographicCRS>(crs) == nullptr);
+ EXPECT_EQ(*(crs->name()->description()), "my name");
+ EXPECT_EQ(crs->identifiers().size(), 1);
+ EXPECT_EQ(crs->ellipsoid()->semiMajorAxis(), Length(2));
+ EXPECT_EQ(*(crs->ellipsoid()->inverseFlattening()), Scale(300));
+ }
+ {
+ auto crs = factory->createGeodeticCRS("TEST_REF_ANOTHER");
+ EXPECT_TRUE(nn_dynamic_pointer_cast<GeographicCRS>(crs) != nullptr);
+ EXPECT_EQ(*(crs->name()->description()), "my name TEST_REF_ANOTHER");
+ EXPECT_EQ(crs->identifiers().size(), 1);
+ EXPECT_EQ(crs->ellipsoid()->semiMajorAxis(), Length(2));
+ EXPECT_EQ(*(crs->ellipsoid()->inverseFlattening()), Scale(300));
+ }
+
+ EXPECT_THROW(factory->createGeodeticCRS("TEST_WRONG"), FactoryException);
+
+ EXPECT_THROW(factory->createGeodeticCRS("TEST_RECURSIVE"),
+ FactoryException);
+}
+
+// ---------------------------------------------------------------------------
+
+TEST_F(FactoryWithTmpDatabase, custom_projected_crs) {
+ createStructure();
+ populateWithFakeEPSG();
+
+ ASSERT_TRUE(execute("INSERT INTO projected_crs "
+ "VALUES('TEST_NS','TEST','my name',NULL,NULL,NULL,"
+ "NULL,NULL,NULL,NULL,NULL,NULL,NULL,"
+ "'+proj=mbt_s +unused_flag',0);"))
+ << last_error();
+
+ ASSERT_TRUE(execute("INSERT INTO projected_crs "
+ "VALUES('TEST_NS','TEST_BOUND','my name',NULL,NULL,"
+ "NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,"
+ "'+proj=mbt_s +unused_flag +towgs84=1,2,3',0);"))
+ << last_error();
+
+ ASSERT_TRUE(execute("INSERT INTO projected_crs "
+ "VALUES('TEST_NS','TEST_WRONG','my name',NULL,NULL,"
+ "NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,"
+ "'+proj=longlat',0);"))
+ << last_error();
+
+ // Unknown ellipsoid
+ ASSERT_TRUE(execute("INSERT INTO projected_crs "
+ "VALUES('TEST_NS','TEST_MERC','merc',NULL,NULL,NULL,"
+ "NULL,NULL,NULL,NULL,NULL,NULL,NULL,"
+ "'+proj=merc +x_0=0 +R=1',0);"))
+ << last_error();
+
+ // Well-known ellipsoid
+ ASSERT_TRUE(execute("INSERT INTO projected_crs "
+ "VALUES('TEST_NS','TEST_MERC2','merc2',NULL,NULL,NULL,"
+ "NULL,NULL,NULL,NULL,NULL,NULL,NULL,"
+ "'+proj=merc +x_0=0 +ellps=GRS80',0);"))
+ << last_error();
+
+ // WKT1_GDAL
+ ASSERT_TRUE(
+ execute("INSERT INTO projected_crs "
+ "VALUES('TEST_NS','TEST_WKT1_GDAL','WKT1_GDAL',NULL,NULL,NULL,"
+ "NULL,NULL,NULL,NULL,NULL,NULL,NULL,"
+ "'"
+ "PROJCS[\"unknown\",\n"
+ " GEOGCS[\"unknown\",\n"
+ " DATUM[\"Unknown_based_on_WGS84_ellipsoid\",\n"
+ " SPHEROID[\"WGS 84\",6378137,298.257223563,\n"
+ " AUTHORITY[\"EPSG\",\"7030\"]]],\n"
+ " PRIMEM[\"Greenwich\",0,\n"
+ " AUTHORITY[\"EPSG\",\"8901\"]],\n"
+ " UNIT[\"degree\",0.0174532925199433,\n"
+ " AUTHORITY[\"EPSG\",\"9122\"]]],\n"
+ " PROJECTION[\"Mercator_1SP\"],\n"
+ " PARAMETER[\"central_meridian\",0],\n"
+ " PARAMETER[\"scale_factor\",1],\n"
+ " PARAMETER[\"false_easting\",0],\n"
+ " PARAMETER[\"false_northing\",0],\n"
+ " UNIT[\"metre\",1,\n"
+ " AUTHORITY[\"EPSG\",\"9001\"]],\n"
+ " AXIS[\"Easting\",EAST],\n"
+ " AXIS[\"Northing\",NORTH]]"
+ "',0);"))
+ << last_error();
+
+ auto factory =
+ AuthorityFactory::create(DatabaseContext::create(m_ctxt), "TEST_NS");
+ {
+ auto crs = factory->createProjectedCRS("TEST");
+ EXPECT_EQ(*(crs->name()->description()), "my name");
+ EXPECT_EQ(crs->identifiers().size(), 1);
+ EXPECT_EQ(crs->derivingConversion()->targetCRS().get(), crs.get());
+ EXPECT_EQ(
+ crs->exportToPROJString(PROJStringFormatter::create().get()),
+ "+proj=pipeline +step +proj=unitconvert +xy_in=deg +xy_out=rad "
+ "+step +proj=mbt_s +unused_flag +ellps=WGS84");
+ EXPECT_TRUE(crs->canonicalBoundCRS() == nullptr);
+ }
+ {
+ auto crs = factory->createProjectedCRS("TEST_BOUND");
+ EXPECT_EQ(*(crs->name()->description()), "my name");
+ EXPECT_EQ(crs->identifiers().size(), 1);
+ EXPECT_EQ(crs->derivingConversion()->targetCRS().get(), crs.get());
+ EXPECT_EQ(
+ crs->exportToPROJString(PROJStringFormatter::create().get()),
+ "+proj=pipeline +step +proj=unitconvert +xy_in=deg +xy_out=rad "
+ "+step +proj=mbt_s +unused_flag +ellps=WGS84");
+ EXPECT_TRUE(crs->canonicalBoundCRS() != nullptr);
+ }
+
+ EXPECT_THROW(factory->createProjectedCRS("TEST_WRONG"), FactoryException);
+
+ {
+ auto obj =
+ PROJStringParser().createFromPROJString("+proj=merc +a=1 +b=1");
+ auto crs = nn_dynamic_pointer_cast<ProjectedCRS>(obj);
+ ASSERT_TRUE(crs != nullptr);
+ auto res = crs->identify(factory);
+ EXPECT_EQ(res.size(), 1);
+ if (!res.empty()) {
+ EXPECT_EQ(res.front().first->nameStr(), "merc");
+ }
+ }
+
+ {
+ auto obj =
+ PROJStringParser().createFromPROJString("+proj=merc +ellps=GRS80");
+ auto crs = nn_dynamic_pointer_cast<ProjectedCRS>(obj);
+ ASSERT_TRUE(crs != nullptr);
+ auto res = crs->identify(factory);
+ EXPECT_EQ(res.size(), 1);
+ if (!res.empty()) {
+ EXPECT_EQ(res.front().first->nameStr(), "merc2");
+ }
+ }
+
+ {
+ auto obj = PROJStringParser().createFromPROJString(
+ "+proj=merc +a=6378137 +rf=298.257222101");
+ auto crs = nn_dynamic_pointer_cast<ProjectedCRS>(obj);
+ ASSERT_TRUE(crs != nullptr);
+ auto res = crs->identify(factory);
+ EXPECT_EQ(res.size(), 1);
+ if (!res.empty()) {
+ EXPECT_EQ(res.front().first->nameStr(), "merc2");
+ }
+ }
+
+ {
+ auto obj =
+ PROJStringParser().createFromPROJString("+proj=merc +ellps=WGS84");
+ auto crs = nn_dynamic_pointer_cast<ProjectedCRS>(obj);
+ ASSERT_TRUE(crs != nullptr);
+ auto res = crs->identify(factory);
+ EXPECT_EQ(res.size(), 1);
+ if (!res.empty()) {
+ EXPECT_EQ(res.front().first->nameStr(), "WKT1_GDAL");
+ }
+ }
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(factory, attachExtraDatabases_none) {
+ auto ctxt = DatabaseContext::create(std::string(), {});
+ auto factory = AuthorityFactory::create(ctxt, "EPSG");
+ auto crs = factory->createGeodeticCRS("4979");
+ auto gcrs = nn_dynamic_pointer_cast<GeographicCRS>(crs);
+}
+
+// ---------------------------------------------------------------------------
+
+#ifndef SQLITE_OPEN_URI
+static int MyUnlink(const std::string &filename) {
+#ifdef _MSC_VER
+ return _unlink(filename.c_str());
+#else
+ return unlink(filename.c_str());
+#endif
+}
+#endif
+
+// ---------------------------------------------------------------------------
+
+TEST(factory, attachExtraDatabases_auxiliary) {
+
+#ifdef SQLITE_OPEN_URI
+ std::string auxDbName("file:proj_test_aux.db?mode=memory&cache=shared");
+#else
+ const char *temp = getenv("TEMP");
+ if (!temp) {
+ temp = getenv("TMP");
+ }
+ if (!temp) {
+ temp = "/tmp";
+ }
+ std::string auxDbName(std::string(temp) + "/proj_test_aux.db");
+ MyUnlink(auxDbName);
+#endif
+ {
+ sqlite3 *dbAux = nullptr;
+ sqlite3_open_v2(auxDbName.c_str(), &dbAux,
+ SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE
+#ifdef SQLITE_OPEN_URI
+ | SQLITE_OPEN_URI
+#endif
+ ,
+ nullptr);
+ ASSERT_TRUE(dbAux != nullptr);
+ ASSERT_TRUE(sqlite3_exec(dbAux, "BEGIN", nullptr, nullptr, nullptr) ==
+ SQLITE_OK);
+ {
+ auto ctxt = DatabaseContext::create();
+ const auto dbStructure = ctxt->getDatabaseStructure();
+ for (const auto &sql : dbStructure) {
+ if (sql.find("CREATE TRIGGER") == std::string::npos) {
+ ASSERT_TRUE(sqlite3_exec(dbAux, sql.c_str(), nullptr,
+ nullptr, nullptr) == SQLITE_OK);
+ }
+ }
+ }
+
+ ASSERT_TRUE(
+ sqlite3_exec(
+ dbAux,
+ "INSERT INTO geodetic_crs VALUES('OTHER','OTHER_4326','WGS "
+ "84',NULL,NULL,'geographic 2D','EPSG','6422','EPSG','6326',"
+ "'EPSG','1262',NULL,0);",
+ nullptr, nullptr, nullptr) == SQLITE_OK);
+ ASSERT_TRUE(sqlite3_exec(dbAux, "COMMIT", nullptr, nullptr, nullptr) ==
+ SQLITE_OK);
+
+ {
+ auto ctxt = DatabaseContext::create(std::string(), {auxDbName});
+ // Look for object located in main DB
+ {
+ auto factory = AuthorityFactory::create(ctxt, "EPSG");
+ auto crs = factory->createGeodeticCRS("4326");
+ auto gcrs = nn_dynamic_pointer_cast<GeographicCRS>(crs);
+ }
+ // Look for object located in auxiliary DB
+ {
+ auto factory = AuthorityFactory::create(ctxt, "OTHER");
+ auto crs = factory->createGeodeticCRS("OTHER_4326");
+ auto gcrs = nn_dynamic_pointer_cast<GeographicCRS>(crs);
+ }
+ }
+
+ {
+ auto ctxt =
+ DatabaseContext::create(std::string(), {auxDbName, ":memory:"});
+ // Look for object located in main DB
+ {
+ auto factory = AuthorityFactory::create(ctxt, "EPSG");
+ auto crs = factory->createGeodeticCRS("4326");
+ auto gcrs = nn_dynamic_pointer_cast<GeographicCRS>(crs);
+ }
+ // Look for object located in auxiliary DB
+ {
+ auto factory = AuthorityFactory::create(ctxt, "OTHER");
+ auto crs = factory->createGeodeticCRS("OTHER_4326");
+ auto gcrs = nn_dynamic_pointer_cast<GeographicCRS>(crs);
+ }
+ }
+
+ {
+ auto ctxt = DatabaseContext::create(std::string(), {":memory:"});
+ // Look for object located in main DB
+ {
+ auto factory = AuthorityFactory::create(ctxt, "EPSG");
+ auto crs = factory->createGeodeticCRS("4326");
+ auto gcrs = nn_dynamic_pointer_cast<GeographicCRS>(crs);
+ }
+ // Look for object located in auxiliary DB
+ {
+ auto factory = AuthorityFactory::create(ctxt, "OTHER");
+ EXPECT_THROW(factory->createGeodeticCRS("OTHER_4326"),
+ FactoryException);
+ }
+ }
+
+ sqlite3_close(dbAux);
+ }
+#ifndef SQLITE_OPEN_URI
+ MyUnlink(auxDbName);
+#endif
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(factory, attachExtraDatabases_auxiliary_error) {
+ EXPECT_THROW(DatabaseContext::create(std::string(), {"i_dont_exist_db"}),
+ FactoryException);
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(factory, getOfficialNameFromAlias) {
+ auto ctxt = DatabaseContext::create(std::string(), {});
+ auto factory = AuthorityFactory::create(ctxt, std::string());
+ std::string outTableName;
+ std::string outAuthName;
+ std::string outCode;
+
+ {
+ auto officialName = factory->getOfficialNameFromAlias(
+ "GCS_WGS_1984", std::string(), std::string(), outTableName,
+ outAuthName, outCode);
+ EXPECT_EQ(officialName, "WGS 84");
+ EXPECT_EQ(outTableName, "geodetic_crs");
+ EXPECT_EQ(outAuthName, "EPSG");
+ EXPECT_EQ(outCode, "4326");
+ }
+
+ {
+ auto officialName = factory->getOfficialNameFromAlias(
+ "GCS_WGS_1984", "geodetic_crs", "ESRI", outTableName, outAuthName,
+ outCode);
+ EXPECT_EQ(officialName, "WGS 84");
+ EXPECT_EQ(outTableName, "geodetic_crs");
+ EXPECT_EQ(outAuthName, "EPSG");
+ EXPECT_EQ(outCode, "4326");
+ }
+
+ {
+ auto officialName = factory->getOfficialNameFromAlias(
+ "no match", std::string(), std::string(), outTableName, outAuthName,
+ outCode);
+ EXPECT_EQ(officialName, "");
+ }
+}
+
+// ---------------------------------------------------------------------------
+
+TEST_F(FactoryWithTmpDatabase,
+ createOperations_exact_transform_not_whole_area) {
+ createStructure();
+ populateWithFakeEPSG();
+
+ ASSERT_TRUE(
+ execute("INSERT INTO other_transformation "
+ "VALUES('OTHER','PARTIAL_AREA_PERFECT_ACCURACY',"
+ "'PARTIAL_AREA_PERFECT_ACCURACY',NULL,NULL,'PROJ',"
+ "'PROJString','+proj=helmert +x=1',"
+ "'EPSG','4326','EPSG','4326','EPSG','1933',0.0,NULL,NULL,NULL,"
+ "NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,"
+ "NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,"
+ "NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,"
+ "NULL,NULL,NULL,NULL,NULL,NULL,0);"))
+ << last_error();
+
+ ASSERT_TRUE(
+ execute("INSERT INTO other_transformation "
+ "VALUES('OTHER','WHOLE_AREA_APPROX_ACCURACY',"
+ "'WHOLE_AREA_APPROX_ACCURACY',NULL,NULL,'PROJ',"
+ "'PROJString','+proj=helmert +x=2',"
+ "'EPSG','4326','EPSG','4326','EPSG','1262',1.0,NULL,NULL,NULL,"
+ "NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,"
+ "NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,"
+ "NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,"
+ "NULL,NULL,NULL,NULL,NULL,NULL,0);"))
+ << last_error();
+
+ auto dbContext = DatabaseContext::create(m_ctxt);
+ auto authFactory =
+ AuthorityFactory::create(dbContext, std::string("OTHER"));
+ auto ctxt = CoordinateOperationContext::create(authFactory, nullptr, 0.0);
+ ctxt->setSpatialCriterion(
+ CoordinateOperationContext::SpatialCriterion::PARTIAL_INTERSECTION);
+ auto list = CoordinateOperationFactory::create()->createOperations(
+ AuthorityFactory::create(dbContext, "EPSG")
+ ->createCoordinateReferenceSystem("4326"),
+ AuthorityFactory::create(dbContext, "EPSG")
+ ->createCoordinateReferenceSystem("4326"),
+ ctxt);
+ ASSERT_EQ(list.size(), 2);
+ EXPECT_EQ(list[0]->nameStr(), "WHOLE_AREA_APPROX_ACCURACY");
+ EXPECT_EQ(list[1]->nameStr(), "PARTIAL_AREA_PERFECT_ACCURACY");
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(factory, createObjectsFromName) {
+ auto ctxt = DatabaseContext::create();
+ auto factory = AuthorityFactory::create(ctxt, std::string());
+ auto factoryEPSG = AuthorityFactory::create(ctxt, "EPSG");
+
+ EXPECT_EQ(factory->createObjectsFromName("").size(), 0);
+
+ // ellipsoid + 3 geodeticCRS
+ EXPECT_EQ(factory->createObjectsFromName("WGS 84", {}, false).size(), 4);
+
+ EXPECT_EQ(factory->createObjectsFromName("WGS 84", {}, true, 10).size(),
+ 10);
+
+ EXPECT_EQ(factory
+ ->createObjectsFromName(
+ "WGS 84", {AuthorityFactory::ObjectType::CRS}, false)
+ .size(),
+ 3);
+
+ {
+ auto res = factoryEPSG->createObjectsFromName(
+ "WGS84", {AuthorityFactory::ObjectType::GEOGRAPHIC_2D_CRS}, true);
+ EXPECT_EQ(res.size(), 2); // EPSG:4326 and EPSG:4030
+ if (!res.empty()) {
+ EXPECT_EQ(res.front()->getEPSGCode(), 4326);
+ }
+ }
+
+ // Prime meridian
+ EXPECT_EQ(factoryEPSG->createObjectsFromName("Paris", {}, false, 2).size(),
+ 1);
+ // Ellipsoid
+ EXPECT_EQ(
+ factoryEPSG->createObjectsFromName("Clarke 1880 (IGN)", {}, false, 2)
+ .size(),
+ 1);
+ // Geodetic datum
+ EXPECT_EQ(
+ factoryEPSG->createObjectsFromName("Hungarian Datum 1909", {}, false, 2)
+ .size(),
+ 1);
+ // Vertical datum
+ EXPECT_EQ(factoryEPSG->createObjectsFromName("EGM2008 geoid", {}, false, 2)
+ .size(),
+ 1);
+ // Geodetic CRS
+ EXPECT_EQ(factoryEPSG
+ ->createObjectsFromName(
+ "Unknown datum based upon the Airy 1830 ellipsoid", {},
+ false, 2)
+ .size(),
+ 1);
+ // Projected CRS
+ EXPECT_EQ(factoryEPSG
+ ->createObjectsFromName(
+ "Anguilla 1957 / British West Indies Grid", {}, false, 2)
+ .size(),
+ 1);
+ // Vertical CRS
+ EXPECT_EQ(factoryEPSG->createObjectsFromName("EGM2008 height", {}, false, 2)
+ .size(),
+ 1);
+ // Compound CRS
+ EXPECT_EQ(factoryEPSG
+ ->createObjectsFromName(
+ "KKJ / Finland Uniform Coordinate System + N60 height",
+ {}, false, 2)
+ .size(),
+ 1);
+ // Conversion
+ EXPECT_EQ(
+ factoryEPSG->createObjectsFromName("Belgian Lambert 2008", {}, false, 2)
+ .size(),
+ 1);
+ // Helmert transform
+ EXPECT_EQ(
+ factoryEPSG->createObjectsFromName("MGI to ETRS89 (4)", {}, false, 2)
+ .size(),
+ 1);
+ // Grid transform
+ EXPECT_EQ(factoryEPSG
+ ->createObjectsFromName("Guam 1963 to NAD83(HARN) (1)", {},
+ false, 2)
+ .size(),
+ 1);
+ // Other transform
+ EXPECT_EQ(factoryEPSG
+ ->createObjectsFromName(
+ "Monte Mario (Rome) to Monte Mario (1)", {}, false, 2)
+ .size(),
+ 1);
+ // Concatenated operation
+ EXPECT_EQ(
+ factoryEPSG
+ ->createObjectsFromName("MGI (Ferro) to WGS 84 (2)", {}, false, 2)
+ .size(),
+ 1);
+
+ // Deprecated object
+ EXPECT_EQ(factoryEPSG
+ ->createObjectsFromName(
+ "NAD27(CGQ77) / SCoPQ zone 2 (deprecated)", {}, false, 2)
+ .size(),
+ 1);
+
+ const auto types = std::vector<AuthorityFactory::ObjectType>{
+ AuthorityFactory::ObjectType::PRIME_MERIDIAN,
+ AuthorityFactory::ObjectType::ELLIPSOID,
+ AuthorityFactory::ObjectType::DATUM,
+ AuthorityFactory::ObjectType::GEODETIC_REFERENCE_FRAME,
+ AuthorityFactory::ObjectType::VERTICAL_REFERENCE_FRAME,
+ AuthorityFactory::ObjectType::CRS,
+ AuthorityFactory::ObjectType::GEODETIC_CRS,
+ AuthorityFactory::ObjectType::GEOCENTRIC_CRS,
+ AuthorityFactory::ObjectType::GEOGRAPHIC_CRS,
+ AuthorityFactory::ObjectType::GEOGRAPHIC_2D_CRS,
+ AuthorityFactory::ObjectType::GEOGRAPHIC_3D_CRS,
+ AuthorityFactory::ObjectType::PROJECTED_CRS,
+ AuthorityFactory::ObjectType::VERTICAL_CRS,
+ AuthorityFactory::ObjectType::COMPOUND_CRS,
+ AuthorityFactory::ObjectType::COORDINATE_OPERATION,
+ AuthorityFactory::ObjectType::CONVERSION,
+ AuthorityFactory::ObjectType::TRANSFORMATION,
+ AuthorityFactory::ObjectType::CONCATENATED_OPERATION,
+ };
+ for (const auto type : types) {
+ factory->createObjectsFromName("i_dont_exist", {type}, false, 1);
+ }
+ factory->createObjectsFromName("i_dont_exist", types, false, 1);
+}
+
+} // namespace
diff --git a/test/unit/test_io.cpp b/test/unit/test_io.cpp
new file mode 100644
index 00000000..7d26d82a
--- /dev/null
+++ b/test/unit/test_io.cpp
@@ -0,0 +1,7148 @@
+/******************************************************************************
+ *
+ * Project: PROJ
+ * Purpose: Test ISO19111:2018 implementation
+ * Author: Even Rouault <even dot rouault at spatialys dot com>
+ *
+ ******************************************************************************
+ * Copyright (c) 2018, Even Rouault <even dot rouault at spatialys dot 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.
+ ****************************************************************************/
+
+#include "gtest_include.h"
+
+// to be able to use internal::toString
+#define FROM_PROJ_CPP
+
+#include "proj/common.hpp"
+#include "proj/coordinateoperation.hpp"
+#include "proj/coordinatesystem.hpp"
+#include "proj/crs.hpp"
+#include "proj/datum.hpp"
+#include "proj/io.hpp"
+#include "proj/metadata.hpp"
+#include "proj/util.hpp"
+
+#include "proj/internal/internal.hpp"
+
+#include <string>
+
+using namespace osgeo::proj::common;
+using namespace osgeo::proj::crs;
+using namespace osgeo::proj::cs;
+using namespace osgeo::proj::datum;
+using namespace osgeo::proj::internal;
+using namespace osgeo::proj::io;
+using namespace osgeo::proj::metadata;
+using namespace osgeo::proj::operation;
+using namespace osgeo::proj::util;
+
+// ---------------------------------------------------------------------------
+
+TEST(io, wkt_parsing) {
+ {
+ auto n = WKTNode::createFrom("MYNODE[]");
+ EXPECT_EQ(n->value(), "MYNODE");
+ EXPECT_TRUE(n->children().empty());
+ EXPECT_EQ(n->toString(), "MYNODE");
+ }
+ {
+ auto n = WKTNode::createFrom(" MYNODE [ ] ");
+ EXPECT_EQ(n->value(), "MYNODE");
+ EXPECT_TRUE(n->children().empty());
+ }
+ EXPECT_THROW(WKTNode::createFrom(""), ParsingException);
+ EXPECT_THROW(WKTNode::createFrom("x"), ParsingException);
+ EXPECT_THROW(WKTNode::createFrom("x,"), ParsingException);
+ EXPECT_THROW(WKTNode::createFrom("x["), ParsingException);
+ EXPECT_THROW(WKTNode::createFrom("["), ParsingException);
+
+ {
+ auto n = WKTNode::createFrom("MYNODE[\"x\"]");
+ EXPECT_EQ(n->value(), "MYNODE");
+ ASSERT_EQ(n->children().size(), 1);
+ EXPECT_EQ(n->children()[0]->value(), "\"x\"");
+ EXPECT_EQ(n->toString(), "MYNODE[\"x\"]");
+ }
+
+ EXPECT_THROW(WKTNode::createFrom("MYNODE[\"x\""), ParsingException);
+
+ {
+ auto n = WKTNode::createFrom("MYNODE[ \"x\" ]");
+ EXPECT_EQ(n->value(), "MYNODE");
+ ASSERT_EQ(n->children().size(), 1);
+ EXPECT_EQ(n->children()[0]->value(), "\"x\"");
+ }
+
+ {
+ auto n = WKTNode::createFrom("MYNODE[\"x[\",1]");
+ EXPECT_EQ(n->value(), "MYNODE");
+ ASSERT_EQ(n->children().size(), 2);
+ EXPECT_EQ(n->children()[0]->value(), "\"x[\"");
+ EXPECT_EQ(n->children()[1]->value(), "1");
+ EXPECT_EQ(n->toString(), "MYNODE[\"x[\",1]");
+ }
+
+ EXPECT_THROW(WKTNode::createFrom("MYNODE[\"x\","), ParsingException);
+
+ {
+ auto n = WKTNode::createFrom("A[B[y]]");
+ EXPECT_EQ(n->value(), "A");
+ ASSERT_EQ(n->children().size(), 1);
+ EXPECT_EQ(n->children()[0]->value(), "B");
+ ASSERT_EQ(n->children()[0]->children().size(), 1);
+ EXPECT_EQ(n->children()[0]->children()[0]->value(), "y");
+ EXPECT_EQ(n->toString(), "A[B[y]]");
+ }
+
+ EXPECT_THROW(WKTNode::createFrom("A[B["), ParsingException);
+
+ std::string str;
+ for (int i = 0; i < 17; i++) {
+ str = "A[" + str + "]";
+ }
+ EXPECT_THROW(WKTNode::createFrom(str), ParsingException);
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(io, wkt_parsing_with_parenthesis) {
+
+ auto n = WKTNode::createFrom("A(\"x\",B(\"y\"))");
+ EXPECT_EQ(n->toString(), "A[\"x\",B[\"y\"]]");
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(io, wkt_parsing_with_double_quotes_inside) {
+
+ auto n = WKTNode::createFrom("A[\"xy\"\"z\"]");
+ EXPECT_EQ(n->children()[0]->value(), "\"xy\"z\"");
+ EXPECT_EQ(n->toString(), "A[\"xy\"\"z\"]");
+
+ EXPECT_THROW(WKTNode::createFrom("A[\"x\""), ParsingException);
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(io, wkt_parsing_with_printed_quotes) {
+ static const std::string startPrintedQuote("\xE2\x80\x9C");
+ static const std::string endPrintedQuote("\xE2\x80\x9D");
+
+ auto n = WKTNode::createFrom("A[" + startPrintedQuote + "x" +
+ endPrintedQuote + "]");
+ EXPECT_EQ(n->children()[0]->value(), "\"x\"");
+ EXPECT_EQ(n->toString(), "A[\"x\"]");
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(wkt_parse, sphere) {
+ auto obj = WKTParser().createFromWKT(
+ "ELLIPSOID[\"Sphere\",6378137,0,LENGTHUNIT[\"metre\",1]]");
+ auto ellipsoid = nn_dynamic_pointer_cast<Ellipsoid>(obj);
+ ASSERT_TRUE(ellipsoid != nullptr);
+ EXPECT_TRUE(ellipsoid->isSphere());
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(wkt_parse, datum_with_ANCHOR) {
+ auto obj = WKTParser().createFromWKT(
+ "DATUM[\"WGS_1984 with anchor\",\n"
+ " ELLIPSOID[\"WGS 84\",6378137,298.257223563,\n"
+ " LENGTHUNIT[\"metre\",1,\n"
+ " ID[\"EPSG\",9001]],\n"
+ " ID[\"EPSG\",7030]],\n"
+ " ANCHOR[\"My anchor\"]]");
+ auto datum = nn_dynamic_pointer_cast<GeodeticReferenceFrame>(obj);
+ ASSERT_TRUE(datum != nullptr);
+ EXPECT_EQ(datum->ellipsoid()->celestialBody(), "Earth");
+ EXPECT_EQ(datum->primeMeridian()->nameStr(), "Greenwich");
+ auto anchor = datum->anchorDefinition();
+ EXPECT_TRUE(anchor.has_value());
+ EXPECT_EQ(*anchor, "My anchor");
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(wkt_parse, datum_no_pm_not_earth) {
+ auto obj = WKTParser().createFromWKT("DATUM[\"unnamed\",\n"
+ " ELLIPSOID[\"unnamed\",1,0,\n"
+ " LENGTHUNIT[\"metre\",1]]]");
+ auto datum = nn_dynamic_pointer_cast<GeodeticReferenceFrame>(obj);
+ ASSERT_TRUE(datum != nullptr);
+ EXPECT_EQ(datum->ellipsoid()->celestialBody(), "Non-Earth body");
+ EXPECT_EQ(datum->primeMeridian()->nameStr(), "Reference meridian");
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(wkt_parse, dynamic_geodetic_reference_frame) {
+ auto obj = WKTParser().createFromWKT(
+ "GEOGCRS[\"WGS 84 (G1762)\","
+ "DYNAMIC[FRAMEEPOCH[2005.0]],"
+ "TRF[\"World Geodetic System 1984 (G1762)\","
+ " ELLIPSOID[\"WGS 84\",6378137,298.257223563],"
+ " ANCHOR[\"My anchor\"]],"
+ "CS[ellipsoidal,3],"
+ " AXIS[\"(lat)\",north,ANGLEUNIT[\"degree\",0.0174532925199433]],"
+ " AXIS[\"(lon)\",east,ANGLEUNIT[\"degree\",0.0174532925199433]],"
+ " AXIS[\"ellipsoidal height (h)\",up,LENGTHUNIT[\"metre\",1.0]]"
+ "]");
+ auto crs = nn_dynamic_pointer_cast<GeodeticCRS>(obj);
+ ASSERT_TRUE(crs != nullptr);
+ auto dgrf =
+ std::dynamic_pointer_cast<DynamicGeodeticReferenceFrame>(crs->datum());
+ ASSERT_TRUE(dgrf != nullptr);
+ auto anchor = dgrf->anchorDefinition();
+ EXPECT_TRUE(anchor.has_value());
+ EXPECT_EQ(*anchor, "My anchor");
+ EXPECT_TRUE(dgrf->frameReferenceEpoch() ==
+ Measure(2005.0, UnitOfMeasure::YEAR));
+ auto model = dgrf->deformationModelName();
+ EXPECT_TRUE(!model.has_value());
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(wkt_parse, dynamic_geodetic_reference_frame_with_model) {
+ auto obj = WKTParser().createFromWKT(
+ "GEOGCRS[\"WGS 84 (G1762)\","
+ "DYNAMIC[FRAMEEPOCH[2005.0],MODEL[\"my_model\"]],"
+ "TRF[\"World Geodetic System 1984 (G1762)\","
+ " ELLIPSOID[\"WGS 84\",6378137,298.257223563],"
+ " ANCHOR[\"My anchor\"]],"
+ "CS[ellipsoidal,3],"
+ " AXIS[\"(lat)\",north,ANGLEUNIT[\"degree\",0.0174532925199433]],"
+ " AXIS[\"(lon)\",east,ANGLEUNIT[\"degree\",0.0174532925199433]],"
+ " AXIS[\"ellipsoidal height (h)\",up,LENGTHUNIT[\"metre\",1.0]]"
+ "]");
+ auto crs = nn_dynamic_pointer_cast<GeodeticCRS>(obj);
+ ASSERT_TRUE(crs != nullptr);
+ auto dgrf =
+ std::dynamic_pointer_cast<DynamicGeodeticReferenceFrame>(crs->datum());
+ ASSERT_TRUE(dgrf != nullptr);
+ auto anchor = dgrf->anchorDefinition();
+ EXPECT_TRUE(anchor.has_value());
+ EXPECT_EQ(*anchor, "My anchor");
+ EXPECT_TRUE(dgrf->frameReferenceEpoch() ==
+ Measure(2005.0, UnitOfMeasure::YEAR));
+ auto model = dgrf->deformationModelName();
+ EXPECT_TRUE(model.has_value());
+ EXPECT_EQ(*model, "my_model");
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(wkt_parse, dynamic_geodetic_reference_frame_with_velocitygrid) {
+ auto obj = WKTParser().createFromWKT(
+ "GEOGCRS[\"WGS 84 (G1762)\","
+ "DYNAMIC[FRAMEEPOCH[2005.0],VELOCITYGRID[\"my_model\"]],"
+ "TRF[\"World Geodetic System 1984 (G1762)\","
+ " ELLIPSOID[\"WGS 84\",6378137,298.257223563],"
+ " ANCHOR[\"My anchor\"]],"
+ "CS[ellipsoidal,3],"
+ " AXIS[\"(lat)\",north,ANGLEUNIT[\"degree\",0.0174532925199433]],"
+ " AXIS[\"(lon)\",east,ANGLEUNIT[\"degree\",0.0174532925199433]],"
+ " AXIS[\"ellipsoidal height (h)\",up,LENGTHUNIT[\"metre\",1.0]]"
+ "]");
+ auto crs = nn_dynamic_pointer_cast<GeodeticCRS>(obj);
+ ASSERT_TRUE(crs != nullptr);
+ auto dgrf =
+ std::dynamic_pointer_cast<DynamicGeodeticReferenceFrame>(crs->datum());
+ ASSERT_TRUE(dgrf != nullptr);
+ auto anchor = dgrf->anchorDefinition();
+ EXPECT_TRUE(anchor.has_value());
+ EXPECT_EQ(*anchor, "My anchor");
+ EXPECT_TRUE(dgrf->frameReferenceEpoch() ==
+ Measure(2005.0, UnitOfMeasure::YEAR));
+ auto model = dgrf->deformationModelName();
+ EXPECT_TRUE(model.has_value());
+ EXPECT_EQ(*model, "my_model");
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(wkt_parse, geogcrs_with_ensemble) {
+ auto obj = WKTParser().createFromWKT(
+ "GEOGCRS[\"WGS 84\","
+ "ENSEMBLE[\"WGS 84 ensemble\","
+ " MEMBER[\"WGS 84 (TRANSIT)\"],"
+ " MEMBER[\"WGS 84 (G730)\"],"
+ " MEMBER[\"WGS 84 (G834)\"],"
+ " MEMBER[\"WGS 84 (G1150)\"],"
+ " MEMBER[\"WGS 84 (G1674)\"],"
+ " MEMBER[\"WGS 84 (G1762)\"],"
+ " ELLIPSOID[\"WGS "
+ "84\",6378137,298.2572236,LENGTHUNIT[\"metre\",1.0]],"
+ " ENSEMBLEACCURACY[2]"
+ "],"
+ "CS[ellipsoidal,3],"
+ " AXIS[\"(lat)\",north,ANGLEUNIT[\"degree\",0.0174532925199433]],"
+ " AXIS[\"(lon)\",east,ANGLEUNIT[\"degree\",0.0174532925199433]],"
+ " AXIS[\"ellipsoidal height (h)\",up,LENGTHUNIT[\"metre\",1.0]]"
+ "]");
+ auto crs = nn_dynamic_pointer_cast<GeodeticCRS>(obj);
+ ASSERT_TRUE(crs != nullptr);
+ ASSERT_TRUE(crs->datum() == nullptr);
+ ASSERT_TRUE(crs->datumEnsemble() != nullptr);
+ EXPECT_EQ(crs->datumEnsemble()->datums().size(), 6);
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(wkt_parse, invalid_geogcrs_with_ensemble) {
+ auto wkt =
+ "GEOGCRS[\"WGS 84\","
+ "ENSEMBLE[\"WGS 84 ensemble\","
+ " MEMBER[\"WGS 84 (TRANSIT)\"],"
+ " MEMBER[\"WGS 84 (G730)\"],"
+ " MEMBER[\"WGS 84 (G834)\"],"
+ " MEMBER[\"WGS 84 (G1150)\"],"
+ " MEMBER[\"WGS 84 (G1674)\"],"
+ " MEMBER[\"WGS 84 (G1762)\"],"
+ " ENSEMBLEACCURACY[2]"
+ "],"
+ "CS[ellipsoidal,3],"
+ " AXIS[\"(lat)\",north,ANGLEUNIT[\"degree\",0.0174532925199433]],"
+ " AXIS[\"(lon)\",east,ANGLEUNIT[\"degree\",0.0174532925199433]],"
+ " AXIS[\"ellipsoidal height (h)\",up,LENGTHUNIT[\"metre\",1.0]]"
+ "]";
+ EXPECT_THROW(WKTParser().createFromWKT(wkt), ParsingException);
+}
+
+// ---------------------------------------------------------------------------
+
+static void checkEPSG_4326(GeographicCRSPtr crs, bool latLong = true,
+ bool checkEPSGCodes = true) {
+ if (checkEPSGCodes) {
+ ASSERT_EQ(crs->identifiers().size(), 1);
+ EXPECT_EQ(crs->identifiers()[0]->code(), "4326");
+ EXPECT_EQ(*(crs->identifiers()[0]->codeSpace()), "EPSG");
+ }
+ EXPECT_EQ(crs->nameStr(), "WGS 84");
+
+ auto cs = crs->coordinateSystem();
+ ASSERT_EQ(cs->axisList().size(), 2);
+ if (latLong) {
+ EXPECT_EQ(cs->axisList()[0]->nameStr(), "Latitude");
+ EXPECT_EQ(cs->axisList()[0]->abbreviation(), "lat");
+ EXPECT_EQ(cs->axisList()[0]->direction(), AxisDirection::NORTH);
+
+ EXPECT_EQ(cs->axisList()[1]->nameStr(), "Longitude");
+ EXPECT_EQ(cs->axisList()[1]->abbreviation(), "lon");
+ EXPECT_EQ(cs->axisList()[1]->direction(), AxisDirection::EAST);
+ } else {
+ EXPECT_EQ(cs->axisList()[0]->nameStr(), "Longitude");
+ EXPECT_EQ(cs->axisList()[0]->abbreviation(), "lon");
+ EXPECT_EQ(cs->axisList()[0]->direction(), AxisDirection::EAST);
+
+ EXPECT_EQ(cs->axisList()[1]->nameStr(), "Latitude");
+ EXPECT_EQ(cs->axisList()[1]->abbreviation(), "lat");
+ EXPECT_EQ(cs->axisList()[1]->direction(), AxisDirection::NORTH);
+ }
+
+ auto datum = crs->datum();
+ if (checkEPSGCodes) {
+ ASSERT_EQ(datum->identifiers().size(), 1);
+ EXPECT_EQ(datum->identifiers()[0]->code(), "6326");
+ EXPECT_EQ(*(datum->identifiers()[0]->codeSpace()), "EPSG");
+ }
+ EXPECT_EQ(datum->nameStr(), "World Geodetic System 1984");
+
+ auto ellipsoid = datum->ellipsoid();
+ EXPECT_EQ(ellipsoid->semiMajorAxis().value(), 6378137.0);
+ EXPECT_EQ(ellipsoid->semiMajorAxis().unit(), UnitOfMeasure::METRE);
+ EXPECT_EQ(ellipsoid->inverseFlattening()->value(), 298.257223563);
+ if (checkEPSGCodes) {
+ ASSERT_EQ(ellipsoid->identifiers().size(), 1);
+ EXPECT_EQ(ellipsoid->identifiers()[0]->code(), "7030");
+ EXPECT_EQ(*(ellipsoid->identifiers()[0]->codeSpace()), "EPSG");
+ }
+ EXPECT_EQ(ellipsoid->nameStr(), "WGS 84");
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(wkt_parse, wkt1_EPSG_4326) {
+ auto obj = WKTParser().createFromWKT(
+ "GEOGCS[\"WGS 84\","
+ " DATUM[\"WGS_1984\","
+ " SPHEROID[\"WGS 84\",6378137,298.257223563,"
+ " AUTHORITY[\"EPSG\",\"7030\"]],"
+ " AUTHORITY[\"EPSG\",\"6326\"]],"
+ " PRIMEM[\"Greenwich\",0,"
+ " AUTHORITY[\"EPSG\",\"8901\"]],"
+ " UNIT[\"degree\",0.0174532925199433,"
+ " AUTHORITY[\"EPSG\",\"9122\"]],"
+ " AUTHORITY[\"EPSG\",\"4326\"]]");
+ auto crs = nn_dynamic_pointer_cast<GeographicCRS>(obj);
+ ASSERT_TRUE(crs != nullptr);
+ checkEPSG_4326(crs, false /* longlat order */);
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(wkt_parse, wkt1_EPSG_4807_grad_mess) {
+ auto obj = WKTParser().createFromWKT(
+ "GEOGCS[\"NTF (Paris)\",\n"
+ " DATUM[\"Nouvelle_Triangulation_Francaise_Paris\",\n"
+ " SPHEROID[\"Clarke 1880 (IGN)\",6378249.2,293.466021293627,\n"
+ " AUTHORITY[\"EPSG\",\"6807\"]],\n"
+ " AUTHORITY[\"EPSG\",\"6807\"]],\n"
+ /* WKT1_GDAL weirdness: PRIMEM is converted to degree */
+ " PRIMEM[\"Paris\",2.33722917,\n"
+ " AUTHORITY[\"EPSG\",\"8903\"]],\n"
+ " UNIT[\"grad\",0.015707963267949,\n"
+ " AUTHORITY[\"EPSG\",9105]],\n"
+ " AXIS[\"latitude\",NORTH],\n"
+ " AXIS[\"longitude\",EAST],\n"
+ " AUTHORITY[\"EPSG\",\"4807\"]]");
+ auto crs = nn_dynamic_pointer_cast<GeographicCRS>(obj);
+ ASSERT_TRUE(crs != nullptr);
+
+ auto datum = crs->datum();
+ auto primem = datum->primeMeridian();
+ EXPECT_EQ(primem->longitude().unit(), UnitOfMeasure::GRAD);
+ // Check that we have corrected the value that was in degree into grad.
+ EXPECT_EQ(primem->longitude().value(), 2.5969213);
+}
+
+// ---------------------------------------------------------------------------
+
+static std::string contentWKT2_EPSG_4326(
+ "[\"WGS 84\",\n"
+ " DATUM[\"World Geodetic System 1984\",\n"
+ " ELLIPSOID[\"WGS 84\",6378137,298.257223563,\n"
+ " LENGTHUNIT[\"metre\",1,\n"
+ " ID[\"EPSG\",9001]],\n"
+ " ID[\"EPSG\",7030]],\n"
+ " ID[\"EPSG\",6326]],\n"
+ " PRIMEM[\"Greenwich\",0,\n"
+ " ANGLEUNIT[\"degree\",0.0174532925199433,\n"
+ " ID[\"EPSG\",9122]],\n"
+ " ID[\"EPSG\",8901]],\n"
+ " CS[ellipsoidal,2],\n"
+ " AXIS[\"latitude\",north,\n"
+ " ORDER[1],\n"
+ " ANGLEUNIT[\"degree\",0.0174532925199433,\n"
+ " ID[\"EPSG\",9122]]],\n"
+ " AXIS[\"longitude\",east,\n"
+ " ORDER[2],\n"
+ " ANGLEUNIT[\"degree\",0.0174532925199433,\n"
+ " ID[\"EPSG\",9122]]],\n"
+ " ID[\"EPSG\",4326]]");
+
+// ---------------------------------------------------------------------------
+
+TEST(wkt_parse, wkt2_GEODCRS_EPSG_4326) {
+ auto obj = WKTParser().createFromWKT("GEODCRS" + contentWKT2_EPSG_4326);
+ auto crs = nn_dynamic_pointer_cast<GeographicCRS>(obj);
+ ASSERT_TRUE(crs != nullptr);
+ checkEPSG_4326(crs);
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(wkt_parse, wkt2_long_GEODETICCRS_EPSG_4326) {
+ auto obj = WKTParser().createFromWKT("GEODETICCRS" + contentWKT2_EPSG_4326);
+ auto crs = nn_dynamic_pointer_cast<GeographicCRS>(obj);
+ ASSERT_TRUE(crs != nullptr);
+ checkEPSG_4326(crs);
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(wkt_parse, wkt2_2018_GEOGCRS_EPSG_4326) {
+ auto obj = WKTParser().createFromWKT("GEOGCRS" + contentWKT2_EPSG_4326);
+ auto crs = nn_dynamic_pointer_cast<GeographicCRS>(obj);
+ ASSERT_TRUE(crs != nullptr);
+ checkEPSG_4326(crs);
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(wkt_parse, wkt2_2018_long_GEOGRAPHICCRS_EPSG_4326) {
+ auto obj =
+ WKTParser().createFromWKT("GEOGRAPHICCRS" + contentWKT2_EPSG_4326);
+ auto crs = nn_dynamic_pointer_cast<GeographicCRS>(obj);
+ ASSERT_TRUE(crs != nullptr);
+ checkEPSG_4326(crs);
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(wkt_parse, wkt2_simplified_EPSG_4326) {
+ auto obj = WKTParser().createFromWKT(
+ "GEODCRS[\"WGS 84\",\n"
+ " DATUM[\"World Geodetic System 1984\",\n"
+ " ELLIPSOID[\"WGS 84\",6378137,298.257223563]],\n"
+ " CS[ellipsoidal,2],\n"
+ " AXIS[\"latitude (lat)\",north],\n" // test "name
+ // (abbreviation)"
+ " AXIS[\"longitude (lon)\",east],\n"
+ " UNIT[\"degree\",0.0174532925199433],\n"
+ " ID[\"EPSG\",4326]]");
+ auto crs = nn_dynamic_pointer_cast<GeographicCRS>(obj);
+ ASSERT_TRUE(crs != nullptr);
+ checkEPSG_4326(crs, true /* latlong */, false /* no EPSG codes */);
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(wkt_parse, wkt2_GEODETICDATUM) {
+ auto obj = WKTParser().createFromWKT(
+ "GEODCRS[\"WGS 84\",\n"
+ " GEODETICDATUM[\"World Geodetic System 1984\",\n"
+ " ELLIPSOID[\"WGS 84\",6378137,298.257223563]],\n"
+ " CS[ellipsoidal,2],\n"
+ " AXIS[\"(lat)\",north],\n" // test "(abbreviation)"
+ " AXIS[\"(lon)\",east],\n"
+ " UNIT[\"degree\",0.0174532925199433],\n"
+ " ID[\"EPSG\",4326]]");
+ auto crs = nn_dynamic_pointer_cast<GeographicCRS>(obj);
+ ASSERT_TRUE(crs != nullptr);
+ checkEPSG_4326(crs, true /* latlong */, false /* no EPSG codes */);
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(wkt_parse, wkt2_TRF) {
+ auto obj = WKTParser().createFromWKT(
+ "GEODCRS[\"WGS 84\",\n"
+ " TRF[\"World Geodetic System 1984\",\n"
+ " ELLIPSOID[\"WGS 84\",6378137,298.257223563]],\n"
+ " CS[ellipsoidal,2],\n"
+ " AXIS[\"latitude\",north],\n"
+ " AXIS[\"longitude\",east],\n"
+ " UNIT[\"degree\",0.0174532925199433],\n"
+ " ID[\"EPSG\",4326]]");
+ auto crs = nn_dynamic_pointer_cast<GeographicCRS>(obj);
+ ASSERT_TRUE(crs != nullptr);
+ checkEPSG_4326(crs, true /* latlong */, false /* no EPSG codes */);
+}
+
+// ---------------------------------------------------------------------------
+
+static void checkEPSG_4979(GeographicCRSPtr crs) {
+ ASSERT_EQ(crs->identifiers().size(), 1);
+ EXPECT_EQ(crs->identifiers()[0]->code(), "4979");
+ EXPECT_EQ(*(crs->identifiers()[0]->codeSpace()), "EPSG");
+ EXPECT_EQ(crs->nameStr(), "WGS 84");
+
+ auto cs = crs->coordinateSystem();
+ ASSERT_EQ(cs->axisList().size(), 3);
+ EXPECT_EQ(cs->axisList()[0]->nameStr(), "Latitude");
+ EXPECT_EQ(cs->axisList()[0]->abbreviation(), "lat");
+ EXPECT_EQ(cs->axisList()[0]->direction(), AxisDirection::NORTH);
+
+ EXPECT_EQ(cs->axisList()[1]->nameStr(), "Longitude");
+ EXPECT_EQ(cs->axisList()[1]->abbreviation(), "lon");
+ EXPECT_EQ(cs->axisList()[1]->direction(), AxisDirection::EAST);
+
+ EXPECT_EQ(cs->axisList()[2]->nameStr(), "Ellipsoidal height");
+ EXPECT_EQ(cs->axisList()[2]->abbreviation(), "h");
+ EXPECT_EQ(cs->axisList()[2]->direction(), AxisDirection::UP);
+
+ auto datum = crs->datum();
+ EXPECT_EQ(datum->nameStr(), "World Geodetic System 1984");
+
+ auto ellipsoid = datum->ellipsoid();
+ EXPECT_EQ(ellipsoid->semiMajorAxis().value(), 6378137.0);
+ EXPECT_EQ(ellipsoid->semiMajorAxis().unit(), UnitOfMeasure::METRE);
+ EXPECT_EQ(ellipsoid->inverseFlattening()->value(), 298.257223563);
+ EXPECT_EQ(ellipsoid->nameStr(), "WGS 84");
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(wkt_parse, wkt2_EPSG_4979) {
+ auto obj = WKTParser().createFromWKT(
+ "GEODCRS[\"WGS 84\",\n"
+ " DATUM[\"World Geodetic System 1984\",\n"
+ " ELLIPSOID[\"WGS 84\",6378137,298.257223563]],\n"
+ " CS[ellipsoidal,3],\n"
+ " AXIS[\"latitude\",north,\n"
+ " UNIT[\"degree\",0.0174532925199433]],\n"
+ " AXIS[\"longitude\",east,\n"
+ " UNIT[\"degree\",0.0174532925199433]],\n"
+ " AXIS[\"ellipsoidal height\",up,\n"
+ " UNIT[\"metre\",1]],\n"
+ " ID[\"EPSG\",4979]]");
+ auto crs = nn_dynamic_pointer_cast<GeographicCRS>(obj);
+ ASSERT_TRUE(crs != nullptr);
+ checkEPSG_4979(crs);
+}
+
+// ---------------------------------------------------------------------------
+
+static void checkGeocentric(GeodeticCRSPtr crs) {
+ // Explicitly check this is NOT a GeographicCRS
+ EXPECT_TRUE(!dynamic_cast<GeographicCRS *>(crs.get()));
+
+ EXPECT_EQ(crs->nameStr(), "WGS 84 (geocentric)");
+
+ EXPECT_TRUE(crs->isGeocentric());
+ auto cs = crs->coordinateSystem();
+ ASSERT_EQ(cs->axisList().size(), 3);
+
+ EXPECT_EQ(cs->axisList()[0]->nameStr(), "Geocentric X");
+ EXPECT_EQ(cs->axisList()[0]->abbreviation(), "X");
+ EXPECT_EQ(cs->axisList()[0]->direction(), AxisDirection::GEOCENTRIC_X);
+
+ EXPECT_EQ(cs->axisList()[1]->nameStr(), "Geocentric Y");
+ EXPECT_EQ(cs->axisList()[1]->abbreviation(), "Y");
+ EXPECT_EQ(cs->axisList()[1]->direction(), AxisDirection::GEOCENTRIC_Y);
+
+ EXPECT_EQ(cs->axisList()[2]->nameStr(), "Geocentric Z");
+ EXPECT_EQ(cs->axisList()[2]->abbreviation(), "Z");
+ EXPECT_EQ(cs->axisList()[2]->direction(), AxisDirection::GEOCENTRIC_Z);
+
+ auto datum = crs->datum();
+ EXPECT_EQ(datum->nameStr(), "World Geodetic System 1984");
+
+ auto ellipsoid = datum->ellipsoid();
+ EXPECT_EQ(ellipsoid->semiMajorAxis().value(), 6378137.0);
+ EXPECT_EQ(ellipsoid->semiMajorAxis().unit(), UnitOfMeasure::METRE);
+ EXPECT_EQ(ellipsoid->inverseFlattening()->value(), 298.257223563);
+ EXPECT_EQ(ellipsoid->nameStr(), "WGS 84");
+
+ auto primem = datum->primeMeridian();
+ ASSERT_EQ(primem->longitude().unit(), UnitOfMeasure::DEGREE);
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(wkt_parse, wkt2_geocentric) {
+ auto wkt = "GEODCRS[\"WGS 84 (geocentric)\",\n"
+ " DATUM[\"World Geodetic System 1984\",\n"
+ " ELLIPSOID[\"WGS 84\",6378137,298.257223563,\n"
+ " LENGTHUNIT[\"metre\",1,\n"
+ " ID[\"EPSG\",9001]],\n"
+ " ID[\"EPSG\",7030]],\n"
+ " ID[\"EPSG\",6326]],\n"
+ " PRIMEM[\"Greenwich\",0,\n"
+ " ANGLEUNIT[\"degree\",0.0174532925199433,\n"
+ " ID[\"EPSG\",9122]],\n"
+ " ID[\"EPSG\",8901]],\n"
+ " CS[Cartesian,3],\n"
+ " AXIS[\"(X)\",geocentricX,\n"
+ " ORDER[1],\n"
+ " LENGTHUNIT[\"metre\",1,\n"
+ " ID[\"EPSG\",9001]]],\n"
+ " AXIS[\"(Y)\",geocentricY,\n"
+ " ORDER[2],\n"
+ " LENGTHUNIT[\"metre\",1,\n"
+ " ID[\"EPSG\",9001]]],\n"
+ " AXIS[\"(Z)\",geocentricZ,\n"
+ " ORDER[3],\n"
+ " LENGTHUNIT[\"metre\",1,\n"
+ " ID[\"EPSG\",9001]]],\n"
+ " ID[\"EPSG\",4328]]";
+ auto obj = WKTParser().createFromWKT(wkt);
+ auto crs = nn_dynamic_pointer_cast<GeodeticCRS>(obj);
+ ASSERT_TRUE(crs != nullptr);
+ checkGeocentric(crs);
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(wkt_parse, wkt2_simplified_geocentric) {
+ auto wkt = "GEODCRS[\"WGS 84 (geocentric)\",\n"
+ " DATUM[\"World Geodetic System 1984\",\n"
+ " ELLIPSOID[\"WGS 84\",6378137,298.257223563]],\n"
+ " CS[Cartesian,3],\n"
+ " AXIS[\"(X)\",geocentricX],\n"
+ " AXIS[\"(Y)\",geocentricY],\n"
+ " AXIS[\"(Z)\",geocentricZ],\n"
+ " UNIT[\"metre\",1],\n"
+ " ID[\"EPSG\",4328]]";
+ auto obj = WKTParser().createFromWKT(wkt);
+ auto crs = nn_dynamic_pointer_cast<GeodeticCRS>(obj);
+ ASSERT_TRUE(crs != nullptr);
+ checkGeocentric(crs);
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(wkt_parse, wkt1_geocentric) {
+ auto wkt = "GEOCCS[\"WGS 84 (geocentric)\",\n"
+ " DATUM[\"WGS_1984\",\n"
+ " SPHEROID[\"WGS 84\",6378137,298.257223563,\n"
+ " AUTHORITY[\"EPSG\",\"7030\"]],\n"
+ " AUTHORITY[\"EPSG\",\"6326\"]],\n"
+ " PRIMEM[\"Greenwich\",0,\n"
+ " AUTHORITY[\"EPSG\",\"8901\"]],\n"
+ " UNIT[\"metre\",1,\n"
+ " AUTHORITY[\"EPSG\",9001]],\n"
+ " AXIS[\"Geocentric X\",OTHER],\n"
+ " AXIS[\"Geocentric Y\",OTHER],\n"
+ " AXIS[\"Geocentric Z\",NORTH],\n"
+ " AUTHORITY[\"EPSG\",\"4328\"]]";
+ auto obj = WKTParser().createFromWKT(wkt);
+ auto crs = nn_dynamic_pointer_cast<GeodeticCRS>(obj);
+ ASSERT_TRUE(crs != nullptr);
+ checkGeocentric(crs);
+}
+
+// ---------------------------------------------------------------------------
+
+static void checkProjected(ProjectedCRSPtr crs, bool checkEPSGCodes = true) {
+ EXPECT_EQ(crs->nameStr(), "WGS 84 / UTM zone 31N");
+ ASSERT_EQ(crs->identifiers().size(), 1);
+ EXPECT_EQ(crs->identifiers()[0]->code(), "32631");
+ EXPECT_EQ(*(crs->identifiers()[0]->codeSpace()), "EPSG");
+
+ auto geogCRS = nn_dynamic_pointer_cast<GeographicCRS>(crs->baseCRS());
+ ASSERT_TRUE(geogCRS != nullptr);
+ checkEPSG_4326(NN_CHECK_ASSERT(geogCRS), true, checkEPSGCodes);
+
+ auto conversion = crs->derivingConversion();
+ EXPECT_EQ(conversion->nameStr(), "UTM zone 31N");
+ auto method = conversion->method();
+ EXPECT_EQ(method->nameStr(), "Transverse Mercator");
+ auto values = conversion->parameterValues();
+ ASSERT_EQ(values.size(), 5);
+ {
+ const auto &opParamvalue =
+ nn_dynamic_pointer_cast<OperationParameterValue>(values[0]);
+ ASSERT_TRUE(opParamvalue);
+ const auto &paramName = opParamvalue->parameter()->nameStr();
+ const auto &parameterValue = opParamvalue->parameterValue();
+ EXPECT_EQ(paramName, "Latitude of natural origin");
+ EXPECT_EQ(parameterValue->type(), ParameterValue::Type::MEASURE);
+ auto measure = parameterValue->value();
+ EXPECT_EQ(measure.unit(), UnitOfMeasure::DEGREE);
+ EXPECT_EQ(measure.value(), 0);
+ }
+ {
+ const auto &opParamvalue =
+ nn_dynamic_pointer_cast<OperationParameterValue>(values[1]);
+ ASSERT_TRUE(opParamvalue);
+ const auto &paramName = opParamvalue->parameter()->nameStr();
+ const auto &parameterValue = opParamvalue->parameterValue();
+ EXPECT_EQ(paramName, "Longitude of natural origin");
+ EXPECT_EQ(parameterValue->type(), ParameterValue::Type::MEASURE);
+ auto measure = parameterValue->value();
+ EXPECT_EQ(measure.unit(), UnitOfMeasure::DEGREE);
+ EXPECT_EQ(measure.value(), 3);
+ }
+ {
+ const auto &opParamvalue =
+ nn_dynamic_pointer_cast<OperationParameterValue>(values[2]);
+ ASSERT_TRUE(opParamvalue);
+ const auto &paramName = opParamvalue->parameter()->nameStr();
+ const auto &parameterValue = opParamvalue->parameterValue();
+ EXPECT_EQ(paramName, "Scale factor at natural origin");
+ EXPECT_EQ(parameterValue->type(), ParameterValue::Type::MEASURE);
+ auto measure = parameterValue->value();
+ EXPECT_EQ(measure.unit(), UnitOfMeasure::SCALE_UNITY);
+ EXPECT_EQ(measure.value(), 0.9996);
+ }
+ {
+ const auto &opParamvalue =
+ nn_dynamic_pointer_cast<OperationParameterValue>(values[3]);
+ ASSERT_TRUE(opParamvalue);
+ const auto &paramName = opParamvalue->parameter()->nameStr();
+ const auto &parameterValue = opParamvalue->parameterValue();
+ EXPECT_EQ(paramName, "False easting");
+ EXPECT_EQ(parameterValue->type(), ParameterValue::Type::MEASURE);
+ auto measure = parameterValue->value();
+ EXPECT_EQ(measure.unit(), UnitOfMeasure::METRE);
+ EXPECT_EQ(measure.value(), 500000);
+ }
+ {
+ const auto &opParamvalue =
+ nn_dynamic_pointer_cast<OperationParameterValue>(values[4]);
+ ASSERT_TRUE(opParamvalue);
+ const auto &paramName = opParamvalue->parameter()->nameStr();
+ const auto &parameterValue = opParamvalue->parameterValue();
+ EXPECT_EQ(paramName, "False northing");
+ EXPECT_EQ(parameterValue->type(), ParameterValue::Type::MEASURE);
+ auto measure = parameterValue->value();
+ EXPECT_EQ(measure.unit(), UnitOfMeasure::METRE);
+ EXPECT_EQ(measure.value(), 0);
+ }
+
+ auto cs = crs->coordinateSystem();
+ ASSERT_EQ(cs->axisList().size(), 2);
+
+ EXPECT_EQ(cs->axisList()[0]->nameStr(), "Easting");
+ EXPECT_EQ(cs->axisList()[0]->abbreviation(), "E");
+ EXPECT_EQ(cs->axisList()[0]->direction(), AxisDirection::EAST);
+
+ EXPECT_EQ(cs->axisList()[1]->nameStr(), "Northing");
+ EXPECT_EQ(cs->axisList()[1]->abbreviation(), "N");
+ EXPECT_EQ(cs->axisList()[1]->direction(), AxisDirection::NORTH);
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(wkt_parse, wkt1_projected) {
+ auto wkt = "PROJCS[\"WGS 84 / UTM zone 31N\",\n"
+ " GEOGCS[\"WGS 84\",\n"
+ " DATUM[\"WGS_1984\",\n"
+ " SPHEROID[\"WGS 84\",6378137,298.257223563,\n"
+ " AUTHORITY[\"EPSG\",\"7030\"]],\n"
+ " AUTHORITY[\"EPSG\",\"6326\"]],\n"
+ " PRIMEM[\"Greenwich\",0,\n"
+ " AUTHORITY[\"EPSG\",\"8901\"]],\n"
+ " UNIT[\"degree\",0.0174532925199433,\n"
+ " AUTHORITY[\"EPSG\",9122]],\n"
+ " AXIS[\"latitude\",NORTH],\n"
+ " AXIS[\"longitude\",EAST],\n"
+ " AUTHORITY[\"EPSG\",\"4326\"]],\n"
+ " PROJECTION[\"Transverse_Mercator\"],\n"
+ " PARAMETER[\"latitude_of_origin\",0],\n"
+ " PARAMETER[\"central_meridian\",3],\n"
+ " PARAMETER[\"scale_factor\",0.9996],\n"
+ " PARAMETER[\"false_easting\",500000],\n"
+ " PARAMETER[\"false_northing\",0],\n"
+ " UNIT[\"metre\",1,\n"
+ " AUTHORITY[\"EPSG\",9001]],\n"
+ " AXIS[\"(E)\",EAST],\n"
+ " AXIS[\"(N)\",NORTH],\n"
+ " AUTHORITY[\"EPSG\",\"32631\"]]";
+ auto obj = WKTParser().createFromWKT(wkt);
+ auto crs = nn_dynamic_pointer_cast<ProjectedCRS>(obj);
+ ASSERT_TRUE(crs != nullptr);
+ checkProjected(crs);
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(wkt_parse, wkt1_projected_no_axis) {
+ auto wkt = "PROJCS[\"WGS 84 / UTM zone 31N\",\n"
+ " GEOGCS[\"WGS 84\",\n"
+ " DATUM[\"WGS_1984\",\n"
+ " SPHEROID[\"WGS 84\",6378137,298.257223563,\n"
+ " AUTHORITY[\"EPSG\",\"7030\"]],\n"
+ " AUTHORITY[\"EPSG\",\"6326\"]],\n"
+ " PRIMEM[\"Greenwich\",0,\n"
+ " AUTHORITY[\"EPSG\",\"8901\"]],\n"
+ " UNIT[\"degree\",0.0174532925199433,\n"
+ " AUTHORITY[\"EPSG\",9122]],\n"
+ " AXIS[\"latitude\",NORTH],\n"
+ " AXIS[\"longitude\",EAST],\n"
+ " AUTHORITY[\"EPSG\",\"4326\"]],\n"
+ " PROJECTION[\"Transverse_Mercator\"],\n"
+ " PARAMETER[\"latitude_of_origin\",0],\n"
+ " PARAMETER[\"central_meridian\",3],\n"
+ " PARAMETER[\"scale_factor\",0.9996],\n"
+ " PARAMETER[\"false_easting\",500000],\n"
+ " PARAMETER[\"false_northing\",0],\n"
+ " UNIT[\"metre\",1,\n"
+ " AUTHORITY[\"EPSG\",9001]],\n"
+ " AUTHORITY[\"EPSG\",\"32631\"]]";
+ auto obj = WKTParser().createFromWKT(wkt);
+ auto crs = nn_dynamic_pointer_cast<ProjectedCRS>(obj);
+ ASSERT_TRUE(crs != nullptr);
+ checkProjected(crs);
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(wkt_parse, wkt1_krovak_south_west) {
+ auto wkt =
+ "PROJCS[\"S-JTSK / Krovak\","
+ " GEOGCS[\"S-JTSK\","
+ " DATUM[\"System_Jednotne_Trigonometricke_Site_Katastralni\","
+ " SPHEROID[\"Bessel 1841\",6377397.155,299.1528128,"
+ " AUTHORITY[\"EPSG\",\"7004\"]],"
+ " AUTHORITY[\"EPSG\",\"6156\"]],"
+ " PRIMEM[\"Greenwich\",0,"
+ " AUTHORITY[\"EPSG\",\"8901\"]],"
+ " UNIT[\"degree\",0.0174532925199433,"
+ " AUTHORITY[\"EPSG\",\"9122\"]],"
+ " AUTHORITY[\"EPSG\",\"4156\"]],"
+ " PROJECTION[\"Krovak\"],"
+ " PARAMETER[\"latitude_of_center\",49.5],"
+ " PARAMETER[\"longitude_of_center\",24.83333333333333],"
+ " PARAMETER[\"azimuth\",30.28813972222222],"
+ " PARAMETER[\"pseudo_standard_parallel_1\",78.5],"
+ " PARAMETER[\"scale_factor\",0.9999],"
+ " PARAMETER[\"false_easting\",0],"
+ " PARAMETER[\"false_northing\",0],"
+ " UNIT[\"metre\",1,"
+ " AUTHORITY[\"EPSG\",\"9001\"]],"
+ " AXIS[\"X\",SOUTH],"
+ " AXIS[\"Y\",WEST],"
+ " AUTHORITY[\"EPSG\",\"5513\"]]";
+
+ auto obj = WKTParser().createFromWKT(wkt);
+ auto crs = nn_dynamic_pointer_cast<ProjectedCRS>(obj);
+ ASSERT_TRUE(crs != nullptr);
+
+ EXPECT_EQ(crs->derivingConversion()->method()->nameStr(), "Krovak");
+
+ auto expected_wkt2 =
+ "PROJCRS[\"S-JTSK / Krovak\",\n"
+ " BASEGEODCRS[\"S-JTSK\",\n"
+ " DATUM[\"System_Jednotne_Trigonometricke_Site_Katastralni\",\n"
+ " ELLIPSOID[\"Bessel 1841\",6377397.155,299.1528128,\n"
+ " LENGTHUNIT[\"metre\",1]]],\n"
+ " PRIMEM[\"Greenwich\",0,\n"
+ " ANGLEUNIT[\"degree\",0.0174532925199433]]],\n"
+ " CONVERSION[\"unnamed\",\n"
+ " METHOD[\"Krovak\",\n"
+ " ID[\"EPSG\",9819]],\n"
+ " PARAMETER[\"Latitude of projection centre\",49.5,\n"
+ " ANGLEUNIT[\"degree\",0.0174532925199433],\n"
+ " ID[\"EPSG\",8811]],\n"
+ " PARAMETER[\"Longitude of origin\",24.8333333333333,\n"
+ " ANGLEUNIT[\"degree\",0.0174532925199433],\n"
+ " ID[\"EPSG\",8833]],\n"
+ " PARAMETER[\"Co-latitude of cone axis\",30.2881397222222,\n"
+ " ANGLEUNIT[\"degree\",0.0174532925199433],\n"
+ " ID[\"EPSG\",1036]],\n"
+ " PARAMETER[\"Latitude of pseudo standard parallel\",78.5,\n"
+ " ANGLEUNIT[\"degree\",0.0174532925199433],\n"
+ " ID[\"EPSG\",8818]],\n"
+ " PARAMETER[\"Scale factor on pseudo standard "
+ "parallel\",0.9999,\n"
+ " SCALEUNIT[\"unity\",1],\n"
+ " ID[\"EPSG\",8819]],\n"
+ " PARAMETER[\"False easting\",0,\n"
+ " LENGTHUNIT[\"metre\",1],\n"
+ " ID[\"EPSG\",8806]],\n"
+ " PARAMETER[\"False northing\",0,\n"
+ " LENGTHUNIT[\"metre\",1],\n"
+ " ID[\"EPSG\",8807]]],\n"
+ " CS[Cartesian,2],\n"
+ " AXIS[\"x\",south,\n"
+ " ORDER[1],\n"
+ " LENGTHUNIT[\"metre\",1]],\n"
+ " AXIS[\"y\",west,\n"
+ " ORDER[2],\n"
+ " LENGTHUNIT[\"metre\",1]],\n"
+ " ID[\"EPSG\",5513]]";
+
+ EXPECT_EQ(crs->exportToWKT(WKTFormatter::create().get()), expected_wkt2);
+
+ auto projString =
+ crs->exportToPROJString(PROJStringFormatter::create().get());
+ auto expectedPROJString =
+ "+proj=pipeline +step +proj=unitconvert +xy_in=deg +xy_out=rad "
+ "+step +proj=krovak +axis=swu +lat_0=49.5 "
+ "+lon_0=24.8333333333333 +k=0.9999 +x_0=0 +y_0=0 +ellps=bessel";
+ EXPECT_EQ(projString, expectedPROJString);
+
+ obj = PROJStringParser().createFromPROJString(projString);
+ crs = nn_dynamic_pointer_cast<ProjectedCRS>(obj);
+ ASSERT_TRUE(crs != nullptr);
+ auto wkt2 = crs->exportToWKT(WKTFormatter::create().get());
+ EXPECT_TRUE(
+ wkt2.find("PARAMETER[\"Latitude of pseudo standard parallel\",78.5,") !=
+ std::string::npos)
+ << wkt2;
+ EXPECT_TRUE(
+ wkt2.find("PARAMETER[\"Co-latitude of cone axis\",30.2881397222222,") !=
+ std::string::npos)
+ << wkt2;
+ EXPECT_EQ(crs->exportToPROJString(PROJStringFormatter::create().get()),
+ expectedPROJString);
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(wkt_parse, wkt1_krovak_north_oriented) {
+ auto wkt =
+ "PROJCS[\"S-JTSK / Krovak East North\","
+ " GEOGCS[\"S-JTSK\","
+ " DATUM[\"System_Jednotne_Trigonometricke_Site_Katastralni\","
+ " SPHEROID[\"Bessel 1841\",6377397.155,299.1528128,"
+ " AUTHORITY[\"EPSG\",\"7004\"]],"
+ " AUTHORITY[\"EPSG\",\"6156\"]],"
+ " PRIMEM[\"Greenwich\",0,"
+ " AUTHORITY[\"EPSG\",\"8901\"]],"
+ " UNIT[\"degree\",0.0174532925199433,"
+ " AUTHORITY[\"EPSG\",\"9122\"]],"
+ " AUTHORITY[\"EPSG\",\"4156\"]],"
+ " PROJECTION[\"Krovak\"],"
+ " PARAMETER[\"latitude_of_center\",49.5],"
+ " PARAMETER[\"longitude_of_center\",24.83333333333333],"
+ " PARAMETER[\"azimuth\",30.28813972222222],"
+ " PARAMETER[\"pseudo_standard_parallel_1\",78.5],"
+ " PARAMETER[\"scale_factor\",0.9999],"
+ " PARAMETER[\"false_easting\",0],"
+ " PARAMETER[\"false_northing\",0],"
+ " UNIT[\"metre\",1,"
+ " AUTHORITY[\"EPSG\",\"9001\"]],"
+ " AXIS[\"X\",EAST],"
+ " AXIS[\"Y\",NORTH],"
+ " AUTHORITY[\"EPSG\",\"5514\"]]";
+
+ auto obj = WKTParser().createFromWKT(wkt);
+ auto crs = nn_dynamic_pointer_cast<ProjectedCRS>(obj);
+ ASSERT_TRUE(crs != nullptr);
+
+ EXPECT_EQ(crs->derivingConversion()->method()->nameStr(),
+ "Krovak (North Orientated)");
+
+ EXPECT_EQ(
+ crs->exportToWKT(WKTFormatter::create().get()),
+ "PROJCRS[\"S-JTSK / Krovak East North\",\n"
+ " BASEGEODCRS[\"S-JTSK\",\n"
+ " DATUM[\"System_Jednotne_Trigonometricke_Site_Katastralni\",\n"
+ " ELLIPSOID[\"Bessel 1841\",6377397.155,299.1528128,\n"
+ " LENGTHUNIT[\"metre\",1]]],\n"
+ " PRIMEM[\"Greenwich\",0,\n"
+ " ANGLEUNIT[\"degree\",0.0174532925199433]]],\n"
+ " CONVERSION[\"unnamed\",\n"
+ " METHOD[\"Krovak (North Orientated)\",\n"
+ " ID[\"EPSG\",1041]],\n"
+ " PARAMETER[\"Latitude of projection centre\",49.5,\n"
+ " ANGLEUNIT[\"degree\",0.0174532925199433],\n"
+ " ID[\"EPSG\",8811]],\n"
+ " PARAMETER[\"Longitude of origin\",24.8333333333333,\n"
+ " ANGLEUNIT[\"degree\",0.0174532925199433],\n"
+ " ID[\"EPSG\",8833]],\n"
+ " PARAMETER[\"Co-latitude of cone axis\",30.2881397222222,\n"
+ " ANGLEUNIT[\"degree\",0.0174532925199433],\n"
+ " ID[\"EPSG\",1036]],\n"
+ " PARAMETER[\"Latitude of pseudo standard parallel\",78.5,\n"
+ " ANGLEUNIT[\"degree\",0.0174532925199433],\n"
+ " ID[\"EPSG\",8818]],\n"
+ " PARAMETER[\"Scale factor on pseudo standard "
+ "parallel\",0.9999,\n"
+ " SCALEUNIT[\"unity\",1],\n"
+ " ID[\"EPSG\",8819]],\n"
+ " PARAMETER[\"False easting\",0,\n"
+ " LENGTHUNIT[\"metre\",1],\n"
+ " ID[\"EPSG\",8806]],\n"
+ " PARAMETER[\"False northing\",0,\n"
+ " LENGTHUNIT[\"metre\",1],\n"
+ " ID[\"EPSG\",8807]]],\n"
+ " CS[Cartesian,2],\n"
+ " AXIS[\"x\",east,\n"
+ " ORDER[1],\n"
+ " LENGTHUNIT[\"metre\",1]],\n"
+ " AXIS[\"y\",north,\n"
+ " ORDER[2],\n"
+ " LENGTHUNIT[\"metre\",1]],\n"
+ " ID[\"EPSG\",5514]]");
+
+ EXPECT_EQ(crs->exportToPROJString(PROJStringFormatter::create().get()),
+ "+proj=pipeline +step +proj=unitconvert +xy_in=deg +xy_out=rad "
+ "+step +proj=krovak +lat_0=49.5 +lon_0=24.8333333333333 "
+ "+k=0.9999 +x_0=0 +y_0=0 +ellps=bessel");
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(wkt_parse, wkt2_projected) {
+ auto wkt = "PROJCRS[\"WGS 84 / UTM zone 31N\",\n"
+ " BASEGEODCRS[\"WGS 84\",\n"
+ " DATUM[\"World Geodetic System 1984\",\n"
+ " ELLIPSOID[\"WGS 84\",6378137,298.257223563,\n"
+ " LENGTHUNIT[\"metre\",1,\n"
+ " ID[\"EPSG\",9001]],\n"
+ " ID[\"EPSG\",7030]],\n"
+ " ID[\"EPSG\",6326]],\n"
+ " PRIMEM[\"Greenwich\",0,\n"
+ " ANGLEUNIT[\"degree\",0.0174532925199433,\n"
+ " ID[\"EPSG\",9122]],\n"
+ " ID[\"EPSG\",8901]]],\n"
+ " CONVERSION[\"UTM zone 31N\",\n"
+ " METHOD[\"Transverse Mercator\",\n"
+ " ID[\"EPSG\",9807]],\n"
+ " PARAMETER[\"Latitude of natural origin\",0,\n"
+ " ANGLEUNIT[\"degree\",0.0174532925199433,\n"
+ " ID[\"EPSG\",9122]],\n"
+ " ID[\"EPSG\",8801]],\n"
+ " PARAMETER[\"Longitude of natural origin\",3,\n"
+ " ANGLEUNIT[\"degree\",0.0174532925199433,\n"
+ " ID[\"EPSG\",9122]],\n"
+ " ID[\"EPSG\",8802]],\n"
+ " PARAMETER[\"Scale factor at natural origin\",0.9996,\n"
+ " SCALEUNIT[\"unity\",1,\n"
+ " ID[\"EPSG\",9201]],\n"
+ " ID[\"EPSG\",8805]],\n"
+ " PARAMETER[\"False easting\",500000,\n"
+ " LENGTHUNIT[\"metre\",1,\n"
+ " ID[\"EPSG\",9001]],\n"
+ " ID[\"EPSG\",8806]],\n"
+ " PARAMETER[\"False northing\",0,\n"
+ " LENGTHUNIT[\"metre\",1,\n"
+ " ID[\"EPSG\",9001]],\n"
+ " ID[\"EPSG\",8807]],\n"
+ " ID[\"EPSG\",16031]],\n"
+ " CS[Cartesian,2],\n"
+ " AXIS[\"(E)\",east,\n"
+ " ORDER[1],\n"
+ " LENGTHUNIT[\"metre\",1,\n"
+ " ID[\"EPSG\",9001]]],\n"
+ " AXIS[\"(N)\",north,\n"
+ " ORDER[2],\n"
+ " LENGTHUNIT[\"metre\",1,\n"
+ " ID[\"EPSG\",9001]]],\n"
+ " ID[\"EPSG\",32631]]";
+ auto obj = WKTParser().createFromWKT(wkt);
+ auto crs = nn_dynamic_pointer_cast<ProjectedCRS>(obj);
+ ASSERT_TRUE(crs != nullptr);
+ checkProjected(crs, /*checkEPSGCodes = */ false);
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(wkt_parse, wkt2_2018_simplified_projected) {
+ auto wkt = "PROJCRS[\"WGS 84 / UTM zone 31N\",\n"
+ " BASEGEOGCRS[\"WGS 84\",\n"
+ " DATUM[\"World Geodetic System 1984\",\n"
+ " ELLIPSOID[\"WGS 84\",6378137,298.257223563]],\n"
+ " UNIT[\"degree\",0.0174532925199433]],\n"
+ " CONVERSION[\"UTM zone 31N\",\n"
+ " METHOD[\"Transverse Mercator\"],\n"
+ " PARAMETER[\"Latitude of natural origin\",0],\n"
+ " PARAMETER[\"Longitude of natural origin\",3],\n"
+ " PARAMETER[\"Scale factor at natural origin\",0.9996],\n"
+ " PARAMETER[\"False easting\",500000],\n"
+ " PARAMETER[\"False northing\",0]],\n"
+ " CS[Cartesian,2],\n"
+ " AXIS[\"(E)\",east],\n"
+ " AXIS[\"(N)\",north],\n"
+ " UNIT[\"metre\",1],\n"
+ " ID[\"EPSG\",32631]]";
+ auto obj = WKTParser().createFromWKT(wkt);
+ auto crs = nn_dynamic_pointer_cast<ProjectedCRS>(obj);
+ ASSERT_TRUE(crs != nullptr);
+ checkProjected(crs, /*checkEPSGCodes = */ false);
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(crs, projected_angular_unit_from_primem) {
+ auto obj = WKTParser().createFromWKT(
+ "PROJCRS[\"NTF (Paris) / Lambert Nord France\",\n"
+ " BASEGEODCRS[\"NTF (Paris)\",\n"
+ " DATUM[\"Nouvelle Triangulation Francaise (Paris)\",\n"
+ " ELLIPSOID[\"Clarke 1880 "
+ "(IGN)\",6378249.2,293.4660213,LENGTHUNIT[\"metre\",1.0]]],\n"
+ " PRIMEM[\"Paris\",2.5969213,ANGLEUNIT[\"grad\",0.015707963268]]],\n"
+ " CONVERSION[\"Lambert Nord France\",\n"
+ " METHOD[\"Lambert Conic Conformal (1SP)\",ID[\"EPSG\",9801]],\n"
+ " PARAMETER[\"Latitude of natural "
+ "origin\",55,ANGLEUNIT[\"grad\",0.015707963268]],\n"
+ " PARAMETER[\"Longitude of natural "
+ "origin\",0,ANGLEUNIT[\"grad\",0.015707963268]],\n"
+ " PARAMETER[\"Scale factor at natural "
+ "origin\",0.999877341,SCALEUNIT[\"unity\",1.0]],\n"
+ " PARAMETER[\"False easting\",600000,LENGTHUNIT[\"metre\",1.0]],\n"
+ " PARAMETER[\"False northing\",200000,LENGTHUNIT[\"metre\",1.0]]],\n"
+ " CS[cartesian,2],\n"
+ " AXIS[\"easting (X)\",east,ORDER[1]],\n"
+ " AXIS[\"northing (Y)\",north,ORDER[2]],\n"
+ " LENGTHUNIT[\"metre\",1.0],\n"
+ " ID[\"EPSG\",27561]]");
+ auto crs = nn_dynamic_pointer_cast<ProjectedCRS>(obj);
+ ASSERT_TRUE(crs != nullptr);
+ EXPECT_EQ(crs->baseCRS()->coordinateSystem()->axisList()[0]->unit(),
+ UnitOfMeasure::GRAD);
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(wkt_parse, cs_with_MERIDIAN) {
+ auto wkt =
+ "PROJCRS[\"dummy\",\n"
+ " BASEGEOGCRS[\"WGS 84\",\n"
+ " DATUM[\"World Geodetic System 1984\",\n"
+ " ELLIPSOID[\"WGS 84\",6378137,298.257223563]],\n"
+ " UNIT[\"degree\",0.0174532925199433]],\n"
+ " CONVERSION[\"dummy\",\n"
+ " METHOD[\"dummy\"]]\n"
+ " CS[Cartesian,2],\n"
+ " AXIS[\"easting "
+ "(X)\",south,MERIDIAN[90,ANGLEUNIT[\"degree\",0.0174532925199433]]],\n"
+ " AXIS[\"northing (Y)\",north],\n"
+ " UNIT[\"metre\",1],\n"
+ " ID[\"EPSG\",32631]]";
+ auto obj = WKTParser().createFromWKT(wkt);
+ auto crs = nn_dynamic_pointer_cast<ProjectedCRS>(obj);
+ ASSERT_TRUE(crs != nullptr);
+ ASSERT_EQ(crs->coordinateSystem()->axisList().size(), 2);
+ auto axis = crs->coordinateSystem()->axisList()[0];
+ auto meridian = axis->meridian();
+ ASSERT_TRUE(meridian != nullptr);
+ EXPECT_EQ(meridian->longitude().value(), 90.0);
+ EXPECT_EQ(meridian->longitude().unit(), UnitOfMeasure::DEGREE);
+ ASSERT_TRUE(crs->coordinateSystem()->axisList()[1]->meridian() == nullptr);
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(wkt_parse, cs_with_multiple_ID) {
+ auto wkt = "GEODCRS[\"WGS 84\",\n"
+ " DATUM[\"World Geodetic System 1984\",\n"
+ " ELLIPSOID[\"WGS 84\",6378137,298.257223563]],\n"
+ " CS[Cartesian,3],\n"
+ " AXIS[\"(X)\",geocentricX],\n"
+ " AXIS[\"(Y)\",geocentricY],\n"
+ " AXIS[\"(Z)\",geocentricZ],\n"
+ " UNIT[\"metre\",1],\n"
+ " ID[\"authorityA\",\"codeA\"],\n"
+ " ID[\"authorityB\",\"codeB\"]]";
+
+ auto obj = WKTParser().createFromWKT(wkt);
+ auto crs = nn_dynamic_pointer_cast<GeodeticCRS>(obj);
+ ASSERT_TRUE(crs != nullptr);
+ EXPECT_EQ(crs->nameStr(), "WGS 84");
+ ASSERT_EQ(crs->identifiers().size(), 2);
+ EXPECT_EQ(crs->identifiers()[0]->code(), "codeA");
+ EXPECT_EQ(*(crs->identifiers()[0]->codeSpace()), "authorityA");
+ EXPECT_EQ(crs->identifiers()[1]->code(), "codeB");
+ EXPECT_EQ(*(crs->identifiers()[1]->codeSpace()), "authorityB");
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(wkt_parse, vertcrs_WKT2) {
+ auto wkt = "VERTCRS[\"ODN height\",\n"
+ " VDATUM[\"Ordnance Datum Newlyn\"],\n"
+ " CS[vertical,1],\n"
+ " AXIS[\"gravity-related height (H)\",up,\n"
+ " LENGTHUNIT[\"metre\",1]],\n"
+ " ID[\"EPSG\",5701]]";
+
+ auto obj = WKTParser().createFromWKT(wkt);
+ auto crs = nn_dynamic_pointer_cast<VerticalCRS>(obj);
+ ASSERT_TRUE(crs != nullptr);
+ EXPECT_EQ(crs->nameStr(), "ODN height");
+ ASSERT_EQ(crs->identifiers().size(), 1);
+ EXPECT_EQ(crs->identifiers()[0]->code(), "5701");
+ EXPECT_EQ(*(crs->identifiers()[0]->codeSpace()), "EPSG");
+
+ auto datum = crs->datum();
+ EXPECT_EQ(datum->nameStr(), "Ordnance Datum Newlyn");
+ // ASSERT_EQ(datum->identifiers().size(), 1);
+ // EXPECT_EQ(datum->identifiers()[0]->code(), "5101");
+ // EXPECT_EQ(*(datum->identifiers()[0]->codeSpace()), "EPSG");
+
+ auto cs = crs->coordinateSystem();
+ ASSERT_EQ(cs->axisList().size(), 1);
+ EXPECT_EQ(cs->axisList()[0]->nameStr(), "Gravity-related height");
+ EXPECT_EQ(cs->axisList()[0]->abbreviation(), "H");
+ EXPECT_EQ(cs->axisList()[0]->direction(), AxisDirection::UP);
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(wkt_parse, vertcrs_VRF_WKT2) {
+ auto wkt = "VERTCRS[\"ODN height\",\n"
+ " VRF[\"Ordnance Datum Newlyn\"],\n"
+ " CS[vertical,1],\n"
+ " AXIS[\"gravity-related height (H)\",up,\n"
+ " LENGTHUNIT[\"metre\",1]],\n"
+ " ID[\"EPSG\",5701]]";
+
+ auto obj = WKTParser().createFromWKT(wkt);
+ auto crs = nn_dynamic_pointer_cast<VerticalCRS>(obj);
+ ASSERT_TRUE(crs != nullptr);
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(wkt_parse, vertcrs_WKT1_GDAL) {
+ auto wkt = "VERT_CS[\"ODN height\",\n"
+ " VERT_DATUM[\"Ordnance Datum Newlyn\",2005,\n"
+ " AUTHORITY[\"EPSG\",\"5101\"]],\n"
+ " UNIT[\"metre\",1,\n"
+ " AUTHORITY[\"EPSG\",9001]],\n"
+ " AXIS[\"gravity-related height\",UP],\n"
+ " AUTHORITY[\"EPSG\",\"5701\"]]";
+
+ auto obj = WKTParser().createFromWKT(wkt);
+ auto crs = nn_dynamic_pointer_cast<VerticalCRS>(obj);
+ ASSERT_TRUE(crs != nullptr);
+ EXPECT_EQ(crs->nameStr(), "ODN height");
+ ASSERT_EQ(crs->identifiers().size(), 1);
+ EXPECT_EQ(crs->identifiers()[0]->code(), "5701");
+ EXPECT_EQ(*(crs->identifiers()[0]->codeSpace()), "EPSG");
+
+ auto datum = crs->datum();
+ EXPECT_EQ(datum->nameStr(), "Ordnance Datum Newlyn");
+ ASSERT_EQ(datum->identifiers().size(), 1);
+ EXPECT_EQ(datum->identifiers()[0]->code(), "5101");
+ EXPECT_EQ(*(datum->identifiers()[0]->codeSpace()), "EPSG");
+
+ auto cs = crs->coordinateSystem();
+ ASSERT_EQ(cs->axisList().size(), 1);
+ EXPECT_EQ(cs->axisList()[0]->nameStr(), "Gravity-related height");
+ EXPECT_EQ(cs->axisList()[0]->abbreviation(), ""); // "H" in WKT2
+ EXPECT_EQ(cs->axisList()[0]->direction(), AxisDirection::UP);
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(wkt_parse, vertcrs_WKT1_GDAL_minimum) {
+ auto wkt = "VERT_CS[\"ODN height\",\n"
+ " VERT_DATUM[\"Ordnance Datum Newlyn\",2005],\n"
+ " UNIT[\"metre\",1]]";
+
+ auto obj = WKTParser().createFromWKT(wkt);
+ auto crs = nn_dynamic_pointer_cast<VerticalCRS>(obj);
+ EXPECT_EQ(crs->nameStr(), "ODN height");
+
+ auto datum = crs->datum();
+ EXPECT_EQ(datum->nameStr(), "Ordnance Datum Newlyn");
+
+ auto cs = crs->coordinateSystem();
+ ASSERT_EQ(cs->axisList().size(), 1);
+ EXPECT_EQ(cs->axisList()[0]->nameStr(), "Gravity-related height");
+ EXPECT_EQ(cs->axisList()[0]->direction(), AxisDirection::UP);
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(wkt_parse, dynamic_vertical_reference_frame) {
+ auto obj = WKTParser().createFromWKT(
+ "VERTCRS[\"RH2000\","
+ " DYNAMIC[FRAMEEPOCH[2000.0],MODEL[\"NKG2016LU\"]],"
+ " VDATUM[\"Rikets Hojdsystem 2000\",ANCHOR[\"my anchor\"]],"
+ " CS[vertical,1],"
+ " AXIS[\"gravity-related height (H)\",up],"
+ " LENGTHUNIT[\"metre\",1.0]"
+ "]");
+ auto crs = nn_dynamic_pointer_cast<VerticalCRS>(obj);
+ ASSERT_TRUE(crs != nullptr);
+ auto dgrf =
+ std::dynamic_pointer_cast<DynamicVerticalReferenceFrame>(crs->datum());
+ ASSERT_TRUE(dgrf != nullptr);
+ auto anchor = dgrf->anchorDefinition();
+ EXPECT_TRUE(anchor.has_value());
+ EXPECT_EQ(*anchor, "my anchor");
+ EXPECT_TRUE(dgrf->frameReferenceEpoch() ==
+ Measure(2000.0, UnitOfMeasure::YEAR));
+ auto model = dgrf->deformationModelName();
+ EXPECT_TRUE(model.has_value());
+ EXPECT_EQ(*model, "NKG2016LU");
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(wkt_parse, vertcrs_with_ensemble) {
+ auto obj = WKTParser().createFromWKT(
+ "VERTCRS[\"unnamed\",\n"
+ " ENSEMBLE[\"unnamed\",\n"
+ " MEMBER[\"vdatum1\"],\n"
+ " MEMBER[\"vdatum2\"],\n"
+ " ENSEMBLEACCURACY[100]],\n"
+ " CS[vertical,1],\n"
+ " AXIS[\"gravity-related height (H)\",up,\n"
+ " LENGTHUNIT[\"metre\",1]]]");
+ auto crs = nn_dynamic_pointer_cast<VerticalCRS>(obj);
+ ASSERT_TRUE(crs != nullptr);
+ ASSERT_TRUE(crs->datum() == nullptr);
+ ASSERT_TRUE(crs->datumEnsemble() != nullptr);
+ EXPECT_EQ(crs->datumEnsemble()->datums().size(), 2);
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(wkt_parse, vdatum_with_ANCHOR) {
+ auto obj = WKTParser().createFromWKT("VDATUM[\"Ordnance Datum Newlyn\",\n"
+ " ANCHOR[\"my anchor\"],\n"
+ " ID[\"EPSG\",5101]]");
+ auto datum = nn_dynamic_pointer_cast<VerticalReferenceFrame>(obj);
+ ASSERT_TRUE(datum != nullptr);
+ auto anchor = datum->anchorDefinition();
+ EXPECT_TRUE(anchor.has_value());
+ EXPECT_EQ(*anchor, "my anchor");
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(wkt_parse, COMPOUNDCRS) {
+ auto obj = WKTParser().createFromWKT(
+ "COMPOUNDCRS[\"horizontal + vertical\",\n"
+ " PROJCRS[\"WGS 84 / UTM zone 31N\",\n"
+ " BASEGEODCRS[\"WGS 84\",\n"
+ " DATUM[\"World Geodetic System 1984\",\n"
+ " ELLIPSOID[\"WGS 84\",6378137,298.257223563,\n"
+ " LENGTHUNIT[\"metre\",1]]],\n"
+ " PRIMEM[\"Greenwich\",0,\n"
+ " ANGLEUNIT[\"degree\",0.0174532925199433]]],\n"
+ " CONVERSION[\"UTM zone 31N\",\n"
+ " METHOD[\"Transverse Mercator\",\n"
+ " ID[\"EPSG\",9807]],\n"
+ " PARAMETER[\"Latitude of natural origin\",0,\n"
+ " ANGLEUNIT[\"degree\",0.0174532925199433],\n"
+ " ID[\"EPSG\",8801]],\n"
+ " PARAMETER[\"Longitude of natural origin\",3,\n"
+ " ANGLEUNIT[\"degree\",0.0174532925199433],\n"
+ " ID[\"EPSG\",8802]],\n"
+ " PARAMETER[\"Scale factor at natural origin\",0.9996,\n"
+ " SCALEUNIT[\"unity\",1],\n"
+ " ID[\"EPSG\",8805]],\n"
+ " PARAMETER[\"False easting\",500000,\n"
+ " LENGTHUNIT[\"metre\",1],\n"
+ " ID[\"EPSG\",8806]],\n"
+ " PARAMETER[\"False northing\",0,\n"
+ " LENGTHUNIT[\"metre\",1],\n"
+ " ID[\"EPSG\",8807]]],\n"
+ " CS[Cartesian,2],\n"
+ " AXIS[\"(E)\",east,\n"
+ " ORDER[1],\n"
+ " LENGTHUNIT[\"metre\",1]],\n"
+ " AXIS[\"(N)\",north,\n"
+ " ORDER[2],\n"
+ " LENGTHUNIT[\"metre\",1]]],\n"
+ " VERTCRS[\"ODN height\",\n"
+ " VDATUM[\"Ordnance Datum Newlyn\"],\n"
+ " CS[vertical,1],\n"
+ " AXIS[\"gravity-related height (H)\",up,\n"
+ " LENGTHUNIT[\"metre\",1]]],\n"
+ " ID[\"codespace\",\"code\"]]");
+ auto crs = nn_dynamic_pointer_cast<CompoundCRS>(obj);
+ ASSERT_TRUE(crs != nullptr);
+ EXPECT_EQ(crs->nameStr(), "horizontal + vertical");
+ EXPECT_EQ(crs->componentReferenceSystems().size(), 2);
+ ASSERT_EQ(crs->identifiers().size(), 1);
+ EXPECT_EQ(crs->identifiers()[0]->code(), "code");
+ EXPECT_EQ(*(crs->identifiers()[0]->codeSpace()), "codespace");
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(wkt_parse, COMPD_CS) {
+ auto obj = WKTParser().createFromWKT(
+ "COMPD_CS[\"horizontal + vertical\",\n"
+ " PROJCS[\"WGS 84 / UTM zone 31N\",\n"
+ " GEOGCS[\"WGS 84\",\n"
+ " DATUM[\"World Geodetic System 1984\",\n"
+ " SPHEROID[\"WGS 84\",6378137,298.257223563,\n"
+ " AUTHORITY[\"EPSG\",\"7030\"]],\n"
+ " AUTHORITY[\"EPSG\",\"6326\"]],\n"
+ " PRIMEM[\"Greenwich\",0,\n"
+ " AUTHORITY[\"EPSG\",\"8901\"]],\n"
+ " UNIT[\"degree\",0.0174532925199433,\n"
+ " AUTHORITY[\"EPSG\",9122]],\n"
+ " AXIS[\"Latitude\",NORTH],\n"
+ " AXIS[\"Longitude\",EAST],\n"
+ " AUTHORITY[\"EPSG\",\"4326\"]],\n"
+ " PROJECTION[\"Transverse_Mercator\"],\n"
+ " PARAMETER[\"latitude_of_origin\",0],\n"
+ " PARAMETER[\"central_meridian\",3],\n"
+ " PARAMETER[\"scale_factor\",0.9996],\n"
+ " PARAMETER[\"false_easting\",500000],\n"
+ " PARAMETER[\"false_northing\",0],\n"
+ " UNIT[\"metre\",1,\n"
+ " AUTHORITY[\"EPSG\",9001]],\n"
+ " AXIS[\"Easting\",EAST],\n"
+ " AXIS[\"Northing\",NORTH],\n"
+ " AUTHORITY[\"EPSG\",\"32631\"]],\n"
+ " VERT_CS[\"ODN height\",\n"
+ " VERT_DATUM[\"Ordnance Datum Newlyn\",2005,\n"
+ " AUTHORITY[\"EPSG\",\"5101\"]],\n"
+ " UNIT[\"metre\",1,\n"
+ " AUTHORITY[\"EPSG\",9001]],\n"
+ " AXIS[\"Gravity-related height\",UP],\n"
+ " AUTHORITY[\"EPSG\",\"5701\"]],\n"
+ " AUTHORITY[\"codespace\",\"code\"]]");
+ auto crs = nn_dynamic_pointer_cast<CompoundCRS>(obj);
+ ASSERT_TRUE(crs != nullptr);
+ EXPECT_EQ(crs->nameStr(), "horizontal + vertical");
+ EXPECT_EQ(crs->componentReferenceSystems().size(), 2);
+ ASSERT_EQ(crs->identifiers().size(), 1);
+ EXPECT_EQ(crs->identifiers()[0]->code(), "code");
+ EXPECT_EQ(*(crs->identifiers()[0]->codeSpace()), "codespace");
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(wkt_parse, COORDINATEOPERATION) {
+
+ std::string src_wkt;
+ {
+ auto formatter = WKTFormatter::create();
+ formatter->setOutputId(false);
+ src_wkt = GeographicCRS::EPSG_4326->exportToWKT(formatter.get());
+ }
+
+ std::string dst_wkt;
+ {
+ auto formatter = WKTFormatter::create();
+ formatter->setOutputId(false);
+ dst_wkt = GeographicCRS::EPSG_4807->exportToWKT(formatter.get());
+ }
+
+ std::string interpolation_wkt;
+ {
+ auto formatter = WKTFormatter::create();
+ formatter->setOutputId(false);
+ interpolation_wkt =
+ GeographicCRS::EPSG_4979->exportToWKT(formatter.get());
+ }
+
+ auto wkt =
+ "COORDINATEOPERATION[\"transformationName\",\n"
+ " SOURCECRS[" +
+ src_wkt + "],\n"
+ " TARGETCRS[" +
+ dst_wkt +
+ "],\n"
+ " METHOD[\"operationMethodName\",\n"
+ " ID[\"codeSpaceOperationMethod\",\"codeOperationMethod\"]],\n"
+ " PARAMETERFILE[\"paramName\",\"foo.bin\"],\n"
+ " INTERPOLATIONCRS[" +
+ interpolation_wkt +
+ "],\n"
+ " OPERATIONACCURACY[0.1],\n"
+ " ID[\"codeSpaceTransformation\",\"codeTransformation\"],\n"
+ " REMARK[\"my remarks\"]]";
+
+ auto obj = WKTParser().createFromWKT(wkt);
+ auto transf = nn_dynamic_pointer_cast<Transformation>(obj);
+ ASSERT_TRUE(transf != nullptr);
+ EXPECT_EQ(transf->nameStr(), "transformationName");
+ ASSERT_EQ(transf->identifiers().size(), 1);
+ EXPECT_EQ(transf->identifiers()[0]->code(), "codeTransformation");
+ EXPECT_EQ(*(transf->identifiers()[0]->codeSpace()),
+ "codeSpaceTransformation");
+ ASSERT_EQ(transf->coordinateOperationAccuracies().size(), 1);
+ EXPECT_EQ(transf->coordinateOperationAccuracies()[0]->value(), "0.1");
+ EXPECT_EQ(transf->sourceCRS()->nameStr(),
+ GeographicCRS::EPSG_4326->nameStr());
+ EXPECT_EQ(transf->targetCRS()->nameStr(),
+ GeographicCRS::EPSG_4807->nameStr());
+ ASSERT_TRUE(transf->interpolationCRS() != nullptr);
+ EXPECT_EQ(transf->interpolationCRS()->nameStr(),
+ GeographicCRS::EPSG_4979->nameStr());
+ EXPECT_EQ(transf->method()->nameStr(), "operationMethodName");
+ EXPECT_EQ(transf->parameterValues().size(), 1);
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(wkt_parse, CONCATENATEDOPERATION) {
+
+ auto transf_1 = Transformation::create(
+ PropertyMap().set(IdentifiedObject::NAME_KEY, "transf_1"),
+ nn_static_pointer_cast<CRS>(GeographicCRS::EPSG_4326),
+ nn_static_pointer_cast<CRS>(GeographicCRS::EPSG_4807), nullptr,
+ PropertyMap().set(IdentifiedObject::NAME_KEY, "operationMethodName"),
+ std::vector<OperationParameterNNPtr>{OperationParameter::create(
+ PropertyMap().set(IdentifiedObject::NAME_KEY, "paramName"))},
+ std::vector<ParameterValueNNPtr>{
+ ParameterValue::createFilename("foo.bin")},
+ std::vector<PositionalAccuracyNNPtr>());
+
+ auto transf_2 = Transformation::create(
+ PropertyMap().set(IdentifiedObject::NAME_KEY, "transf_2"),
+ nn_static_pointer_cast<CRS>(GeographicCRS::EPSG_4807),
+ nn_static_pointer_cast<CRS>(GeographicCRS::EPSG_4979), nullptr,
+ PropertyMap().set(IdentifiedObject::NAME_KEY, "operationMethodName"),
+ std::vector<OperationParameterNNPtr>{OperationParameter::create(
+ PropertyMap().set(IdentifiedObject::NAME_KEY, "paramName"))},
+ std::vector<ParameterValueNNPtr>{
+ ParameterValue::createFilename("foo.bin")},
+ std::vector<PositionalAccuracyNNPtr>());
+
+ auto concat_in = ConcatenatedOperation::create(
+ PropertyMap()
+ .set(Identifier::CODESPACE_KEY, "codeSpace")
+ .set(Identifier::CODE_KEY, "code")
+ .set(IdentifiedObject::NAME_KEY, "name")
+ .set(IdentifiedObject::REMARKS_KEY, "my remarks"),
+ std::vector<CoordinateOperationNNPtr>{transf_1, transf_2},
+ std::vector<PositionalAccuracyNNPtr>{
+ PositionalAccuracy::create("0.1")});
+
+ auto wkt = concat_in->exportToWKT(
+ WKTFormatter::create(WKTFormatter::Convention::WKT2_2018).get());
+
+ auto obj = WKTParser().createFromWKT(wkt);
+ auto concat = nn_dynamic_pointer_cast<ConcatenatedOperation>(obj);
+ ASSERT_TRUE(concat != nullptr);
+ EXPECT_EQ(concat->nameStr(), "name");
+ ASSERT_EQ(concat->identifiers().size(), 1);
+ EXPECT_EQ(concat->identifiers()[0]->code(), "code");
+ EXPECT_EQ(*(concat->identifiers()[0]->codeSpace()), "codeSpace");
+ ASSERT_EQ(concat->operations().size(), 2);
+ ASSERT_EQ(concat->operations()[0]->nameStr(), transf_1->nameStr());
+ ASSERT_EQ(concat->operations()[1]->nameStr(), transf_2->nameStr());
+ ASSERT_TRUE(concat->sourceCRS() != nullptr);
+ ASSERT_TRUE(concat->targetCRS() != nullptr);
+ ASSERT_EQ(concat->sourceCRS()->nameStr(), transf_1->sourceCRS()->nameStr());
+ ASSERT_EQ(concat->targetCRS()->nameStr(), transf_2->targetCRS()->nameStr());
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(wkt_parse, BOUNDCRS_transformation_from_names) {
+
+ auto projcrs = ProjectedCRS::create(
+ PropertyMap().set(IdentifiedObject::NAME_KEY, "my PROJCRS"),
+ GeographicCRS::create(
+ PropertyMap().set(IdentifiedObject::NAME_KEY, "my GEOGCRS"),
+ GeodeticReferenceFrame::EPSG_6326,
+ EllipsoidalCS::createLatitudeLongitude(UnitOfMeasure::DEGREE)),
+ Conversion::createUTM(PropertyMap(), 31, true),
+ CartesianCS::createEastingNorthing(UnitOfMeasure::METRE));
+
+ auto wkt =
+ "BOUNDCRS[SOURCECRS[" +
+ projcrs->exportToWKT(WKTFormatter::create().get()) + "],\n" +
+ "TARGETCRS[" +
+ GeographicCRS::EPSG_4326->exportToWKT(WKTFormatter::create().get()) +
+ "],\n"
+ " ABRIDGEDTRANSFORMATION[\"Transformation to WGS84\",\n"
+ " METHOD[\"Coordinate Frame\"],\n"
+ " PARAMETER[\"X-axis translation\",1],\n"
+ " PARAMETER[\"Y-axis translation\",2],\n"
+ " PARAMETER[\"Z-axis translation\",3],\n"
+ " PARAMETER[\"X-axis rotation\",-4],\n"
+ " PARAMETER[\"Y-axis rotation\",-5],\n"
+ " PARAMETER[\"Z-axis rotation\",-6],\n"
+ " PARAMETER[\"Scale difference\",1.000007]]]";
+
+ auto obj = WKTParser().createFromWKT(wkt);
+ auto crs = nn_dynamic_pointer_cast<BoundCRS>(obj);
+ ASSERT_TRUE(crs != nullptr);
+
+ EXPECT_EQ(crs->baseCRS()->nameStr(), projcrs->nameStr());
+
+ EXPECT_EQ(crs->hubCRS()->nameStr(), GeographicCRS::EPSG_4326->nameStr());
+
+ ASSERT_TRUE(crs->transformation()->sourceCRS() != nullptr);
+ EXPECT_EQ(crs->transformation()->sourceCRS()->nameStr(),
+ projcrs->baseCRS()->nameStr());
+
+ auto params = crs->transformation()->getTOWGS84Parameters();
+ auto expected = std::vector<double>{1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0};
+ ASSERT_EQ(params.size(), expected.size());
+ for (int i = 0; i < 7; i++) {
+ EXPECT_NEAR(params[i], expected[i], 1e-10);
+ }
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(wkt_parse, BOUNDCRS_transformation_from_codes) {
+
+ auto projcrs = ProjectedCRS::create(
+ PropertyMap().set(IdentifiedObject::NAME_KEY, "my PROJCRS"),
+ GeographicCRS::create(
+ PropertyMap().set(IdentifiedObject::NAME_KEY, "my GEOGCRS"),
+ GeodeticReferenceFrame::EPSG_6326,
+ EllipsoidalCS::createLatitudeLongitude(UnitOfMeasure::DEGREE)),
+ Conversion::createUTM(PropertyMap(), 31, true),
+ CartesianCS::createEastingNorthing(UnitOfMeasure::METRE));
+
+ auto wkt =
+ "BOUNDCRS[SOURCECRS[" +
+ projcrs->exportToWKT(WKTFormatter::create().get()) + "],\n" +
+ "TARGETCRS[" +
+ GeographicCRS::EPSG_4326->exportToWKT(WKTFormatter::create().get()) +
+ "],\n"
+ " ABRIDGEDTRANSFORMATION[\"Transformation to WGS84\",\n"
+ " METHOD[\"bla\",ID[\"EPSG\",1032]],\n"
+ " PARAMETER[\"tx\",1,ID[\"EPSG\",8605]],\n"
+ " PARAMETER[\"ty\",2,ID[\"EPSG\",8606]],\n"
+ " PARAMETER[\"tz\",3,ID[\"EPSG\",8607]],\n"
+ " PARAMETER[\"rotx\",-4,ID[\"EPSG\",8608]],\n"
+ " PARAMETER[\"roty\",-5,ID[\"EPSG\",8609]],\n"
+ " PARAMETER[\"rotz\",-6,ID[\"EPSG\",8610]],\n"
+ " PARAMETER[\"scale\",1.000007,ID[\"EPSG\",8611]]]]";
+
+ auto obj = WKTParser().createFromWKT(wkt);
+ auto crs = nn_dynamic_pointer_cast<BoundCRS>(obj);
+ ASSERT_TRUE(crs != nullptr);
+
+ EXPECT_EQ(crs->baseCRS()->nameStr(), projcrs->nameStr());
+
+ EXPECT_EQ(crs->hubCRS()->nameStr(), GeographicCRS::EPSG_4326->nameStr());
+
+ ASSERT_TRUE(crs->transformation()->sourceCRS() != nullptr);
+ EXPECT_EQ(crs->transformation()->sourceCRS()->nameStr(),
+ projcrs->baseCRS()->nameStr());
+
+ auto params = crs->transformation()->getTOWGS84Parameters();
+ auto expected = std::vector<double>{1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0};
+ ASSERT_EQ(params.size(), expected.size());
+ for (int i = 0; i < 7; i++) {
+ EXPECT_NEAR(params[i], expected[i], 1e-10);
+ }
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(wkt_parse, geogcs_TOWGS84_3terms) {
+ auto wkt = "GEOGCS[\"my GEOGCRS\",\n"
+ " DATUM[\"WGS_1984\",\n"
+ " SPHEROID[\"WGS 84\",6378137,298.257223563],\n"
+ " TOWGS84[1,2,3]],\n"
+ " PRIMEM[\"Greenwich\",0],\n"
+ " UNIT[\"degree\",0.0174532925199433]]";
+
+ auto obj = WKTParser().createFromWKT(wkt);
+ auto crs = nn_dynamic_pointer_cast<BoundCRS>(obj);
+ ASSERT_TRUE(crs != nullptr);
+
+ EXPECT_EQ(crs->baseCRS()->nameStr(), "my GEOGCRS");
+
+ EXPECT_EQ(crs->hubCRS()->nameStr(), GeographicCRS::EPSG_4326->nameStr());
+
+ ASSERT_TRUE(crs->transformation()->sourceCRS() != nullptr);
+ EXPECT_EQ(crs->transformation()->sourceCRS()->nameStr(), "my GEOGCRS");
+
+ auto params = crs->transformation()->getTOWGS84Parameters();
+ auto expected = std::vector<double>{1.0, 2.0, 3.0, 0.0, 0.0, 0.0, 0.0};
+ ASSERT_EQ(params.size(), expected.size());
+ for (int i = 0; i < 7; i++) {
+ EXPECT_NEAR(params[i], expected[i], 1e-10);
+ }
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(wkt_parse, projcs_TOWGS84_7terms) {
+ auto wkt = "PROJCS[\"my PROJCRS\",\n"
+ " GEOGCS[\"my GEOGCRS\",\n"
+ " DATUM[\"WGS_1984\",\n"
+ " SPHEROID[\"WGS 84\",6378137,298.257223563,\n"
+ " AUTHORITY[\"EPSG\",\"7030\"]],\n"
+ " TOWGS84[1,2,3,4,5,6,7],\n"
+ " AUTHORITY[\"EPSG\",\"6326\"]],\n"
+ " PRIMEM[\"Greenwich\",0,\n"
+ " AUTHORITY[\"EPSG\",\"8901\"]],\n"
+ " UNIT[\"degree\",0.0174532925199433,\n"
+ " AUTHORITY[\"EPSG\",9122]],\n"
+ " AXIS[\"Latitude\",NORTH],\n"
+ " AXIS[\"Longitude\",EAST]],\n"
+ " PROJECTION[\"Transverse_Mercator\"],\n"
+ " PARAMETER[\"latitude_of_origin\",0],\n"
+ " PARAMETER[\"central_meridian\",3],\n"
+ " PARAMETER[\"scale_factor\",0.9996],\n"
+ " PARAMETER[\"false_easting\",500000],\n"
+ " PARAMETER[\"false_northing\",0],\n"
+ " UNIT[\"metre\",1,\n"
+ " AUTHORITY[\"EPSG\",9001]],\n"
+ " AXIS[\"Easting\",EAST],\n"
+ " AXIS[\"Northing\",NORTH]]";
+
+ auto obj = WKTParser().createFromWKT(wkt);
+ auto crs = nn_dynamic_pointer_cast<BoundCRS>(obj);
+ ASSERT_TRUE(crs != nullptr);
+
+ EXPECT_EQ(crs->baseCRS()->nameStr(), "my PROJCRS");
+
+ EXPECT_EQ(crs->hubCRS()->nameStr(), GeographicCRS::EPSG_4326->nameStr());
+
+ ASSERT_TRUE(crs->transformation()->sourceCRS() != nullptr);
+ EXPECT_EQ(crs->transformation()->sourceCRS()->nameStr(), "my GEOGCRS");
+
+ auto params = crs->transformation()->getTOWGS84Parameters();
+ auto expected = std::vector<double>{1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0};
+ ASSERT_EQ(params.size(), expected.size());
+ for (int i = 0; i < 7; i++) {
+ EXPECT_NEAR(params[i], expected[i], 1e-10);
+ }
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(wkt_parse, WKT1_VERT_DATUM_EXTENSION) {
+ auto wkt = "VERT_CS[\"EGM2008 geoid height\",\n"
+ " VERT_DATUM[\"EGM2008 geoid\",2005,\n"
+ " EXTENSION[\"PROJ4_GRIDS\",\"egm08_25.gtx\"],\n"
+ " AUTHORITY[\"EPSG\",\"1027\"]],\n"
+ " UNIT[\"metre\",1,\n"
+ " AUTHORITY[\"EPSG\",\"9001\"]],\n"
+ " AXIS[\"Up\",UP],\n"
+ " AUTHORITY[\"EPSG\",\"3855\"]]";
+
+ auto obj = WKTParser().createFromWKT(wkt);
+ auto crs = nn_dynamic_pointer_cast<BoundCRS>(obj);
+ ASSERT_TRUE(crs != nullptr);
+
+ EXPECT_EQ(crs->baseCRS()->nameStr(), "EGM2008 geoid height");
+
+ EXPECT_EQ(crs->hubCRS()->nameStr(), GeographicCRS::EPSG_4979->nameStr());
+
+ ASSERT_TRUE(crs->transformation()->sourceCRS() != nullptr);
+ EXPECT_EQ(crs->transformation()->sourceCRS()->nameStr(),
+ crs->baseCRS()->nameStr());
+
+ ASSERT_TRUE(crs->transformation()->targetCRS() != nullptr);
+ EXPECT_EQ(crs->transformation()->targetCRS()->nameStr(),
+ crs->hubCRS()->nameStr());
+
+ EXPECT_EQ(crs->transformation()->nameStr(),
+ "EGM2008 geoid height to WGS84 ellipsoidal height");
+ EXPECT_EQ(crs->transformation()->method()->nameStr(),
+ "GravityRelatedHeight to Geographic3D");
+ ASSERT_EQ(crs->transformation()->parameterValues().size(), 1);
+ {
+ const auto &opParamvalue =
+ nn_dynamic_pointer_cast<OperationParameterValue>(
+ crs->transformation()->parameterValues()[0]);
+ ASSERT_TRUE(opParamvalue);
+ const auto &paramName = opParamvalue->parameter()->nameStr();
+ const auto &parameterValue = opParamvalue->parameterValue();
+ EXPECT_TRUE(opParamvalue->parameter()->getEPSGCode() == 8666);
+ EXPECT_EQ(paramName, "Geoid (height correction) model file");
+ EXPECT_EQ(parameterValue->type(), ParameterValue::Type::FILENAME);
+ EXPECT_EQ(parameterValue->valueFile(), "egm08_25.gtx");
+ }
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(wkt_parse, WKT1_DATUM_EXTENSION) {
+ auto wkt =
+ "PROJCS[\"unnamed\",\n"
+ " GEOGCS[\"International 1909 (Hayford)\",\n"
+ " DATUM[\"unknown\",\n"
+ " SPHEROID[\"intl\",6378388,297],\n"
+ " EXTENSION[\"PROJ4_GRIDS\",\"nzgd2kgrid0005.gsb\"]],\n"
+ " PRIMEM[\"Greenwich\",0],\n"
+ " UNIT[\"degree\",0.0174532925199433]],\n"
+ " PROJECTION[\"New_Zealand_Map_Grid\"],\n"
+ " PARAMETER[\"latitude_of_origin\",-41],\n"
+ " PARAMETER[\"central_meridian\",173],\n"
+ " PARAMETER[\"false_easting\",2510000],\n"
+ " PARAMETER[\"false_northing\",6023150],\n"
+ " UNIT[\"Meter\",1]]";
+
+ auto obj = WKTParser().createFromWKT(wkt);
+ auto crs = nn_dynamic_pointer_cast<BoundCRS>(obj);
+ ASSERT_TRUE(crs != nullptr);
+
+ EXPECT_EQ(crs->baseCRS()->nameStr(), "unnamed");
+
+ EXPECT_EQ(crs->hubCRS()->nameStr(), GeographicCRS::EPSG_4326->nameStr());
+
+ ASSERT_TRUE(crs->transformation()->sourceCRS() != nullptr);
+ EXPECT_EQ(crs->transformation()->sourceCRS()->nameStr(),
+ crs->baseCRS()->nameStr());
+
+ ASSERT_TRUE(crs->transformation()->targetCRS() != nullptr);
+ EXPECT_EQ(crs->transformation()->targetCRS()->nameStr(),
+ crs->hubCRS()->nameStr());
+
+ EXPECT_EQ(crs->transformation()->nameStr(),
+ "International 1909 (Hayford) to WGS84");
+ EXPECT_EQ(crs->transformation()->method()->nameStr(), "NTv2");
+ ASSERT_EQ(crs->transformation()->parameterValues().size(), 1);
+ {
+ const auto &opParamvalue =
+ nn_dynamic_pointer_cast<OperationParameterValue>(
+ crs->transformation()->parameterValues()[0]);
+ ASSERT_TRUE(opParamvalue);
+ const auto &paramName = opParamvalue->parameter()->nameStr();
+ const auto &parameterValue = opParamvalue->parameterValue();
+ EXPECT_TRUE(opParamvalue->parameter()->getEPSGCode() == 8656);
+ EXPECT_EQ(paramName, "Latitude and longitude difference file");
+ EXPECT_EQ(parameterValue->type(), ParameterValue::Type::FILENAME);
+ EXPECT_EQ(parameterValue->valueFile(), "nzgd2kgrid0005.gsb");
+ }
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(wkt_parse, DerivedGeographicCRS_WKT2) {
+ auto wkt = "GEODCRS[\"WMO Atlantic Pole\",\n"
+ " BASEGEODCRS[\"WGS 84\",\n"
+ " DATUM[\"World Geodetic System 1984\",\n"
+ " ELLIPSOID[\"WGS 84\",6378137,298.257223563,\n"
+ " LENGTHUNIT[\"metre\",1]]],\n"
+ " PRIMEM[\"Greenwich\",0,\n"
+ " ANGLEUNIT[\"degree\",0.0174532925199433]]],\n"
+ " DERIVINGCONVERSION[\"Atlantic pole\",\n"
+ " METHOD[\"Pole rotation\"],\n"
+ " PARAMETER[\"Latitude of rotated pole\",52,\n"
+ " ANGLEUNIT[\"degree\",0.0174532925199433,\n"
+ " ID[\"EPSG\",9122]]],\n"
+ " PARAMETER[\"Longitude of rotated pole\",-30,\n"
+ " ANGLEUNIT[\"degree\",0.0174532925199433,\n"
+ " ID[\"EPSG\",9122]]],\n"
+ " PARAMETER[\"Axis rotation\",-25,\n"
+ " ANGLEUNIT[\"degree\",0.0174532925199433,\n"
+ " ID[\"EPSG\",9122]]]],\n"
+ " CS[ellipsoidal,2],\n"
+ " AXIS[\"latitude\",north,\n"
+ " ORDER[1],\n"
+ " ANGLEUNIT[\"degree\",0.0174532925199433,\n"
+ " ID[\"EPSG\",9122]]],\n"
+ " AXIS[\"longitude\",east,\n"
+ " ORDER[2],\n"
+ " ANGLEUNIT[\"degree\",0.0174532925199433,\n"
+ " ID[\"EPSG\",9122]]]]";
+
+ auto obj = WKTParser().createFromWKT(wkt);
+ auto crs = nn_dynamic_pointer_cast<DerivedGeographicCRS>(obj);
+ ASSERT_TRUE(crs != nullptr);
+
+ EXPECT_EQ(crs->nameStr(), "WMO Atlantic Pole");
+
+ EXPECT_EQ(crs->baseCRS()->nameStr(), "WGS 84");
+ EXPECT_TRUE(nn_dynamic_pointer_cast<GeographicCRS>(crs->baseCRS()) !=
+ nullptr);
+
+ EXPECT_EQ(crs->derivingConversion()->nameStr(), "Atlantic pole");
+
+ EXPECT_TRUE(nn_dynamic_pointer_cast<EllipsoidalCS>(
+ crs->coordinateSystem()) != nullptr);
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(wkt_parse, DerivedGeographicCRS_WKT2_2018) {
+ auto wkt = "GEOGCRS[\"WMO Atlantic Pole\",\n"
+ " BASEGEOGCRS[\"WGS 84\",\n"
+ " DATUM[\"World Geodetic System 1984\",\n"
+ " ELLIPSOID[\"WGS 84\",6378137,298.257223563,\n"
+ " LENGTHUNIT[\"metre\",1]]],\n"
+ " PRIMEM[\"Greenwich\",0,\n"
+ " ANGLEUNIT[\"degree\",0.0174532925199433]]],\n"
+ " DERIVINGCONVERSION[\"Atlantic pole\",\n"
+ " METHOD[\"Pole rotation\"],\n"
+ " PARAMETER[\"Latitude of rotated pole\",52,\n"
+ " ANGLEUNIT[\"degree\",0.0174532925199433,\n"
+ " ID[\"EPSG\",9122]]],\n"
+ " PARAMETER[\"Longitude of rotated pole\",-30,\n"
+ " ANGLEUNIT[\"degree\",0.0174532925199433,\n"
+ " ID[\"EPSG\",9122]]],\n"
+ " PARAMETER[\"Axis rotation\",-25,\n"
+ " ANGLEUNIT[\"degree\",0.0174532925199433,\n"
+ " ID[\"EPSG\",9122]]]],\n"
+ " CS[ellipsoidal,2],\n"
+ " AXIS[\"latitude\",north,\n"
+ " ORDER[1],\n"
+ " ANGLEUNIT[\"degree\",0.0174532925199433,\n"
+ " ID[\"EPSG\",9122]]],\n"
+ " AXIS[\"longitude\",east,\n"
+ " ORDER[2],\n"
+ " ANGLEUNIT[\"degree\",0.0174532925199433,\n"
+ " ID[\"EPSG\",9122]]]]";
+
+ auto obj = WKTParser().createFromWKT(wkt);
+ auto crs = nn_dynamic_pointer_cast<DerivedGeographicCRS>(obj);
+ ASSERT_TRUE(crs != nullptr);
+
+ EXPECT_EQ(crs->nameStr(), "WMO Atlantic Pole");
+
+ EXPECT_EQ(crs->baseCRS()->nameStr(), "WGS 84");
+ EXPECT_TRUE(nn_dynamic_pointer_cast<GeographicCRS>(crs->baseCRS()) !=
+ nullptr);
+
+ EXPECT_EQ(crs->derivingConversion()->nameStr(), "Atlantic pole");
+
+ EXPECT_TRUE(nn_dynamic_pointer_cast<EllipsoidalCS>(
+ crs->coordinateSystem()) != nullptr);
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(wkt_parse, DerivedGeodeticCRS) {
+ auto wkt = "GEODCRS[\"Derived geodetic CRS\",\n"
+ " BASEGEODCRS[\"WGS 84\",\n"
+ " DATUM[\"World Geodetic System 1984\",\n"
+ " ELLIPSOID[\"WGS 84\",6378137,298.257223563,\n"
+ " LENGTHUNIT[\"metre\",1]]],\n"
+ " PRIMEM[\"Greenwich\",0,\n"
+ " ANGLEUNIT[\"degree\",0.0174532925199433]]],\n"
+ " DERIVINGCONVERSION[\"Some conversion\",\n"
+ " METHOD[\"Some method\"]],\n"
+ " CS[Cartesian,3],\n"
+ " AXIS[\"(X)\",geocentricX,\n"
+ " ORDER[1],\n"
+ " LENGTHUNIT[\"metre\",1,\n"
+ " ID[\"EPSG\",9001]]],\n"
+ " AXIS[\"(Y)\",geocentricY,\n"
+ " ORDER[2],\n"
+ " LENGTHUNIT[\"metre\",1,\n"
+ " ID[\"EPSG\",9001]]],\n"
+ " AXIS[\"(Z)\",geocentricZ,\n"
+ " ORDER[3],\n"
+ " LENGTHUNIT[\"metre\",1,\n"
+ " ID[\"EPSG\",9001]]]]";
+ ;
+
+ auto obj = WKTParser().createFromWKT(wkt);
+ auto crs = nn_dynamic_pointer_cast<DerivedGeodeticCRS>(obj);
+ ASSERT_TRUE(crs != nullptr);
+
+ EXPECT_EQ(crs->nameStr(), "Derived geodetic CRS");
+
+ EXPECT_EQ(crs->baseCRS()->nameStr(), "WGS 84");
+ EXPECT_TRUE(nn_dynamic_pointer_cast<GeographicCRS>(crs->baseCRS()) !=
+ nullptr);
+
+ EXPECT_EQ(crs->derivingConversion()->nameStr(), "Some conversion");
+
+ EXPECT_TRUE(nn_dynamic_pointer_cast<CartesianCS>(crs->coordinateSystem()) !=
+ nullptr);
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(wkt_parse, DerivedProjectedCRS) {
+ auto wkt =
+ "DERIVEDPROJCRS[\"derived projectedCRS\",\n"
+ " BASEPROJCRS[\"WGS 84 / UTM zone 31N\",\n"
+ " BASEGEOGCRS[\"WGS 84\",\n"
+ " DATUM[\"World Geodetic System 1984\",\n"
+ " ELLIPSOID[\"WGS 84\",6378137,298.257223563,\n"
+ " LENGTHUNIT[\"metre\",1]]],\n"
+ " PRIMEM[\"Greenwich\",0,\n"
+ " ANGLEUNIT[\"degree\",0.0174532925199433]]],\n"
+ " CONVERSION[\"UTM zone 31N\",\n"
+ " METHOD[\"Transverse Mercator\",\n"
+ " ID[\"EPSG\",9807]],\n"
+ " PARAMETER[\"Latitude of natural origin\",0,\n"
+ " ANGLEUNIT[\"degree\",0.0174532925199433],\n"
+ " ID[\"EPSG\",8801]],\n"
+ " PARAMETER[\"Longitude of natural origin\",3,\n"
+ " ANGLEUNIT[\"degree\",0.0174532925199433],\n"
+ " ID[\"EPSG\",8802]],\n"
+ " PARAMETER[\"Scale factor at natural origin\",0.9996,\n"
+ " SCALEUNIT[\"unity\",1],\n"
+ " ID[\"EPSG\",8805]],\n"
+ " PARAMETER[\"False easting\",500000,\n"
+ " LENGTHUNIT[\"metre\",1],\n"
+ " ID[\"EPSG\",8806]],\n"
+ " PARAMETER[\"False northing\",0,\n"
+ " LENGTHUNIT[\"metre\",1],\n"
+ " ID[\"EPSG\",8807]]]],\n"
+ " DERIVINGCONVERSION[\"unnamed\",\n"
+ " METHOD[\"PROJ unimplemented\"]],\n"
+ " CS[Cartesian,2],\n"
+ " AXIS[\"(E)\",east,\n"
+ " ORDER[1],\n"
+ " LENGTHUNIT[\"metre\",1,\n"
+ " ID[\"EPSG\",9001]]],\n"
+ " AXIS[\"(N)\",north,\n"
+ " ORDER[2],\n"
+ " LENGTHUNIT[\"metre\",1,\n"
+ " ID[\"EPSG\",9001]]]]";
+
+ auto obj = WKTParser().createFromWKT(wkt);
+ auto crs = nn_dynamic_pointer_cast<DerivedProjectedCRS>(obj);
+ ASSERT_TRUE(crs != nullptr);
+
+ EXPECT_EQ(crs->nameStr(), "derived projectedCRS");
+
+ EXPECT_EQ(crs->baseCRS()->nameStr(), "WGS 84 / UTM zone 31N");
+ EXPECT_TRUE(nn_dynamic_pointer_cast<ProjectedCRS>(crs->baseCRS()) !=
+ nullptr);
+
+ EXPECT_EQ(crs->derivingConversion()->nameStr(), "unnamed");
+
+ EXPECT_TRUE(nn_dynamic_pointer_cast<CartesianCS>(crs->coordinateSystem()) !=
+ nullptr);
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(wkt_parse, DerivedProjectedCRS_ordinal) {
+ auto wkt = "DERIVEDPROJCRS[\"derived projectedCRS\",\n"
+ " BASEPROJCRS[\"BASEPROJCRS\",\n"
+ " BASEGEOGCRS[\"WGS 84\",\n"
+ " DATUM[\"World Geodetic System 1984\",\n"
+ " ELLIPSOID[\"WGS 84\",6378137,298.257223563,\n"
+ " LENGTHUNIT[\"metre\",1]]],\n"
+ " PRIMEM[\"Greenwich\",0,\n"
+ " ANGLEUNIT[\"degree\",0.0174532925199433],\n"
+ " ID[\"EPSG\",8901]]],\n"
+ " CONVERSION[\"unnamed\",\n"
+ " METHOD[\"PROJ unimplemented\"]]],\n"
+ " DERIVINGCONVERSION[\"unnamed\",\n"
+ " METHOD[\"PROJ unimplemented\"]],\n"
+ " CS[ordinal,2],\n"
+ " AXIS[\"inline (I)\",northNorthWest,\n"
+ " ORDER[1]],\n"
+ " AXIS[\"crossline (J)\",westSouthWest,\n"
+ " ORDER[2]]]";
+
+ auto obj = WKTParser().createFromWKT(wkt);
+ auto crs = nn_dynamic_pointer_cast<DerivedProjectedCRS>(obj);
+ ASSERT_TRUE(crs != nullptr);
+ EXPECT_TRUE(nn_dynamic_pointer_cast<OrdinalCS>(crs->coordinateSystem()) !=
+ nullptr);
+
+ EXPECT_EQ(
+ crs->exportToWKT(
+ WKTFormatter::create(WKTFormatter::Convention::WKT2_2018).get()),
+ wkt);
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(wkt_parse, TemporalDatum) {
+ auto wkt = "TDATUM[\"Gregorian calendar\",\n"
+ " CALENDAR[\"my calendar\"],\n"
+ " TIMEORIGIN[0000-01-01]]";
+
+ auto obj = WKTParser().createFromWKT(wkt);
+ auto tdatum = nn_dynamic_pointer_cast<TemporalDatum>(obj);
+ ASSERT_TRUE(tdatum != nullptr);
+
+ EXPECT_EQ(tdatum->nameStr(), "Gregorian calendar");
+ EXPECT_EQ(tdatum->temporalOrigin().toString(), "0000-01-01");
+ EXPECT_EQ(tdatum->calendar(), "my calendar");
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(wkt_parse, TemporalDatum_no_calendar) {
+ auto wkt = "TDATUM[\"Gregorian calendar\",\n"
+ " TIMEORIGIN[0000-01-01]]";
+
+ auto obj = WKTParser().createFromWKT(wkt);
+ auto tdatum = nn_dynamic_pointer_cast<TemporalDatum>(obj);
+ ASSERT_TRUE(tdatum != nullptr);
+
+ EXPECT_EQ(tdatum->nameStr(), "Gregorian calendar");
+ EXPECT_EQ(tdatum->temporalOrigin().toString(), "0000-01-01");
+ EXPECT_EQ(tdatum->calendar(), "proleptic Gregorian");
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(wkt_parse, dateTimeTemporalCRS_WKT2) {
+ auto wkt = "TIMECRS[\"Temporal CRS\",\n"
+ " TDATUM[\"Gregorian calendar\",\n"
+ " TIMEORIGIN[0000-01-01]],\n"
+ " CS[temporal,1],\n"
+ " AXIS[\"time (T)\",future]]";
+
+ auto obj = WKTParser().createFromWKT(wkt);
+ auto crs = nn_dynamic_pointer_cast<TemporalCRS>(obj);
+ ASSERT_TRUE(crs != nullptr);
+
+ EXPECT_EQ(crs->nameStr(), "Temporal CRS");
+ auto tdatum = crs->datum();
+ EXPECT_EQ(tdatum->nameStr(), "Gregorian calendar");
+ EXPECT_EQ(tdatum->temporalOrigin().toString(), "0000-01-01");
+ EXPECT_EQ(tdatum->calendar(), "proleptic Gregorian");
+ EXPECT_TRUE(nn_dynamic_pointer_cast<DateTimeTemporalCS>(
+ crs->coordinateSystem()) != nullptr);
+ ASSERT_EQ(crs->coordinateSystem()->axisList().size(), 1);
+ EXPECT_EQ(crs->coordinateSystem()->axisList()[0]->unit().type(),
+ UnitOfMeasure::Type::NONE);
+ EXPECT_EQ(crs->coordinateSystem()->axisList()[0]->unit().name(), "");
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(wkt_parse, dateTimeTemporalCRS_WKT2_2018) {
+ auto wkt = "TIMECRS[\"Temporal CRS\",\n"
+ " TDATUM[\"Gregorian calendar\",\n"
+ " CALENDAR[\"proleptic Gregorian\"],\n"
+ " TIMEORIGIN[0000-01-01]],\n"
+ " CS[TemporalDateTime,1],\n"
+ " AXIS[\"time (T)\",future]]";
+
+ auto obj = WKTParser().createFromWKT(wkt);
+ auto crs = nn_dynamic_pointer_cast<TemporalCRS>(obj);
+ ASSERT_TRUE(crs != nullptr);
+
+ EXPECT_EQ(crs->nameStr(), "Temporal CRS");
+ auto tdatum = crs->datum();
+ EXPECT_EQ(tdatum->nameStr(), "Gregorian calendar");
+ EXPECT_EQ(tdatum->temporalOrigin().toString(), "0000-01-01");
+ EXPECT_EQ(tdatum->calendar(), "proleptic Gregorian");
+ EXPECT_TRUE(nn_dynamic_pointer_cast<DateTimeTemporalCS>(
+ crs->coordinateSystem()) != nullptr);
+ ASSERT_EQ(crs->coordinateSystem()->axisList().size(), 1);
+ EXPECT_EQ(crs->coordinateSystem()->axisList()[0]->unit().type(),
+ UnitOfMeasure::Type::NONE);
+ EXPECT_EQ(crs->coordinateSystem()->axisList()[0]->unit().name(), "");
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(wkt_parse, temporalCountCRSWithConvFactor_WKT2_2018) {
+ auto wkt = "TIMECRS[\"GPS milliseconds\",\n"
+ " TDATUM[\"GPS time origin\",\n"
+ " TIMEORIGIN[1980-01-01T00:00:00.0Z]],\n"
+ " CS[TemporalCount,1],\n"
+ " AXIS[\"(T)\",future,\n"
+ " TIMEUNIT[\"milliseconds (ms)\",0.001]]]";
+
+ auto obj = WKTParser().createFromWKT(wkt);
+ auto crs = nn_dynamic_pointer_cast<TemporalCRS>(obj);
+ ASSERT_TRUE(crs != nullptr);
+
+ EXPECT_EQ(crs->nameStr(), "GPS milliseconds");
+ auto tdatum = crs->datum();
+ EXPECT_EQ(tdatum->nameStr(), "GPS time origin");
+ EXPECT_EQ(tdatum->temporalOrigin().toString(), "1980-01-01T00:00:00.0Z");
+ EXPECT_EQ(tdatum->calendar(), "proleptic Gregorian");
+ EXPECT_TRUE(nn_dynamic_pointer_cast<TemporalCountCS>(
+ crs->coordinateSystem()) != nullptr);
+ ASSERT_EQ(crs->coordinateSystem()->axisList().size(), 1);
+ EXPECT_EQ(crs->coordinateSystem()->axisList()[0]->unit().name(),
+ "milliseconds (ms)");
+ EXPECT_EQ(crs->coordinateSystem()->axisList()[0]->unit().conversionToSI(),
+ 0.001);
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(wkt_parse, temporalCountCRSWithoutConvFactor_WKT2_2018) {
+ auto wkt = "TIMECRS[\"Calendar hours from 1979-12-29\",\n"
+ " TDATUM[\"29 December 1979\",\n"
+ " CALENDAR[\"proleptic Gregorian\"],\n"
+ " TIMEORIGIN[1979-12-29T00]],\n"
+ " CS[TemporalCount,1],\n"
+ " AXIS[\"time\",future,\n"
+ " TIMEUNIT[\"hour\"]]]";
+
+ auto obj = WKTParser().createFromWKT(wkt);
+ auto crs = nn_dynamic_pointer_cast<TemporalCRS>(obj);
+ ASSERT_TRUE(crs != nullptr);
+
+ EXPECT_EQ(crs->nameStr(), "Calendar hours from 1979-12-29");
+ auto tdatum = crs->datum();
+ EXPECT_EQ(tdatum->nameStr(), "29 December 1979");
+ EXPECT_EQ(tdatum->temporalOrigin().toString(), "1979-12-29T00");
+ EXPECT_TRUE(nn_dynamic_pointer_cast<TemporalCountCS>(
+ crs->coordinateSystem()) != nullptr);
+ ASSERT_EQ(crs->coordinateSystem()->axisList().size(), 1);
+ EXPECT_EQ(crs->coordinateSystem()->axisList()[0]->unit().name(), "hour");
+ EXPECT_EQ(crs->coordinateSystem()->axisList()[0]->unit().conversionToSI(),
+ 0.0);
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(wkt_parse, temporalMeasureCRSWithoutConvFactor_WKT2_2018) {
+ auto wkt = "TIMECRS[\"Decimal Years CE\",\n"
+ " TIMEDATUM[\"Common Era\",\n"
+ " TIMEORIGIN[0000]],\n"
+ " CS[TemporalMeasure,1],\n"
+ " AXIS[\"decimal years (a)\",future,\n"
+ " TIMEUNIT[\"year\"]]]";
+
+ auto obj = WKTParser().createFromWKT(wkt);
+ auto crs = nn_dynamic_pointer_cast<TemporalCRS>(obj);
+ ASSERT_TRUE(crs != nullptr);
+
+ EXPECT_EQ(crs->nameStr(), "Decimal Years CE");
+ auto tdatum = crs->datum();
+ EXPECT_EQ(tdatum->nameStr(), "Common Era");
+ EXPECT_EQ(tdatum->temporalOrigin().toString(), "0000");
+ EXPECT_TRUE(nn_dynamic_pointer_cast<TemporalMeasureCS>(
+ crs->coordinateSystem()) != nullptr);
+ auto cs = crs->coordinateSystem();
+ ASSERT_EQ(cs->axisList().size(), 1);
+ auto axis = cs->axisList()[0];
+ EXPECT_EQ(axis->nameStr(), "Decimal years");
+ EXPECT_EQ(axis->unit().name(), "year");
+ EXPECT_EQ(axis->unit().conversionToSI(), 0.0);
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(wkt_parse, EDATUM) {
+ auto wkt = "EDATUM[\"Engineering datum\",\n"
+ " ANCHOR[\"my anchor\"]]";
+
+ auto obj = WKTParser().createFromWKT(wkt);
+ auto edatum = nn_dynamic_pointer_cast<EngineeringDatum>(obj);
+ ASSERT_TRUE(edatum != nullptr);
+
+ EXPECT_EQ(edatum->nameStr(), "Engineering datum");
+ auto anchor = edatum->anchorDefinition();
+ EXPECT_TRUE(anchor.has_value());
+ EXPECT_EQ(*anchor, "my anchor");
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(wkt_parse, ENGINEERINGDATUM) {
+ auto wkt = "ENGINEERINGDATUM[\"Engineering datum\"]";
+
+ auto obj = WKTParser().createFromWKT(wkt);
+ auto edatum = nn_dynamic_pointer_cast<EngineeringDatum>(obj);
+ ASSERT_TRUE(edatum != nullptr);
+
+ EXPECT_EQ(edatum->nameStr(), "Engineering datum");
+ auto anchor = edatum->anchorDefinition();
+ EXPECT_TRUE(!anchor.has_value());
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(wkt_parse, ENGCRS) {
+ auto wkt = "ENGCRS[\"Engineering CRS\",\n"
+ " EDATUM[\"Engineering datum\"],\n"
+ " CS[Cartesian,2],\n"
+ " AXIS[\"(E)\",east,\n"
+ " ORDER[1],\n"
+ " LENGTHUNIT[\"metre\",1,\n"
+ " ID[\"EPSG\",9001]]],\n"
+ " AXIS[\"(N)\",north,\n"
+ " ORDER[2],\n"
+ " LENGTHUNIT[\"metre\",1,\n"
+ " ID[\"EPSG\",9001]]]]";
+
+ auto obj = WKTParser().createFromWKT(wkt);
+ auto crs = nn_dynamic_pointer_cast<EngineeringCRS>(obj);
+ ASSERT_TRUE(crs != nullptr);
+
+ EXPECT_EQ(crs->nameStr(), "Engineering CRS");
+ EXPECT_EQ(crs->datum()->nameStr(), "Engineering datum");
+ auto cs = crs->coordinateSystem();
+ ASSERT_EQ(cs->axisList().size(), 2);
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(wkt_parse, ENGINEERINGCRS) {
+ auto wkt = "ENGINEERINGCRS[\"Engineering CRS\",\n"
+ " ENGINEERINGDATUM[\"Engineering datum\"],\n"
+ " CS[Cartesian,2],\n"
+ " AXIS[\"(E)\",east,\n"
+ " ORDER[1],\n"
+ " LENGTHUNIT[\"metre\",1,\n"
+ " ID[\"EPSG\",9001]]],\n"
+ " AXIS[\"(N)\",north,\n"
+ " ORDER[2],\n"
+ " LENGTHUNIT[\"metre\",1,\n"
+ " ID[\"EPSG\",9001]]]]";
+
+ auto obj = WKTParser().createFromWKT(wkt);
+ auto crs = nn_dynamic_pointer_cast<EngineeringCRS>(obj);
+ ASSERT_TRUE(crs != nullptr);
+
+ EXPECT_EQ(crs->nameStr(), "Engineering CRS");
+ EXPECT_EQ(crs->datum()->nameStr(), "Engineering datum");
+ auto cs = crs->coordinateSystem();
+ ASSERT_EQ(cs->axisList().size(), 2);
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(wkt_parse, LOCAL_CS_short) {
+ auto wkt = "LOCAL_CS[\"Engineering CRS\"]";
+
+ auto obj = WKTParser().createFromWKT(wkt);
+ auto crs = nn_dynamic_pointer_cast<EngineeringCRS>(obj);
+ ASSERT_TRUE(crs != nullptr);
+
+ EXPECT_EQ(crs->nameStr(), "Engineering CRS");
+ EXPECT_FALSE(!crs->datum()->nameStr().empty());
+ auto cs = crs->coordinateSystem();
+ ASSERT_EQ(cs->axisList().size(), 2);
+
+ EXPECT_EQ(
+ crs->exportToWKT(
+ WKTFormatter::create(WKTFormatter::Convention::WKT1_GDAL).get()),
+ wkt);
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(wkt_parse, LOCAL_CS_long_one_aix) {
+ auto wkt = "LOCAL_CS[\"Engineering CRS\",\n"
+ " LOCAL_DATUM[\"Engineering datum\",12345],\n"
+ " AXIS[\"height\",up]]";
+
+ auto obj = WKTParser().createFromWKT(wkt);
+ auto crs = nn_dynamic_pointer_cast<EngineeringCRS>(obj);
+ ASSERT_TRUE(crs != nullptr);
+
+ EXPECT_EQ(crs->nameStr(), "Engineering CRS");
+ EXPECT_EQ(crs->datum()->nameStr(), "Engineering datum");
+ auto cs = crs->coordinateSystem();
+ ASSERT_EQ(cs->axisList().size(), 1);
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(wkt_parse, LOCAL_CS_long_two_axis) {
+ auto wkt = "LOCAL_CS[\"Engineering CRS\",\n"
+ " LOCAL_DATUM[\"Engineering datum\",12345],\n"
+ " AXIS[\"Easting\",EAST],\n"
+ " AXIS[\"Northing\",NORTH]]";
+
+ auto obj = WKTParser().createFromWKT(wkt);
+ auto crs = nn_dynamic_pointer_cast<EngineeringCRS>(obj);
+ ASSERT_TRUE(crs != nullptr);
+
+ EXPECT_EQ(crs->nameStr(), "Engineering CRS");
+ EXPECT_EQ(crs->datum()->nameStr(), "Engineering datum");
+ auto cs = crs->coordinateSystem();
+ ASSERT_EQ(cs->axisList().size(), 2);
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(wkt_parse, PDATUM) {
+ auto wkt = "PDATUM[\"Parametric datum\",\n"
+ " ANCHOR[\"my anchor\"]]";
+
+ auto obj = WKTParser().createFromWKT(wkt);
+ auto datum = nn_dynamic_pointer_cast<ParametricDatum>(obj);
+ ASSERT_TRUE(datum != nullptr);
+
+ EXPECT_EQ(datum->nameStr(), "Parametric datum");
+ auto anchor = datum->anchorDefinition();
+ EXPECT_TRUE(anchor.has_value());
+ EXPECT_EQ(*anchor, "my anchor");
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(wkt_parse, PARAMETRICDATUM) {
+ auto wkt = "PARAMETRICDATUM[\"Parametric datum\",\n"
+ " ANCHOR[\"my anchor\"]]";
+
+ auto obj = WKTParser().createFromWKT(wkt);
+ auto datum = nn_dynamic_pointer_cast<ParametricDatum>(obj);
+ ASSERT_TRUE(datum != nullptr);
+
+ EXPECT_EQ(datum->nameStr(), "Parametric datum");
+ auto anchor = datum->anchorDefinition();
+ EXPECT_TRUE(anchor.has_value());
+ EXPECT_EQ(*anchor, "my anchor");
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(wkt_parse, PARAMETRICCRS) {
+ auto wkt =
+ "PARAMETRICCRS[\"WMO standard atmosphere layer 0\","
+ " PDATUM[\"Mean Sea Level\",ANCHOR[\"1013.25 hPa at 15°C\"]],"
+ " CS[parametric,1],"
+ " AXIS[\"pressure (hPa)\",up],"
+ " PARAMETRICUNIT[\"HectoPascal\",100.0]"
+ " ]";
+
+ auto obj = WKTParser().createFromWKT(wkt);
+ auto crs = nn_dynamic_pointer_cast<ParametricCRS>(obj);
+ ASSERT_TRUE(crs != nullptr);
+
+ EXPECT_EQ(crs->nameStr(), "WMO standard atmosphere layer 0");
+ EXPECT_EQ(crs->datum()->nameStr(), "Mean Sea Level");
+ auto cs = crs->coordinateSystem();
+ EXPECT_TRUE(nn_dynamic_pointer_cast<ParametricCS>(cs) != nullptr);
+ ASSERT_EQ(cs->axisList().size(), 1);
+ auto axis = cs->axisList()[0];
+ EXPECT_EQ(axis->nameStr(), "Pressure");
+ EXPECT_EQ(axis->unit().name(), "HectoPascal");
+ EXPECT_EQ(axis->unit().type(), UnitOfMeasure::Type::PARAMETRIC);
+ EXPECT_EQ(axis->unit().conversionToSI(), 100.0);
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(wkt_parse, PARAMETRICCRS_PARAMETRICDATUM) {
+ auto wkt = "PARAMETRICCRS[\"WMO standard atmosphere layer 0\","
+ " PARAMETRICDATUM[\"Mean Sea Level\"],"
+ " CS[parametric,1],"
+ " AXIS[\"pressure (hPa)\",up],"
+ " PARAMETRICUNIT[\"HectoPascal\",100.0]"
+ " ]";
+
+ auto obj = WKTParser().createFromWKT(wkt);
+ auto crs = nn_dynamic_pointer_cast<ParametricCRS>(obj);
+ ASSERT_TRUE(crs != nullptr);
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(wkt_parse, DerivedVerticalCRS) {
+ auto wkt = "VERTCRS[\"Derived vertCRS\",\n"
+ " BASEVERTCRS[\"ODN height\",\n"
+ " VDATUM[\"Ordnance Datum Newlyn\"]],\n"
+ " DERIVINGCONVERSION[\"unnamed\",\n"
+ " METHOD[\"PROJ unimplemented\"]],\n"
+ " CS[vertical,1],\n"
+ " AXIS[\"gravity-related height (H)\",up,\n"
+ " LENGTHUNIT[\"metre\",1,\n"
+ " ID[\"EPSG\",9001]]]]";
+
+ auto obj = WKTParser().createFromWKT(wkt);
+ auto crs = nn_dynamic_pointer_cast<DerivedVerticalCRS>(obj);
+ ASSERT_TRUE(crs != nullptr);
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(wkt_parse, DerivedEngineeringCRS) {
+
+ auto wkt = "ENGCRS[\"Derived EngineeringCRS\",\n"
+ " BASEENGCRS[\"Engineering CRS\",\n"
+ " EDATUM[\"Engineering datum\"]],\n"
+ " DERIVINGCONVERSION[\"unnamed\",\n"
+ " METHOD[\"PROJ unimplemented\"]],\n"
+ " CS[Cartesian,2],\n"
+ " AXIS[\"(E)\",east,\n"
+ " ORDER[1],\n"
+ " LENGTHUNIT[\"metre\",1,\n"
+ " ID[\"EPSG\",9001]]],\n"
+ " AXIS[\"(N)\",north,\n"
+ " ORDER[2],\n"
+ " LENGTHUNIT[\"metre\",1,\n"
+ " ID[\"EPSG\",9001]]]]";
+
+ auto obj = WKTParser().createFromWKT(wkt);
+ auto crs = nn_dynamic_pointer_cast<DerivedEngineeringCRS>(obj);
+ ASSERT_TRUE(crs != nullptr);
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(wkt_parse, DerivedParametricCRS) {
+
+ auto wkt = "PARAMETRICCRS[\"Derived ParametricCRS\",\n"
+ " BASEPARAMCRS[\"Parametric CRS\",\n"
+ " PDATUM[\"Parametric datum\"]],\n"
+ " DERIVINGCONVERSION[\"unnamed\",\n"
+ " METHOD[\"PROJ unimplemented\"]],\n"
+ " CS[parametric,1],\n"
+ " AXIS[\"pressure (hPa)\",up,\n"
+ " PARAMETRICUNIT[\"HectoPascal\",100]]]";
+
+ auto obj = WKTParser().createFromWKT(wkt);
+ auto crs = nn_dynamic_pointer_cast<DerivedParametricCRS>(obj);
+ ASSERT_TRUE(crs != nullptr);
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(wkt_parse, DerivedTemporalCRS) {
+
+ auto wkt = "TIMECRS[\"Derived TemporalCRS\",\n"
+ " BASETIMECRS[\"Temporal CRS\",\n"
+ " TDATUM[\"Gregorian calendar\",\n"
+ " CALENDAR[\"proleptic Gregorian\"],\n"
+ " TIMEORIGIN[0000-01-01]]],\n"
+ " DERIVINGCONVERSION[\"unnamed\",\n"
+ " METHOD[\"PROJ unimplemented\"]],\n"
+ " CS[TemporalDateTime,1],\n"
+ " AXIS[\"time (T)\",future]]";
+
+ auto obj = WKTParser().createFromWKT(wkt);
+ auto crs = nn_dynamic_pointer_cast<DerivedTemporalCRS>(obj);
+ ASSERT_TRUE(crs != nullptr);
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(wkt_parse, ensemble) {
+ auto wkt = "ENSEMBLE[\"test\",\n"
+ " MEMBER[\"World Geodetic System 1984\",\n"
+ " ID[\"EPSG\",6326]],\n"
+ " MEMBER[\"other datum\"],\n"
+ " ELLIPSOID[\"WGS 84\",6378137,298.257223563,\n"
+ " LENGTHUNIT[\"metre\",1],\n"
+ " ID[\"EPSG\",7030]],\n"
+ " ENSEMBLEACCURACY[100]]";
+
+ auto obj = WKTParser().createFromWKT(wkt);
+ auto ensemble = nn_dynamic_pointer_cast<DatumEnsemble>(obj);
+ ASSERT_TRUE(ensemble != nullptr);
+
+ ASSERT_EQ(ensemble->datums().size(), 2);
+ auto firstDatum =
+ nn_dynamic_pointer_cast<GeodeticReferenceFrame>(ensemble->datums()[0]);
+ ASSERT_TRUE(firstDatum != nullptr);
+ EXPECT_EQ(firstDatum->nameStr(), "World Geodetic System 1984");
+ ASSERT_EQ(firstDatum->identifiers().size(), 1);
+ EXPECT_EQ(firstDatum->identifiers()[0]->code(), "6326");
+ EXPECT_EQ(*(firstDatum->identifiers()[0]->codeSpace()), "EPSG");
+
+ EXPECT_EQ(firstDatum->ellipsoid()->nameStr(), "WGS 84");
+
+ EXPECT_EQ(ensemble->positionalAccuracy()->value(), "100");
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(wkt_parse, ensemble_vdatum) {
+ auto wkt = "ENSEMBLE[\"unnamed\",\n"
+ " MEMBER[\"vdatum1\"],\n"
+ " MEMBER[\"vdatum2\"],\n"
+ " ENSEMBLEACCURACY[100]]";
+
+ auto obj = WKTParser().createFromWKT(wkt);
+ auto ensemble = nn_dynamic_pointer_cast<DatumEnsemble>(obj);
+ ASSERT_TRUE(ensemble != nullptr);
+
+ ASSERT_EQ(ensemble->datums().size(), 2);
+ auto firstDatum =
+ nn_dynamic_pointer_cast<VerticalReferenceFrame>(ensemble->datums()[0]);
+ ASSERT_TRUE(firstDatum != nullptr);
+ EXPECT_EQ(firstDatum->nameStr(), "vdatum1");
+
+ EXPECT_EQ(ensemble->positionalAccuracy()->value(), "100");
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(wkt_parse, esri_geogcs_datum_spheroid_name_hardcoded_substitution) {
+ auto wkt = "GEOGCS[\"GCS_WGS_1984\",DATUM[\"D_WGS_1984\","
+ "SPHEROID[\"WGS_1984\",6378137.0,298.257223563]],"
+ "PRIMEM[\"Greenwich\",0.0],"
+ "UNIT[\"Degree\",0.0174532925199433]]";
+
+ // Test substitutions of CRS, datum and ellipsoid names from ESRI names
+ // to EPSG names.
+ auto obj = WKTParser().createFromWKT(wkt);
+ auto crs = nn_dynamic_pointer_cast<GeodeticCRS>(obj);
+ ASSERT_TRUE(crs != nullptr);
+
+ EXPECT_EQ(crs->nameStr(), "WGS 84");
+ EXPECT_EQ(crs->datum()->nameStr(), "World Geodetic System 1984");
+ EXPECT_EQ(crs->ellipsoid()->nameStr(), "WGS 84");
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(wkt_parse, esri_geogcs_datum_spheroid_name_from_db_substitution) {
+ auto wkt = "GEOGCS[\"GCS_WGS_1966\",DATUM[\"D_WGS_1966\","
+ "SPHEROID[\"WGS_1966\",6378145.0,298.25]],"
+ "PRIMEM[\"Greenwich\",0.0],"
+ "UNIT[\"Degree\",0.0174532925199433]]";
+
+ // Test substitutions of CRS, datum and ellipsoid names from ESRI names
+ // to EPSG names.
+ auto obj = WKTParser()
+ .attachDatabaseContext(DatabaseContext::create())
+ .createFromWKT(wkt);
+ auto crs = nn_dynamic_pointer_cast<GeodeticCRS>(obj);
+ ASSERT_TRUE(crs != nullptr);
+
+ EXPECT_EQ(crs->nameStr(), "WGS 66");
+ EXPECT_EQ(crs->datum()->nameStr(), "World Geodetic System 1966");
+ EXPECT_EQ(crs->ellipsoid()->nameStr(), "WGS_1966");
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(wkt_parse, esri_datum_name_with_prime_meridian) {
+ auto wkt = "GEOGCS[\"GCS_NTF_Paris\",DATUM[\"D_NTF\","
+ "SPHEROID[\"Clarke_1880_IGN\",6378249.2,293.4660212936265]],"
+ "PRIMEM[\"Paris\",2.337229166666667],"
+ "UNIT[\"Grad\",0.01570796326794897]]";
+
+ // D_NTF normally translates to "Nouvelle Triangulation Francaise",
+ // but as we have a non-Greenwich prime meridian, we also test if
+ // "Nouvelle Triangulation Francaise (Paris)" is not a registered datum name
+ auto obj = WKTParser()
+ .attachDatabaseContext(DatabaseContext::create())
+ .createFromWKT(wkt);
+ auto crs = nn_dynamic_pointer_cast<GeodeticCRS>(obj);
+ ASSERT_TRUE(crs != nullptr);
+
+ EXPECT_EQ(crs->nameStr(), "NTF (Paris)");
+ EXPECT_EQ(crs->datum()->nameStr(),
+ "Nouvelle Triangulation Francaise (Paris)");
+ EXPECT_EQ(crs->ellipsoid()->nameStr(), "Clarke 1880 (IGN)");
+}
+
+// ---------------------------------------------------------------------------
+
+static const struct {
+ const char *esriProjectionName;
+ std::vector<std::pair<const char *, double>> esriParams;
+ const char *wkt2ProjectionName;
+ std::vector<std::pair<const char *, double>> wkt2Params;
+} esriProjDefs[] = {
+
+ {"Plate_Carree",
+ {{"False_Easting", 1}, {"False_Northing", 2}, {"Central_Meridian", 3}},
+ "Equidistant Cylindrical",
+ {
+ {"Latitude of 1st standard parallel", 0},
+ {"Longitude of natural origin", 3},
+ {"False easting", 1},
+ {"False northing", 2},
+ }},
+
+ {"Equidistant_Cylindrical",
+ {
+ {"False_Easting", 1},
+ {"False_Northing", 2},
+ {"Central_Meridian", 3},
+ {"Standard_Parallel_1", 4},
+ },
+ "Equidistant Cylindrical",
+ {
+ {"Latitude of 1st standard parallel", 4},
+ {"Longitude of natural origin", 3},
+ {"False easting", 1},
+ {"False northing", 2},
+ }},
+
+ {"Miller_Cylindrical",
+ {{"False_Easting", 1}, {"False_Northing", 2}, {"Central_Meridian", 3}},
+ "Miller Cylindrical",
+ {
+ {"Longitude of natural origin", 3},
+ {"False easting", 1},
+ {"False northing", 2},
+ }},
+
+ {"Mercator",
+ {{"False_Easting", 1},
+ {"False_Northing", 2},
+ {"Central_Meridian", 3},
+ {"Standard_Parallel_1", 4}},
+ "Mercator (variant B)",
+ {
+ {"Latitude of 1st standard parallel", 4},
+ {"Longitude of natural origin", 3},
+ {"False easting", 1},
+ {"False northing", 2},
+ }},
+
+ {"Gauss_Kruger",
+ {{"False_Easting", 1},
+ {"False_Northing", 2},
+ {"Central_Meridian", 3},
+ {"Scale_Factor", 4},
+ {"Latitude_Of_Origin", 5}},
+ "Transverse Mercator",
+ {
+ {"Latitude of natural origin", 5},
+ {"Longitude of natural origin", 3},
+ {"Scale factor at natural origin", 4},
+ {"False easting", 1},
+ {"False northing", 2},
+ }},
+
+ {"Transverse_Mercator",
+ {{"False_Easting", 1},
+ {"False_Northing", 2},
+ {"Central_Meridian", 3},
+ {"Scale_Factor", 4},
+ {"Latitude_Of_Origin", 5}},
+ "Transverse Mercator",
+ {
+ {"Latitude of natural origin", 5},
+ {"Longitude of natural origin", 3},
+ {"Scale factor at natural origin", 4},
+ {"False easting", 1},
+ {"False northing", 2},
+ }},
+
+ {"Albers",
+ {{"False_Easting", 1},
+ {"False_Northing", 2},
+ {"Central_Meridian", 3},
+ {"Standard_Parallel_1", 4},
+ {"Standard_Parallel_2", 5},
+ {"Latitude_Of_Origin", 6}},
+ "Albers Equal Area",
+ {
+ {"Latitude of false origin", 6},
+ {"Longitude of false origin", 3},
+ {"Latitude of 1st standard parallel", 4},
+ {"Latitude of 2nd standard parallel", 5},
+ {"Easting at false origin", 1},
+ {"Northing at false origin", 2},
+ }},
+
+ {"Sinusoidal",
+ {{"False_Easting", 1}, {"False_Northing", 2}, {"Central_Meridian", 3}},
+ "Sinusoidal",
+ {
+ {"Longitude of natural origin", 3},
+ {"False easting", 1},
+ {"False northing", 2},
+ }},
+
+ {"Mollweide",
+ {{"False_Easting", 1}, {"False_Northing", 2}, {"Central_Meridian", 3}},
+ "Mollweide",
+ {
+ {"Longitude of natural origin", 3},
+ {"False easting", 1},
+ {"False northing", 2},
+ }},
+
+ {"Eckert_I",
+ {{"False_Easting", 1}, {"False_Northing", 2}, {"Central_Meridian", 3}},
+ "Eckert I",
+ {
+ {"Longitude of natural origin", 3},
+ {"False easting", 1},
+ {"False northing", 2},
+ }},
+
+ // skipping Eckert_II to Eckert_VI
+
+ {"Gall_Stereographic",
+ {{"False_Easting", 1}, {"False_Northing", 2}, {"Central_Meridian", 3}},
+ "Gall Stereographic",
+ {
+ {"Longitude of natural origin", 3},
+ {"False easting", 1},
+ {"False northing", 2},
+ }},
+
+ {"Winkel_I",
+ {
+ {"False_Easting", 1},
+ {"False_Northing", 2},
+ {"Central_Meridian", 3},
+ {"Standard_Parallel_1", 4},
+ },
+ "Winkel I",
+ {
+ {"Longitude of natural origin", 3},
+ {"Latitude of 1st standard parallel", 4},
+ {"False easting", 1},
+ {"False northing", 2},
+ }},
+
+ {"Winkel_II",
+ {
+ {"False_Easting", 1},
+ {"False_Northing", 2},
+ {"Central_Meridian", 3},
+ {"Standard_Parallel_1", 4},
+ },
+ "Winkel II",
+ {
+ {"Longitude of natural origin", 3},
+ {"Latitude of 1st standard parallel", 4},
+ {"False easting", 1},
+ {"False northing", 2},
+ }},
+
+ {"Lambert_Conformal_Conic",
+ {{"False_Easting", 1},
+ {"False_Northing", 2},
+ {"Central_Meridian", 3},
+ {"Standard_Parallel_1", 4},
+ {"Scale_Factor", 5},
+ {"Latitude_Of_Origin", 4}},
+ "Lambert Conic Conformal (1SP)",
+ {
+ {"Latitude of natural origin", 4},
+ {"Longitude of natural origin", 3},
+ {"Scale factor at natural origin", 5},
+ {"False easting", 1},
+ {"False northing", 2},
+ }},
+
+ {"Lambert_Conformal_Conic",
+ {{"False_Easting", 1},
+ {"False_Northing", 2},
+ {"Central_Meridian", 3},
+ {"Standard_Parallel_1", 4},
+ {"Standard_Parallel_2", 5},
+ {"Latitude_Of_Origin", 6}},
+ "Lambert Conic Conformal (2SP)",
+ {
+ {"Latitude of false origin", 6},
+ {"Longitude of false origin", 3},
+ {"Latitude of 1st standard parallel", 4},
+ {"Latitude of 2nd standard parallel", 5},
+ {"Easting at false origin", 1},
+ {"Northing at false origin", 2},
+ }},
+
+ // Unusual variant of above with Scale_Factor=1
+ {"Lambert_Conformal_Conic",
+ {{"False_Easting", 1},
+ {"False_Northing", 2},
+ {"Central_Meridian", 3},
+ {"Standard_Parallel_1", 4},
+ {"Standard_Parallel_2", 5},
+ {"Scale_Factor", 1.0},
+ {"Latitude_Of_Origin", 6}},
+ "Lambert Conic Conformal (2SP)",
+ {
+ {"Latitude of false origin", 6},
+ {"Longitude of false origin", 3},
+ {"Latitude of 1st standard parallel", 4},
+ {"Latitude of 2nd standard parallel", 5},
+ {"Easting at false origin", 1},
+ {"Northing at false origin", 2},
+ }},
+
+ {"Polyconic",
+ {{"False_Easting", 1},
+ {"False_Northing", 2},
+ {"Central_Meridian", 3},
+ {"Latitude_Of_Origin", 4}},
+ "American Polyconic",
+ {
+ {"Latitude of natural origin", 4},
+ {"Longitude of natural origin", 3},
+ {"False easting", 1},
+ {"False northing", 2},
+ }},
+
+ {"Quartic_Authalic",
+ {{"False_Easting", 1}, {"False_Northing", 2}, {"Central_Meridian", 3}},
+ "Quartic Authalic",
+ {
+ {"Longitude of natural origin", 3},
+ {"False easting", 1},
+ {"False northing", 2},
+ }},
+
+ {"Loximuthal",
+ {{"False_Easting", 1},
+ {"False_Northing", 2},
+ {"Central_Meridian", 3},
+ {"Central_Parallel", 4}},
+ "Loximuthal",
+ {
+ {"Latitude of natural origin", 4},
+ {"Longitude of natural origin", 3},
+ {"False easting", 1},
+ {"False northing", 2},
+ }},
+
+ {"Bonne",
+ {{"False_Easting", 1},
+ {"False_Northing", 2},
+ {"Central_Meridian", 3},
+ {"Standard_Parallel_1", 4}},
+ "Bonne",
+ {
+ {"Latitude of natural origin", 4},
+ {"Longitude of natural origin", 3},
+ {"False easting", 1},
+ {"False northing", 2},
+ }},
+
+ {"Hotine_Oblique_Mercator_Two_Point_Natural_Origin",
+ {{"False_Easting", 1},
+ {"False_Northing", 2},
+ {"Latitude_Of_1st_Point", 3},
+ {"Latitude_Of_2nd_Point", 4},
+ {"Scale_Factor", 5},
+ {"Longitude_Of_1st_Point", 6},
+ {"Longitude_Of_2nd_Point", 7},
+ {"Latitude_Of_Center", 8}},
+ "Hotine Oblique Mercator Two Point Natural Origin",
+ {
+ {"Latitude of projection centre", 8},
+ {"Latitude of 1st point", 3},
+ {"Longitude of 1st point", 6},
+ {"Latitude of 2nd point", 4},
+ {"Longitude of 2nd point", 7},
+ {"Scale factor on initial line", 5},
+ {"Easting at projection centre", 1},
+ {"Northing at projection centre", 2},
+ }},
+
+ {"Stereographic",
+ {{"False_Easting", 1},
+ {"False_Northing", 2},
+ {"Central_Meridian", 3},
+ {"Scale_Factor", 4},
+ {"Latitude_Of_Origin", 5}},
+ "Stereographic",
+ {
+ {"Latitude of natural origin", 5},
+ {"Longitude of natural origin", 3},
+ {"Scale factor at natural origin", 4},
+ {"False easting", 1},
+ {"False northing", 2},
+ }},
+
+ {"Stereographic",
+ {{"False_Easting", 1},
+ {"False_Northing", 2},
+ {"Central_Meridian", 3},
+ {"Scale_Factor", 4},
+ {"Latitude_Of_Origin", 90}},
+ "Polar Stereographic (variant A)",
+ {
+ {"Latitude of natural origin", 90},
+ {"Longitude of natural origin", 3},
+ {"Scale factor at natural origin", 4},
+ {"False easting", 1},
+ {"False northing", 2},
+ }},
+
+ {"Stereographic",
+ {{"False_Easting", 1},
+ {"False_Northing", 2},
+ {"Central_Meridian", 3},
+ {"Scale_Factor", 4},
+ {"Latitude_Of_Origin", -90}},
+ "Polar Stereographic (variant A)",
+ {
+ {"Latitude of natural origin", -90},
+ {"Longitude of natural origin", 3},
+ {"Scale factor at natural origin", 4},
+ {"False easting", 1},
+ {"False northing", 2},
+ }},
+
+ {"Equidistant_Conic",
+ {{"False_Easting", 1},
+ {"False_Northing", 2},
+ {"Central_Meridian", 3},
+ {"Standard_Parallel_1", 4},
+ {"Standard_Parallel_2", 5},
+ {"Latitude_Of_Origin", 6}},
+ "Equidistant Conic",
+ {
+ {"Latitude of natural origin", 6},
+ {"Longitude of natural origin", 3},
+ {"Latitude of 1st standard parallel", 4},
+ {"Latitude of 2nd standard parallel", 5},
+ {"False easting", 1},
+ {"False northing", 2},
+ }},
+
+ {"Cassini",
+ {{"False_Easting", 1},
+ {"False_Northing", 2},
+ {"Central_Meridian", 3},
+ {"Scale_Factor", 1},
+ {"Latitude_Of_Origin", 4}},
+ "Cassini-Soldner",
+ {
+ {"Latitude of natural origin", 4},
+ {"Longitude of natural origin", 3},
+ {"False easting", 1},
+ {"False northing", 2},
+ }},
+
+ {"Van_der_Grinten_I",
+ {{"False_Easting", 1}, {"False_Northing", 2}, {"Central_Meridian", 3}},
+ "Van Der Grinten",
+ {
+ {"Longitude of natural origin", 3},
+ {"False easting", 1},
+ {"False northing", 2},
+ }},
+
+ {"Robinson",
+ {{"False_Easting", 1}, {"False_Northing", 2}, {"Central_Meridian", 3}},
+ "Robinson",
+ {
+ {"Longitude of natural origin", 3},
+ {"False easting", 1},
+ {"False northing", 2},
+ }},
+
+ {"Two_Point_Equidistant",
+ {{"False_Easting", 1},
+ {"False_Northing", 2},
+ {"Latitude_Of_1st_Point", 3},
+ {"Latitude_Of_2nd_Point", 4},
+ {"Longitude_Of_1st_Point", 5},
+ {"Longitude_Of_2nd_Point", 6}},
+ "Two Point Equidistant",
+ {
+ {"Latitude of 1st point", 3},
+ {"Longitude of 1st point", 5},
+ {"Latitude of 2nd point", 4},
+ {"Longitude of 2nd point", 6},
+ {"False easting", 1},
+ {"False northing", 2},
+ }},
+
+ {"Azimuthal_Equidistant",
+ {{"False_Easting", 1},
+ {"False_Northing", 2},
+ {"Central_Meridian", 3},
+ {"Latitude_Of_Origin", 4}},
+ "Modified Azimuthal Equidistant",
+ {
+ {"Latitude of natural origin", 4},
+ {"Longitude of natural origin", 3},
+ {"False easting", 1},
+ {"False northing", 2},
+ }},
+
+ {"Lambert_Azimuthal_Equal_Area",
+ {{"False_Easting", 1},
+ {"False_Northing", 2},
+ {"Central_Meridian", 3},
+ {"Latitude_Of_Origin", 4}},
+ "Lambert Azimuthal Equal Area",
+ {
+ {"Latitude of natural origin", 4},
+ {"Longitude of natural origin", 3},
+ {"False easting", 1},
+ {"False northing", 2},
+ }},
+
+ {"Cylindrical_Equal_Area",
+ {{"False_Easting", 1},
+ {"False_Northing", 2},
+ {"Central_Meridian", 3},
+ {"Standard_Parallel_1", 4}},
+ "Lambert Cylindrical Equal Area (Spherical)",
+ {
+ {"Latitude of 1st standard parallel", 4},
+ {"Longitude of natural origin", 3},
+ {"False easting", 1},
+ {"False northing", 2},
+ }},
+
+ // Untested: Hotine_Oblique_Mercator_Two_Point_Center
+
+ {"Hotine_Oblique_Mercator_Azimuth_Natural_Origin",
+ {{"False_Easting", 1},
+ {"False_Northing", 2},
+ {"Scale_Factor", 3},
+ {"Azimuth", 4},
+ {"Longitude_Of_Center", 5},
+ {"Latitude_Of_Center", 6}},
+ "Hotine Oblique Mercator (variant A)",
+ {
+ {"Latitude of projection centre", 6},
+ {"Longitude of projection centre", 5},
+ {"Azimuth of initial line", 4},
+ {"Angle from Rectified to Skew Grid", 4},
+ {"Scale factor on initial line", 3},
+ {"False easting", 1},
+ {"False northing", 2},
+ }},
+
+ {"Hotine_Oblique_Mercator_Azimuth_Center",
+ {{"False_Easting", 1},
+ {"False_Northing", 2},
+ {"Scale_Factor", 3},
+ {"Azimuth", 4},
+ {"Longitude_Of_Center", 5},
+ {"Latitude_Of_Center", 6}},
+ "Hotine Oblique Mercator (variant B)",
+ {
+ {"Latitude of projection centre", 6},
+ {"Longitude of projection centre", 5},
+ {"Azimuth of initial line", 4},
+ {"Angle from Rectified to Skew Grid", 4},
+ {"Scale factor on initial line", 3},
+ {"Easting at projection centre", 1},
+ {"Northing at projection centre", 2},
+ }},
+
+ {"Double_Stereographic",
+ {{"False_Easting", 1},
+ {"False_Northing", 2},
+ {"Central_Meridian", 3},
+ {"Scale_Factor", 4},
+ {"Latitude_Of_Origin", 5}},
+ "Oblique Stereographic",
+ {
+ {"Latitude of natural origin", 5},
+ {"Longitude of natural origin", 3},
+ {"Scale factor at natural origin", 4},
+ {"False easting", 1},
+ {"False northing", 2},
+ }},
+
+ {"Krovak",
+ {{"False_Easting", 1},
+ {"False_Northing", 2},
+ {"Pseudo_Standard_Parallel_1", 3},
+ {"Scale_Factor", 4},
+ {"Azimuth", 5},
+ {"Longitude_Of_Center", 6},
+ {"Latitude_Of_Center", 7},
+ {"X_Scale", 1},
+ {"Y_Scale", 1},
+ {"XY_Plane_Rotation", 0}},
+ "Krovak",
+ {
+ {"Latitude of projection centre", 7},
+ {"Longitude of origin", 6},
+ {"Co-latitude of cone axis", 5},
+ {"Latitude of pseudo standard parallel", 3},
+ {"Scale factor on pseudo standard parallel", 4},
+ {"False easting", 1},
+ {"False northing", 2},
+ }},
+
+ {"Krovak",
+ {{"False_Easting", 1},
+ {"False_Northing", 2},
+ {"Pseudo_Standard_Parallel_1", 3},
+ {"Scale_Factor", 4},
+ {"Azimuth", 5},
+ {"Longitude_Of_Center", 6},
+ {"Latitude_Of_Center", 7},
+ {"X_Scale", -1},
+ {"Y_Scale", 1},
+ {"XY_Plane_Rotation", 90}},
+ "Krovak (North Orientated)",
+ {
+ {"Latitude of projection centre", 7},
+ {"Longitude of origin", 6},
+ {"Co-latitude of cone axis", 5},
+ {"Latitude of pseudo standard parallel", 3},
+ {"Scale factor on pseudo standard parallel", 4},
+ {"False easting", 1},
+ {"False northing", 2},
+ }},
+
+ {"New_Zealand_Map_Grid",
+ {{"False_Easting", 1},
+ {"False_Northing", 2},
+ {"Longitude_Of_Origin", 3},
+ {"Latitude_Of_Origin", 4}},
+ "New Zealand Map Grid",
+ {
+ {"Latitude of natural origin", 4},
+ {"Longitude of natural origin", 3},
+ {"False easting", 1},
+ {"False northing", 2},
+ }},
+
+ {"Orthographic",
+ {{"False_Easting", 1},
+ {"False_Northing", 2},
+ {"Longitude_Of_Center", 3},
+ {"Latitude_Of_Center", 4}},
+ "Orthographic",
+ {
+ {"Latitude of natural origin", 4},
+ {"Longitude of natural origin", 3},
+ {"False easting", 1},
+ {"False northing", 2},
+ }},
+
+ {"Winkel_Tripel",
+ {{"False_Easting", 1},
+ {"False_Northing", 2},
+ {"Central_Meridian", 3},
+ {"Standard_Parallel_1", 4}},
+ "Winkel Tripel",
+ {
+ {"Longitude of natural origin", 3},
+ {"Latitude of 1st standard parallel", 4},
+ {"False easting", 1},
+ {"False northing", 2},
+ }},
+
+ {"Aitoff",
+ {{"False_Easting", 1}, {"False_Northing", 2}, {"Central_Meridian", 3}},
+ "Aitoff",
+ {
+ {"Longitude of natural origin", 3},
+ {"False easting", 1},
+ {"False northing", 2},
+ }},
+
+ {"Craster_Parabolic",
+ {{"False_Easting", 1}, {"False_Northing", 2}, {"Central_Meridian", 3}},
+ "Craster Parabolic",
+ {
+ {"Longitude of natural origin", 3},
+ {"False easting", 1},
+ {"False northing", 2},
+ }},
+
+ {"Gnomonic",
+ {{"False_Easting", 1},
+ {"False_Northing", 2},
+ {"Longitude_Of_Center", 3},
+ {"Latitude_Of_Center", 4}},
+ "Gnomonic",
+ {
+ {"Latitude of natural origin", 4},
+ {"Longitude of natural origin", 3},
+ {"False easting", 1},
+ {"False northing", 2},
+ }},
+
+ {"Stereographic_North_Pole",
+ {{"False_Easting", 1},
+ {"False_Northing", 2},
+ {"Central_Meridian", 3},
+ {"Standard_Parallel_1", 4}},
+ "Polar Stereographic (variant B)",
+ {
+ {"Latitude of standard parallel", 4},
+ {"Longitude of origin", 3},
+ {"False easting", 1},
+ {"False northing", 2},
+ }},
+
+ {"Stereographic_South_Pole",
+ {{"False_Easting", 1},
+ {"False_Northing", 2},
+ {"Central_Meridian", 3},
+ {"Standard_Parallel_1", -4}},
+ "Polar Stereographic (variant B)",
+ {
+ {"Latitude of standard parallel", -4},
+ {"Longitude of origin", 3},
+ {"False easting", 1},
+ {"False northing", 2},
+ }},
+
+ {"Rectified_Skew_Orthomorphic_Natural_Origin",
+ {
+ {"False_Easting", 1},
+ {"False_Northing", 2},
+ {"Scale_Factor", 3},
+ {"Azimuth", 4},
+ {"Longitude_Of_Center", 5},
+ {"Latitude_Of_Center", 6},
+ {"XY_Plane_Rotation", 7},
+ },
+ "Hotine Oblique Mercator (variant A)",
+ {
+ {"Latitude of projection centre", 6},
+ {"Longitude of projection centre", 5},
+ {"Azimuth of initial line", 4},
+ {"Angle from Rectified to Skew Grid", 7},
+ {"Scale factor on initial line", 3},
+ {"False easting", 1},
+ {"False northing", 2},
+ }},
+
+ // Temptative mapping
+ {"Rectified_Skew_Orthomorphic_Center",
+ {
+ {"False_Easting", 1},
+ {"False_Northing", 2},
+ {"Scale_Factor", 3},
+ {"Azimuth", 4},
+ {"Longitude_Of_Center", 5},
+ {"Latitude_Of_Center", 6},
+ {"XY_Plane_Rotation", 7},
+ },
+ "Hotine Oblique Mercator (variant B)",
+ {
+ {"Latitude of projection centre", 6},
+ {"Longitude of projection centre", 5},
+ {"Azimuth of initial line", 4},
+ {"Angle from Rectified to Skew Grid", 7},
+ {"Scale factor on initial line", 3},
+ {"Easting at projection centre", 1},
+ {"Northing at projection centre", 2},
+ }},
+
+ {"Goode_Homolosine",
+ {{"False_Easting", 1}, {"False_Northing", 2}, {"Central_Meridian", 3}},
+ "Goode Homolosine",
+ {
+ {"Longitude of natural origin", 3},
+ {"False easting", 1},
+ {"False northing", 2},
+ }},
+
+ {"Equidistant_Cylindrical_Ellipsoidal",
+ {
+ {"False_Easting", 1},
+ {"False_Northing", 2},
+ {"Central_Meridian", 3},
+ {"Standard_Parallel_1", 4},
+ },
+ "Equidistant Cylindrical",
+ {
+ {"Latitude of 1st standard parallel", 4},
+ {"Longitude of natural origin", 3},
+ {"False easting", 1},
+ {"False northing", 2},
+ }},
+
+ {"Laborde_Oblique_Mercator",
+ {{"False_Easting", 1},
+ {"False_Northing", 2},
+ {"Scale_Factor", 3},
+ {"Azimuth", 4},
+ {"Longitude_Of_Center", 5},
+ {"Latitude_Of_Center", 6}},
+ "Laborde Oblique Mercator",
+ {
+ {"Latitude of projection centre", 6},
+ {"Longitude of projection centre", 5},
+ {"Azimuth of initial line", 4},
+ {"Scale factor on initial line", 3},
+ {"False easting", 1},
+ {"False northing", 2},
+ }},
+
+ {"Gnomonic_Ellipsoidal",
+ {{"False_Easting", 1},
+ {"False_Northing", 2},
+ {"Longitude_Of_Center", 3},
+ {"Latitude_Of_Center", 4}},
+ "Gnomonic",
+ {
+ {"Latitude of natural origin", 4},
+ {"Longitude of natural origin", 3},
+ {"False easting", 1},
+ {"False northing", 2},
+ }},
+
+ {"Wagner_IV",
+ {{"False_Easting", 1},
+ {"False_Northing", 2},
+ {"Central_Meridian", 3},
+ {"Latitude_Of_Center", 0}},
+ "Wagner IV",
+ {
+ {"Longitude of natural origin", 3},
+ {"False easting", 1},
+ {"False northing", 2},
+ }},
+
+ {"Wagner_V",
+ {{"False_Easting", 1}, {"False_Northing", 2}, {"Central_Meridian", 3}},
+ "Wagner V",
+ {
+ {"Longitude of natural origin", 3},
+ {"False easting", 1},
+ {"False northing", 2},
+ }},
+
+ {"Wagner_VII",
+ {{"False_Easting", 1}, {"False_Northing", 2}, {"Central_Meridian", 3}},
+ "Wagner VII",
+ {
+ {"Longitude of natural origin", 3},
+ {"False easting", 1},
+ {"False northing", 2},
+ }},
+
+ {"Geostationary_Satellite",
+ {
+ {"False_Easting", 1},
+ {"False_Northing", 2},
+ {"Longitude_Of_Center", 3},
+ {"Height", 4},
+ {"Option", 0.0},
+ },
+ "Geostationary Satellite (Sweep Y)",
+ {
+ {"Longitude of natural origin", 3},
+ {"Satellite Height", 4},
+ {"False easting", 1},
+ {"False northing", 2},
+ }},
+
+ {"Mercator_Auxiliary_Sphere",
+ {{"False_Easting", 1},
+ {"False_Northing", 2},
+ {"Central_Meridian", 3},
+ {"Standard_Parallel_1", 4},
+ {"Auxiliary_Sphere_Type", 0}},
+ "Popular Visualisation Pseudo Mercator",
+ {
+ {"Latitude of natural origin", 4},
+ {"Longitude of natural origin", 3},
+ {"False easting", 1},
+ {"False northing", 2},
+ }},
+
+ {
+ "Unknown_Method",
+ {{"False_Easting", 1},
+ {"False_Northing", 2},
+ {"Longitude_Of_Origin", 3},
+ {"Latitude_Of_Origin", 4}},
+ "Unknown_Method",
+ {{"False_Easting", 1},
+ {"False_Northing", 2},
+ {"Longitude_Of_Origin", 3},
+ {"Latitude_Of_Origin", 4}},
+ },
+
+};
+
+TEST(wkt_parse, esri_projcs) {
+
+ for (const auto &projDef : esriProjDefs) {
+ std::string wkt("PROJCS[\"unnamed\",GEOGCS[\"GCS_WGS_1984\","
+ "DATUM[\"D_WGS_1984\",SPHEROID[\"WGS_1984\","
+ "6378137.0,298.257223563]],PRIMEM[\"Greenwich\",0.0],"
+ "UNIT[\"Degree\",0.0174532925199433]],PROJECTION[\"");
+ wkt += projDef.esriProjectionName;
+ wkt += "\"],";
+ for (const auto &param : projDef.esriParams) {
+ wkt += "PARAMETER[\"";
+ wkt += param.first;
+ wkt += "\",";
+ wkt += toString(param.second);
+ wkt += "],";
+ }
+ wkt += "UNIT[\"Meter\",1.0]]";
+
+ auto obj = WKTParser().createFromWKT(wkt);
+ auto crs = nn_dynamic_pointer_cast<ProjectedCRS>(obj);
+ ASSERT_TRUE(crs != nullptr);
+
+ auto conv = crs->derivingConversion();
+ auto method = conv->method();
+ EXPECT_EQ(method->nameStr(), projDef.wkt2ProjectionName) << wkt;
+ auto values = conv->parameterValues();
+ EXPECT_EQ(values.size(), projDef.wkt2Params.size()) << wkt;
+ if (values.size() == projDef.wkt2Params.size()) {
+ for (size_t i = 0; i < values.size(); i++) {
+ const auto &opParamvalue =
+ nn_dynamic_pointer_cast<OperationParameterValue>(values[i]);
+ ASSERT_TRUE(opParamvalue);
+ const auto &paramName = opParamvalue->parameter()->nameStr();
+ const auto &parameterValue = opParamvalue->parameterValue();
+ EXPECT_EQ(paramName, projDef.wkt2Params[i].first) << wkt;
+ EXPECT_EQ(parameterValue->type(),
+ ParameterValue::Type::MEASURE);
+ auto measure = parameterValue->value();
+ EXPECT_EQ(measure.value(), projDef.wkt2Params[i].second) << wkt;
+ }
+ }
+ }
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(wkt_parse, wkt1_esri_krovak_south_west) {
+ auto wkt = "PROJCS[\"S-JTSK_Krovak\",GEOGCS[\"GCS_S_JTSK\","
+ "DATUM[\"D_S_JTSK\","
+ "SPHEROID[\"Bessel_1841\",6377397.155,299.1528128]],"
+ "PRIMEM[\"Greenwich\",0.0],UNIT[\"Degree\",0.0174532925199433]],"
+ "PROJECTION[\"Krovak\"],PARAMETER[\"False_Easting\",0.0],"
+ "PARAMETER[\"False_Northing\",0.0],"
+ "PARAMETER[\"Pseudo_Standard_Parallel_1\",78.5],"
+ "PARAMETER[\"Scale_Factor\",0.9999],"
+ "PARAMETER[\"Azimuth\",30.28813975277778],"
+ "PARAMETER[\"Longitude_Of_Center\",24.83333333333333],"
+ "PARAMETER[\"Latitude_Of_Center\",49.5],"
+ "PARAMETER[\"X_Scale\",1.0],"
+ "PARAMETER[\"Y_Scale\",1.0],"
+ "PARAMETER[\"XY_Plane_Rotation\",0.0],UNIT[\"Meter\",1.0]]";
+
+ auto obj = WKTParser()
+ .attachDatabaseContext(DatabaseContext::create())
+ .createFromWKT(wkt);
+ auto crs = nn_dynamic_pointer_cast<ProjectedCRS>(obj);
+ ASSERT_TRUE(crs != nullptr);
+
+ EXPECT_EQ(crs->derivingConversion()->method()->nameStr(), "Krovak");
+
+ auto expected_wkt2 =
+ "PROJCRS[\"S-JTSK / Krovak\",\n"
+ " BASEGEODCRS[\"S-JTSK\",\n"
+ " DATUM[\"System of the Unified Trigonometrical Cadastral "
+ "Network\",\n"
+ " ELLIPSOID[\"Bessel 1841\",6377397.155,299.1528128,\n"
+ " LENGTHUNIT[\"metre\",1]],\n"
+ " ID[\"EPSG\",6156]],\n"
+ " PRIMEM[\"Greenwich\",0,\n"
+ " ANGLEUNIT[\"Degree\",0.0174532925199433]]],\n"
+ " CONVERSION[\"unnamed\",\n"
+ " METHOD[\"Krovak\",\n"
+ " ID[\"EPSG\",9819]],\n"
+ " PARAMETER[\"Latitude of projection centre\",49.5,\n"
+ " ANGLEUNIT[\"Degree\",0.0174532925199433],\n"
+ " ID[\"EPSG\",8811]],\n"
+ " PARAMETER[\"Longitude of origin\",24.8333333333333,\n"
+ " ANGLEUNIT[\"Degree\",0.0174532925199433],\n"
+ " ID[\"EPSG\",8833]],\n"
+ " PARAMETER[\"Co-latitude of cone axis\",30.2881397527778,\n"
+ " ANGLEUNIT[\"Degree\",0.0174532925199433],\n"
+ " ID[\"EPSG\",1036]],\n"
+ " PARAMETER[\"Latitude of pseudo standard parallel\",78.5,\n"
+ " ANGLEUNIT[\"Degree\",0.0174532925199433],\n"
+ " ID[\"EPSG\",8818]],\n"
+ " PARAMETER[\"Scale factor on pseudo standard "
+ "parallel\",0.9999,\n"
+ " SCALEUNIT[\"unity\",1],\n"
+ " ID[\"EPSG\",8819]],\n"
+ " PARAMETER[\"False easting\",0,\n"
+ " LENGTHUNIT[\"metre\",1],\n"
+ " ID[\"EPSG\",8806]],\n"
+ " PARAMETER[\"False northing\",0,\n"
+ " LENGTHUNIT[\"metre\",1],\n"
+ " ID[\"EPSG\",8807]]],\n"
+ " CS[Cartesian,2],\n"
+ " AXIS[\"southing\",south,\n"
+ " ORDER[1],\n"
+ " LENGTHUNIT[\"metre\",1,\n"
+ " ID[\"EPSG\",9001]]],\n"
+ " AXIS[\"westing\",west,\n"
+ " ORDER[2],\n"
+ " LENGTHUNIT[\"metre\",1,\n"
+ " ID[\"EPSG\",9001]]]]";
+
+ EXPECT_EQ(crs->exportToWKT(WKTFormatter::create().get()), expected_wkt2);
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(wkt_parse, wkt1_esri_normalize_unit) {
+ auto wkt = "PROJCS[\"Accra_Ghana_Grid\",GEOGCS[\"GCS_Accra\","
+ "DATUM[\"D_Accra\",SPHEROID[\"War_Office\",6378300.0,296.0]],"
+ "PRIMEM[\"Greenwich\",0.0],UNIT[\"Degree\",0.0174532925199433]],"
+ "PROJECTION[\"Transverse_Mercator\"],"
+ "PARAMETER[\"False_Easting\",900000.0],"
+ "PARAMETER[\"False_Northing\",0.0],"
+ "PARAMETER[\"Central_Meridian\",-1.0],"
+ "PARAMETER[\"Scale_Factor\",0.99975],"
+ "PARAMETER[\"Latitude_Of_Origin\",4.666666666666667],"
+ "UNIT[\"Foot_Gold_Coast\",0.3047997101815088]]";
+
+ auto obj = WKTParser()
+ .attachDatabaseContext(DatabaseContext::create())
+ .createFromWKT(wkt);
+ auto crs = nn_dynamic_pointer_cast<ProjectedCRS>(obj);
+ ASSERT_TRUE(crs != nullptr);
+
+ EXPECT_EQ(crs->coordinateSystem()->axisList()[0]->unit().name(),
+ "Gold Coast foot");
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(wkt_parse, wkt1_esri_ups_north) {
+ auto wkt = "PROJCS[\"UPS_North\",GEOGCS[\"GCS_WGS_1984\","
+ "DATUM[\"D_WGS_1984\","
+ "SPHEROID[\"WGS_1984\",6378137.0,298.257223563]],"
+ "PRIMEM[\"Greenwich\",0.0],UNIT[\"Degree\",0.0174532925199433]],"
+ "PROJECTION[\"Stereographic\"],"
+ "PARAMETER[\"False_Easting\",2000000.0],"
+ "PARAMETER[\"False_Northing\",2000000.0],"
+ "PARAMETER[\"Central_Meridian\",0.0],"
+ "PARAMETER[\"Scale_Factor\",0.994],"
+ "PARAMETER[\"Latitude_Of_Origin\",90.0],"
+ "UNIT[\"Meter\",1.0]]";
+
+ auto obj = WKTParser()
+ .attachDatabaseContext(DatabaseContext::create())
+ .createFromWKT(wkt);
+ auto crs = nn_dynamic_pointer_cast<ProjectedCRS>(obj);
+ ASSERT_TRUE(crs != nullptr);
+
+ EXPECT_EQ(crs->nameStr(), "WGS 84 / UPS North (E,N)");
+ EXPECT_EQ(crs->coordinateSystem()->axisList()[0]->direction(),
+ AxisDirection::SOUTH);
+ EXPECT_EQ(crs->coordinateSystem()->axisList()[0]->abbreviation(), "E");
+ EXPECT_EQ(crs->coordinateSystem()->axisList()[1]->direction(),
+ AxisDirection::SOUTH);
+ EXPECT_EQ(crs->coordinateSystem()->axisList()[1]->abbreviation(), "N");
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(wkt_parse, wkt1_esri_ups_south) {
+ auto wkt = "PROJCS[\"UPS_South\",GEOGCS[\"GCS_WGS_1984\","
+ "DATUM[\"D_WGS_1984\","
+ "SPHEROID[\"WGS_1984\",6378137.0,298.257223563]],"
+ "PRIMEM[\"Greenwich\",0.0],UNIT[\"Degree\",0.0174532925199433]],"
+ "PROJECTION[\"Stereographic\"],"
+ "PARAMETER[\"False_Easting\",2000000.0],"
+ "PARAMETER[\"False_Northing\",2000000.0],"
+ "PARAMETER[\"Central_Meridian\",0.0],"
+ "PARAMETER[\"Scale_Factor\",0.994],"
+ "PARAMETER[\"Latitude_Of_Origin\",-90.0],"
+ "UNIT[\"Meter\",1.0]]";
+
+ auto obj = WKTParser()
+ .attachDatabaseContext(DatabaseContext::create())
+ .createFromWKT(wkt);
+ auto crs = nn_dynamic_pointer_cast<ProjectedCRS>(obj);
+ ASSERT_TRUE(crs != nullptr);
+
+ EXPECT_EQ(crs->nameStr(), "WGS 84 / UPS South (E,N)");
+ EXPECT_EQ(crs->coordinateSystem()->axisList()[0]->direction(),
+ AxisDirection::NORTH);
+ EXPECT_EQ(crs->coordinateSystem()->axisList()[0]->abbreviation(), "E");
+ EXPECT_EQ(crs->coordinateSystem()->axisList()[1]->direction(),
+ AxisDirection::NORTH);
+ EXPECT_EQ(crs->coordinateSystem()->axisList()[1]->abbreviation(), "N");
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(wkt_parse, invalid) {
+ EXPECT_THROW(WKTParser().createFromWKT(""), ParsingException);
+ EXPECT_THROW(WKTParser().createFromWKT("A"), ParsingException);
+ EXPECT_THROW(WKTParser().createFromWKT("UNKNOWN[\"foo\"]"),
+ ParsingException);
+ EXPECT_THROW(WKTParser().createFromWKT("INVALID["), ParsingException);
+ EXPECT_THROW(WKTParser().createFromWKT("INVALID[]"), ParsingException);
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(wkt_parse, invalid_SPHEROID) {
+ EXPECT_NO_THROW(WKTParser().createFromWKT("SPHEROID[\"x\",1,0.5]"));
+ EXPECT_THROW(WKTParser().createFromWKT("SPHEROID[\"x\"]"),
+ ParsingException); // major axis not number
+ EXPECT_THROW(WKTParser().createFromWKT("SPHEROID[\"x\",\"1\",0.5]"),
+ ParsingException); // major axis not number
+ EXPECT_THROW(WKTParser().createFromWKT("SPHEROID[\"x\",1,\"0.5\"]"),
+ ParsingException); // reverse flatting not number
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(wkt_parse, invalid_DATUM) {
+ EXPECT_NO_THROW(
+ WKTParser().createFromWKT("DATUM[\"x\",SPHEROID[\"x\",1,0.5]]"));
+ EXPECT_THROW(WKTParser().createFromWKT("DATUM[\"x\"]"), ParsingException);
+ EXPECT_THROW(WKTParser().createFromWKT("DATUM[\"x\",FOO[]]"),
+ ParsingException);
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(wkt_parse, invalid_ENSEMBLE) {
+ EXPECT_THROW(WKTParser().createFromWKT("ENSEMBLE[]"), ParsingException);
+ EXPECT_THROW(WKTParser().createFromWKT("ENSEMBLE[\"x\"]"),
+ ParsingException);
+ EXPECT_THROW(WKTParser().createFromWKT(
+ "ENSEMBLE[\"x\",MEMBER[\"vdatum1\"],MEMBER[\"vdatum2\"]]"),
+ ParsingException);
+ EXPECT_THROW(
+ WKTParser().createFromWKT("ENSEMBLE[\"x\",MEMBER[],MEMBER[\"vdatum2\"],"
+ "ENSEMBLEACCURACY[\"100\"]]"),
+ ParsingException);
+ EXPECT_THROW(
+ WKTParser().createFromWKT("ENSEMBLE[\"x\",MEMBER[\"vdatum1\"],MEMBER["
+ "\"vdatum2\"],ENSEMBLEACCURACY[]]"),
+ ParsingException);
+ EXPECT_THROW(
+ WKTParser().createFromWKT("ENSEMBLE[\"x\",ENSEMBLEACCURACY[\"100\"]]"),
+ ParsingException);
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(wkt_parse, invalid_GEOGCS) {
+ EXPECT_NO_THROW(WKTParser().createFromWKT(
+ "GEOGCS[\"x\",DATUM[\"x\",SPHEROID[\"x\",1,0.5]],PRIMEM[\"x\",0],UNIT["
+ "\"degree\",0.0174532925199433]]"));
+
+ // missing PRIMEM
+ EXPECT_THROW(
+ WKTParser().createFromWKT("GEOGCS[\"x\",DATUM[\"x\",SPHEROID[\"x\",1,0."
+ "5]],UNIT[\"degree\",0.0174532925199433]]"),
+ ParsingException);
+
+ // missing UNIT
+ EXPECT_THROW(
+ WKTParser().createFromWKT(
+ "GEOGCS[\"x\",DATUM[\"x\",SPHEROID[\"x\",1,0.5]],PRIMEM[\"x\",0]]"),
+ ParsingException);
+
+ EXPECT_THROW(WKTParser().createFromWKT("GEOGCS[\"x\"]"), ParsingException);
+ EXPECT_THROW(WKTParser().createFromWKT("GEOGCS[\"x\",FOO[]]"),
+ ParsingException);
+
+ // not enough children for DATUM
+ EXPECT_THROW(
+ WKTParser().createFromWKT("GEOGCS[\"x\",DATUM[\"x\"],PRIMEM[\"x\",0],"
+ "UNIT[\"degree\",0.0174532925199433]]"),
+ ParsingException);
+
+ // not enough children for AUTHORITY
+ EXPECT_THROW(WKTParser().createFromWKT("GEOGCS[\"x\",DATUM[\"x\",SPHEROID["
+ "\"x\",1,0.5]],PRIMEM[\"x\",0],UNIT["
+ "\"degree\",0.0174532925199433],"
+ "AUTHORITY[\"x\"]]"),
+ ParsingException);
+
+ // not enough children for AUTHORITY, but ignored
+ EXPECT_NO_THROW(WKTParser().setStrict(false).createFromWKT(
+ "GEOGCS[\"x\",DATUM[\"x\",SPHEROID[\"x\",1,0.5]],PRIMEM[\"x\",0],UNIT["
+ "\"degree\",0.0174532925199433],AUTHORITY[\"x\"]]"));
+
+ EXPECT_NO_THROW(WKTParser().createFromWKT(
+ "GEOGCS[\"x\",DATUM[\"x\",SPHEROID[\"x\",1,0.5]],PRIMEM[\"x\",0],UNIT["
+ "\"degree\",0.0174532925199433]]"));
+
+ // PRIMEM not numeric
+ EXPECT_THROW(
+ WKTParser().createFromWKT(
+ "GEOGCS[\"x\",DATUM[\"x\",SPHEROID[\"x\",1,0."
+ "5]],PRIMEM[\"x\",\"a\"],UNIT[\"degree\",0.0174532925199433]]"),
+ ParsingException);
+
+ // not enough children for PRIMEM
+ EXPECT_THROW(WKTParser().createFromWKT("GEOGCS[\"x\",DATUM[\"x\",SPHEROID["
+ "\"x\",1,0.5]],PRIMEM[\"x\"],UNIT["
+ "\"degree\",0.0174532925199433]]"),
+ ParsingException);
+
+ EXPECT_NO_THROW(WKTParser().createFromWKT(
+ "GEOGCS[\"x\",DATUM[\"x\",SPHEROID[\"x\",1,0.5]],PRIMEM[\"x\",0],UNIT["
+ "\"degree\",0.0174532925199433],AXIS[\"latitude\","
+ "NORTH],AXIS[\"longitude\",EAST]]"));
+
+ // one axis only
+ EXPECT_THROW(WKTParser().createFromWKT(
+ "GEOGCS[\"x\",DATUM[\"x\",SPHEROID[\"x\",1,0."
+ "5]],PRIMEM[\"x\",0],UNIT[\"degree\",0.0174532925199433],"
+ "AXIS[\"latitude\",NORTH]]"),
+ ParsingException);
+
+ // invalid axis
+ EXPECT_THROW(WKTParser().createFromWKT("GEOGCS[\"x\",DATUM[\"x\",SPHEROID["
+ "\"x\",1,0.5]],PRIMEM[\"x\",0],UNIT["
+ "\"degree\",0.0174532925199433],"
+ "AXIS[\"latitude\","
+ "NORTH],AXIS[\"longitude\"]]"),
+ ParsingException);
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(wkt_parse, invalid_UNIT) {
+ std::string startWKT("GEODCRS[\"x\",DATUM[\"x\",SPHEROID[\"x\",1,0.5]],CS["
+ "ellipsoidal,2],AXIS[\"latitude\",north],AXIS["
+ "\"longitude\",east,");
+
+ EXPECT_NO_THROW(WKTParser().createFromWKT(
+ startWKT + "UNIT[\"degree\",0.0174532925199433]]]"));
+
+ // not enough children
+ EXPECT_THROW(WKTParser().createFromWKT(startWKT + "UNIT[\"x\"]]]"),
+ ParsingException);
+
+ // invalid conversion factor
+ EXPECT_THROW(
+ WKTParser().createFromWKT(startWKT + "UNIT[\"x\",\"invalid\"]]]"),
+ ParsingException);
+
+ // invalid ID
+ EXPECT_THROW(
+ WKTParser().createFromWKT(startWKT + "UNIT[\"x\",1,ID[\"x\"]]]]"),
+ ParsingException);
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(wkt_parse, invalid_GEOCCS) {
+ EXPECT_NO_THROW(
+ WKTParser().createFromWKT("GEOCCS[\"x\",DATUM[\"x\",SPHEROID[\"x\",1,0."
+ "5]],PRIMEM[\"x\",0],UNIT[\"metre\",1]]"));
+
+ // missing PRIMEM
+ EXPECT_THROW(WKTParser().createFromWKT("GEOCCS[\"x\",DATUM[\"x\",SPHEROID["
+ "\"x\",1,0.5]],UNIT[\"metre\",1]]"),
+ ParsingException);
+
+ // missing UNIT
+ EXPECT_THROW(
+ WKTParser().createFromWKT(
+ "GEOCCS[\"x\",DATUM[\"x\",SPHEROID[\"x\",1,0.5]],PRIMEM[\"x\",0]]"),
+ ParsingException);
+
+ // ellipsoidal CS is invalid in a GEOCCS
+ EXPECT_THROW(WKTParser().createFromWKT("GEOCCS[\"x\",DATUM[\"x\",SPHEROID["
+ "\"x\",1,0.5]],PRIMEM[\"x\",0],UNIT["
+ "\"degree\",0.0174532925199433],"
+ "AXIS[\"latitude\","
+ "NORTH],AXIS[\"longitude\",EAST]]"),
+ ParsingException);
+
+ // 3 axis required
+ EXPECT_THROW(WKTParser().createFromWKT(
+ "GEOCCS[\"x\",DATUM[\"x\",SPHEROID[\"x\",1,0.5]],PRIMEM["
+ "\"x\",0],UNIT[\"metre\",1],AXIS["
+ "\"Geocentric X\",OTHER],AXIS[\"Geocentric Y\",OTHER]]"),
+ ParsingException);
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(wkt_parse, invalid_CS_of_GEODCRS) {
+
+ std::string startWKT("GEODCRS[\"x\",DATUM[\"x\",SPHEROID[\"x\",1,0.5]]");
+
+ // missing CS
+ EXPECT_THROW(WKTParser().createFromWKT(startWKT + "]"), ParsingException);
+
+ // CS: not enough children
+ EXPECT_THROW(WKTParser().createFromWKT(startWKT + ",CS[x]]"),
+ ParsingException);
+
+ // CS: invalid type
+ EXPECT_THROW(WKTParser().createFromWKT(startWKT + ",CS[x,2]]"),
+ ParsingException);
+
+ // CS: invalid number of axis
+ EXPECT_THROW(WKTParser().createFromWKT(startWKT + ",CS[ellipsoidal,0]]"),
+ ParsingException);
+
+ // CS: number of axis is not a number
+ EXPECT_THROW(
+ WKTParser().createFromWKT(startWKT + ",CS[ellipsoidal,\"x\"]]"),
+ ParsingException);
+
+ // CS: invalid CS type
+ EXPECT_THROW(WKTParser().createFromWKT(startWKT +
+ ",CS[invalid,2],AXIS[\"latitude\","
+ "north],AXIS[\"longitude\",east]]"),
+ ParsingException);
+
+ // CS: OK
+ EXPECT_NO_THROW(WKTParser().createFromWKT(
+ startWKT + ",CS[ellipsoidal,2],AXIS[\"latitude\",north],AXIS["
+ "\"longitude\",east]]"));
+
+ // CS: Cartesian with 2 axis unexpected
+ EXPECT_THROW(WKTParser().createFromWKT(startWKT +
+ ",CS[Cartesian,2],AXIS[\"latitude\","
+ "north],AXIS[\"longitude\",east]]"),
+ ParsingException);
+
+ // CS: missing axis
+ EXPECT_THROW(WKTParser().createFromWKT(
+ startWKT + ",CS[ellipsoidal,2],AXIS[\"latitude\",north]]"),
+ ParsingException);
+
+ // not enough children in AXIS
+ EXPECT_THROW(
+ WKTParser().createFromWKT(
+ startWKT +
+ ",CS[ellipsoidal,2],AXIS[\"latitude\",north],AXIS[\"longitude\"]]"),
+ ParsingException);
+
+ // not enough children in ORDER
+ EXPECT_THROW(WKTParser().createFromWKT(
+ startWKT +
+ ",CS[ellipsoidal,2],AXIS[\"latitude\",north,ORDER[]],AXIS["
+ "\"longitude\",east]]"),
+ ParsingException);
+
+ // invalid value in ORDER
+ EXPECT_THROW(
+ WKTParser().createFromWKT(
+ startWKT +
+ ",CS[ellipsoidal,2],AXIS[\"latitude\",north,ORDER[\"x\"]],AXIS["
+ "\"longitude\",east]]"),
+ ParsingException);
+
+ // unexpected ORDER value
+ EXPECT_THROW(
+ WKTParser().createFromWKT(
+ startWKT +
+ ",CS[ellipsoidal,2],AXIS[\"latitude\",north,ORDER[2]],AXIS["
+ "\"longitude\",east]]"),
+ ParsingException);
+
+ // Invalid CS type
+ EXPECT_THROW(WKTParser().createFromWKT(
+ startWKT +
+ ",CS[vertical,1],\n"
+ " AXIS[\"gravity-related height (H)\",up],\n"
+ " UNIT[\"metre\",1]]"),
+ ParsingException);
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(wkt_parse, invalid_CS_of_GEOGRAPHICCRS) {
+ // A GeographicCRS must have an ellipsoidal CS
+ EXPECT_THROW(WKTParser().createFromWKT(
+ "GEOGRAPHICCRS[\"x\",DATUM[\"x\",SPHEROID[\"x\",1,0.5]],"
+ "CS[Cartesian,3],AXIS[\"(X)\",geocentricX],AXIS[\"(Y)\","
+ "geocentricY],AXIS[\"(Z)\",geocentricZ]]"),
+ ParsingException);
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(wkt_parse, invalid_DYNAMIC) {
+ std::string prefix("GEOGCRS[\"WGS 84 (G1762)\",");
+ std::string suffix(
+ "TRF[\"World Geodetic System 1984 (G1762)\","
+ "ELLIPSOID[\"WGS 84\",6378137,298.257223563]],"
+ "CS[ellipsoidal,3],"
+ " AXIS[\"(lat)\",north,ANGLEUNIT[\"degree\",0.0174532925199433]],"
+ " AXIS[\"(lon)\",east,ANGLEUNIT[\"degree\",0.0174532925199433]],"
+ " AXIS[\"ellipsoidal height (h)\",up,LENGTHUNIT[\"metre\",1.0]]"
+ "]");
+
+ EXPECT_NO_THROW(WKTParser().createFromWKT(
+ prefix + "DYNAMIC[FRAMEEPOCH[2015]]," + suffix));
+
+ EXPECT_THROW(WKTParser().createFromWKT(prefix + "DYNAMIC[]," + suffix),
+ ParsingException);
+ EXPECT_THROW(
+ WKTParser().createFromWKT(prefix + "DYNAMIC[FRAMEEPOCH[]]," + suffix),
+ ParsingException);
+ EXPECT_THROW(WKTParser().createFromWKT(
+ prefix + "DYNAMIC[FRAMEEPOCH[\"invalid\"]]," + suffix),
+ ParsingException);
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(wkt_parse, invalid_PROJCRS) {
+ // missing BASEGEODCRS
+ EXPECT_THROW(
+ WKTParser().createFromWKT("PROJCRS[\"WGS 84 / UTM zone 31N\"]"),
+ ParsingException);
+
+ std::string startWKT("PROJCRS[\"WGS 84 / UTM zone 31N\",BASEGEOGCRS[\"WGS "
+ "84\",DATUM[\"WGS_1984\",ELLIPSOID[\"WGS "
+ "84\",6378137,298.257223563]],UNIT[\"degree\",0."
+ "0174532925199433]]");
+
+ // missing CONVERSION
+ EXPECT_THROW(WKTParser().createFromWKT(startWKT + "]"), ParsingException);
+
+ // not enough children in CONVERSION
+ EXPECT_THROW(WKTParser().createFromWKT(startWKT + ",CONVERSION[\"x\"]]"),
+ ParsingException);
+
+ // not enough children in METHOD
+ EXPECT_THROW(
+ WKTParser().createFromWKT(startWKT + ",CONVERSION[\"x\",METHOD[]]]"),
+ ParsingException);
+
+ // not enough children in PARAMETER
+ EXPECT_THROW(
+ WKTParser().createFromWKT(
+ startWKT + ",CONVERSION[\"x\",METHOD[\"y\"],PARAMETER[\"z\"]]]"),
+ ParsingException);
+
+ // non numeric value for PARAMETER
+ EXPECT_THROW(
+ WKTParser().createFromWKT(
+ startWKT +
+ ",CONVERSION[\"x\",METHOD[\"y\"],PARAMETER[\"z\",\"foo\"]]]"),
+ ParsingException);
+
+ // missing CS
+ EXPECT_THROW(WKTParser().createFromWKT(startWKT +
+ ",CONVERSION[\"x\",METHOD[\"y\"]]]"),
+ ParsingException);
+
+ // CS is not Cartesian
+ EXPECT_THROW(WKTParser().createFromWKT(
+ startWKT + ",CONVERSION[\"x\",METHOD[\"y\"]],CS["
+ "ellipsoidal,2],AXIS[\"latitude\",north],AXIS["
+ "\"longitude\",east]]"),
+ ParsingException);
+
+ // not enough children in MERIDIAN
+ EXPECT_THROW(WKTParser().createFromWKT(
+ startWKT + ",CONVERSION[\"x\",METHOD[\"y\"]],CS["
+ "Cartesian,2],AXIS[\"easting (X)\",south,"
+ "MERIDIAN[90]],AXIS["
+ "\"northing (Y)\",south]]"),
+ ParsingException);
+
+ // non numeric angle value for MERIDIAN
+ EXPECT_THROW(
+ WKTParser().createFromWKT(
+ startWKT +
+ ",CONVERSION[\"x\",METHOD[\"y\"]],CS["
+ "Cartesian,2],AXIS[\"easting (X)\",south,"
+ "MERIDIAN[\"x\",UNIT[\"degree\",0.0174532925199433]]],AXIS["
+ "\"northing (Y)\",south]]"),
+ ParsingException);
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(wkt_parse, invalid_PROJCS) {
+
+ std::string startWKT(
+ "PROJCS[\"WGS 84 / UTM zone 31N\",\n"
+ " GEOGCS[\"WGS 84\",\n"
+ " DATUM[\"WGS_1984\",\n"
+ " SPHEROID[\"WGS 84\",6378137,298.257223563,\n"
+ " AUTHORITY[\"EPSG\",\"7030\"]],\n"
+ " AUTHORITY[\"EPSG\",\"6326\"]],\n"
+ " PRIMEM[\"x\",0],\n"
+ " UNIT[\"degree\",0.0174532925199433,\n"
+ " AUTHORITY[\"EPSG\",9122]],\n"
+ " AXIS[\"latitude\",NORTH],\n"
+ " AXIS[\"longitude\",EAST],\n"
+ " AUTHORITY[\"EPSG\",\"4326\"]]\n");
+
+ // missing PROJECTION
+ EXPECT_THROW(WKTParser().createFromWKT(startWKT + "]"), ParsingException);
+
+ // not enough children in PROJECTION
+ EXPECT_THROW(WKTParser().createFromWKT(startWKT +
+ ",PROJECTION[],UNIT[\"metre\",1]]"),
+ ParsingException);
+
+ // not enough children in PARAMETER
+ EXPECT_THROW(WKTParser().createFromWKT(
+ startWKT +
+ ",PROJECTION[\"x\"],PARAMETER[\"z\"],UNIT[\"metre\",1]]"),
+ ParsingException);
+
+ // not enough children in PARAMETER
+ EXPECT_THROW(
+ WKTParser().createFromWKT(
+ startWKT +
+ ",PROJECTION[\"x\"],PARAMETER[\"z\",\"foo\"],UNIT[\"metre\",1]]"),
+ ParsingException);
+
+ EXPECT_NO_THROW(WKTParser().createFromWKT(
+ startWKT + ",PROJECTION[\"x\"],UNIT[\"metre\",1]]"));
+
+ // missing UNIT
+ EXPECT_THROW(WKTParser().createFromWKT(startWKT + ",PROJECTION[\"x\"]]"),
+ ParsingException);
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(wkt_parse, invalid_VERTCRS) {
+
+ // missing VDATUM
+ EXPECT_THROW(WKTParser().createFromWKT(
+ "VERTCRS[\"foo\",CS[vertical,1],AXIS[\"x\",up]]"),
+ ParsingException);
+
+ // missing CS
+ EXPECT_THROW(WKTParser().createFromWKT("VERTCRS[\"foo\",VDATUM[\"bar\"]]"),
+ ParsingException);
+
+ // CS is not of type vertical
+ EXPECT_THROW(WKTParser().createFromWKT("VERTCRS[\"foo\",VDATUM[\"bar\"],CS["
+ "ellipsoidal,2],AXIS[\"latitude\","
+ "north],AXIS["
+ "\"longitude\",east]]"),
+ ParsingException);
+
+ // verticalCS should have only 1 axis
+ EXPECT_THROW(
+ WKTParser().createFromWKT("VERTCRS[\"foo\",VDATUM[\"bar\"],CS[vertical,"
+ "2],AXIS[\"latitude\",north],AXIS["
+ "\"longitude\",east]]"),
+ ParsingException);
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(wkt_parse, invalid_VERT_CS) {
+
+ EXPECT_NO_THROW(WKTParser().createFromWKT(
+ "VERT_CS[\"x\",VERT_DATUM[\"y\",2005],UNIT[\"metre\",1]]"));
+
+ // Missing VERT_DATUM
+ EXPECT_THROW(WKTParser().createFromWKT("VERT_CS[\"x\",UNIT[\"metre\",1]]"),
+ ParsingException);
+
+ // Missing UNIT
+ EXPECT_THROW(
+ WKTParser().createFromWKT("VERT_CS[\"x\",VERT_DATUM[\"y\",2005]]"),
+ ParsingException);
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(wkt_parse, invalid_COORDINATEOPERATION) {
+
+ std::string src_wkt;
+ {
+ auto formatter = WKTFormatter::create();
+ formatter->setOutputId(false);
+ src_wkt = GeographicCRS::EPSG_4326->exportToWKT(formatter.get());
+ }
+
+ std::string dst_wkt;
+ {
+ auto formatter = WKTFormatter::create();
+ formatter->setOutputId(false);
+ dst_wkt = GeographicCRS::EPSG_4807->exportToWKT(formatter.get());
+ }
+
+ std::string interpolation_wkt;
+ {
+ auto formatter = WKTFormatter::create();
+ formatter->setOutputId(false);
+ interpolation_wkt =
+ GeographicCRS::EPSG_4979->exportToWKT(formatter.get());
+ }
+
+ // Valid
+ {
+ auto wkt = "COORDINATEOPERATION[\"transformationName\",\n"
+ " SOURCECRS[" +
+ src_wkt + "],\n"
+ " TARGETCRS[" +
+ dst_wkt + "],\n"
+ " METHOD[\"operationMethodName\"],\n"
+ " PARAMETERFILE[\"paramName\",\"foo.bin\"]]";
+
+ EXPECT_NO_THROW(WKTParser().createFromWKT(wkt));
+ }
+
+ // Missing SOURCECRS
+ {
+ auto wkt = "COORDINATEOPERATION[\"transformationName\",\n"
+ " TARGETCRS[" +
+ dst_wkt + "],\n"
+ " METHOD[\"operationMethodName\"],\n"
+ " PARAMETERFILE[\"paramName\",\"foo.bin\"]]";
+
+ EXPECT_THROW(WKTParser().createFromWKT(wkt), ParsingException);
+ }
+
+ // Invalid content in SOURCECRS
+ {
+ auto wkt = "COORDINATEOPERATION[\"transformationName\",\n"
+ " SOURCECRS[FOO],\n"
+ " TARGETCRS[" +
+ dst_wkt + "],\n"
+ " METHOD[\"operationMethodName\"],\n"
+ " PARAMETERFILE[\"paramName\",\"foo.bin\"]]";
+
+ EXPECT_THROW(WKTParser().createFromWKT(wkt), ParsingException);
+ }
+
+ // Missing TARGETCRS
+ {
+ auto wkt = "COORDINATEOPERATION[\"transformationName\",\n"
+ " SOURCECRS[" +
+ src_wkt + "],\n"
+ " METHOD[\"operationMethodName\"],\n"
+ " PARAMETERFILE[\"paramName\",\"foo.bin\"]]";
+
+ EXPECT_THROW(WKTParser().createFromWKT(wkt), ParsingException);
+ }
+
+ // Invalid content in TARGETCRS
+ {
+ auto wkt = "COORDINATEOPERATION[\"transformationName\",\n"
+ " SOURCECRS[" +
+ src_wkt + "],\n"
+ " TARGETCRS[FOO],\n"
+ " METHOD[\"operationMethodName\"],\n"
+ " PARAMETERFILE[\"paramName\",\"foo.bin\"]]";
+
+ EXPECT_THROW(WKTParser().createFromWKT(wkt), ParsingException);
+ }
+
+ // Missing METHOD
+ {
+ auto wkt = "COORDINATEOPERATION[\"transformationName\",\n"
+ " SOURCECRS[" +
+ src_wkt + "],\n"
+ " TARGETCRS[" +
+ dst_wkt + "]]";
+
+ EXPECT_THROW(WKTParser().createFromWKT(wkt), ParsingException);
+ }
+
+ // Invalid METHOD
+ {
+ auto wkt = "COORDINATEOPERATION[\"transformationName\",\n"
+ " SOURCECRS[" +
+ src_wkt + "],\n"
+ " TARGETCRS[" +
+ dst_wkt + "],\n"
+ " METHOD[],\n"
+ " PARAMETERFILE[\"paramName\",\"foo.bin\"]]";
+
+ EXPECT_THROW(WKTParser().createFromWKT(wkt), ParsingException);
+ }
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(wkt_parse, invalid_CONCATENATEDOPERATION) {
+
+ // No STEP
+ EXPECT_THROW(WKTParser().createFromWKT("CONCATENATEDOPERATION[\"name\"]"),
+ ParsingException);
+
+ auto transf_1 = Transformation::create(
+ PropertyMap().set(IdentifiedObject::NAME_KEY, "transf_1"),
+ nn_static_pointer_cast<CRS>(GeographicCRS::EPSG_4326),
+ nn_static_pointer_cast<CRS>(GeographicCRS::EPSG_4807), nullptr,
+ PropertyMap().set(IdentifiedObject::NAME_KEY, "operationMethodName"),
+ std::vector<OperationParameterNNPtr>{OperationParameter::create(
+ PropertyMap().set(IdentifiedObject::NAME_KEY, "paramName"))},
+ std::vector<ParameterValueNNPtr>{
+ ParameterValue::createFilename("foo.bin")},
+ std::vector<PositionalAccuracyNNPtr>());
+
+ // One single STEP
+ {
+ auto wkt =
+ "CONCATENATEDOPERATION[\"name\",\n"
+ " SOURCECRS[" +
+ transf_1->sourceCRS()->exportToWKT(WKTFormatter::create().get()) +
+ "],\n"
+ " TARGETCRS[" +
+ transf_1->targetCRS()->exportToWKT(WKTFormatter::create().get()) +
+ "],\n"
+ " STEP[" +
+ transf_1->exportToWKT(WKTFormatter::create().get()) +
+ "],\n"
+ " ID[\"codeSpace\",\"code\"],\n"
+ " REMARK[\"my remarks\"]]";
+
+ EXPECT_THROW(WKTParser().createFromWKT(wkt), ParsingException);
+ }
+
+ // empty STEP
+ {
+ auto wkt =
+ "CONCATENATEDOPERATION[\"name\",\n"
+ " SOURCECRS[" +
+ transf_1->sourceCRS()->exportToWKT(WKTFormatter::create().get()) +
+ "],\n"
+ " TARGETCRS[" +
+ transf_1->targetCRS()->exportToWKT(WKTFormatter::create().get()) +
+ "],\n"
+ " STEP[],\n"
+ " STEP[],\n"
+ " ID[\"codeSpace\",\"code\"],\n"
+ " REMARK[\"my remarks\"]]";
+ EXPECT_THROW(WKTParser().createFromWKT(wkt), ParsingException);
+ }
+
+ // Invalid content in STEP
+ {
+ auto wkt =
+ "CONCATENATEDOPERATION[\"name\",\n"
+ " SOURCECRS[" +
+ transf_1->sourceCRS()->exportToWKT(WKTFormatter::create().get()) +
+ "],\n"
+ " TARGETCRS[" +
+ transf_1->targetCRS()->exportToWKT(WKTFormatter::create().get()) +
+ "],\n"
+ " STEP[" +
+ transf_1->sourceCRS()->exportToWKT(WKTFormatter::create().get()) +
+ "],\n"
+ " STEP[" +
+ transf_1->sourceCRS()->exportToWKT(WKTFormatter::create().get()) +
+ "],\n"
+ " ID[\"codeSpace\",\"code\"],\n"
+ " REMARK[\"my remarks\"]]";
+
+ EXPECT_THROW(WKTParser().createFromWKT(wkt), ParsingException);
+ }
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(wkt_parse, invalid_BOUNDCRS) {
+
+ auto projcrs = ProjectedCRS::create(
+ PropertyMap().set(IdentifiedObject::NAME_KEY, "my PROJCRS"),
+ GeographicCRS::create(
+ PropertyMap().set(IdentifiedObject::NAME_KEY, "my GEOGCRS"),
+ GeodeticReferenceFrame::EPSG_6326,
+ EllipsoidalCS::createLatitudeLongitude(UnitOfMeasure::DEGREE)),
+ Conversion::createUTM(PropertyMap(), 31, true),
+ CartesianCS::createEastingNorthing(UnitOfMeasure::METRE));
+
+ EXPECT_NO_THROW(WKTParser().createFromWKT(
+ "BOUNDCRS[SOURCECRS[" +
+ projcrs->exportToWKT(WKTFormatter::create().get()) + "],\n" +
+ "TARGETCRS[" +
+ GeographicCRS::EPSG_4326->exportToWKT(WKTFormatter::create().get()) +
+ "],\n"
+ " ABRIDGEDTRANSFORMATION[\"foo\",\n"
+ " METHOD[\"bar\"]]]"));
+
+ // Missing SOURCECRS
+ EXPECT_THROW(
+ WKTParser().createFromWKT("BOUNDCRS[TARGETCRS[" +
+ GeographicCRS::EPSG_4326->exportToWKT(
+ WKTFormatter::create().get()) +
+ "],\n"
+ " ABRIDGEDTRANSFORMATION[\"foo\",\n"
+ " METHOD[\"bar\"]]]"),
+ ParsingException);
+
+ // Invalid SOURCECRS
+ EXPECT_THROW(
+ WKTParser().createFromWKT("BOUNDCRS[SOURCECRS[foo], TARGETCRS[" +
+ GeographicCRS::EPSG_4326->exportToWKT(
+ WKTFormatter::create().get()) +
+ "],\n"
+ " ABRIDGEDTRANSFORMATION[\"foo\",\n"
+ " METHOD[\"bar\"]]]"),
+ ParsingException);
+
+ // Missing TARGETCRS
+ EXPECT_THROW(WKTParser().createFromWKT(
+ "BOUNDCRS[SOURCECRS[" +
+ projcrs->exportToWKT(WKTFormatter::create().get()) +
+ "],\n"
+ " ABRIDGEDTRANSFORMATION[\"foo\",\n"
+ " METHOD[\"bar\"]]]"),
+ ParsingException);
+
+ // Invalid TARGETCRS
+ EXPECT_THROW(WKTParser().createFromWKT(
+ "BOUNDCRS[SOURCECRS[" +
+ projcrs->exportToWKT(WKTFormatter::create().get()) +
+ "],TARGETCRS[\"foo\"],\n"
+ " ABRIDGEDTRANSFORMATION[\"foo\",\n"
+ " METHOD[\"bar\"]]]"),
+ ParsingException);
+
+ // Missing ABRIDGEDTRANSFORMATION
+ EXPECT_THROW(WKTParser().createFromWKT(
+ "BOUNDCRS[SOURCECRS[" +
+ projcrs->exportToWKT(WKTFormatter::create().get()) +
+ "],\n" + "TARGETCRS[" +
+ GeographicCRS::EPSG_4326->exportToWKT(
+ WKTFormatter::create().get()) +
+ "]]"),
+ ParsingException);
+
+ // Missing METHOD
+ EXPECT_THROW(WKTParser().createFromWKT(
+ "BOUNDCRS[SOURCECRS[" +
+ projcrs->exportToWKT(WKTFormatter::create().get()) +
+ "],\n" + "TARGETCRS[" +
+ GeographicCRS::EPSG_4326->exportToWKT(
+ WKTFormatter::create().get()) +
+ "],"
+ "ABRIDGEDTRANSFORMATION[\"foo\"]]"),
+ ParsingException);
+
+ // Invalid METHOD
+ EXPECT_THROW(WKTParser().createFromWKT(
+ "BOUNDCRS[SOURCECRS[" +
+ projcrs->exportToWKT(WKTFormatter::create().get()) +
+ "],\n" + "TARGETCRS[" +
+ GeographicCRS::EPSG_4326->exportToWKT(
+ WKTFormatter::create().get()) +
+ "],"
+ "ABRIDGEDTRANSFORMATION[\"foo\",METHOD[]]]"),
+ ParsingException);
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(wkt_parse, invalid_TOWGS84) {
+ EXPECT_THROW(WKTParser().createFromWKT(
+ "GEOGCS[\"WGS 84\","
+ " DATUM[\"WGS_1984\","
+ " SPHEROID[\"WGS 84\",6378137,298.257223563],"
+ " TOWGS84[0]],"
+ " PRIMEM[\"Greenwich\",0],"
+ " UNIT[\"degree\",0.0174532925199433]]"),
+ ParsingException);
+
+ EXPECT_THROW(WKTParser().createFromWKT(
+ "GEOGCS[\"WGS 84\","
+ " DATUM[\"WGS_1984\","
+ " SPHEROID[\"WGS 84\",6378137,298.257223563],"
+ " TOWGS84[0,0,0,0,0,0,\"foo\"]],"
+ " PRIMEM[\"Greenwich\",0],"
+ " UNIT[\"degree\",0.0174532925199433]]"),
+ ParsingException);
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(wkt_parse, invalid_DerivedGeographicCRS) {
+
+ EXPECT_NO_THROW(WKTParser().createFromWKT(
+ "GEODCRS[\"WMO Atlantic Pole\",\n"
+ " BASEGEODCRS[\"WGS 84\",\n"
+ " DATUM[\"World Geodetic System 1984\",\n"
+ " ELLIPSOID[\"WGS 84\",6378137,298.257223563]]],\n"
+ " DERIVINGCONVERSION[\"foo\",\n"
+ " METHOD[\"bar\"]],\n"
+ " CS[ellipsoidal,2],\n"
+ " AXIS[\"latitude\",north],\n"
+ " AXIS[\"longitude\",east]]"));
+
+ // Missing DERIVINGCONVERSION
+ EXPECT_THROW(
+ WKTParser().createFromWKT(
+ "GEODCRS[\"WMO Atlantic Pole\",\n"
+ " BASEGEODCRS[\"WGS 84\",\n"
+ " DATUM[\"World Geodetic System 1984\",\n"
+ " ELLIPSOID[\"WGS 84\",6378137,298.257223563]]],\n"
+ " CS[ellipsoidal,2],\n"
+ " AXIS[\"latitude\",north],\n"
+ " AXIS[\"longitude\",east]]"),
+ ParsingException);
+
+ // Missing CS
+ EXPECT_THROW(
+ WKTParser().createFromWKT(
+ "GEODCRS[\"WMO Atlantic Pole\",\n"
+ " BASEGEODCRS[\"WGS 84\",\n"
+ " DATUM[\"World Geodetic System 1984\",\n"
+ " ELLIPSOID[\"WGS 84\",6378137,298.257223563]]],\n"
+ " DERIVINGCONVERSION[\"foo\",\n"
+ " METHOD[\"bar\"]]]"),
+ ParsingException);
+
+ // CS should be ellipsoidal given root node is GEOGCRS
+ EXPECT_THROW(
+ WKTParser().createFromWKT(
+ "GEOGCRS[\"WMO Atlantic Pole\",\n"
+ " BASEGEOGCRS[\"WGS 84\",\n"
+ " DATUM[\"World Geodetic System 1984\",\n"
+ " ELLIPSOID[\"WGS 84\",6378137,298.257223563]]],\n"
+ " DERIVINGCONVERSION[\"foo\",\n"
+ " METHOD[\"bar\"]],\n"
+ " CS[Cartesian,3],\n"
+ " AXIS[\"(X)\",geocentricX],\n"
+ " AXIS[\"(Y)\",geocentricY],\n"
+ " AXIS[\"(Z)\",geocentricZ],\n"
+ " UNIT[\"metre\",1]]"),
+ ParsingException);
+
+ // CS should have 3 axis
+ EXPECT_THROW(
+ WKTParser().createFromWKT(
+ "GEODCRS[\"WMO Atlantic Pole\",\n"
+ " BASEGEODCRS[\"WGS 84\",\n"
+ " DATUM[\"World Geodetic System 1984\",\n"
+ " ELLIPSOID[\"WGS 84\",6378137,298.257223563]]],\n"
+ " DERIVINGCONVERSION[\"foo\",\n"
+ " METHOD[\"bar\"]],\n"
+ " CS[Cartesian,2],\n"
+ " AXIS[\"(X)\",geocentricX],\n"
+ " AXIS[\"(Y)\",geocentricY],\n"
+ " UNIT[\"metre\",1]]"),
+ ParsingException);
+
+ // Invalid CS type
+ EXPECT_THROW(
+ WKTParser().createFromWKT(
+ "GEODCRS[\"WMO Atlantic Pole\",\n"
+ " BASEGEODCRS[\"WGS 84\",\n"
+ " DATUM[\"World Geodetic System 1984\",\n"
+ " ELLIPSOID[\"WGS 84\",6378137,298.257223563]]],\n"
+ " DERIVINGCONVERSION[\"foo\",\n"
+ " METHOD[\"bar\"]],\n"
+ " CS[vertical,1],\n"
+ " AXIS[\"gravity-related height (H)\",up],\n"
+ " UNIT[\"metre\",1]]"),
+ ParsingException);
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(wkt_parse, invalid_TemporalCRS) {
+
+ EXPECT_NO_THROW(
+ WKTParser().createFromWKT("TIMECRS[\"Temporal CRS\",\n"
+ " TDATUM[\"Gregorian calendar\",\n"
+ " TIMEORIGIN[0000-01-01]],\n"
+ " CS[temporal,1],\n"
+ " AXIS[\"time (T)\",future]]"));
+
+ // Missing TDATUM
+ EXPECT_THROW(
+ WKTParser().createFromWKT("TIMECRS[\"Temporal CRS\",\n"
+ " CS[temporal,1],\n"
+ " AXIS[\"time (T)\",future]]"),
+ ParsingException);
+
+ // Missing CS
+ EXPECT_THROW(
+ WKTParser().createFromWKT("TIMECRS[\"Temporal CRS\",\n"
+ " TDATUM[\"Gregorian calendar\",\n"
+ " TIMEORIGIN[0000-01-01]]]"),
+ ParsingException);
+
+ // CS should be temporal
+ EXPECT_THROW(
+ WKTParser().createFromWKT("TIMECRS[\"Temporal CRS\",\n"
+ " TDATUM[\"Gregorian calendar\",\n"
+ " TIMEORIGIN[0000-01-01]],\n"
+ " CS[Cartesian,2],\n"
+ " AXIS[\"(X)\",geocentricX],\n"
+ " AXIS[\"(Y)\",geocentricY],\n"
+ " UNIT[\"metre\",1]]"),
+ ParsingException);
+
+ // CS should have 1 axis
+ EXPECT_THROW(
+ WKTParser().createFromWKT("TIMECRS[\"Temporal CRS\",\n"
+ " TDATUM[\"Gregorian calendar\",\n"
+ " TIMEORIGIN[0000-01-01]],\n"
+ " CS[temporal,2],\n"
+ " AXIS[\"time (T)\",future],\n"
+ " AXIS[\"time2 (T)\",future]]"),
+ ParsingException);
+
+ // CS should have 1 axis
+ EXPECT_THROW(
+ WKTParser().createFromWKT("TIMECRS[\"Temporal CRS\",\n"
+ " TDATUM[\"Gregorian calendar\",\n"
+ " TIMEORIGIN[0000-01-01]],\n"
+ " CS[TemporalDateTime,2],\n"
+ " AXIS[\"time (T)\",future],\n"
+ " AXIS[\"time2 (T)\",future]]"),
+ ParsingException);
+
+ // CS should have 1 axis
+ EXPECT_THROW(
+ WKTParser().createFromWKT("TIMECRS[\"Temporal CRS\",\n"
+ " TDATUM[\"Gregorian calendar\",\n"
+ " TIMEORIGIN[0000-01-01]],\n"
+ " CS[TemporalCount,2],\n"
+ " AXIS[\"time (T)\",future],\n"
+ " AXIS[\"time2 (T)\",future]]"),
+ ParsingException);
+
+ // CS should have 1 axis
+ EXPECT_THROW(
+ WKTParser().createFromWKT("TIMECRS[\"Temporal CRS\",\n"
+ " TDATUM[\"Gregorian calendar\",\n"
+ " TIMEORIGIN[0000-01-01]],\n"
+ " CS[TemporalMeasure,2],\n"
+ " AXIS[\"time (T)\",future],\n"
+ " AXIS[\"time2 (T)\",future]]"),
+ ParsingException);
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(wkt_parse, invalid_EngineeingCRS) {
+
+ EXPECT_NO_THROW(
+ WKTParser().createFromWKT("ENGCRS[\"name\",\n"
+ " EDATUM[\"name\"],\n"
+ " CS[temporal,1],\n"
+ " AXIS[\"time (T)\",future]]"));
+
+ // Missing EDATUM
+ EXPECT_THROW(
+ WKTParser().createFromWKT("ENGCRS[\"name\",\n"
+ " CS[temporal,1],\n"
+ " AXIS[\"time (T)\",future]]"),
+ ParsingException);
+
+ // Missing CS
+ EXPECT_THROW(WKTParser().createFromWKT("ENGCRS[\"name\",\n"
+ " EDATUM[\"name\"]]"),
+ ParsingException);
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(wkt_parse, invalid_LOCAL_CS) {
+
+ EXPECT_THROW(
+ WKTParser().createFromWKT("LOCAL_CS[\"name\",\n"
+ " LOCAL_DATUM[\"name\",1234],\n"
+ " AXIS[\"Geodetic latitude\",NORTH],\n"
+ " AXIS[\"Geodetic longitude\",EAST],\n"
+ " AXIS[\"Ellipsoidal height\",UP]]"),
+ ParsingException);
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(wkt_parse, invalid_ParametricCRS) {
+
+ EXPECT_NO_THROW(
+ WKTParser().createFromWKT("PARAMETRICCRS[\"name\",\n"
+ " PDATUM[\"name\"],\n"
+ " CS[parametric,1],\n"
+ " AXIS[\"time (T)\",future]]"));
+
+ // Missing PDATUM
+ EXPECT_THROW(
+ WKTParser().createFromWKT("PARAMETRICCRS[\"name\",\n"
+ " CS[parametric,1],\n"
+ " AXIS[\"time (T)\",future]]"),
+ ParsingException);
+
+ // Missing CS
+ EXPECT_THROW(WKTParser().createFromWKT("PARAMETRICCRS[\"name\",\n"
+ " PDATUM[\"name\"]]"),
+ ParsingException);
+
+ // Invalid number of axis for CS
+ EXPECT_THROW(
+ WKTParser().createFromWKT("PARAMETRICCRS[\"name\",\n"
+ " PDATUM[\"name\"],\n"
+ " CS[parametric,2],\n"
+ " AXIS[\"time (T)\",future],\n"
+ " AXIS[\"time (T)\",future]]"),
+ ParsingException);
+
+ // Invalid CS type
+ EXPECT_THROW(
+ WKTParser().createFromWKT("PARAMETRICCRS[\"name\",\n"
+ " PDATUM[\"name\"],\n"
+ " CS[temporal,1],\n"
+ " AXIS[\"time (T)\",future]]"),
+ ParsingException);
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(wkt_parse, invalid_DERIVEDPROJCRS) {
+ EXPECT_NO_THROW(WKTParser().createFromWKT(
+ "DERIVEDPROJCRS[\"derived projectedCRS\",\n"
+ " BASEPROJCRS[\"BASEPROJCRS\",\n"
+ " BASEGEOGCRS[\"WGS 84\",\n"
+ " DATUM[\"World Geodetic System 1984\",\n"
+ " ELLIPSOID[\"WGS 84\",6378137,298.257223563,\n"
+ " LENGTHUNIT[\"metre\",1]]]],\n"
+ " CONVERSION[\"unnamed\",\n"
+ " METHOD[\"PROJ unimplemented\"]]],\n"
+ " DERIVINGCONVERSION[\"unnamed\",\n"
+ " METHOD[\"PROJ unimplemented\"]],\n"
+ " CS[Cartesian,2],\n"
+ " AXIS[\"(E)\",east],\n"
+ " AXIS[\"(N)\",north]]"));
+
+ EXPECT_THROW(
+ WKTParser().createFromWKT("DERIVEDPROJCRS[\"derived projectedCRS\",\n"
+ " DERIVINGCONVERSION[\"unnamed\",\n"
+ " METHOD[\"PROJ unimplemented\"]],\n"
+ " CS[Cartesian,2],\n"
+ " AXIS[\"(E)\",east],\n"
+ " AXIS[\"(N)\",north]]"),
+ ParsingException);
+
+ // Missing DERIVINGCONVERSION
+ EXPECT_THROW(
+ WKTParser().createFromWKT(
+ "DERIVEDPROJCRS[\"derived projectedCRS\",\n"
+ " BASEPROJCRS[\"BASEPROJCRS\",\n"
+ " BASEGEOGCRS[\"WGS 84\",\n"
+ " DATUM[\"World Geodetic System 1984\",\n"
+ " ELLIPSOID[\"WGS 84\",6378137,298.257223563,\n"
+ " LENGTHUNIT[\"metre\",1]]]],\n"
+ " CONVERSION[\"unnamed\",\n"
+ " METHOD[\"PROJ unimplemented\"]]],\n"
+ " CS[Cartesian,2],\n"
+ " AXIS[\"(E)\",east],\n"
+ " AXIS[\"(N)\",north]]"),
+ ParsingException);
+
+ // Missing CS
+ EXPECT_THROW(
+ WKTParser().createFromWKT(
+ "DERIVEDPROJCRS[\"derived projectedCRS\",\n"
+ " BASEPROJCRS[\"BASEPROJCRS\",\n"
+ " BASEGEOGCRS[\"WGS 84\",\n"
+ " DATUM[\"World Geodetic System 1984\",\n"
+ " ELLIPSOID[\"WGS 84\",6378137,298.257223563,\n"
+ " LENGTHUNIT[\"metre\",1]]]],\n"
+ " CONVERSION[\"unnamed\",\n"
+ " METHOD[\"PROJ unimplemented\"]]],\n"
+ " DERIVINGCONVERSION[\"unnamed\",\n"
+ " METHOD[\"PROJ unimplemented\"]]]"),
+ ParsingException);
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(wkt_parse, invalid_DerivedVerticalCRS) {
+ EXPECT_NO_THROW(WKTParser().createFromWKT(
+ "VERTCRS[\"Derived vertCRS\",\n"
+ " BASEVERTCRS[\"ODN height\",\n"
+ " VDATUM[\"Ordnance Datum Newlyn\"]],\n"
+ " DERIVINGCONVERSION[\"unnamed\",\n"
+ " METHOD[\"PROJ unimplemented\"]],\n"
+ " CS[vertical,1],\n"
+ " AXIS[\"gravity-related height (H)\",up]]"));
+
+ // Missing DERIVINGCONVERSION
+ EXPECT_THROW(WKTParser().createFromWKT(
+ "VERTCRS[\"Derived vertCRS\",\n"
+ " BASEVERTCRS[\"ODN height\",\n"
+ " VDATUM[\"Ordnance Datum Newlyn\"]],\n"
+ " CS[vertical,1],\n"
+ " AXIS[\"gravity-related height (H)\",up]]"),
+ ParsingException);
+
+ // Missing CS
+ EXPECT_THROW(WKTParser().createFromWKT(
+ "VERTCRS[\"Derived vertCRS\",\n"
+ " BASEVERTCRS[\"ODN height\",\n"
+ " VDATUM[\"Ordnance Datum Newlyn\"]],\n"
+ " DERIVINGCONVERSION[\"unnamed\",\n"
+ " METHOD[\"PROJ unimplemented\"]]]"),
+ ParsingException);
+
+ // Wrong CS type
+ EXPECT_THROW(WKTParser().createFromWKT(
+ "VERTCRS[\"Derived vertCRS\",\n"
+ " BASEVERTCRS[\"ODN height\",\n"
+ " VDATUM[\"Ordnance Datum Newlyn\"]],\n"
+ " DERIVINGCONVERSION[\"unnamed\",\n"
+ " METHOD[\"PROJ unimplemented\"]],\n"
+ " CS[parametric,1],\n"
+ " AXIS[\"gravity-related height (H)\",up]]"),
+ ParsingException);
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(wkt_parse, invalid_DerivedEngineeringCRS) {
+ EXPECT_NO_THROW(
+ WKTParser().createFromWKT("ENGCRS[\"Derived EngineeringCRS\",\n"
+ " BASEENGCRS[\"Engineering CRS\",\n"
+ " EDATUM[\"Engineering datum\"]],\n"
+ " DERIVINGCONVERSION[\"unnamed\",\n"
+ " METHOD[\"PROJ unimplemented\"]],\n"
+ " CS[Cartesian,2],\n"
+ " AXIS[\"(E)\",east],\n"
+ " AXIS[\"(N)\",north],\n"
+ " LENGTHUNIT[\"metre\",1]]"));
+
+ // Missing DERIVINGCONVERSION
+ EXPECT_THROW(
+ WKTParser().createFromWKT("ENGCRS[\"Derived EngineeringCRS\",\n"
+ " BASEENGCRS[\"Engineering CRS\",\n"
+ " EDATUM[\"Engineering datum\"]],\n"
+ " CS[Cartesian,2],\n"
+ " AXIS[\"(E)\",east],\n"
+ " AXIS[\"(N)\",north],\n"
+ " LENGTHUNIT[\"metre\",1]]"),
+ ParsingException);
+
+ // Missing CS
+ EXPECT_THROW(
+ WKTParser().createFromWKT("ENGCRS[\"Derived EngineeringCRS\",\n"
+ " BASEENGCRS[\"Engineering CRS\",\n"
+ " EDATUM[\"Engineering datum\"]],\n"
+ " DERIVINGCONVERSION[\"unnamed\",\n"
+ " METHOD[\"PROJ unimplemented\"]]]"),
+ ParsingException);
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(wkt_parse, invalid_DerivedParametricCRS) {
+ EXPECT_NO_THROW(WKTParser().createFromWKT(
+ "PARAMETRICCRS[\"Derived ParametricCRS\",\n"
+ " BASEPARAMCRS[\"Parametric CRS\",\n"
+ " PDATUM[\"Parametric datum\"]],\n"
+ " DERIVINGCONVERSION[\"unnamed\",\n"
+ " METHOD[\"PROJ unimplemented\"]],\n"
+ " CS[parametric,1],\n"
+ " AXIS[\"pressure (hPa)\",up,\n"
+ " PARAMETRICUNIT[\"HectoPascal\",100]]]"));
+
+ // Missing DERIVINGCONVERSION
+ EXPECT_THROW(WKTParser().createFromWKT(
+ "PARAMETRICCRS[\"Derived ParametricCRS\",\n"
+ " BASEPARAMCRS[\"Parametric CRS\",\n"
+ " PDATUM[\"Parametric datum\"]],\n"
+ " CS[parametric,1],\n"
+ " AXIS[\"pressure (hPa)\",up,\n"
+ " PARAMETRICUNIT[\"HectoPascal\",100]]]"),
+ ParsingException);
+
+ // Missing CS
+ EXPECT_THROW(
+ WKTParser().createFromWKT("PARAMETRICCRS[\"Derived ParametricCRS\",\n"
+ " BASEPARAMCRS[\"Parametric CRS\",\n"
+ " PDATUM[\"Parametric datum\"]],\n"
+ " DERIVINGCONVERSION[\"unnamed\",\n"
+ " METHOD[\"PROJ unimplemented\"]]]"),
+ ParsingException);
+
+ // Wrong CS type
+ EXPECT_THROW(
+ WKTParser().createFromWKT("PARAMETRICCRS[\"Derived ParametricCRS\",\n"
+ " BASEPARAMCRS[\"Parametric CRS\",\n"
+ " PDATUM[\"Parametric datum\"]],\n"
+ " DERIVINGCONVERSION[\"unnamed\",\n"
+ " METHOD[\"PROJ unimplemented\"]],\n"
+ " CS[TemporalDateTime,1],\n"
+ " AXIS[\"time (T)\",future]]"),
+ ParsingException);
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(wkt_parse, invalid_DerivedTemporalCRS) {
+ EXPECT_NO_THROW(WKTParser().createFromWKT(
+ "TIMECRS[\"Derived TemporalCRS\",\n"
+ " BASETIMECRS[\"Temporal CRS\",\n"
+ " TDATUM[\"Gregorian calendar\",\n"
+ " CALENDAR[\"proleptic Gregorian\"],\n"
+ " TIMEORIGIN[0000-01-01]]],\n"
+ " DERIVINGCONVERSION[\"unnamed\",\n"
+ " METHOD[\"PROJ unimplemented\"]],\n"
+ " CS[TemporalDateTime,1],\n"
+ " AXIS[\"time (T)\",future]]"));
+
+ // Missing DERIVINGCONVERSION
+ EXPECT_THROW(WKTParser().createFromWKT(
+ "TIMECRS[\"Derived TemporalCRS\",\n"
+ " BASETIMECRS[\"Temporal CRS\",\n"
+ " TDATUM[\"Gregorian calendar\",\n"
+ " CALENDAR[\"proleptic Gregorian\"],\n"
+ " TIMEORIGIN[0000-01-01]]],\n"
+ " CS[TemporalDateTime,1],\n"
+ " AXIS[\"time (T)\",future]]"),
+ ParsingException);
+
+ // Missing CS
+ EXPECT_THROW(WKTParser().createFromWKT(
+ "TIMECRS[\"Derived TemporalCRS\",\n"
+ " BASETIMECRS[\"Temporal CRS\",\n"
+ " TDATUM[\"Gregorian calendar\",\n"
+ " CALENDAR[\"proleptic Gregorian\"],\n"
+ " TIMEORIGIN[0000-01-01]]],\n"
+ " DERIVINGCONVERSION[\"unnamed\",\n"
+ " METHOD[\"PROJ unimplemented\"]]]"),
+ ParsingException);
+
+ // Wrong CS type
+ EXPECT_THROW(WKTParser().createFromWKT(
+ "TIMECRS[\"Derived TemporalCRS\",\n"
+ " BASETIMECRS[\"Temporal CRS\",\n"
+ " TDATUM[\"Gregorian calendar\",\n"
+ " CALENDAR[\"proleptic Gregorian\"],\n"
+ " TIMEORIGIN[0000-01-01]]],\n"
+ " DERIVINGCONVERSION[\"unnamed\",\n"
+ " METHOD[\"PROJ unimplemented\"]],\n"
+ " CS[parametric,1],\n"
+ " AXIS[\"pressure (hPa)\",up,\n"
+ " PARAMETRICUNIT[\"HectoPascal\",100]]]"),
+ ParsingException);
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(io, projstringformatter) {
+
+ {
+ auto fmt = PROJStringFormatter::create();
+ fmt->addStep("my_proj");
+ EXPECT_EQ(fmt->toString(), "+proj=my_proj");
+ }
+
+ {
+ auto fmt = PROJStringFormatter::create();
+ fmt->addStep("my_proj");
+ fmt->setCurrentStepInverted(true);
+ EXPECT_EQ(fmt->toString(), "+proj=pipeline +step +inv +proj=my_proj");
+ }
+
+ {
+ auto fmt = PROJStringFormatter::create();
+ fmt->addStep("my_proj1");
+ fmt->addStep("my_proj2");
+ EXPECT_EQ(fmt->toString(),
+ "+proj=pipeline +step +proj=my_proj1 +step +proj=my_proj2");
+ }
+
+ {
+ auto fmt = PROJStringFormatter::create();
+ fmt->addStep("my_proj1");
+ fmt->setCurrentStepInverted(true);
+ fmt->addStep("my_proj2");
+ EXPECT_EQ(
+ fmt->toString(),
+ "+proj=pipeline +step +inv +proj=my_proj1 +step +proj=my_proj2");
+ }
+
+ {
+ auto fmt = PROJStringFormatter::create();
+ fmt->startInversion();
+ fmt->addStep("my_proj1");
+ fmt->setCurrentStepInverted(true);
+ fmt->addStep("my_proj2");
+ fmt->stopInversion();
+ EXPECT_EQ(
+ fmt->toString(),
+ "+proj=pipeline +step +inv +proj=my_proj2 +step +proj=my_proj1");
+ }
+
+ {
+ auto fmt = PROJStringFormatter::create();
+ fmt->startInversion();
+ fmt->addStep("my_proj1");
+ fmt->setCurrentStepInverted(true);
+ fmt->startInversion();
+ fmt->addStep("my_proj2");
+ fmt->stopInversion();
+ fmt->stopInversion();
+ EXPECT_EQ(fmt->toString(),
+ "+proj=pipeline +step +proj=my_proj2 +step +proj=my_proj1");
+ }
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(io, projstringformatter_helmert_3_param_noop) {
+ auto fmt = PROJStringFormatter::create();
+ fmt->addStep("helmert");
+ fmt->addParam("x", 0);
+ fmt->addParam("y", 0);
+ fmt->addParam("z", 0);
+ EXPECT_EQ(fmt->toString(), "");
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(io, projstringformatter_helmert_7_param_noop) {
+ auto fmt = PROJStringFormatter::create();
+ fmt->addStep("helmert");
+ fmt->addParam("x", 0);
+ fmt->addParam("y", 0);
+ fmt->addParam("z", 0);
+ fmt->addParam("rx", 0);
+ fmt->addParam("ry", 0);
+ fmt->addParam("rz", 0);
+ fmt->addParam("s", 0);
+ fmt->addParam("convention", "position_vector");
+ EXPECT_EQ(fmt->toString(), "");
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(io, projstringformatter_merge_consecutive_helmert_3_param) {
+ auto fmt = PROJStringFormatter::create();
+ fmt->addStep("helmert");
+ fmt->addParam("x", 10);
+ fmt->addParam("y", 20);
+ fmt->addParam("z", 30);
+ fmt->addStep("helmert");
+ fmt->addParam("x", -1);
+ fmt->addParam("y", -2);
+ fmt->addParam("z", -3);
+ EXPECT_EQ(fmt->toString(), "+proj=helmert +x=9 +y=18 +z=27");
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(io, projstringformatter_merge_consecutive_helmert_3_param_noop) {
+ auto fmt = PROJStringFormatter::create();
+ fmt->addStep("helmert");
+ fmt->addParam("x", 10);
+ fmt->addParam("y", 20);
+ fmt->addParam("z", 30);
+ fmt->addStep("helmert");
+ fmt->addParam("x", -10);
+ fmt->addParam("y", -20);
+ fmt->addParam("z", -30);
+ EXPECT_EQ(fmt->toString(), "");
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(io, projstringformatter_cart_grs80_wgs84) {
+ auto fmt = PROJStringFormatter::create();
+ fmt->addStep("cart");
+ fmt->addParam("ellps", "WGS84");
+ fmt->addStep("cart");
+ fmt->setCurrentStepInverted(true);
+ fmt->addParam("ellps", "GRS80");
+ EXPECT_EQ(fmt->toString(), "");
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(io, projparse_longlat) {
+
+ auto expected = "GEODCRS[\"unknown\",\n"
+ " DATUM[\"World Geodetic System 1984\",\n"
+ " ELLIPSOID[\"WGS 84\",6378137,298.257223563,\n"
+ " LENGTHUNIT[\"metre\",1]],\n"
+ " ID[\"EPSG\",6326]],\n"
+ " PRIMEM[\"Greenwich\",0,\n"
+ " ANGLEUNIT[\"degree\",0.0174532925199433],\n"
+ " ID[\"EPSG\",8901]],\n"
+ " CS[ellipsoidal,2],\n"
+ " AXIS[\"longitude\",east,\n"
+ " ORDER[1],\n"
+ " ANGLEUNIT[\"degree\",0.0174532925199433,\n"
+ " ID[\"EPSG\",9122]]],\n"
+ " AXIS[\"latitude\",north,\n"
+ " ORDER[2],\n"
+ " ANGLEUNIT[\"degree\",0.0174532925199433,\n"
+ " ID[\"EPSG\",9122]]]]";
+ {
+ auto obj = PROJStringParser().createFromPROJString("+proj=longlat");
+ auto crs = nn_dynamic_pointer_cast<GeographicCRS>(obj);
+ ASSERT_TRUE(crs != nullptr);
+ WKTFormatterNNPtr f(WKTFormatter::create());
+ crs->exportToWKT(f.get());
+ EXPECT_EQ(f->toString(), expected);
+ }
+
+ {
+ auto obj = PROJStringParser().createFromPROJString(
+ "+proj=longlat +datum=WGS84");
+ auto crs = nn_dynamic_pointer_cast<GeographicCRS>(obj);
+ ASSERT_TRUE(crs != nullptr);
+ WKTFormatterNNPtr f(WKTFormatter::create());
+ crs->exportToWKT(f.get());
+ EXPECT_EQ(f->toString(), expected);
+ }
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(io, projparse_longlat_datum_NAD83) {
+ auto obj =
+ PROJStringParser().createFromPROJString("+proj=longlat +datum=NAD83");
+ auto crs = nn_dynamic_pointer_cast<GeographicCRS>(obj);
+ ASSERT_TRUE(crs != nullptr);
+ WKTFormatterNNPtr f(WKTFormatter::create());
+ crs->exportToWKT(f.get());
+ EXPECT_EQ(f->toString(),
+ "GEODCRS[\"unknown\",\n"
+ " DATUM[\"North American Datum 1983\",\n"
+ " ELLIPSOID[\"GRS 1980\",6378137,298.257222101,\n"
+ " LENGTHUNIT[\"metre\",1]],\n"
+ " ID[\"EPSG\",6269]],\n"
+ " PRIMEM[\"Greenwich\",0,\n"
+ " ANGLEUNIT[\"degree\",0.0174532925199433],\n"
+ " ID[\"EPSG\",8901]],\n"
+ " CS[ellipsoidal,2],\n"
+ " AXIS[\"longitude\",east,\n"
+ " ORDER[1],\n"
+ " ANGLEUNIT[\"degree\",0.0174532925199433,\n"
+ " ID[\"EPSG\",9122]]],\n"
+ " AXIS[\"latitude\",north,\n"
+ " ORDER[2],\n"
+ " ANGLEUNIT[\"degree\",0.0174532925199433,\n"
+ " ID[\"EPSG\",9122]]]]");
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(io, projparse_longlat_datum_NAD27) {
+ auto obj =
+ PROJStringParser().createFromPROJString("+proj=longlat +datum=NAD27");
+ auto crs = nn_dynamic_pointer_cast<GeographicCRS>(obj);
+ ASSERT_TRUE(crs != nullptr);
+ WKTFormatterNNPtr f(WKTFormatter::create());
+ crs->exportToWKT(f.get());
+ EXPECT_EQ(f->toString(),
+ "GEODCRS[\"unknown\",\n"
+ " DATUM[\"North American Datum 1927\",\n"
+ " ELLIPSOID[\"Clarke 1866\",6378206.4,294.978698213898,\n"
+ " LENGTHUNIT[\"metre\",1]],\n"
+ " ID[\"EPSG\",6267]],\n"
+ " PRIMEM[\"Greenwich\",0,\n"
+ " ANGLEUNIT[\"degree\",0.0174532925199433],\n"
+ " ID[\"EPSG\",8901]],\n"
+ " CS[ellipsoidal,2],\n"
+ " AXIS[\"longitude\",east,\n"
+ " ORDER[1],\n"
+ " ANGLEUNIT[\"degree\",0.0174532925199433,\n"
+ " ID[\"EPSG\",9122]]],\n"
+ " AXIS[\"latitude\",north,\n"
+ " ORDER[2],\n"
+ " ANGLEUNIT[\"degree\",0.0174532925199433,\n"
+ " ID[\"EPSG\",9122]]]]");
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(io, projparse_longlat_datum_other) {
+ auto obj = PROJStringParser().createFromPROJString(
+ "+proj=longlat +datum=carthage");
+ auto crs = nn_dynamic_pointer_cast<GeographicCRS>(obj);
+ ASSERT_TRUE(crs != nullptr);
+ WKTFormatterNNPtr f(WKTFormatter::create());
+ crs->exportToWKT(f.get());
+ EXPECT_EQ(f->toString(),
+ "GEODCRS[\"unknown\",\n"
+ " DATUM[\"Carthage\",\n"
+ " ELLIPSOID[\"Clarke 1880 (IGN)\",6378249.2,293.4660213,\n"
+ " LENGTHUNIT[\"metre\",1]],\n"
+ " ID[\"EPSG\",6223]],\n"
+ " PRIMEM[\"Greenwich\",0,\n"
+ " ANGLEUNIT[\"degree\",0.0174532925199433],\n"
+ " ID[\"EPSG\",8901]],\n"
+ " CS[ellipsoidal,2],\n"
+ " AXIS[\"longitude\",east,\n"
+ " ORDER[1],\n"
+ " ANGLEUNIT[\"degree\",0.0174532925199433,\n"
+ " ID[\"EPSG\",9122]]],\n"
+ " AXIS[\"latitude\",north,\n"
+ " ORDER[2],\n"
+ " ANGLEUNIT[\"degree\",0.0174532925199433,\n"
+ " ID[\"EPSG\",9122]]]]");
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(io, projparse_longlat_ellps_WGS84) {
+ auto obj =
+ PROJStringParser().createFromPROJString("+proj=longlat +ellps=WGS84");
+ auto crs = nn_dynamic_pointer_cast<GeographicCRS>(obj);
+ ASSERT_TRUE(crs != nullptr);
+ WKTFormatterNNPtr f(WKTFormatter::create());
+ f->simulCurNodeHasId();
+ crs->exportToWKT(f.get());
+ auto expected = "GEODCRS[\"unknown\",\n"
+ " DATUM[\"Unknown based on WGS84 ellipsoid\",\n"
+ " ELLIPSOID[\"WGS 84\",6378137,298.257223563,\n"
+ " LENGTHUNIT[\"metre\",1]]],\n"
+ " PRIMEM[\"Greenwich\",0,\n"
+ " ANGLEUNIT[\"degree\",0.0174532925199433]],\n"
+ " CS[ellipsoidal,2],\n"
+ " AXIS[\"longitude\",east,\n"
+ " ORDER[1],\n"
+ " ANGLEUNIT[\"degree\",0.0174532925199433]],\n"
+ " AXIS[\"latitude\",north,\n"
+ " ORDER[2],\n"
+ " ANGLEUNIT[\"degree\",0.0174532925199433]]]";
+ EXPECT_EQ(f->toString(), expected);
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(io, projparse_longlat_ellps_GRS80) {
+ auto obj =
+ PROJStringParser().createFromPROJString("+proj=longlat +ellps=GRS80");
+ auto crs = nn_dynamic_pointer_cast<GeographicCRS>(obj);
+ ASSERT_TRUE(crs != nullptr);
+ WKTFormatterNNPtr f(WKTFormatter::create());
+ f->simulCurNodeHasId();
+ crs->exportToWKT(f.get());
+ auto expected = "GEODCRS[\"unknown\",\n"
+ " DATUM[\"Unknown based on GRS80 ellipsoid\",\n"
+ " ELLIPSOID[\"GRS 1980\",6378137,298.257222101,\n"
+ " LENGTHUNIT[\"metre\",1]]],\n"
+ " PRIMEM[\"Greenwich\",0,\n"
+ " ANGLEUNIT[\"degree\",0.0174532925199433]],\n"
+ " CS[ellipsoidal,2],\n"
+ " AXIS[\"longitude\",east,\n"
+ " ORDER[1],\n"
+ " ANGLEUNIT[\"degree\",0.0174532925199433]],\n"
+ " AXIS[\"latitude\",north,\n"
+ " ORDER[2],\n"
+ " ANGLEUNIT[\"degree\",0.0174532925199433]]]";
+ EXPECT_EQ(f->toString(), expected);
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(io, projparse_longlat_a_b) {
+ auto obj =
+ PROJStringParser().createFromPROJString("+proj=longlat +a=2 +b=1.5");
+ auto crs = nn_dynamic_pointer_cast<GeographicCRS>(obj);
+ ASSERT_TRUE(crs != nullptr);
+ WKTFormatterNNPtr f(WKTFormatter::create());
+ f->simulCurNodeHasId();
+ crs->exportToWKT(f.get());
+ auto expected = "GEODCRS[\"unknown\",\n"
+ " DATUM[\"unknown\",\n"
+ " ELLIPSOID[\"unknown\",2,4,\n"
+ " LENGTHUNIT[\"metre\",1]]],\n"
+ " PRIMEM[\"Reference meridian\",0,\n"
+ " ANGLEUNIT[\"degree\",0.0174532925199433]],\n"
+ " CS[ellipsoidal,2],\n"
+ " AXIS[\"longitude\",east,\n"
+ " ORDER[1],\n"
+ " ANGLEUNIT[\"degree\",0.0174532925199433]],\n"
+ " AXIS[\"latitude\",north,\n"
+ " ORDER[2],\n"
+ " ANGLEUNIT[\"degree\",0.0174532925199433]]]";
+ EXPECT_EQ(f->toString(), expected);
+ EXPECT_EQ(crs->ellipsoid()->celestialBody(), "Non-Earth body");
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(io, projparse_longlat_a_rf_WGS84) {
+ auto obj = PROJStringParser().createFromPROJString(
+ "+proj=longlat +a=6378137 +rf=298.257223563");
+ auto crs = nn_dynamic_pointer_cast<GeographicCRS>(obj);
+ ASSERT_TRUE(crs != nullptr);
+ WKTFormatterNNPtr f(WKTFormatter::create());
+ f->simulCurNodeHasId();
+ crs->exportToWKT(f.get());
+ auto expected = "GEODCRS[\"unknown\",\n"
+ " DATUM[\"unknown\",\n"
+ " ELLIPSOID[\"WGS 84\",6378137,298.257223563,\n"
+ " LENGTHUNIT[\"metre\",1]]],\n"
+ " PRIMEM[\"Greenwich\",0,\n"
+ " ANGLEUNIT[\"degree\",0.0174532925199433]],\n"
+ " CS[ellipsoidal,2],\n"
+ " AXIS[\"longitude\",east,\n"
+ " ORDER[1],\n"
+ " ANGLEUNIT[\"degree\",0.0174532925199433]],\n"
+ " AXIS[\"latitude\",north,\n"
+ " ORDER[2],\n"
+ " ANGLEUNIT[\"degree\",0.0174532925199433]]]";
+ EXPECT_EQ(f->toString(), expected);
+ EXPECT_EQ(crs->ellipsoid()->celestialBody(), Ellipsoid::EARTH);
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(io, projparse_longlat_a_rf) {
+ auto obj =
+ PROJStringParser().createFromPROJString("+proj=longlat +a=2 +rf=4");
+ auto crs = nn_dynamic_pointer_cast<GeographicCRS>(obj);
+ ASSERT_TRUE(crs != nullptr);
+ WKTFormatterNNPtr f(WKTFormatter::create());
+ f->simulCurNodeHasId();
+ crs->exportToWKT(f.get());
+ auto expected = "GEODCRS[\"unknown\",\n"
+ " DATUM[\"unknown\",\n"
+ " ELLIPSOID[\"unknown\",2,4,\n"
+ " LENGTHUNIT[\"metre\",1]]],\n"
+ " PRIMEM[\"Reference meridian\",0,\n"
+ " ANGLEUNIT[\"degree\",0.0174532925199433]],\n"
+ " CS[ellipsoidal,2],\n"
+ " AXIS[\"longitude\",east,\n"
+ " ORDER[1],\n"
+ " ANGLEUNIT[\"degree\",0.0174532925199433]],\n"
+ " AXIS[\"latitude\",north,\n"
+ " ORDER[2],\n"
+ " ANGLEUNIT[\"degree\",0.0174532925199433]]]";
+ EXPECT_EQ(f->toString(), expected);
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(io, projparse_longlat_R) {
+ auto obj = PROJStringParser().createFromPROJString("+proj=longlat +R=2");
+ auto crs = nn_dynamic_pointer_cast<GeographicCRS>(obj);
+ ASSERT_TRUE(crs != nullptr);
+ WKTFormatterNNPtr f(WKTFormatter::create());
+ f->simulCurNodeHasId();
+ crs->exportToWKT(f.get());
+ auto expected = "GEODCRS[\"unknown\",\n"
+ " DATUM[\"unknown\",\n"
+ " ELLIPSOID[\"unknown\",2,0,\n"
+ " LENGTHUNIT[\"metre\",1]]],\n"
+ " PRIMEM[\"Reference meridian\",0,\n"
+ " ANGLEUNIT[\"degree\",0.0174532925199433]],\n"
+ " CS[ellipsoidal,2],\n"
+ " AXIS[\"longitude\",east,\n"
+ " ORDER[1],\n"
+ " ANGLEUNIT[\"degree\",0.0174532925199433]],\n"
+ " AXIS[\"latitude\",north,\n"
+ " ORDER[2],\n"
+ " ANGLEUNIT[\"degree\",0.0174532925199433]]]";
+ EXPECT_EQ(f->toString(), expected);
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(io, projparse_longlat_pm_paris) {
+ auto obj =
+ PROJStringParser().createFromPROJString("+proj=longlat +pm=paris");
+ auto crs = nn_dynamic_pointer_cast<GeographicCRS>(obj);
+ ASSERT_TRUE(crs != nullptr);
+ WKTFormatterNNPtr f(WKTFormatter::create());
+ f->simulCurNodeHasId();
+ crs->exportToWKT(f.get());
+ auto expected = "GEODCRS[\"unknown\",\n"
+ " DATUM[\"Unknown based on WGS84 ellipsoid\",\n"
+ " ELLIPSOID[\"WGS 84\",6378137,298.257223563,\n"
+ " LENGTHUNIT[\"metre\",1]]],\n"
+ " PRIMEM[\"Paris\",2.5969213,\n"
+ " ANGLEUNIT[\"grad\",0.015707963267949]],\n"
+ " CS[ellipsoidal,2],\n"
+ " AXIS[\"longitude\",east,\n"
+ " ORDER[1],\n"
+ " ANGLEUNIT[\"degree\",0.0174532925199433]],\n"
+ " AXIS[\"latitude\",north,\n"
+ " ORDER[2],\n"
+ " ANGLEUNIT[\"degree\",0.0174532925199433]]]";
+
+ EXPECT_EQ(f->toString(), expected);
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(io, projparse_longlat_pm_ferro) {
+ auto obj = PROJStringParser().createFromPROJString(
+ "+proj=longlat +ellps=bessel +pm=ferro");
+ auto crs = nn_dynamic_pointer_cast<GeographicCRS>(obj);
+ ASSERT_TRUE(crs != nullptr);
+ WKTFormatterNNPtr f(WKTFormatter::create());
+ f->simulCurNodeHasId();
+ crs->exportToWKT(f.get());
+ auto expected =
+ "GEODCRS[\"unknown\",\n"
+ " DATUM[\"Unknown based on Bessel 1841 ellipsoid\",\n"
+ " ELLIPSOID[\"Bessel 1841\",6377397.155,299.1528128,\n"
+ " LENGTHUNIT[\"metre\",1]]],\n"
+ " PRIMEM[\"Ferro\",-17.6666666666667,\n"
+ " ANGLEUNIT[\"degree\",0.0174532925199433]],\n"
+ " CS[ellipsoidal,2],\n"
+ " AXIS[\"longitude\",east,\n"
+ " ORDER[1],\n"
+ " ANGLEUNIT[\"degree\",0.0174532925199433]],\n"
+ " AXIS[\"latitude\",north,\n"
+ " ORDER[2],\n"
+ " ANGLEUNIT[\"degree\",0.0174532925199433]]]";
+
+ EXPECT_EQ(f->toString(), expected);
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(io, projparse_longlat_pm_numeric) {
+ auto obj = PROJStringParser().createFromPROJString("+proj=longlat +pm=2.5");
+ auto crs = nn_dynamic_pointer_cast<GeographicCRS>(obj);
+ ASSERT_TRUE(crs != nullptr);
+ WKTFormatterNNPtr f(WKTFormatter::create());
+ f->simulCurNodeHasId();
+ crs->exportToWKT(f.get());
+ auto expected = "GEODCRS[\"unknown\",\n"
+ " DATUM[\"Unknown based on WGS84 ellipsoid\",\n"
+ " ELLIPSOID[\"WGS 84\",6378137,298.257223563,\n"
+ " LENGTHUNIT[\"metre\",1]]],\n"
+ " PRIMEM[\"unknown\",2.5,\n"
+ " ANGLEUNIT[\"degree\",0.0174532925199433]],\n"
+ " CS[ellipsoidal,2],\n"
+ " AXIS[\"longitude\",east,\n"
+ " ORDER[1],\n"
+ " ANGLEUNIT[\"degree\",0.0174532925199433]],\n"
+ " AXIS[\"latitude\",north,\n"
+ " ORDER[2],\n"
+ " ANGLEUNIT[\"degree\",0.0174532925199433]]]";
+
+ EXPECT_EQ(f->toString(), expected);
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(io, projparse_longlat_complex) {
+ std::string input =
+ "+proj=pipeline +step +proj=longlat +ellps=clrk80ign "
+ "+pm=paris +step +proj=unitconvert +xy_in=rad +xy_out=grad +step "
+ "+proj=axisswap +order=2,1";
+ auto obj = PROJStringParser().createFromPROJString(input);
+ auto crs = nn_dynamic_pointer_cast<GeographicCRS>(obj);
+ ASSERT_TRUE(crs != nullptr);
+ EXPECT_EQ(
+ crs->exportToPROJString(
+ PROJStringFormatter::create(PROJStringFormatter::Convention::PROJ_5)
+ .get()),
+ input);
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(io, projparse_longlat_towgs84_3_terms) {
+ auto obj = PROJStringParser().createFromPROJString(
+ "+proj=longlat +ellps=GRS80 +towgs84=1.2,2,3");
+ auto crs = nn_dynamic_pointer_cast<BoundCRS>(obj);
+ ASSERT_TRUE(crs != nullptr);
+ WKTFormatterNNPtr f(WKTFormatter::create());
+ f->simulCurNodeHasId();
+ crs->exportToWKT(f.get());
+
+ auto wkt = f->toString();
+ EXPECT_TRUE(wkt.find("METHOD[\"Geocentric translations") !=
+ std::string::npos)
+ << wkt;
+ EXPECT_TRUE(wkt.find("PARAMETER[\"X-axis translation\",1.2") !=
+ std::string::npos)
+ << wkt;
+ EXPECT_TRUE(wkt.find("PARAMETER[\"Y-axis translation\",2") !=
+ std::string::npos)
+ << wkt;
+ EXPECT_TRUE(wkt.find("PARAMETER[\"Z-axis translation\",3") !=
+ std::string::npos)
+ << wkt;
+
+ EXPECT_EQ(
+ crs->exportToPROJString(
+ PROJStringFormatter::create(PROJStringFormatter::Convention::PROJ_4)
+ .get()),
+ "+proj=longlat +ellps=GRS80 +towgs84=1.2,2,3,0,0,0,0");
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(io, projparse_longlat_towgs84_7_terms) {
+ auto obj = PROJStringParser().createFromPROJString(
+ "+proj=longlat +ellps=GRS80 +towgs84=1.2,2,3,4,5,6,7");
+ auto crs = nn_dynamic_pointer_cast<BoundCRS>(obj);
+ ASSERT_TRUE(crs != nullptr);
+ WKTFormatterNNPtr f(WKTFormatter::create());
+ f->simulCurNodeHasId();
+ crs->exportToWKT(f.get());
+
+ auto wkt = f->toString();
+ EXPECT_TRUE(wkt.find("METHOD[\"Position Vector transformation") !=
+ std::string::npos)
+ << wkt;
+ EXPECT_TRUE(wkt.find("PARAMETER[\"X-axis translation\",1.2") !=
+ std::string::npos)
+ << wkt;
+ EXPECT_TRUE(wkt.find("PARAMETER[\"Y-axis translation\",2") !=
+ std::string::npos)
+ << wkt;
+ EXPECT_TRUE(wkt.find("PARAMETER[\"Z-axis translation\",3") !=
+ std::string::npos)
+ << wkt;
+ EXPECT_TRUE(wkt.find("PARAMETER[\"Scale difference\",1.000007") !=
+ std::string::npos)
+ << wkt;
+
+ EXPECT_EQ(
+ crs->exportToPROJString(
+ PROJStringFormatter::create(PROJStringFormatter::Convention::PROJ_4)
+ .get()),
+ "+proj=longlat +ellps=GRS80 +towgs84=1.2,2,3,4,5,6,7");
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(io, projparse_longlat_nadgrids) {
+ auto obj = PROJStringParser().createFromPROJString(
+ "+proj=longlat +ellps=GRS80 +nadgrids=foo.gsb");
+ auto crs = nn_dynamic_pointer_cast<BoundCRS>(obj);
+ ASSERT_TRUE(crs != nullptr);
+ WKTFormatterNNPtr f(WKTFormatter::create());
+ f->simulCurNodeHasId();
+ crs->exportToWKT(f.get());
+
+ auto wkt = f->toString();
+ EXPECT_TRUE(wkt.find("METHOD[\"NTv2\"") != std::string::npos) << wkt;
+ EXPECT_TRUE(wkt.find("PARAMETERFILE[\"Latitude and longitude difference "
+ "file\",\"foo.gsb\"]") != std::string::npos)
+ << wkt;
+
+ EXPECT_EQ(
+ crs->exportToPROJString(
+ PROJStringFormatter::create(PROJStringFormatter::Convention::PROJ_4)
+ .get()),
+ "+proj=longlat +ellps=GRS80 +nadgrids=foo.gsb");
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(io, projparse_longlat_geoidgrids) {
+ auto obj = PROJStringParser().createFromPROJString(
+ "+proj=longlat +ellps=GRS80 +geoidgrids=foo.gtx");
+ auto crs = nn_dynamic_pointer_cast<CompoundCRS>(obj);
+ ASSERT_TRUE(crs != nullptr);
+ WKTFormatterNNPtr f(WKTFormatter::create());
+ f->simulCurNodeHasId();
+ crs->exportToWKT(f.get());
+
+ auto wkt = f->toString();
+ EXPECT_TRUE(
+ wkt.find(
+ "ABRIDGEDTRANSFORMATION[\"unknown to WGS84 ellipsoidal height\"") !=
+ std::string::npos)
+ << wkt;
+ EXPECT_TRUE(wkt.find("PARAMETERFILE[\"Geoid (height correction) model "
+ "file\",\"foo.gtx\"]") != std::string::npos)
+ << wkt;
+
+ EXPECT_EQ(
+ crs->exportToPROJString(
+ PROJStringFormatter::create(PROJStringFormatter::Convention::PROJ_4)
+ .get()),
+ "+proj=longlat +ellps=GRS80 +geoidgrids=foo.gtx +vunits=m");
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(io, projparse_longlat_geoidgrids_vunits) {
+ auto obj = PROJStringParser().createFromPROJString(
+ "+proj=longlat +ellps=GRS80 +geoidgrids=foo.gtx +vunits=ft");
+ auto crs = nn_dynamic_pointer_cast<CompoundCRS>(obj);
+ ASSERT_TRUE(crs != nullptr);
+ WKTFormatterNNPtr f(WKTFormatter::create());
+ f->simulCurNodeHasId();
+ f->setMultiLine(false);
+ crs->exportToWKT(f.get());
+
+ auto wkt = f->toString();
+ EXPECT_TRUE(wkt.find("AXIS[\"gravity-related height "
+ "(H)\",up,LENGTHUNIT[\"foot\",0.3048]") !=
+ std::string::npos)
+ << wkt;
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(io, projparse_longlat_vunits) {
+ auto obj = PROJStringParser().createFromPROJString(
+ "+proj=longlat +ellps=GRS80 +vunits=ft");
+ auto crs = nn_dynamic_pointer_cast<GeographicCRS>(obj);
+ ASSERT_TRUE(crs != nullptr);
+ WKTFormatterNNPtr f(WKTFormatter::create());
+ f->simulCurNodeHasId();
+ f->setMultiLine(false);
+ crs->exportToWKT(f.get());
+
+ auto wkt = f->toString();
+ EXPECT_TRUE(wkt.find("AXIS[\"ellipsoidal height "
+ "(h)\",up,ORDER[3],LENGTHUNIT[\"foot\",0.3048]") !=
+ std::string::npos)
+ << wkt;
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(io, projparse_vunits) {
+ auto obj = PROJStringParser().createFromPROJString("+vunits=ft");
+ auto crs = nn_dynamic_pointer_cast<VerticalCRS>(obj);
+ ASSERT_TRUE(crs != nullptr);
+ EXPECT_EQ(crs->exportToPROJString(PROJStringFormatter::create().get()),
+ "+vunits=ft");
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(io, projparse_vto_meter) {
+ auto obj = PROJStringParser().createFromPROJString("+vto_meter=2");
+ auto crs = nn_dynamic_pointer_cast<VerticalCRS>(obj);
+ ASSERT_TRUE(crs != nullptr);
+ EXPECT_EQ(crs->exportToPROJString(PROJStringFormatter::create().get()),
+ "+vto_meter=2");
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(io, projparse_longlat_axis_enu) {
+ auto obj = PROJStringParser().createFromPROJString(
+ "+proj=longlat +ellps=GRS80 +axis=enu");
+ auto crs = nn_dynamic_pointer_cast<GeographicCRS>(obj);
+ ASSERT_TRUE(crs != nullptr);
+ WKTFormatterNNPtr f(WKTFormatter::create());
+ f->simulCurNodeHasId();
+ f->setMultiLine(false);
+ crs->exportToWKT(f.get());
+
+ auto wkt = f->toString();
+ EXPECT_TRUE(wkt.find("AXIS[\"longitude\",east,ORDER[1]") !=
+ std::string::npos)
+ << wkt;
+ EXPECT_TRUE(wkt.find("AXIS[\"latitude\",north,ORDER[2]") !=
+ std::string::npos)
+ << wkt;
+
+ EXPECT_EQ(crs->exportToPROJString(PROJStringFormatter::create().get()),
+ "+proj=pipeline +step +proj=longlat +ellps=GRS80 +step "
+ "+proj=unitconvert +xy_in=rad +xy_out=deg");
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(io, projparse_longlat_axis_neu) {
+ auto obj = PROJStringParser().createFromPROJString(
+ "+proj=longlat +ellps=GRS80 +axis=neu");
+ auto crs = nn_dynamic_pointer_cast<GeographicCRS>(obj);
+ ASSERT_TRUE(crs != nullptr);
+ WKTFormatterNNPtr f(WKTFormatter::create());
+ f->simulCurNodeHasId();
+ f->setMultiLine(false);
+ crs->exportToWKT(f.get());
+
+ auto wkt = f->toString();
+ EXPECT_TRUE(wkt.find("AXIS[\"latitude\",north,ORDER[1]") !=
+ std::string::npos)
+ << wkt;
+ EXPECT_TRUE(wkt.find("AXIS[\"longitude\",east,ORDER[2]") !=
+ std::string::npos)
+ << wkt;
+
+ EXPECT_EQ(crs->exportToPROJString(PROJStringFormatter::create().get()),
+ "+proj=pipeline +step +proj=longlat +ellps=GRS80 +step "
+ "+proj=unitconvert +xy_in=rad +xy_out=deg +step +proj=axisswap "
+ "+order=2,1");
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(io, projparse_longlat_axis_swu) {
+ auto obj = PROJStringParser().createFromPROJString(
+ "+proj=longlat +ellps=GRS80 +axis=swu");
+ auto crs = nn_dynamic_pointer_cast<GeographicCRS>(obj);
+ ASSERT_TRUE(crs != nullptr);
+ WKTFormatterNNPtr f(WKTFormatter::create());
+ f->simulCurNodeHasId();
+ f->setMultiLine(false);
+ crs->exportToWKT(f.get());
+
+ auto wkt = f->toString();
+ EXPECT_TRUE(wkt.find("AXIS[\"latitude\",south,ORDER[1]") !=
+ std::string::npos)
+ << wkt;
+ EXPECT_TRUE(wkt.find("AXIS[\"longitude\",west,ORDER[2]") !=
+ std::string::npos)
+ << wkt;
+
+ EXPECT_EQ(crs->exportToPROJString(PROJStringFormatter::create().get()),
+ "+proj=pipeline +step +proj=longlat +ellps=GRS80 +step "
+ "+proj=unitconvert +xy_in=rad +xy_out=deg +step +proj=axisswap "
+ "+order=-2,-1");
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(io, projparse_longlat_unitconvert_deg) {
+ auto obj = PROJStringParser().createFromPROJString(
+ "+proj=pipeline +step +proj=longlat +ellps=GRS80 +step "
+ "+proj=unitconvert +xy_in=rad +xy_out=deg");
+ auto crs = nn_dynamic_pointer_cast<GeographicCRS>(obj);
+ ASSERT_TRUE(crs != nullptr);
+
+ EXPECT_EQ(crs->exportToPROJString(PROJStringFormatter::create().get()),
+ "+proj=pipeline +step +proj=longlat +ellps=GRS80 +step "
+ "+proj=unitconvert +xy_in=rad +xy_out=deg");
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(io, projparse_longlat_unitconvert_grad) {
+ auto obj = PROJStringParser().createFromPROJString(
+ "+proj=pipeline +step +proj=longlat +ellps=GRS80 +step "
+ "+proj=unitconvert +xy_in=rad +xy_out=grad");
+ auto crs = nn_dynamic_pointer_cast<GeographicCRS>(obj);
+ ASSERT_TRUE(crs != nullptr);
+
+ EXPECT_EQ(crs->exportToPROJString(PROJStringFormatter::create().get()),
+ "+proj=pipeline +step +proj=longlat +ellps=GRS80 +step "
+ "+proj=unitconvert +xy_in=rad +xy_out=grad");
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(io, projparse_longlat_unitconvert_rad) {
+ auto obj = PROJStringParser().createFromPROJString(
+ "+proj=pipeline +step +proj=longlat +ellps=GRS80 +step "
+ "+proj=unitconvert +xy_in=rad +xy_out=rad");
+ auto crs = nn_dynamic_pointer_cast<GeographicCRS>(obj);
+ ASSERT_TRUE(crs != nullptr);
+
+ EXPECT_EQ(crs->exportToPROJString(PROJStringFormatter::create().get()),
+ "+proj=pipeline +step +proj=longlat +ellps=GRS80 +step "
+ "+proj=unitconvert +xy_in=rad +xy_out=rad");
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(io, projparse_longlat_axisswap) {
+ for (auto order1 : {"1", "-1", "2", "-2"}) {
+ for (auto order2 : {"1", "-1", "2", "-2"}) {
+ if (std::abs(atoi(order1) * atoi(order2)) == 2 &&
+ !(atoi(order1) == 1 && atoi(order2) == 2)) {
+ auto str = "+proj=pipeline +step +proj=longlat +ellps=GRS80 "
+ "+step +proj=axisswap +order=" +
+ std::string(order1) + "," + order2;
+ auto obj = PROJStringParser().createFromPROJString(str);
+ auto crs = nn_dynamic_pointer_cast<GeographicCRS>(obj);
+ ASSERT_TRUE(crs != nullptr);
+
+ EXPECT_EQ(
+ crs->exportToPROJString(
+ PROJStringFormatter::create().get()),
+ "+proj=pipeline +step +proj=longlat +ellps=GRS80 +step "
+ "+proj=unitconvert +xy_in=rad +xy_out=deg +step "
+ "+proj=axisswap +order=" +
+ std::string(order1) + "," + order2);
+ }
+ }
+ }
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(io, projparse_tmerc) {
+ auto obj = PROJStringParser().createFromPROJString(
+ "+proj=tmerc +x_0=1 +lat_0=1 +k_0=2");
+ auto crs = nn_dynamic_pointer_cast<ProjectedCRS>(obj);
+ ASSERT_TRUE(crs != nullptr);
+ WKTFormatterNNPtr f(WKTFormatter::create());
+ f->simulCurNodeHasId();
+ crs->exportToWKT(f.get());
+ auto expected = "PROJCRS[\"unknown\",\n"
+ " BASEGEODCRS[\"unknown\",\n"
+ " DATUM[\"World Geodetic System 1984\",\n"
+ " ELLIPSOID[\"WGS 84\",6378137,298.257223563,\n"
+ " LENGTHUNIT[\"metre\",1]]],\n"
+ " PRIMEM[\"Greenwich\",0,\n"
+ " ANGLEUNIT[\"degree\",0.0174532925199433]]],\n"
+ " CONVERSION[\"unknown\",\n"
+ " METHOD[\"Transverse Mercator\",\n"
+ " ID[\"EPSG\",9807]],\n"
+ " PARAMETER[\"Latitude of natural origin\",1,\n"
+ " ANGLEUNIT[\"degree\",0.0174532925199433],\n"
+ " ID[\"EPSG\",8801]],\n"
+ " PARAMETER[\"Longitude of natural origin\",0,\n"
+ " ANGLEUNIT[\"degree\",0.0174532925199433],\n"
+ " ID[\"EPSG\",8802]],\n"
+ " PARAMETER[\"Scale factor at natural origin\",2,\n"
+ " SCALEUNIT[\"unity\",1],\n"
+ " ID[\"EPSG\",8805]],\n"
+ " PARAMETER[\"False easting\",1,\n"
+ " LENGTHUNIT[\"metre\",1],\n"
+ " ID[\"EPSG\",8806]],\n"
+ " PARAMETER[\"False northing\",0,\n"
+ " LENGTHUNIT[\"metre\",1],\n"
+ " ID[\"EPSG\",8807]]],\n"
+ " CS[Cartesian,2],\n"
+ " AXIS[\"(E)\",east,\n"
+ " ORDER[1],\n"
+ " LENGTHUNIT[\"metre\",1]],\n"
+ " AXIS[\"(N)\",north,\n"
+ " ORDER[2],\n"
+ " LENGTHUNIT[\"metre\",1]]]";
+
+ EXPECT_EQ(f->toString(), expected);
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(io, projparse_tmerc_south_oriented) {
+ auto obj = PROJStringParser().createFromPROJString(
+ "+proj=tmerc +axis=wsu +x_0=1 +lat_0=1 +k_0=2");
+ auto crs = nn_dynamic_pointer_cast<ProjectedCRS>(obj);
+ ASSERT_TRUE(crs != nullptr);
+ WKTFormatterNNPtr f(WKTFormatter::create());
+ f->simulCurNodeHasId();
+ crs->exportToWKT(f.get());
+ auto expected =
+ "PROJCRS[\"unknown\",\n"
+ " BASEGEODCRS[\"unknown\",\n"
+ " DATUM[\"World Geodetic System 1984\",\n"
+ " ELLIPSOID[\"WGS 84\",6378137,298.257223563,\n"
+ " LENGTHUNIT[\"metre\",1]]],\n"
+ " PRIMEM[\"Greenwich\",0,\n"
+ " ANGLEUNIT[\"degree\",0.0174532925199433]]],\n"
+ " CONVERSION[\"unknown\",\n"
+ " METHOD[\"Transverse Mercator (South Orientated)\",\n"
+ " ID[\"EPSG\",9808]],\n"
+ " PARAMETER[\"Latitude of natural origin\",1,\n"
+ " ANGLEUNIT[\"degree\",0.0174532925199433],\n"
+ " ID[\"EPSG\",8801]],\n"
+ " PARAMETER[\"Longitude of natural origin\",0,\n"
+ " ANGLEUNIT[\"degree\",0.0174532925199433],\n"
+ " ID[\"EPSG\",8802]],\n"
+ " PARAMETER[\"Scale factor at natural origin\",2,\n"
+ " SCALEUNIT[\"unity\",1],\n"
+ " ID[\"EPSG\",8805]],\n"
+ " PARAMETER[\"False easting\",1,\n"
+ " LENGTHUNIT[\"metre\",1],\n"
+ " ID[\"EPSG\",8806]],\n"
+ " PARAMETER[\"False northing\",0,\n"
+ " LENGTHUNIT[\"metre\",1],\n"
+ " ID[\"EPSG\",8807]]],\n"
+ " CS[Cartesian,2],\n"
+ " AXIS[\"westing\",west,\n"
+ " ORDER[1],\n"
+ " LENGTHUNIT[\"metre\",1]],\n"
+ " AXIS[\"southing\",south,\n"
+ " ORDER[2],\n"
+ " LENGTHUNIT[\"metre\",1]]]";
+
+ EXPECT_EQ(f->toString(), expected);
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(io, projparse_lcc_as_lcc1sp) {
+ auto obj = PROJStringParser().createFromPROJString(
+ "+proj=lcc +lat_0=45 +lat_1=45");
+ auto crs = nn_dynamic_pointer_cast<ProjectedCRS>(obj);
+ ASSERT_TRUE(crs != nullptr);
+ WKTFormatterNNPtr f(WKTFormatter::create());
+ f->simulCurNodeHasId();
+ f->setMultiLine(false);
+ crs->exportToWKT(f.get());
+ auto wkt = f->toString();
+ EXPECT_TRUE(wkt.find("Lambert Conic Conformal (1SP)") != std::string::npos)
+ << wkt;
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(io, projparse_lcc_as_lcc2sp) {
+ auto obj = PROJStringParser().createFromPROJString(
+ "+proj=lcc +lat_0=45 +lat_1=46");
+ auto crs = nn_dynamic_pointer_cast<ProjectedCRS>(obj);
+ ASSERT_TRUE(crs != nullptr);
+ WKTFormatterNNPtr f(WKTFormatter::create());
+ f->simulCurNodeHasId();
+ f->setMultiLine(false);
+ crs->exportToWKT(f.get());
+ auto wkt = f->toString();
+ EXPECT_TRUE(wkt.find("Lambert Conic Conformal (2SP)") != std::string::npos)
+ << wkt;
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(io, projparse_lcc_as_lcc2sp_michigan) {
+ auto obj = PROJStringParser().createFromPROJString(
+ "+proj=lcc +lat_0=45 +lat_1=46 +k_0=1.02");
+ auto crs = nn_dynamic_pointer_cast<ProjectedCRS>(obj);
+ ASSERT_TRUE(crs != nullptr);
+ WKTFormatterNNPtr f(WKTFormatter::create());
+ f->simulCurNodeHasId();
+ f->setMultiLine(false);
+ crs->exportToWKT(f.get());
+ auto wkt = f->toString();
+ EXPECT_TRUE(wkt.find("Lambert Conic Conformal (2SP Michigan)") !=
+ std::string::npos)
+ << wkt;
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(io, projparse_aeqd_guam) {
+ auto obj = PROJStringParser().createFromPROJString("+proj=aeqd +guam");
+ auto crs = nn_dynamic_pointer_cast<ProjectedCRS>(obj);
+ ASSERT_TRUE(crs != nullptr);
+ WKTFormatterNNPtr f(WKTFormatter::create());
+ f->simulCurNodeHasId();
+ f->setMultiLine(false);
+ crs->exportToWKT(f.get());
+ auto wkt = f->toString();
+ EXPECT_TRUE(wkt.find("Guam Projection") != std::string::npos) << wkt;
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(io, projparse_cea_ellipsoidal) {
+ auto obj =
+ PROJStringParser().createFromPROJString("+proj=cea +ellps=GRS80");
+ auto crs = nn_dynamic_pointer_cast<ProjectedCRS>(obj);
+ ASSERT_TRUE(crs != nullptr);
+ WKTFormatterNNPtr f(WKTFormatter::create());
+ f->simulCurNodeHasId();
+ f->setMultiLine(false);
+ crs->exportToWKT(f.get());
+ auto wkt = f->toString();
+ EXPECT_TRUE(
+ wkt.find(
+ "METHOD[\"Lambert Cylindrical Equal Area\",ID[\"EPSG\",9835]]") !=
+ std::string::npos)
+ << wkt;
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(io, projparse_geos_sweep_x) {
+ auto obj = PROJStringParser().createFromPROJString("+proj=geos +sweep=x");
+ auto crs = nn_dynamic_pointer_cast<ProjectedCRS>(obj);
+ ASSERT_TRUE(crs != nullptr);
+ WKTFormatterNNPtr f(WKTFormatter::create());
+ f->simulCurNodeHasId();
+ f->setMultiLine(false);
+ crs->exportToWKT(f.get());
+ auto wkt = f->toString();
+ EXPECT_TRUE(wkt.find("Geostationary Satellite (Sweep X)") !=
+ std::string::npos)
+ << wkt;
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(io, projparse_geos_sweep_y) {
+ auto obj = PROJStringParser().createFromPROJString("+proj=geos");
+ auto crs = nn_dynamic_pointer_cast<ProjectedCRS>(obj);
+ ASSERT_TRUE(crs != nullptr);
+ WKTFormatterNNPtr f(WKTFormatter::create());
+ f->simulCurNodeHasId();
+ f->setMultiLine(false);
+ crs->exportToWKT(f.get());
+ auto wkt = f->toString();
+ EXPECT_TRUE(wkt.find("Geostationary Satellite (Sweep Y)") !=
+ std::string::npos)
+ << wkt;
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(io, projparse_omerc_nouoff) {
+ auto obj = PROJStringParser().createFromPROJString(
+ "+proj=omerc +no_uoff +alpha=2 +gamma=3");
+ auto crs = nn_dynamic_pointer_cast<ProjectedCRS>(obj);
+ ASSERT_TRUE(crs != nullptr);
+ WKTFormatterNNPtr f(WKTFormatter::create());
+ f->simulCurNodeHasId();
+ f->setMultiLine(false);
+ crs->exportToWKT(f.get());
+ auto wkt = f->toString();
+ EXPECT_TRUE(wkt.find("METHOD[\"Hotine Oblique Mercator (variant "
+ "A)\",ID[\"EPSG\",9812]]") != std::string::npos)
+ << wkt;
+ EXPECT_TRUE(wkt.find("PARAMETER[\"Azimuth of initial line\",2") !=
+ std::string::npos)
+ << wkt;
+ EXPECT_TRUE(wkt.find("PARAMETER[\"Angle from Rectified to Skew Grid\",3") !=
+ std::string::npos)
+ << wkt;
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(io, projparse_omerc_tpno) {
+ auto obj = PROJStringParser().createFromPROJString(
+ "+proj=omerc +lat_1=1 +lat_2=2 +lon_1=3 +lon_2=4");
+ auto crs = nn_dynamic_pointer_cast<ProjectedCRS>(obj);
+ ASSERT_TRUE(crs != nullptr);
+ WKTFormatterNNPtr f(WKTFormatter::create());
+ f->simulCurNodeHasId();
+ f->setMultiLine(false);
+ crs->exportToWKT(f.get());
+ auto wkt = f->toString();
+ EXPECT_TRUE(
+ wkt.find(
+ "METHOD[\"Hotine Oblique Mercator Two Point Natural Origin\"]") !=
+ std::string::npos)
+ << wkt;
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(io, projparse_omerc_variant_b) {
+ auto obj = PROJStringParser().createFromPROJString("+proj=omerc +alpha=2");
+ auto crs = nn_dynamic_pointer_cast<ProjectedCRS>(obj);
+ ASSERT_TRUE(crs != nullptr);
+ WKTFormatterNNPtr f(WKTFormatter::create());
+ f->simulCurNodeHasId();
+ f->setMultiLine(false);
+ crs->exportToWKT(f.get());
+ auto wkt = f->toString();
+ EXPECT_TRUE(wkt.find("METHOD[\"Hotine Oblique Mercator (variant "
+ "B)\",ID[\"EPSG\",9815]]") != std::string::npos)
+ << wkt;
+ EXPECT_TRUE(wkt.find("PARAMETER[\"Angle from Rectified to Skew Grid\",2") !=
+ std::string::npos)
+ << wkt;
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(io, projparse_somerc) {
+ auto obj = PROJStringParser().createFromPROJString(
+ "+proj=somerc +lat_0=1 +lon_0=2 +k_0=3 +x_0=4 +y_0=5");
+ auto crs = nn_dynamic_pointer_cast<ProjectedCRS>(obj);
+ ASSERT_TRUE(crs != nullptr);
+ WKTFormatterNNPtr f(WKTFormatter::create());
+ f->simulCurNodeHasId();
+ f->setMultiLine(false);
+ crs->exportToWKT(f.get());
+ auto wkt = f->toString();
+ EXPECT_TRUE(wkt.find("METHOD[\"Hotine Oblique Mercator (variant "
+ "B)\",ID[\"EPSG\",9815]]") != std::string::npos)
+ << wkt;
+ EXPECT_TRUE(wkt.find("\"Latitude of projection centre\",1") !=
+ std::string::npos)
+ << wkt;
+ EXPECT_TRUE(wkt.find("\"Longitude of projection centre\",2") !=
+ std::string::npos)
+ << wkt;
+ EXPECT_TRUE(wkt.find("\"Scale factor on initial line\",3") !=
+ std::string::npos)
+ << wkt;
+ EXPECT_TRUE(wkt.find("\"Azimuth of initial line\",90") != std::string::npos)
+ << wkt;
+ EXPECT_TRUE(wkt.find("\"Angle from Rectified to Skew Grid\",90") !=
+ std::string::npos)
+ << wkt;
+ EXPECT_TRUE(wkt.find("\"Easting at projection centre\",4") !=
+ std::string::npos)
+ << wkt;
+ EXPECT_TRUE(wkt.find("\"Northing at projection centre\",5") !=
+ std::string::npos)
+ << wkt;
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(io, projparse_krovak) {
+ auto obj = PROJStringParser().createFromPROJString("+proj=krovak");
+ auto crs = nn_dynamic_pointer_cast<ProjectedCRS>(obj);
+ ASSERT_TRUE(crs != nullptr);
+ WKTFormatterNNPtr f(WKTFormatter::create());
+ f->simulCurNodeHasId();
+ f->setMultiLine(false);
+ crs->exportToWKT(f.get());
+ auto wkt = f->toString();
+ EXPECT_TRUE(
+ wkt.find("METHOD[\"Krovak (North Orientated)\",ID[\"EPSG\",1041]]") !=
+ std::string::npos)
+ << wkt;
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(io, projparse_krovak_axis_swu) {
+ auto obj =
+ PROJStringParser().createFromPROJString("+proj=krovak +axis=swu");
+ auto crs = nn_dynamic_pointer_cast<ProjectedCRS>(obj);
+ ASSERT_TRUE(crs != nullptr);
+ WKTFormatterNNPtr f(WKTFormatter::create());
+ f->simulCurNodeHasId();
+ f->setMultiLine(false);
+ crs->exportToWKT(f.get());
+ auto wkt = f->toString();
+ EXPECT_TRUE(wkt.find("METHOD[\"Krovak\",ID[\"EPSG\",9819]]") !=
+ std::string::npos)
+ << wkt;
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(io, projparse_etmerc) {
+ auto obj = PROJStringParser().createFromPROJString("+proj=etmerc");
+ auto crs = nn_dynamic_pointer_cast<ProjectedCRS>(obj);
+ ASSERT_TRUE(crs != nullptr);
+ WKTFormatterNNPtr f(WKTFormatter::create());
+ f->simulCurNodeHasId();
+ f->setMultiLine(false);
+ crs->exportToWKT(f.get());
+ auto wkt = f->toString();
+ EXPECT_TRUE(wkt.find("METHOD[\"Transverse Mercator\",ID[\"EPSG\",9807]]") !=
+ std::string::npos)
+ << wkt;
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(io, projparse_merc_variant_B) {
+ auto obj = PROJStringParser().createFromPROJString("+proj=merc +lat_ts=1");
+ auto crs = nn_dynamic_pointer_cast<ProjectedCRS>(obj);
+ ASSERT_TRUE(crs != nullptr);
+ WKTFormatterNNPtr f(WKTFormatter::create());
+ f->simulCurNodeHasId();
+ f->setMultiLine(false);
+ crs->exportToWKT(f.get());
+ auto wkt = f->toString();
+ EXPECT_TRUE(
+ wkt.find("METHOD[\"Mercator (variant B)\",ID[\"EPSG\",9805]]") !=
+ std::string::npos)
+ << wkt;
+ EXPECT_TRUE(wkt.find("PARAMETER[\"Latitude of 1st standard parallel\",1") !=
+ std::string::npos)
+ << wkt;
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(io, projparse_merc_google_mercator) {
+ auto obj = PROJStringParser().createFromPROJString(
+ "+proj=merc +a=6378137 +b=6378137 +lat_ts=0 +lon_0=0 +x_0=0 +y_0=0 "
+ "+k=1 +units=m +nadgrids=@null");
+ auto crs = nn_dynamic_pointer_cast<ProjectedCRS>(obj);
+ ASSERT_TRUE(crs != nullptr);
+ WKTFormatterNNPtr f(WKTFormatter::create());
+ f->simulCurNodeHasId();
+ f->setMultiLine(false);
+ crs->exportToWKT(f.get());
+ auto wkt = f->toString();
+ EXPECT_TRUE(wkt.find("METHOD[\"Popular Visualisation Pseudo "
+ "Mercator\",ID[\"EPSG\",1024]") != std::string::npos)
+ << wkt;
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(io, projparse_merc_stere_polar_variant_B) {
+ auto obj = PROJStringParser().createFromPROJString(
+ "+proj=stere +lat_0=90 +lat_ts=70");
+ auto crs = nn_dynamic_pointer_cast<ProjectedCRS>(obj);
+ ASSERT_TRUE(crs != nullptr);
+ WKTFormatterNNPtr f(WKTFormatter::create());
+ f->simulCurNodeHasId();
+ f->setMultiLine(false);
+ crs->exportToWKT(f.get());
+ auto wkt = f->toString();
+ EXPECT_TRUE(
+ wkt.find(
+ "METHOD[\"Polar Stereographic (variant B)\",ID[\"EPSG\",9829]]") !=
+ std::string::npos)
+ << wkt;
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(io, projparse_merc_stere_polar_variant_A) {
+ auto obj = PROJStringParser().createFromPROJString(
+ "+proj=stere +lat_0=-90 +k=0.994");
+ auto crs = nn_dynamic_pointer_cast<ProjectedCRS>(obj);
+ ASSERT_TRUE(crs != nullptr);
+ WKTFormatterNNPtr f(WKTFormatter::create());
+ f->simulCurNodeHasId();
+ f->setMultiLine(false);
+ crs->exportToWKT(f.get());
+ auto wkt = f->toString();
+ EXPECT_TRUE(
+ wkt.find(
+ "METHOD[\"Polar Stereographic (variant A)\",ID[\"EPSG\",9810]]") !=
+ std::string::npos)
+ << wkt;
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(io, projparse_merc_stere) {
+ auto obj = PROJStringParser().createFromPROJString("+proj=stere +lat_0=30");
+ auto crs = nn_dynamic_pointer_cast<ProjectedCRS>(obj);
+ ASSERT_TRUE(crs != nullptr);
+ WKTFormatterNNPtr f(WKTFormatter::create());
+ f->simulCurNodeHasId();
+ f->setMultiLine(false);
+ crs->exportToWKT(f.get());
+ auto wkt = f->toString();
+ EXPECT_TRUE(wkt.find("METHOD[\"Stereographic\"]") != std::string::npos)
+ << wkt;
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(io, projparse_utm) {
+ auto obj = PROJStringParser().createFromPROJString("+proj=utm +zone=1");
+ auto crs = nn_dynamic_pointer_cast<ProjectedCRS>(obj);
+ ASSERT_TRUE(crs != nullptr);
+ WKTFormatterNNPtr f(WKTFormatter::create());
+ f->simulCurNodeHasId();
+ f->setMultiLine(false);
+ crs->exportToWKT(f.get());
+ auto wkt = f->toString();
+ EXPECT_TRUE(wkt.find("CONVERSION[\"UTM zone 1N\",METHOD[\"Transverse "
+ "Mercator\",ID[\"EPSG\",9807]]") != std::string::npos)
+ << wkt;
+ EXPECT_TRUE(wkt.find("\"Longitude of natural origin\",-177,") !=
+ std::string::npos)
+ << wkt;
+ EXPECT_TRUE(wkt.find("\"False northing\",0,") != std::string::npos) << wkt;
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(io, projparse_utm_south) {
+ auto obj =
+ PROJStringParser().createFromPROJString("+proj=utm +zone=1 +south");
+ auto crs = nn_dynamic_pointer_cast<ProjectedCRS>(obj);
+ ASSERT_TRUE(crs != nullptr);
+ WKTFormatterNNPtr f(WKTFormatter::create());
+ f->simulCurNodeHasId();
+ f->setMultiLine(false);
+ crs->exportToWKT(f.get());
+ auto wkt = f->toString();
+ EXPECT_TRUE(wkt.find("CONVERSION[\"UTM zone 1S\",METHOD[\"Transverse "
+ "Mercator\",ID[\"EPSG\",9807]]") != std::string::npos)
+ << wkt;
+ EXPECT_TRUE(wkt.find("\"Longitude of natural origin\",-177,") !=
+ std::string::npos)
+ << wkt;
+ EXPECT_TRUE(wkt.find("\"False northing\",10000000,") != std::string::npos)
+ << wkt;
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(io, projparse_non_earth_ellipsoid) {
+ std::string input("+proj=merc +lon_0=0 +k=1 +x_0=0 +y_0=0 +R=1");
+ auto obj = PROJStringParser().createFromPROJString(input);
+ auto crs = nn_dynamic_pointer_cast<ProjectedCRS>(obj);
+ ASSERT_TRUE(crs != nullptr);
+ EXPECT_EQ(
+ crs->exportToPROJString(
+ PROJStringFormatter::create(PROJStringFormatter::Convention::PROJ_4)
+ .get()),
+ input);
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(io, projparse_axisswap_unitconvert_longlat_proj) {
+ std::string input =
+ "+proj=pipeline +step +proj=axisswap +order=2,1 +step "
+ "+proj=unitconvert +xy_in=grad +xy_out=rad +step +inv +proj=longlat "
+ "+ellps=clrk80ign +pm=paris +step +proj=lcc +lat_1=49.5 "
+ "+lat_0=49.5 +lon_0=0 +k_0=0.999877341 +x_0=600000 +y_0=200000 "
+ "+ellps=clrk80ign +pm=paris";
+ auto obj = PROJStringParser().createFromPROJString(input);
+ auto crs = nn_dynamic_pointer_cast<ProjectedCRS>(obj);
+ ASSERT_TRUE(crs != nullptr);
+ EXPECT_EQ(
+ crs->exportToPROJString(
+ PROJStringFormatter::create(PROJStringFormatter::Convention::PROJ_5)
+ .get()),
+ input);
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(io, projparse_axisswap_unitconvert_proj_axisswap) {
+ std::string input =
+ "+proj=pipeline +step +proj=axisswap +order=2,1 +step "
+ "+proj=unitconvert +xy_in=deg +xy_out=rad +step +proj=igh "
+ "+lon_0=0 +x_0=0 +y_0=0 +ellps=GRS80 +step +proj=axisswap +order=2,1";
+ auto obj = PROJStringParser().createFromPROJString(input);
+ auto crs = nn_dynamic_pointer_cast<ProjectedCRS>(obj);
+ ASSERT_TRUE(crs != nullptr);
+ EXPECT_EQ(
+ crs->exportToPROJString(
+ PROJStringFormatter::create(PROJStringFormatter::Convention::PROJ_5)
+ .get()),
+ input);
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(io, projparse_axisswap_unitconvert_proj_unitconvert) {
+ std::string input =
+ "+proj=pipeline +step +proj=axisswap +order=2,1 +step "
+ "+proj=unitconvert +xy_in=deg +xy_out=rad +step +proj=igh "
+ "+lon_0=0 +x_0=0 +y_0=0 +ellps=GRS80 +step +proj=unitconvert +xy_in=m "
+ "+z_in=m +xy_out=ft +z_out=ft";
+ auto obj = PROJStringParser().createFromPROJString(input);
+ auto crs = nn_dynamic_pointer_cast<ProjectedCRS>(obj);
+ ASSERT_TRUE(crs != nullptr);
+ EXPECT_EQ(
+ crs->exportToPROJString(
+ PROJStringFormatter::create(PROJStringFormatter::Convention::PROJ_5)
+ .get()),
+ input);
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(io, projparse_axisswap_unitconvert_proj_unitconvert_numeric_axisswap) {
+ std::string input =
+ "+proj=pipeline +step +proj=axisswap +order=2,1 +step "
+ "+proj=unitconvert +xy_in=deg +xy_out=rad +step +proj=igh "
+ "+lon_0=0 +x_0=0 +y_0=0 +ellps=GRS80 +step +proj=unitconvert +xy_in=m "
+ "+z_in=m +xy_out=2.5 +z_out=2.5 +step +proj=axisswap +order=-2,-1";
+ auto obj = PROJStringParser().createFromPROJString(input);
+ auto crs = nn_dynamic_pointer_cast<ProjectedCRS>(obj);
+ ASSERT_TRUE(crs != nullptr);
+ EXPECT_EQ(
+ crs->exportToPROJString(
+ PROJStringFormatter::create(PROJStringFormatter::Convention::PROJ_5)
+ .get()),
+ input);
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(io, projparse_projected_units) {
+ auto obj = PROJStringParser().createFromPROJString(
+ "+proj=tmerc +x_0=0.304800609601219 +units=us-ft");
+ auto crs = nn_dynamic_pointer_cast<ProjectedCRS>(obj);
+ ASSERT_TRUE(crs != nullptr);
+ WKTFormatterNNPtr f(WKTFormatter::create());
+ f->simulCurNodeHasId();
+ f->setMultiLine(false);
+ crs->exportToWKT(f.get());
+ auto wkt = f->toString();
+ EXPECT_TRUE(wkt.find("PARAMETER[\"False easting\",1,LENGTHUNIT[\"US survey "
+ "foot\",0.304800609601219]") != std::string::npos)
+ << wkt;
+ EXPECT_TRUE(wkt.find("AXIS[\"(E)\",east,ORDER[1],LENGTHUNIT[\"US survey "
+ "foot\",0.304800609601219]") != std::string::npos)
+ << wkt;
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(io, projparse_projected_to_meter_known) {
+ auto obj = PROJStringParser().createFromPROJString(
+ "+proj=tmerc +to_meter=0.304800609601219");
+ auto crs = nn_dynamic_pointer_cast<ProjectedCRS>(obj);
+ ASSERT_TRUE(crs != nullptr);
+ WKTFormatterNNPtr f(WKTFormatter::create());
+ f->simulCurNodeHasId();
+ f->setMultiLine(false);
+ crs->exportToWKT(f.get());
+ auto wkt = f->toString();
+ EXPECT_TRUE(wkt.find("PARAMETER[\"False easting\",0,LENGTHUNIT[\"US survey "
+ "foot\",0.304800609601219]") != std::string::npos)
+ << wkt;
+ EXPECT_TRUE(wkt.find("AXIS[\"(E)\",east,ORDER[1],LENGTHUNIT[\"US survey "
+ "foot\",0.304800609601219]") != std::string::npos)
+ << wkt;
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(io, projparse_projected_to_meter_unknown) {
+ auto obj =
+ PROJStringParser().createFromPROJString("+proj=tmerc +to_meter=0.1234");
+ auto crs = nn_dynamic_pointer_cast<ProjectedCRS>(obj);
+ ASSERT_TRUE(crs != nullptr);
+ WKTFormatterNNPtr f(WKTFormatter::create());
+ f->simulCurNodeHasId();
+ f->setMultiLine(false);
+ crs->exportToWKT(f.get());
+ auto wkt = f->toString();
+ EXPECT_TRUE(
+ wkt.find(
+ "PARAMETER[\"False easting\",0,LENGTHUNIT[\"unknown\",0.1234]") !=
+ std::string::npos)
+ << wkt;
+ EXPECT_TRUE(
+ wkt.find("AXIS[\"(E)\",east,ORDER[1],LENGTHUNIT[\"unknown\",0.1234]") !=
+ std::string::npos)
+ << wkt;
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(io, projparse_projected_vunits) {
+ auto obj =
+ PROJStringParser().createFromPROJString("+proj=tmerc +vunits=ft");
+ auto crs = nn_dynamic_pointer_cast<CompoundCRS>(obj);
+ ASSERT_TRUE(crs != nullptr);
+ WKTFormatterNNPtr f(WKTFormatter::create());
+ f->simulCurNodeHasId();
+ f->setMultiLine(false);
+ crs->exportToWKT(f.get());
+ auto wkt = f->toString();
+ EXPECT_TRUE(wkt.find("CS[Cartesian,2]") != std::string::npos) << wkt;
+ EXPECT_TRUE(wkt.find("CS[vertical,1],AXIS[\"gravity-related height "
+ "(H)\",up,LENGTHUNIT[\"foot\",0.3048]") !=
+ std::string::npos)
+ << wkt;
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(io, projparse_projected_unknown) {
+ auto obj = PROJStringParser().createFromPROJString(
+ "+proj=mbt_s +unused_flag +lat_0=45 +lon_0=0 +k=1 +x_0=10 +y_0=0");
+ auto crs = nn_dynamic_pointer_cast<ProjectedCRS>(obj);
+ ASSERT_TRUE(crs != nullptr);
+
+ {
+ WKTFormatterNNPtr f(WKTFormatter::create());
+ f->simulCurNodeHasId();
+ f->setMultiLine(false);
+ crs->exportToWKT(f.get());
+ auto wkt = f->toString();
+ EXPECT_TRUE(
+ wkt.find("CONVERSION[\"unknown\",METHOD[\"PROJ mbt_s "
+ "unused_flag\"],PARAMETER[\"lat_0\",45,ANGLEUNIT["
+ "\"degree\",0.0174532925199433]],PARAMETER[\"lon_0\","
+ "0,ANGLEUNIT[\"degree\",0.0174532925199433]],"
+ "PARAMETER[\"k\",1,SCALEUNIT[\"unity\",1]],PARAMETER["
+ "\"x_0\",10,LENGTHUNIT[\"metre\",1]],PARAMETER[\"y_0\","
+ "0,LENGTHUNIT[\"metre\",1]]]") != std::string::npos)
+ << wkt;
+ }
+
+ std::string expected_wkt1 =
+ "PROJCS[\"unknown\",GEOGCS[\"unknown\",DATUM[\"WGS_1984\",SPHEROID["
+ "\"WGS "
+ "84\",6378137,298.257223563,AUTHORITY[\"EPSG\",\"7030\"]],AUTHORITY["
+ "\"EPSG\",\"6326\"]],PRIMEM[\"Greenwich\",0,AUTHORITY[\"EPSG\","
+ "\"8901\"]],UNIT[\"degree\",0.0174532925199433,AUTHORITY[\"EPSG\","
+ "\"9122\"]],AXIS[\"Longitude\",EAST],AXIS[\"Latitude\",NORTH]],"
+ "PROJECTION[\"custom_proj4\"],UNIT[\"metre\",1,AUTHORITY[\"EPSG\","
+ "\"9001\"]],AXIS[\"Easting\",EAST],AXIS[\"Northing\",NORTH],EXTENSION["
+ "\"PROJ4\",\"+proj=mbt_s +datum=WGS84 +unused_flag +lat_0=45 "
+ "+lon_0=0 +k=1 +x_0=10 +y_0=0 +wktext +no_defs\"]]";
+
+ {
+ WKTFormatterNNPtr f(
+ WKTFormatter::create(WKTFormatter::Convention::WKT1_GDAL));
+ f->simulCurNodeHasId();
+ f->setMultiLine(false);
+ crs->exportToWKT(f.get());
+ auto wkt = f->toString();
+ EXPECT_EQ(wkt, expected_wkt1);
+ }
+
+ EXPECT_EQ(
+ crs->exportToPROJString(
+ PROJStringFormatter::create(PROJStringFormatter::Convention::PROJ_4)
+ .get()),
+ "+proj=mbt_s +unused_flag +lat_0=45 +lon_0=0 +k=1 +x_0=10 "
+ "+y_0=0 +datum=WGS84");
+
+ EXPECT_EQ(
+ crs->exportToPROJString(
+ PROJStringFormatter::create(PROJStringFormatter::Convention::PROJ_5)
+ .get()),
+ "+proj=pipeline +step +proj=unitconvert +xy_in=deg +xy_out=rad "
+ "+step +proj=mbt_s +unused_flag +lat_0=45 +lon_0=0 +k=1 "
+ "+x_0=10 +y_0=0 +ellps=WGS84");
+
+ {
+ auto obj2 = WKTParser().createFromWKT(expected_wkt1);
+ auto crs2 = nn_dynamic_pointer_cast<ProjectedCRS>(obj2);
+ ASSERT_TRUE(crs2 != nullptr);
+
+ WKTFormatterNNPtr f(WKTFormatter::create());
+ f->simulCurNodeHasId();
+ f->setMultiLine(false);
+ crs2->exportToWKT(f.get());
+ auto wkt = f->toString();
+ EXPECT_TRUE(
+ wkt.find("CONVERSION[\"unknown\",METHOD[\"PROJ mbt_s "
+ "unused_flag\"],PARAMETER[\"lat_0\",45,ANGLEUNIT["
+ "\"degree\",0.0174532925199433]],PARAMETER[\"lon_0\","
+ "0,ANGLEUNIT[\"degree\",0.0174532925199433]],"
+ "PARAMETER[\"k\",1,SCALEUNIT[\"unity\",1]],PARAMETER["
+ "\"x_0\",10,LENGTHUNIT[\"metre\",1]],PARAMETER[\"y_0\","
+ "0,LENGTHUNIT[\"metre\",1]]]") != std::string::npos)
+ << wkt;
+ }
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(io, projparse_geocent) {
+ auto obj =
+ PROJStringParser().createFromPROJString("+proj=geocent +ellps=WGS84");
+ auto crs = nn_dynamic_pointer_cast<GeodeticCRS>(obj);
+ ASSERT_TRUE(crs != nullptr);
+ WKTFormatterNNPtr f(WKTFormatter::create());
+ f->simulCurNodeHasId();
+ f->setMultiLine(false);
+ crs->exportToWKT(f.get());
+ auto wkt = f->toString();
+ EXPECT_EQ(wkt, "GEODCRS[\"unknown\",DATUM[\"Unknown based on WGS84 "
+ "ellipsoid\",ELLIPSOID[\"WGS "
+ "84\",6378137,298.257223563,LENGTHUNIT[\"metre\",1]]],"
+ "PRIMEM[\"Greenwich\",0,ANGLEUNIT[\"degree\",0."
+ "0174532925199433]],CS[Cartesian,3],AXIS[\"(X)\","
+ "geocentricX,ORDER[1],LENGTHUNIT[\"metre\",1]],AXIS[\"(Y)\","
+ "geocentricY,ORDER[2],LENGTHUNIT[\"metre\",1]],AXIS[\"(Z)\","
+ "geocentricZ,ORDER[3],LENGTHUNIT[\"metre\",1]]]");
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(io, projparse_cart_unit) {
+ std::string input(
+ "+proj=pipeline +step +proj=cart +ellps=WGS84 +step "
+ "+proj=unitconvert +xy_in=m +z_in=m +xy_out=km +z_out=km");
+ auto obj = PROJStringParser().createFromPROJString(input);
+ auto crs = nn_dynamic_pointer_cast<GeodeticCRS>(obj);
+ ASSERT_TRUE(crs != nullptr);
+ EXPECT_EQ(
+ crs->exportToPROJString(
+ PROJStringFormatter::create(PROJStringFormatter::Convention::PROJ_5)
+ .get()),
+ input);
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(io, projparse_cart_unit_numeric) {
+ std::string input(
+ "+proj=pipeline +step +proj=cart +ellps=WGS84 +step "
+ "+proj=unitconvert +xy_in=m +z_in=m +xy_out=500 +z_out=500");
+ auto obj = PROJStringParser().createFromPROJString(input);
+ auto crs = nn_dynamic_pointer_cast<GeodeticCRS>(obj);
+ ASSERT_TRUE(crs != nullptr);
+ EXPECT_EQ(
+ crs->exportToPROJString(
+ PROJStringFormatter::create(PROJStringFormatter::Convention::PROJ_5)
+ .get()),
+ input);
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(io, projparse_ob_tran_longlat) {
+ std::string input(
+ "+proj=pipeline +step +proj=axisswap +order=2,1 +step "
+ "+proj=unitconvert +xy_in=deg +xy_out=rad +step +proj=ob_tran "
+ "+o_proj=longlat +o_lat_p=52 +o_lon_p=-30 +lon_0=-25 +ellps=WGS84 "
+ "+step +proj=unitconvert +xy_in=rad +xy_out=deg +step "
+ "+proj=axisswap +order=2,1");
+ auto obj = PROJStringParser().createFromPROJString(input);
+ auto crs = nn_dynamic_pointer_cast<DerivedGeographicCRS>(obj);
+ ASSERT_TRUE(crs != nullptr);
+ EXPECT_EQ(
+ crs->exportToPROJString(
+ PROJStringFormatter::create(PROJStringFormatter::Convention::PROJ_5)
+ .get()),
+ input);
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(io, projparse_helmert_translation) {
+ std::string projString("+proj=helmert +x=1 +y=2 +z=3");
+ auto obj = PROJStringParser().createFromPROJString(projString);
+ auto transf = nn_dynamic_pointer_cast<Transformation>(obj);
+ ASSERT_TRUE(transf != nullptr);
+ EXPECT_EQ(
+ transf->exportToPROJString(
+ PROJStringFormatter::create(PROJStringFormatter::Convention::PROJ_5)
+ .get()),
+ projString);
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(io, projparse_helmert_translation_inv) {
+ std::string projString(
+ "+proj=pipeline +step +inv +proj=helmert +x=1 +y=2 +z=3");
+ auto obj = PROJStringParser().createFromPROJString(projString);
+ auto transf = nn_dynamic_pointer_cast<Transformation>(obj);
+ ASSERT_TRUE(transf != nullptr);
+ EXPECT_EQ(
+ transf->exportToPROJString(
+ PROJStringFormatter::create(PROJStringFormatter::Convention::PROJ_5)
+ .get()),
+ "+proj=helmert +x=-1 +y=-2 +z=-3");
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(io, projparse_helmert_position_vector) {
+ std::string projString("+proj=helmert +x=1 +y=2 +z=3 +rx=4 +ry=5 +rz=6 "
+ "+s=7 +convention=position_vector");
+ auto obj = PROJStringParser().createFromPROJString(projString);
+ auto transf = nn_dynamic_pointer_cast<Transformation>(obj);
+ ASSERT_TRUE(transf != nullptr);
+ EXPECT_EQ(
+ transf->exportToPROJString(
+ PROJStringFormatter::create(PROJStringFormatter::Convention::PROJ_5)
+ .get()),
+ projString);
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(io, projparse_helmert_position_vector_inv) {
+ std::string projString("+proj=pipeline +step +inv +proj=helmert +x=1 +y=2 "
+ "+z=3 +rx=4 +ry=5 +rz=6 "
+ "+s=7 +convention=position_vector");
+ auto obj = PROJStringParser().createFromPROJString(projString);
+ auto transf = nn_dynamic_pointer_cast<CoordinateOperation>(obj);
+ ASSERT_TRUE(transf != nullptr);
+ EXPECT_EQ(
+ transf->exportToPROJString(
+ PROJStringFormatter::create(PROJStringFormatter::Convention::PROJ_5)
+ .get()),
+ projString);
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(io, projparse_helmert_time_dependent_position_vector) {
+ std::string projString("+proj=helmert +x=1 +y=2 +z=3 +rx=4 +ry=5 +rz=6 "
+ "+s=7 +dx=0.1 +dy=0.2 +dz=0.3 +drx=0.4 +dry=0.5 "
+ "+drz=0.6 +ds=0.7 +t_epoch=2018.5 "
+ "+convention=position_vector");
+ auto obj = PROJStringParser().createFromPROJString(projString);
+ auto transf = nn_dynamic_pointer_cast<Transformation>(obj);
+ ASSERT_TRUE(transf != nullptr);
+ EXPECT_EQ(
+ transf->exportToPROJString(
+ PROJStringFormatter::create(PROJStringFormatter::Convention::PROJ_5)
+ .get()),
+ projString);
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(io, projparse_helmert_coordinate_frame) {
+ std::string projString("+proj=helmert +x=1 +y=2 +z=3 +rx=4 +ry=5 +rz=6 "
+ "+s=7 +convention=coordinate_frame");
+ auto obj = PROJStringParser().createFromPROJString(projString);
+ auto transf = nn_dynamic_pointer_cast<Transformation>(obj);
+ ASSERT_TRUE(transf != nullptr);
+ EXPECT_EQ(
+ transf->exportToPROJString(
+ PROJStringFormatter::create(PROJStringFormatter::Convention::PROJ_5)
+ .get()),
+ projString);
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(io, projparse_helmert_time_dependent_coordinate_frame) {
+ std::string projString("+proj=helmert +x=1 +y=2 +z=3 +rx=4 +ry=5 +rz=6 "
+ "+s=7 +dx=0.1 +dy=0.2 +dz=0.3 +drx=0.4 +dry=0.5 "
+ "+drz=0.6 +ds=0.7 +t_epoch=2018.5 "
+ "+convention=coordinate_frame");
+ auto obj = PROJStringParser().createFromPROJString(projString);
+ auto transf = nn_dynamic_pointer_cast<Transformation>(obj);
+ ASSERT_TRUE(transf != nullptr);
+ EXPECT_EQ(
+ transf->exportToPROJString(
+ PROJStringFormatter::create(PROJStringFormatter::Convention::PROJ_5)
+ .get()),
+ projString);
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(io, projparse_helmert_complex_pipeline) {
+ std::string projString(
+ "+proj=pipeline +step +proj=cart "
+ "+ellps=WGS84 +step +proj=helmert +x=-1 +y=-2 +z=-3 +rx=-4 "
+ "+ry=-5 +rz=-6 +s=-7 +convention=position_vector +step +inv "
+ "+proj=cart +ellps=clrk80ign");
+ auto obj = PROJStringParser().createFromPROJString(projString);
+ auto transf = nn_dynamic_pointer_cast<Transformation>(obj);
+ ASSERT_TRUE(transf != nullptr);
+ EXPECT_EQ(
+ transf->exportToPROJString(
+ PROJStringFormatter::create(PROJStringFormatter::Convention::PROJ_5)
+ .get()),
+ "+proj=pipeline +step +proj=unitconvert +xy_in=deg +xy_out=rad "
+ "+step +proj=cart +ellps=WGS84 +step +proj=helmert +x=-1 +y=-2 "
+ "+z=-3 +rx=-4 +ry=-5 +rz=-6 +s=-7 +convention=position_vector "
+ "+step +inv +proj=cart +ellps=clrk80ign +step +proj=unitconvert "
+ "+xy_in=rad +xy_out=deg");
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(io, projparse_helmert_complex_pipeline_2) {
+ std::string projString(
+ "+proj=pipeline +step +proj=axisswap +order=2,1 +step "
+ "+proj=unitconvert +xy_in=deg +xy_out=rad +step +proj=cart "
+ "+ellps=WGS84 +step +proj=helmert +x=-1 +y=-2 +z=-3 +rx=-4 "
+ "+ry=-5 +rz=-6 +s=-7 +convention=position_vector +step +inv "
+ "+proj=cart +ellps=clrk80ign +step "
+ "+proj=longlat +ellps=clrk80ign +pm=paris +step "
+ "+proj=unitconvert +xy_in=rad +xy_out=grad +step +proj=axisswap "
+ "+order=2,1");
+ auto obj = PROJStringParser().createFromPROJString(projString);
+ auto transf = nn_dynamic_pointer_cast<Transformation>(obj);
+ ASSERT_TRUE(transf != nullptr);
+ EXPECT_EQ(
+ transf->exportToPROJString(
+ PROJStringFormatter::create(PROJStringFormatter::Convention::PROJ_5)
+ .get()),
+ projString);
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(io, projparse_helmert_errors) {
+ // Missing convention
+ EXPECT_THROW(PROJStringParser().createFromPROJString("+proj=helmert +rx=4"),
+ ParsingException);
+
+ EXPECT_THROW(PROJStringParser().createFromPROJString(
+ "+proj=helmert +convention=unhandled"),
+ ParsingException);
+
+ EXPECT_THROW(PROJStringParser().createFromPROJString(
+ "+proj=helmert +unhandled_keyword"),
+ ParsingException);
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(io, projparse_molodensky) {
+ std::string projString("+proj=molodensky +ellps=WGS84 +dx=84.87 +dy=96.49 "
+ "+dz=116.95 +da=251 +df=1.41927e-05");
+ auto obj = PROJStringParser().createFromPROJString(projString);
+ auto transf = nn_dynamic_pointer_cast<Transformation>(obj);
+ ASSERT_TRUE(transf != nullptr);
+
+ WKTFormatterNNPtr f(WKTFormatter::create());
+ f->simulCurNodeHasId();
+ f->setMultiLine(false);
+ transf->exportToWKT(f.get());
+ auto wkt = f->toString();
+ EXPECT_EQ(
+ wkt,
+ "COORDINATEOPERATION[\"unknown\",SOURCECRS[GEODCRS[\"unknown\",DATUM["
+ "\"Unknown based on WGS84 ellipsoid\",ELLIPSOID[\"WGS "
+ "84\",6378137,298.257223563,LENGTHUNIT[\"metre\",1]]],PRIMEM["
+ "\"Greenwich\",0,ANGLEUNIT[\"degree\",0.0174532925199433]],CS["
+ "ellipsoidal,2],AXIS[\"longitude\",east,ORDER[1],ANGLEUNIT[\"degree\","
+ "0.0174532925199433]],AXIS[\"latitude\",north,ORDER[2],ANGLEUNIT["
+ "\"degree\",0.0174532925199433]]]],TARGETCRS[GEODCRS[\"unknown\",DATUM["
+ "\"unknown\",ELLIPSOID[\"unknown\",6378388,297.000000198989,LENGTHUNIT["
+ "\"metre\",1]]],PRIMEM[\"Greenwich\",0,ANGLEUNIT[\"degree\",0."
+ "0174532925199433]],CS[ellipsoidal,2],AXIS[\"longitude\",east,ORDER[1],"
+ "ANGLEUNIT[\"degree\",0.0174532925199433]],AXIS[\"latitude\",north,"
+ "ORDER[2],ANGLEUNIT[\"degree\",0.0174532925199433]]]],METHOD["
+ "\"Molodensky\",ID[\"EPSG\",9604]],PARAMETER[\"X-axis "
+ "translation\",84.87,LENGTHUNIT[\"metre\",1],ID[\"EPSG\",8605]],"
+ "PARAMETER[\"Y-axis "
+ "translation\",96.49,LENGTHUNIT[\"metre\",1],ID[\"EPSG\",8606]],"
+ "PARAMETER[\"Z-axis "
+ "translation\",116.95,LENGTHUNIT[\"metre\",1],ID[\"EPSG\",8607]],"
+ "PARAMETER[\"Semi-major axis length "
+ "difference\",251,LENGTHUNIT[\"metre\",1],ID[\"EPSG\",8654]],PARAMETER["
+ "\"Flattening difference\",1.41927e-05,ID[\"EPSG\",8655]]]");
+
+ EXPECT_EQ(
+ transf->exportToPROJString(
+ PROJStringFormatter::create(PROJStringFormatter::Convention::PROJ_5)
+ .get()),
+ "+proj=pipeline +step +proj=unitconvert +xy_in=deg +xy_out=rad "
+ "+step +proj=molodensky +ellps=WGS84 +dx=84.87 +dy=96.49 "
+ "+dz=116.95 +da=251 +df=1.41927e-05 +step +proj=longlat "
+ "+a=6378388 +rf=297.000000198989 +step +proj=unitconvert "
+ "+xy_in=rad +xy_out=deg");
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(io, projparse_molodensky_inv) {
+ std::string projString("+proj=pipeline +step +inv +proj=molodensky "
+ "+ellps=WGS84 +dx=84.87 +dy=96.49 "
+ "+dz=116.95 +da=251 +df=1.41927e-05");
+ auto obj = PROJStringParser().createFromPROJString(projString);
+ auto transf = nn_dynamic_pointer_cast<CoordinateOperation>(obj);
+ ASSERT_TRUE(transf != nullptr);
+ EXPECT_EQ(
+ transf->exportToPROJString(
+ PROJStringFormatter::create(PROJStringFormatter::Convention::PROJ_5)
+ .get()),
+ "+proj=pipeline +step +proj=unitconvert +xy_in=deg +xy_out=rad "
+ "+step +inv +proj=longlat +a=6378388 +rf=297.000000198989 +step "
+ "+proj=molodensky +a=6378388 +rf=297.000000198989 +dx=-84.87 "
+ "+dy=-96.49 +dz=-116.95 +da=-251 +df=-1.41927e-05 +step "
+ "+proj=unitconvert +xy_in=rad +xy_out=deg");
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(io, projparse_molodensky_abridged) {
+ std::string projString("+proj=molodensky +ellps=WGS84 +dx=84.87 +dy=96.49 "
+ "+dz=116.95 +da=251 +df=1.41927e-05 +abridged");
+ auto obj = PROJStringParser().createFromPROJString(projString);
+ auto transf = nn_dynamic_pointer_cast<Transformation>(obj);
+ ASSERT_TRUE(transf != nullptr);
+ EXPECT_EQ(
+ transf->exportToPROJString(
+ PROJStringFormatter::create(PROJStringFormatter::Convention::PROJ_5)
+ .get()),
+ "+proj=pipeline +step +proj=unitconvert +xy_in=deg +xy_out=rad "
+ "+step +proj=molodensky +ellps=WGS84 +dx=84.87 +dy=96.49 "
+ "+dz=116.95 +da=251 +df=1.41927e-05 +abridged +step +proj=longlat "
+ "+a=6378388 +rf=297.000000198989 +step "
+ "+proj=unitconvert +xy_in=rad +xy_out=deg");
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(io, projparse_molodensky_complex_pipeline) {
+ std::string projString(
+ "+proj=pipeline +step +proj=unitconvert +xy_in=deg +xy_out=rad "
+ "+proj=longlat +ellps=WGS84 +step +proj=molodensky +ellps=WGS84 "
+ "+dx=84.87 +dy=96.49 "
+ "+dz=116.95 +da=251 +df=1.41927e-05 +step +proj=longlat "
+ "+ellps=GRS80 +step +proj=unitconvert "
+ "+xy_in=rad +xy_out=deg");
+ auto obj = PROJStringParser().createFromPROJString(projString);
+ auto transf = nn_dynamic_pointer_cast<Transformation>(obj);
+ ASSERT_TRUE(transf != nullptr);
+
+ WKTFormatterNNPtr f(WKTFormatter::create());
+ f->simulCurNodeHasId();
+ f->setMultiLine(false);
+ transf->exportToWKT(f.get());
+ auto wkt = f->toString();
+ EXPECT_TRUE(wkt.find("SOURCECRS[GEODCRS[\"unknown\",DATUM[\"Unknown based "
+ "on WGS84 ellipsoid\"") != std::string::npos)
+ << wkt;
+ EXPECT_TRUE(wkt.find("TARGETCRS[GEODCRS[\"unknown\",DATUM[\"Unknown based "
+ "on GRS80 ellipsoid\"") != std::string::npos)
+ << wkt;
+
+ EXPECT_EQ(
+ transf->exportToPROJString(
+ PROJStringFormatter::create(PROJStringFormatter::Convention::PROJ_5)
+ .get()),
+ "+proj=pipeline +step +proj=unitconvert +xy_in=deg +xy_out=rad "
+ "+step +proj=molodensky +ellps=WGS84 +dx=84.87 +dy=96.49 "
+ "+dz=116.95 +da=251 +df=1.41927e-05 +step +proj=unitconvert "
+ "+xy_in=rad +xy_out=deg");
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(io, projparse_longlat_title) {
+ std::string projString("+title=Ile d'Amsterdam 1963 +proj=longlat "
+ "+towgs84=109.7530,-528.1330,-362.2440 "
+ "+a=6378388.0000 +rf=297.0000000000000 +units=m "
+ "+no_defs");
+ auto obj = PROJStringParser().createFromPROJString(projString);
+ auto crs = nn_dynamic_pointer_cast<BoundCRS>(obj);
+ ASSERT_TRUE(crs != nullptr);
+ auto baseCRS = nn_dynamic_pointer_cast<GeographicCRS>(crs->baseCRS());
+ ASSERT_TRUE(baseCRS != nullptr);
+ EXPECT_EQ(baseCRS->nameStr(), "Ile d'Amsterdam 1963");
+ EXPECT_EQ(baseCRS->datum()->nameStr(), "Ile d'Amsterdam 1963");
+ EXPECT_EQ(
+ crs->exportToPROJString(
+ PROJStringFormatter::create(PROJStringFormatter::Convention::PROJ_4)
+ .get()),
+ "+proj=longlat +ellps=intl +towgs84=109.753,-528.133,-362.244,0,0,0,0");
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(io, projparse_projected_title) {
+ std::string projString(
+ "+title=Amsterdam 1963 +proj=tmerc "
+ "+towgs84=109.7530,-528.1330,-362.2440 +a=6378388.0000 "
+ "+rf=297.0000000000000 +lat_0=0.000000000 +lon_0=75.000000000 "
+ "+k_0=0.99960000 +x_0=500000.000 +y_0=10000000.000 +units=m +no_defs");
+ auto obj = PROJStringParser().createFromPROJString(projString);
+ auto crs = nn_dynamic_pointer_cast<BoundCRS>(obj);
+ ASSERT_TRUE(crs != nullptr);
+ auto baseCRS = nn_dynamic_pointer_cast<ProjectedCRS>(crs->baseCRS());
+ ASSERT_TRUE(baseCRS != nullptr);
+ EXPECT_EQ(baseCRS->nameStr(), "Amsterdam 1963");
+ EXPECT_EQ(baseCRS->baseCRS()->nameStr(), "unknown");
+ EXPECT_EQ(
+ crs->exportToPROJString(
+ PROJStringFormatter::create(PROJStringFormatter::Convention::PROJ_4)
+ .get()),
+ "+proj=utm +zone=43 +south +ellps=intl "
+ "+towgs84=109.753,-528.133,-362.244,0,0,0,0");
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(io, projparse_init) {
+
+ {
+ auto obj = PROJStringParser().createFromPROJString("init=epsg:4326");
+ auto co = nn_dynamic_pointer_cast<CoordinateOperation>(obj);
+ ASSERT_TRUE(co != nullptr);
+ EXPECT_EQ(co->exportToPROJString(PROJStringFormatter::create().get()),
+ "+init=epsg:4326");
+ }
+
+ {
+ auto obj = PROJStringParser().createFromPROJString(
+ "title=mytitle init=epsg:4326 ellps=WGS84");
+ auto co = nn_dynamic_pointer_cast<CoordinateOperation>(obj);
+ ASSERT_TRUE(co != nullptr);
+ EXPECT_EQ(co->nameStr(), "mytitle");
+ EXPECT_EQ(co->exportToPROJString(PROJStringFormatter::create().get()),
+ "+init=epsg:4326 +ellps=WGS84");
+ }
+
+ {
+ auto obj = PROJStringParser().createFromPROJString(
+ "proj=pipeline step init=epsg:4326 step proj=longlat");
+ auto co = nn_dynamic_pointer_cast<CoordinateOperation>(obj);
+ ASSERT_TRUE(co != nullptr);
+ EXPECT_EQ(co->exportToPROJString(PROJStringFormatter::create().get()),
+ "+proj=pipeline +step +init=epsg:4326 +step +proj=longlat");
+ }
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(io, projparse_errors) {
+ EXPECT_THROW(PROJStringParser().createFromPROJString(""), ParsingException);
+
+ EXPECT_THROW(PROJStringParser().createFromPROJString("foo"),
+ ParsingException);
+
+ EXPECT_THROW(PROJStringParser().createFromPROJString("inv"),
+ ParsingException);
+
+ EXPECT_THROW(PROJStringParser().createFromPROJString("step"),
+ ParsingException);
+
+ EXPECT_THROW(PROJStringParser().createFromPROJString("proj=unknown"),
+ ParsingException);
+
+ EXPECT_THROW(
+ PROJStringParser().createFromPROJString(
+ "proj=pipeline step proj=unitconvert step proj=longlat a=invalid"),
+ ParsingException);
+
+ EXPECT_THROW(PROJStringParser().createFromPROJString(
+ "proj=pipeline step proj=pipeline"),
+ ParsingException);
+
+ EXPECT_THROW(PROJStringParser().createFromPROJString(
+ "proj=pipeline step init=epsg:4326 init=epsg:4326"),
+ ParsingException);
+
+ EXPECT_THROW(PROJStringParser().createFromPROJString(
+ "proj=pipeline step init=epsg:4326 proj=longlat"),
+ ParsingException);
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(io, projparse_longlat_errors) {
+ EXPECT_THROW(
+ PROJStringParser().createFromPROJString("+proj=longlat +datum=unknown"),
+ ParsingException);
+
+ EXPECT_THROW(
+ PROJStringParser().createFromPROJString("+proj=longlat +ellps=unknown"),
+ ParsingException);
+
+ EXPECT_THROW(PROJStringParser().createFromPROJString(
+ "+proj=longlat +a=invalid +b=1"),
+ ParsingException);
+
+ EXPECT_THROW(PROJStringParser().createFromPROJString(
+ "+proj=longlat +a=1 +b=invalid"),
+ ParsingException);
+
+ EXPECT_THROW(PROJStringParser().createFromPROJString(
+ "+proj=longlat +a=invalid +rf=1"),
+ ParsingException);
+
+ EXPECT_THROW(PROJStringParser().createFromPROJString(
+ "+proj=longlat +a=1 +rf=invalid"),
+ ParsingException);
+
+ EXPECT_THROW(
+ PROJStringParser().createFromPROJString("+proj=longlat +R=invalid"),
+ ParsingException);
+
+ EXPECT_THROW(PROJStringParser().createFromPROJString("+proj=longlat +a=1"),
+ ParsingException);
+
+ EXPECT_THROW(PROJStringParser().createFromPROJString("+proj=longlat +b=1"),
+ ParsingException);
+
+ EXPECT_THROW(PROJStringParser().createFromPROJString("+proj=longlat +rf=1"),
+ ParsingException);
+
+ EXPECT_THROW(
+ PROJStringParser().createFromPROJString("+proj=longlat +pm=unknown"),
+ ParsingException);
+
+ EXPECT_THROW(
+ PROJStringParser().createFromPROJString(
+ "+proj=longlat +ellps=GRS80 +towgs84=1.2,2,3,4,5,6,invalid"),
+ ParsingException);
+
+ EXPECT_THROW(
+ PROJStringParser().createFromPROJString("+proj=longlat +axis=foo"),
+ ParsingException);
+
+ EXPECT_THROW(PROJStringParser().createFromPROJString(
+ "+proj=pipeline +step +proj=longlat +ellps=GRS80 +step "
+ "+proj=unitconvert +xy_in=rad +xy_out=foo"),
+ ParsingException);
+
+ EXPECT_THROW(PROJStringParser().createFromPROJString(
+ "+proj=pipeline +step +proj=longlat +ellps=GRS80 +step "
+ "+proj=axisswap"),
+ ParsingException);
+
+ EXPECT_THROW(PROJStringParser().createFromPROJString(
+ "+proj=pipeline +step +proj=longlat +ellps=GRS80 +step "
+ "+proj=axisswap +order=0,0"),
+ ParsingException);
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(io, projparse_projected_errors) {
+ EXPECT_THROW(
+ PROJStringParser().createFromPROJString("+proj=tmerc +units=foo"),
+ ParsingException);
+ EXPECT_THROW(
+ PROJStringParser().createFromPROJString("+proj=tmerc +x_0=foo"),
+ ParsingException);
+ EXPECT_THROW(
+ PROJStringParser().createFromPROJString("+proj=tmerc +lat_0=foo"),
+ ParsingException);
+ // Inconsitent pm values between geogCRS and projectedCRS
+ EXPECT_THROW(PROJStringParser().createFromPROJString(
+ "+proj=pipeline +step +proj=longlat +ellps=WGS84 "
+ "+proj=tmerc +ellps=WGS84 +lat_0=foo +pm=paris"),
+ ParsingException);
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(io, createFromUserInput) {
+ auto dbContext = DatabaseContext::create();
+ EXPECT_THROW(createFromUserInput("foo", nullptr), ParsingException);
+ EXPECT_THROW(createFromUserInput("GEOGCRS", nullptr), ParsingException);
+ EXPECT_THROW(createFromUserInput("+proj=unhandled", nullptr),
+ ParsingException);
+ EXPECT_THROW(createFromUserInput("EPSG:4326", nullptr), ParsingException);
+ EXPECT_THROW(
+ createFromUserInput("urn:ogc:def:unhandled:EPSG::4326", dbContext),
+ ParsingException);
+
+ EXPECT_NO_THROW(createFromUserInput("+proj=longlat", nullptr));
+ EXPECT_NO_THROW(createFromUserInput("EPSG:4326", dbContext));
+ EXPECT_NO_THROW(
+ createFromUserInput("urn:ogc:def:crs:EPSG::4326", dbContext));
+ EXPECT_NO_THROW(createFromUserInput(
+ "urn:ogc:def:coordinateOperation:EPSG::1671", dbContext));
+ EXPECT_NO_THROW(
+ createFromUserInput("urn:ogc:def:datum:EPSG::6326", dbContext));
+ EXPECT_NO_THROW(
+ createFromUserInput("urn:ogc:def:meridian:EPSG::8901", dbContext));
+ EXPECT_NO_THROW(
+ createFromUserInput("urn:ogc:def:ellipsoid:EPSG::7030", dbContext));
+ EXPECT_NO_THROW(createFromUserInput(
+ "GEOGCRS[\"WGS 84\",\n"
+ " DATUM[\"World Geodetic System 1984\",\n"
+ " ELLIPSOID[\"WGS 84\",6378137,298.257223563]],\n"
+ " CS[ellipsoidal,3],\n"
+ " AXIS[\"latitude\",north,\n"
+ " UNIT[\"degree\",0.0174532925199433]],\n"
+ " AXIS[\"longitude\",east,\n"
+ " UNIT[\"degree\",0.0174532925199433]],\n"
+ " AXIS[\"ellipsoidal height\",up,\n"
+ " UNIT[\"metre\",1]],\n"
+ " ID[\"EPSG\",4979]]",
+ nullptr));
+
+ // Search names in the database
+ EXPECT_THROW(createFromUserInput("foobar", dbContext), ParsingException);
+ EXPECT_NO_THROW(createFromUserInput("WGS 84", dbContext));
+ EXPECT_NO_THROW(createFromUserInput("WGS84", dbContext));
+ EXPECT_NO_THROW(createFromUserInput("UTM zone 31N", dbContext));
+ EXPECT_THROW(createFromUserInput("UTM zone 31", dbContext),
+ ParsingException);
+ EXPECT_NO_THROW(createFromUserInput("WGS84 UTM zone 31N", dbContext));
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(io, guessDialect) {
+ EXPECT_EQ(WKTParser().guessDialect("LOCAL_CS[\"foo\"]"),
+ WKTParser::WKTGuessedDialect::WKT1_GDAL);
+
+ EXPECT_EQ(WKTParser().guessDialect(
+ "GEOGCS[\"GCS_WGS_1984\",DATUM[\"D_WGS_1984\",SPHEROID[\"WGS_"
+ "1984\",6378137.0,298.257223563]],PRIMEM[\"Greenwich\",0.0],"
+ "UNIT[\"Degree\",0.0174532925199433]]"),
+ WKTParser::WKTGuessedDialect::WKT1_ESRI);
+
+ EXPECT_EQ(WKTParser().guessDialect(
+ "GEOGCRS[\"WGS 84\",\n"
+ " DATUM[\"World Geodetic System 1984\",\n"
+ " ELLIPSOID[\"WGS 84\",6378137,298.257223563]],\n"
+ " CS[ellipsoidal,2],\n"
+ " AXIS[\"geodetic latitude (Lat)\",north],\n"
+ " AXIS[\"geodetic longitude (Lon)\",east],\n"
+ " UNIT[\"degree\",0.0174532925199433]]"),
+ WKTParser::WKTGuessedDialect::WKT2_2018);
+
+ EXPECT_EQ(
+ WKTParser().guessDialect("TIMECRS[\"Temporal CRS\",\n"
+ " TDATUM[\"Gregorian calendar\",\n"
+ " CALENDAR[\"proleptic Gregorian\"],\n"
+ " TIMEORIGIN[0000-01-01]],\n"
+ " CS[TemporalDateTime,1],\n"
+ " AXIS[\"time (T)\",future]]"),
+ WKTParser::WKTGuessedDialect::WKT2_2018);
+
+ EXPECT_EQ(WKTParser().guessDialect(
+ "GEODCRS[\"WGS 84\",\n"
+ " DATUM[\"World Geodetic System 1984\",\n"
+ " ELLIPSOID[\"WGS 84\",6378137,298.257223563]],\n"
+ " CS[ellipsoidal,2],\n"
+ " AXIS[\"geodetic latitude (Lat)\",north],\n"
+ " AXIS[\"geodetic longitude (Lon)\",east],\n"
+ " UNIT[\"degree\",0.0174532925199433]]"),
+ WKTParser::WKTGuessedDialect::WKT2_2015);
+
+ EXPECT_EQ(WKTParser().guessDialect("foo"),
+ WKTParser::WKTGuessedDialect::NOT_WKT);
+}
diff --git a/test/unit/test_metadata.cpp b/test/unit/test_metadata.cpp
new file mode 100644
index 00000000..96b8cbff
--- /dev/null
+++ b/test/unit/test_metadata.cpp
@@ -0,0 +1,388 @@
+/******************************************************************************
+ *
+ * Project: PROJ
+ * Purpose: Test ISO19111:2018 implementation
+ * Author: Even Rouault <even dot rouault at spatialys dot com>
+ *
+ ******************************************************************************
+ * Copyright (c) 2018, Even Rouault <even dot rouault at spatialys dot 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.
+ ****************************************************************************/
+
+#include "gtest_include.h"
+
+#include "proj/io.hpp"
+#include "proj/metadata.hpp"
+#include "proj/util.hpp"
+
+using namespace osgeo::proj::io;
+using namespace osgeo::proj::metadata;
+using namespace osgeo::proj::util;
+
+// ---------------------------------------------------------------------------
+
+TEST(metadata, citation) {
+ Citation c("my citation");
+ Citation c2(c);
+ ASSERT_TRUE(c2.title().has_value());
+ ASSERT_EQ(*(c2.title()), "my citation");
+}
+
+// ---------------------------------------------------------------------------
+
+static bool equals(ExtentNNPtr extent1, ExtentNNPtr extent2) {
+ return extent1->contains(extent2) && extent2->contains(extent1);
+}
+
+static bool equals(GeographicExtentNNPtr extent1,
+ GeographicExtentNNPtr extent2) {
+ return extent1->contains(extent2) && extent2->contains(extent1);
+}
+
+static GeographicExtentNNPtr getBBox(ExtentNNPtr extent) {
+ assert(extent->geographicElements().size() == 1);
+ return extent->geographicElements()[0];
+}
+
+TEST(metadata, extent) {
+ Extent::create(
+ optional<std::string>(), std::vector<GeographicExtentNNPtr>(),
+ std::vector<VerticalExtentNNPtr>(), std::vector<TemporalExtentNNPtr>());
+
+ auto world = Extent::createFromBBOX(-180, -90, 180, 90);
+ EXPECT_TRUE(world->isEquivalentTo(world.get()));
+ EXPECT_TRUE(world->contains(world));
+
+ auto west_hemisphere = Extent::createFromBBOX(-180, -90, 0, 90);
+ EXPECT_TRUE(!world->isEquivalentTo(west_hemisphere.get()));
+ EXPECT_TRUE(world->contains(west_hemisphere));
+ EXPECT_TRUE(!west_hemisphere->contains(world));
+
+ auto world_inter_world = world->intersection(world);
+ ASSERT_TRUE(world_inter_world != nullptr);
+ EXPECT_TRUE(equals(NN_CHECK_ASSERT(world_inter_world), world));
+
+ auto france = Extent::createFromBBOX(-5, 40, 12, 51);
+ EXPECT_TRUE(france->contains(france));
+ EXPECT_TRUE(world->contains(france));
+ EXPECT_TRUE(!france->contains(
+ world)); // We are only speaking about geography here ;-)
+ EXPECT_TRUE(world->intersects(france));
+ EXPECT_TRUE(france->intersects(world));
+
+ auto france_inter_france = france->intersection(france);
+ ASSERT_TRUE(france_inter_france != nullptr);
+ EXPECT_TRUE(equals(NN_CHECK_ASSERT(france_inter_france), france));
+
+ auto france_inter_world = france->intersection(world);
+ ASSERT_TRUE(france_inter_world != nullptr);
+ EXPECT_TRUE(equals(NN_CHECK_ASSERT(france_inter_world), france));
+
+ auto world_inter_france = world->intersection(france);
+ ASSERT_TRUE(world_inter_france != nullptr);
+ EXPECT_TRUE(equals(NN_CHECK_ASSERT(world_inter_france), france));
+
+ auto france_shifted =
+ Extent::createFromBBOX(-5 + 5, 40 + 5, 12 + 5, 51 + 5);
+ EXPECT_TRUE(france->intersects(france_shifted));
+ EXPECT_TRUE(france_shifted->intersects(france));
+ EXPECT_TRUE(!france->contains(france_shifted));
+ EXPECT_TRUE(!france_shifted->contains(france));
+
+ auto europe = Extent::createFromBBOX(-30, 25, 30, 70);
+ EXPECT_TRUE(europe->contains(france));
+ EXPECT_TRUE(!france->contains(europe));
+
+ auto france_inter_europe = france->intersection(europe);
+ ASSERT_TRUE(france_inter_europe != nullptr);
+ EXPECT_TRUE(equals(NN_CHECK_ASSERT(france_inter_europe), france));
+
+ auto europe_intersects_france = europe->intersection(france);
+ ASSERT_TRUE(europe_intersects_france != nullptr);
+ EXPECT_TRUE(equals(NN_CHECK_ASSERT(europe_intersects_france), france));
+
+ auto nz = Extent::createFromBBOX(155.0, -60.0, -170.0, -25.0);
+ EXPECT_TRUE(nz->contains(nz));
+ EXPECT_TRUE(world->contains(nz));
+ EXPECT_TRUE(nz->intersects(world));
+ EXPECT_TRUE(world->intersects(nz));
+ EXPECT_TRUE(!nz->contains(world));
+ EXPECT_TRUE(!nz->contains(france));
+ EXPECT_TRUE(!france->contains(nz));
+ EXPECT_TRUE(!nz->intersects(france));
+ EXPECT_TRUE(!france->intersects(nz));
+
+ {
+ auto nz_inter_world = nz->intersection(world);
+ ASSERT_TRUE(nz_inter_world != nullptr);
+ EXPECT_TRUE(equals(NN_CHECK_ASSERT(nz_inter_world), nz));
+ }
+
+ {
+ auto nz_inter_world = getBBox(nz)->intersection(getBBox(world));
+ ASSERT_TRUE(nz_inter_world != nullptr);
+ EXPECT_TRUE(equals(NN_CHECK_ASSERT(nz_inter_world), getBBox(nz)));
+ }
+
+ {
+ auto world_inter_nz = world->intersection(nz);
+ ASSERT_TRUE(world_inter_nz != nullptr);
+ EXPECT_TRUE(equals(NN_CHECK_ASSERT(world_inter_nz), nz));
+ }
+
+ {
+ auto world_inter_nz = getBBox(world)->intersection(getBBox(nz));
+ ASSERT_TRUE(world_inter_nz != nullptr);
+ EXPECT_TRUE(equals(NN_CHECK_ASSERT(world_inter_nz), getBBox(nz)));
+ }
+
+ EXPECT_TRUE(nz->intersection(france) == nullptr);
+ EXPECT_TRUE(france->intersection(nz) == nullptr);
+
+ auto bbox_antimeridian_north =
+ Extent::createFromBBOX(155.0, 10.0, -170.0, 30.0);
+ EXPECT_TRUE(!nz->contains(bbox_antimeridian_north));
+ EXPECT_TRUE(!bbox_antimeridian_north->contains(nz));
+ EXPECT_TRUE(!nz->intersects(bbox_antimeridian_north));
+ EXPECT_TRUE(!bbox_antimeridian_north->intersects(nz));
+ EXPECT_TRUE(!nz->intersection(bbox_antimeridian_north));
+ EXPECT_TRUE(!bbox_antimeridian_north->intersection(nz));
+
+ auto nz_pos_long = Extent::createFromBBOX(155.0, -60.0, 180.0, -25.0);
+ EXPECT_TRUE(nz->contains(nz_pos_long));
+ EXPECT_TRUE(!nz_pos_long->contains(nz));
+ EXPECT_TRUE(nz->intersects(nz_pos_long));
+ EXPECT_TRUE(nz_pos_long->intersects(nz));
+ auto nz_inter_nz_pos_long = nz->intersection(nz_pos_long);
+ ASSERT_TRUE(nz_inter_nz_pos_long != nullptr);
+ EXPECT_TRUE(equals(NN_CHECK_ASSERT(nz_inter_nz_pos_long), nz_pos_long));
+ auto nz_pos_long_inter_nz = nz_pos_long->intersection(nz);
+ ASSERT_TRUE(nz_pos_long_inter_nz != nullptr);
+ EXPECT_TRUE(equals(NN_CHECK_ASSERT(nz_pos_long_inter_nz), nz_pos_long));
+
+ auto nz_neg_long = Extent::createFromBBOX(-180.0, -60.0, -170.0, -25.0);
+ EXPECT_TRUE(nz->contains(nz_neg_long));
+ EXPECT_TRUE(!nz_neg_long->contains(nz));
+ EXPECT_TRUE(nz->intersects(nz_neg_long));
+ EXPECT_TRUE(nz_neg_long->intersects(nz));
+ auto nz_inter_nz_neg_long = nz->intersection(nz_neg_long);
+ ASSERT_TRUE(nz_inter_nz_neg_long != nullptr);
+ EXPECT_TRUE(equals(NN_CHECK_ASSERT(nz_inter_nz_neg_long), nz_neg_long));
+ auto nz_neg_long_inter_nz = nz_neg_long->intersection(nz);
+ ASSERT_TRUE(nz_neg_long_inter_nz != nullptr);
+ EXPECT_TRUE(equals(NN_CHECK_ASSERT(nz_neg_long_inter_nz), nz_neg_long));
+
+ auto nz_smaller = Extent::createFromBBOX(160, -55.0, -175.0, -30.0);
+ EXPECT_TRUE(nz->contains(nz_smaller));
+ EXPECT_TRUE(!nz_smaller->contains(nz));
+
+ auto nz_pos_long_shifted_west =
+ Extent::createFromBBOX(150.0, -60.0, 175.0, -25.0);
+ EXPECT_TRUE(!nz->contains(nz_pos_long_shifted_west));
+ EXPECT_TRUE(!nz_pos_long_shifted_west->contains(nz));
+ EXPECT_TRUE(nz->intersects(nz_pos_long_shifted_west));
+ EXPECT_TRUE(nz_pos_long_shifted_west->intersects(nz));
+
+ auto nz_smaller_shifted = Extent::createFromBBOX(165, -60.0, -170.0, -25.0);
+ EXPECT_TRUE(!nz_smaller->contains(nz_smaller_shifted));
+ EXPECT_TRUE(!nz_smaller_shifted->contains(nz_smaller));
+ EXPECT_TRUE(nz_smaller->intersects(nz_smaller_shifted));
+ EXPECT_TRUE(nz_smaller_shifted->intersects(nz_smaller));
+
+ auto nz_shifted = Extent::createFromBBOX(165.0, -60.0, -160.0, -25.0);
+ auto nz_intersect_nz_shifted = nz->intersection(nz_shifted);
+ ASSERT_TRUE(nz_intersect_nz_shifted != nullptr);
+ EXPECT_TRUE(equals(NN_CHECK_ASSERT(nz_intersect_nz_shifted),
+ Extent::createFromBBOX(165, -60.0, -170.0, -25.0)));
+
+ auto nz_inter_nz_smaller = nz->intersection(nz_smaller);
+ ASSERT_TRUE(nz_inter_nz_smaller != nullptr);
+ EXPECT_TRUE(equals(NN_CHECK_ASSERT(nz_inter_nz_smaller), nz_smaller));
+
+ auto nz_smaller_inter_nz = nz_smaller->intersection(nz);
+ ASSERT_TRUE(nz_smaller_inter_nz != nullptr);
+ EXPECT_TRUE(equals(NN_CHECK_ASSERT(nz_smaller_inter_nz), nz_smaller));
+
+ auto world_smaller = Extent::createFromBBOX(-179, -90, 179, 90);
+ EXPECT_TRUE(!world_smaller->contains(nz));
+ EXPECT_TRUE(!nz->contains(world_smaller));
+
+ auto nz_inter_world_smaller = nz->intersection(world_smaller);
+ ASSERT_TRUE(nz_inter_world_smaller != nullptr);
+ EXPECT_TRUE(equals(NN_CHECK_ASSERT(nz_inter_world_smaller),
+ Extent::createFromBBOX(155, -60, 179, -25)));
+
+ auto world_smaller_inter_nz = world_smaller->intersection(nz);
+ ASSERT_TRUE(world_smaller_inter_nz != nullptr);
+ EXPECT_TRUE(equals(NN_CHECK_ASSERT(world_smaller_inter_nz),
+ Extent::createFromBBOX(155, -60, 179, -25)));
+
+ auto world_smaller_east = Extent::createFromBBOX(-179, -90, 150, 90);
+ EXPECT_TRUE(!world_smaller_east->contains(nz));
+ EXPECT_TRUE(!nz->contains(world_smaller_east));
+
+ auto nz_inter_world_smaller_east = nz->intersection(world_smaller_east);
+ ASSERT_TRUE(nz_inter_world_smaller_east != nullptr);
+ EXPECT_EQ(nn_dynamic_pointer_cast<GeographicBoundingBox>(
+ nz_inter_world_smaller_east->geographicElements()[0])
+ ->westBoundLongitude(),
+ -179);
+ EXPECT_EQ(nn_dynamic_pointer_cast<GeographicBoundingBox>(
+ nz_inter_world_smaller_east->geographicElements()[0])
+ ->eastBoundLongitude(),
+ -170);
+ EXPECT_TRUE(equals(NN_CHECK_ASSERT(nz_inter_world_smaller_east),
+ Extent::createFromBBOX(-179, -60, -170, -25)));
+
+ auto world_smaller_east_inter_nz = world_smaller_east->intersection(nz);
+ ASSERT_TRUE(world_smaller_east_inter_nz != nullptr);
+ EXPECT_EQ(nn_dynamic_pointer_cast<GeographicBoundingBox>(
+ world_smaller_east_inter_nz->geographicElements()[0])
+ ->westBoundLongitude(),
+ -179);
+ EXPECT_EQ(nn_dynamic_pointer_cast<GeographicBoundingBox>(
+ world_smaller_east_inter_nz->geographicElements()[0])
+ ->eastBoundLongitude(),
+ -170);
+ EXPECT_TRUE(equals(NN_CHECK_ASSERT(world_smaller_east_inter_nz),
+ Extent::createFromBBOX(-179, -60, -170, -25)));
+
+ auto east_hemisphere = Extent::createFromBBOX(0, -90, 180, 90);
+ auto east_hemisphere_inter_nz = east_hemisphere->intersection(nz);
+ ASSERT_TRUE(east_hemisphere_inter_nz != nullptr);
+ EXPECT_TRUE(equals(NN_CHECK_ASSERT(east_hemisphere_inter_nz),
+ Extent::createFromBBOX(155.0, -60.0, 180.0, -25.0)));
+
+ auto minus_180_to_156 = Extent::createFromBBOX(-180, -90, 156, 90);
+ auto minus_180_to_156_inter_nz = minus_180_to_156->intersection(nz);
+ ASSERT_TRUE(minus_180_to_156_inter_nz != nullptr);
+ EXPECT_TRUE(equals(NN_CHECK_ASSERT(minus_180_to_156_inter_nz),
+ Extent::createFromBBOX(-180.0, -60.0, -170.0, -25.0)));
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(metadata, identifier_empty) {
+ auto id(Identifier::create());
+ Identifier id2(*id);
+ ASSERT_TRUE(!id2.authority().has_value());
+ ASSERT_TRUE(id2.code().empty());
+ ASSERT_TRUE(!id2.codeSpace().has_value());
+ ASSERT_TRUE(!id2.version().has_value());
+ ASSERT_TRUE(!id2.description().has_value());
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(metadata, identifier_properties) {
+ PropertyMap properties;
+ properties.set(Identifier::AUTHORITY_KEY, "authority");
+ properties.set(Identifier::CODESPACE_KEY, "codespace");
+ properties.set(Identifier::VERSION_KEY, "version");
+ properties.set(Identifier::DESCRIPTION_KEY, "description");
+ auto id(Identifier::create("my code", properties));
+ Identifier id2(*id);
+ ASSERT_TRUE(id2.authority().has_value());
+ ASSERT_EQ(*(id2.authority()->title()), "authority");
+ ASSERT_EQ(id2.code(), "my code");
+ ASSERT_TRUE(id2.codeSpace().has_value());
+ ASSERT_EQ(*(id2.codeSpace()), "codespace");
+ ASSERT_TRUE(id2.version().has_value());
+ ASSERT_EQ(*(id2.version()), "version");
+ ASSERT_TRUE(id2.description().has_value());
+ ASSERT_EQ(*(id2.description()), "description");
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(metadata, identifier_code_integer) {
+ PropertyMap properties;
+ properties.set(Identifier::CODE_KEY, 1234);
+ auto id(Identifier::create(std::string(), properties));
+ ASSERT_EQ(id->code(), "1234");
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(metadata, identifier_code_string) {
+ PropertyMap properties;
+ properties.set(Identifier::CODE_KEY, "1234");
+ auto id(Identifier::create(std::string(), properties));
+ ASSERT_EQ(id->code(), "1234");
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(metadata, identifier_code_invalid_type) {
+ PropertyMap properties;
+ properties.set(Identifier::CODE_KEY, true);
+ ASSERT_THROW(Identifier::create(std::string(), properties),
+ InvalidValueTypeException);
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(metadata, identifier_authority_citation) {
+ PropertyMap properties;
+ properties.set(Identifier::AUTHORITY_KEY,
+ nn_make_shared<Citation>("authority"));
+ auto id(Identifier::create(std::string(), properties));
+ ASSERT_TRUE(id->authority().has_value());
+ ASSERT_EQ(*(id->authority()->title()), "authority");
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(metadata, identifier_authority_invalid_type) {
+ PropertyMap properties;
+ properties.set(Identifier::AUTHORITY_KEY, true);
+ ASSERT_THROW(Identifier::create(std::string(), properties),
+ InvalidValueTypeException);
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(metadata, id) {
+ auto in_wkt = "ID[\"EPSG\",4946,1.5,\n"
+ " CITATION[\"my citation\"],\n"
+ " URI[\"urn:ogc:def:crs:EPSG::4946\"]]";
+ auto id =
+ nn_dynamic_pointer_cast<Identifier>(WKTParser().createFromWKT(in_wkt));
+ ASSERT_TRUE(id != nullptr);
+
+ EXPECT_TRUE(id->authority().has_value());
+ EXPECT_EQ(*(id->authority()->title()), "my citation");
+ EXPECT_EQ(*(id->codeSpace()), "EPSG");
+ EXPECT_EQ(id->code(), "4946");
+ EXPECT_TRUE(id->version().has_value());
+ EXPECT_EQ(*(id->version()), "1.5");
+ EXPECT_TRUE(id->uri().has_value());
+ EXPECT_EQ(*(id->uri()), "urn:ogc:def:crs:EPSG::4946");
+
+ auto got_wkt = id->exportToWKT(WKTFormatter::create().get());
+ EXPECT_EQ(got_wkt, in_wkt);
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(metadata, Identifier_isEquivalentName) {
+ EXPECT_TRUE(Identifier::isEquivalentName("Central_Meridian",
+ "Central_- ()/Meridian"));
+}
diff --git a/test/unit/test_operation.cpp b/test/unit/test_operation.cpp
new file mode 100644
index 00000000..2d2688a8
--- /dev/null
+++ b/test/unit/test_operation.cpp
@@ -0,0 +1,6271 @@
+/******************************************************************************
+ *
+ * Project: PROJ
+ * Purpose: Test ISO19111:2018 implementation
+ * Author: Even Rouault <even dot rouault at spatialys dot com>
+ *
+ ******************************************************************************
+ * Copyright (c) 2018, Even Rouault <even dot rouault at spatialys dot 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.
+ ****************************************************************************/
+
+#include "gtest_include.h"
+
+// to be able to use internal::replaceAll
+#define FROM_PROJ_CPP
+
+#include "proj/common.hpp"
+#include "proj/coordinateoperation.hpp"
+#include "proj/coordinatesystem.hpp"
+#include "proj/crs.hpp"
+#include "proj/datum.hpp"
+#include "proj/io.hpp"
+#include "proj/metadata.hpp"
+#include "proj/util.hpp"
+
+#include "proj/internal/internal.hpp"
+
+#include "proj_constants.h"
+
+#include <string>
+#include <vector>
+
+using namespace osgeo::proj::common;
+using namespace osgeo::proj::crs;
+using namespace osgeo::proj::cs;
+using namespace osgeo::proj::datum;
+using namespace osgeo::proj::io;
+using namespace osgeo::proj::internal;
+using namespace osgeo::proj::metadata;
+using namespace osgeo::proj::operation;
+using namespace osgeo::proj::util;
+
+namespace {
+struct UnrelatedObject : public IComparable {
+ UnrelatedObject() = default;
+
+ bool _isEquivalentTo(const IComparable *, Criterion) const override {
+ assert(false);
+ return false;
+ }
+};
+
+static nn<std::shared_ptr<UnrelatedObject>> createUnrelatedObject() {
+ return nn_make_shared<UnrelatedObject>();
+}
+} // namespace
+
+// ---------------------------------------------------------------------------
+
+TEST(operation, method) {
+
+ auto method = OperationMethod::create(
+ PropertyMap(), std::vector<OperationParameterNNPtr>{});
+ EXPECT_TRUE(method->isEquivalentTo(method.get()));
+ EXPECT_FALSE(method->isEquivalentTo(createUnrelatedObject().get()));
+ auto otherMethod = OperationMethod::create(
+ PropertyMap(),
+ std::vector<OperationParameterNNPtr>{OperationParameter::create(
+ PropertyMap().set(IdentifiedObject::NAME_KEY, "paramName"))});
+ EXPECT_TRUE(otherMethod->isEquivalentTo(otherMethod.get()));
+ EXPECT_FALSE(method->isEquivalentTo(otherMethod.get()));
+ auto otherMethod2 = OperationMethod::create(
+ PropertyMap(),
+ std::vector<OperationParameterNNPtr>{OperationParameter::create(
+ PropertyMap().set(IdentifiedObject::NAME_KEY, "paramName2"))});
+ EXPECT_FALSE(otherMethod->isEquivalentTo(otherMethod2.get()));
+ EXPECT_FALSE(otherMethod->isEquivalentTo(
+ otherMethod2.get(), IComparable::Criterion::EQUIVALENT));
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(operation, method_parameter_different_order) {
+
+ auto method1 = OperationMethod::create(
+ PropertyMap(), std::vector<OperationParameterNNPtr>{
+ OperationParameter::create(PropertyMap().set(
+ IdentifiedObject::NAME_KEY, "paramName")),
+ OperationParameter::create(PropertyMap().set(
+ IdentifiedObject::NAME_KEY, "paramName2"))});
+
+ auto method2 = OperationMethod::create(
+ PropertyMap(), std::vector<OperationParameterNNPtr>{
+ OperationParameter::create(PropertyMap().set(
+ IdentifiedObject::NAME_KEY, "paramName2")),
+ OperationParameter::create(PropertyMap().set(
+ IdentifiedObject::NAME_KEY, "paramName"))});
+
+ auto method3 = OperationMethod::create(
+ PropertyMap(), std::vector<OperationParameterNNPtr>{
+ OperationParameter::create(PropertyMap().set(
+ IdentifiedObject::NAME_KEY, "paramName3")),
+ OperationParameter::create(PropertyMap().set(
+ IdentifiedObject::NAME_KEY, "paramName"))});
+
+ EXPECT_FALSE(method1->isEquivalentTo(method2.get()));
+ EXPECT_TRUE(method1->isEquivalentTo(method2.get(),
+ IComparable::Criterion::EQUIVALENT));
+ EXPECT_FALSE(method1->isEquivalentTo(method3.get(),
+ IComparable::Criterion::EQUIVALENT));
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(operation, ParameterValue) {
+
+ auto valStr1 = ParameterValue::create("str1");
+ auto valStr2 = ParameterValue::create("str2");
+ EXPECT_TRUE(valStr1->isEquivalentTo(valStr1.get()));
+ EXPECT_FALSE(valStr1->isEquivalentTo(createUnrelatedObject().get()));
+ EXPECT_FALSE(valStr1->isEquivalentTo(valStr2.get()));
+
+ auto valMeasure1 = ParameterValue::create(Angle(-90.0));
+ auto valMeasure1Eps = ParameterValue::create(Angle(-90.0 - 1e-11));
+ auto valMeasure2 = ParameterValue::create(Angle(-89.0));
+ EXPECT_TRUE(valMeasure1->isEquivalentTo(valMeasure1.get()));
+ EXPECT_TRUE(valMeasure1->isEquivalentTo(
+ valMeasure1.get(), IComparable::Criterion::EQUIVALENT));
+ EXPECT_FALSE(valMeasure1->isEquivalentTo(valMeasure1Eps.get()));
+ EXPECT_TRUE(valMeasure1->isEquivalentTo(
+ valMeasure1Eps.get(), IComparable::Criterion::EQUIVALENT));
+
+ EXPECT_FALSE(valMeasure1->isEquivalentTo(valStr1.get()));
+ EXPECT_FALSE(valMeasure1->isEquivalentTo(valMeasure2.get()));
+ EXPECT_FALSE(valMeasure1->isEquivalentTo(
+ valMeasure2.get(), IComparable::Criterion::EQUIVALENT));
+
+ auto valInt1 = ParameterValue::create(1);
+ auto valInt2 = ParameterValue::create(2);
+ EXPECT_TRUE(valInt1->isEquivalentTo(valInt1.get()));
+ EXPECT_FALSE(valInt1->isEquivalentTo(valInt2.get()));
+
+ auto valTrue = ParameterValue::create(true);
+ auto valFalse = ParameterValue::create(false);
+ EXPECT_TRUE(valTrue->isEquivalentTo(valTrue.get()));
+ EXPECT_FALSE(valTrue->isEquivalentTo(valFalse.get()));
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(operation, OperationParameter) {
+
+ auto op1 = OperationParameter::create(
+ PropertyMap().set(IdentifiedObject::NAME_KEY, "paramName"));
+ auto op2 = OperationParameter::create(
+ PropertyMap().set(IdentifiedObject::NAME_KEY, "paramName2"));
+ EXPECT_TRUE(op1->isEquivalentTo(op1.get()));
+ EXPECT_FALSE(op1->isEquivalentTo(createUnrelatedObject().get()));
+ EXPECT_FALSE(op1->isEquivalentTo(op2.get()));
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(operation, OperationParameterValue) {
+
+ auto op1 = OperationParameter::create(
+ PropertyMap().set(IdentifiedObject::NAME_KEY, "paramName"));
+ auto op2 = OperationParameter::create(
+ PropertyMap().set(IdentifiedObject::NAME_KEY, "paramName2"));
+ auto valStr1 = ParameterValue::create("str1");
+ auto valStr2 = ParameterValue::create("str2");
+ auto opv11 = OperationParameterValue::create(op1, valStr1);
+ EXPECT_TRUE(opv11->isEquivalentTo(opv11.get()));
+ EXPECT_FALSE(opv11->isEquivalentTo(createUnrelatedObject().get()));
+ auto opv12 = OperationParameterValue::create(op1, valStr2);
+ EXPECT_FALSE(opv11->isEquivalentTo(opv12.get()));
+ auto opv21 = OperationParameterValue::create(op2, valStr1);
+ EXPECT_FALSE(opv11->isEquivalentTo(opv12.get()));
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(operation, SingleOperation) {
+
+ auto sop1 = Transformation::create(
+ PropertyMap(), nn_static_pointer_cast<CRS>(GeographicCRS::EPSG_4326),
+ nn_static_pointer_cast<CRS>(GeographicCRS::EPSG_4807),
+ static_cast<CRSPtr>(GeographicCRS::EPSG_4979.as_nullable()),
+ PropertyMap(),
+ std::vector<OperationParameterNNPtr>{OperationParameter::create(
+ PropertyMap().set(IdentifiedObject::NAME_KEY, "paramName"))},
+ std::vector<ParameterValueNNPtr>{
+ ParameterValue::createFilename("foo.bin")},
+ std::vector<PositionalAccuracyNNPtr>{
+ PositionalAccuracy::create("0.1")});
+
+ EXPECT_TRUE(sop1->isEquivalentTo(sop1.get()));
+ EXPECT_FALSE(sop1->isEquivalentTo(createUnrelatedObject().get()));
+
+ auto sop2 = Transformation::create(
+ PropertyMap(), nn_static_pointer_cast<CRS>(GeographicCRS::EPSG_4326),
+ nn_static_pointer_cast<CRS>(GeographicCRS::EPSG_4807),
+ static_cast<CRSPtr>(GeographicCRS::EPSG_4979.as_nullable()),
+ PropertyMap(),
+ std::vector<OperationParameterNNPtr>{OperationParameter::create(
+ PropertyMap().set(IdentifiedObject::NAME_KEY, "paramName2"))},
+ std::vector<ParameterValueNNPtr>{
+ ParameterValue::createFilename("foo.bin")},
+ std::vector<PositionalAccuracyNNPtr>{
+ PositionalAccuracy::create("0.1")});
+ EXPECT_FALSE(sop1->isEquivalentTo(sop2.get()));
+
+ auto sop3 = Transformation::create(
+ PropertyMap(), nn_static_pointer_cast<CRS>(GeographicCRS::EPSG_4326),
+ nn_static_pointer_cast<CRS>(GeographicCRS::EPSG_4807),
+ static_cast<CRSPtr>(GeographicCRS::EPSG_4979.as_nullable()),
+ PropertyMap(),
+ std::vector<OperationParameterNNPtr>{
+ OperationParameter::create(
+ PropertyMap().set(IdentifiedObject::NAME_KEY, "paramName")),
+ OperationParameter::create(
+ PropertyMap().set(IdentifiedObject::NAME_KEY, "paramName2"))},
+ std::vector<ParameterValueNNPtr>{
+ ParameterValue::createFilename("foo.bin"),
+ ParameterValue::createFilename("foo2.bin")},
+ std::vector<PositionalAccuracyNNPtr>{
+ PositionalAccuracy::create("0.1")});
+ EXPECT_FALSE(sop1->isEquivalentTo(sop3.get()));
+
+ auto sop4 = Transformation::create(
+ PropertyMap(), nn_static_pointer_cast<CRS>(GeographicCRS::EPSG_4326),
+ nn_static_pointer_cast<CRS>(GeographicCRS::EPSG_4807),
+ static_cast<CRSPtr>(GeographicCRS::EPSG_4979.as_nullable()),
+ PropertyMap(),
+ std::vector<OperationParameterNNPtr>{OperationParameter::create(
+ PropertyMap().set(IdentifiedObject::NAME_KEY, "paramName"))},
+ std::vector<ParameterValueNNPtr>{
+ ParameterValue::createFilename("foo2.bin")},
+ std::vector<PositionalAccuracyNNPtr>{
+ PositionalAccuracy::create("0.1")});
+ EXPECT_FALSE(sop1->isEquivalentTo(sop4.get()));
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(operation, SingleOperation_different_order) {
+
+ auto sop1 = Transformation::create(
+ PropertyMap().set(IdentifiedObject::NAME_KEY, "ignored1"),
+ GeographicCRS::EPSG_4326, GeographicCRS::EPSG_4807, nullptr,
+ PropertyMap(),
+ std::vector<OperationParameterNNPtr>{
+ OperationParameter::create(
+ PropertyMap().set(IdentifiedObject::NAME_KEY, "paramName")),
+ OperationParameter::create(
+ PropertyMap().set(IdentifiedObject::NAME_KEY, "paramName2"))},
+ std::vector<ParameterValueNNPtr>{
+ ParameterValue::createFilename("foo.bin"),
+ ParameterValue::createFilename("foo2.bin")},
+ {});
+
+ auto sop2 = Transformation::create(
+ PropertyMap().set(IdentifiedObject::NAME_KEY, "ignored2"),
+ GeographicCRS::EPSG_4326, GeographicCRS::EPSG_4807, nullptr,
+ PropertyMap(),
+ std::vector<OperationParameterNNPtr>{
+ OperationParameter::create(
+ PropertyMap().set(IdentifiedObject::NAME_KEY, "paramName2")),
+ OperationParameter::create(
+ PropertyMap().set(IdentifiedObject::NAME_KEY, "paramName"))},
+ std::vector<ParameterValueNNPtr>{
+ ParameterValue::createFilename("foo2.bin"),
+ ParameterValue::createFilename("foo.bin")},
+ {});
+
+ auto sop3 = Transformation::create(
+ PropertyMap().set(IdentifiedObject::NAME_KEY, "ignored3"),
+ GeographicCRS::EPSG_4326, GeographicCRS::EPSG_4807, nullptr,
+ PropertyMap(),
+ std::vector<OperationParameterNNPtr>{
+ OperationParameter::create(
+ PropertyMap().set(IdentifiedObject::NAME_KEY, "paramName")),
+ OperationParameter::create(
+ PropertyMap().set(IdentifiedObject::NAME_KEY, "paramName2"))},
+ std::vector<ParameterValueNNPtr>{
+ ParameterValue::createFilename("foo2.bin"),
+ ParameterValue::createFilename("foo.bin")},
+ {});
+
+ EXPECT_FALSE(sop1->isEquivalentTo(sop2.get()));
+ EXPECT_TRUE(
+ sop1->isEquivalentTo(sop2.get(), IComparable::Criterion::EQUIVALENT));
+ EXPECT_FALSE(
+ sop1->isEquivalentTo(sop3.get(), IComparable::Criterion::EQUIVALENT));
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(operation, transformation_to_wkt) {
+ PropertyMap propertiesTransformation;
+ propertiesTransformation
+ .set(Identifier::CODESPACE_KEY, "codeSpaceTransformation")
+ .set(Identifier::CODE_KEY, "codeTransformation")
+ .set(IdentifiedObject::NAME_KEY, "transformationName")
+ .set(IdentifiedObject::REMARKS_KEY, "my remarks");
+
+ auto transf = Transformation::create(
+ propertiesTransformation,
+ nn_static_pointer_cast<CRS>(GeographicCRS::EPSG_4326),
+ nn_static_pointer_cast<CRS>(GeographicCRS::EPSG_4807),
+ static_cast<CRSPtr>(GeographicCRS::EPSG_4979.as_nullable()),
+ PropertyMap()
+ .set(Identifier::CODESPACE_KEY, "codeSpaceOperationMethod")
+ .set(Identifier::CODE_KEY, "codeOperationMethod")
+ .set(IdentifiedObject::NAME_KEY, "operationMethodName"),
+ std::vector<OperationParameterNNPtr>{OperationParameter::create(
+ PropertyMap().set(IdentifiedObject::NAME_KEY, "paramName"))},
+ std::vector<ParameterValueNNPtr>{
+ ParameterValue::createFilename("foo.bin")},
+ std::vector<PositionalAccuracyNNPtr>{
+ PositionalAccuracy::create("0.1")});
+
+ std::string src_wkt;
+ {
+ auto formatter = WKTFormatter::create();
+ formatter->setOutputId(false);
+ src_wkt = GeographicCRS::EPSG_4326->exportToWKT(formatter.get());
+ }
+
+ std::string dst_wkt;
+ {
+ auto formatter = WKTFormatter::create();
+ formatter->setOutputId(false);
+ dst_wkt = GeographicCRS::EPSG_4807->exportToWKT(formatter.get());
+ }
+
+ std::string interpolation_wkt;
+ {
+ auto formatter = WKTFormatter::create();
+ formatter->setOutputId(false);
+ interpolation_wkt =
+ GeographicCRS::EPSG_4979->exportToWKT(formatter.get());
+ }
+
+ auto expected =
+ "COORDINATEOPERATION[\"transformationName\",\n"
+ " SOURCECRS[" +
+ src_wkt + "],\n"
+ " TARGETCRS[" +
+ dst_wkt +
+ "],\n"
+ " METHOD[\"operationMethodName\",\n"
+ " ID[\"codeSpaceOperationMethod\",\"codeOperationMethod\"]],\n"
+ " PARAMETERFILE[\"paramName\",\"foo.bin\"],\n"
+ " INTERPOLATIONCRS[" +
+ interpolation_wkt +
+ "],\n"
+ " OPERATIONACCURACY[0.1],\n"
+ " ID[\"codeSpaceTransformation\",\"codeTransformation\"],\n"
+ " REMARK[\"my remarks\"]]";
+
+ EXPECT_EQ(
+ replaceAll(replaceAll(transf->exportToWKT(WKTFormatter::create().get()),
+ " ", ""),
+ "\n", ""),
+ replaceAll(replaceAll(expected, " ", ""), "\n", ""));
+
+ EXPECT_THROW(
+ transf->exportToWKT(
+ WKTFormatter::create(WKTFormatter::Convention::WKT1_GDAL).get()),
+ FormattingException);
+
+ EXPECT_TRUE(transf->isEquivalentTo(transf.get()));
+ EXPECT_FALSE(transf->isEquivalentTo(createUnrelatedObject().get()));
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(operation, concatenated_operation) {
+
+ PropertyMap propertiesTransformation;
+ propertiesTransformation
+ .set(Identifier::CODESPACE_KEY, "codeSpaceTransformation")
+ .set(Identifier::CODE_KEY, "codeTransformation")
+ .set(IdentifiedObject::NAME_KEY, "transformationName")
+ .set(IdentifiedObject::REMARKS_KEY, "my remarks");
+
+ auto transf_1 = Transformation::create(
+ propertiesTransformation,
+ nn_static_pointer_cast<CRS>(GeographicCRS::EPSG_4326),
+ nn_static_pointer_cast<CRS>(GeographicCRS::EPSG_4807), nullptr,
+ PropertyMap().set(IdentifiedObject::NAME_KEY, "operationMethodName"),
+ std::vector<OperationParameterNNPtr>{OperationParameter::create(
+ PropertyMap().set(IdentifiedObject::NAME_KEY, "paramName"))},
+ std::vector<ParameterValueNNPtr>{
+ ParameterValue::createFilename("foo.bin")},
+ std::vector<PositionalAccuracyNNPtr>());
+
+ auto transf_2 = Transformation::create(
+ propertiesTransformation,
+ nn_static_pointer_cast<CRS>(GeographicCRS::EPSG_4807),
+ nn_static_pointer_cast<CRS>(GeographicCRS::EPSG_4979), nullptr,
+ PropertyMap().set(IdentifiedObject::NAME_KEY, "operationMethodName"),
+ std::vector<OperationParameterNNPtr>{OperationParameter::create(
+ PropertyMap().set(IdentifiedObject::NAME_KEY, "paramName"))},
+ std::vector<ParameterValueNNPtr>{
+ ParameterValue::createFilename("foo.bin")},
+ std::vector<PositionalAccuracyNNPtr>());
+
+ auto concat = ConcatenatedOperation::create(
+ PropertyMap()
+ .set(Identifier::CODESPACE_KEY, "codeSpace")
+ .set(Identifier::CODE_KEY, "code")
+ .set(IdentifiedObject::NAME_KEY, "name")
+ .set(IdentifiedObject::REMARKS_KEY, "my remarks"),
+ std::vector<CoordinateOperationNNPtr>{transf_1, transf_2},
+ std::vector<PositionalAccuracyNNPtr>{
+ PositionalAccuracy::create("0.1")});
+
+ std::string src_wkt;
+ {
+ auto formatter =
+ WKTFormatter::create(WKTFormatter::Convention::WKT2_2018);
+ formatter->setOutputId(false);
+ src_wkt = GeographicCRS::EPSG_4326->exportToWKT(formatter.get());
+ }
+
+ std::string dst_wkt;
+ {
+ auto formatter =
+ WKTFormatter::create(WKTFormatter::Convention::WKT2_2018);
+ formatter->setOutputId(false);
+ dst_wkt = GeographicCRS::EPSG_4979->exportToWKT(formatter.get());
+ }
+
+ std::string step1_wkt;
+ {
+ auto formatter =
+ WKTFormatter::create(WKTFormatter::Convention::WKT2_2018);
+ formatter->setOutputId(false);
+ step1_wkt = transf_1->exportToWKT(formatter.get());
+ }
+
+ std::string step2_wkt;
+ {
+ auto formatter =
+ WKTFormatter::create(WKTFormatter::Convention::WKT2_2018);
+ formatter->setOutputId(false);
+ step2_wkt = transf_2->exportToWKT(formatter.get());
+ }
+
+ auto expected = "CONCATENATEDOPERATION[\"name\",\n"
+ " SOURCECRS[" +
+ src_wkt + "],\n"
+ " TARGETCRS[" +
+ dst_wkt + "],\n"
+ " STEP[" +
+ step1_wkt + "],\n"
+ " STEP[" +
+ step2_wkt + "],\n"
+ " ID[\"codeSpace\",\"code\"],\n"
+ " REMARK[\"my remarks\"]]";
+
+ EXPECT_EQ(replaceAll(replaceAll(concat->exportToWKT(
+ WKTFormatter::create(
+ WKTFormatter::Convention::WKT2_2018)
+ .get()),
+ " ", ""),
+ "\n", ""),
+ replaceAll(replaceAll(expected, " ", ""), "\n", ""));
+
+ EXPECT_THROW(concat->exportToWKT(WKTFormatter::create().get()),
+ FormattingException);
+
+ EXPECT_THROW(ConcatenatedOperation::create(
+ PropertyMap().set(IdentifiedObject::NAME_KEY, "name"),
+ std::vector<CoordinateOperationNNPtr>{transf_1, transf_1},
+ std::vector<PositionalAccuracyNNPtr>()),
+ InvalidOperation);
+
+ auto inv = concat->inverse();
+ EXPECT_EQ(inv->nameStr(), "Inverse of name");
+ EXPECT_EQ(inv->sourceCRS()->nameStr(), concat->targetCRS()->nameStr());
+ EXPECT_EQ(inv->targetCRS()->nameStr(), concat->sourceCRS()->nameStr());
+ auto inv_as_concat = nn_dynamic_pointer_cast<ConcatenatedOperation>(inv);
+ ASSERT_TRUE(inv_as_concat != nullptr);
+
+ ASSERT_EQ(inv_as_concat->operations().size(), 2);
+ EXPECT_EQ(inv_as_concat->operations()[0]->nameStr(),
+ "Inverse of transformationName");
+ EXPECT_EQ(inv_as_concat->operations()[1]->nameStr(),
+ "Inverse of transformationName");
+
+ EXPECT_TRUE(concat->isEquivalentTo(concat.get()));
+ EXPECT_FALSE(concat->isEquivalentTo(createUnrelatedObject().get()));
+ EXPECT_FALSE(
+ ConcatenatedOperation::create(PropertyMap(),
+ std::vector<CoordinateOperationNNPtr>{
+ transf_1, transf_1->inverse()},
+ std::vector<PositionalAccuracyNNPtr>())
+ ->isEquivalentTo(ConcatenatedOperation::create(
+ PropertyMap(),
+ std::vector<CoordinateOperationNNPtr>{
+ transf_1->inverse(), transf_1},
+ std::vector<PositionalAccuracyNNPtr>())
+ .get()));
+ EXPECT_FALSE(
+ ConcatenatedOperation::create(PropertyMap(),
+ std::vector<CoordinateOperationNNPtr>{
+ transf_1, transf_1->inverse()},
+ std::vector<PositionalAccuracyNNPtr>())
+ ->isEquivalentTo(ConcatenatedOperation::create(
+ PropertyMap(),
+ std::vector<CoordinateOperationNNPtr>{
+ transf_1, transf_1->inverse(), transf_1},
+ std::vector<PositionalAccuracyNNPtr>())
+ .get()));
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(operation, transformation_createGeocentricTranslations) {
+
+ auto transf = Transformation::createGeocentricTranslations(
+ PropertyMap(), GeographicCRS::EPSG_4269, GeographicCRS::EPSG_4326, 1.0,
+ 2.0, 3.0, std::vector<PositionalAccuracyNNPtr>());
+
+ auto params = transf->getTOWGS84Parameters();
+ auto expected = std::vector<double>{1.0, 2.0, 3.0, 0.0, 0.0, 0.0, 0.0};
+ EXPECT_EQ(params, expected);
+
+ auto inv_transf = transf->inverse();
+ auto inv_transf_as_transf =
+ nn_dynamic_pointer_cast<Transformation>(inv_transf);
+ ASSERT_TRUE(inv_transf_as_transf != nullptr);
+
+ EXPECT_EQ(transf->sourceCRS()->nameStr(),
+ inv_transf_as_transf->targetCRS()->nameStr());
+ EXPECT_EQ(transf->targetCRS()->nameStr(),
+ inv_transf_as_transf->sourceCRS()->nameStr());
+ auto expected_inv =
+ std::vector<double>{-1.0, -2.0, -3.0, 0.0, 0.0, 0.0, 0.0};
+ EXPECT_EQ(inv_transf_as_transf->getTOWGS84Parameters(), expected_inv);
+
+ EXPECT_EQ(transf->exportToPROJString(PROJStringFormatter::create().get()),
+ "+proj=pipeline +step +proj=axisswap +order=2,1 +step "
+ "+proj=unitconvert +xy_in=deg +xy_out=rad +step +proj=cart "
+ "+ellps=GRS80 +step +proj=helmert +x=1 +y=2 +z=3 +step +inv "
+ "+proj=cart +ellps=WGS84 +step +proj=unitconvert +xy_in=rad "
+ "+xy_out=deg +step +proj=axisswap +order=2,1");
+}
+
+// ---------------------------------------------------------------------------
+
+static GeodeticCRSNNPtr createGeocentricDatumWGS84() {
+ PropertyMap propertiesCRS;
+ propertiesCRS.set(Identifier::CODESPACE_KEY, "EPSG")
+ .set(Identifier::CODE_KEY, 4328)
+ .set(IdentifiedObject::NAME_KEY, "WGS 84");
+ return GeodeticCRS::create(
+ propertiesCRS, GeodeticReferenceFrame::EPSG_6326,
+ CartesianCS::createGeocentric(UnitOfMeasure::METRE));
+}
+
+// ---------------------------------------------------------------------------
+
+static GeodeticCRSNNPtr createGeocentricKM() {
+ PropertyMap propertiesCRS;
+ propertiesCRS.set(Identifier::CODESPACE_KEY, "EPSG")
+ .set(Identifier::CODE_KEY, 4328)
+ .set(IdentifiedObject::NAME_KEY, "WGS 84");
+ return GeodeticCRS::create(
+ propertiesCRS, GeodeticReferenceFrame::EPSG_6326,
+ CartesianCS::createGeocentric(
+ UnitOfMeasure("kilometre", 1000.0, UnitOfMeasure::Type::LINEAR)));
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(operation,
+ transformation_createGeocentricTranslations_between_geocentricCRS) {
+
+ auto transf1 = Transformation::createGeocentricTranslations(
+ PropertyMap(), createGeocentricDatumWGS84(), createGeocentricKM(), 1.0,
+ 2.0, 3.0, std::vector<PositionalAccuracyNNPtr>());
+
+ EXPECT_EQ(transf1->exportToPROJString(PROJStringFormatter::create().get()),
+ "+proj=pipeline +step +proj=helmert +x=1 +y=2 +z=3 +step "
+ "+proj=unitconvert +xy_in=m +z_in=m +xy_out=km +z_out=km");
+
+ auto transf2 = Transformation::createGeocentricTranslations(
+ PropertyMap(), createGeocentricKM(), createGeocentricDatumWGS84(), 1.0,
+ 2.0, 3.0, std::vector<PositionalAccuracyNNPtr>());
+
+ EXPECT_EQ(transf2->exportToPROJString(PROJStringFormatter::create().get()),
+ "+proj=pipeline +step +proj=unitconvert +xy_in=km +z_in=km "
+ "+xy_out=m +z_out=m +step +proj=helmert +x=1 +y=2 +z=3");
+
+ auto transf3 = Transformation::createGeocentricTranslations(
+ PropertyMap(), createGeocentricKM(), createGeocentricKM(), 1.0, 2.0,
+ 3.0, std::vector<PositionalAccuracyNNPtr>());
+
+ EXPECT_EQ(transf3->exportToPROJString(PROJStringFormatter::create().get()),
+ "+proj=pipeline +step +proj=unitconvert +xy_in=km +z_in=km "
+ "+xy_out=m +z_out=m +step +proj=helmert +x=1 +y=2 +z=3 +step "
+ "+proj=unitconvert +xy_in=m +z_in=m +xy_out=km +z_out=km");
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(operation, transformation_createGeocentricTranslations_null) {
+
+ auto transf = Transformation::createGeocentricTranslations(
+ PropertyMap(), createGeocentricDatumWGS84(),
+ createGeocentricDatumWGS84(), 0.0, 0.0, 0.0,
+ std::vector<PositionalAccuracyNNPtr>());
+
+ EXPECT_EQ(transf->inverse()->exportToPROJString(
+ PROJStringFormatter::create().get()),
+ "");
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(operation, transformation_createGeocentricTranslations_neg_zero) {
+
+ auto transf = Transformation::createGeocentricTranslations(
+ PropertyMap(), createGeocentricDatumWGS84(),
+ createGeocentricDatumWGS84(), 1.0, -0.0, 0.0,
+ std::vector<PositionalAccuracyNNPtr>());
+
+ EXPECT_EQ(transf->inverse()->exportToPROJString(
+ PROJStringFormatter::create().get()),
+ "+proj=helmert +x=-1 +y=0 +z=0");
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(operation, transformation_createPositionVector) {
+
+ auto transf = Transformation::createPositionVector(
+ PropertyMap(), GeographicCRS::EPSG_4269, GeographicCRS::EPSG_4326, 1.0,
+ 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, std::vector<PositionalAccuracyNNPtr>{
+ PositionalAccuracy::create("100")});
+ ASSERT_EQ(transf->coordinateOperationAccuracies().size(), 1);
+
+ auto expected = std::vector<double>{1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0};
+ EXPECT_EQ(transf->getTOWGS84Parameters(), expected);
+
+ EXPECT_EQ(transf->exportToPROJString(PROJStringFormatter::create().get()),
+ "+proj=pipeline +step +proj=axisswap +order=2,1 +step "
+ "+proj=unitconvert +xy_in=deg +xy_out=rad +step +proj=cart "
+ "+ellps=GRS80 +step +proj=helmert +x=1 +y=2 +z=3 +rx=4 +ry=5 "
+ "+rz=6 +s=7 +convention=position_vector +step +inv +proj=cart "
+ "+ellps=WGS84 +step "
+ "+proj=unitconvert +xy_in=rad +xy_out=deg +step +proj=axisswap "
+ "+order=2,1");
+
+ auto inv_transf = transf->inverse();
+ ASSERT_EQ(inv_transf->coordinateOperationAccuracies().size(), 1);
+
+ EXPECT_EQ(transf->sourceCRS()->nameStr(),
+ inv_transf->targetCRS()->nameStr());
+ EXPECT_EQ(transf->targetCRS()->nameStr(),
+ inv_transf->sourceCRS()->nameStr());
+
+#ifdef USE_APPROXIMATE_HELMERT_INVERSE
+ auto inv_transf_as_transf =
+ nn_dynamic_pointer_cast<Transformation>(inv_transf);
+ ASSERT_TRUE(inv_transf_as_transf != nullptr);
+#else
+ EXPECT_EQ(
+ inv_transf->exportToPROJString(PROJStringFormatter::create().get()),
+ "+proj=pipeline +step +proj=axisswap +order=2,1 +step "
+ "+proj=unitconvert +xy_in=deg +xy_out=rad +step +proj=cart "
+ "+ellps=WGS84 +step +inv +proj=helmert +x=1 +y=2 +z=3 +rx=4 "
+ "+ry=5 +rz=6 +s=7 +convention=position_vector +step +inv "
+ "+proj=cart +ellps=GRS80 +step +proj=unitconvert +xy_in=rad "
+ "+xy_out=deg +step +proj=axisswap +order=2,1");
+
+ // In WKT, use approximate formula
+ auto wkt = inv_transf->exportToWKT(WKTFormatter::create().get());
+ EXPECT_TRUE(
+ wkt.find("Transformation from WGS 84 to NAD83 (approx. inversion)") !=
+ std::string::npos)
+ << wkt;
+ EXPECT_TRUE(wkt.find("Position Vector transformation (geog2D domain)") !=
+ std::string::npos)
+ << wkt;
+ EXPECT_TRUE(wkt.find("ID[\"EPSG\",9606]]") != std::string::npos) << wkt;
+ EXPECT_TRUE(wkt.find("\"X-axis translation\",-1") != std::string::npos)
+ << wkt;
+ EXPECT_TRUE(wkt.find("\"Y-axis translation\",-2") != std::string::npos)
+ << wkt;
+ EXPECT_TRUE(wkt.find("\"Z-axis translation\",-3") != std::string::npos)
+ << wkt;
+ EXPECT_TRUE(wkt.find("\"X-axis rotation\",-4") != std::string::npos) << wkt;
+ EXPECT_TRUE(wkt.find("\"Y-axis rotation\",-5") != std::string::npos) << wkt;
+ EXPECT_TRUE(wkt.find("\"Z-axis rotation\",-6") != std::string::npos) << wkt;
+ EXPECT_TRUE(wkt.find("\"Scale difference\",-7") != std::string::npos)
+ << wkt;
+#endif
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(operation, transformation_createCoordinateFrameRotation) {
+
+ auto transf = Transformation::createCoordinateFrameRotation(
+ PropertyMap(), GeographicCRS::EPSG_4269, GeographicCRS::EPSG_4326, 1.0,
+ 2.0, 3.0, -4.0, -5.0, -6.0, 7.0,
+ std::vector<PositionalAccuracyNNPtr>());
+
+ auto params = transf->getTOWGS84Parameters();
+ auto expected = std::vector<double>{1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0};
+ EXPECT_EQ(params, expected);
+
+ EXPECT_EQ(transf->exportToPROJString(PROJStringFormatter::create().get()),
+ "+proj=pipeline +step +proj=axisswap +order=2,1 +step "
+ "+proj=unitconvert +xy_in=deg +xy_out=rad +step +proj=cart "
+ "+ellps=GRS80 +step +proj=helmert +x=1 +y=2 +z=3 +rx=-4 +ry=-5 "
+ "+rz=-6 +s=7 +convention=coordinate_frame +step +inv +proj=cart "
+ "+ellps=WGS84 +step "
+ "+proj=unitconvert +xy_in=rad +xy_out=deg +step +proj=axisswap "
+ "+order=2,1");
+
+ auto inv_transf = transf->inverse();
+ ASSERT_EQ(inv_transf->coordinateOperationAccuracies().size(), 0);
+
+ EXPECT_EQ(transf->sourceCRS()->nameStr(),
+ inv_transf->targetCRS()->nameStr());
+ EXPECT_EQ(transf->targetCRS()->nameStr(),
+ inv_transf->sourceCRS()->nameStr());
+
+#ifdef USE_APPROXIMATE_HELMERT_INVERSE
+ auto inv_transf_as_transf =
+ nn_dynamic_pointer_cast<Transformation>(inv_transf);
+ ASSERT_TRUE(inv_transf_as_transf != nullptr);
+#else
+ EXPECT_EQ(
+ inv_transf->exportToPROJString(PROJStringFormatter::create().get()),
+ "+proj=pipeline +step +proj=axisswap +order=2,1 +step "
+ "+proj=unitconvert +xy_in=deg +xy_out=rad +step +proj=cart "
+ "+ellps=WGS84 +step +inv +proj=helmert +x=1 +y=2 +z=3 +rx=-4 "
+ "+ry=-5 +rz=-6 +s=7 +convention=coordinate_frame +step +inv "
+ "+proj=cart +ellps=GRS80 +step +proj=unitconvert +xy_in=rad "
+ "+xy_out=deg +step +proj=axisswap +order=2,1");
+
+ // In WKT, use approximate formula
+ auto wkt = inv_transf->exportToWKT(WKTFormatter::create().get());
+ EXPECT_TRUE(
+ wkt.find("Transformation from WGS 84 to NAD83 (approx. inversion)") !=
+ std::string::npos)
+ << wkt;
+ EXPECT_TRUE(wkt.find("Coordinate Frame rotation (geog2D domain)") !=
+ std::string::npos)
+ << wkt;
+ EXPECT_TRUE(wkt.find("ID[\"EPSG\",9607]]") != std::string::npos) << wkt;
+ EXPECT_TRUE(wkt.find("\"X-axis translation\",-1") != std::string::npos)
+ << wkt;
+ EXPECT_TRUE(wkt.find("\"Y-axis translation\",-2") != std::string::npos)
+ << wkt;
+ EXPECT_TRUE(wkt.find("\"Z-axis translation\",-3") != std::string::npos)
+ << wkt;
+ EXPECT_TRUE(wkt.find("\"X-axis rotation\",4") != std::string::npos) << wkt;
+ EXPECT_TRUE(wkt.find("\"Y-axis rotation\",5") != std::string::npos) << wkt;
+ EXPECT_TRUE(wkt.find("\"Z-axis rotation\",6") != std::string::npos) << wkt;
+ EXPECT_TRUE(wkt.find("\"Scale difference\",-7") != std::string::npos)
+ << wkt;
+#endif
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(operation, transformation_createTimeDependentPositionVector) {
+
+ auto transf = Transformation::createTimeDependentPositionVector(
+ PropertyMap(), GeographicCRS::EPSG_4269, GeographicCRS::EPSG_4326, 1.0,
+ 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 2018.5,
+ std::vector<PositionalAccuracyNNPtr>());
+
+ auto inv_transf = transf->inverse();
+
+ EXPECT_EQ(transf->sourceCRS()->nameStr(),
+ inv_transf->targetCRS()->nameStr());
+ EXPECT_EQ(transf->targetCRS()->nameStr(),
+ inv_transf->sourceCRS()->nameStr());
+
+ auto projString =
+ inv_transf->exportToPROJString(PROJStringFormatter::create().get());
+ EXPECT_TRUE(projString.find("+proj=helmert +x=1 +y=2 +z=3 +rx=4 +ry=5 "
+ "+rz=6 +s=7 +dx=0.1 +dy=0.2 +dz=0.3 +drx=0.4 "
+ "+dry=0.5 +drz=0.6 +ds=0.7 +t_epoch=2018.5 "
+ "+convention=position_vector") !=
+ std::string::npos)
+ << projString;
+
+ // In WKT, use approximate formula
+ auto wkt = inv_transf->exportToWKT(WKTFormatter::create().get());
+ EXPECT_TRUE(
+ wkt.find("Transformation from WGS 84 to NAD83 (approx. inversion)") !=
+ std::string::npos)
+ << wkt;
+ EXPECT_TRUE(wkt.find("Time-dependent Position Vector tfm (geog2D)") !=
+ std::string::npos)
+ << wkt;
+ EXPECT_TRUE(wkt.find("ID[\"EPSG\",1054]]") != std::string::npos) << wkt;
+ EXPECT_TRUE(wkt.find("\"X-axis translation\",-1") != std::string::npos)
+ << wkt;
+ EXPECT_TRUE(wkt.find("\"Y-axis translation\",-2") != std::string::npos)
+ << wkt;
+ EXPECT_TRUE(wkt.find("\"Z-axis translation\",-3") != std::string::npos)
+ << wkt;
+ EXPECT_TRUE(wkt.find("\"X-axis rotation\",-4") != std::string::npos) << wkt;
+ EXPECT_TRUE(wkt.find("\"Y-axis rotation\",-5") != std::string::npos) << wkt;
+ EXPECT_TRUE(wkt.find("\"Z-axis rotation\",-6") != std::string::npos) << wkt;
+ EXPECT_TRUE(wkt.find("\"Scale difference\",-7") != std::string::npos)
+ << wkt;
+ EXPECT_TRUE(wkt.find("\"Rate of change of X-axis translation\",-0.1") !=
+ std::string::npos)
+ << wkt;
+ EXPECT_TRUE(wkt.find("\"Rate of change of Y-axis translation\",-0.2") !=
+ std::string::npos)
+ << wkt;
+ EXPECT_TRUE(wkt.find("\"Rate of change of Z-axis translation\",-0.3") !=
+ std::string::npos)
+ << wkt;
+ EXPECT_TRUE(wkt.find("\"Rate of change of X-axis rotation\",-0.4") !=
+ std::string::npos)
+ << wkt;
+ EXPECT_TRUE(wkt.find("\"Rate of change of Y-axis rotation\",-0.5") !=
+ std::string::npos)
+ << wkt;
+ EXPECT_TRUE(wkt.find("\"Rate of change of Z-axis rotation\",-0.6") !=
+ std::string::npos)
+ << wkt;
+ EXPECT_TRUE(wkt.find("\"Rate of change of Scale difference\",-0.7") !=
+ std::string::npos)
+ << wkt;
+ EXPECT_TRUE(wkt.find("\"Parameter reference epoch\",2018.5") !=
+ std::string::npos)
+ << wkt;
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(operation, transformation_createTimeDependentCoordinateFrameRotation) {
+
+ auto transf = Transformation::createTimeDependentCoordinateFrameRotation(
+ PropertyMap(), GeographicCRS::EPSG_4269, GeographicCRS::EPSG_4326, 1.0,
+ 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 2018.5,
+ std::vector<PositionalAccuracyNNPtr>());
+
+ auto inv_transf = transf->inverse();
+
+ EXPECT_EQ(transf->sourceCRS()->nameStr(),
+ inv_transf->targetCRS()->nameStr());
+ EXPECT_EQ(transf->targetCRS()->nameStr(),
+ inv_transf->sourceCRS()->nameStr());
+
+ auto projString =
+ inv_transf->exportToPROJString(PROJStringFormatter::create().get());
+ EXPECT_TRUE(projString.find("+proj=helmert +x=1 +y=2 +z=3 +rx=4 +ry=5 "
+ "+rz=6 +s=7 +dx=0.1 +dy=0.2 +dz=0.3 +drx=0.4 "
+ "+dry=0.5 +drz=0.6 +ds=0.7 +t_epoch=2018.5 "
+ "+convention=coordinate_frame") !=
+ std::string::npos)
+ << projString;
+
+ // In WKT, use approximate formula
+ auto wkt = inv_transf->exportToWKT(WKTFormatter::create().get());
+ EXPECT_TRUE(
+ wkt.find("Transformation from WGS 84 to NAD83 (approx. inversion)") !=
+ std::string::npos)
+ << wkt;
+ EXPECT_TRUE(wkt.find("Time-dependent Coordinate Frame rotation (geog2D)") !=
+ std::string::npos)
+ << wkt;
+ EXPECT_TRUE(wkt.find("ID[\"EPSG\",1057]]") != std::string::npos) << wkt;
+ EXPECT_TRUE(wkt.find("\"X-axis translation\",-1") != std::string::npos)
+ << wkt;
+ EXPECT_TRUE(wkt.find("\"Y-axis translation\",-2") != std::string::npos)
+ << wkt;
+ EXPECT_TRUE(wkt.find("\"Z-axis translation\",-3") != std::string::npos)
+ << wkt;
+ EXPECT_TRUE(wkt.find("\"X-axis rotation\",-4") != std::string::npos) << wkt;
+ EXPECT_TRUE(wkt.find("\"Y-axis rotation\",-5") != std::string::npos) << wkt;
+ EXPECT_TRUE(wkt.find("\"Z-axis rotation\",-6") != std::string::npos) << wkt;
+ EXPECT_TRUE(wkt.find("\"Scale difference\",-7") != std::string::npos)
+ << wkt;
+ EXPECT_TRUE(wkt.find("\"Rate of change of X-axis translation\",-0.1") !=
+ std::string::npos)
+ << wkt;
+ EXPECT_TRUE(wkt.find("\"Rate of change of Y-axis translation\",-0.2") !=
+ std::string::npos)
+ << wkt;
+ EXPECT_TRUE(wkt.find("\"Rate of change of Z-axis translation\",-0.3") !=
+ std::string::npos)
+ << wkt;
+ EXPECT_TRUE(wkt.find("\"Rate of change of X-axis rotation\",-0.4") !=
+ std::string::npos)
+ << wkt;
+ EXPECT_TRUE(wkt.find("\"Rate of change of Y-axis rotation\",-0.5") !=
+ std::string::npos)
+ << wkt;
+ EXPECT_TRUE(wkt.find("\"Rate of change of Z-axis rotation\",-0.6") !=
+ std::string::npos)
+ << wkt;
+ EXPECT_TRUE(wkt.find("\"Rate of change of Scale difference\",-0.7") !=
+ std::string::npos)
+ << wkt;
+ EXPECT_TRUE(wkt.find("\"Parameter reference epoch\",2018.5") !=
+ std::string::npos)
+ << wkt;
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(operation, transformation_successive_helmert_noop) {
+
+ auto transf_1 = Transformation::createPositionVector(
+ PropertyMap(), GeographicCRS::EPSG_4326, GeographicCRS::EPSG_4269, 1.0,
+ 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, std::vector<PositionalAccuracyNNPtr>());
+ auto transf_2 = Transformation::createPositionVector(
+ PropertyMap(), GeographicCRS::EPSG_4269, GeographicCRS::EPSG_4326, -1.0,
+ -2.0, -3.0, -4.0, -5.0, -6.0, -7.0,
+ std::vector<PositionalAccuracyNNPtr>());
+
+ auto concat = ConcatenatedOperation::create(
+ PropertyMap(),
+ std::vector<CoordinateOperationNNPtr>{transf_1, transf_2},
+ std::vector<PositionalAccuracyNNPtr>{});
+
+ EXPECT_EQ(concat->exportToPROJString(PROJStringFormatter::create().get()),
+ "");
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(operation, transformation_successive_helmert_non_trivial_1) {
+
+ auto transf_1 = Transformation::createPositionVector(
+ PropertyMap(), GeographicCRS::EPSG_4326, GeographicCRS::EPSG_4269, 1.0,
+ 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, std::vector<PositionalAccuracyNNPtr>());
+ auto transf_2 = Transformation::createPositionVector(
+ PropertyMap(), GeographicCRS::EPSG_4269, GeographicCRS::EPSG_4326, -1.0,
+ -2.0, -3.0, -4.0, -5.0, -6.0, 7.0,
+ std::vector<PositionalAccuracyNNPtr>());
+
+ auto concat = ConcatenatedOperation::create(
+ PropertyMap(),
+ std::vector<CoordinateOperationNNPtr>{transf_1, transf_2},
+ std::vector<PositionalAccuracyNNPtr>{});
+
+ EXPECT_NE(concat->exportToPROJString(PROJStringFormatter::create().get()),
+ "");
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(operation, transformation_successive_helmert_non_trivial_2) {
+
+ auto transf_1 = Transformation::createPositionVector(
+ PropertyMap(), GeographicCRS::EPSG_4326, GeographicCRS::EPSG_4269, 1.0,
+ 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, std::vector<PositionalAccuracyNNPtr>());
+ auto transf_2 = Transformation::createCoordinateFrameRotation(
+ PropertyMap(), GeographicCRS::EPSG_4269, GeographicCRS::EPSG_4326, -1.0,
+ -2.0, -3.0, -4.0, -5.0, -6.0, -7.0,
+ std::vector<PositionalAccuracyNNPtr>());
+
+ auto concat = ConcatenatedOperation::create(
+ PropertyMap(),
+ std::vector<CoordinateOperationNNPtr>{transf_1, transf_2},
+ std::vector<PositionalAccuracyNNPtr>{});
+
+ EXPECT_NE(concat->exportToPROJString(PROJStringFormatter::create().get()),
+ "");
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(operation, transformation_createMolodensky) {
+
+ auto transf = Transformation::createMolodensky(
+ PropertyMap(), GeographicCRS::EPSG_4326, GeographicCRS::EPSG_4269, 1.0,
+ 2.0, 3.0, 4.0, 5.0, std::vector<PositionalAccuracyNNPtr>());
+
+ auto wkt = transf->exportToWKT(WKTFormatter::create().get());
+ EXPECT_TRUE(replaceAll(replaceAll(wkt, " ", ""), "\n", "")
+ .find("METHOD[\"Molodensky\",ID[\"EPSG\",9604]]") !=
+ std::string::npos)
+ << wkt;
+
+ auto inv_transf = transf->inverse();
+ auto inv_transf_as_transf =
+ nn_dynamic_pointer_cast<Transformation>(inv_transf);
+ ASSERT_TRUE(inv_transf_as_transf != nullptr);
+
+ EXPECT_EQ(transf->sourceCRS()->nameStr(),
+ inv_transf_as_transf->targetCRS()->nameStr());
+ EXPECT_EQ(transf->targetCRS()->nameStr(),
+ inv_transf_as_transf->sourceCRS()->nameStr());
+
+ auto projString = inv_transf_as_transf->exportToPROJString(
+ PROJStringFormatter::create().get());
+ EXPECT_EQ(projString, "+proj=pipeline +step +proj=axisswap +order=2,1 "
+ "+step +proj=unitconvert +xy_in=deg +xy_out=rad "
+ "+step +proj=molodensky +ellps=GRS80 +dx=-1 +dy=-2 "
+ "+dz=-3 +da=-4 +df=-5 +step +proj=unitconvert "
+ "+xy_in=rad +xy_out=deg +step +proj=axisswap "
+ "+order=2,1");
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(operation, transformation_createAbridgedMolodensky) {
+
+ auto transf = Transformation::createAbridgedMolodensky(
+ PropertyMap(), GeographicCRS::EPSG_4326, GeographicCRS::EPSG_4269, 1.0,
+ 2.0, 3.0, 4.0, 5.0, std::vector<PositionalAccuracyNNPtr>());
+
+ auto wkt = transf->exportToWKT(WKTFormatter::create().get());
+ EXPECT_TRUE(replaceAll(replaceAll(wkt, " ", ""), "\n", "")
+ .find(replaceAll(
+ "METHOD[\"Abridged Molodensky\",ID[\"EPSG\",9605]]",
+ " ", "")) != std::string::npos)
+ << wkt;
+
+ auto inv_transf = transf->inverse();
+ auto inv_transf_as_transf =
+ nn_dynamic_pointer_cast<Transformation>(inv_transf);
+ ASSERT_TRUE(inv_transf_as_transf != nullptr);
+
+ EXPECT_EQ(transf->sourceCRS()->nameStr(),
+ inv_transf_as_transf->targetCRS()->nameStr());
+ EXPECT_EQ(transf->targetCRS()->nameStr(),
+ inv_transf_as_transf->sourceCRS()->nameStr());
+
+ auto projString = inv_transf_as_transf->exportToPROJString(
+ PROJStringFormatter::create().get());
+ EXPECT_EQ(projString, "+proj=pipeline +step +proj=axisswap +order=2,1 "
+ "+step +proj=unitconvert +xy_in=deg +xy_out=rad "
+ "+step +proj=molodensky +ellps=GRS80 +dx=-1 +dy=-2 "
+ "+dz=-3 +da=-4 +df=-5 +abridged +step "
+ "+proj=unitconvert +xy_in=rad +xy_out=deg +step "
+ "+proj=axisswap +order=2,1");
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(operation, transformation_inverse) {
+
+ auto transf = Transformation::create(
+ PropertyMap()
+ .set(IdentifiedObject::NAME_KEY, "my transformation")
+ .set(Identifier::CODESPACE_KEY, "my codeSpace")
+ .set(Identifier::CODE_KEY, "my code"),
+ GeographicCRS::EPSG_4326, GeographicCRS::EPSG_4269, nullptr,
+ PropertyMap()
+ .set(IdentifiedObject::NAME_KEY, "my operation")
+ .set(Identifier::CODESPACE_KEY, "my codeSpace")
+ .set(Identifier::CODE_KEY, "my code"),
+ std::vector<OperationParameterNNPtr>{OperationParameter::create(
+ PropertyMap().set(IdentifiedObject::NAME_KEY, "paramName"))},
+ std::vector<ParameterValueNNPtr>{
+ ParameterValue::createFilename("foo.bin")},
+ std::vector<PositionalAccuracyNNPtr>{
+ PositionalAccuracy::create("0.1")});
+ auto inv = transf->inverse();
+ EXPECT_EQ(inv->inverse(), transf);
+ EXPECT_EQ(
+ inv->exportToWKT(WKTFormatter::create().get()),
+ "COORDINATEOPERATION[\"Inverse of my transformation\",\n"
+ " SOURCECRS[\n"
+ " GEODCRS[\"NAD83\",\n"
+ " DATUM[\"North American Datum 1983\",\n"
+ " ELLIPSOID[\"GRS 1980\",6378137,298.257222101,\n"
+ " LENGTHUNIT[\"metre\",1]]],\n"
+ " PRIMEM[\"Greenwich\",0,\n"
+ " ANGLEUNIT[\"degree\",0.0174532925199433]],\n"
+ " CS[ellipsoidal,2],\n"
+ " AXIS[\"latitude\",north,\n"
+ " ORDER[1],\n"
+ " ANGLEUNIT[\"degree\",0.0174532925199433]],\n"
+ " AXIS[\"longitude\",east,\n"
+ " ORDER[2],\n"
+ " ANGLEUNIT[\"degree\",0.0174532925199433]]]],\n"
+ " TARGETCRS[\n"
+ " GEODCRS[\"WGS 84\",\n"
+ " DATUM[\"World Geodetic System 1984\",\n"
+ " ELLIPSOID[\"WGS 84\",6378137,298.257223563,\n"
+ " LENGTHUNIT[\"metre\",1]]],\n"
+ " PRIMEM[\"Greenwich\",0,\n"
+ " ANGLEUNIT[\"degree\",0.0174532925199433]],\n"
+ " CS[ellipsoidal,2],\n"
+ " AXIS[\"latitude\",north,\n"
+ " ORDER[1],\n"
+ " ANGLEUNIT[\"degree\",0.0174532925199433]],\n"
+ " AXIS[\"longitude\",east,\n"
+ " ORDER[2],\n"
+ " ANGLEUNIT[\"degree\",0.0174532925199433]]]],\n"
+ " METHOD[\"Inverse of my operation\",\n"
+ " ID[\"INVERSE(my codeSpace)\",\"my code\"]],\n"
+ " PARAMETERFILE[\"paramName\",\"foo.bin\"],\n"
+ " OPERATIONACCURACY[0.1],\n"
+ " ID[\"INVERSE(my codeSpace)\",\"my code\"]]");
+
+ EXPECT_THROW(inv->exportToPROJString(PROJStringFormatter::create().get()),
+ FormattingException);
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(operation, transformation_createTOWGS84) {
+
+ EXPECT_THROW(Transformation::createTOWGS84(GeographicCRS::EPSG_4326,
+ std::vector<double>()),
+ InvalidOperation);
+
+ auto crsIn = CompoundCRS::create(PropertyMap(), std::vector<CRSNNPtr>{});
+ EXPECT_THROW(
+ Transformation::createTOWGS84(crsIn, std::vector<double>(7, 0)),
+ InvalidOperation);
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(operation, utm_export) {
+ auto conv = Conversion::createUTM(PropertyMap(), 1, false);
+ EXPECT_EQ(conv->exportToPROJString(PROJStringFormatter::create().get()),
+ "+proj=utm +zone=1 +south");
+
+ EXPECT_EQ(conv->exportToWKT(WKTFormatter::create().get()),
+ "CONVERSION[\"UTM zone 1S\",\n"
+ " METHOD[\"Transverse Mercator\",\n"
+ " ID[\"EPSG\",9807]],\n"
+ " PARAMETER[\"Latitude of natural origin\",0,\n"
+ " ANGLEUNIT[\"degree\",0.0174532925199433],\n"
+ " ID[\"EPSG\",8801]],\n"
+ " PARAMETER[\"Longitude of natural origin\",-177,\n"
+ " ANGLEUNIT[\"degree\",0.0174532925199433],\n"
+ " ID[\"EPSG\",8802]],\n"
+ " PARAMETER[\"Scale factor at natural origin\",0.9996,\n"
+ " SCALEUNIT[\"unity\",1],\n"
+ " ID[\"EPSG\",8805]],\n"
+ " PARAMETER[\"False easting\",500000,\n"
+ " LENGTHUNIT[\"metre\",1],\n"
+ " ID[\"EPSG\",8806]],\n"
+ " PARAMETER[\"False northing\",10000000,\n"
+ " LENGTHUNIT[\"metre\",1],\n"
+ " ID[\"EPSG\",8807]],\n"
+ " ID[\"EPSG\",17001]]");
+
+ EXPECT_EQ(
+ conv->exportToWKT(
+ WKTFormatter::create(WKTFormatter::Convention::WKT1_GDAL).get()),
+ "PROJECTION[\"Transverse_Mercator\"],\n"
+ "PARAMETER[\"latitude_of_origin\",0],\n"
+ "PARAMETER[\"central_meridian\",-177],\n"
+ "PARAMETER[\"scale_factor\",0.9996],\n"
+ "PARAMETER[\"false_easting\",500000],\n"
+ "PARAMETER[\"false_northing\",10000000]");
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(operation, tmerc_export) {
+ auto conv = Conversion::createTransverseMercator(
+ PropertyMap(), Angle(1), Angle(2), Scale(3), Length(4), Length(5));
+ EXPECT_EQ(conv->exportToPROJString(PROJStringFormatter::create().get()),
+ "+proj=tmerc +lat_0=1 +lon_0=2 +k_0=3 +x_0=4 +y_0=5");
+
+ {
+ auto formatter = PROJStringFormatter::create();
+ formatter->setUseETMercForTMerc(true);
+ EXPECT_EQ(conv->exportToPROJString(formatter.get()),
+ "+proj=etmerc +lat_0=1 +lon_0=2 +k_0=3 +x_0=4 +y_0=5");
+ }
+
+ EXPECT_EQ(conv->exportToWKT(WKTFormatter::create().get()),
+ "CONVERSION[\"Transverse Mercator\",\n"
+ " METHOD[\"Transverse Mercator\",\n"
+ " ID[\"EPSG\",9807]],\n"
+ " PARAMETER[\"Latitude of natural origin\",1,\n"
+ " ANGLEUNIT[\"degree\",0.0174532925199433],\n"
+ " ID[\"EPSG\",8801]],\n"
+ " PARAMETER[\"Longitude of natural origin\",2,\n"
+ " ANGLEUNIT[\"degree\",0.0174532925199433],\n"
+ " ID[\"EPSG\",8802]],\n"
+ " PARAMETER[\"Scale factor at natural origin\",3,\n"
+ " SCALEUNIT[\"unity\",1],\n"
+ " ID[\"EPSG\",8805]],\n"
+ " PARAMETER[\"False easting\",4,\n"
+ " LENGTHUNIT[\"metre\",1],\n"
+ " ID[\"EPSG\",8806]],\n"
+ " PARAMETER[\"False northing\",5,\n"
+ " LENGTHUNIT[\"metre\",1],\n"
+ " ID[\"EPSG\",8807]]]");
+
+ EXPECT_EQ(
+ conv->exportToWKT(
+ WKTFormatter::create(WKTFormatter::Convention::WKT1_GDAL).get()),
+ "PROJECTION[\"Transverse_Mercator\"],\n"
+ "PARAMETER[\"latitude_of_origin\",1],\n"
+ "PARAMETER[\"central_meridian\",2],\n"
+ "PARAMETER[\"scale_factor\",3],\n"
+ "PARAMETER[\"false_easting\",4],\n"
+ "PARAMETER[\"false_northing\",5]");
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(operation, gstmerc_export) {
+ auto conv = Conversion::createGaussSchreiberTransverseMercator(
+ PropertyMap(), Angle(1), Angle(2), Scale(3), Length(4), Length(5));
+ EXPECT_EQ(conv->exportToPROJString(PROJStringFormatter::create().get()),
+ "+proj=gstmerc +lat_0=1 +lon_0=2 +k_0=3 +x_0=4 +y_0=5");
+
+ EXPECT_EQ(conv->exportToWKT(WKTFormatter::create().get()),
+ "CONVERSION[\"Gauss Schreiber Transverse Mercator\",\n"
+ " METHOD[\"Gauss Schreiber Transverse Mercator\"],\n"
+ " PARAMETER[\"Latitude of natural origin\",1,\n"
+ " ANGLEUNIT[\"degree\",0.0174532925199433],\n"
+ " ID[\"EPSG\",8801]],\n"
+ " PARAMETER[\"Longitude of natural origin\",2,\n"
+ " ANGLEUNIT[\"degree\",0.0174532925199433],\n"
+ " ID[\"EPSG\",8802]],\n"
+ " PARAMETER[\"Scale factor at natural origin\",3,\n"
+ " SCALEUNIT[\"unity\",1],\n"
+ " ID[\"EPSG\",8805]],\n"
+ " PARAMETER[\"False easting\",4,\n"
+ " LENGTHUNIT[\"metre\",1],\n"
+ " ID[\"EPSG\",8806]],\n"
+ " PARAMETER[\"False northing\",5,\n"
+ " LENGTHUNIT[\"metre\",1],\n"
+ " ID[\"EPSG\",8807]]]");
+
+ EXPECT_EQ(
+ conv->exportToWKT(
+ WKTFormatter::create(WKTFormatter::Convention::WKT1_GDAL).get()),
+ "PROJECTION[\"Gauss_Schreiber_Transverse_Mercator\"],\n"
+ "PARAMETER[\"latitude_of_origin\",1],\n"
+ "PARAMETER[\"central_meridian\",2],\n"
+ "PARAMETER[\"scale_factor\",3],\n"
+ "PARAMETER[\"false_easting\",4],\n"
+ "PARAMETER[\"false_northing\",5]");
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(operation, tmerc_south_oriented_export) {
+ auto conv = Conversion::createTransverseMercatorSouthOriented(
+ PropertyMap(), Angle(1), Angle(2), Scale(3), Length(4), Length(5));
+
+ EXPECT_EQ(conv->exportToPROJString(PROJStringFormatter::create().get()),
+ "+proj=tmerc +axis=wsu +lat_0=1 +lon_0=2 +k_0=3 +x_0=4 +y_0=5");
+
+ EXPECT_EQ(conv->exportToWKT(WKTFormatter::create().get()),
+ "CONVERSION[\"Transverse Mercator (South Orientated)\",\n"
+ " METHOD[\"Transverse Mercator (South Orientated)\",\n"
+ " ID[\"EPSG\",9808]],\n"
+ " PARAMETER[\"Latitude of natural origin\",1,\n"
+ " ANGLEUNIT[\"degree\",0.0174532925199433],\n"
+ " ID[\"EPSG\",8801]],\n"
+ " PARAMETER[\"Longitude of natural origin\",2,\n"
+ " ANGLEUNIT[\"degree\",0.0174532925199433],\n"
+ " ID[\"EPSG\",8802]],\n"
+ " PARAMETER[\"Scale factor at natural origin\",3,\n"
+ " SCALEUNIT[\"unity\",1],\n"
+ " ID[\"EPSG\",8805]],\n"
+ " PARAMETER[\"False easting\",4,\n"
+ " LENGTHUNIT[\"metre\",1],\n"
+ " ID[\"EPSG\",8806]],\n"
+ " PARAMETER[\"False northing\",5,\n"
+ " LENGTHUNIT[\"metre\",1],\n"
+ " ID[\"EPSG\",8807]]]");
+
+ EXPECT_EQ(
+ conv->exportToWKT(
+ WKTFormatter::create(WKTFormatter::Convention::WKT1_GDAL).get()),
+ "PROJECTION[\"Transverse_Mercator_South_Orientated\"],\n"
+ "PARAMETER[\"latitude_of_origin\",1],\n"
+ "PARAMETER[\"central_meridian\",2],\n"
+ "PARAMETER[\"scale_factor\",3],\n"
+ "PARAMETER[\"false_easting\",4],\n"
+ "PARAMETER[\"false_northing\",5]");
+
+ auto wkt = "PROJCRS[\"Hartebeesthoek94 / Lo29\","
+ " BASEGEODCRS[\"Hartebeesthoek94\","
+ " DATUM[\"Hartebeesthoek94\","
+ " ELLIPSOID[\"WGS "
+ "84\",6378137,298.257223563,LENGTHUNIT[\"metre\",1.0]]]],"
+ " CONVERSION[\"South African Survey Grid zone 29\","
+ " METHOD[\"Transverse Mercator (South "
+ "Orientated)\",ID[\"EPSG\",9808]],"
+ " PARAMETER[\"Latitude of natural "
+ "origin\",0,ANGLEUNIT[\"degree\",0.01745329252]],"
+ " PARAMETER[\"Longitude of natural "
+ "origin\",29,ANGLEUNIT[\"degree\",0.01745329252]],"
+ " PARAMETER[\"Scale factor at natural "
+ "origin\",1,SCALEUNIT[\"unity\",1.0]],"
+ " PARAMETER[\"False easting\",0,LENGTHUNIT[\"metre\",1.0]],"
+ " PARAMETER[\"False northing\",0,LENGTHUNIT[\"metre\",1.0]]],"
+ " CS[cartesian,2],"
+ " AXIS[\"westing (Y)\",west,ORDER[1]],"
+ " AXIS[\"southing (X)\",south,ORDER[2]],"
+ " LENGTHUNIT[\"metre\",1.0],"
+ " ID[\"EPSG\",2053]]";
+ auto obj = WKTParser().createFromWKT(wkt);
+ auto crs = nn_dynamic_pointer_cast<ProjectedCRS>(obj);
+ ASSERT_TRUE(crs != nullptr);
+ EXPECT_EQ(crs->exportToPROJString(PROJStringFormatter::create().get()),
+ "+proj=pipeline +step +proj=axisswap +order=2,1 +step "
+ "+proj=unitconvert +xy_in=deg +xy_out=rad +step +proj=tmerc "
+ "+axis=wsu +lat_0=0 +lon_0=29 +k_0=1 +x_0=0 +y_0=0 +ellps=WGS84");
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(operation, tped_export) {
+ auto conv = Conversion::createTwoPointEquidistant(
+ PropertyMap(), Angle(1), Angle(2), Angle(3), Angle(4), Length(5),
+ Length(6));
+ EXPECT_EQ(conv->exportToPROJString(PROJStringFormatter::create().get()),
+ "+proj=tpeqd +lat_1=1 +lon_1=2 +lat_2=3 +lon_2=4 +x_0=5 +y_0=6");
+
+ auto formatter = WKTFormatter::create();
+ formatter->simulCurNodeHasId();
+ EXPECT_EQ(conv->exportToWKT(formatter.get()),
+ "CONVERSION[\"Two Point Equidistant\",\n"
+ " METHOD[\"Two Point Equidistant\"],\n"
+ " PARAMETER[\"Latitude of 1st point\",1,\n"
+ " ANGLEUNIT[\"degree\",0.0174532925199433]],\n"
+ " PARAMETER[\"Longitude of 1st point\",2,\n"
+ " ANGLEUNIT[\"degree\",0.0174532925199433]],\n"
+ " PARAMETER[\"Latitude of 2nd point\",3,\n"
+ " ANGLEUNIT[\"degree\",0.0174532925199433]],\n"
+ " PARAMETER[\"Longitude of 2nd point\",4,\n"
+ " ANGLEUNIT[\"degree\",0.0174532925199433]],\n"
+ " PARAMETER[\"False easting\",5,\n"
+ " LENGTHUNIT[\"metre\",1],\n"
+ " ID[\"EPSG\",8806]],\n"
+ " PARAMETER[\"False northing\",6,\n"
+ " LENGTHUNIT[\"metre\",1],\n"
+ " ID[\"EPSG\",8807]]]");
+
+ EXPECT_EQ(
+ conv->exportToWKT(
+ WKTFormatter::create(WKTFormatter::Convention::WKT1_GDAL).get()),
+ "PROJECTION[\"Two_Point_Equidistant\"],\n"
+ "PARAMETER[\"Latitude_Of_1st_Point\",1],\n"
+ "PARAMETER[\"Longitude_Of_1st_Point\",2],\n"
+ "PARAMETER[\"Latitude_Of_2nd_Point\",3],\n"
+ "PARAMETER[\"Longitude_Of_2nd_Point\",4],\n"
+ "PARAMETER[\"false_easting\",5],\n"
+ "PARAMETER[\"false_northing\",6]");
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(operation, tmg_export) {
+ auto conv = Conversion::createTunisiaMappingGrid(
+ PropertyMap(), Angle(1), Angle(2), Length(3), Length(4));
+ EXPECT_THROW(conv->exportToPROJString(PROJStringFormatter::create().get()),
+ FormattingException);
+
+ EXPECT_EQ(conv->exportToWKT(WKTFormatter::create().get()),
+ "CONVERSION[\"Tunisia Mapping Grid\",\n"
+ " METHOD[\"Tunisia Mapping Grid\",\n"
+ " ID[\"EPSG\",9816]],\n"
+ " PARAMETER[\"Latitude of false origin\",1,\n"
+ " ANGLEUNIT[\"degree\",0.0174532925199433],\n"
+ " ID[\"EPSG\",8821]],\n"
+ " PARAMETER[\"Longitude of false origin\",2,\n"
+ " ANGLEUNIT[\"degree\",0.0174532925199433],\n"
+ " ID[\"EPSG\",8822]],\n"
+ " PARAMETER[\"Easting at false origin\",3,\n"
+ " LENGTHUNIT[\"metre\",1],\n"
+ " ID[\"EPSG\",8826]],\n"
+ " PARAMETER[\"Northing at false origin\",4,\n"
+ " LENGTHUNIT[\"metre\",1],\n"
+ " ID[\"EPSG\",8827]]]");
+
+ EXPECT_EQ(
+ conv->exportToWKT(
+ WKTFormatter::create(WKTFormatter::Convention::WKT1_GDAL).get()),
+ "PROJECTION[\"Tunisia_Mapping_Grid\"],\n"
+ "PARAMETER[\"latitude_of_origin\",1],\n"
+ "PARAMETER[\"central_meridian\",2],\n"
+ "PARAMETER[\"false_easting\",3],\n"
+ "PARAMETER[\"false_northing\",4]");
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(operation, aea_export) {
+ auto conv = Conversion::createAlbersEqualArea(PropertyMap(), Angle(1),
+ Angle(2), Angle(3), Angle(4),
+ Length(5), Length(6));
+
+ EXPECT_EQ(conv->exportToPROJString(PROJStringFormatter::create().get()),
+ "+proj=aea +lat_0=1 +lon_0=2 +lat_1=3 +lat_2=4 +x_0=5 +y_0=6");
+
+ EXPECT_EQ(conv->exportToWKT(WKTFormatter::create().get()),
+ "CONVERSION[\"Albers Equal Area\",\n"
+ " METHOD[\"Albers Equal Area\",\n"
+ " ID[\"EPSG\",9822]],\n"
+ " PARAMETER[\"Latitude of false origin\",1,\n"
+ " ANGLEUNIT[\"degree\",0.0174532925199433],\n"
+ " ID[\"EPSG\",8821]],\n"
+ " PARAMETER[\"Longitude of false origin\",2,\n"
+ " ANGLEUNIT[\"degree\",0.0174532925199433],\n"
+ " ID[\"EPSG\",8822]],\n"
+ " PARAMETER[\"Latitude of 1st standard parallel\",3,\n"
+ " ANGLEUNIT[\"degree\",0.0174532925199433],\n"
+ " ID[\"EPSG\",8823]],\n"
+ " PARAMETER[\"Latitude of 2nd standard parallel\",4,\n"
+ " ANGLEUNIT[\"degree\",0.0174532925199433],\n"
+ " ID[\"EPSG\",8824]],\n"
+ " PARAMETER[\"Easting at false origin\",5,\n"
+ " LENGTHUNIT[\"metre\",1],\n"
+ " ID[\"EPSG\",8826]],\n"
+ " PARAMETER[\"Northing at false origin\",6,\n"
+ " LENGTHUNIT[\"metre\",1],\n"
+ " ID[\"EPSG\",8827]]]");
+
+ EXPECT_EQ(
+ conv->exportToWKT(
+ WKTFormatter::create(WKTFormatter::Convention::WKT1_GDAL).get()),
+ "PROJECTION[\"Albers_Conic_Equal_Area\"],\n"
+ "PARAMETER[\"latitude_of_center\",1],\n"
+ "PARAMETER[\"longitude_of_center\",2],\n"
+ "PARAMETER[\"standard_parallel_1\",3],\n"
+ "PARAMETER[\"standard_parallel_2\",4],\n"
+ "PARAMETER[\"false_easting\",5],\n"
+ "PARAMETER[\"false_northing\",6]");
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(operation, azimuthal_equidistant_export) {
+ auto conv = Conversion::createAzimuthalEquidistant(
+ PropertyMap(), Angle(1), Angle(2), Length(3), Length(4));
+
+ EXPECT_EQ(conv->exportToPROJString(PROJStringFormatter::create().get()),
+ "+proj=aeqd +lat_0=1 +lon_0=2 +x_0=3 +y_0=4");
+
+ EXPECT_EQ(conv->exportToWKT(WKTFormatter::create().get()),
+ "CONVERSION[\"Modified Azimuthal Equidistant\",\n"
+ " METHOD[\"Modified Azimuthal Equidistant\",\n"
+ " ID[\"EPSG\",9832]],\n"
+ " PARAMETER[\"Latitude of natural origin\",1,\n"
+ " ANGLEUNIT[\"degree\",0.0174532925199433],\n"
+ " ID[\"EPSG\",8801]],\n"
+ " PARAMETER[\"Longitude of natural origin\",2,\n"
+ " ANGLEUNIT[\"degree\",0.0174532925199433],\n"
+ " ID[\"EPSG\",8802]],\n"
+ " PARAMETER[\"False easting\",3,\n"
+ " LENGTHUNIT[\"metre\",1],\n"
+ " ID[\"EPSG\",8806]],\n"
+ " PARAMETER[\"False northing\",4,\n"
+ " LENGTHUNIT[\"metre\",1],\n"
+ " ID[\"EPSG\",8807]]]");
+
+ EXPECT_EQ(
+ conv->exportToWKT(
+ WKTFormatter::create(WKTFormatter::Convention::WKT1_GDAL).get()),
+ "PROJECTION[\"Azimuthal_Equidistant\"],\n"
+ "PARAMETER[\"latitude_of_center\",1],\n"
+ "PARAMETER[\"longitude_of_center\",2],\n"
+ "PARAMETER[\"false_easting\",3],\n"
+ "PARAMETER[\"false_northing\",4]");
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(operation, guam_projection_export) {
+ auto conv = Conversion::createGuamProjection(
+ PropertyMap(), Angle(1), Angle(2), Length(3), Length(4));
+
+ EXPECT_EQ(conv->exportToPROJString(PROJStringFormatter::create().get()),
+ "+proj=aeqd +guam +lat_0=1 +lon_0=2 +x_0=3 +y_0=4");
+
+ EXPECT_EQ(conv->exportToWKT(WKTFormatter::create().get()),
+ "CONVERSION[\"Guam Projection\",\n"
+ " METHOD[\"Guam Projection\",\n"
+ " ID[\"EPSG\",9831]],\n"
+ " PARAMETER[\"Latitude of natural origin\",1,\n"
+ " ANGLEUNIT[\"degree\",0.0174532925199433],\n"
+ " ID[\"EPSG\",8801]],\n"
+ " PARAMETER[\"Longitude of natural origin\",2,\n"
+ " ANGLEUNIT[\"degree\",0.0174532925199433],\n"
+ " ID[\"EPSG\",8802]],\n"
+ " PARAMETER[\"False easting\",3,\n"
+ " LENGTHUNIT[\"metre\",1],\n"
+ " ID[\"EPSG\",8806]],\n"
+ " PARAMETER[\"False northing\",4,\n"
+ " LENGTHUNIT[\"metre\",1],\n"
+ " ID[\"EPSG\",8807]]]");
+
+ EXPECT_THROW(
+ conv->exportToWKT(
+ WKTFormatter::create(WKTFormatter::Convention::WKT1_GDAL).get()),
+ FormattingException);
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(operation, bonne_export) {
+ auto conv = Conversion::createBonne(PropertyMap(), Angle(1), Angle(2),
+ Length(3), Length(4));
+
+ EXPECT_EQ(conv->exportToPROJString(PROJStringFormatter::create().get()),
+ "+proj=bonne +lat_1=1 +lon_0=2 +x_0=3 +y_0=4");
+
+ EXPECT_EQ(conv->exportToWKT(WKTFormatter::create().get()),
+ "CONVERSION[\"Bonne\",\n"
+ " METHOD[\"Bonne\",\n"
+ " ID[\"EPSG\",9827]],\n"
+ " PARAMETER[\"Latitude of natural origin\",1,\n"
+ " ANGLEUNIT[\"degree\",0.0174532925199433],\n"
+ " ID[\"EPSG\",8801]],\n"
+ " PARAMETER[\"Longitude of natural origin\",2,\n"
+ " ANGLEUNIT[\"degree\",0.0174532925199433],\n"
+ " ID[\"EPSG\",8802]],\n"
+ " PARAMETER[\"False easting\",3,\n"
+ " LENGTHUNIT[\"metre\",1],\n"
+ " ID[\"EPSG\",8806]],\n"
+ " PARAMETER[\"False northing\",4,\n"
+ " LENGTHUNIT[\"metre\",1],\n"
+ " ID[\"EPSG\",8807]]]");
+
+ EXPECT_EQ(
+ conv->exportToWKT(
+ WKTFormatter::create(WKTFormatter::Convention::WKT1_GDAL).get()),
+ "PROJECTION[\"Bonne\"],\n"
+ "PARAMETER[\"standard_parallel_1\",1],\n"
+ "PARAMETER[\"central_meridian\",2],\n"
+ "PARAMETER[\"false_easting\",3],\n"
+ "PARAMETER[\"false_northing\",4]");
+
+ auto obj = WKTParser().createFromWKT(
+ "PROJCS[\"unnamed\","
+ "GEOGCS[\"unnamed ellipse\","
+ " DATUM[\"unknown\","
+ " SPHEROID[\"unnamed\",6378137,298.257223563]],"
+ " PRIMEM[\"Greenwich\",0],"
+ " UNIT[\"degree\",0.0174532925199433]],"
+ "PROJECTION[\"Bonne\"],"
+ "PARAMETER[\"standard_parallel_1\",1],"
+ "PARAMETER[\"central_meridian\",2],"
+ "PARAMETER[\"false_easting\",3],"
+ "PARAMETER[\"false_northing\",4],"
+ "UNIT[\"metre\",1]]");
+ auto crs = nn_dynamic_pointer_cast<ProjectedCRS>(obj);
+ ASSERT_TRUE(crs != nullptr);
+ EXPECT_EQ(crs->exportToPROJString(PROJStringFormatter::create().get()),
+ "+proj=pipeline +step +proj=unitconvert +xy_in=deg +xy_out=rad "
+ "+step +proj=bonne +lat_1=1 +lon_0=2 +x_0=3 +y_0=4 +ellps=WGS84");
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(operation, lambert_cylindrical_equal_area_spherical_export) {
+ auto conv = Conversion::createLambertCylindricalEqualAreaSpherical(
+ PropertyMap(), Angle(1), Angle(2), Length(3), Length(4));
+
+ EXPECT_EQ(conv->exportToPROJString(PROJStringFormatter::create().get()),
+ "+proj=cea +lat_ts=1 +lon_0=2 +x_0=3 +y_0=4");
+
+ EXPECT_EQ(conv->exportToWKT(WKTFormatter::create().get()),
+ "CONVERSION[\"Lambert Cylindrical Equal Area (Spherical)\",\n"
+ " METHOD[\"Lambert Cylindrical Equal Area (Spherical)\",\n"
+ " ID[\"EPSG\",9834]],\n"
+ " PARAMETER[\"Latitude of 1st standard parallel\",1,\n"
+ " ANGLEUNIT[\"degree\",0.0174532925199433],\n"
+ " ID[\"EPSG\",8823]],\n"
+ " PARAMETER[\"Longitude of natural origin\",2,\n"
+ " ANGLEUNIT[\"degree\",0.0174532925199433],\n"
+ " ID[\"EPSG\",8802]],\n"
+ " PARAMETER[\"False easting\",3,\n"
+ " LENGTHUNIT[\"metre\",1],\n"
+ " ID[\"EPSG\",8806]],\n"
+ " PARAMETER[\"False northing\",4,\n"
+ " LENGTHUNIT[\"metre\",1],\n"
+ " ID[\"EPSG\",8807]]]");
+
+ EXPECT_EQ(
+ conv->exportToWKT(
+ WKTFormatter::create(WKTFormatter::Convention::WKT1_GDAL).get()),
+ "PROJECTION[\"Cylindrical_Equal_Area\"],\n"
+ "PARAMETER[\"standard_parallel_1\",1],\n"
+ "PARAMETER[\"central_meridian\",2],\n"
+ "PARAMETER[\"false_easting\",3],\n"
+ "PARAMETER[\"false_northing\",4]");
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(operation, lambert_cylindrical_equal_area_export) {
+ auto conv = Conversion::createLambertCylindricalEqualArea(
+ PropertyMap(), Angle(1), Angle(2), Length(3), Length(4));
+
+ EXPECT_EQ(conv->exportToPROJString(PROJStringFormatter::create().get()),
+ "+proj=cea +lat_ts=1 +lon_0=2 +x_0=3 +y_0=4");
+
+ EXPECT_EQ(conv->exportToWKT(WKTFormatter::create().get()),
+ "CONVERSION[\"Lambert Cylindrical Equal Area\",\n"
+ " METHOD[\"Lambert Cylindrical Equal Area\",\n"
+ " ID[\"EPSG\",9835]],\n"
+ " PARAMETER[\"Latitude of 1st standard parallel\",1,\n"
+ " ANGLEUNIT[\"degree\",0.0174532925199433],\n"
+ " ID[\"EPSG\",8823]],\n"
+ " PARAMETER[\"Longitude of natural origin\",2,\n"
+ " ANGLEUNIT[\"degree\",0.0174532925199433],\n"
+ " ID[\"EPSG\",8802]],\n"
+ " PARAMETER[\"False easting\",3,\n"
+ " LENGTHUNIT[\"metre\",1],\n"
+ " ID[\"EPSG\",8806]],\n"
+ " PARAMETER[\"False northing\",4,\n"
+ " LENGTHUNIT[\"metre\",1],\n"
+ " ID[\"EPSG\",8807]]]");
+
+ EXPECT_EQ(
+ conv->exportToWKT(
+ WKTFormatter::create(WKTFormatter::Convention::WKT1_GDAL).get()),
+ "PROJECTION[\"Cylindrical_Equal_Area\"],\n"
+ "PARAMETER[\"standard_parallel_1\",1],\n"
+ "PARAMETER[\"central_meridian\",2],\n"
+ "PARAMETER[\"false_easting\",3],\n"
+ "PARAMETER[\"false_northing\",4]");
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(operation, lcc1sp_export) {
+ auto conv = Conversion::createLambertConicConformal_1SP(
+ PropertyMap(), Angle(1), Angle(2), Scale(3), Length(4), Length(5));
+ EXPECT_EQ(conv->exportToPROJString(PROJStringFormatter::create().get()),
+ "+proj=lcc +lat_1=1 +lat_0=1 +lon_0=2 +k_0=3 +x_0=4 +y_0=5");
+
+ EXPECT_EQ(conv->exportToWKT(WKTFormatter::create().get()),
+ "CONVERSION[\"Lambert Conic Conformal (1SP)\",\n"
+ " METHOD[\"Lambert Conic Conformal (1SP)\",\n"
+ " ID[\"EPSG\",9801]],\n"
+ " PARAMETER[\"Latitude of natural origin\",1,\n"
+ " ANGLEUNIT[\"degree\",0.0174532925199433],\n"
+ " ID[\"EPSG\",8801]],\n"
+ " PARAMETER[\"Longitude of natural origin\",2,\n"
+ " ANGLEUNIT[\"degree\",0.0174532925199433],\n"
+ " ID[\"EPSG\",8802]],\n"
+ " PARAMETER[\"Scale factor at natural origin\",3,\n"
+ " SCALEUNIT[\"unity\",1],\n"
+ " ID[\"EPSG\",8805]],\n"
+ " PARAMETER[\"False easting\",4,\n"
+ " LENGTHUNIT[\"metre\",1],\n"
+ " ID[\"EPSG\",8806]],\n"
+ " PARAMETER[\"False northing\",5,\n"
+ " LENGTHUNIT[\"metre\",1],\n"
+ " ID[\"EPSG\",8807]]]");
+
+ EXPECT_EQ(
+ conv->exportToWKT(
+ WKTFormatter::create(WKTFormatter::Convention::WKT1_GDAL).get()),
+ "PROJECTION[\"Lambert_Conformal_Conic_1SP\"],\n"
+ "PARAMETER[\"latitude_of_origin\",1],\n"
+ "PARAMETER[\"central_meridian\",2],\n"
+ "PARAMETER[\"scale_factor\",3],\n"
+ "PARAMETER[\"false_easting\",4],\n"
+ "PARAMETER[\"false_northing\",5]");
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(operation, lcc2sp_export) {
+ auto conv = Conversion::createLambertConicConformal_2SP(
+ PropertyMap(), Angle(1), Angle(2), Angle(3), Angle(4), Length(5),
+ Length(6));
+ EXPECT_EQ(conv->exportToPROJString(PROJStringFormatter::create().get()),
+ "+proj=lcc +lat_0=1 +lon_0=2 +lat_1=3 +lat_2=4 +x_0=5 +y_0=6");
+
+ EXPECT_EQ(conv->exportToWKT(WKTFormatter::create().get()),
+ "CONVERSION[\"Lambert Conic Conformal (2SP)\",\n"
+ " METHOD[\"Lambert Conic Conformal (2SP)\",\n"
+ " ID[\"EPSG\",9802]],\n"
+ " PARAMETER[\"Latitude of false origin\",1,\n"
+ " ANGLEUNIT[\"degree\",0.0174532925199433],\n"
+ " ID[\"EPSG\",8821]],\n"
+ " PARAMETER[\"Longitude of false origin\",2,\n"
+ " ANGLEUNIT[\"degree\",0.0174532925199433],\n"
+ " ID[\"EPSG\",8822]],\n"
+ " PARAMETER[\"Latitude of 1st standard parallel\",3,\n"
+ " ANGLEUNIT[\"degree\",0.0174532925199433],\n"
+ " ID[\"EPSG\",8823]],\n"
+ " PARAMETER[\"Latitude of 2nd standard parallel\",4,\n"
+ " ANGLEUNIT[\"degree\",0.0174532925199433],\n"
+ " ID[\"EPSG\",8824]],\n"
+ " PARAMETER[\"Easting at false origin\",5,\n"
+ " LENGTHUNIT[\"metre\",1],\n"
+ " ID[\"EPSG\",8826]],\n"
+ " PARAMETER[\"Northing at false origin\",6,\n"
+ " LENGTHUNIT[\"metre\",1],\n"
+ " ID[\"EPSG\",8827]]]");
+
+ EXPECT_EQ(
+ conv->exportToWKT(
+ WKTFormatter::create(WKTFormatter::Convention::WKT1_GDAL).get()),
+ "PROJECTION[\"Lambert_Conformal_Conic_2SP\"],\n"
+ "PARAMETER[\"latitude_of_origin\",1],\n"
+ "PARAMETER[\"central_meridian\",2],\n"
+ "PARAMETER[\"standard_parallel_1\",3],\n"
+ "PARAMETER[\"standard_parallel_2\",4],\n"
+ "PARAMETER[\"false_easting\",5],\n"
+ "PARAMETER[\"false_northing\",6]");
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(operation, lcc2sp_isEquivalentTo_parallels_switched) {
+ auto conv1 = Conversion::createLambertConicConformal_2SP(
+ PropertyMap(), Angle(1), Angle(2), Angle(3), Angle(4), Length(5),
+ Length(6));
+ auto conv2 = Conversion::createLambertConicConformal_2SP(
+ PropertyMap(), Angle(1), Angle(2), Angle(4), Angle(3), Length(5),
+ Length(6));
+
+ EXPECT_TRUE(
+ conv1->isEquivalentTo(conv2.get(), IComparable::Criterion::EQUIVALENT));
+
+ auto conv3 = Conversion::createLambertConicConformal_2SP(
+ PropertyMap(), Angle(1), Angle(2), Angle(3), Angle(3), Length(5),
+ Length(6));
+
+ EXPECT_FALSE(
+ conv1->isEquivalentTo(conv3.get(), IComparable::Criterion::EQUIVALENT));
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(operation, lcc2sp_michigan_export) {
+ auto conv = Conversion::createLambertConicConformal_2SP_Michigan(
+ PropertyMap(), Angle(1), Angle(2), Angle(3), Angle(4), Length(5),
+ Length(6), Scale(7));
+ EXPECT_EQ(
+ conv->exportToPROJString(PROJStringFormatter::create().get()),
+ "+proj=lcc +lat_0=1 +lon_0=2 +lat_1=3 +lat_2=4 +x_0=5 +y_0=6 +k_0=7");
+
+ EXPECT_EQ(conv->exportToWKT(WKTFormatter::create().get()),
+ "CONVERSION[\"Lambert Conic Conformal (2SP Michigan)\",\n"
+ " METHOD[\"Lambert Conic Conformal (2SP Michigan)\",\n"
+ " ID[\"EPSG\",1051]],\n"
+ " PARAMETER[\"Latitude of false origin\",1,\n"
+ " ANGLEUNIT[\"degree\",0.0174532925199433],\n"
+ " ID[\"EPSG\",8821]],\n"
+ " PARAMETER[\"Longitude of false origin\",2,\n"
+ " ANGLEUNIT[\"degree\",0.0174532925199433],\n"
+ " ID[\"EPSG\",8822]],\n"
+ " PARAMETER[\"Latitude of 1st standard parallel\",3,\n"
+ " ANGLEUNIT[\"degree\",0.0174532925199433],\n"
+ " ID[\"EPSG\",8823]],\n"
+ " PARAMETER[\"Latitude of 2nd standard parallel\",4,\n"
+ " ANGLEUNIT[\"degree\",0.0174532925199433],\n"
+ " ID[\"EPSG\",8824]],\n"
+ " PARAMETER[\"Easting at false origin\",5,\n"
+ " LENGTHUNIT[\"metre\",1],\n"
+ " ID[\"EPSG\",8826]],\n"
+ " PARAMETER[\"Northing at false origin\",6,\n"
+ " LENGTHUNIT[\"metre\",1],\n"
+ " ID[\"EPSG\",8827]],\n"
+ " PARAMETER[\"Ellipsoid scaling factor\",7,\n"
+ " SCALEUNIT[\"unity\",1],\n"
+ " ID[\"EPSG\",1038]]]");
+
+ EXPECT_THROW(
+ conv->exportToWKT(
+ WKTFormatter::create(WKTFormatter::Convention::WKT1_GDAL).get()),
+ FormattingException);
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(operation, lcc2sp_belgium_export) {
+ auto conv = Conversion::createLambertConicConformal_2SP_Belgium(
+ PropertyMap(), Angle(1), Angle(2), Angle(3), Angle(4), Length(5),
+ Length(6));
+ EXPECT_EQ(conv->exportToPROJString(PROJStringFormatter::create().get()),
+ "+proj=lcc +lat_0=1 +lon_0=2 +lat_1=3 +lat_2=4 +x_0=5 +y_0=6");
+
+ EXPECT_EQ(conv->exportToWKT(WKTFormatter::create().get()),
+ "CONVERSION[\"Lambert Conic Conformal (2SP Belgium)\",\n"
+ " METHOD[\"Lambert Conic Conformal (2SP Belgium)\",\n"
+ " ID[\"EPSG\",9803]],\n"
+ " PARAMETER[\"Latitude of false origin\",1,\n"
+ " ANGLEUNIT[\"degree\",0.0174532925199433],\n"
+ " ID[\"EPSG\",8821]],\n"
+ " PARAMETER[\"Longitude of false origin\",2,\n"
+ " ANGLEUNIT[\"degree\",0.0174532925199433],\n"
+ " ID[\"EPSG\",8822]],\n"
+ " PARAMETER[\"Latitude of 1st standard parallel\",3,\n"
+ " ANGLEUNIT[\"degree\",0.0174532925199433],\n"
+ " ID[\"EPSG\",8823]],\n"
+ " PARAMETER[\"Latitude of 2nd standard parallel\",4,\n"
+ " ANGLEUNIT[\"degree\",0.0174532925199433],\n"
+ " ID[\"EPSG\",8824]],\n"
+ " PARAMETER[\"Easting at false origin\",5,\n"
+ " LENGTHUNIT[\"metre\",1],\n"
+ " ID[\"EPSG\",8826]],\n"
+ " PARAMETER[\"Northing at false origin\",6,\n"
+ " LENGTHUNIT[\"metre\",1],\n"
+ " ID[\"EPSG\",8827]]]");
+
+ EXPECT_EQ(
+ conv->exportToWKT(
+ WKTFormatter::create(WKTFormatter::Convention::WKT1_GDAL).get()),
+ "PROJECTION[\"Lambert_Conformal_Conic_2SP_Belgium\"],\n"
+ "PARAMETER[\"latitude_of_origin\",1],\n"
+ "PARAMETER[\"central_meridian\",2],\n"
+ "PARAMETER[\"standard_parallel_1\",3],\n"
+ "PARAMETER[\"standard_parallel_2\",4],\n"
+ "PARAMETER[\"false_easting\",5],\n"
+ "PARAMETER[\"false_northing\",6]");
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(operation, cassini_soldner_export) {
+ auto conv = Conversion::createCassiniSoldner(
+ PropertyMap(), Angle(1), Angle(2), Length(4), Length(5));
+
+ EXPECT_EQ(conv->exportToPROJString(PROJStringFormatter::create().get()),
+ "+proj=cass +lat_0=1 +lon_0=2 +x_0=4 +y_0=5");
+
+ EXPECT_EQ(conv->exportToWKT(WKTFormatter::create().get()),
+ "CONVERSION[\"Cassini-Soldner\",\n"
+ " METHOD[\"Cassini-Soldner\",\n"
+ " ID[\"EPSG\",9806]],\n"
+ " PARAMETER[\"Latitude of natural origin\",1,\n"
+ " ANGLEUNIT[\"degree\",0.0174532925199433],\n"
+ " ID[\"EPSG\",8801]],\n"
+ " PARAMETER[\"Longitude of natural origin\",2,\n"
+ " ANGLEUNIT[\"degree\",0.0174532925199433],\n"
+ " ID[\"EPSG\",8802]],\n"
+ " PARAMETER[\"False easting\",4,\n"
+ " LENGTHUNIT[\"metre\",1],\n"
+ " ID[\"EPSG\",8806]],\n"
+ " PARAMETER[\"False northing\",5,\n"
+ " LENGTHUNIT[\"metre\",1],\n"
+ " ID[\"EPSG\",8807]]]");
+
+ EXPECT_EQ(
+ conv->exportToWKT(
+ WKTFormatter::create(WKTFormatter::Convention::WKT1_GDAL).get()),
+ "PROJECTION[\"Cassini_Soldner\"],\n"
+ "PARAMETER[\"latitude_of_origin\",1],\n"
+ "PARAMETER[\"central_meridian\",2],\n"
+ "PARAMETER[\"false_easting\",4],\n"
+ "PARAMETER[\"false_northing\",5]");
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(operation, equidistant_conic_export) {
+ auto conv = Conversion::createEquidistantConic(PropertyMap(), Angle(1),
+ Angle(2), Angle(3), Angle(4),
+ Length(5), Length(6));
+
+ EXPECT_EQ(conv->exportToPROJString(PROJStringFormatter::create().get()),
+ "+proj=eqdc +lat_0=1 +lon_0=2 +lat_1=3 +lat_2=4 +x_0=5 +y_0=6");
+
+ EXPECT_EQ(conv->exportToWKT(WKTFormatter::create().get()),
+ "CONVERSION[\"Equidistant Conic\",\n"
+ " METHOD[\"Equidistant Conic\"],\n"
+ " PARAMETER[\"Latitude of natural origin\",1,\n"
+ " ANGLEUNIT[\"degree\",0.0174532925199433],\n"
+ " ID[\"EPSG\",8801]],\n"
+ " PARAMETER[\"Longitude of natural origin\",2,\n"
+ " ANGLEUNIT[\"degree\",0.0174532925199433],\n"
+ " ID[\"EPSG\",8802]],\n"
+ " PARAMETER[\"Latitude of 1st standard parallel\",3,\n"
+ " ANGLEUNIT[\"degree\",0.0174532925199433],\n"
+ " ID[\"EPSG\",8823]],\n"
+ " PARAMETER[\"Latitude of 2nd standard parallel\",4,\n"
+ " ANGLEUNIT[\"degree\",0.0174532925199433],\n"
+ " ID[\"EPSG\",8824]],\n"
+ " PARAMETER[\"False easting\",5,\n"
+ " LENGTHUNIT[\"metre\",1],\n"
+ " ID[\"EPSG\",8806]],\n"
+ " PARAMETER[\"False northing\",6,\n"
+ " LENGTHUNIT[\"metre\",1],\n"
+ " ID[\"EPSG\",8807]]]");
+
+ EXPECT_EQ(
+ conv->exportToWKT(
+ WKTFormatter::create(WKTFormatter::Convention::WKT1_GDAL).get()),
+ "PROJECTION[\"Equidistant_Conic\"],\n"
+ "PARAMETER[\"latitude_of_center\",1],\n"
+ "PARAMETER[\"longitude_of_center\",2],\n"
+ "PARAMETER[\"standard_parallel_1\",3],\n"
+ "PARAMETER[\"standard_parallel_2\",4],\n"
+ "PARAMETER[\"false_easting\",5],\n"
+ "PARAMETER[\"false_northing\",6]");
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(operation, eckert_export) {
+
+ std::vector<std::string> numbers{"", "1", "2", "3", "4", "5", "6"};
+ std::vector<std::string> latinNumbers{"", "I", "II", "III",
+ "IV", "V", "VI"};
+
+ for (int i = 1; i <= 6; i++) {
+ auto conv =
+ (i == 1)
+ ? Conversion::createEckertI(PropertyMap(), Angle(1), Length(2),
+ Length(3))
+ : (i == 2)
+ ? Conversion::createEckertII(PropertyMap(), Angle(1),
+ Length(2), Length(3))
+ : (i == 3)
+ ? Conversion::createEckertIII(
+ PropertyMap(), Angle(1), Length(2), Length(3))
+ : (i == 4) ? Conversion::createEckertIV(
+ PropertyMap(), Angle(1), Length(2),
+ Length(3))
+ : (i == 5) ? Conversion::createEckertV(
+ PropertyMap(), Angle(1),
+ Length(2), Length(3))
+ :
+
+ Conversion::createEckertVI(
+ PropertyMap(), Angle(1),
+ Length(2), Length(3));
+
+ EXPECT_EQ(conv->exportToPROJString(PROJStringFormatter::create().get()),
+ "+proj=eck" + numbers[i] + " +lon_0=1 +x_0=2 +y_0=3");
+
+ EXPECT_EQ(conv->exportToWKT(WKTFormatter::create().get()),
+ "CONVERSION[\"Eckert " + latinNumbers[i] +
+ "\",\n"
+ " METHOD[\"Eckert " +
+ latinNumbers[i] +
+ "\"],\n"
+ " PARAMETER[\"Longitude of natural origin\",1,\n"
+ " ANGLEUNIT[\"degree\",0.0174532925199433],\n"
+ " ID[\"EPSG\",8802]],\n"
+ " PARAMETER[\"False easting\",2,\n"
+ " LENGTHUNIT[\"metre\",1],\n"
+ " ID[\"EPSG\",8806]],\n"
+ " PARAMETER[\"False northing\",3,\n"
+ " LENGTHUNIT[\"metre\",1],\n"
+ " ID[\"EPSG\",8807]]]");
+
+ EXPECT_EQ(conv->exportToWKT(
+ WKTFormatter::create(WKTFormatter::Convention::WKT1_GDAL)
+ .get()),
+ "PROJECTION[\"Eckert_" + latinNumbers[i] +
+ "\"],\n"
+ "PARAMETER[\"central_meridian\",1],\n"
+ "PARAMETER[\"false_easting\",2],\n"
+ "PARAMETER[\"false_northing\",3]");
+ }
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(operation, createEquidistantCylindrical) {
+ auto conv = Conversion::createEquidistantCylindrical(
+ PropertyMap(), Angle(1), Angle(2), Length(3), Length(4));
+
+ EXPECT_EQ(conv->exportToPROJString(PROJStringFormatter::create().get()),
+ "+proj=eqc +lat_ts=1 +lon_0=2 +x_0=3 +y_0=4");
+
+ EXPECT_EQ(conv->exportToWKT(WKTFormatter::create().get()),
+ "CONVERSION[\"Equidistant Cylindrical\",\n"
+ " METHOD[\"Equidistant Cylindrical\",\n"
+ " ID[\"EPSG\",1028]],\n"
+ " PARAMETER[\"Latitude of 1st standard parallel\",1,\n"
+ " ANGLEUNIT[\"degree\",0.0174532925199433],\n"
+ " ID[\"EPSG\",8823]],\n"
+ " PARAMETER[\"Longitude of natural origin\",2,\n"
+ " ANGLEUNIT[\"degree\",0.0174532925199433],\n"
+ " ID[\"EPSG\",8802]],\n"
+ " PARAMETER[\"False easting\",3,\n"
+ " LENGTHUNIT[\"metre\",1],\n"
+ " ID[\"EPSG\",8806]],\n"
+ " PARAMETER[\"False northing\",4,\n"
+ " LENGTHUNIT[\"metre\",1],\n"
+ " ID[\"EPSG\",8807]]]");
+
+ EXPECT_EQ(
+ conv->exportToWKT(
+ WKTFormatter::create(WKTFormatter::Convention::WKT1_GDAL).get()),
+ "PROJECTION[\"Equirectangular\"],\n"
+ "PARAMETER[\"standard_parallel_1\",1],\n"
+ "PARAMETER[\"central_meridian\",2],\n"
+ "PARAMETER[\"false_easting\",3],\n"
+ "PARAMETER[\"false_northing\",4]");
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(operation, createEquidistantCylindricalSpherical) {
+ auto conv = Conversion::createEquidistantCylindricalSpherical(
+ PropertyMap(), Angle(1), Angle(2), Length(3), Length(4));
+
+ EXPECT_EQ(conv->exportToPROJString(PROJStringFormatter::create().get()),
+ "+proj=eqc +lat_ts=1 +lon_0=2 +x_0=3 +y_0=4");
+
+ EXPECT_EQ(conv->exportToWKT(WKTFormatter::create().get()),
+ "CONVERSION[\"Equidistant Cylindrical (Spherical)\",\n"
+ " METHOD[\"Equidistant Cylindrical (Spherical)\",\n"
+ " ID[\"EPSG\",1029]],\n"
+ " PARAMETER[\"Latitude of 1st standard parallel\",1,\n"
+ " ANGLEUNIT[\"degree\",0.0174532925199433],\n"
+ " ID[\"EPSG\",8823]],\n"
+ " PARAMETER[\"Longitude of natural origin\",2,\n"
+ " ANGLEUNIT[\"degree\",0.0174532925199433],\n"
+ " ID[\"EPSG\",8802]],\n"
+ " PARAMETER[\"False easting\",3,\n"
+ " LENGTHUNIT[\"metre\",1],\n"
+ " ID[\"EPSG\",8806]],\n"
+ " PARAMETER[\"False northing\",4,\n"
+ " LENGTHUNIT[\"metre\",1],\n"
+ " ID[\"EPSG\",8807]]]");
+
+ EXPECT_EQ(
+ conv->exportToWKT(
+ WKTFormatter::create(WKTFormatter::Convention::WKT1_GDAL).get()),
+ "PROJECTION[\"Equirectangular\"],\n"
+ "PARAMETER[\"standard_parallel_1\",1],\n"
+ "PARAMETER[\"central_meridian\",2],\n"
+ "PARAMETER[\"false_easting\",3],\n"
+ "PARAMETER[\"false_northing\",4]");
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(operation, gall_export) {
+
+ auto conv =
+ Conversion::createGall(PropertyMap(), Angle(1), Length(2), Length(3));
+
+ EXPECT_EQ(conv->exportToPROJString(PROJStringFormatter::create().get()),
+ "+proj=gall +lon_0=1 +x_0=2 +y_0=3");
+
+ EXPECT_EQ(conv->exportToWKT(WKTFormatter::create().get()),
+ "CONVERSION[\"Gall Stereographic\",\n"
+ " METHOD[\"Gall Stereographic\"],\n"
+ " PARAMETER[\"Longitude of natural origin\",1,\n"
+ " ANGLEUNIT[\"degree\",0.0174532925199433],\n"
+ " ID[\"EPSG\",8802]],\n"
+ " PARAMETER[\"False easting\",2,\n"
+ " LENGTHUNIT[\"metre\",1],\n"
+ " ID[\"EPSG\",8806]],\n"
+ " PARAMETER[\"False northing\",3,\n"
+ " LENGTHUNIT[\"metre\",1],\n"
+ " ID[\"EPSG\",8807]]]");
+
+ EXPECT_EQ(
+ conv->exportToWKT(
+ WKTFormatter::create(WKTFormatter::Convention::WKT1_GDAL).get()),
+ "PROJECTION[\"Gall_Stereographic\"],\n"
+ "PARAMETER[\"central_meridian\",1],\n"
+ "PARAMETER[\"false_easting\",2],\n"
+ "PARAMETER[\"false_northing\",3]");
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(operation, goode_homolosine_export) {
+
+ auto conv = Conversion::createGoodeHomolosine(PropertyMap(), Angle(1),
+ Length(2), Length(3));
+
+ EXPECT_EQ(conv->exportToPROJString(PROJStringFormatter::create().get()),
+ "+proj=goode +lon_0=1 +x_0=2 +y_0=3");
+
+ EXPECT_EQ(conv->exportToWKT(WKTFormatter::create().get()),
+ "CONVERSION[\"Goode Homolosine\",\n"
+ " METHOD[\"Goode Homolosine\"],\n"
+ " PARAMETER[\"Longitude of natural origin\",1,\n"
+ " ANGLEUNIT[\"degree\",0.0174532925199433],\n"
+ " ID[\"EPSG\",8802]],\n"
+ " PARAMETER[\"False easting\",2,\n"
+ " LENGTHUNIT[\"metre\",1],\n"
+ " ID[\"EPSG\",8806]],\n"
+ " PARAMETER[\"False northing\",3,\n"
+ " LENGTHUNIT[\"metre\",1],\n"
+ " ID[\"EPSG\",8807]]]");
+
+ EXPECT_EQ(
+ conv->exportToWKT(
+ WKTFormatter::create(WKTFormatter::Convention::WKT1_GDAL).get()),
+ "PROJECTION[\"Goode_Homolosine\"],\n"
+ "PARAMETER[\"central_meridian\",1],\n"
+ "PARAMETER[\"false_easting\",2],\n"
+ "PARAMETER[\"false_northing\",3]");
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(operation, interrupted_goode_homolosine_export) {
+
+ auto conv = Conversion::createInterruptedGoodeHomolosine(
+ PropertyMap(), Angle(1), Length(2), Length(3));
+
+ EXPECT_EQ(conv->exportToPROJString(PROJStringFormatter::create().get()),
+ "+proj=igh +lon_0=1 +x_0=2 +y_0=3");
+
+ EXPECT_EQ(conv->exportToWKT(WKTFormatter::create().get()),
+ "CONVERSION[\"Interrupted Goode Homolosine\",\n"
+ " METHOD[\"Interrupted Goode Homolosine\"],\n"
+ " PARAMETER[\"Longitude of natural origin\",1,\n"
+ " ANGLEUNIT[\"degree\",0.0174532925199433],\n"
+ " ID[\"EPSG\",8802]],\n"
+ " PARAMETER[\"False easting\",2,\n"
+ " LENGTHUNIT[\"metre\",1],\n"
+ " ID[\"EPSG\",8806]],\n"
+ " PARAMETER[\"False northing\",3,\n"
+ " LENGTHUNIT[\"metre\",1],\n"
+ " ID[\"EPSG\",8807]]]");
+
+ EXPECT_EQ(
+ conv->exportToWKT(
+ WKTFormatter::create(WKTFormatter::Convention::WKT1_GDAL).get()),
+ "PROJECTION[\"Interrupted_Goode_Homolosine\"],\n"
+ "PARAMETER[\"central_meridian\",1],\n"
+ "PARAMETER[\"false_easting\",2],\n"
+ "PARAMETER[\"false_northing\",3]");
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(operation, geostationary_satellite_sweep_x_export) {
+
+ auto conv = Conversion::createGeostationarySatelliteSweepX(
+ PropertyMap(), Angle(1), Length(2), Length(3), Length(4));
+
+ EXPECT_EQ(conv->exportToPROJString(PROJStringFormatter::create().get()),
+ "+proj=geos +sweep=x +lon_0=1 +h=2 +x_0=3 +y_0=4");
+
+ EXPECT_EQ(conv->exportToWKT(WKTFormatter::create().get()),
+ "CONVERSION[\"Geostationary Satellite (Sweep X)\",\n"
+ " METHOD[\"Geostationary Satellite (Sweep X)\"],\n"
+ " PARAMETER[\"Longitude of natural origin\",1,\n"
+ " ANGLEUNIT[\"degree\",0.0174532925199433],\n"
+ " ID[\"EPSG\",8802]],\n"
+ " PARAMETER[\"Satellite Height\",2,\n"
+ " LENGTHUNIT[\"metre\",1,\n"
+ " ID[\"EPSG\",9001]]],\n"
+ " PARAMETER[\"False easting\",3,\n"
+ " LENGTHUNIT[\"metre\",1],\n"
+ " ID[\"EPSG\",8806]],\n"
+ " PARAMETER[\"False northing\",4,\n"
+ " LENGTHUNIT[\"metre\",1],\n"
+ " ID[\"EPSG\",8807]]]");
+
+ EXPECT_THROW(
+ conv->exportToWKT(
+ WKTFormatter::create(WKTFormatter::Convention::WKT1_GDAL).get()),
+ FormattingException);
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(operation, geostationary_satellite_sweep_y_export) {
+
+ auto conv = Conversion::createGeostationarySatelliteSweepY(
+ PropertyMap(), Angle(1), Length(2), Length(3), Length(4));
+
+ EXPECT_EQ(conv->exportToPROJString(PROJStringFormatter::create().get()),
+ "+proj=geos +lon_0=1 +h=2 +x_0=3 +y_0=4");
+
+ EXPECT_EQ(conv->exportToWKT(WKTFormatter::create().get()),
+ "CONVERSION[\"Geostationary Satellite (Sweep Y)\",\n"
+ " METHOD[\"Geostationary Satellite (Sweep Y)\"],\n"
+ " PARAMETER[\"Longitude of natural origin\",1,\n"
+ " ANGLEUNIT[\"degree\",0.0174532925199433],\n"
+ " ID[\"EPSG\",8802]],\n"
+ " PARAMETER[\"Satellite Height\",2,\n"
+ " LENGTHUNIT[\"metre\",1,\n"
+ " ID[\"EPSG\",9001]]],\n"
+ " PARAMETER[\"False easting\",3,\n"
+ " LENGTHUNIT[\"metre\",1],\n"
+ " ID[\"EPSG\",8806]],\n"
+ " PARAMETER[\"False northing\",4,\n"
+ " LENGTHUNIT[\"metre\",1],\n"
+ " ID[\"EPSG\",8807]]]");
+
+ EXPECT_EQ(
+ conv->exportToWKT(
+ WKTFormatter::create(WKTFormatter::Convention::WKT1_GDAL).get()),
+ "PROJECTION[\"Geostationary_Satellite\"],\n"
+ "PARAMETER[\"central_meridian\",1],\n"
+ "PARAMETER[\"satellite_height\",2],\n"
+ "PARAMETER[\"false_easting\",3],\n"
+ "PARAMETER[\"false_northing\",4]");
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(operation, gnomonic_export) {
+ auto conv = Conversion::createGnomonic(PropertyMap(), Angle(1), Angle(2),
+ Length(4), Length(5));
+ EXPECT_EQ(conv->exportToPROJString(PROJStringFormatter::create().get()),
+ "+proj=gnom +lat_0=1 +lon_0=2 +x_0=4 +y_0=5");
+
+ EXPECT_EQ(conv->exportToWKT(WKTFormatter::create().get()),
+ "CONVERSION[\"Gnomonic\",\n"
+ " METHOD[\"Gnomonic\"],\n"
+ " PARAMETER[\"Latitude of natural origin\",1,\n"
+ " ANGLEUNIT[\"degree\",0.0174532925199433],\n"
+ " ID[\"EPSG\",8801]],\n"
+ " PARAMETER[\"Longitude of natural origin\",2,\n"
+ " ANGLEUNIT[\"degree\",0.0174532925199433],\n"
+ " ID[\"EPSG\",8802]],\n"
+ " PARAMETER[\"False easting\",4,\n"
+ " LENGTHUNIT[\"metre\",1],\n"
+ " ID[\"EPSG\",8806]],\n"
+ " PARAMETER[\"False northing\",5,\n"
+ " LENGTHUNIT[\"metre\",1],\n"
+ " ID[\"EPSG\",8807]]]");
+
+ EXPECT_EQ(
+ conv->exportToWKT(
+ WKTFormatter::create(WKTFormatter::Convention::WKT1_GDAL).get()),
+ "PROJECTION[\"Gnomonic\"],\n"
+ "PARAMETER[\"latitude_of_origin\",1],\n"
+ "PARAMETER[\"central_meridian\",2],\n"
+ "PARAMETER[\"false_easting\",4],\n"
+ "PARAMETER[\"false_northing\",5]");
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(operation, hotine_oblique_mercator_variant_A_export) {
+ auto conv = Conversion::createHotineObliqueMercatorVariantA(
+ PropertyMap(), Angle(1), Angle(2), Angle(3), Angle(4), Scale(5),
+ Length(6), Length(7));
+ EXPECT_EQ(conv->exportToPROJString(PROJStringFormatter::create().get()),
+ "+proj=omerc +no_uoff +lat_0=1 +lonc=2 +alpha=3 +gamma=4 +k=5 "
+ "+x_0=6 +y_0=7");
+
+ EXPECT_EQ(conv->exportToWKT(WKTFormatter::create().get()),
+ "CONVERSION[\"Hotine Oblique Mercator (variant A)\",\n"
+ " METHOD[\"Hotine Oblique Mercator (variant A)\",\n"
+ " ID[\"EPSG\",9812]],\n"
+ " PARAMETER[\"Latitude of projection centre\",1,\n"
+ " ANGLEUNIT[\"degree\",0.0174532925199433],\n"
+ " ID[\"EPSG\",8811]],\n"
+ " PARAMETER[\"Longitude of projection centre\",2,\n"
+ " ANGLEUNIT[\"degree\",0.0174532925199433],\n"
+ " ID[\"EPSG\",8812]],\n"
+ " PARAMETER[\"Azimuth of initial line\",3,\n"
+ " ANGLEUNIT[\"degree\",0.0174532925199433],\n"
+ " ID[\"EPSG\",8813]],\n"
+ " PARAMETER[\"Angle from Rectified to Skew Grid\",4,\n"
+ " ANGLEUNIT[\"degree\",0.0174532925199433],\n"
+ " ID[\"EPSG\",8814]],\n"
+ " PARAMETER[\"Scale factor on initial line\",5,\n"
+ " SCALEUNIT[\"unity\",1],\n"
+ " ID[\"EPSG\",8815]],\n"
+ " PARAMETER[\"False easting\",6,\n"
+ " LENGTHUNIT[\"metre\",1],\n"
+ " ID[\"EPSG\",8806]],\n"
+ " PARAMETER[\"False northing\",7,\n"
+ " LENGTHUNIT[\"metre\",1],\n"
+ " ID[\"EPSG\",8807]]]");
+
+ EXPECT_EQ(
+ conv->exportToWKT(
+ WKTFormatter::create(WKTFormatter::Convention::WKT1_GDAL).get()),
+ "PROJECTION[\"Hotine_Oblique_Mercator\"],\n"
+ "PARAMETER[\"latitude_of_center\",1],\n"
+ "PARAMETER[\"longitude_of_center\",2],\n"
+ "PARAMETER[\"azimuth\",3],\n"
+ "PARAMETER[\"rectified_grid_angle\",4],\n"
+ "PARAMETER[\"scale_factor\",5],\n"
+ "PARAMETER[\"false_easting\",6],\n"
+ "PARAMETER[\"false_northing\",7]");
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(operation, hotine_oblique_mercator_variant_A_export_swiss_mercator) {
+ auto conv = Conversion::createHotineObliqueMercatorVariantA(
+ PropertyMap(), Angle(1), Angle(2), Angle(90), Angle(90), Scale(5),
+ Length(6), Length(7));
+ EXPECT_EQ(conv->exportToPROJString(PROJStringFormatter::create().get()),
+ "+proj=somerc +lat_0=1 +lon_0=2 +k_0=5 "
+ "+x_0=6 +y_0=7");
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(operation, hotine_oblique_mercator_variant_B_export) {
+ auto conv = Conversion::createHotineObliqueMercatorVariantB(
+ PropertyMap(), Angle(1), Angle(2), Angle(3), Angle(4), Scale(5),
+ Length(6), Length(7));
+ EXPECT_EQ(conv->exportToPROJString(PROJStringFormatter::create().get()),
+ "+proj=omerc +lat_0=1 +lonc=2 +alpha=3 +gamma=4 +k=5 "
+ "+x_0=6 +y_0=7");
+
+ EXPECT_EQ(conv->exportToWKT(WKTFormatter::create().get()),
+ "CONVERSION[\"Hotine Oblique Mercator (variant B)\",\n"
+ " METHOD[\"Hotine Oblique Mercator (variant B)\",\n"
+ " ID[\"EPSG\",9815]],\n"
+ " PARAMETER[\"Latitude of projection centre\",1,\n"
+ " ANGLEUNIT[\"degree\",0.0174532925199433],\n"
+ " ID[\"EPSG\",8811]],\n"
+ " PARAMETER[\"Longitude of projection centre\",2,\n"
+ " ANGLEUNIT[\"degree\",0.0174532925199433],\n"
+ " ID[\"EPSG\",8812]],\n"
+ " PARAMETER[\"Azimuth of initial line\",3,\n"
+ " ANGLEUNIT[\"degree\",0.0174532925199433],\n"
+ " ID[\"EPSG\",8813]],\n"
+ " PARAMETER[\"Angle from Rectified to Skew Grid\",4,\n"
+ " ANGLEUNIT[\"degree\",0.0174532925199433],\n"
+ " ID[\"EPSG\",8814]],\n"
+ " PARAMETER[\"Scale factor on initial line\",5,\n"
+ " SCALEUNIT[\"unity\",1],\n"
+ " ID[\"EPSG\",8815]],\n"
+ " PARAMETER[\"Easting at projection centre\",6,\n"
+ " LENGTHUNIT[\"metre\",1],\n"
+ " ID[\"EPSG\",8816]],\n"
+ " PARAMETER[\"Northing at projection centre\",7,\n"
+ " LENGTHUNIT[\"metre\",1],\n"
+ " ID[\"EPSG\",8817]]]");
+
+ EXPECT_EQ(
+ conv->exportToWKT(
+ WKTFormatter::create(WKTFormatter::Convention::WKT1_GDAL).get()),
+ "PROJECTION[\"Hotine_Oblique_Mercator_Azimuth_Center\"],\n"
+ "PARAMETER[\"latitude_of_center\",1],\n"
+ "PARAMETER[\"longitude_of_center\",2],\n"
+ "PARAMETER[\"azimuth\",3],\n"
+ "PARAMETER[\"rectified_grid_angle\",4],\n"
+ "PARAMETER[\"scale_factor\",5],\n"
+ "PARAMETER[\"false_easting\",6],\n"
+ "PARAMETER[\"false_northing\",7]");
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(operation, hotine_oblique_mercator_variant_B_export_swiss_mercator) {
+ auto conv = Conversion::createHotineObliqueMercatorVariantB(
+ PropertyMap(), Angle(1), Angle(2), Angle(90), Angle(90), Scale(5),
+ Length(6), Length(7));
+ EXPECT_EQ(conv->exportToPROJString(PROJStringFormatter::create().get()),
+ "+proj=somerc +lat_0=1 +lon_0=2 +k_0=5 "
+ "+x_0=6 +y_0=7");
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(operation, hotine_oblique_mercator_two_point_natural_origin_export) {
+ auto conv = Conversion::createHotineObliqueMercatorTwoPointNaturalOrigin(
+ PropertyMap(), Angle(1), Angle(2), Angle(3), Angle(4), Angle(5),
+ Scale(6), Length(7), Length(8));
+ EXPECT_EQ(conv->exportToPROJString(PROJStringFormatter::create().get()),
+ "+proj=omerc +lat_0=1 +lat_1=2 +lon_1=3 +lat_2=4 +lon_2=5 +k=6 "
+ "+x_0=7 +y_0=8");
+
+ auto formatter = WKTFormatter::create();
+ formatter->simulCurNodeHasId();
+ EXPECT_EQ(
+ conv->exportToWKT(formatter.get()),
+ "CONVERSION[\"Hotine Oblique Mercator Two Point Natural Origin\",\n"
+ " METHOD[\"Hotine Oblique Mercator Two Point Natural Origin\"],\n"
+ " PARAMETER[\"Latitude of projection centre\",1,\n"
+ " ANGLEUNIT[\"degree\",0.0174532925199433],\n"
+ " ID[\"EPSG\",8811]],\n"
+ " PARAMETER[\"Latitude of 1st point\",2,\n"
+ " ANGLEUNIT[\"degree\",0.0174532925199433]],\n"
+ " PARAMETER[\"Longitude of 1st point\",3,\n"
+ " ANGLEUNIT[\"degree\",0.0174532925199433]],\n"
+ " PARAMETER[\"Latitude of 2nd point\",4,\n"
+ " ANGLEUNIT[\"degree\",0.0174532925199433]],\n"
+ " PARAMETER[\"Longitude of 2nd point\",5,\n"
+ " ANGLEUNIT[\"degree\",0.0174532925199433]],\n"
+ " PARAMETER[\"Scale factor on initial line\",6,\n"
+ " SCALEUNIT[\"unity\",1],\n"
+ " ID[\"EPSG\",8815]],\n"
+ " PARAMETER[\"Easting at projection centre\",7,\n"
+ " LENGTHUNIT[\"metre\",1],\n"
+ " ID[\"EPSG\",8816]],\n"
+ " PARAMETER[\"Northing at projection centre\",8,\n"
+ " LENGTHUNIT[\"metre\",1],\n"
+ " ID[\"EPSG\",8817]]]");
+
+ EXPECT_EQ(
+ conv->exportToWKT(
+ WKTFormatter::create(WKTFormatter::Convention::WKT1_GDAL).get()),
+ "PROJECTION[\"Hotine_Oblique_Mercator_Two_Point_Natural_Origin\"],\n"
+ "PARAMETER[\"latitude_of_origin\",1],\n"
+ "PARAMETER[\"latitude_of_point_1\",2],\n"
+ "PARAMETER[\"longitude_of_point_1\",3],\n"
+ "PARAMETER[\"latitude_of_point_2\",4],\n"
+ "PARAMETER[\"longitude_of_point_2\",5],\n"
+ "PARAMETER[\"scale_factor\",6],\n"
+ "PARAMETER[\"false_easting\",7],\n"
+ "PARAMETER[\"false_northing\",8]");
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(operation, imw_polyconic_export) {
+ auto conv = Conversion::createInternationalMapWorldPolyconic(
+ PropertyMap(), Angle(1), Angle(3), Angle(4), Length(5), Length(6));
+
+ EXPECT_EQ(conv->exportToPROJString(PROJStringFormatter::create().get()),
+ "+proj=imw_p +lon_0=1 +lat_1=3 +lat_2=4 +x_0=5 +y_0=6");
+
+ EXPECT_EQ(conv->exportToWKT(WKTFormatter::create().get()),
+ "CONVERSION[\"International Map of the World Polyconic\",\n"
+ " METHOD[\"International Map of the World Polyconic\"],\n"
+ " PARAMETER[\"Longitude of natural origin\",1,\n"
+ " ANGLEUNIT[\"degree\",0.0174532925199433],\n"
+ " ID[\"EPSG\",8802]],\n"
+ " PARAMETER[\"Latitude of 1st standard parallel\",3,\n"
+ " ANGLEUNIT[\"degree\",0.0174532925199433],\n"
+ " ID[\"EPSG\",8823]],\n"
+ " PARAMETER[\"Latitude of 2nd standard parallel\",4,\n"
+ " ANGLEUNIT[\"degree\",0.0174532925199433],\n"
+ " ID[\"EPSG\",8824]],\n"
+ " PARAMETER[\"False easting\",5,\n"
+ " LENGTHUNIT[\"metre\",1],\n"
+ " ID[\"EPSG\",8806]],\n"
+ " PARAMETER[\"False northing\",6,\n"
+ " LENGTHUNIT[\"metre\",1],\n"
+ " ID[\"EPSG\",8807]]]");
+
+ EXPECT_EQ(
+ conv->exportToWKT(
+ WKTFormatter::create(WKTFormatter::Convention::WKT1_GDAL).get()),
+ "PROJECTION[\"International_Map_of_the_World_Polyconic\"],\n"
+ "PARAMETER[\"central_meridian\",1],\n"
+ "PARAMETER[\"standard_parallel_1\",3],\n"
+ "PARAMETER[\"standard_parallel_2\",4],\n"
+ "PARAMETER[\"false_easting\",5],\n"
+ "PARAMETER[\"false_northing\",6]");
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(operation, krovak_north_oriented_export) {
+ auto conv = Conversion::createKrovakNorthOriented(
+ PropertyMap(), Angle(49.5), Angle(42.5), Angle(30.28813972222222),
+ Angle(78.5), Scale(0.9999), Length(5), Length(6));
+
+ EXPECT_EQ(conv->exportToPROJString(PROJStringFormatter::create().get()),
+ "+proj=krovak +lat_0=49.5 +lon_0=42.5 +k=0.9999 +x_0=5 +y_0=6");
+
+ EXPECT_EQ(
+ conv->exportToWKT(WKTFormatter::create().get()),
+ "CONVERSION[\"Krovak (North Orientated)\",\n"
+ " METHOD[\"Krovak (North Orientated)\",\n"
+ " ID[\"EPSG\",1041]],\n"
+ " PARAMETER[\"Latitude of projection centre\",49.5,\n"
+ " ANGLEUNIT[\"degree\",0.0174532925199433],\n"
+ " ID[\"EPSG\",8811]],\n"
+ " PARAMETER[\"Longitude of origin\",42.5,\n"
+ " ANGLEUNIT[\"degree\",0.0174532925199433],\n"
+ " ID[\"EPSG\",8833]],\n"
+ " PARAMETER[\"Co-latitude of cone axis\",30.2881397222222,\n"
+ " ANGLEUNIT[\"degree\",0.0174532925199433],\n"
+ " ID[\"EPSG\",1036]],\n"
+ " PARAMETER[\"Latitude of pseudo standard parallel\",78.5,\n"
+ " ANGLEUNIT[\"degree\",0.0174532925199433],\n"
+ " ID[\"EPSG\",8818]],\n"
+ " PARAMETER[\"Scale factor on pseudo standard parallel\",0.9999,\n"
+ " SCALEUNIT[\"unity\",1],\n"
+ " ID[\"EPSG\",8819]],\n"
+ " PARAMETER[\"False easting\",5,\n"
+ " LENGTHUNIT[\"metre\",1],\n"
+ " ID[\"EPSG\",8806]],\n"
+ " PARAMETER[\"False northing\",6,\n"
+ " LENGTHUNIT[\"metre\",1],\n"
+ " ID[\"EPSG\",8807]]]");
+
+ EXPECT_EQ(
+ conv->exportToWKT(
+ WKTFormatter::create(WKTFormatter::Convention::WKT1_GDAL).get()),
+ "PROJECTION[\"Krovak\"],\n"
+ "PARAMETER[\"latitude_of_center\",49.5],\n"
+ "PARAMETER[\"longitude_of_center\",42.5],\n"
+ "PARAMETER[\"azimuth\",30.2881397222222],\n"
+ "PARAMETER[\"pseudo_standard_parallel_1\",78.5],\n"
+ "PARAMETER[\"scale_factor\",0.9999],\n"
+ "PARAMETER[\"false_easting\",5],\n"
+ "PARAMETER[\"false_northing\",6]");
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(operation, krovak_export) {
+ auto conv = Conversion::createKrovak(
+ PropertyMap(), Angle(49.5), Angle(42.5), Angle(30.28813972222222),
+ Angle(78.5), Scale(0.9999), Length(5), Length(6));
+
+ EXPECT_EQ(conv->exportToPROJString(PROJStringFormatter::create().get()),
+ "+proj=krovak +axis=swu +lat_0=49.5 +lon_0=42.5 +k=0.9999 +x_0=5 "
+ "+y_0=6");
+
+ EXPECT_EQ(
+ conv->exportToWKT(WKTFormatter::create().get()),
+ "CONVERSION[\"Krovak\",\n"
+ " METHOD[\"Krovak\",\n"
+ " ID[\"EPSG\",9819]],\n"
+ " PARAMETER[\"Latitude of projection centre\",49.5,\n"
+ " ANGLEUNIT[\"degree\",0.0174532925199433],\n"
+ " ID[\"EPSG\",8811]],\n"
+ " PARAMETER[\"Longitude of origin\",42.5,\n"
+ " ANGLEUNIT[\"degree\",0.0174532925199433],\n"
+ " ID[\"EPSG\",8833]],\n"
+ " PARAMETER[\"Co-latitude of cone axis\",30.2881397222222,\n"
+ " ANGLEUNIT[\"degree\",0.0174532925199433],\n"
+ " ID[\"EPSG\",1036]],\n"
+ " PARAMETER[\"Latitude of pseudo standard parallel\",78.5,\n"
+ " ANGLEUNIT[\"degree\",0.0174532925199433],\n"
+ " ID[\"EPSG\",8818]],\n"
+ " PARAMETER[\"Scale factor on pseudo standard parallel\",0.9999,\n"
+ " SCALEUNIT[\"unity\",1],\n"
+ " ID[\"EPSG\",8819]],\n"
+ " PARAMETER[\"False easting\",5,\n"
+ " LENGTHUNIT[\"metre\",1],\n"
+ " ID[\"EPSG\",8806]],\n"
+ " PARAMETER[\"False northing\",6,\n"
+ " LENGTHUNIT[\"metre\",1],\n"
+ " ID[\"EPSG\",8807]]]");
+
+ EXPECT_EQ(
+ conv->exportToWKT(
+ WKTFormatter::create(WKTFormatter::Convention::WKT1_GDAL).get()),
+ "PROJECTION[\"Krovak\"],\n"
+ "PARAMETER[\"latitude_of_center\",49.5],\n"
+ "PARAMETER[\"longitude_of_center\",42.5],\n"
+ "PARAMETER[\"azimuth\",30.2881397222222],\n"
+ "PARAMETER[\"pseudo_standard_parallel_1\",78.5],\n"
+ "PARAMETER[\"scale_factor\",0.9999],\n"
+ "PARAMETER[\"false_easting\",5],\n"
+ "PARAMETER[\"false_northing\",6]");
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(operation, lambert_azimuthal_equal_area_export) {
+ auto conv = Conversion::createLambertAzimuthalEqualArea(
+ PropertyMap(), Angle(1), Angle(2), Length(3), Length(4));
+
+ EXPECT_EQ(conv->exportToPROJString(PROJStringFormatter::create().get()),
+ "+proj=laea +lat_0=1 +lon_0=2 +x_0=3 +y_0=4");
+
+ EXPECT_EQ(conv->exportToWKT(WKTFormatter::create().get()),
+ "CONVERSION[\"Lambert Azimuthal Equal Area\",\n"
+ " METHOD[\"Lambert Azimuthal Equal Area\",\n"
+ " ID[\"EPSG\",9820]],\n"
+ " PARAMETER[\"Latitude of natural origin\",1,\n"
+ " ANGLEUNIT[\"degree\",0.0174532925199433],\n"
+ " ID[\"EPSG\",8801]],\n"
+ " PARAMETER[\"Longitude of natural origin\",2,\n"
+ " ANGLEUNIT[\"degree\",0.0174532925199433],\n"
+ " ID[\"EPSG\",8802]],\n"
+ " PARAMETER[\"False easting\",3,\n"
+ " LENGTHUNIT[\"metre\",1],\n"
+ " ID[\"EPSG\",8806]],\n"
+ " PARAMETER[\"False northing\",4,\n"
+ " LENGTHUNIT[\"metre\",1],\n"
+ " ID[\"EPSG\",8807]]]");
+
+ EXPECT_EQ(
+ conv->exportToWKT(
+ WKTFormatter::create(WKTFormatter::Convention::WKT1_GDAL).get()),
+ "PROJECTION[\"Lambert_Azimuthal_Equal_Area\"],\n"
+ "PARAMETER[\"latitude_of_center\",1],\n"
+ "PARAMETER[\"longitude_of_center\",2],\n"
+ "PARAMETER[\"false_easting\",3],\n"
+ "PARAMETER[\"false_northing\",4]");
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(operation, miller_cylindrical_export) {
+ auto conv = Conversion::createMillerCylindrical(PropertyMap(), Angle(2),
+ Length(3), Length(4));
+
+ EXPECT_EQ(conv->exportToPROJString(PROJStringFormatter::create().get()),
+ "+proj=mill +R_A +lon_0=2 +x_0=3 +y_0=4");
+
+ EXPECT_EQ(conv->exportToWKT(WKTFormatter::create().get()),
+ "CONVERSION[\"Miller Cylindrical\",\n"
+ " METHOD[\"Miller Cylindrical\"],\n"
+ " PARAMETER[\"Longitude of natural origin\",2,\n"
+ " ANGLEUNIT[\"degree\",0.0174532925199433],\n"
+ " ID[\"EPSG\",8802]],\n"
+ " PARAMETER[\"False easting\",3,\n"
+ " LENGTHUNIT[\"metre\",1],\n"
+ " ID[\"EPSG\",8806]],\n"
+ " PARAMETER[\"False northing\",4,\n"
+ " LENGTHUNIT[\"metre\",1],\n"
+ " ID[\"EPSG\",8807]]]");
+
+ EXPECT_EQ(
+ conv->exportToWKT(
+ WKTFormatter::create(WKTFormatter::Convention::WKT1_GDAL).get()),
+ "PROJECTION[\"Miller_Cylindrical\"],\n"
+ "PARAMETER[\"longitude_of_center\",2],\n"
+ "PARAMETER[\"false_easting\",3],\n"
+ "PARAMETER[\"false_northing\",4]");
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(operation, mercator_variant_A_export) {
+ auto conv = Conversion::createMercatorVariantA(
+ PropertyMap(), Angle(0), Angle(1), Scale(2), Length(3), Length(4));
+
+ EXPECT_EQ(conv->exportToPROJString(PROJStringFormatter::create().get()),
+ "+proj=merc +lon_0=1 +k=2 +x_0=3 +y_0=4");
+
+ EXPECT_EQ(conv->exportToWKT(WKTFormatter::create().get()),
+ "CONVERSION[\"Mercator (variant A)\",\n"
+ " METHOD[\"Mercator (variant A)\",\n"
+ " ID[\"EPSG\",9804]],\n"
+ " PARAMETER[\"Latitude of natural origin\",0,\n"
+ " ANGLEUNIT[\"degree\",0.0174532925199433],\n"
+ " ID[\"EPSG\",8801]],\n"
+ " PARAMETER[\"Longitude of natural origin\",1,\n"
+ " ANGLEUNIT[\"degree\",0.0174532925199433],\n"
+ " ID[\"EPSG\",8802]],\n"
+ " PARAMETER[\"Scale factor at natural origin\",2,\n"
+ " SCALEUNIT[\"unity\",1],\n"
+ " ID[\"EPSG\",8805]],\n"
+ " PARAMETER[\"False easting\",3,\n"
+ " LENGTHUNIT[\"metre\",1],\n"
+ " ID[\"EPSG\",8806]],\n"
+ " PARAMETER[\"False northing\",4,\n"
+ " LENGTHUNIT[\"metre\",1],\n"
+ " ID[\"EPSG\",8807]]]");
+
+ EXPECT_EQ(
+ conv->exportToWKT(
+ WKTFormatter::create(WKTFormatter::Convention::WKT1_GDAL).get()),
+ "PROJECTION[\"Mercator_1SP\"],\n"
+ "PARAMETER[\"central_meridian\",1],\n"
+ "PARAMETER[\"scale_factor\",2],\n"
+ "PARAMETER[\"false_easting\",3],\n"
+ "PARAMETER[\"false_northing\",4]");
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(operation, mercator_variant_A_export_latitude_origin_non_zero) {
+ auto conv = Conversion::createMercatorVariantA(
+ PropertyMap(), Angle(10), Angle(1), Scale(2), Length(3), Length(4));
+
+ EXPECT_THROW(conv->exportToPROJString(PROJStringFormatter::create().get()),
+ FormattingException);
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(operation, wkt1_import_mercator_variant_A) {
+ auto wkt = "PROJCS[\"test\",\n"
+ " GEOGCS[\"WGS 84\",\n"
+ " DATUM[\"WGS 1984\",\n"
+ " SPHEROID[\"WGS 84\",6378137,298.257223563]],\n"
+ " PRIMEM[\"Greenwich\",0],\n"
+ " UNIT[\"degree\",0.0174532925199433]],\n"
+ " PROJECTION[\"Mercator_1SP\"],\n"
+ " PARAMETER[\"central_meridian\",1],\n"
+ " PARAMETER[\"scale_factor\",2],\n"
+ " PARAMETER[\"false_easting\",3],\n"
+ " PARAMETER[\"false_northing\",4],\n"
+ " UNIT[\"metre\",1]]";
+ auto obj = WKTParser().createFromWKT(wkt);
+ auto crs = nn_dynamic_pointer_cast<ProjectedCRS>(obj);
+ ASSERT_TRUE(crs != nullptr);
+
+ auto conversion = crs->derivingConversion();
+ auto convRef = Conversion::createMercatorVariantA(
+ PropertyMap().set(IdentifiedObject::NAME_KEY, "unnamed"), Angle(0),
+ Angle(1), Scale(2), Length(3), Length(4));
+
+ EXPECT_EQ(conversion->exportToWKT(WKTFormatter::create().get()),
+ convRef->exportToWKT(WKTFormatter::create().get()));
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(operation, mercator_variant_B_export) {
+ auto conv = Conversion::createMercatorVariantB(
+ PropertyMap(), Angle(1), Angle(2), Length(3), Length(4));
+
+ EXPECT_EQ(conv->exportToPROJString(PROJStringFormatter::create().get()),
+ "+proj=merc +lat_ts=1 +lon_0=2 +x_0=3 +y_0=4");
+
+ EXPECT_EQ(conv->exportToWKT(WKTFormatter::create().get()),
+ "CONVERSION[\"Mercator (variant B)\",\n"
+ " METHOD[\"Mercator (variant B)\",\n"
+ " ID[\"EPSG\",9805]],\n"
+ " PARAMETER[\"Latitude of 1st standard parallel\",1,\n"
+ " ANGLEUNIT[\"degree\",0.0174532925199433],\n"
+ " ID[\"EPSG\",8823]],\n"
+ " PARAMETER[\"Longitude of natural origin\",2,\n"
+ " ANGLEUNIT[\"degree\",0.0174532925199433],\n"
+ " ID[\"EPSG\",8802]],\n"
+ " PARAMETER[\"False easting\",3,\n"
+ " LENGTHUNIT[\"metre\",1],\n"
+ " ID[\"EPSG\",8806]],\n"
+ " PARAMETER[\"False northing\",4,\n"
+ " LENGTHUNIT[\"metre\",1],\n"
+ " ID[\"EPSG\",8807]]]");
+
+ EXPECT_EQ(
+ conv->exportToWKT(
+ WKTFormatter::create(WKTFormatter::Convention::WKT1_GDAL).get()),
+ "PROJECTION[\"Mercator_2SP\"],\n"
+ "PARAMETER[\"standard_parallel_1\",1],\n"
+ "PARAMETER[\"central_meridian\",2],\n"
+ "PARAMETER[\"false_easting\",3],\n"
+ "PARAMETER[\"false_northing\",4]");
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(operation, webmerc_export) {
+ auto conv = Conversion::createPopularVisualisationPseudoMercator(
+ PropertyMap(), Angle(0), Angle(2), Length(3), Length(4));
+
+ EXPECT_EQ(conv->exportToPROJString(PROJStringFormatter::create().get()),
+ "+proj=webmerc +lat_0=0 +lon_0=2 +x_0=3 +y_0=4");
+
+ EXPECT_THROW(
+ conv->exportToPROJString(
+ PROJStringFormatter::create(PROJStringFormatter::Convention::PROJ_4)
+ .get()),
+ FormattingException);
+
+ EXPECT_EQ(conv->exportToWKT(WKTFormatter::create().get()),
+ "CONVERSION[\"Popular Visualisation Pseudo Mercator\",\n"
+ " METHOD[\"Popular Visualisation Pseudo Mercator\",\n"
+ " ID[\"EPSG\",1024]],\n"
+ " PARAMETER[\"Latitude of natural origin\",0,\n"
+ " ANGLEUNIT[\"degree\",0.0174532925199433],\n"
+ " ID[\"EPSG\",8801]],\n"
+ " PARAMETER[\"Longitude of natural origin\",2,\n"
+ " ANGLEUNIT[\"degree\",0.0174532925199433],\n"
+ " ID[\"EPSG\",8802]],\n"
+ " PARAMETER[\"False easting\",3,\n"
+ " LENGTHUNIT[\"metre\",1],\n"
+ " ID[\"EPSG\",8806]],\n"
+ " PARAMETER[\"False northing\",4,\n"
+ " LENGTHUNIT[\"metre\",1],\n"
+ " ID[\"EPSG\",8807]]]");
+
+ auto projCRS = ProjectedCRS::create(
+ PropertyMap().set(IdentifiedObject::NAME_KEY, "Pseudo-Mercator"),
+ GeographicCRS::EPSG_4326, conv,
+ CartesianCS::createEastingNorthing(UnitOfMeasure::METRE));
+
+ EXPECT_EQ(
+ projCRS->exportToWKT(
+ WKTFormatter::create(WKTFormatter::Convention::WKT1_GDAL).get()),
+ "PROJCS[\"Pseudo-Mercator\",\n"
+ " GEOGCS[\"WGS 84\",\n"
+ " DATUM[\"WGS_1984\",\n"
+ " SPHEROID[\"WGS 84\",6378137,298.257223563,\n"
+ " AUTHORITY[\"EPSG\",\"7030\"]],\n"
+ " AUTHORITY[\"EPSG\",\"6326\"]],\n"
+ " PRIMEM[\"Greenwich\",0,\n"
+ " AUTHORITY[\"EPSG\",\"8901\"]],\n"
+ " UNIT[\"degree\",0.0174532925199433,\n"
+ " AUTHORITY[\"EPSG\",\"9122\"]],\n"
+ " AXIS[\"Latitude\",NORTH],\n"
+ " AXIS[\"Longitude\",EAST],\n"
+ " AUTHORITY[\"EPSG\",\"4326\"]],\n"
+ " PROJECTION[\"Mercator_1SP\"],\n"
+ " PARAMETER[\"central_meridian\",2],\n"
+ " PARAMETER[\"scale_factor\",1],\n"
+ " PARAMETER[\"false_easting\",3],\n"
+ " PARAMETER[\"false_northing\",4],\n"
+ " UNIT[\"metre\",1,\n"
+ " AUTHORITY[\"EPSG\",\"9001\"]],\n"
+ " AXIS[\"Easting\",EAST],\n"
+ " AXIS[\"Northing\",NORTH],\n"
+ " EXTENSION[\"PROJ4\",\"+proj=merc "
+ "+a=6378137 +b=6378137 +lat_ts=0 +lon_0=2 "
+ "+x_0=3 +y_0=4 +k=1 +units=m "
+ "+nadgrids=@null +wktext +no_defs\"]]");
+
+ EXPECT_EQ(
+ projCRS->exportToPROJString(
+ PROJStringFormatter::create(PROJStringFormatter::Convention::PROJ_5)
+ .get()),
+ "+proj=pipeline +step +proj=axisswap +order=2,1 +step "
+ "+proj=unitconvert +xy_in=deg +xy_out=rad +step +proj=webmerc "
+ "+lat_0=0 +lon_0=2 +x_0=3 +y_0=4 +ellps=WGS84");
+
+ EXPECT_EQ(
+ projCRS->exportToPROJString(
+ PROJStringFormatter::create(PROJStringFormatter::Convention::PROJ_4)
+ .get()),
+ "+proj=merc +a=6378137 +b=6378137 +lat_ts=0 +lon_0=2 +x_0=3 "
+ "+y_0=4 +k=1 +units=m +nadgrids=@null +wktext +no_defs");
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(operation, webmerc_import_from_GDAL_wkt1) {
+
+ auto projCRS = ProjectedCRS::create(
+ PropertyMap().set(IdentifiedObject::NAME_KEY, "Pseudo-Mercator"),
+ GeographicCRS::EPSG_4326,
+ Conversion::createPopularVisualisationPseudoMercator(
+ PropertyMap(), Angle(0), Angle(0), Length(0), Length(0)),
+ CartesianCS::createEastingNorthing(UnitOfMeasure::METRE));
+
+ auto wkt1 = projCRS->exportToWKT(
+ WKTFormatter::create(WKTFormatter::Convention::WKT1_GDAL).get());
+ auto obj = WKTParser().createFromWKT(wkt1);
+ auto crs = nn_dynamic_pointer_cast<ProjectedCRS>(obj);
+ ASSERT_TRUE(crs != nullptr);
+
+ auto convGot = crs->derivingConversion();
+
+ EXPECT_EQ(convGot->exportToWKT(WKTFormatter::create().get()),
+ "CONVERSION[\"unnamed\",\n"
+ " METHOD[\"Popular Visualisation Pseudo Mercator\",\n"
+ " ID[\"EPSG\",1024]],\n"
+ " PARAMETER[\"Latitude of natural origin\",0,\n"
+ " ANGLEUNIT[\"degree\",0.0174532925199433],\n"
+ " ID[\"EPSG\",8801]],\n"
+ " PARAMETER[\"Longitude of natural origin\",0,\n"
+ " ANGLEUNIT[\"degree\",0.0174532925199433],\n"
+ " ID[\"EPSG\",8802]],\n"
+ " PARAMETER[\"False easting\",0,\n"
+ " LENGTHUNIT[\"metre\",1],\n"
+ " ID[\"EPSG\",8806]],\n"
+ " PARAMETER[\"False northing\",0,\n"
+ " LENGTHUNIT[\"metre\",1],\n"
+ " ID[\"EPSG\",8807]]]");
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(operation, webmerc_import_from_GDAL_wkt1_EPSG_3785_deprecated) {
+
+ auto wkt1 =
+ "PROJCS[\"Popular Visualisation CRS / Mercator (deprecated)\","
+ " GEOGCS[\"Popular Visualisation CRS\","
+ " DATUM[\"Popular_Visualisation_Datum\","
+ " SPHEROID[\"Popular Visualisation Sphere\",6378137,0,"
+ " AUTHORITY[\"EPSG\",\"7059\"]],"
+ " TOWGS84[0,0,0,0,0,0,0],"
+ " AUTHORITY[\"EPSG\",\"6055\"]],"
+ " PRIMEM[\"Greenwich\",0,"
+ " AUTHORITY[\"EPSG\",\"8901\"]],"
+ " UNIT[\"degree\",0.0174532925199433,"
+ " AUTHORITY[\"EPSG\",\"9122\"]],"
+ " AUTHORITY[\"EPSG\",\"4055\"]],"
+ " PROJECTION[\"Mercator_1SP\"],"
+ " PARAMETER[\"central_meridian\",0],"
+ " PARAMETER[\"scale_factor\",1],"
+ " PARAMETER[\"false_easting\",0],"
+ " PARAMETER[\"false_northing\",0],"
+ " UNIT[\"metre\",1,"
+ " AUTHORITY[\"EPSG\",\"9001\"]],"
+ " AXIS[\"X\",EAST],"
+ " AXIS[\"Y\",NORTH],"
+ " EXTENSION[\"PROJ4\",\"+proj=merc +a=6378137 +b=6378137 "
+ "+lat_ts=0.0 +lon_0=0.0 +x_0=0.0 +y_0=0 +k=1.0 +units=m "
+ "+nadgrids=@null +wktext +no_defs\"]]";
+
+ auto obj = WKTParser().createFromWKT(wkt1);
+ auto crs = nn_dynamic_pointer_cast<ProjectedCRS>(obj);
+ ASSERT_TRUE(crs != nullptr);
+
+ EXPECT_EQ(crs->exportToPROJString(PROJStringFormatter::create().get()),
+ "+proj=pipeline +step +proj=axisswap +order=2,1 +step "
+ "+proj=unitconvert +xy_in=deg +xy_out=rad +step +proj=webmerc "
+ "+lat_0=0 +lon_0=0 +x_0=0 +y_0=0 "
+ "+ellps=WGS84");
+
+ EXPECT_EQ(
+ crs->exportToPROJString(
+ PROJStringFormatter::create(PROJStringFormatter::Convention::PROJ_4)
+ .get()),
+ "+proj=merc +a=6378137 +b=6378137 +lat_ts=0 +lon_0=0 +x_0=0 "
+ "+y_0=0 +k=1 +units=m +nadgrids=@null +wktext +no_defs");
+
+ auto convGot = crs->derivingConversion();
+
+ EXPECT_EQ(convGot->exportToWKT(WKTFormatter::create().get()),
+ "CONVERSION[\"unnamed\",\n"
+ " METHOD[\"Popular Visualisation Pseudo Mercator\",\n"
+ " ID[\"EPSG\",1024]],\n"
+ " PARAMETER[\"Latitude of natural origin\",0,\n"
+ " ANGLEUNIT[\"degree\",0.0174532925199433],\n"
+ " ID[\"EPSG\",8801]],\n"
+ " PARAMETER[\"Longitude of natural origin\",0,\n"
+ " ANGLEUNIT[\"degree\",0.0174532925199433],\n"
+ " ID[\"EPSG\",8802]],\n"
+ " PARAMETER[\"False easting\",0,\n"
+ " LENGTHUNIT[\"metre\",1],\n"
+ " ID[\"EPSG\",8806]],\n"
+ " PARAMETER[\"False northing\",0,\n"
+ " LENGTHUNIT[\"metre\",1],\n"
+ " ID[\"EPSG\",8807]]]");
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(operation, webmerc_import_from_WKT2_EPSG_3785_deprecated) {
+ auto wkt2 =
+ "PROJCRS[\"Popular Visualisation CRS / Mercator\",\n"
+ " BASEGEODCRS[\"Popular Visualisation CRS\",\n"
+ " DATUM[\"Popular Visualisation Datum\",\n"
+ " ELLIPSOID[\"Popular Visualisation Sphere\",6378137,0,\n"
+ " LENGTHUNIT[\"metre\",1]]],\n"
+ " PRIMEM[\"Greenwich\",0,\n"
+ " ANGLEUNIT[\"degree\",0.0174532925199433]]],\n"
+ " CONVERSION[\"Popular Visualisation Mercator\",\n"
+ " METHOD[\"Mercator (1SP) (Spherical)\",\n"
+ " ID[\"EPSG\",9841]],\n"
+ " PARAMETER[\"Latitude of natural origin\",0,\n"
+ " ANGLEUNIT[\"degree\",0.0174532925199433],\n"
+ " ID[\"EPSG\",8801]],\n"
+ " PARAMETER[\"Longitude of natural origin\",0,\n"
+ " ANGLEUNIT[\"degree\",0.0174532925199433],\n"
+ " ID[\"EPSG\",8802]],\n"
+ " PARAMETER[\"Scale factor at natural origin\",1,\n"
+ " SCALEUNIT[\"unity\",1],\n"
+ " ID[\"EPSG\",8805]],\n"
+ " PARAMETER[\"False easting\",0,\n"
+ " LENGTHUNIT[\"metre\",1],\n"
+ " ID[\"EPSG\",8806]],\n"
+ " PARAMETER[\"False northing\",0,\n"
+ " LENGTHUNIT[\"metre\",1],\n"
+ " ID[\"EPSG\",8807]]],\n"
+ " CS[Cartesian,2],\n"
+ " AXIS[\"easting (X)\",east,\n"
+ " ORDER[1],\n"
+ " LENGTHUNIT[\"metre\",1]],\n"
+ " AXIS[\"northing (Y)\",north,\n"
+ " ORDER[2],\n"
+ " LENGTHUNIT[\"metre\",1]]]";
+
+ auto obj = WKTParser().createFromWKT(wkt2);
+ auto crs = nn_dynamic_pointer_cast<ProjectedCRS>(obj);
+ ASSERT_TRUE(crs != nullptr);
+
+ EXPECT_EQ(crs->exportToPROJString(PROJStringFormatter::create().get()),
+ "+proj=pipeline +step +proj=axisswap +order=2,1 +step "
+ "+proj=unitconvert +xy_in=deg +xy_out=rad +step +proj=webmerc "
+ "+ellps=WGS84");
+
+ EXPECT_EQ(
+ crs->exportToPROJString(
+ PROJStringFormatter::create(PROJStringFormatter::Convention::PROJ_4)
+ .get()),
+ "+proj=merc +a=6378137 +b=6378137 +lat_ts=0 +lon_0=0 +x_0=0 "
+ "+y_0=0 +k=1 +units=m +nadgrids=@null +wktext +no_defs");
+
+ EXPECT_EQ(
+ crs->exportToWKT(
+ WKTFormatter::create(WKTFormatter::Convention::WKT2_2015).get()),
+ wkt2);
+
+ EXPECT_EQ(
+ crs->exportToWKT(
+ WKTFormatter::create(WKTFormatter::Convention::WKT1_GDAL).get()),
+ "PROJCS[\"Popular Visualisation CRS / Mercator\",\n"
+ " GEOGCS[\"Popular Visualisation CRS\",\n"
+ " DATUM[\"Popular_Visualisation_Datum\",\n"
+ " SPHEROID[\"Popular Visualisation Sphere\",6378137,0],\n"
+ " TOWGS84[0,0,0,0,0,0,0]],\n"
+ " PRIMEM[\"Greenwich\",0],\n"
+ " UNIT[\"degree\",0.0174532925199433],\n"
+ " AXIS[\"Latitude\",NORTH],\n"
+ " AXIS[\"Longitude\",EAST]],\n"
+ " PROJECTION[\"Mercator_1SP\"],\n"
+ " PARAMETER[\"central_meridian\",0],\n"
+ " PARAMETER[\"scale_factor\",1],\n"
+ " PARAMETER[\"false_easting\",0],\n"
+ " PARAMETER[\"false_northing\",0],\n"
+ " UNIT[\"metre\",1],\n"
+ " AXIS[\"Easting\",EAST],\n"
+ " AXIS[\"Northing\",NORTH],\n"
+ " EXTENSION[\"PROJ4\",\"+proj=merc +a=6378137 +b=6378137 +lat_ts=0 "
+ "+lon_0=0 +x_0=0 +y_0=0 +k=1 +units=m +nadgrids=@null +wktext "
+ "+no_defs\"]]");
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(operation, mollweide_export) {
+
+ auto conv = Conversion::createMollweide(PropertyMap(), Angle(1), Length(2),
+ Length(3));
+
+ EXPECT_EQ(conv->exportToPROJString(PROJStringFormatter::create().get()),
+ "+proj=moll +lon_0=1 +x_0=2 +y_0=3");
+
+ EXPECT_EQ(conv->exportToWKT(WKTFormatter::create().get()),
+ "CONVERSION[\"Mollweide\",\n"
+ " METHOD[\"Mollweide\"],\n"
+ " PARAMETER[\"Longitude of natural origin\",1,\n"
+ " ANGLEUNIT[\"degree\",0.0174532925199433],\n"
+ " ID[\"EPSG\",8802]],\n"
+ " PARAMETER[\"False easting\",2,\n"
+ " LENGTHUNIT[\"metre\",1],\n"
+ " ID[\"EPSG\",8806]],\n"
+ " PARAMETER[\"False northing\",3,\n"
+ " LENGTHUNIT[\"metre\",1],\n"
+ " ID[\"EPSG\",8807]]]");
+
+ EXPECT_EQ(
+ conv->exportToWKT(
+ WKTFormatter::create(WKTFormatter::Convention::WKT1_GDAL).get()),
+ "PROJECTION[\"Mollweide\"],\n"
+ "PARAMETER[\"central_meridian\",1],\n"
+ "PARAMETER[\"false_easting\",2],\n"
+ "PARAMETER[\"false_northing\",3]");
+}
+// ---------------------------------------------------------------------------
+
+TEST(operation, nzmg_export) {
+ auto conv = Conversion::createNewZealandMappingGrid(
+ PropertyMap(), Angle(1), Angle(2), Length(4), Length(5));
+
+ EXPECT_EQ(conv->exportToPROJString(PROJStringFormatter::create().get()),
+ "+proj=nzmg +lat_0=1 +lon_0=2 +x_0=4 +y_0=5");
+
+ EXPECT_EQ(conv->exportToWKT(WKTFormatter::create().get()),
+ "CONVERSION[\"New Zealand Map Grid\",\n"
+ " METHOD[\"New Zealand Map Grid\",\n"
+ " ID[\"EPSG\",9811]],\n"
+ " PARAMETER[\"Latitude of natural origin\",1,\n"
+ " ANGLEUNIT[\"degree\",0.0174532925199433],\n"
+ " ID[\"EPSG\",8801]],\n"
+ " PARAMETER[\"Longitude of natural origin\",2,\n"
+ " ANGLEUNIT[\"degree\",0.0174532925199433],\n"
+ " ID[\"EPSG\",8802]],\n"
+ " PARAMETER[\"False easting\",4,\n"
+ " LENGTHUNIT[\"metre\",1],\n"
+ " ID[\"EPSG\",8806]],\n"
+ " PARAMETER[\"False northing\",5,\n"
+ " LENGTHUNIT[\"metre\",1],\n"
+ " ID[\"EPSG\",8807]]]");
+
+ EXPECT_EQ(
+ conv->exportToWKT(
+ WKTFormatter::create(WKTFormatter::Convention::WKT1_GDAL).get()),
+ "PROJECTION[\"New_Zealand_Map_Grid\"],\n"
+ "PARAMETER[\"latitude_of_origin\",1],\n"
+ "PARAMETER[\"central_meridian\",2],\n"
+ "PARAMETER[\"false_easting\",4],\n"
+ "PARAMETER[\"false_northing\",5]");
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(operation, oblique_stereographic_export) {
+ auto conv = Conversion::createObliqueStereographic(
+ PropertyMap(), Angle(1), Angle(2), Scale(3), Length(4), Length(5));
+
+ EXPECT_EQ(conv->exportToPROJString(PROJStringFormatter::create().get()),
+ "+proj=sterea +lat_0=1 +lon_0=2 +k=3 +x_0=4 +y_0=5");
+
+ EXPECT_EQ(conv->exportToWKT(WKTFormatter::create().get()),
+ "CONVERSION[\"Oblique Stereographic\",\n"
+ " METHOD[\"Oblique Stereographic\",\n"
+ " ID[\"EPSG\",9809]],\n"
+ " PARAMETER[\"Latitude of natural origin\",1,\n"
+ " ANGLEUNIT[\"degree\",0.0174532925199433],\n"
+ " ID[\"EPSG\",8801]],\n"
+ " PARAMETER[\"Longitude of natural origin\",2,\n"
+ " ANGLEUNIT[\"degree\",0.0174532925199433],\n"
+ " ID[\"EPSG\",8802]],\n"
+ " PARAMETER[\"Scale factor at natural origin\",3,\n"
+ " SCALEUNIT[\"unity\",1],\n"
+ " ID[\"EPSG\",8805]],\n"
+ " PARAMETER[\"False easting\",4,\n"
+ " LENGTHUNIT[\"metre\",1],\n"
+ " ID[\"EPSG\",8806]],\n"
+ " PARAMETER[\"False northing\",5,\n"
+ " LENGTHUNIT[\"metre\",1],\n"
+ " ID[\"EPSG\",8807]]]");
+
+ EXPECT_EQ(
+ conv->exportToWKT(
+ WKTFormatter::create(WKTFormatter::Convention::WKT1_GDAL).get()),
+ "PROJECTION[\"Oblique_Stereographic\"],\n"
+ "PARAMETER[\"latitude_of_origin\",1],\n"
+ "PARAMETER[\"central_meridian\",2],\n"
+ "PARAMETER[\"scale_factor\",3],\n"
+ "PARAMETER[\"false_easting\",4],\n"
+ "PARAMETER[\"false_northing\",5]");
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(operation, orthographic_export) {
+ auto conv = Conversion::createOrthographic(PropertyMap(), Angle(1),
+ Angle(2), Length(4), Length(5));
+
+ EXPECT_EQ(conv->exportToPROJString(PROJStringFormatter::create().get()),
+ "+proj=ortho +lat_0=1 +lon_0=2 +x_0=4 +y_0=5");
+
+ EXPECT_EQ(conv->exportToWKT(WKTFormatter::create().get()),
+ "CONVERSION[\"Orthographic\",\n"
+ " METHOD[\"Orthographic\",\n"
+ " ID[\"EPSG\",9840]],\n"
+ " PARAMETER[\"Latitude of natural origin\",1,\n"
+ " ANGLEUNIT[\"degree\",0.0174532925199433],\n"
+ " ID[\"EPSG\",8801]],\n"
+ " PARAMETER[\"Longitude of natural origin\",2,\n"
+ " ANGLEUNIT[\"degree\",0.0174532925199433],\n"
+ " ID[\"EPSG\",8802]],\n"
+ " PARAMETER[\"False easting\",4,\n"
+ " LENGTHUNIT[\"metre\",1],\n"
+ " ID[\"EPSG\",8806]],\n"
+ " PARAMETER[\"False northing\",5,\n"
+ " LENGTHUNIT[\"metre\",1],\n"
+ " ID[\"EPSG\",8807]]]");
+
+ EXPECT_EQ(
+ conv->exportToWKT(
+ WKTFormatter::create(WKTFormatter::Convention::WKT1_GDAL).get()),
+ "PROJECTION[\"Orthographic\"],\n"
+ "PARAMETER[\"latitude_of_origin\",1],\n"
+ "PARAMETER[\"central_meridian\",2],\n"
+ "PARAMETER[\"false_easting\",4],\n"
+ "PARAMETER[\"false_northing\",5]");
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(operation, american_polyconic_export) {
+ auto conv = Conversion::createAmericanPolyconic(
+ PropertyMap(), Angle(1), Angle(2), Length(4), Length(5));
+
+ EXPECT_EQ(conv->exportToPROJString(PROJStringFormatter::create().get()),
+ "+proj=poly +lat_0=1 +lon_0=2 +x_0=4 +y_0=5");
+
+ EXPECT_EQ(conv->exportToWKT(WKTFormatter::create().get()),
+ "CONVERSION[\"American Polyconic\",\n"
+ " METHOD[\"American Polyconic\",\n"
+ " ID[\"EPSG\",9818]],\n"
+ " PARAMETER[\"Latitude of natural origin\",1,\n"
+ " ANGLEUNIT[\"degree\",0.0174532925199433],\n"
+ " ID[\"EPSG\",8801]],\n"
+ " PARAMETER[\"Longitude of natural origin\",2,\n"
+ " ANGLEUNIT[\"degree\",0.0174532925199433],\n"
+ " ID[\"EPSG\",8802]],\n"
+ " PARAMETER[\"False easting\",4,\n"
+ " LENGTHUNIT[\"metre\",1],\n"
+ " ID[\"EPSG\",8806]],\n"
+ " PARAMETER[\"False northing\",5,\n"
+ " LENGTHUNIT[\"metre\",1],\n"
+ " ID[\"EPSG\",8807]]]");
+
+ EXPECT_EQ(
+ conv->exportToWKT(
+ WKTFormatter::create(WKTFormatter::Convention::WKT1_GDAL).get()),
+ "PROJECTION[\"Polyconic\"],\n"
+ "PARAMETER[\"latitude_of_origin\",1],\n"
+ "PARAMETER[\"central_meridian\",2],\n"
+ "PARAMETER[\"false_easting\",4],\n"
+ "PARAMETER[\"false_northing\",5]");
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(operation, polar_stereographic_variant_A_export) {
+ auto conv = Conversion::createPolarStereographicVariantA(
+ PropertyMap(), Angle(90), Angle(2), Scale(3), Length(4), Length(5));
+
+ EXPECT_EQ(conv->exportToPROJString(PROJStringFormatter::create().get()),
+ "+proj=stere +lat_0=90 +lon_0=2 +k=3 +x_0=4 +y_0=5");
+
+ EXPECT_EQ(conv->exportToWKT(WKTFormatter::create().get()),
+ "CONVERSION[\"Polar Stereographic (variant A)\",\n"
+ " METHOD[\"Polar Stereographic (variant A)\",\n"
+ " ID[\"EPSG\",9810]],\n"
+ " PARAMETER[\"Latitude of natural origin\",90,\n"
+ " ANGLEUNIT[\"degree\",0.0174532925199433],\n"
+ " ID[\"EPSG\",8801]],\n"
+ " PARAMETER[\"Longitude of natural origin\",2,\n"
+ " ANGLEUNIT[\"degree\",0.0174532925199433],\n"
+ " ID[\"EPSG\",8802]],\n"
+ " PARAMETER[\"Scale factor at natural origin\",3,\n"
+ " SCALEUNIT[\"unity\",1],\n"
+ " ID[\"EPSG\",8805]],\n"
+ " PARAMETER[\"False easting\",4,\n"
+ " LENGTHUNIT[\"metre\",1],\n"
+ " ID[\"EPSG\",8806]],\n"
+ " PARAMETER[\"False northing\",5,\n"
+ " LENGTHUNIT[\"metre\",1],\n"
+ " ID[\"EPSG\",8807]]]");
+
+ EXPECT_EQ(
+ conv->exportToWKT(
+ WKTFormatter::create(WKTFormatter::Convention::WKT1_GDAL).get()),
+ "PROJECTION[\"Polar_Stereographic\"],\n"
+ "PARAMETER[\"latitude_of_origin\",90],\n"
+ "PARAMETER[\"central_meridian\",2],\n"
+ "PARAMETER[\"scale_factor\",3],\n"
+ "PARAMETER[\"false_easting\",4],\n"
+ "PARAMETER[\"false_northing\",5]");
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(operation, polar_stereographic_variant_B_export_positive_lat) {
+ auto conv = Conversion::createPolarStereographicVariantB(
+ PropertyMap(), Angle(70), Angle(2), Length(4), Length(5));
+
+ EXPECT_EQ(conv->exportToPROJString(PROJStringFormatter::create().get()),
+ "+proj=stere +lat_0=90 +lat_ts=70 +lon_0=2 +x_0=4 +y_0=5");
+
+ EXPECT_EQ(conv->exportToWKT(WKTFormatter::create().get()),
+ "CONVERSION[\"Polar Stereographic (variant B)\",\n"
+ " METHOD[\"Polar Stereographic (variant B)\",\n"
+ " ID[\"EPSG\",9829]],\n"
+ " PARAMETER[\"Latitude of standard parallel\",70,\n"
+ " ANGLEUNIT[\"degree\",0.0174532925199433],\n"
+ " ID[\"EPSG\",8832]],\n"
+ " PARAMETER[\"Longitude of origin\",2,\n"
+ " ANGLEUNIT[\"degree\",0.0174532925199433],\n"
+ " ID[\"EPSG\",8833]],\n"
+ " PARAMETER[\"False easting\",4,\n"
+ " LENGTHUNIT[\"metre\",1],\n"
+ " ID[\"EPSG\",8806]],\n"
+ " PARAMETER[\"False northing\",5,\n"
+ " LENGTHUNIT[\"metre\",1],\n"
+ " ID[\"EPSG\",8807]]]");
+
+ EXPECT_EQ(
+ conv->exportToWKT(
+ WKTFormatter::create(WKTFormatter::Convention::WKT1_GDAL).get()),
+ "PROJECTION[\"Polar_Stereographic\"],\n"
+ "PARAMETER[\"latitude_of_origin\",70],\n"
+ "PARAMETER[\"central_meridian\",2],\n"
+ "PARAMETER[\"false_easting\",4],\n"
+ "PARAMETER[\"false_northing\",5]");
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(operation, polar_stereographic_variant_B_export_negative_lat) {
+ auto conv = Conversion::createPolarStereographicVariantB(
+ PropertyMap(), Angle(-70), Angle(2), Length(4), Length(5));
+
+ EXPECT_EQ(conv->exportToPROJString(PROJStringFormatter::create().get()),
+ "+proj=stere +lat_0=-90 +lat_ts=-70 +lon_0=2 +x_0=4 +y_0=5");
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(operation, wkt1_import_polar_stereographic_variantA) {
+ auto wkt = "PROJCS[\"test\",\n"
+ " GEOGCS[\"WGS 84\",\n"
+ " DATUM[\"WGS 1984\",\n"
+ " SPHEROID[\"WGS 84\",6378137,298.257223563]],\n"
+ " PRIMEM[\"Greenwich\",0],\n"
+ " UNIT[\"degree\",0.0174532925199433]],\n"
+ " PROJECTION[\"Polar_Stereographic\"],\n"
+ " PARAMETER[\"latitude_of_origin\",-90],\n"
+ " PARAMETER[\"central_meridian\",2],\n"
+ " PARAMETER[\"scale_factor\",3],\n"
+ " PARAMETER[\"false_easting\",4],\n"
+ " PARAMETER[\"false_northing\",5]"
+ " UNIT[\"metre\",1]]";
+ auto obj = WKTParser().createFromWKT(wkt);
+ auto crs = nn_dynamic_pointer_cast<ProjectedCRS>(obj);
+ ASSERT_TRUE(crs != nullptr);
+
+ auto conversion = crs->derivingConversion();
+ auto convRef = Conversion::createPolarStereographicVariantA(
+ PropertyMap().set(IdentifiedObject::NAME_KEY, "unnamed"), Angle(-90),
+ Angle(2), Scale(3), Length(4), Length(5));
+
+ EXPECT_EQ(conversion->exportToWKT(WKTFormatter::create().get()),
+ convRef->exportToWKT(WKTFormatter::create().get()));
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(operation, wkt1_import_polar_stereographic_variantB) {
+ auto wkt = "PROJCS[\"test\",\n"
+ " GEOGCS[\"WGS 84\",\n"
+ " DATUM[\"WGS 1984\",\n"
+ " SPHEROID[\"WGS 84\",6378137,298.257223563]],\n"
+ " PRIMEM[\"Greenwich\",0],\n"
+ " UNIT[\"degree\",0.0174532925199433]],\n"
+ " PROJECTION[\"Polar_Stereographic\"],\n"
+ " PARAMETER[\"latitude_of_origin\",-70],\n"
+ " PARAMETER[\"central_meridian\",2],\n"
+ " PARAMETER[\"scale_factor\",1],\n"
+ " PARAMETER[\"false_easting\",4],\n"
+ " PARAMETER[\"false_northing\",5]"
+ " UNIT[\"metre\",1]]";
+ auto obj = WKTParser().createFromWKT(wkt);
+ auto crs = nn_dynamic_pointer_cast<ProjectedCRS>(obj);
+ ASSERT_TRUE(crs != nullptr);
+
+ auto conversion = crs->derivingConversion();
+ auto convRef = Conversion::createPolarStereographicVariantB(
+ PropertyMap().set(IdentifiedObject::NAME_KEY, "unnamed"), Angle(-70),
+ Angle(2), Length(4), Length(5));
+
+ EXPECT_EQ(conversion->exportToWKT(WKTFormatter::create().get()),
+ convRef->exportToWKT(WKTFormatter::create().get()));
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(operation, wkt1_import_polar_stereographic_ambiguous) {
+ auto wkt = "PROJCS[\"test\",\n"
+ " GEOGCS[\"WGS 84\",\n"
+ " DATUM[\"WGS 1984\",\n"
+ " SPHEROID[\"WGS 84\",6378137,298.257223563]],\n"
+ " PRIMEM[\"Greenwich\",0],\n"
+ " UNIT[\"degree\",0.0174532925199433]],\n"
+ " PROJECTION[\"Polar_Stereographic\"],\n"
+ " PARAMETER[\"latitude_of_origin\",-70],\n"
+ " PARAMETER[\"central_meridian\",2],\n"
+ " PARAMETER[\"scale_factor\",3],\n"
+ " PARAMETER[\"false_easting\",4],\n"
+ " PARAMETER[\"false_northing\",5]"
+ " UNIT[\"metre\",1]]";
+ auto obj = WKTParser().createFromWKT(wkt);
+ auto crs = nn_dynamic_pointer_cast<ProjectedCRS>(obj);
+ ASSERT_TRUE(crs != nullptr);
+
+ auto conversion = crs->derivingConversion();
+ EXPECT_EQ(conversion->method()->nameStr(), "Polar_Stereographic");
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(operation, wkt1_import_equivalent_parameters) {
+ auto wkt = "PROJCS[\"test\",\n"
+ " GEOGCS[\"WGS 84\",\n"
+ " DATUM[\"WGS 1984\",\n"
+ " SPHEROID[\"WGS 84\",6378137,298.257223563]],\n"
+ " PRIMEM[\"Greenwich\",0],\n"
+ " UNIT[\"degree\",0.0174532925199433]],\n"
+ " PROJECTION[\"Hotine Oblique Mercator Two Point Natural "
+ "Origin\"],\n"
+ " PARAMETER[\"latitude_of_origin\",1],\n"
+ " PARAMETER[\"Latitude_Of_1st_Point\",2],\n"
+ " PARAMETER[\"Longitude_Of_1st_Point\",3],\n"
+ " PARAMETER[\"Latitude_Of_2nd_Point\",4],\n"
+ " PARAMETER[\"Longitude_Of 2nd_Point\",5],\n"
+ " PARAMETER[\"scale_factor\",6],\n"
+ " PARAMETER[\"false_easting\",7],\n"
+ " PARAMETER[\"false_northing\",8],\n"
+ " UNIT[\"metre\",1]]";
+ auto obj = WKTParser().createFromWKT(wkt);
+ auto crs = nn_dynamic_pointer_cast<ProjectedCRS>(obj);
+ ASSERT_TRUE(crs != nullptr);
+
+ auto conversion = crs->derivingConversion();
+ auto convRef = Conversion::createHotineObliqueMercatorTwoPointNaturalOrigin(
+ PropertyMap(), Angle(1), Angle(2), Angle(3), Angle(4), Angle(5),
+ Scale(6), Length(7), Length(8));
+
+ EXPECT_EQ(
+ conversion->exportToWKT(
+ WKTFormatter::create(WKTFormatter::Convention::WKT1_GDAL).get()),
+ convRef->exportToWKT(
+ WKTFormatter::create(WKTFormatter::Convention::WKT1_GDAL).get()));
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(operation, robinson_export) {
+
+ auto conv = Conversion::createRobinson(PropertyMap(), Angle(1), Length(2),
+ Length(3));
+
+ EXPECT_EQ(conv->exportToPROJString(PROJStringFormatter::create().get()),
+ "+proj=robin +lon_0=1 +x_0=2 +y_0=3");
+
+ EXPECT_EQ(conv->exportToWKT(WKTFormatter::create().get()),
+ "CONVERSION[\"Robinson\",\n"
+ " METHOD[\"Robinson\"],\n"
+ " PARAMETER[\"Longitude of natural origin\",1,\n"
+ " ANGLEUNIT[\"degree\",0.0174532925199433],\n"
+ " ID[\"EPSG\",8802]],\n"
+ " PARAMETER[\"False easting\",2,\n"
+ " LENGTHUNIT[\"metre\",1],\n"
+ " ID[\"EPSG\",8806]],\n"
+ " PARAMETER[\"False northing\",3,\n"
+ " LENGTHUNIT[\"metre\",1],\n"
+ " ID[\"EPSG\",8807]]]");
+
+ EXPECT_EQ(
+ conv->exportToWKT(
+ WKTFormatter::create(WKTFormatter::Convention::WKT1_GDAL).get()),
+ "PROJECTION[\"Robinson\"],\n"
+ "PARAMETER[\"longitude_of_center\",1],\n"
+ "PARAMETER[\"false_easting\",2],\n"
+ "PARAMETER[\"false_northing\",3]");
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(operation, sinusoidal_export) {
+
+ auto conv = Conversion::createSinusoidal(PropertyMap(), Angle(1), Length(2),
+ Length(3));
+
+ EXPECT_EQ(conv->exportToPROJString(PROJStringFormatter::create().get()),
+ "+proj=sinu +lon_0=1 +x_0=2 +y_0=3");
+
+ EXPECT_EQ(conv->exportToWKT(WKTFormatter::create().get()),
+ "CONVERSION[\"Sinusoidal\",\n"
+ " METHOD[\"Sinusoidal\"],\n"
+ " PARAMETER[\"Longitude of natural origin\",1,\n"
+ " ANGLEUNIT[\"degree\",0.0174532925199433],\n"
+ " ID[\"EPSG\",8802]],\n"
+ " PARAMETER[\"False easting\",2,\n"
+ " LENGTHUNIT[\"metre\",1],\n"
+ " ID[\"EPSG\",8806]],\n"
+ " PARAMETER[\"False northing\",3,\n"
+ " LENGTHUNIT[\"metre\",1],\n"
+ " ID[\"EPSG\",8807]]]");
+
+ EXPECT_EQ(
+ conv->exportToWKT(
+ WKTFormatter::create(WKTFormatter::Convention::WKT1_GDAL).get()),
+ "PROJECTION[\"Sinusoidal\"],\n"
+ "PARAMETER[\"longitude_of_center\",1],\n"
+ "PARAMETER[\"false_easting\",2],\n"
+ "PARAMETER[\"false_northing\",3]");
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(operation, stereographic_export) {
+ auto conv = Conversion::createStereographic(
+ PropertyMap(), Angle(1), Angle(2), Scale(3), Length(4), Length(5));
+
+ EXPECT_EQ(conv->exportToPROJString(PROJStringFormatter::create().get()),
+ "+proj=stere +lat_0=1 +lon_0=2 +k=3 +x_0=4 +y_0=5");
+
+ EXPECT_EQ(conv->exportToWKT(WKTFormatter::create().get()),
+ "CONVERSION[\"Stereographic\",\n"
+ " METHOD[\"Stereographic\"],\n"
+ " PARAMETER[\"Latitude of natural origin\",1,\n"
+ " ANGLEUNIT[\"degree\",0.0174532925199433],\n"
+ " ID[\"EPSG\",8801]],\n"
+ " PARAMETER[\"Longitude of natural origin\",2,\n"
+ " ANGLEUNIT[\"degree\",0.0174532925199433],\n"
+ " ID[\"EPSG\",8802]],\n"
+ " PARAMETER[\"Scale factor at natural origin\",3,\n"
+ " SCALEUNIT[\"unity\",1],\n"
+ " ID[\"EPSG\",8805]],\n"
+ " PARAMETER[\"False easting\",4,\n"
+ " LENGTHUNIT[\"metre\",1],\n"
+ " ID[\"EPSG\",8806]],\n"
+ " PARAMETER[\"False northing\",5,\n"
+ " LENGTHUNIT[\"metre\",1],\n"
+ " ID[\"EPSG\",8807]]]");
+
+ EXPECT_EQ(
+ conv->exportToWKT(
+ WKTFormatter::create(WKTFormatter::Convention::WKT1_GDAL).get()),
+ "PROJECTION[\"Stereographic\"],\n"
+ "PARAMETER[\"latitude_of_origin\",1],\n"
+ "PARAMETER[\"central_meridian\",2],\n"
+ "PARAMETER[\"scale_factor\",3],\n"
+ "PARAMETER[\"false_easting\",4],\n"
+ "PARAMETER[\"false_northing\",5]");
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(operation, vandergrinten_export) {
+
+ auto conv = Conversion::createVanDerGrinten(PropertyMap(), Angle(1),
+ Length(2), Length(3));
+
+ EXPECT_EQ(conv->exportToPROJString(PROJStringFormatter::create().get()),
+ "+proj=vandg +R_A +lon_0=1 +x_0=2 +y_0=3");
+
+ EXPECT_EQ(conv->exportToWKT(WKTFormatter::create().get()),
+ "CONVERSION[\"Van Der Grinten\",\n"
+ " METHOD[\"Van Der Grinten\"],\n"
+ " PARAMETER[\"Longitude of natural origin\",1,\n"
+ " ANGLEUNIT[\"degree\",0.0174532925199433],\n"
+ " ID[\"EPSG\",8802]],\n"
+ " PARAMETER[\"False easting\",2,\n"
+ " LENGTHUNIT[\"metre\",1],\n"
+ " ID[\"EPSG\",8806]],\n"
+ " PARAMETER[\"False northing\",3,\n"
+ " LENGTHUNIT[\"metre\",1],\n"
+ " ID[\"EPSG\",8807]]]");
+
+ EXPECT_EQ(
+ conv->exportToWKT(
+ WKTFormatter::create(WKTFormatter::Convention::WKT1_GDAL).get()),
+ "PROJECTION[\"VanDerGrinten\"],\n"
+ "PARAMETER[\"central_meridian\",1],\n"
+ "PARAMETER[\"false_easting\",2],\n"
+ "PARAMETER[\"false_northing\",3]");
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(operation, wagner_export) {
+
+ std::vector<std::string> numbers{"", "1", "2", "3", "4", "5", "6", "7"};
+ std::vector<std::string> latinNumbers{"", "I", "II", "III",
+ "IV", "V", "VI", "VII"};
+
+ for (int i = 1; i <= 7; i++) {
+ if (i == 3)
+ continue;
+ auto conv =
+ (i == 1)
+ ? Conversion::createWagnerI(PropertyMap(), Angle(1), Length(2),
+ Length(3))
+ : (i == 2)
+ ? Conversion::createWagnerII(PropertyMap(), Angle(1),
+ Length(2), Length(3))
+ : (i == 4)
+ ? Conversion::createWagnerIV(
+ PropertyMap(), Angle(1), Length(2), Length(3))
+ : (i == 5) ? Conversion::createWagnerV(
+ PropertyMap(), Angle(1), Length(2),
+ Length(3))
+ : (i == 6) ?
+
+ Conversion::createWagnerVI(
+ PropertyMap(), Angle(1),
+ Length(2), Length(3))
+ :
+
+ Conversion::createWagnerVII(
+ PropertyMap(), Angle(1),
+ Length(2), Length(3));
+
+ EXPECT_EQ(conv->exportToPROJString(PROJStringFormatter::create().get()),
+ "+proj=wag" + numbers[i] + " +lon_0=1 +x_0=2 +y_0=3");
+
+ auto formatter = WKTFormatter::create();
+ formatter->simulCurNodeHasId();
+ EXPECT_EQ(conv->exportToWKT(formatter.get()),
+ "CONVERSION[\"Wagner " + latinNumbers[i] +
+ "\",\n"
+ " METHOD[\"Wagner " +
+ latinNumbers[i] +
+ "\"],\n"
+ " PARAMETER[\"Longitude of natural origin\",1,\n"
+ " ANGLEUNIT[\"degree\",0.0174532925199433],\n"
+ " ID[\"EPSG\",8802]],\n"
+ " PARAMETER[\"False easting\",2,\n"
+ " LENGTHUNIT[\"metre\",1],\n"
+ " ID[\"EPSG\",8806]],\n"
+ " PARAMETER[\"False northing\",3,\n"
+ " LENGTHUNIT[\"metre\",1],\n"
+ " ID[\"EPSG\",8807]]]");
+
+ EXPECT_EQ(conv->exportToWKT(
+ WKTFormatter::create(WKTFormatter::Convention::WKT1_GDAL)
+ .get()),
+ "PROJECTION[\"Wagner_" + latinNumbers[i] +
+ "\"],\n"
+ "PARAMETER[\"central_meridian\",1],\n"
+ "PARAMETER[\"false_easting\",2],\n"
+ "PARAMETER[\"false_northing\",3]");
+ }
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(operation, wagnerIII_export) {
+
+ auto conv = Conversion::createWagnerIII(PropertyMap(), Angle(1), Angle(2),
+ Length(3), Length(4));
+
+ EXPECT_EQ(conv->exportToPROJString(PROJStringFormatter::create().get()),
+ "+proj=wag3 +lat_ts=1 +lon_0=2 +x_0=3 +y_0=4");
+
+ auto formatter = WKTFormatter::create();
+ formatter->simulCurNodeHasId();
+ EXPECT_EQ(conv->exportToWKT(formatter.get()),
+ "CONVERSION[\"Wagner III\",\n"
+ " METHOD[\"Wagner III\"],\n"
+ " PARAMETER[\"Latitude of true scale\",1,\n"
+ " ANGLEUNIT[\"degree\",0.0174532925199433]],\n"
+ " PARAMETER[\"Longitude of natural origin\",2,\n"
+ " ANGLEUNIT[\"degree\",0.0174532925199433],\n"
+ " ID[\"EPSG\",8802]],\n"
+ " PARAMETER[\"False easting\",3,\n"
+ " LENGTHUNIT[\"metre\",1],\n"
+ " ID[\"EPSG\",8806]],\n"
+ " PARAMETER[\"False northing\",4,\n"
+ " LENGTHUNIT[\"metre\",1],\n"
+ " ID[\"EPSG\",8807]]]");
+
+ EXPECT_EQ(
+ conv->exportToWKT(
+ WKTFormatter::create(WKTFormatter::Convention::WKT1_GDAL).get()),
+ "PROJECTION[\"Wagner_III\"],\n"
+ "PARAMETER[\"latitude_of_origin\",1],\n"
+ "PARAMETER[\"central_meridian\",2],\n"
+ "PARAMETER[\"false_easting\",3],\n"
+ "PARAMETER[\"false_northing\",4]");
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(operation, qsc_export) {
+
+ auto conv = Conversion::createQuadrilateralizedSphericalCube(
+ PropertyMap(), Angle(1), Angle(2), Length(3), Length(4));
+
+ EXPECT_EQ(conv->exportToPROJString(PROJStringFormatter::create().get()),
+ "+proj=qsc +lat_0=1 +lon_0=2 +x_0=3 +y_0=4");
+
+ EXPECT_EQ(conv->exportToWKT(WKTFormatter::create().get()),
+ "CONVERSION[\"Quadrilateralized Spherical Cube\",\n"
+ " METHOD[\"Quadrilateralized Spherical Cube\"],\n"
+ " PARAMETER[\"Latitude of natural origin\",1,\n"
+ " ANGLEUNIT[\"degree\",0.0174532925199433],\n"
+ " ID[\"EPSG\",8801]],\n"
+ " PARAMETER[\"Longitude of natural origin\",2,\n"
+ " ANGLEUNIT[\"degree\",0.0174532925199433],\n"
+ " ID[\"EPSG\",8802]],\n"
+ " PARAMETER[\"False easting\",3,\n"
+ " LENGTHUNIT[\"metre\",1],\n"
+ " ID[\"EPSG\",8806]],\n"
+ " PARAMETER[\"False northing\",4,\n"
+ " LENGTHUNIT[\"metre\",1],\n"
+ " ID[\"EPSG\",8807]]]");
+
+ EXPECT_EQ(
+ conv->exportToWKT(
+ WKTFormatter::create(WKTFormatter::Convention::WKT1_GDAL).get()),
+ "PROJECTION[\"Quadrilateralized_Spherical_Cube\"],\n"
+ "PARAMETER[\"latitude_of_origin\",1],\n"
+ "PARAMETER[\"central_meridian\",2],\n"
+ "PARAMETER[\"false_easting\",3],\n"
+ "PARAMETER[\"false_northing\",4]");
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(operation, sch_export) {
+
+ auto conv = Conversion::createSphericalCrossTrackHeight(
+ PropertyMap(), Angle(1), Angle(2), Angle(3), Length(4));
+
+ EXPECT_EQ(conv->exportToPROJString(PROJStringFormatter::create().get()),
+ "+proj=sch +plat_0=1 +plon_0=2 +phdg_0=3 +h_0=4");
+
+ auto formatter = WKTFormatter::create();
+ formatter->simulCurNodeHasId();
+ EXPECT_EQ(conv->exportToWKT(formatter.get()),
+ "CONVERSION[\"Spherical Cross-Track Height\",\n"
+ " METHOD[\"Spherical Cross-Track Height\"],\n"
+ " PARAMETER[\"Peg point latitude\",1,\n"
+ " ANGLEUNIT[\"degree\",0.0174532925199433]],\n"
+ " PARAMETER[\"Peg point longitude\",2,\n"
+ " ANGLEUNIT[\"degree\",0.0174532925199433]],\n"
+ " PARAMETER[\"Peg point heading\",3,\n"
+ " ANGLEUNIT[\"degree\",0.0174532925199433]],\n"
+ " PARAMETER[\"Peg point height\",4,\n"
+ " LENGTHUNIT[\"metre\",1]]]");
+
+ EXPECT_EQ(
+ conv->exportToWKT(
+ WKTFormatter::create(WKTFormatter::Convention::WKT1_GDAL).get()),
+ "PROJECTION[\"Spherical_Cross_Track_Height\"],\n"
+ "PARAMETER[\"peg_point_latitude\",1],\n"
+ "PARAMETER[\"peg_point_longitude\",2],\n"
+ "PARAMETER[\"peg_point_heading\",3],\n"
+ "PARAMETER[\"peg_point_height\",4]");
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(operation, conversion_inverse) {
+ auto conv = Conversion::createTransverseMercator(
+ PropertyMap(), Angle(1), Angle(2), Scale(3), Length(4), Length(5));
+ auto inv = conv->inverse();
+ EXPECT_EQ(inv->inverse(), conv);
+ EXPECT_EQ(inv->exportToWKT(WKTFormatter::create().get()),
+ "CONVERSION[\"Inverse of Transverse Mercator\",\n"
+ " METHOD[\"Inverse of Transverse Mercator\",\n"
+ " ID[\"INVERSE(EPSG)\",9807]],\n"
+ " PARAMETER[\"Latitude of natural origin\",1,\n"
+ " ANGLEUNIT[\"degree\",0.0174532925199433],\n"
+ " ID[\"EPSG\",8801]],\n"
+ " PARAMETER[\"Longitude of natural origin\",2,\n"
+ " ANGLEUNIT[\"degree\",0.0174532925199433],\n"
+ " ID[\"EPSG\",8802]],\n"
+ " PARAMETER[\"Scale factor at natural origin\",3,\n"
+ " SCALEUNIT[\"unity\",1],\n"
+ " ID[\"EPSG\",8805]],\n"
+ " PARAMETER[\"False easting\",4,\n"
+ " LENGTHUNIT[\"metre\",1],\n"
+ " ID[\"EPSG\",8806]],\n"
+ " PARAMETER[\"False northing\",5,\n"
+ " LENGTHUNIT[\"metre\",1],\n"
+ " ID[\"EPSG\",8807]]]");
+
+ EXPECT_EQ(inv->exportToPROJString(PROJStringFormatter::create().get()),
+ "+proj=pipeline +step +inv +proj=tmerc +lat_0=1 +lon_0=2 +k_0=3 "
+ "+x_0=4 +y_0=5");
+
+ EXPECT_TRUE(inv->isEquivalentTo(inv.get()));
+ EXPECT_FALSE(inv->isEquivalentTo(createUnrelatedObject().get()));
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(operation, eqearth_export) {
+
+ auto conv = Conversion::createEqualEarth(PropertyMap(), Angle(1), Length(2),
+ Length(3));
+
+ EXPECT_EQ(conv->exportToPROJString(PROJStringFormatter::create().get()),
+ "+proj=eqearth +lon_0=1 +x_0=2 +y_0=3");
+
+ EXPECT_EQ(conv->exportToWKT(WKTFormatter::create().get()),
+ "CONVERSION[\"Equal Earth\",\n"
+ " METHOD[\"Equal Earth\",\n"
+ " ID[\"EPSG\",1078]],\n"
+ " PARAMETER[\"Longitude of natural origin\",1,\n"
+ " ANGLEUNIT[\"degree\",0.0174532925199433],\n"
+ " ID[\"EPSG\",8802]],\n"
+ " PARAMETER[\"False easting\",2,\n"
+ " LENGTHUNIT[\"metre\",1],\n"
+ " ID[\"EPSG\",8806]],\n"
+ " PARAMETER[\"False northing\",3,\n"
+ " LENGTHUNIT[\"metre\",1],\n"
+ " ID[\"EPSG\",8807]]]");
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(operation, laborde_oblique_mercator) {
+
+ // Content of EPSG:29701 "Tananarive (Paris) / Laborde Grid"
+ auto projString = "+proj=pipeline +step +proj=axisswap +order=2,1 +step "
+ "+proj=unitconvert +xy_in=grad +xy_out=rad +step +inv "
+ "+proj=longlat +ellps=intl +pm=paris +step +proj=labrd "
+ "+lat_0=-18.9 +lon_0=44.1 +azi=18.9 +k=0.9995 "
+ "+x_0=400000 +y_0=800000 +ellps=intl +pm=paris +step "
+ "+proj=axisswap +order=2,1";
+ auto obj = PROJStringParser().createFromPROJString(projString);
+ auto crs = nn_dynamic_pointer_cast<ProjectedCRS>(obj);
+ ASSERT_TRUE(crs != nullptr);
+ EXPECT_EQ(crs->exportToPROJString(PROJStringFormatter::create().get()),
+ projString);
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(operation, PROJ_based) {
+ auto conv = SingleOperation::createPROJBased(PropertyMap(), "+proj=merc",
+ nullptr, nullptr);
+
+ EXPECT_EQ(conv->exportToPROJString(PROJStringFormatter::create().get()),
+ "+proj=merc");
+
+ EXPECT_EQ(conv->exportToWKT(WKTFormatter::create().get()),
+ "CONVERSION[\"PROJ-based coordinate operation\",\n"
+ " METHOD[\"PROJ-based operation method\"],\n"
+ " PARAMETER[\"PROJ string\",\"+proj=merc\"]]");
+
+ EXPECT_EQ(conv->inverse()->exportToPROJString(
+ PROJStringFormatter::create().get()),
+ "+proj=pipeline +step +inv +proj=merc");
+
+ auto str = "+proj=pipeline +step +proj=unitconvert +xy_in=grad +xy_out=rad "
+ "+step +proj=axisswap +order=2,1 +step +proj=longlat "
+ "+ellps=clrk80ign +pm=paris +step +proj=axisswap +order=2,1";
+ EXPECT_EQ(
+ SingleOperation::createPROJBased(PropertyMap(), str, nullptr, nullptr)
+ ->exportToPROJString(PROJStringFormatter::create().get()),
+ str);
+
+ EXPECT_THROW(SingleOperation::createPROJBased(PropertyMap(), "+inv",
+ nullptr, nullptr)
+ ->exportToPROJString(PROJStringFormatter::create().get()),
+ FormattingException);
+ EXPECT_THROW(
+ SingleOperation::createPROJBased(PropertyMap(), "foo", nullptr, nullptr)
+ ->exportToPROJString(PROJStringFormatter::create().get()),
+ FormattingException);
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(operation, PROJ_based_empty) {
+ auto conv =
+ SingleOperation::createPROJBased(PropertyMap(), "", nullptr, nullptr);
+
+ EXPECT_EQ(conv->exportToPROJString(PROJStringFormatter::create().get()),
+ "");
+
+ EXPECT_EQ(conv->exportToWKT(WKTFormatter::create().get()),
+ "CONVERSION[\"PROJ-based coordinate operation\",\n"
+ " METHOD[\"PROJ-based operation method\"],\n"
+ " PARAMETER[\"PROJ string\",\"\"]]");
+
+ EXPECT_THROW(
+ conv->exportToWKT(
+ WKTFormatter::create(WKTFormatter::Convention::WKT1_GDAL).get()),
+ FormattingException);
+
+ EXPECT_EQ(conv->inverse()->exportToPROJString(
+ PROJStringFormatter::create().get()),
+ "");
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(operation, PROJ_based_with_global_parameters) {
+ auto conv = SingleOperation::createPROJBased(
+ PropertyMap(), "+proj=pipeline +ellps=WGS84 +step +proj=longlat",
+ nullptr, nullptr);
+
+ EXPECT_EQ(conv->exportToPROJString(PROJStringFormatter::create().get()),
+ "+proj=pipeline +ellps=WGS84 +step +proj=longlat");
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(operation, geogCRS_to_geogCRS) {
+
+ auto op = CoordinateOperationFactory::create()->createOperation(
+ GeographicCRS::EPSG_4807, GeographicCRS::EPSG_4326);
+ ASSERT_TRUE(op != nullptr);
+ EXPECT_EQ(
+ op->exportToPROJString(PROJStringFormatter::create().get()),
+ "+proj=pipeline +step +proj=axisswap +order=2,1 +step "
+ "+proj=unitconvert +xy_in=grad +xy_out=rad +step +inv +proj=longlat "
+ "+ellps=clrk80ign +pm=paris +step +proj=unitconvert +xy_in=rad "
+ "+xy_out=deg +step +proj=axisswap +order=2,1");
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(operation, geogCRS_to_geogCRS_context_default) {
+ auto authFactory =
+ AuthorityFactory::create(DatabaseContext::create(), "EPSG");
+ auto ctxt = CoordinateOperationContext::create(authFactory, nullptr, 0);
+ ctxt->setSpatialCriterion(
+ CoordinateOperationContext::SpatialCriterion::PARTIAL_INTERSECTION);
+ ctxt->setAllowUseIntermediateCRS(false);
+
+ // Directly found in database
+ {
+ auto list = CoordinateOperationFactory::create()->createOperations(
+ authFactory->createCoordinateReferenceSystem("4179"), // Pulkovo 42
+ authFactory->createCoordinateReferenceSystem("4258"), // ETRS89
+ ctxt);
+ ASSERT_EQ(list.size(), 2);
+ // Romania has a larger area than Poland (given our approx formula)
+ EXPECT_EQ(list[0]->getEPSGCode(), 15994); // Romania - 3m
+ EXPECT_EQ(list[1]->getEPSGCode(), 1644); // Poland - 1m
+
+ EXPECT_EQ(
+ list[0]->exportToPROJString(PROJStringFormatter::create().get()),
+ "+proj=pipeline +step +proj=axisswap +order=2,1 +step "
+ "+proj=unitconvert +xy_in=deg +xy_out=rad +step +proj=cart "
+ "+ellps=krass +step +proj=helmert +x=2.3287 +y=-147.0425 "
+ "+z=-92.0802 +rx=0.3092483 +ry=-0.32482185 +rz=-0.49729934 "
+ "+s=5.68906266 +convention=coordinate_frame +step +inv "
+ "+proj=cart +ellps=GRS80 +step +proj=unitconvert +xy_in=rad "
+ "+xy_out=deg +step +proj=axisswap +order=2,1");
+ }
+
+ // Reverse case
+ {
+ auto list = CoordinateOperationFactory::create()->createOperations(
+ authFactory->createCoordinateReferenceSystem("4258"),
+ authFactory->createCoordinateReferenceSystem("4179"), ctxt);
+ ASSERT_EQ(list.size(), 2);
+ // Romania has a larger area than Poland (given our approx formula)
+ EXPECT_EQ(list[0]->nameStr(),
+ "Inverse of Pulkovo 1942(58) to ETRS89 (4)"); // Romania - 3m
+
+ EXPECT_EQ(
+ list[0]->exportToPROJString(PROJStringFormatter::create().get()),
+ "+proj=pipeline +step +proj=axisswap +order=2,1 +step "
+ "+proj=unitconvert +xy_in=deg +xy_out=rad +step +proj=cart "
+ "+ellps=GRS80 +step +inv +proj=helmert +x=2.3287 "
+ "+y=-147.0425 +z=-92.0802 +rx=0.3092483 +ry=-0.32482185 "
+ "+rz=-0.49729934 +s=5.68906266 +convention=coordinate_frame "
+ "+step +inv +proj=cart +ellps=krass +step +proj=unitconvert "
+ "+xy_in=rad +xy_out=deg +step +proj=axisswap +order=2,1");
+ }
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(operation, geogCRS_to_geogCRS_context_filter_accuracy) {
+ auto authFactory =
+ AuthorityFactory::create(DatabaseContext::create(), "EPSG");
+ {
+ auto ctxt =
+ CoordinateOperationContext::create(authFactory, nullptr, 1.0);
+ ctxt->setSpatialCriterion(
+ CoordinateOperationContext::SpatialCriterion::PARTIAL_INTERSECTION);
+
+ auto list = CoordinateOperationFactory::create()->createOperations(
+ authFactory->createCoordinateReferenceSystem("4179"),
+ authFactory->createCoordinateReferenceSystem("4258"), ctxt);
+ ASSERT_EQ(list.size(), 1);
+ EXPECT_EQ(list[0]->getEPSGCode(), 1644); // Poland - 1m
+ }
+ {
+ auto ctxt =
+ CoordinateOperationContext::create(authFactory, nullptr, 0.9);
+ ctxt->setSpatialCriterion(
+ CoordinateOperationContext::SpatialCriterion::PARTIAL_INTERSECTION);
+
+ auto list = CoordinateOperationFactory::create()->createOperations(
+ authFactory->createCoordinateReferenceSystem("4179"),
+ authFactory->createCoordinateReferenceSystem("4258"), ctxt);
+ ASSERT_EQ(list.size(), 0);
+ }
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(operation, geogCRS_to_geogCRS_context_filter_bbox) {
+ auto authFactory =
+ AuthorityFactory::create(DatabaseContext::create(), "EPSG");
+ // INSERT INTO "area" VALUES('EPSG','1197','Romania','Romania - onshore and
+ // offshore.',43.44,48.27,20.26,31.41,0);
+ {
+ auto ctxt = CoordinateOperationContext::create(
+ authFactory, Extent::createFromBBOX(20.26, 43.44, 31.41, 48.27),
+ 0.0);
+ auto list = CoordinateOperationFactory::create()->createOperations(
+ authFactory->createCoordinateReferenceSystem("4179"),
+ authFactory->createCoordinateReferenceSystem("4258"), ctxt);
+ ASSERT_EQ(list.size(), 1);
+ EXPECT_EQ(list[0]->getEPSGCode(), 15994); // Romania - 3m
+ }
+ {
+ auto ctxt = CoordinateOperationContext::create(
+ authFactory, Extent::createFromBBOX(20.26 + .1, 43.44 + .1,
+ 31.41 - .1, 48.27 - .1),
+ 0.0);
+ auto list = CoordinateOperationFactory::create()->createOperations(
+ authFactory->createCoordinateReferenceSystem("4179"),
+ authFactory->createCoordinateReferenceSystem("4258"), ctxt);
+ ASSERT_EQ(list.size(), 1);
+ EXPECT_EQ(list[0]->getEPSGCode(), 15994); // Romania - 3m
+ }
+ {
+ auto ctxt = CoordinateOperationContext::create(
+ authFactory, Extent::createFromBBOX(20.26 - .1, 43.44 - .1,
+ 31.41 + .1, 48.27 + .1),
+ 0.0);
+ auto list = CoordinateOperationFactory::create()->createOperations(
+ authFactory->createCoordinateReferenceSystem("4179"),
+ authFactory->createCoordinateReferenceSystem("4258"), ctxt);
+ ASSERT_EQ(list.size(), 1);
+ EXPECT_EQ(
+ list[0]->exportToPROJString(PROJStringFormatter::create().get()),
+ "");
+ }
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(operation, geogCRS_to_geogCRS_context_incompatible_area) {
+ auto authFactory =
+ AuthorityFactory::create(DatabaseContext::create(), "EPSG");
+ auto ctxt = CoordinateOperationContext::create(authFactory, nullptr, 0.0);
+ auto list = CoordinateOperationFactory::create()->createOperations(
+ authFactory->createCoordinateReferenceSystem("4267"), // NAD27
+ authFactory->createCoordinateReferenceSystem("4258"), // ETRS 89
+ ctxt);
+ ASSERT_EQ(list.size(), 1);
+ EXPECT_EQ(list[0]->exportToPROJString(PROJStringFormatter::create().get()),
+ "");
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(operation, geogCRS_to_geogCRS_context_inverse_needed) {
+ auto authFactory =
+ AuthorityFactory::create(DatabaseContext::create(), "EPSG");
+ {
+ auto ctxt =
+ CoordinateOperationContext::create(authFactory, nullptr, 0.0);
+ ctxt->setUsePROJAlternativeGridNames(false);
+ auto list = CoordinateOperationFactory::create()->createOperations(
+ authFactory->createCoordinateReferenceSystem("4275"), // NTF
+ authFactory->createCoordinateReferenceSystem("4258"), // ETRS89
+ ctxt);
+ ASSERT_EQ(list.size(), 3);
+ EXPECT_EQ(
+ list[0]->exportToPROJString(PROJStringFormatter::create().get()),
+ "+proj=pipeline +step +proj=axisswap +order=2,1 +step "
+ "+proj=unitconvert +xy_in=deg +xy_out=rad +step +proj=cart "
+ "+ellps=clrk80ign +step +proj=helmert +x=-168 +y=-60 +z=320 "
+ "+step +inv +proj=cart +ellps=GRS80 +step +proj=unitconvert "
+ "+xy_in=rad +xy_out=deg +step +proj=axisswap +order=2,1");
+ EXPECT_EQ(list[1]->exportToPROJString(
+ PROJStringFormatter::create(
+ PROJStringFormatter::Convention::PROJ_5,
+ authFactory->databaseContext())
+ .get()),
+ "");
+ EXPECT_EQ(list[2]->exportToPROJString(
+ PROJStringFormatter::create(
+ PROJStringFormatter::Convention::PROJ_5,
+ authFactory->databaseContext())
+ .get()),
+ "+proj=pipeline +step +proj=axisswap +order=2,1 +step "
+ "+proj=unitconvert +xy_in=deg +xy_out=rad +step "
+ "+proj=hgridshift +grids=ntf_r93.gsb +step +proj=unitconvert "
+ "+xy_in=rad +xy_out=deg +step +proj=axisswap +order=2,1");
+ }
+ {
+ auto ctxt =
+ CoordinateOperationContext::create(authFactory, nullptr, 0.0);
+ auto list = CoordinateOperationFactory::create()->createOperations(
+ authFactory->createCoordinateReferenceSystem("4275"), // NTF
+ authFactory->createCoordinateReferenceSystem("4258"), // ETRS89
+ ctxt);
+ ASSERT_EQ(list.size(), 2);
+ EXPECT_EQ(
+ list[0]->exportToPROJString(PROJStringFormatter::create().get()),
+ "+proj=pipeline +step +proj=axisswap +order=2,1 +step "
+ "+proj=unitconvert +xy_in=deg +xy_out=rad +step "
+ "+proj=hgridshift +grids=ntf_r93.gsb +step +proj=unitconvert "
+ "+xy_in=rad +xy_out=deg +step +proj=axisswap +order=2,1");
+ }
+ {
+ auto ctxt =
+ CoordinateOperationContext::create(authFactory, nullptr, 0.0);
+ auto list = CoordinateOperationFactory::create()->createOperations(
+ authFactory->createCoordinateReferenceSystem("4258"), // ETRS89
+ authFactory->createCoordinateReferenceSystem("4275"), // NTF
+ ctxt);
+ ASSERT_EQ(list.size(), 2);
+ EXPECT_EQ(
+ list[0]->exportToPROJString(PROJStringFormatter::create().get()),
+ "+proj=pipeline +step +proj=axisswap +order=2,1 +step "
+ "+proj=unitconvert +xy_in=deg +xy_out=rad +step +inv "
+ "+proj=hgridshift +grids=ntf_r93.gsb +step +proj=unitconvert "
+ "+xy_in=rad +xy_out=deg +step +proj=axisswap +order=2,1");
+ }
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(operation, geogCRS_to_geogCRS_context_ntv1_ntv2_ctable2) {
+ auto authFactory =
+ AuthorityFactory::create(DatabaseContext::create(), "EPSG");
+ auto ctxt = CoordinateOperationContext::create(authFactory, nullptr, 0.0);
+ ctxt->setSpatialCriterion(
+ CoordinateOperationContext::SpatialCriterion::PARTIAL_INTERSECTION);
+ ctxt->setGridAvailabilityUse(
+ CoordinateOperationContext::GridAvailabilityUse::
+ IGNORE_GRID_AVAILABILITY);
+
+ auto list = CoordinateOperationFactory::create()->createOperations(
+ authFactory->createCoordinateReferenceSystem("4267"), // NAD27
+ authFactory->createCoordinateReferenceSystem("4269"), // NAD83
+ ctxt);
+ ASSERT_EQ(list.size(), 6);
+ EXPECT_EQ(list[0]->exportToPROJString(PROJStringFormatter::create().get()),
+ "+proj=pipeline +step +proj=axisswap +order=2,1 +step "
+ "+proj=unitconvert +xy_in=deg +xy_out=rad +step +proj=hgridshift "
+ "+grids=ntv1_can.dat +step +proj=unitconvert +xy_in=rad "
+ "+xy_out=deg +step +proj=axisswap +order=2,1");
+ EXPECT_EQ(list[1]->exportToPROJString(PROJStringFormatter::create().get()),
+ "+proj=pipeline +step +proj=axisswap +order=2,1 +step "
+ "+proj=unitconvert +xy_in=deg +xy_out=rad +step +proj=hgridshift "
+ "+grids=ntv2_0.gsb +step +proj=unitconvert +xy_in=rad "
+ "+xy_out=deg +step +proj=axisswap +order=2,1");
+ EXPECT_EQ(list[2]->exportToPROJString(PROJStringFormatter::create().get()),
+ "+proj=pipeline +step +proj=axisswap +order=2,1 +step "
+ "+proj=unitconvert +xy_in=deg +xy_out=rad +step +proj=hgridshift "
+ "+grids=conus +step +proj=unitconvert +xy_in=rad +xy_out=deg "
+ "+step +proj=axisswap +order=2,1");
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(operation, vertCRS_to_geogCRS_context) {
+ auto authFactory =
+ AuthorityFactory::create(DatabaseContext::create(), "EPSG");
+ {
+ auto ctxt =
+ CoordinateOperationContext::create(authFactory, nullptr, 0.0);
+ ctxt->setUsePROJAlternativeGridNames(false);
+ auto list = CoordinateOperationFactory::create()->createOperations(
+ authFactory->createCoordinateReferenceSystem(
+ "3855"), // EGM2008 height
+ authFactory->createCoordinateReferenceSystem("4979"), // WGS 84
+ ctxt);
+ ASSERT_EQ(list.size(), 2);
+ EXPECT_EQ(list[1]->exportToPROJString(
+ PROJStringFormatter::create(
+ PROJStringFormatter::Convention::PROJ_5,
+ authFactory->databaseContext())
+ .get()),
+ "+proj=vgridshift +grids=egm08_25.gtx");
+ }
+ {
+ auto ctxt =
+ CoordinateOperationContext::create(authFactory, nullptr, 0.0);
+ auto list = CoordinateOperationFactory::create()->createOperations(
+ authFactory->createCoordinateReferenceSystem(
+ "3855"), // EGM2008 height
+ authFactory->createCoordinateReferenceSystem("4979"), // WGS 84
+ ctxt);
+ ASSERT_EQ(list.size(), 2);
+ EXPECT_EQ(
+ list[0]->exportToPROJString(PROJStringFormatter::create().get()),
+ "+proj=vgridshift +grids=egm08_25.gtx");
+ }
+ {
+ auto ctxt =
+ CoordinateOperationContext::create(authFactory, nullptr, 0.0);
+ auto list = CoordinateOperationFactory::create()->createOperations(
+ authFactory->createCoordinateReferenceSystem("4979"), // WGS 84
+ authFactory->createCoordinateReferenceSystem(
+ "3855"), // EGM2008 height
+ ctxt);
+ ASSERT_EQ(list.size(), 2);
+ EXPECT_EQ(
+ list[0]->exportToPROJString(PROJStringFormatter::create().get()),
+ "+proj=pipeline +step +inv +proj=vgridshift +grids=egm08_25.gtx");
+ }
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(operation, geogCRS_to_geogCRS_noop) {
+
+ auto op = CoordinateOperationFactory::create()->createOperation(
+ GeographicCRS::EPSG_4326, GeographicCRS::EPSG_4326);
+ ASSERT_TRUE(op != nullptr);
+ EXPECT_EQ(op->nameStr(), "Null geographic offset from WGS 84 to WGS 84");
+ EXPECT_EQ(op->exportToPROJString(PROJStringFormatter::create().get()), "");
+ EXPECT_EQ(op->inverse()->nameStr(), op->nameStr());
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(operation, geogCRS_to_geogCRS_longitude_rotation) {
+
+ auto src = GeographicCRS::create(
+ PropertyMap().set(IdentifiedObject::NAME_KEY, "A"),
+ GeodeticReferenceFrame::create(PropertyMap(), Ellipsoid::WGS84,
+ optional<std::string>(),
+ PrimeMeridian::GREENWICH),
+ EllipsoidalCS::createLatitudeLongitude(UnitOfMeasure::DEGREE));
+ auto dest = GeographicCRS::create(
+ PropertyMap().set(IdentifiedObject::NAME_KEY, "B"),
+ GeodeticReferenceFrame::create(PropertyMap(), Ellipsoid::WGS84,
+ optional<std::string>(),
+ PrimeMeridian::PARIS),
+ EllipsoidalCS::createLatitudeLongitude(UnitOfMeasure::DEGREE));
+
+ auto op = CoordinateOperationFactory::create()->createOperation(src, dest);
+ ASSERT_TRUE(op != nullptr);
+ EXPECT_EQ(op->exportToPROJString(PROJStringFormatter::create().get()),
+ "+proj=pipeline +step +proj=axisswap +order=2,1 +step "
+ "+proj=unitconvert +xy_in=deg +xy_out=rad +step +proj=longlat "
+ "+ellps=WGS84 +pm=paris +step +proj=unitconvert +xy_in=rad "
+ "+xy_out=deg +step +proj=axisswap +order=2,1");
+ EXPECT_EQ(op->inverse()->exportToWKT(WKTFormatter::create().get()),
+ CoordinateOperationFactory::create()
+ ->createOperation(dest, src)
+ ->exportToWKT(WKTFormatter::create().get()));
+ EXPECT_TRUE(
+ op->inverse()->isEquivalentTo(CoordinateOperationFactory::create()
+ ->createOperation(dest, src)
+ .get()));
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(operation, geogCRS_to_geogCRS_longitude_rotation_context) {
+ auto authFactory =
+ AuthorityFactory::create(DatabaseContext::create(), "EPSG");
+ auto ctxt = CoordinateOperationContext::create(authFactory, nullptr, 0.0);
+ auto list = CoordinateOperationFactory::create()->createOperations(
+ authFactory->createCoordinateReferenceSystem("4807"), // NTF(Paris)
+ authFactory->createCoordinateReferenceSystem("4275"), // NTF
+ ctxt);
+ ASSERT_EQ(list.size(), 2);
+ EXPECT_EQ(list[0]->nameStr(), "NTF (Paris) to NTF (1)");
+ EXPECT_EQ(list[0]->exportToPROJString(PROJStringFormatter::create().get()),
+ "+proj=pipeline +step +proj=axisswap +order=2,1 +step "
+ "+proj=unitconvert +xy_in=grad +xy_out=rad +step +inv "
+ "+proj=longlat +ellps=clrk80ign +pm=paris +step "
+ "+proj=unitconvert +xy_in=rad +xy_out=deg +step +proj=axisswap "
+ "+order=2,1");
+ EXPECT_EQ(list[1]->nameStr(), "NTF (Paris) to NTF (2)");
+ EXPECT_EQ(list[1]->exportToPROJString(PROJStringFormatter::create().get()),
+ "+proj=pipeline +step +proj=axisswap +order=2,1 +step "
+ "+proj=unitconvert +xy_in=grad +xy_out=rad +step +inv "
+ "+proj=longlat +ellps=clrk80ign +pm=2.33720833333333 +step "
+ "+proj=unitconvert +xy_in=rad +xy_out=deg +step +proj=axisswap "
+ "+order=2,1");
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(operation, geogCRS_to_geogCRS_context_concatenated_operation) {
+ auto authFactory =
+ AuthorityFactory::create(DatabaseContext::create(), "EPSG");
+ auto ctxt = CoordinateOperationContext::create(authFactory, nullptr, 0.0);
+ auto list = CoordinateOperationFactory::create()->createOperations(
+ authFactory->createCoordinateReferenceSystem("4807"), // NTF(Paris)
+ authFactory->createCoordinateReferenceSystem("4171"), // RGF93
+ ctxt);
+ ASSERT_EQ(list.size(), 3);
+ EXPECT_EQ(list[0]->nameStr(), "NTF (Paris) to RGF93 (2)");
+ EXPECT_EQ(list[0]->exportToPROJString(PROJStringFormatter::create().get()),
+ "+proj=pipeline +step +proj=axisswap +order=2,1 +step "
+ "+proj=unitconvert +xy_in=grad +xy_out=rad +step +inv "
+ "+proj=longlat +ellps=clrk80ign +pm=paris +step +proj=hgridshift "
+ "+grids=ntf_r93.gsb +step +proj=unitconvert +xy_in=rad "
+ "+xy_out=deg +step +proj=axisswap +order=2,1");
+
+ EXPECT_TRUE(nn_dynamic_pointer_cast<ConcatenatedOperation>(list[0]) !=
+ nullptr);
+ auto grids = list[0]->gridsNeeded(DatabaseContext::create());
+ EXPECT_EQ(grids.size(), 1);
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(operation, geogCRS_to_geogCRS_context_same_grid_name) {
+ auto authFactory =
+ AuthorityFactory::create(DatabaseContext::create(), "EPSG");
+ auto ctxt = CoordinateOperationContext::create(authFactory, nullptr, 0.0);
+ auto list = CoordinateOperationFactory::create()->createOperations(
+ authFactory->createCoordinateReferenceSystem("4314"), // DHDN
+ authFactory->createCoordinateReferenceSystem("4258"), // ETRS89
+ ctxt);
+ ASSERT_TRUE(!list.empty());
+ EXPECT_EQ(list[0]->nameStr(), "DHDN to ETRS89 (8)");
+ EXPECT_EQ(list[0]->exportToPROJString(PROJStringFormatter::create().get()),
+ "+proj=pipeline +step +proj=axisswap +order=2,1 +step "
+ "+proj=unitconvert +xy_in=deg +xy_out=rad +step +proj=hgridshift "
+ "+grids=BETA2007.gsb +step +proj=unitconvert +xy_in=rad "
+ "+xy_out=deg +step +proj=axisswap +order=2,1");
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(operation, geogCRS_to_geogCRS_geographic_offset_context) {
+ auto authFactory =
+ AuthorityFactory::create(DatabaseContext::create(), "EPSG");
+ auto ctxt = CoordinateOperationContext::create(authFactory, nullptr, 0.0);
+ auto list = CoordinateOperationFactory::create()->createOperations(
+ authFactory->createCoordinateReferenceSystem("4120"), // NTF(Paris)
+ authFactory->createCoordinateReferenceSystem("4121"), // NTF
+ ctxt);
+ ASSERT_EQ(list.size(), 1);
+ EXPECT_EQ(list[0]->nameStr(), "Greek to GGRS87 (1)");
+ EXPECT_EQ(list[0]->exportToPROJString(PROJStringFormatter::create().get()),
+ "+proj=pipeline +step +proj=axisswap +order=2,1 +step "
+ "+proj=unitconvert +xy_in=deg +xy_out=rad +step +proj=geogoffset "
+ "+dlat=-5.86 +dlon=0.28 +step +proj=unitconvert +xy_in=rad "
+ "+xy_out=deg +step +proj=axisswap +order=2,1");
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(operation, geogCRS_to_geogCRS_CH1903_to_CH1903plus_context) {
+ auto authFactory =
+ AuthorityFactory::create(DatabaseContext::create(), "EPSG");
+ auto ctxt = CoordinateOperationContext::create(authFactory, nullptr, 0.0);
+ auto list = CoordinateOperationFactory::create()->createOperations(
+ authFactory->createCoordinateReferenceSystem("4149"), // CH1903
+ authFactory->createCoordinateReferenceSystem("4150"), // CH1903+
+ ctxt);
+ ASSERT_EQ(list.size(), 2);
+
+ EXPECT_EQ(list[0]->nameStr(),
+ "CH1903 to ETRS89 (1) + Inverse of CH1903+ to ETRS89 (1)");
+ EXPECT_EQ(list[0]->exportToPROJString(PROJStringFormatter::create().get()),
+ "");
+
+ EXPECT_EQ(list[1]->nameStr(), "CH1903 to CH1903+ (1)");
+ EXPECT_EQ(list[1]->exportToPROJString(PROJStringFormatter::create().get()),
+ "+proj=pipeline +step +proj=axisswap +order=2,1 "
+ "+step +proj=unitconvert +xy_in=deg +xy_out=rad "
+ "+step +proj=hgridshift +grids=CHENyx06a.gsb "
+ "+step +proj=unitconvert +xy_in=rad +xy_out=deg "
+ "+step +proj=axisswap +order=2,1");
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(operation, geogCRS_to_geogCRS_3D) {
+
+ auto geogcrs_m_obj =
+ PROJStringParser().createFromPROJString("+proj=longlat +vunits=m");
+ auto geogcrs_m = nn_dynamic_pointer_cast<CRS>(geogcrs_m_obj);
+ ASSERT_TRUE(geogcrs_m != nullptr);
+
+ auto geogcrs_ft_obj =
+ PROJStringParser().createFromPROJString("+proj=longlat +vunits=ft");
+ auto geogcrs_ft = nn_dynamic_pointer_cast<CRS>(geogcrs_ft_obj);
+ ASSERT_TRUE(geogcrs_ft != nullptr);
+
+ {
+ auto op = CoordinateOperationFactory::create()->createOperation(
+ NN_CHECK_ASSERT(geogcrs_m), NN_CHECK_ASSERT(geogcrs_ft));
+ ASSERT_TRUE(op != nullptr);
+ EXPECT_EQ(op->exportToPROJString(PROJStringFormatter::create().get()),
+ "+proj=unitconvert +z_in=m +z_out=ft");
+ }
+
+ {
+ auto op = CoordinateOperationFactory::create()->createOperation(
+ NN_CHECK_ASSERT(geogcrs_ft), NN_CHECK_ASSERT(geogcrs_m));
+ ASSERT_TRUE(op != nullptr);
+ EXPECT_EQ(op->exportToPROJString(PROJStringFormatter::create().get()),
+ "+proj=unitconvert +z_in=ft +z_out=m");
+ }
+
+ auto geogcrs_m_with_pm_obj = PROJStringParser().createFromPROJString(
+ "+proj=longlat +pm=paris +vunits=m");
+ auto geogcrs_m_with_pm =
+ nn_dynamic_pointer_cast<CRS>(geogcrs_m_with_pm_obj);
+ ASSERT_TRUE(geogcrs_m_with_pm != nullptr);
+
+ {
+ auto op = CoordinateOperationFactory::create()->createOperation(
+ NN_CHECK_ASSERT(geogcrs_m_with_pm), NN_CHECK_ASSERT(geogcrs_ft));
+ ASSERT_TRUE(op != nullptr);
+ EXPECT_EQ(op->exportToPROJString(PROJStringFormatter::create().get()),
+ "+proj=pipeline +step +proj=unitconvert +xy_in=deg +z_in=m "
+ "+xy_out=rad +z_out=m +step +inv +proj=longlat +ellps=WGS84 "
+ "+pm=paris +step +proj=unitconvert +xy_in=rad +z_in=m "
+ "+xy_out=deg +z_out=ft");
+ }
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(operation, geocentricCRS_to_geogCRS_same_datum) {
+
+ auto op = CoordinateOperationFactory::create()->createOperation(
+ createGeocentricDatumWGS84(), GeographicCRS::EPSG_4326);
+ ASSERT_TRUE(op != nullptr);
+ EXPECT_EQ(op->exportToPROJString(PROJStringFormatter::create().get()),
+ "+proj=pipeline +step +inv +proj=cart +ellps=WGS84 +step "
+ "+proj=unitconvert +xy_in=rad +xy_out=deg +step +proj=axisswap "
+ "+order=2,1");
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(operation, geocentricCRS_to_geogCRS_different_datum) {
+
+ auto op = CoordinateOperationFactory::create()->createOperation(
+ createGeocentricDatumWGS84(), GeographicCRS::EPSG_4269);
+ ASSERT_TRUE(op != nullptr);
+ EXPECT_EQ(op->nameStr(), "Null geocentric translation from WGS 84 to NAD83 "
+ "(geocentric) + Conversion from NAD83 "
+ "(geocentric) to NAD83");
+ EXPECT_EQ(op->exportToPROJString(PROJStringFormatter::create().get()),
+ "+proj=pipeline +step +inv +proj=cart +ellps=GRS80 +step "
+ "+proj=unitconvert +xy_in=rad +xy_out=deg +step +proj=axisswap "
+ "+order=2,1");
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(operation, geogCRS_to_geocentricCRS_different_datum) {
+
+ auto op = CoordinateOperationFactory::create()->createOperation(
+ GeographicCRS::EPSG_4269, createGeocentricDatumWGS84());
+ ASSERT_TRUE(op != nullptr);
+ EXPECT_EQ(op->nameStr(), "Conversion from NAD83 to NAD83 (geocentric) + "
+ "Null geocentric translation from NAD83 "
+ "(geocentric) to WGS 84");
+ EXPECT_EQ(op->exportToPROJString(PROJStringFormatter::create().get()),
+ "+proj=pipeline +step +proj=axisswap +order=2,1 +step "
+ "+proj=unitconvert +xy_in=deg +xy_out=rad +step +proj=cart "
+ "+ellps=GRS80");
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(operation, geocentricCRS_to_geocentricCRS_noop) {
+
+ auto op = CoordinateOperationFactory::create()->createOperation(
+ createGeocentricDatumWGS84(), createGeocentricDatumWGS84());
+ ASSERT_TRUE(op != nullptr);
+ EXPECT_EQ(op->nameStr(),
+ "Null geocentric translation from WGS 84 to WGS 84");
+ EXPECT_EQ(op->exportToPROJString(PROJStringFormatter::create().get()), "");
+ EXPECT_EQ(op->inverse()->nameStr(), op->nameStr());
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(operation, geocentricCRS_to_geogCRS_same_datum_context) {
+ auto authFactory =
+ AuthorityFactory::create(DatabaseContext::create(), "EPSG");
+ auto ctxt = CoordinateOperationContext::create(authFactory, nullptr, 0.0);
+ auto list = CoordinateOperationFactory::create()->createOperations(
+ authFactory->createCoordinateReferenceSystem("4326"),
+ // WGS84 geocentric
+ authFactory->createCoordinateReferenceSystem("4978"), ctxt);
+ ASSERT_EQ(list.size(), 1);
+
+ EXPECT_EQ(list[0]->nameStr(),
+ "Conversion from WGS 84 (geog2D) to WGS 84 (geocentric)");
+ EXPECT_EQ(list[0]->exportToPROJString(PROJStringFormatter::create().get()),
+ "+proj=pipeline +step +proj=axisswap +order=2,1 +step "
+ "+proj=unitconvert +xy_in=deg +xy_out=rad +step +proj=cart "
+ "+ellps=WGS84");
+
+ EXPECT_EQ(list[0]->inverse()->nameStr(),
+ "Conversion from WGS 84 (geocentric) to WGS 84 (geog2D)");
+ EXPECT_EQ(list[0]->inverse()->exportToPROJString(
+ PROJStringFormatter::create().get()),
+ "+proj=pipeline +step +inv +proj=cart +ellps=WGS84 +step "
+ "+proj=unitconvert +xy_in=rad +xy_out=deg +step +proj=axisswap "
+ "+order=2,1");
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(operation, geocentricCRS_to_geogCRS_same_datum_context_all_auth) {
+ // This is to check we don't use OGC:CRS84 as a pivot
+ auto authFactoryEPSG =
+ AuthorityFactory::create(DatabaseContext::create(), "EPSG");
+ auto authFactoryAll =
+ AuthorityFactory::create(DatabaseContext::create(), std::string());
+ auto ctxt =
+ CoordinateOperationContext::create(authFactoryAll, nullptr, 0.0);
+ auto list = CoordinateOperationFactory::create()->createOperations(
+ authFactoryEPSG->createCoordinateReferenceSystem("4326"),
+ // WGS84 geocentric
+ authFactoryEPSG->createCoordinateReferenceSystem("4978"), ctxt);
+ ASSERT_EQ(list.size(), 1);
+
+ EXPECT_EQ(list[0]->nameStr(),
+ "Conversion from WGS 84 (geog2D) to WGS 84 (geocentric)");
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(operation, geocentricCRS_to_geocentricCRS_different_datum_context) {
+ auto authFactory =
+ AuthorityFactory::create(DatabaseContext::create(), "EPSG");
+ auto ctxt = CoordinateOperationContext::create(authFactory, nullptr, 0.0);
+ auto list = CoordinateOperationFactory::create()->createOperations(
+ // ITRF2000 (geocentric)
+ authFactory->createCoordinateReferenceSystem("4919"),
+ // ITRF2005 (geocentric)
+ authFactory->createCoordinateReferenceSystem("4896"), ctxt);
+ ASSERT_EQ(list.size(), 1);
+ EXPECT_EQ(list[0]->nameStr(), "ITRF2000 to ITRF2005 (1)");
+ EXPECT_EQ(list[0]->exportToPROJString(PROJStringFormatter::create().get()),
+ "+proj=helmert +x=-0.0001 "
+ "+y=0.0008 +z=0.0058 +rx=0 +ry=0 +rz=0 +s=-0.0004 +dx=0.0002 "
+ "+dy=-0.0001 +dz=0.0018 +drx=0 +dry=0 +drz=0 +ds=-8e-05 "
+ "+t_epoch=2000 +convention=position_vector");
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(operation, geogCRS_geocentricCRS_same_datum_to_context) {
+ auto authFactory =
+ AuthorityFactory::create(DatabaseContext::create(), "EPSG");
+ auto ctxt = CoordinateOperationContext::create(authFactory, nullptr, 0.0);
+ auto list = CoordinateOperationFactory::create()->createOperations(
+ // WGS84 geocentric
+ authFactory->createCoordinateReferenceSystem("4978"),
+ authFactory->createCoordinateReferenceSystem("4326"), ctxt);
+ ASSERT_EQ(list.size(), 1);
+ EXPECT_EQ(list[0]->nameStr(),
+ "Conversion from WGS 84 (geocentric) to WGS 84 (geog2D)");
+ EXPECT_EQ(list[0]->exportToPROJString(PROJStringFormatter::create().get()),
+ "+proj=pipeline +step +inv +proj=cart +ellps=WGS84 +step "
+ "+proj=unitconvert +xy_in=rad +xy_out=deg +step +proj=axisswap "
+ "+order=2,1");
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(operation,
+ geogCRS_to_geogCRS_different_datum_though_geocentric_transform_context) {
+ auto authFactory =
+ AuthorityFactory::create(DatabaseContext::create(), "EPSG");
+ auto ctxt = CoordinateOperationContext::create(authFactory, nullptr, 0.0);
+ auto list = CoordinateOperationFactory::create()->createOperations(
+ // ITRF2000 (geog3D)
+ authFactory->createCoordinateReferenceSystem("7909"),
+ // ITRF2005 (geog3D)
+ authFactory->createCoordinateReferenceSystem("7910"), ctxt);
+ ASSERT_EQ(list.size(), 1);
+ EXPECT_EQ(list[0]->nameStr(),
+ "Conversion from ITRF2000 (geog3D) to ITRF2000 (geocentric) + "
+ "ITRF2000 to ITRF2005 (1) + "
+ "Conversion from ITRF2005 (geocentric) to ITRF2005 (geog3D)");
+ EXPECT_EQ(list[0]->exportToPROJString(PROJStringFormatter::create().get()),
+ "+proj=pipeline +step +proj=axisswap +order=2,1 +step "
+ "+proj=unitconvert +xy_in=deg +z_in=m +xy_out=rad +z_out=m "
+ "+step +proj=cart +ellps=GRS80 +step +proj=helmert +x=-0.0001 "
+ "+y=0.0008 +z=0.0058 +rx=0 +ry=0 +rz=0 +s=-0.0004 +dx=0.0002 "
+ "+dy=-0.0001 +dz=0.0018 +drx=0 +dry=0 +drz=0 +ds=-8e-05 "
+ "+t_epoch=2000 +convention=position_vector +step +inv "
+ "+proj=cart +ellps=GRS80 +step +proj=unitconvert +xy_in=rad "
+ "+z_in=m +xy_out=deg +z_out=m +step +proj=axisswap +order=2,1");
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(operation, geogCRS_to_geocentricCRS_different_datum_context) {
+ auto authFactory =
+ AuthorityFactory::create(DatabaseContext::create(), "EPSG");
+ auto ctxt = CoordinateOperationContext::create(authFactory, nullptr, 0.0);
+ auto list = CoordinateOperationFactory::create()->createOperations(
+ // ITRF2000 (geog3D)
+ authFactory->createCoordinateReferenceSystem("7909"),
+ // ITRF2005 (geocentric)
+ authFactory->createCoordinateReferenceSystem("4896"), ctxt);
+ ASSERT_EQ(list.size(), 1);
+ EXPECT_EQ(list[0]->nameStr(),
+ "Conversion from ITRF2000 (geog3D) to ITRF2000 (geocentric) + "
+ "ITRF2000 to ITRF2005 (1)");
+ EXPECT_EQ(list[0]->exportToPROJString(PROJStringFormatter::create().get()),
+ "+proj=pipeline +step +proj=axisswap +order=2,1 +step "
+ "+proj=unitconvert +xy_in=deg +z_in=m +xy_out=rad +z_out=m "
+ "+step +proj=cart +ellps=GRS80 +step +proj=helmert +x=-0.0001 "
+ "+y=0.0008 +z=0.0058 +rx=0 +ry=0 +rz=0 +s=-0.0004 +dx=0.0002 "
+ "+dy=-0.0001 +dz=0.0018 +drx=0 +dry=0 +drz=0 +ds=-8e-05 "
+ "+t_epoch=2000 +convention=position_vector");
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(operation, geocentricCRS_to_geogCRS_different_datum_context) {
+ auto authFactory =
+ AuthorityFactory::create(DatabaseContext::create(), "EPSG");
+ auto ctxt = CoordinateOperationContext::create(authFactory, nullptr, 0.0);
+ auto list = CoordinateOperationFactory::create()->createOperations(
+ // ITRF2000 (geocentric)
+ authFactory->createCoordinateReferenceSystem("4919"),
+ // ITRF2005 (geog3D)
+ authFactory->createCoordinateReferenceSystem("7910"), ctxt);
+ ASSERT_EQ(list.size(), 1);
+ EXPECT_EQ(list[0]->nameStr(),
+ "ITRF2000 to ITRF2005 (1) + "
+ "Conversion from ITRF2005 (geocentric) to ITRF2005 (geog3D)");
+ EXPECT_EQ(list[0]->exportToPROJString(PROJStringFormatter::create().get()),
+ "+proj=pipeline +step +proj=helmert +x=-0.0001 "
+ "+y=0.0008 +z=0.0058 +rx=0 +ry=0 +rz=0 +s=-0.0004 +dx=0.0002 "
+ "+dy=-0.0001 +dz=0.0018 +drx=0 +dry=0 +drz=0 +ds=-8e-05 "
+ "+t_epoch=2000 +convention=position_vector +step +inv "
+ "+proj=cart +ellps=GRS80 +step +proj=unitconvert +xy_in=rad "
+ "+z_in=m +xy_out=deg +z_out=m +step +proj=axisswap +order=2,1");
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(operation, esri_projectedCRS_to_geogCRS_with_ITRF_intermediate_context) {
+ auto dbContext = DatabaseContext::create();
+ auto authFactoryEPSG = AuthorityFactory::create(dbContext, "EPSG");
+ auto authFactoryESRI = AuthorityFactory::create(dbContext, "ESRI");
+ auto ctxt =
+ CoordinateOperationContext::create(authFactoryEPSG, nullptr, 0.0);
+ auto list = CoordinateOperationFactory::create()->createOperations(
+ // NAD_1983_CORS96_StatePlane_North_Carolina_FIPS_3200_Ft_US (projected)
+ authFactoryESRI->createCoordinateReferenceSystem("103501"),
+ // ITRF2005 (geog3D)
+ authFactoryEPSG->createCoordinateReferenceSystem("7910"), ctxt);
+ ASSERT_EQ(list.size(), 1);
+ EXPECT_EQ(list[0]->nameStr(),
+ "Inverse of NAD_1983_CORS96_StatePlane_North_Carolina_"
+ "FIPS_3200_Ft_US + "
+ "Conversion from NAD83(CORS96) (geog2D) to NAD83(CORS96) "
+ "(geocentric) + Inverse of ITRF2000 to NAD83(CORS96) (1) + "
+ "ITRF2000 to ITRF2005 (1) + "
+ "Conversion from ITRF2005 (geocentric) to ITRF2005 (geog3D)");
+ EXPECT_EQ(list[0]->exportToPROJString(PROJStringFormatter::create().get()),
+ "+proj=pipeline +step +proj=unitconvert +xy_in=us-ft +z_in=us-ft "
+ "+xy_out=m +z_out=m +step +inv +proj=lcc +lat_0=33.75 +lon_0=-79 "
+ "+lat_1=34.3333333333333 +lat_2=36.1666666666667 "
+ "+x_0=609601.219202438 +y_0=0 +ellps=GRS80 +step +proj=cart "
+ "+ellps=GRS80 +step +inv +proj=helmert +x=0.9956 +y=-1.9013 "
+ "+z=-0.5215 +rx=0.025915 +ry=0.009426 +rz=0.011599 +s=0.00062 "
+ "+dx=0.0007 +dy=-0.0007 +dz=0.0005 +drx=6.7e-05 +dry=-0.000757 "
+ "+drz=-5.1e-05 +ds=-0.00018 +t_epoch=1997 "
+ "+convention=coordinate_frame +step +proj=helmert +x=-0.0001 "
+ "+y=0.0008 +z=0.0058 +rx=0 +ry=0 +rz=0 +s=-0.0004 +dx=0.0002 "
+ "+dy=-0.0001 +dz=0.0018 +drx=0 +dry=0 +drz=0 +ds=-8e-05 "
+ "+t_epoch=2000 +convention=position_vector +step +inv +proj=cart "
+ "+ellps=GRS80 +step +proj=unitconvert +xy_in=rad +z_in=m "
+ "+xy_out=deg +z_out=m +step +proj=axisswap +order=2,1");
+}
+
+// ---------------------------------------------------------------------------
+
+static ProjectedCRSNNPtr createUTM31_WGS84() {
+ return ProjectedCRS::create(
+ PropertyMap(), GeographicCRS::EPSG_4326,
+ Conversion::createUTM(PropertyMap(), 31, true),
+ CartesianCS::createEastingNorthing(UnitOfMeasure::METRE));
+}
+
+// ---------------------------------------------------------------------------
+
+static ProjectedCRSNNPtr createUTM32_WGS84() {
+ return ProjectedCRS::create(
+ PropertyMap(), GeographicCRS::EPSG_4326,
+ Conversion::createUTM(PropertyMap(), 32, true),
+ CartesianCS::createEastingNorthing(UnitOfMeasure::METRE));
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(operation, geogCRS_to_projCRS) {
+
+ auto op = CoordinateOperationFactory::create()->createOperation(
+ GeographicCRS::EPSG_4326, createUTM31_WGS84());
+ ASSERT_TRUE(op != nullptr);
+ EXPECT_TRUE(std::dynamic_pointer_cast<Conversion>(op) != nullptr);
+ EXPECT_EQ(op->exportToPROJString(PROJStringFormatter::create().get()),
+ "+proj=pipeline +step +proj=axisswap +order=2,1 +step "
+ "+proj=unitconvert +xy_in=deg +xy_out=rad +step +proj=utm "
+ "+zone=31 +ellps=WGS84");
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(operation, geogCRS_longlat_to_geogCS_latlong) {
+
+ auto sourceCRS = GeographicCRS::OGC_CRS84;
+ auto targetCRS = GeographicCRS::EPSG_4326;
+ auto op = CoordinateOperationFactory::create()->createOperation(sourceCRS,
+ targetCRS);
+ ASSERT_TRUE(op != nullptr);
+ auto conv = std::dynamic_pointer_cast<Conversion>(op);
+ ASSERT_TRUE(conv != nullptr);
+ EXPECT_TRUE(op->sourceCRS() &&
+ op->sourceCRS()->isEquivalentTo(sourceCRS.get()));
+ EXPECT_TRUE(op->targetCRS() &&
+ op->targetCRS()->isEquivalentTo(targetCRS.get()));
+ EXPECT_EQ(op->exportToPROJString(PROJStringFormatter::create().get()),
+ "+proj=axisswap +order=2,1");
+ auto convInverse = nn_dynamic_pointer_cast<Conversion>(conv->inverse());
+ ASSERT_TRUE(convInverse != nullptr);
+ EXPECT_TRUE(convInverse->sourceCRS() &&
+ convInverse->sourceCRS()->isEquivalentTo(targetCRS.get()));
+ EXPECT_TRUE(convInverse->targetCRS() &&
+ convInverse->targetCRS()->isEquivalentTo(sourceCRS.get()));
+ EXPECT_EQ(conv->method()->exportToWKT(WKTFormatter::create().get()),
+ convInverse->method()->exportToWKT(WKTFormatter::create().get()));
+ EXPECT_TRUE(conv->method()->isEquivalentTo(convInverse->method().get()));
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(operation, geogCRS_longlat_to_geogCS_latlong_database) {
+
+ auto authFactory =
+ AuthorityFactory::create(DatabaseContext::create(), std::string());
+ auto ctxt = CoordinateOperationContext::create(authFactory, nullptr, 0.0);
+ auto list = CoordinateOperationFactory::create()->createOperations(
+ AuthorityFactory::create(DatabaseContext::create(), "OGC")
+ ->createCoordinateReferenceSystem("CRS84"),
+ AuthorityFactory::create(DatabaseContext::create(), "EPSG")
+ ->createCoordinateReferenceSystem("4326"),
+ ctxt);
+ ASSERT_EQ(list.size(), 1);
+ EXPECT_EQ(list[0]->exportToPROJString(PROJStringFormatter::create().get()),
+ "+proj=axisswap +order=2,1");
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(operation, geogCRS_longlat_to_projCRS) {
+
+ auto op = CoordinateOperationFactory::create()->createOperation(
+ GeographicCRS::OGC_CRS84, createUTM31_WGS84());
+ ASSERT_TRUE(op != nullptr);
+ EXPECT_EQ(op->exportToPROJString(PROJStringFormatter::create().get()),
+ "+proj=pipeline +step +proj=unitconvert +xy_in=deg +xy_out=rad "
+ "+step +proj=utm +zone=31 +ellps=WGS84");
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(operation, geogCRS_different_from_baseCRS_to_projCRS) {
+
+ auto op = CoordinateOperationFactory::create()->createOperation(
+ GeographicCRS::EPSG_4807, createUTM31_WGS84());
+ ASSERT_TRUE(op != nullptr);
+ EXPECT_EQ(
+ op->exportToPROJString(PROJStringFormatter::create().get()),
+ "+proj=pipeline +step +proj=axisswap +order=2,1 +step "
+ "+proj=unitconvert +xy_in=grad +xy_out=rad +step +inv +proj=longlat "
+ "+ellps=clrk80ign +pm=paris +step +proj=utm +zone=31 "
+ "+ellps=WGS84");
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(operation,
+ geogCRS_different_from_baseCRS_to_projCRS_context_compatible_area) {
+ auto authFactory =
+ AuthorityFactory::create(DatabaseContext::create(), "EPSG");
+ auto ctxt = CoordinateOperationContext::create(authFactory, nullptr, 0.0);
+ auto list = CoordinateOperationFactory::create()->createOperations(
+ authFactory->createCoordinateReferenceSystem("4807"), // NTF(Paris)
+ authFactory->createCoordinateReferenceSystem("32631"), // UTM31 WGS84
+ ctxt);
+ ASSERT_EQ(list.size(), 4);
+ EXPECT_EQ(
+ list[0]->nameStr(),
+ "NTF (Paris) to NTF (1) + Inverse of WGS 84 to NTF (3) + UTM zone 31N");
+ ASSERT_EQ(list[0]->coordinateOperationAccuracies().size(), 1);
+ EXPECT_EQ(list[0]->coordinateOperationAccuracies()[0]->value(), "1");
+ EXPECT_EQ(list[0]->exportToPROJString(PROJStringFormatter::create().get()),
+ "+proj=pipeline +step +proj=axisswap +order=2,1 +step "
+ "+proj=unitconvert +xy_in=grad +xy_out=rad +step +inv "
+ "+proj=longlat +ellps=clrk80ign +pm=paris +step +proj=hgridshift "
+ "+grids=ntf_r93.gsb +step +proj=utm +zone=31 +ellps=WGS84");
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(operation, geocentricCRS_to_projCRS) {
+
+ auto op = CoordinateOperationFactory::create()->createOperation(
+ createGeocentricDatumWGS84(), createUTM31_WGS84());
+ ASSERT_TRUE(op != nullptr);
+ EXPECT_EQ(op->exportToPROJString(PROJStringFormatter::create().get()),
+ "+proj=pipeline +step +inv +proj=cart +ellps=WGS84 +step "
+ "+proj=utm +zone=31 +ellps=WGS84");
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(operation, projCRS_to_geogCRS) {
+
+ auto op = CoordinateOperationFactory::create()->createOperation(
+ createUTM31_WGS84(), GeographicCRS::EPSG_4326);
+ ASSERT_TRUE(op != nullptr);
+ EXPECT_EQ(op->exportToPROJString(PROJStringFormatter::create().get()),
+ "+proj=pipeline +step +inv +proj=utm +zone=31 +ellps=WGS84 +step "
+ "+proj=unitconvert +xy_in=rad +xy_out=deg +step +proj=axisswap "
+ "+order=2,1");
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(operation, projCRS_to_projCRS) {
+
+ auto op = CoordinateOperationFactory::create()->createOperation(
+ createUTM31_WGS84(), createUTM32_WGS84());
+ ASSERT_TRUE(op != nullptr);
+ EXPECT_EQ(op->exportToPROJString(PROJStringFormatter::create().get()),
+ "+proj=pipeline +step +inv +proj=utm +zone=31 +ellps=WGS84 +step "
+ "+proj=utm +zone=32 +ellps=WGS84");
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(operation, projCRS_to_projCRS_different_baseCRS) {
+
+ auto utm32 = ProjectedCRS::create(
+ PropertyMap(), GeographicCRS::EPSG_4807,
+ Conversion::createUTM(PropertyMap(), 32, true),
+ CartesianCS::createEastingNorthing(UnitOfMeasure::METRE));
+
+ auto op = CoordinateOperationFactory::create()->createOperation(
+ createUTM31_WGS84(), utm32);
+ ASSERT_TRUE(op != nullptr);
+ EXPECT_EQ(op->exportToPROJString(PROJStringFormatter::create().get()),
+ "+proj=pipeline +step +inv +proj=utm +zone=31 +ellps=WGS84 +step "
+ "+proj=utm +zone=32 +ellps=clrk80ign +pm=paris");
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(operation, projCRS_to_projCRS_context_compatible_area) {
+ auto authFactory =
+ AuthorityFactory::create(DatabaseContext::create(), "EPSG");
+ auto ctxt = CoordinateOperationContext::create(authFactory, nullptr, 0.0);
+ auto list = CoordinateOperationFactory::create()->createOperations(
+ authFactory->createCoordinateReferenceSystem("32634"), // UTM 34
+ authFactory->createCoordinateReferenceSystem(
+ "2171"), // Pulkovo 42 Poland I
+ ctxt);
+ ASSERT_EQ(list.size(), 1);
+ EXPECT_EQ(list[0]->nameStr(),
+ "Inverse of UTM zone 34N + Inverse of Pulkovo 1942(58) to WGS 84 "
+ "(1) + Poland zone I");
+ ASSERT_EQ(list[0]->coordinateOperationAccuracies().size(), 1);
+ EXPECT_EQ(list[0]->coordinateOperationAccuracies()[0]->value(), "1");
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(operation, projCRS_to_projCRS_context_compatible_area_bis) {
+ auto authFactory =
+ AuthorityFactory::create(DatabaseContext::create(), "EPSG");
+ auto ctxt = CoordinateOperationContext::create(authFactory, nullptr, 0.0);
+ auto list = CoordinateOperationFactory::create()->createOperations(
+ authFactory->createCoordinateReferenceSystem(
+ "3844"), // Pulkovo 42 Stereo 70 (Romania)
+ authFactory->createCoordinateReferenceSystem("32634"), // UTM 34
+ ctxt);
+ ASSERT_EQ(list.size(), 1);
+ EXPECT_EQ(list[0]->nameStr(), "Inverse of Stereo 70 + "
+ "Pulkovo 1942(58) to WGS 84 "
+ "(19) + UTM zone 34N");
+ ASSERT_EQ(list[0]->coordinateOperationAccuracies().size(), 1);
+ EXPECT_EQ(list[0]->coordinateOperationAccuracies()[0]->value(), "3");
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(operation, projCRS_to_projCRS_context_one_incompatible_area) {
+ auto authFactory =
+ AuthorityFactory::create(DatabaseContext::create(), "EPSG");
+ auto ctxt = CoordinateOperationContext::create(authFactory, nullptr, 0.0);
+ auto list = CoordinateOperationFactory::create()->createOperations(
+ authFactory->createCoordinateReferenceSystem("32631"), // UTM 31
+ authFactory->createCoordinateReferenceSystem(
+ "2171"), // Pulkovo 42 Poland I
+ ctxt);
+ ASSERT_EQ(list.size(), 1);
+ EXPECT_EQ(list[0]->nameStr(),
+ "Inverse of UTM zone 31N + Inverse of Pulkovo 1942(58) to WGS 84 "
+ "(1) + Poland zone I");
+ ASSERT_EQ(list[0]->coordinateOperationAccuracies().size(), 1);
+ EXPECT_EQ(list[0]->coordinateOperationAccuracies()[0]->value(), "1");
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(operation, projCRS_to_projCRS_context_incompatible_areas) {
+ auto authFactory =
+ AuthorityFactory::create(DatabaseContext::create(), "EPSG");
+ auto ctxt = CoordinateOperationContext::create(authFactory, nullptr, 0.0);
+ auto list = CoordinateOperationFactory::create()->createOperations(
+ authFactory->createCoordinateReferenceSystem("32631"), // UTM 31
+ authFactory->createCoordinateReferenceSystem("32633"), // UTM 33
+ ctxt);
+ ASSERT_EQ(list.size(), 1);
+ EXPECT_EQ(list[0]->nameStr(), "Inverse of UTM zone 31N + UTM zone 33N");
+ ASSERT_EQ(list[0]->coordinateOperationAccuracies().size(), 1);
+ EXPECT_EQ(list[0]->coordinateOperationAccuracies()[0]->value(), "0");
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(operation, projCRS_to_projCRS_north_pole_inverted_axis) {
+
+ auto authFactory =
+ AuthorityFactory::create(DatabaseContext::create(), std::string());
+ auto ctxt = CoordinateOperationContext::create(authFactory, nullptr, 0.0);
+ auto list = CoordinateOperationFactory::create()->createOperations(
+ AuthorityFactory::create(DatabaseContext::create(), "EPSG")
+ ->createCoordinateReferenceSystem("32661"),
+ AuthorityFactory::create(DatabaseContext::create(), "EPSG")
+ ->createCoordinateReferenceSystem("5041"),
+ ctxt);
+ ASSERT_EQ(list.size(), 1);
+ EXPECT_EQ(list[0]->exportToPROJString(PROJStringFormatter::create().get()),
+ "+proj=axisswap +order=2,1");
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(operation, projCRS_to_projCRS_south_pole_inverted_axis) {
+
+ auto authFactory =
+ AuthorityFactory::create(DatabaseContext::create(), std::string());
+ auto ctxt = CoordinateOperationContext::create(authFactory, nullptr, 0.0);
+ auto list = CoordinateOperationFactory::create()->createOperations(
+ AuthorityFactory::create(DatabaseContext::create(), "EPSG")
+ ->createCoordinateReferenceSystem("32761"),
+ AuthorityFactory::create(DatabaseContext::create(), "EPSG")
+ ->createCoordinateReferenceSystem("5042"),
+ ctxt);
+ ASSERT_EQ(list.size(), 1);
+ EXPECT_EQ(list[0]->exportToPROJString(PROJStringFormatter::create().get()),
+ "+proj=axisswap +order=2,1");
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(operation, boundCRS_of_geogCRS_to_geogCRS) {
+ auto boundCRS = BoundCRS::createFromTOWGS84(
+ GeographicCRS::EPSG_4807, std::vector<double>{1, 2, 3, 4, 5, 6, 7});
+ auto op = CoordinateOperationFactory::create()->createOperation(
+ boundCRS, GeographicCRS::EPSG_4326);
+ ASSERT_TRUE(op != nullptr);
+ EXPECT_EQ(
+ op->exportToPROJString(PROJStringFormatter::create().get()),
+ "+proj=pipeline +step +proj=axisswap +order=2,1 +step "
+ "+proj=unitconvert +xy_in=grad +xy_out=rad +step +inv +proj=longlat "
+ "+ellps=clrk80ign +pm=paris +step +proj=cart +ellps=clrk80ign "
+ "+step +proj=helmert +x=1 +y=2 +z=3 +rx=4 +ry=5 +rz=6 +s=7 "
+ "+convention=position_vector +step "
+ "+inv +proj=cart +ellps=WGS84 +step +proj=unitconvert +xy_in=rad "
+ "+xy_out=deg +step +proj=axisswap +order=2,1");
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(operation, boundCRS_of_geogCRS_to_unrelated_geogCRS) {
+ auto boundCRS = BoundCRS::createFromTOWGS84(
+ GeographicCRS::EPSG_4807, std::vector<double>{1, 2, 3, 4, 5, 6, 7});
+ auto op = CoordinateOperationFactory::create()->createOperation(
+ boundCRS, GeographicCRS::EPSG_4269);
+ ASSERT_TRUE(op != nullptr);
+ EXPECT_EQ(op->exportToPROJString(PROJStringFormatter::create().get()),
+ CoordinateOperationFactory::create()
+ ->createOperation(GeographicCRS::EPSG_4807,
+ GeographicCRS::EPSG_4269)
+ ->exportToPROJString(PROJStringFormatter::create().get()));
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(operation, boundCRS_of_projCRS_to_geogCRS) {
+ auto utm31 = ProjectedCRS::create(
+ PropertyMap(), GeographicCRS::EPSG_4807,
+ Conversion::createUTM(PropertyMap(), 31, true),
+ CartesianCS::createEastingNorthing(UnitOfMeasure::METRE));
+ auto boundCRS = BoundCRS::createFromTOWGS84(
+ utm31, std::vector<double>{1, 2, 3, 4, 5, 6, 7});
+ auto op = CoordinateOperationFactory::create()->createOperation(
+ boundCRS, GeographicCRS::EPSG_4326);
+ ASSERT_TRUE(op != nullptr);
+ EXPECT_EQ(op->exportToPROJString(PROJStringFormatter::create().get()),
+ "+proj=pipeline +step +inv +proj=utm +zone=31 +ellps=clrk80ign "
+ "+pm=paris "
+ "+step +proj=cart +ellps=clrk80ign +step +proj=helmert +x=1 +y=2 "
+ "+z=3 +rx=4 +ry=5 +rz=6 +s=7 +convention=position_vector +step "
+ "+inv +proj=cart +ellps=WGS84 "
+ "+step +proj=unitconvert +xy_in=rad +xy_out=deg +step "
+ "+proj=axisswap +order=2,1");
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(operation, boundCRS_of_geogCRS_to_projCRS) {
+ auto boundCRS = BoundCRS::createFromTOWGS84(
+ GeographicCRS::EPSG_4807, std::vector<double>{1, 2, 3, 4, 5, 6, 7});
+ auto utm31 = ProjectedCRS::create(
+ PropertyMap(), GeographicCRS::EPSG_4326,
+ Conversion::createUTM(PropertyMap(), 31, true),
+ CartesianCS::createEastingNorthing(UnitOfMeasure::METRE));
+ auto op =
+ CoordinateOperationFactory::create()->createOperation(boundCRS, utm31);
+ ASSERT_TRUE(op != nullptr);
+ EXPECT_EQ(
+ op->exportToPROJString(PROJStringFormatter::create().get()),
+ "+proj=pipeline +step +proj=axisswap +order=2,1 +step "
+ "+proj=unitconvert +xy_in=grad +xy_out=rad +step +inv +proj=longlat "
+ "+ellps=clrk80ign +pm=paris +step +proj=cart +ellps=clrk80ign "
+ "+step +proj=helmert +x=1 +y=2 +z=3 +rx=4 +ry=5 +rz=6 +s=7 "
+ "+convention=position_vector +step "
+ "+inv +proj=cart +ellps=WGS84 +step +proj=utm +zone=31 "
+ "+ellps=WGS84");
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(operation, geogCRS_to_boundCRS_of_geogCRS) {
+ auto boundCRS = BoundCRS::createFromTOWGS84(
+ GeographicCRS::EPSG_4807, std::vector<double>{1, 2, 3, 4, 5, 6, 7});
+ auto op = CoordinateOperationFactory::create()->createOperation(
+ GeographicCRS::EPSG_4326, boundCRS);
+ ASSERT_TRUE(op != nullptr);
+ EXPECT_EQ(op->exportToPROJString(PROJStringFormatter::create().get()),
+ "+proj=pipeline +step +proj=axisswap +order=2,1 +step "
+ "+proj=unitconvert +xy_in=deg +xy_out=rad +step +proj=cart "
+ "+ellps=WGS84 +step +inv +proj=helmert +x=1 +y=2 +z=3 +rx=4 "
+ "+ry=5 +rz=6 +s=7 +convention=position_vector +step +inv "
+ "+proj=cart +ellps=clrk80ign +step +proj=longlat "
+ "+ellps=clrk80ign +pm=paris +step +proj=unitconvert +xy_in=rad "
+ "+xy_out=grad +step +proj=axisswap +order=2,1");
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(operation, boundCRS_to_boundCRS) {
+ auto utm31 = ProjectedCRS::create(
+ PropertyMap(), GeographicCRS::EPSG_4807,
+ Conversion::createUTM(PropertyMap(), 31, true),
+ CartesianCS::createEastingNorthing(UnitOfMeasure::METRE));
+ auto utm32 = ProjectedCRS::create(
+ PropertyMap(), GeographicCRS::EPSG_4269,
+ Conversion::createUTM(PropertyMap(), 32, true),
+ CartesianCS::createEastingNorthing(UnitOfMeasure::METRE));
+ auto boundCRS1 = BoundCRS::createFromTOWGS84(
+ utm31, std::vector<double>{1, 2, 3, 4, 5, 6, 7});
+ auto boundCRS2 = BoundCRS::createFromTOWGS84(
+ utm32, std::vector<double>{8, 9, 10, 11, 12, 13, 14});
+ auto op = CoordinateOperationFactory::create()->createOperation(boundCRS1,
+ boundCRS2);
+ ASSERT_TRUE(op != nullptr);
+ EXPECT_EQ(op->exportToPROJString(PROJStringFormatter::create().get()),
+ "+proj=pipeline +step +inv +proj=utm +zone=31 +ellps=clrk80ign "
+ "+pm=paris "
+ "+step +proj=cart +ellps=clrk80ign +step +proj=helmert +x=1 +y=2 "
+ "+z=3 +rx=4 +ry=5 +rz=6 +s=7 +convention=position_vector +step "
+ "+inv +proj=helmert +x=8 +y=9 +z=10 +rx=11 +ry=12 +rz=13 +s=14 "
+ "+convention=position_vector +step +inv +proj=cart +ellps=GRS80 "
+ "+step +proj=utm +zone=32 +ellps=GRS80");
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(operation, boundCRS_to_boundCRS_noop_for_TOWGS84) {
+ auto boundCRS1 = BoundCRS::createFromTOWGS84(
+ GeographicCRS::EPSG_4807, std::vector<double>{1, 2, 3, 4, 5, 6, 7});
+ auto boundCRS2 = BoundCRS::createFromTOWGS84(
+ GeographicCRS::EPSG_4269, std::vector<double>{1, 2, 3, 4, 5, 6, 7});
+ auto op = CoordinateOperationFactory::create()->createOperation(boundCRS1,
+ boundCRS2);
+ ASSERT_TRUE(op != nullptr);
+ EXPECT_EQ(
+ op->exportToPROJString(PROJStringFormatter::create().get()),
+ "+proj=pipeline +step +proj=axisswap +order=2,1 +step "
+ "+proj=unitconvert +xy_in=grad +xy_out=rad +step +inv +proj=longlat "
+ "+ellps=clrk80ign +pm=paris +step +proj=cart +ellps=clrk80ign "
+ "+step +inv +proj=cart +ellps=GRS80 +step +proj=unitconvert "
+ "+xy_in=rad +xy_out=deg +step +proj=axisswap +order=2,1");
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(operation, boundCRS_to_boundCRS_unralated_hub) {
+ auto boundCRS1 = BoundCRS::createFromTOWGS84(
+ GeographicCRS::EPSG_4807, std::vector<double>{1, 2, 3, 4, 5, 6, 7});
+ auto boundCRS2 = BoundCRS::create(
+ GeographicCRS::EPSG_4269, GeographicCRS::EPSG_4979,
+ Transformation::createGeocentricTranslations(
+ PropertyMap(), GeographicCRS::EPSG_4269, GeographicCRS::EPSG_4979,
+ 1.0, 2.0, 3.0, std::vector<PositionalAccuracyNNPtr>()));
+ auto op = CoordinateOperationFactory::create()->createOperation(boundCRS1,
+ boundCRS2);
+ ASSERT_TRUE(op != nullptr);
+ EXPECT_EQ(op->exportToPROJString(PROJStringFormatter::create().get()),
+ CoordinateOperationFactory::create()
+ ->createOperation(boundCRS1->baseCRS(), boundCRS2->baseCRS())
+ ->exportToPROJString(PROJStringFormatter::create().get()));
+}
+
+// ---------------------------------------------------------------------------
+
+static VerticalCRSNNPtr createVerticalCRS() {
+ PropertyMap propertiesVDatum;
+ propertiesVDatum.set(Identifier::CODESPACE_KEY, "EPSG")
+ .set(Identifier::CODE_KEY, 5101)
+ .set(IdentifiedObject::NAME_KEY, "Ordnance Datum Newlyn");
+ auto vdatum = VerticalReferenceFrame::create(propertiesVDatum);
+ PropertyMap propertiesCRS;
+ propertiesCRS.set(Identifier::CODESPACE_KEY, "EPSG")
+ .set(Identifier::CODE_KEY, 5701)
+ .set(IdentifiedObject::NAME_KEY, "ODN height");
+ return VerticalCRS::create(
+ propertiesCRS, vdatum,
+ VerticalCS::createGravityRelatedHeight(UnitOfMeasure::METRE));
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(operation, compoundCRS_to_geogCRS) {
+
+ auto compound = CompoundCRS::create(
+ PropertyMap(),
+ std::vector<CRSNNPtr>{GeographicCRS::EPSG_4326, createVerticalCRS()});
+ auto op = CoordinateOperationFactory::create()->createOperation(
+ compound, GeographicCRS::EPSG_4807);
+ ASSERT_TRUE(op != nullptr);
+ EXPECT_EQ(op->exportToPROJString(PROJStringFormatter::create().get()),
+ CoordinateOperationFactory::create()
+ ->createOperation(GeographicCRS::EPSG_4326,
+ GeographicCRS::EPSG_4807)
+ ->exportToPROJString(PROJStringFormatter::create().get()));
+}
+
+// ---------------------------------------------------------------------------
+
+static BoundCRSNNPtr createBoundVerticalCRS() {
+ auto vertCRS = createVerticalCRS();
+ auto transformation =
+ Transformation::createGravityRelatedHeightToGeographic3D(
+ PropertyMap(), vertCRS, GeographicCRS::EPSG_4979, "egm08_25.gtx",
+ std::vector<PositionalAccuracyNNPtr>());
+ return BoundCRS::create(vertCRS, GeographicCRS::EPSG_4979, transformation);
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(operation, transformation_height_to_PROJ_string) {
+ auto transf = createBoundVerticalCRS()->transformation();
+ EXPECT_EQ(transf->exportToPROJString(PROJStringFormatter::create().get()),
+ "+proj=vgridshift +grids=egm08_25.gtx");
+
+ auto grids = transf->gridsNeeded(DatabaseContext::create());
+ ASSERT_EQ(grids.size(), 1);
+ auto gridDesc = *(grids.begin());
+ EXPECT_EQ(gridDesc.shortName, "egm08_25.gtx");
+ EXPECT_EQ(gridDesc.packageName, "proj-datumgrid-world");
+ EXPECT_TRUE(gridDesc.url.find(
+ "https://download.osgeo.org/proj/proj-datumgrid-world-") ==
+ 0)
+ << gridDesc.url;
+ if (gridDesc.available) {
+ EXPECT_TRUE(!gridDesc.fullName.empty()) << gridDesc.fullName;
+ EXPECT_TRUE(gridDesc.fullName.find(gridDesc.shortName) !=
+ std::string::npos)
+ << gridDesc.fullName;
+ } else {
+ EXPECT_TRUE(gridDesc.fullName.empty()) << gridDesc.fullName;
+ }
+ EXPECT_EQ(gridDesc.directDownload, true);
+ EXPECT_EQ(gridDesc.openLicense, true);
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(operation, transformation_ntv2_to_PROJ_string) {
+ auto transformation = Transformation::createNTv2(
+ PropertyMap(), GeographicCRS::EPSG_4807, GeographicCRS::EPSG_4326,
+ "foo.gsb", std::vector<PositionalAccuracyNNPtr>());
+ EXPECT_EQ(
+ transformation->exportToPROJString(PROJStringFormatter::create().get()),
+ "+proj=pipeline +step +proj=axisswap +order=2,1 +step "
+ "+proj=unitconvert +xy_in=grad +xy_out=rad +step "
+ "+proj=hgridshift +grids=foo.gsb +step +proj=unitconvert "
+ "+xy_in=rad +xy_out=deg +step +proj=axisswap +order=2,1");
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(operation, transformation_VERTCON_to_PROJ_string) {
+ auto verticalCRS1 = createVerticalCRS();
+
+ auto verticalCRS2 = VerticalCRS::create(
+ PropertyMap(), VerticalReferenceFrame::create(PropertyMap()),
+ VerticalCS::createGravityRelatedHeight(UnitOfMeasure::METRE));
+
+ // Use of this type of transformation is a bit of non-sense here
+ // since it should normally be used with NGVD29 and NAVD88 for VerticalCRS,
+ // and NAD27/NAD83 as horizontal CRS...
+ auto vtransformation = Transformation::createVERTCON(
+ PropertyMap(), verticalCRS1, verticalCRS2, "bla.gtx",
+ std::vector<PositionalAccuracyNNPtr>());
+ EXPECT_EQ(vtransformation->exportToPROJString(
+ PROJStringFormatter::create().get()),
+ "+proj=vgridshift +grids=bla.gtx +multiplier=0.001");
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(operation, transformation_longitude_rotation_to_PROJ_string) {
+
+ auto src = GeographicCRS::create(
+ PropertyMap(), GeodeticReferenceFrame::create(
+ PropertyMap(), Ellipsoid::WGS84,
+ optional<std::string>(), PrimeMeridian::GREENWICH),
+ EllipsoidalCS::createLatitudeLongitude(UnitOfMeasure::DEGREE));
+ auto dest = GeographicCRS::create(
+ PropertyMap(), GeodeticReferenceFrame::create(
+ PropertyMap(), Ellipsoid::WGS84,
+ optional<std::string>(), PrimeMeridian::PARIS),
+ EllipsoidalCS::createLatitudeLongitude(UnitOfMeasure::DEGREE));
+ auto transformation = Transformation::createLongitudeRotation(
+ PropertyMap(), src, dest, Angle(10));
+ EXPECT_EQ(
+ transformation->exportToPROJString(PROJStringFormatter::create().get()),
+ "+proj=pipeline +step +proj=axisswap +order=2,1 +step "
+ "+proj=unitconvert +xy_in=deg +xy_out=rad +step +inv "
+ "+proj=longlat +ellps=WGS84 +pm=10 +step +proj=unitconvert "
+ "+xy_in=rad +xy_out=deg +step +proj=axisswap +order=2,1");
+ EXPECT_EQ(transformation->inverse()->exportToPROJString(
+ PROJStringFormatter::create().get()),
+ "+proj=pipeline +step +proj=axisswap +order=2,1 +step "
+ "+proj=unitconvert +xy_in=deg +xy_out=rad +step +inv "
+ "+proj=longlat +ellps=WGS84 +pm=-10 +step +proj=unitconvert "
+ "+xy_in=rad +xy_out=deg +step +proj=axisswap +order=2,1");
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(operation, transformation_Geographic2D_offsets_to_PROJ_string) {
+
+ auto transformation = Transformation::createGeographic2DOffsets(
+ PropertyMap(), GeographicCRS::EPSG_4326, GeographicCRS::EPSG_4326,
+ Angle(0.5), Angle(-1), {});
+ EXPECT_EQ(
+ transformation->exportToPROJString(PROJStringFormatter::create().get()),
+ "+proj=pipeline +step +proj=axisswap +order=2,1 +step "
+ "+proj=unitconvert +xy_in=deg +xy_out=rad +step +proj=geogoffset "
+ "+dlat=1800 +dlon=-3600 +step +proj=unitconvert +xy_in=rad "
+ "+xy_out=deg +step +proj=axisswap +order=2,1");
+ EXPECT_EQ(transformation->inverse()->exportToPROJString(
+ PROJStringFormatter::create().get()),
+ "+proj=pipeline +step +proj=axisswap +order=2,1 +step "
+ "+proj=unitconvert +xy_in=deg +xy_out=rad +step +proj=geogoffset "
+ "+dlat=-1800 +dlon=3600 +step +proj=unitconvert +xy_in=rad "
+ "+xy_out=deg +step +proj=axisswap +order=2,1");
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(operation, transformation_Geographic3D_offsets_to_PROJ_string) {
+
+ auto transformation = Transformation::createGeographic3DOffsets(
+ PropertyMap(), GeographicCRS::EPSG_4326, GeographicCRS::EPSG_4326,
+ Angle(0.5), Angle(-1), Length(2), {});
+ EXPECT_EQ(
+ transformation->exportToPROJString(PROJStringFormatter::create().get()),
+ "+proj=pipeline +step +proj=axisswap +order=2,1 +step "
+ "+proj=unitconvert +xy_in=deg +xy_out=rad +step +proj=geogoffset "
+ "+dlat=1800 +dlon=-3600 +dh=2 +step +proj=unitconvert +xy_in=rad "
+ "+xy_out=deg +step +proj=axisswap +order=2,1");
+ EXPECT_EQ(transformation->inverse()->exportToPROJString(
+ PROJStringFormatter::create().get()),
+ "+proj=pipeline +step +proj=axisswap +order=2,1 +step "
+ "+proj=unitconvert +xy_in=deg +xy_out=rad +step +proj=geogoffset "
+ "+dlat=-1800 +dlon=3600 +dh=-2 +step +proj=unitconvert "
+ "+xy_in=rad +xy_out=deg +step +proj=axisswap +order=2,1");
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(operation,
+ transformation_Geographic2D_with_height_offsets_to_PROJ_string) {
+
+ auto transformation = Transformation::createGeographic2DWithHeightOffsets(
+ PropertyMap(),
+ CompoundCRS::create(PropertyMap(),
+ {GeographicCRS::EPSG_4326, createVerticalCRS()}),
+ GeographicCRS::EPSG_4326, Angle(0.5), Angle(-1), Length(2), {});
+ EXPECT_EQ(
+ transformation->exportToPROJString(PROJStringFormatter::create().get()),
+ "+proj=pipeline +step +proj=axisswap +order=2,1 +step "
+ "+proj=unitconvert +xy_in=deg +xy_out=rad +step +proj=geogoffset "
+ "+dlat=1800 +dlon=-3600 +dh=2 +step +proj=unitconvert +xy_in=rad "
+ "+xy_out=deg +step +proj=axisswap +order=2,1");
+ EXPECT_EQ(transformation->inverse()->exportToPROJString(
+ PROJStringFormatter::create().get()),
+ "+proj=pipeline +step +proj=axisswap +order=2,1 +step "
+ "+proj=unitconvert +xy_in=deg +xy_out=rad +step +proj=geogoffset "
+ "+dlat=-1800 +dlon=3600 +dh=-2 +step +proj=unitconvert "
+ "+xy_in=rad +xy_out=deg +step +proj=axisswap +order=2,1");
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(operation, transformation_vertical_offset_to_PROJ_string) {
+
+ auto transformation = Transformation::createVerticalOffset(
+ PropertyMap(), createVerticalCRS(), createVerticalCRS(), Length(1), {});
+ EXPECT_EQ(
+ transformation->exportToPROJString(PROJStringFormatter::create().get()),
+ "+proj=geogoffset +dh=1");
+ EXPECT_EQ(transformation->inverse()->exportToPROJString(
+ PROJStringFormatter::create().get()),
+ "+proj=geogoffset +dh=-1");
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(operation, compoundCRS_with_boundVerticalCRS_to_geogCRS) {
+
+ auto compound = CompoundCRS::create(
+ PropertyMap(), std::vector<CRSNNPtr>{GeographicCRS::EPSG_4326,
+ createBoundVerticalCRS()});
+ auto op = CoordinateOperationFactory::create()->createOperation(
+ compound, GeographicCRS::EPSG_4979);
+ ASSERT_TRUE(op != nullptr);
+ EXPECT_EQ(op->exportToPROJString(PROJStringFormatter::create().get()),
+ "+proj=pipeline +step +proj=axisswap +order=2,1 +step "
+ "+proj=unitconvert +xy_in=deg +xy_out=rad +step +proj=vgridshift "
+ "+grids=egm08_25.gtx +step +proj=unitconvert +xy_in=rad "
+ "+xy_out=deg +step +proj=axisswap +order=2,1");
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(operation, compoundCRS_with_boundGeogCRS_to_geogCRS) {
+
+ auto geogCRS = GeographicCRS::create(
+ PropertyMap(), GeodeticReferenceFrame::create(
+ PropertyMap(), Ellipsoid::WGS84,
+ optional<std::string>(), PrimeMeridian::GREENWICH),
+ EllipsoidalCS::createLatitudeLongitude(UnitOfMeasure::DEGREE));
+ auto horizBoundCRS = BoundCRS::createFromTOWGS84(
+ geogCRS, std::vector<double>{1, 2, 3, 4, 5, 6, 7});
+ auto compound = CompoundCRS::create(
+ PropertyMap(),
+ std::vector<CRSNNPtr>{horizBoundCRS, createVerticalCRS()});
+ auto op = CoordinateOperationFactory::create()->createOperation(
+ compound, GeographicCRS::EPSG_4979);
+ ASSERT_TRUE(op != nullptr);
+ EXPECT_EQ(op->exportToPROJString(PROJStringFormatter::create().get()),
+ "+proj=pipeline +step +proj=axisswap +order=2,1 +step "
+ "+proj=unitconvert +xy_in=deg +xy_out=rad +step +proj=cart "
+ "+ellps=WGS84 +step +proj=helmert +x=1 +y=2 +z=3 +rx=4 +ry=5 "
+ "+rz=6 +s=7 +convention=position_vector +step +inv +proj=cart "
+ "+ellps=WGS84 +step +proj=unitconvert +xy_in=rad +xy_out=deg "
+ "+step +proj=axisswap +order=2,1");
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(operation, compoundCRS_with_boundGeogCRS_and_boundVerticalCRS_to_geogCRS) {
+
+ auto horizBoundCRS = BoundCRS::createFromTOWGS84(
+ GeographicCRS::EPSG_4807, std::vector<double>{1, 2, 3, 4, 5, 6, 7});
+ auto compound = CompoundCRS::create(
+ PropertyMap(),
+ std::vector<CRSNNPtr>{horizBoundCRS, createBoundVerticalCRS()});
+ auto op = CoordinateOperationFactory::create()->createOperation(
+ compound, GeographicCRS::EPSG_4979);
+ ASSERT_TRUE(op != nullptr);
+ // Not completely sure the order of horizontal and vertical operations
+ // makes sense
+ EXPECT_EQ(op->exportToPROJString(PROJStringFormatter::create().get()),
+ "+proj=pipeline +step +proj=axisswap +order=2,1 +step "
+ "+proj=unitconvert +xy_in=grad +xy_out=rad +step +inv "
+ "+proj=longlat +ellps=clrk80ign +pm=paris +step +proj=cart "
+ "+ellps=clrk80ign +step +proj=helmert +x=1 +y=2 +z=3 +rx=4 +ry=5 "
+ "+rz=6 +s=7 +convention=position_vector +step +inv +proj=cart "
+ "+ellps=WGS84 +step +proj=vgridshift +grids=egm08_25.gtx +step "
+ "+proj=unitconvert +xy_in=rad +xy_out=deg +step "
+ "+proj=axisswap +order=2,1");
+
+ auto grids = op->gridsNeeded(DatabaseContext::create());
+ EXPECT_EQ(grids.size(), 1);
+
+ auto opInverse = CoordinateOperationFactory::create()->createOperation(
+ GeographicCRS::EPSG_4979, compound);
+ ASSERT_TRUE(opInverse != nullptr);
+ EXPECT_TRUE(opInverse->inverse()->isEquivalentTo(op.get()));
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(operation, compoundCRS_with_boundProjCRS_and_boundVerticalCRS_to_geogCRS) {
+
+ auto horizBoundCRS = BoundCRS::createFromTOWGS84(
+ ProjectedCRS::create(
+ PropertyMap(), GeographicCRS::EPSG_4807,
+ Conversion::createUTM(PropertyMap(), 31, true),
+ CartesianCS::createEastingNorthing(UnitOfMeasure::METRE)),
+ std::vector<double>{1, 2, 3, 4, 5, 6, 7});
+ auto compound = CompoundCRS::create(
+ PropertyMap(),
+ std::vector<CRSNNPtr>{horizBoundCRS, createBoundVerticalCRS()});
+ auto op = CoordinateOperationFactory::create()->createOperation(
+ compound, GeographicCRS::EPSG_4979);
+ ASSERT_TRUE(op != nullptr);
+ // Not completely sure the order of horizontal and vertical operations
+ // makes sense
+ EXPECT_EQ(op->exportToPROJString(PROJStringFormatter::create().get()),
+ "+proj=pipeline +step +inv +proj=utm +zone=31 +ellps=clrk80ign "
+ "+pm=paris +step +proj=cart +ellps=clrk80ign +step +proj=helmert "
+ "+x=1 +y=2 +z=3 +rx=4 +ry=5 +rz=6 +s=7 "
+ "+convention=position_vector +step +inv +proj=cart +ellps=WGS84 "
+ "+step +proj=vgridshift +grids=egm08_25.gtx +step "
+ "+proj=unitconvert +xy_in=rad +xy_out=deg +step "
+ "+proj=axisswap +order=2,1");
+
+ auto opInverse = CoordinateOperationFactory::create()->createOperation(
+ GeographicCRS::EPSG_4979, compound);
+ ASSERT_TRUE(opInverse != nullptr);
+ EXPECT_TRUE(opInverse->inverse()->isEquivalentTo(op.get()));
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(operation, compoundCRS_to_compoundCRS) {
+ auto compound1 = CompoundCRS::create(
+ PropertyMap(),
+ std::vector<CRSNNPtr>{createUTM31_WGS84(), createVerticalCRS()});
+ auto compound2 = CompoundCRS::create(
+ PropertyMap(),
+ std::vector<CRSNNPtr>{createUTM32_WGS84(), createVerticalCRS()});
+ auto op = CoordinateOperationFactory::create()->createOperation(compound1,
+ compound2);
+ ASSERT_TRUE(op != nullptr);
+ auto opRef = CoordinateOperationFactory::create()->createOperation(
+ createUTM31_WGS84(), createUTM32_WGS84());
+ ASSERT_TRUE(opRef != nullptr);
+ EXPECT_TRUE(op->isEquivalentTo(opRef.get()));
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(operation, compoundCRS_to_compoundCRS_with_vertical_transform) {
+ auto verticalCRS1 = createVerticalCRS();
+
+ auto verticalCRS2 = VerticalCRS::create(
+ PropertyMap(), VerticalReferenceFrame::create(PropertyMap()),
+ VerticalCS::createGravityRelatedHeight(UnitOfMeasure::METRE));
+
+ // Use of this type of transformation is a bit of non-sense here
+ // since it should normally be used with NGVD29 and NAVD88 for VerticalCRS,
+ // and NAD27/NAD83 as horizontal CRS...
+ auto vtransformation = Transformation::createVERTCON(
+ PropertyMap(), verticalCRS1, verticalCRS2, "bla.gtx",
+ std::vector<PositionalAccuracyNNPtr>());
+
+ auto compound1 = CompoundCRS::create(
+ PropertyMap(),
+ std::vector<CRSNNPtr>{
+ ProjectedCRS::create(
+ PropertyMap(), GeographicCRS::EPSG_4326,
+ Conversion::createTransverseMercator(PropertyMap(), Angle(1),
+ Angle(2), Scale(3),
+ Length(4), Length(5)),
+ CartesianCS::createEastingNorthing(UnitOfMeasure::METRE)),
+ BoundCRS::create(verticalCRS1, verticalCRS2, vtransformation)});
+ auto compound2 = CompoundCRS::create(
+ PropertyMap(),
+ std::vector<CRSNNPtr>{createUTM32_WGS84(), verticalCRS2});
+
+ auto op = CoordinateOperationFactory::create()->createOperation(compound1,
+ compound2);
+ ASSERT_TRUE(op != nullptr);
+ EXPECT_EQ(op->exportToPROJString(PROJStringFormatter::create().get()),
+ "+proj=pipeline +step +inv +proj=tmerc +lat_0=1 +lon_0=2 +k_0=3 "
+ "+x_0=4 +y_0=5 +ellps=WGS84 +step "
+ "+proj=vgridshift +grids=bla.gtx +multiplier=0.001 +step "
+ "+proj=utm +zone=32 "
+ "+ellps=WGS84");
+ {
+ auto formatter = PROJStringFormatter::create();
+ formatter->setUseETMercForTMerc(true);
+ EXPECT_EQ(op->exportToPROJString(formatter.get()),
+ "+proj=pipeline +step +inv +proj=etmerc +lat_0=1 +lon_0=2 "
+ "+k_0=3 +x_0=4 +y_0=5 +ellps=WGS84 +step "
+ "+proj=vgridshift +grids=bla.gtx +multiplier=0.001 +step "
+ "+proj=utm +zone=32 "
+ "+ellps=WGS84");
+ }
+ {
+ auto formatter = PROJStringFormatter::create();
+ formatter->setUseETMercForTMerc(true);
+ EXPECT_EQ(op->inverse()->exportToPROJString(formatter.get()),
+ "+proj=pipeline +step +inv +proj=utm +zone=32 +ellps=WGS84 "
+ "+step +inv +proj=vgridshift +grids=bla.gtx "
+ "+multiplier=0.001 +step +proj=etmerc +lat_0=1 +lon_0=2 "
+ "+k_0=3 +x_0=4 +y_0=5 +ellps=WGS84");
+ }
+
+ auto opInverse = CoordinateOperationFactory::create()->createOperation(
+ compound2, compound1);
+ ASSERT_TRUE(opInverse != nullptr);
+ {
+ auto formatter = PROJStringFormatter::create();
+ auto formatter2 = PROJStringFormatter::create();
+ EXPECT_EQ(opInverse->inverse()->exportToPROJString(formatter.get()),
+ op->exportToPROJString(formatter2.get()));
+ }
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(operation, vertCRS_to_vertCRS) {
+
+ auto vertcrs_m_obj = PROJStringParser().createFromPROJString("+vunits=m");
+ auto vertcrs_m = nn_dynamic_pointer_cast<VerticalCRS>(vertcrs_m_obj);
+ ASSERT_TRUE(vertcrs_m != nullptr);
+
+ auto vertcrs_ft_obj = PROJStringParser().createFromPROJString("+vunits=ft");
+ auto vertcrs_ft = nn_dynamic_pointer_cast<VerticalCRS>(vertcrs_ft_obj);
+ ASSERT_TRUE(vertcrs_ft != nullptr);
+
+ auto vertcrs_us_ft_obj =
+ PROJStringParser().createFromPROJString("+vunits=us-ft");
+ auto vertcrs_us_ft =
+ nn_dynamic_pointer_cast<VerticalCRS>(vertcrs_us_ft_obj);
+ ASSERT_TRUE(vertcrs_us_ft != nullptr);
+
+ {
+ auto op = CoordinateOperationFactory::create()->createOperation(
+ NN_CHECK_ASSERT(vertcrs_m), NN_CHECK_ASSERT(vertcrs_ft));
+ ASSERT_TRUE(op != nullptr);
+ EXPECT_EQ(op->exportToPROJString(PROJStringFormatter::create().get()),
+ "+proj=unitconvert +z_in=m +z_out=ft");
+ }
+ {
+ auto op = CoordinateOperationFactory::create()->createOperation(
+ NN_CHECK_ASSERT(vertcrs_m), NN_CHECK_ASSERT(vertcrs_ft));
+ ASSERT_TRUE(op != nullptr);
+ EXPECT_EQ(op->inverse()->exportToPROJString(
+ PROJStringFormatter::create().get()),
+ "+proj=unitconvert +z_in=ft +z_out=m");
+ }
+ {
+ auto op = CoordinateOperationFactory::create()->createOperation(
+ NN_CHECK_ASSERT(vertcrs_ft), NN_CHECK_ASSERT(vertcrs_m));
+ ASSERT_TRUE(op != nullptr);
+ EXPECT_EQ(op->exportToPROJString(PROJStringFormatter::create().get()),
+ "+proj=unitconvert +z_in=ft +z_out=m");
+ }
+ {
+ auto op = CoordinateOperationFactory::create()->createOperation(
+ NN_CHECK_ASSERT(vertcrs_ft), NN_CHECK_ASSERT(vertcrs_us_ft));
+ ASSERT_TRUE(op != nullptr);
+ EXPECT_EQ(op->exportToPROJString(PROJStringFormatter::create().get()),
+ "+proj=affine +s33=0.999998");
+ }
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(operation, compoundCRS_to_geogCRS_3D) {
+
+ auto compoundcrs_ft_obj =
+ PROJStringParser().createFromPROJString("+proj=merc +vunits=ft");
+ auto compoundcrs_ft = nn_dynamic_pointer_cast<CRS>(compoundcrs_ft_obj);
+ ASSERT_TRUE(compoundcrs_ft != nullptr);
+
+ auto geogcrs_m_obj =
+ PROJStringParser().createFromPROJString("+proj=longlat +vunits=m");
+ auto geogcrs_m = nn_dynamic_pointer_cast<CRS>(geogcrs_m_obj);
+ ASSERT_TRUE(geogcrs_m != nullptr);
+
+ {
+ auto op = CoordinateOperationFactory::create()->createOperation(
+ NN_CHECK_ASSERT(compoundcrs_ft), NN_CHECK_ASSERT(geogcrs_m));
+ ASSERT_TRUE(op != nullptr);
+ EXPECT_EQ(op->exportToPROJString(PROJStringFormatter::create().get()),
+ "+proj=pipeline +step +inv +proj=merc +lon_0=0 +k=1 +x_0=0 "
+ "+y_0=0 +ellps=WGS84 +step +proj=unitconvert +xy_in=rad "
+ "+z_in=ft +xy_out=deg +z_out=m");
+ }
+
+ {
+ auto op = CoordinateOperationFactory::create()->createOperation(
+ NN_CHECK_ASSERT(geogcrs_m), NN_CHECK_ASSERT(compoundcrs_ft));
+ ASSERT_TRUE(op != nullptr);
+ EXPECT_EQ(op->exportToPROJString(PROJStringFormatter::create().get()),
+ "+proj=pipeline +step +proj=unitconvert +xy_in=deg +z_in=m "
+ "+xy_out=rad +z_out=ft +step +proj=merc +lon_0=0 +k=1 +x_0=0 "
+ "+y_0=0 +ellps=WGS84");
+ }
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(operation, IGNF_LAMB1_TO_EPSG_4326) {
+ auto authFactory =
+ AuthorityFactory::create(DatabaseContext::create(), std::string());
+ auto ctxt = CoordinateOperationContext::create(authFactory, nullptr, 0.0);
+ auto list = CoordinateOperationFactory::create()->createOperations(
+ AuthorityFactory::create(DatabaseContext::create(), "IGNF")
+ ->createCoordinateReferenceSystem("LAMB1"),
+ AuthorityFactory::create(DatabaseContext::create(), "EPSG")
+ ->createCoordinateReferenceSystem("4326"),
+ ctxt);
+ ASSERT_EQ(list.size(), 2);
+
+ EXPECT_EQ(list[0]->exportToPROJString(PROJStringFormatter::create().get()),
+ "+proj=pipeline +step +inv +proj=lcc +lat_1=49.5 +lat_0=49.5 "
+ "+lon_0=0 +k_0=0.99987734 +x_0=600000 +y_0=200000 "
+ "+ellps=clrk80ign +pm=paris +step +proj=hgridshift "
+ "+grids=ntf_r93.gsb +step +proj=unitconvert +xy_in=rad "
+ "+xy_out=deg +step +proj=axisswap +order=2,1");
+
+ EXPECT_EQ(list[1]->exportToPROJString(PROJStringFormatter::create().get()),
+ "+proj=pipeline +step +inv +proj=lcc +lat_1=49.5 +lat_0=49.5 "
+ "+lon_0=0 +k_0=0.99987734 +x_0=600000 +y_0=200000 "
+ "+ellps=clrk80ign +pm=paris +step +proj=cart +ellps=clrk80ign "
+ "+step +proj=helmert +x=-168 +y=-60 +z=320 +step +inv +proj=cart "
+ "+ellps=WGS84 +step +proj=unitconvert +xy_in=rad +xy_out=deg "
+ "+step +proj=axisswap +order=2,1");
+
+ auto list2 = CoordinateOperationFactory::create()->createOperations(
+ AuthorityFactory::create(DatabaseContext::create(), "EPSG")
+ // NTF (Paris) / Lambert Nord France equivalent to IGNF:LAMB1
+ ->createCoordinateReferenceSystem("27561"),
+ AuthorityFactory::create(DatabaseContext::create(), "EPSG")
+ ->createCoordinateReferenceSystem("4326"),
+ ctxt);
+ ASSERT_GE(list2.size(), 4U);
+
+ EXPECT_EQ(replaceAll(list2[0]->exportToPROJString(
+ PROJStringFormatter::create().get()),
+ "0.999877341", "0.99987734"),
+ list[0]->exportToPROJString(PROJStringFormatter::create().get()));
+
+ // The second entry in list2 (list2[1]) uses the
+ // weird +pm=2.33720833333333 from "NTF (Paris) to NTF (2)"
+ // and the third one uses the ESRI geographic offset method with another
+ // value
+ // so skip to the 4th method
+ EXPECT_EQ(replaceAll(list2[3]->exportToPROJString(
+ PROJStringFormatter::create().get()),
+ "0.999877341", "0.99987734"),
+ list[1]->exportToPROJString(PROJStringFormatter::create().get()));
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(operation, isPROJInstanciable) {
+
+ {
+ auto transformation = Transformation::createGeocentricTranslations(
+ PropertyMap(), GeographicCRS::EPSG_4269, GeographicCRS::EPSG_4326,
+ 1.0, 2.0, 3.0, {});
+ EXPECT_TRUE(
+ transformation->isPROJInstanciable(DatabaseContext::create()));
+ }
+
+ // Missing grid
+ {
+ auto transformation = Transformation::createNTv2(
+ PropertyMap(), GeographicCRS::EPSG_4807, GeographicCRS::EPSG_4326,
+ "foo.gsb", std::vector<PositionalAccuracyNNPtr>());
+ EXPECT_FALSE(
+ transformation->isPROJInstanciable(DatabaseContext::create()));
+ }
+
+ // Unsupported method
+ {
+ auto transformation = Transformation::create(
+ PropertyMap(), GeographicCRS::EPSG_4269, GeographicCRS::EPSG_4326,
+ nullptr, OperationMethod::create(
+ PropertyMap(), std::vector<OperationParameterNNPtr>{}),
+ std::vector<GeneralParameterValueNNPtr>{},
+ std::vector<PositionalAccuracyNNPtr>{});
+ EXPECT_FALSE(
+ transformation->isPROJInstanciable(DatabaseContext::create()));
+ }
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(operation, createOperation_on_crs_with_canonical_bound_crs) {
+ auto boundCRS = BoundCRS::createFromTOWGS84(
+ GeographicCRS::EPSG_4267, std::vector<double>{1, 2, 3, 4, 5, 6, 7});
+ auto crs = boundCRS->baseCRSWithCanonicalBoundCRS();
+ {
+ auto op = CoordinateOperationFactory::create()->createOperation(
+ crs, GeographicCRS::EPSG_4326);
+ ASSERT_TRUE(op != nullptr);
+ EXPECT_TRUE(op->isEquivalentTo(boundCRS->transformation().get()));
+ {
+ auto wkt1 = op->exportToWKT(
+ WKTFormatter::create(WKTFormatter::Convention::WKT2_2018)
+ .get());
+ auto wkt2 = boundCRS->transformation()->exportToWKT(
+ WKTFormatter::create(WKTFormatter::Convention::WKT2_2018)
+ .get());
+ EXPECT_EQ(wkt1, wkt2);
+ }
+ }
+ {
+ auto op = CoordinateOperationFactory::create()->createOperation(
+ GeographicCRS::EPSG_4326, crs);
+ ASSERT_TRUE(op != nullptr);
+ EXPECT_TRUE(
+ op->isEquivalentTo(boundCRS->transformation()->inverse().get()));
+ {
+ auto wkt1 = op->exportToWKT(
+ WKTFormatter::create(WKTFormatter::Convention::WKT2_2018)
+ .get());
+ auto wkt2 = boundCRS->transformation()->inverse()->exportToWKT(
+ WKTFormatter::create(WKTFormatter::Convention::WKT2_2018)
+ .get());
+ EXPECT_EQ(wkt1, wkt2);
+ }
+ }
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(operation, mercator_variant_A_to_variant_B) {
+ auto projCRS = ProjectedCRS::create(
+ PropertyMap(), GeographicCRS::EPSG_4326,
+ Conversion::createMercatorVariantA(PropertyMap(), Angle(0), Angle(1),
+ Scale(0.9), Length(3), Length(4)),
+ CartesianCS::createEastingNorthing(UnitOfMeasure::METRE));
+
+ auto conv = projCRS->derivingConversion();
+ auto sameConv =
+ conv->convertToOtherMethod(EPSG_CODE_METHOD_MERCATOR_VARIANT_A);
+ ASSERT_TRUE(sameConv);
+ EXPECT_TRUE(sameConv->isEquivalentTo(conv.get()));
+
+ auto targetConv =
+ conv->convertToOtherMethod(EPSG_CODE_METHOD_MERCATOR_VARIANT_B);
+ ASSERT_TRUE(targetConv);
+
+ auto lat_1 = targetConv->parameterValueNumeric(
+ EPSG_CODE_PARAMETER_LATITUDE_1ST_STD_PARALLEL, UnitOfMeasure::DEGREE);
+ EXPECT_EQ(lat_1, 25.917499691810534) << lat_1;
+
+ EXPECT_EQ(targetConv->parameterValueNumeric(
+ EPSG_CODE_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN,
+ UnitOfMeasure::DEGREE),
+ 1);
+
+ EXPECT_EQ(targetConv->parameterValueNumeric(
+ EPSG_CODE_PARAMETER_FALSE_EASTING, UnitOfMeasure::METRE),
+ 3);
+
+ EXPECT_EQ(targetConv->parameterValueNumeric(
+ EPSG_CODE_PARAMETER_FALSE_NORTHING, UnitOfMeasure::METRE),
+ 4);
+
+ EXPECT_FALSE(
+ conv->isEquivalentTo(targetConv.get(), IComparable::Criterion::STRICT));
+ EXPECT_TRUE(conv->isEquivalentTo(targetConv.get(),
+ IComparable::Criterion::EQUIVALENT));
+ EXPECT_TRUE(targetConv->isEquivalentTo(conv.get(),
+ IComparable::Criterion::EQUIVALENT));
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(operation, mercator_variant_A_to_variant_B_scale_1) {
+ auto projCRS = ProjectedCRS::create(
+ PropertyMap(), GeographicCRS::EPSG_4326,
+ Conversion::createMercatorVariantA(PropertyMap(), Angle(0), Angle(1),
+ Scale(1.0), Length(3), Length(4)),
+ CartesianCS::createEastingNorthing(UnitOfMeasure::METRE));
+
+ auto targetConv = projCRS->derivingConversion()->convertToOtherMethod(
+ EPSG_CODE_METHOD_MERCATOR_VARIANT_B);
+ ASSERT_TRUE(targetConv);
+
+ auto lat_1 = targetConv->parameterValueNumeric(
+ EPSG_CODE_PARAMETER_LATITUDE_1ST_STD_PARALLEL, UnitOfMeasure::DEGREE);
+ EXPECT_EQ(lat_1, 0.0) << lat_1;
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(operation, mercator_variant_A_to_variant_B_no_crs) {
+ auto targetConv =
+ Conversion::createMercatorVariantA(PropertyMap(), Angle(0), Angle(1),
+ Scale(1.0), Length(3), Length(4))
+ ->convertToOtherMethod(EPSG_CODE_METHOD_MERCATOR_VARIANT_B);
+ EXPECT_FALSE(targetConv != nullptr);
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(operation, mercator_variant_A_to_variant_B_invalid_scale) {
+ auto projCRS = ProjectedCRS::create(
+ PropertyMap(), GeographicCRS::EPSG_4326,
+ Conversion::createMercatorVariantA(PropertyMap(), Angle(0), Angle(1),
+ Scale(0.0), Length(3), Length(4)),
+ CartesianCS::createEastingNorthing(UnitOfMeasure::METRE));
+
+ auto targetConv = projCRS->derivingConversion()->convertToOtherMethod(
+ EPSG_CODE_METHOD_MERCATOR_VARIANT_B);
+ EXPECT_FALSE(targetConv != nullptr);
+}
+
+// ---------------------------------------------------------------------------
+
+static GeographicCRSNNPtr geographicCRSInvalidEccentricity() {
+ return GeographicCRS::create(
+ PropertyMap(),
+ GeodeticReferenceFrame::create(
+ PropertyMap(), Ellipsoid::createFlattenedSphere(
+ PropertyMap(), Length(6378137), Scale(0.1)),
+ optional<std::string>(), PrimeMeridian::GREENWICH),
+ EllipsoidalCS::createLatitudeLongitude(UnitOfMeasure::DEGREE));
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(operation, mercator_variant_A_to_variant_B_invalid_eccentricity) {
+ auto projCRS = ProjectedCRS::create(
+ PropertyMap(), geographicCRSInvalidEccentricity(),
+ Conversion::createMercatorVariantA(PropertyMap(), Angle(0), Angle(1),
+ Scale(1.0), Length(3), Length(4)),
+ CartesianCS::createEastingNorthing(UnitOfMeasure::METRE));
+
+ auto targetConv = projCRS->derivingConversion()->convertToOtherMethod(
+ EPSG_CODE_METHOD_MERCATOR_VARIANT_B);
+ EXPECT_FALSE(targetConv != nullptr);
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(operation, mercator_variant_B_to_variant_A) {
+ auto projCRS = ProjectedCRS::create(
+ PropertyMap(), GeographicCRS::EPSG_4326,
+ Conversion::createMercatorVariantB(PropertyMap(),
+ Angle(25.917499691810534), Angle(1),
+ Length(3), Length(4)),
+ CartesianCS::createEastingNorthing(UnitOfMeasure::METRE));
+ auto targetConv = projCRS->derivingConversion()->convertToOtherMethod(
+ EPSG_CODE_METHOD_MERCATOR_VARIANT_A);
+ ASSERT_TRUE(targetConv);
+
+ EXPECT_EQ(targetConv->parameterValueNumeric(
+ EPSG_CODE_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN,
+ UnitOfMeasure::DEGREE),
+ 0);
+
+ EXPECT_EQ(targetConv->parameterValueNumeric(
+ EPSG_CODE_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN,
+ UnitOfMeasure::DEGREE),
+ 1);
+
+ auto k_0 = targetConv->parameterValueNumeric(
+ EPSG_CODE_PARAMETER_SCALE_FACTOR_AT_NATURAL_ORIGIN,
+ UnitOfMeasure::SCALE_UNITY);
+ EXPECT_EQ(k_0, 0.9) << k_0;
+
+ EXPECT_EQ(targetConv->parameterValueNumeric(
+ EPSG_CODE_PARAMETER_FALSE_EASTING, UnitOfMeasure::METRE),
+ 3);
+
+ EXPECT_EQ(targetConv->parameterValueNumeric(
+ EPSG_CODE_PARAMETER_FALSE_NORTHING, UnitOfMeasure::METRE),
+ 4);
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(operation, mercator_variant_B_to_variant_A_invalid_std1) {
+ auto projCRS = ProjectedCRS::create(
+ PropertyMap(), GeographicCRS::EPSG_4326,
+ Conversion::createMercatorVariantB(PropertyMap(), Angle(100), Angle(1),
+ Length(3), Length(4)),
+ CartesianCS::createEastingNorthing(UnitOfMeasure::METRE));
+ auto targetConv = projCRS->derivingConversion()->convertToOtherMethod(
+ EPSG_CODE_METHOD_MERCATOR_VARIANT_A);
+ EXPECT_FALSE(targetConv != nullptr);
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(operation, mercator_variant_B_to_variant_A_invalid_eccentricity) {
+ auto projCRS = ProjectedCRS::create(
+ PropertyMap(), geographicCRSInvalidEccentricity(),
+ Conversion::createMercatorVariantB(PropertyMap(), Angle(0), Angle(1),
+ Length(3), Length(4)),
+ CartesianCS::createEastingNorthing(UnitOfMeasure::METRE));
+ auto targetConv = projCRS->derivingConversion()->convertToOtherMethod(
+ EPSG_CODE_METHOD_MERCATOR_VARIANT_A);
+ EXPECT_FALSE(targetConv != nullptr);
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(operation, lcc2sp_to_lcc1sp) {
+ // equivalent to EPSG:2154
+ auto projCRS = ProjectedCRS::create(
+ PropertyMap(), GeographicCRS::EPSG_4269, // something using GRS80
+ Conversion::createLambertConicConformal_2SP(
+ PropertyMap(), Angle(46.5), Angle(3), Angle(49), Angle(44),
+ Length(700000), Length(6600000)),
+ CartesianCS::createEastingNorthing(UnitOfMeasure::METRE));
+
+ auto conv = projCRS->derivingConversion();
+ auto targetConv = conv->convertToOtherMethod(
+ EPSG_CODE_METHOD_LAMBERT_CONIC_CONFORMAL_1SP);
+ ASSERT_TRUE(targetConv);
+
+ {
+ auto lat_0 = targetConv->parameterValueNumeric(
+ EPSG_CODE_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN,
+ UnitOfMeasure::DEGREE);
+ EXPECT_NEAR(lat_0, 46.519430223986866, 1e-12) << lat_0;
+
+ auto lon_0 = targetConv->parameterValueNumeric(
+ EPSG_CODE_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN,
+ UnitOfMeasure::DEGREE);
+ EXPECT_NEAR(lon_0, 3.0, 1e-15) << lon_0;
+
+ auto k_0 = targetConv->parameterValueNumeric(
+ EPSG_CODE_PARAMETER_SCALE_FACTOR_AT_NATURAL_ORIGIN,
+ UnitOfMeasure::SCALE_UNITY);
+ EXPECT_NEAR(k_0, 0.9990510286374692, 1e-15) << k_0;
+
+ auto x_0 = targetConv->parameterValueNumeric(
+ EPSG_CODE_PARAMETER_FALSE_EASTING, UnitOfMeasure::METRE);
+ EXPECT_NEAR(x_0, 700000, 1e-15) << x_0;
+
+ auto y_0 = targetConv->parameterValueNumeric(
+ EPSG_CODE_PARAMETER_FALSE_NORTHING, UnitOfMeasure::METRE);
+ EXPECT_NEAR(y_0, 6602157.8388103368, 1e-7) << y_0;
+ }
+
+ auto _2sp_from_1sp = targetConv->convertToOtherMethod(
+ EPSG_CODE_METHOD_LAMBERT_CONIC_CONFORMAL_2SP);
+ ASSERT_TRUE(_2sp_from_1sp);
+
+ {
+ auto lat_0 = _2sp_from_1sp->parameterValueNumeric(
+ EPSG_CODE_PARAMETER_LATITUDE_FALSE_ORIGIN, UnitOfMeasure::DEGREE);
+ EXPECT_NEAR(lat_0, 46.5, 1e-15) << lat_0;
+
+ auto lon_0 = _2sp_from_1sp->parameterValueNumeric(
+ EPSG_CODE_PARAMETER_LONGITUDE_FALSE_ORIGIN, UnitOfMeasure::DEGREE);
+ EXPECT_NEAR(lon_0, 3, 1e-15) << lon_0;
+
+ auto lat_1 = _2sp_from_1sp->parameterValueNumeric(
+ EPSG_CODE_PARAMETER_LATITUDE_1ST_STD_PARALLEL,
+ UnitOfMeasure::DEGREE);
+ EXPECT_NEAR(lat_1, 49, 1e-15) << lat_1;
+
+ auto lat_2 = _2sp_from_1sp->parameterValueNumeric(
+ EPSG_CODE_PARAMETER_LATITUDE_2ND_STD_PARALLEL,
+ UnitOfMeasure::DEGREE);
+ EXPECT_NEAR(lat_2, 44, 1e-15) << lat_2;
+
+ auto x_0 = _2sp_from_1sp->parameterValueNumeric(
+ EPSG_CODE_PARAMETER_EASTING_FALSE_ORIGIN, UnitOfMeasure::METRE);
+ EXPECT_NEAR(x_0, 700000, 1e-15) << x_0;
+
+ auto y_0 = _2sp_from_1sp->parameterValueNumeric(
+ EPSG_CODE_PARAMETER_NORTHING_FALSE_ORIGIN, UnitOfMeasure::METRE);
+ EXPECT_NEAR(y_0, 6600000, 1e-15) << y_0;
+ }
+
+ EXPECT_FALSE(
+ conv->isEquivalentTo(targetConv.get(), IComparable::Criterion::STRICT));
+ EXPECT_TRUE(conv->isEquivalentTo(targetConv.get(),
+ IComparable::Criterion::EQUIVALENT));
+ EXPECT_TRUE(targetConv->isEquivalentTo(conv.get(),
+ IComparable::Criterion::EQUIVALENT));
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(operation, lcc2sp_to_lcc1sp_phi0_eq_phi1_eq_phi2) {
+ auto projCRS = ProjectedCRS::create(
+ PropertyMap(), GeographicCRS::EPSG_4269, // something using GRS80
+ Conversion::createLambertConicConformal_2SP(
+ PropertyMap(), Angle(46.5), Angle(3), Angle(46.5), Angle(46.5),
+ Length(700000), Length(6600000)),
+ CartesianCS::createEastingNorthing(UnitOfMeasure::METRE));
+
+ auto conv = projCRS->derivingConversion();
+ auto targetConv = conv->convertToOtherMethod(
+ EPSG_CODE_METHOD_LAMBERT_CONIC_CONFORMAL_1SP);
+ ASSERT_TRUE(targetConv);
+
+ {
+ auto lat_0 = targetConv->parameterValueNumeric(
+ EPSG_CODE_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN,
+ UnitOfMeasure::DEGREE);
+ EXPECT_NEAR(lat_0, 46.5, 1e-15) << lat_0;
+
+ auto lon_0 = targetConv->parameterValueNumeric(
+ EPSG_CODE_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN,
+ UnitOfMeasure::DEGREE);
+ EXPECT_NEAR(lon_0, 3.0, 1e-15) << lon_0;
+
+ auto k_0 = targetConv->parameterValueNumeric(
+ EPSG_CODE_PARAMETER_SCALE_FACTOR_AT_NATURAL_ORIGIN,
+ UnitOfMeasure::SCALE_UNITY);
+ EXPECT_NEAR(k_0, 1.0, 1e-15) << k_0;
+
+ auto x_0 = targetConv->parameterValueNumeric(
+ EPSG_CODE_PARAMETER_FALSE_EASTING, UnitOfMeasure::METRE);
+ EXPECT_NEAR(x_0, 700000, 1e-15) << x_0;
+
+ auto y_0 = targetConv->parameterValueNumeric(
+ EPSG_CODE_PARAMETER_FALSE_NORTHING, UnitOfMeasure::METRE);
+ EXPECT_NEAR(y_0, 6600000, 1e-15) << y_0;
+ }
+
+ auto _2sp_from_1sp = targetConv->convertToOtherMethod(
+ EPSG_CODE_METHOD_LAMBERT_CONIC_CONFORMAL_2SP);
+ ASSERT_TRUE(_2sp_from_1sp);
+
+ {
+ auto lat_0 = _2sp_from_1sp->parameterValueNumeric(
+ EPSG_CODE_PARAMETER_LATITUDE_FALSE_ORIGIN, UnitOfMeasure::DEGREE);
+ EXPECT_NEAR(lat_0, 46.5, 1e-15) << lat_0;
+
+ auto lon_0 = _2sp_from_1sp->parameterValueNumeric(
+ EPSG_CODE_PARAMETER_LONGITUDE_FALSE_ORIGIN, UnitOfMeasure::DEGREE);
+ EXPECT_NEAR(lon_0, 3, 1e-15) << lon_0;
+
+ auto lat_1 = _2sp_from_1sp->parameterValueNumeric(
+ EPSG_CODE_PARAMETER_LATITUDE_1ST_STD_PARALLEL,
+ UnitOfMeasure::DEGREE);
+ EXPECT_NEAR(lat_1, 46.5, 1e-15) << lat_1;
+
+ auto lat_2 = _2sp_from_1sp->parameterValueNumeric(
+ EPSG_CODE_PARAMETER_LATITUDE_2ND_STD_PARALLEL,
+ UnitOfMeasure::DEGREE);
+ EXPECT_NEAR(lat_2, 46.5, 1e-15) << lat_2;
+
+ auto x_0 = _2sp_from_1sp->parameterValueNumeric(
+ EPSG_CODE_PARAMETER_EASTING_FALSE_ORIGIN, UnitOfMeasure::METRE);
+ EXPECT_NEAR(x_0, 700000, 1e-15) << x_0;
+
+ auto y_0 = _2sp_from_1sp->parameterValueNumeric(
+ EPSG_CODE_PARAMETER_NORTHING_FALSE_ORIGIN, UnitOfMeasure::METRE);
+ EXPECT_NEAR(y_0, 6600000, 1e-15) << y_0;
+ }
+
+ EXPECT_TRUE(conv->isEquivalentTo(targetConv.get(),
+ IComparable::Criterion::EQUIVALENT));
+ EXPECT_TRUE(targetConv->isEquivalentTo(conv.get(),
+ IComparable::Criterion::EQUIVALENT));
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(operation, lcc2sp_to_lcc1sp_phi0_diff_phi1_and_phi1_eq_phi2) {
+
+ auto projCRS = ProjectedCRS::create(
+ PropertyMap(), GeographicCRS::EPSG_4269, // something using GRS80
+ Conversion::createLambertConicConformal_2SP(
+ PropertyMap(), Angle(46.123), Angle(3), Angle(46.4567),
+ Angle(46.4567), Length(700000), Length(6600000)),
+ CartesianCS::createEastingNorthing(UnitOfMeasure::METRE));
+
+ auto conv = projCRS->derivingConversion();
+ auto targetConv = conv->convertToOtherMethod(
+ EPSG_CODE_METHOD_LAMBERT_CONIC_CONFORMAL_1SP);
+ ASSERT_TRUE(targetConv);
+
+ {
+ auto lat_0 = targetConv->parameterValueNumeric(
+ EPSG_CODE_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN,
+ UnitOfMeasure::DEGREE);
+ EXPECT_NEAR(lat_0, 46.4567, 1e-14) << lat_0;
+
+ auto lon_0 = targetConv->parameterValueNumeric(
+ EPSG_CODE_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN,
+ UnitOfMeasure::DEGREE);
+ EXPECT_NEAR(lon_0, 3.0, 1e-15) << lon_0;
+
+ auto k_0 = targetConv->parameterValueNumeric(
+ EPSG_CODE_PARAMETER_SCALE_FACTOR_AT_NATURAL_ORIGIN,
+ UnitOfMeasure::SCALE_UNITY);
+ EXPECT_NEAR(k_0, 1.0, 1e-15) << k_0;
+
+ auto x_0 = targetConv->parameterValueNumeric(
+ EPSG_CODE_PARAMETER_FALSE_EASTING, UnitOfMeasure::METRE);
+ EXPECT_NEAR(x_0, 700000, 1e-15) << x_0;
+
+ auto y_0 = targetConv->parameterValueNumeric(
+ EPSG_CODE_PARAMETER_FALSE_NORTHING, UnitOfMeasure::METRE);
+ EXPECT_NEAR(y_0, 6637093.292952879, 1e-8) << y_0;
+ }
+
+ auto _2sp_from_1sp = targetConv->convertToOtherMethod(
+ EPSG_CODE_METHOD_LAMBERT_CONIC_CONFORMAL_2SP);
+ ASSERT_TRUE(_2sp_from_1sp);
+
+ {
+ auto lat_0 = _2sp_from_1sp->parameterValueNumeric(
+ EPSG_CODE_PARAMETER_LATITUDE_FALSE_ORIGIN, UnitOfMeasure::DEGREE);
+ EXPECT_NEAR(lat_0, 46.4567, 1e-14) << lat_0;
+
+ auto lon_0 = _2sp_from_1sp->parameterValueNumeric(
+ EPSG_CODE_PARAMETER_LONGITUDE_FALSE_ORIGIN, UnitOfMeasure::DEGREE);
+ EXPECT_NEAR(lon_0, 3, 1e-15) << lon_0;
+
+ auto lat_1 = _2sp_from_1sp->parameterValueNumeric(
+ EPSG_CODE_PARAMETER_LATITUDE_1ST_STD_PARALLEL,
+ UnitOfMeasure::DEGREE);
+ EXPECT_NEAR(lat_1, 46.4567, 1e-14) << lat_1;
+
+ auto lat_2 = _2sp_from_1sp->parameterValueNumeric(
+ EPSG_CODE_PARAMETER_LATITUDE_2ND_STD_PARALLEL,
+ UnitOfMeasure::DEGREE);
+ EXPECT_NEAR(lat_2, 46.4567, 1e-14) << lat_2;
+
+ auto x_0 = _2sp_from_1sp->parameterValueNumeric(
+ EPSG_CODE_PARAMETER_EASTING_FALSE_ORIGIN, UnitOfMeasure::METRE);
+ EXPECT_NEAR(x_0, 700000, 1e-15) << x_0;
+
+ auto y_0 = _2sp_from_1sp->parameterValueNumeric(
+ EPSG_CODE_PARAMETER_NORTHING_FALSE_ORIGIN, UnitOfMeasure::METRE);
+ EXPECT_NEAR(y_0, 6637093.292952879, 1e-8) << y_0;
+ }
+
+ EXPECT_TRUE(conv->isEquivalentTo(targetConv.get(),
+ IComparable::Criterion::EQUIVALENT));
+ EXPECT_TRUE(targetConv->isEquivalentTo(conv.get(),
+ IComparable::Criterion::EQUIVALENT));
+
+ EXPECT_TRUE(_2sp_from_1sp->isEquivalentTo(
+ targetConv.get(), IComparable::Criterion::EQUIVALENT));
+ EXPECT_TRUE(targetConv->isEquivalentTo(_2sp_from_1sp.get(),
+ IComparable::Criterion::EQUIVALENT));
+
+ EXPECT_TRUE(conv->isEquivalentTo(_2sp_from_1sp.get(),
+ IComparable::Criterion::EQUIVALENT));
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(operation, lcc1sp_to_lcc2sp_invalid_eccentricity) {
+ auto projCRS = ProjectedCRS::create(
+ PropertyMap(), geographicCRSInvalidEccentricity(),
+ Conversion::createLambertConicConformal_1SP(PropertyMap(), Angle(40),
+ Angle(1), Scale(0.99),
+ Length(3), Length(4)),
+ CartesianCS::createEastingNorthing(UnitOfMeasure::METRE));
+ auto targetConv = projCRS->derivingConversion()->convertToOtherMethod(
+ EPSG_CODE_METHOD_LAMBERT_CONIC_CONFORMAL_2SP);
+ EXPECT_FALSE(targetConv != nullptr);
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(operation, lcc1sp_to_lcc2sp_invalid_scale) {
+ auto projCRS = ProjectedCRS::create(
+ PropertyMap(), GeographicCRS::EPSG_4326,
+ Conversion::createLambertConicConformal_1SP(
+ PropertyMap(), Angle(40), Angle(1), Scale(0), Length(3), Length(4)),
+ CartesianCS::createEastingNorthing(UnitOfMeasure::METRE));
+ auto targetConv = projCRS->derivingConversion()->convertToOtherMethod(
+ EPSG_CODE_METHOD_LAMBERT_CONIC_CONFORMAL_2SP);
+ EXPECT_FALSE(targetConv != nullptr);
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(operation, lcc1sp_to_lcc2sp_invalid_lat0) {
+ auto projCRS = ProjectedCRS::create(
+ PropertyMap(), GeographicCRS::EPSG_4326,
+ Conversion::createLambertConicConformal_1SP(PropertyMap(), Angle(100),
+ Angle(1), Scale(0.99),
+ Length(3), Length(4)),
+ CartesianCS::createEastingNorthing(UnitOfMeasure::METRE));
+ auto targetConv = projCRS->derivingConversion()->convertToOtherMethod(
+ EPSG_CODE_METHOD_LAMBERT_CONIC_CONFORMAL_2SP);
+ EXPECT_FALSE(targetConv != nullptr);
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(operation, lcc1sp_to_lcc2sp_null_lat0) {
+ auto projCRS = ProjectedCRS::create(
+ PropertyMap(), GeographicCRS::EPSG_4326,
+ Conversion::createLambertConicConformal_1SP(PropertyMap(), Angle(0),
+ Angle(1), Scale(0.99),
+ Length(3), Length(4)),
+ CartesianCS::createEastingNorthing(UnitOfMeasure::METRE));
+ auto targetConv = projCRS->derivingConversion()->convertToOtherMethod(
+ EPSG_CODE_METHOD_LAMBERT_CONIC_CONFORMAL_2SP);
+ EXPECT_FALSE(targetConv != nullptr);
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(operation, lcc2sp_to_lcc1sp_invalid_lat0) {
+ auto projCRS = ProjectedCRS::create(
+ PropertyMap(), GeographicCRS::EPSG_4326,
+ Conversion::createLambertConicConformal_2SP(
+ PropertyMap(), Angle(100), Angle(3), Angle(44), Angle(49),
+ Length(700000), Length(6600000)),
+ CartesianCS::createEastingNorthing(UnitOfMeasure::METRE));
+ auto targetConv = projCRS->derivingConversion()->convertToOtherMethod(
+ EPSG_CODE_METHOD_LAMBERT_CONIC_CONFORMAL_1SP);
+ EXPECT_FALSE(targetConv != nullptr);
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(operation, lcc2sp_to_lcc1sp_invalid_lat1) {
+ auto projCRS = ProjectedCRS::create(
+ PropertyMap(), GeographicCRS::EPSG_4326,
+ Conversion::createLambertConicConformal_2SP(
+ PropertyMap(), Angle(46.5), Angle(3), Angle(100), Angle(49),
+ Length(700000), Length(6600000)),
+ CartesianCS::createEastingNorthing(UnitOfMeasure::METRE));
+ auto targetConv = projCRS->derivingConversion()->convertToOtherMethod(
+ EPSG_CODE_METHOD_LAMBERT_CONIC_CONFORMAL_1SP);
+ EXPECT_FALSE(targetConv != nullptr);
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(operation, lcc2sp_to_lcc1sp_invalid_lat2) {
+ auto projCRS = ProjectedCRS::create(
+ PropertyMap(), GeographicCRS::EPSG_4326,
+ Conversion::createLambertConicConformal_2SP(
+ PropertyMap(), Angle(46.5), Angle(3), Angle(44), Angle(100),
+ Length(700000), Length(6600000)),
+ CartesianCS::createEastingNorthing(UnitOfMeasure::METRE));
+ auto targetConv = projCRS->derivingConversion()->convertToOtherMethod(
+ EPSG_CODE_METHOD_LAMBERT_CONIC_CONFORMAL_1SP);
+ EXPECT_FALSE(targetConv != nullptr);
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(operation, lcc2sp_to_lcc1sp_invalid_lat1_opposite_lat2) {
+ auto projCRS = ProjectedCRS::create(
+ PropertyMap(), GeographicCRS::EPSG_4326,
+ Conversion::createLambertConicConformal_2SP(
+ PropertyMap(), Angle(46.5), Angle(3), Angle(-49), Angle(49),
+ Length(700000), Length(6600000)),
+ CartesianCS::createEastingNorthing(UnitOfMeasure::METRE));
+ auto targetConv = projCRS->derivingConversion()->convertToOtherMethod(
+ EPSG_CODE_METHOD_LAMBERT_CONIC_CONFORMAL_1SP);
+ EXPECT_FALSE(targetConv != nullptr);
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(operation, lcc2sp_to_lcc1sp_invalid_lat1_and_lat2_close_to_zero) {
+ auto projCRS = ProjectedCRS::create(
+ PropertyMap(), GeographicCRS::EPSG_4326,
+ Conversion::createLambertConicConformal_2SP(
+ PropertyMap(), Angle(46.5), Angle(3), Angle(.0000000000000001),
+ Angle(.0000000000000002), Length(700000), Length(6600000)),
+ CartesianCS::createEastingNorthing(UnitOfMeasure::METRE));
+ auto targetConv = projCRS->derivingConversion()->convertToOtherMethod(
+ EPSG_CODE_METHOD_LAMBERT_CONIC_CONFORMAL_1SP);
+ EXPECT_FALSE(targetConv != nullptr);
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(operation, lcc2sp_to_lcc1sp_invalid_eccentricity) {
+ auto projCRS = ProjectedCRS::create(
+ PropertyMap(), geographicCRSInvalidEccentricity(),
+ Conversion::createLambertConicConformal_2SP(
+ PropertyMap(), Angle(46.5), Angle(3), Angle(44), Angle(49),
+ Length(700000), Length(6600000)),
+ CartesianCS::createEastingNorthing(UnitOfMeasure::METRE));
+ auto targetConv = projCRS->derivingConversion()->convertToOtherMethod(
+ EPSG_CODE_METHOD_LAMBERT_CONIC_CONFORMAL_1SP);
+ EXPECT_FALSE(targetConv != nullptr);
+}
diff --git a/test/unit/test_util.cpp b/test/unit/test_util.cpp
new file mode 100644
index 00000000..67c7da52
--- /dev/null
+++ b/test/unit/test_util.cpp
@@ -0,0 +1,63 @@
+/******************************************************************************
+ *
+ * Project: PROJ
+ * Purpose: Test ISO19111:2018 implementation
+ * Author: Even Rouault <even dot rouault at spatialys dot com>
+ *
+ ******************************************************************************
+ * Copyright (c) 2018, Even Rouault <even dot rouault at spatialys dot 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.
+ ****************************************************************************/
+
+#include "gtest_include.h"
+
+#include "proj/util.hpp"
+
+#include <iostream>
+
+using namespace osgeo::proj::util;
+
+// ---------------------------------------------------------------------------
+
+TEST(util, NameFactory) {
+ LocalNameNNPtr localname(NameFactory::createLocalName(nullptr, "foo"));
+ auto ns = localname->scope();
+ EXPECT_EQ(ns->isGlobal(), true);
+ EXPECT_EQ(ns->name()->toFullyQualifiedName()->toString(), "global");
+ EXPECT_EQ(localname->toFullyQualifiedName()->toString(), "foo");
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(util, NameFactory2) {
+ PropertyMap map;
+ map.set("separator", "/");
+ NameSpaceNNPtr nsIn(NameFactory::createNameSpace(
+ NameFactory::createLocalName(nullptr, std::string("bar")), map));
+ LocalNameNNPtr localname(
+ NameFactory::createLocalName(nsIn, std::string("foo")));
+ auto ns = localname->scope();
+ EXPECT_EQ(ns->isGlobal(), false);
+ auto fullyqualifiedNS = ns->name()->toFullyQualifiedName();
+ EXPECT_EQ(fullyqualifiedNS->toString(), "bar");
+ EXPECT_EQ(fullyqualifiedNS->scope()->isGlobal(), true);
+ EXPECT_EQ(fullyqualifiedNS->scope()->name()->scope()->isGlobal(), true);
+ EXPECT_EQ(localname->toFullyQualifiedName()->toString(), "bar/foo");
+}