diff options
| author | Even Rouault <even.rouault@spatialys.com> | 2018-11-14 17:40:42 +0100 |
|---|---|---|
| committer | Even Rouault <even.rouault@spatialys.com> | 2018-11-14 22:48:29 +0100 |
| commit | d928db15d53805d9b728b440079756081961c536 (patch) | |
| tree | e862a961d26bedb34c58e4f28ef0bdeedb5f3225 /test | |
| parent | 330e8bf686f9c4524075ca1ff50cbca6c9e091da (diff) | |
| download | PROJ-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.gie | 15 | ||||
| -rw-r--r-- | test/unit/CMakeLists.txt | 25 | ||||
| -rw-r--r-- | test/unit/Makefile.am | 13 | ||||
| -rw-r--r-- | test/unit/main.cpp | 5 | ||||
| -rw-r--r-- | test/unit/test_c_api.cpp | 1925 | ||||
| -rw-r--r-- | test/unit/test_common.cpp | 191 | ||||
| -rw-r--r-- | test/unit/test_crs.cpp | 4647 | ||||
| -rw-r--r-- | test/unit/test_datum.cpp | 482 | ||||
| -rw-r--r-- | test/unit/test_factory.cpp | 2732 | ||||
| -rw-r--r-- | test/unit/test_io.cpp | 7148 | ||||
| -rw-r--r-- | test/unit/test_metadata.cpp | 388 | ||||
| -rw-r--r-- | test/unit/test_operation.cpp | 6271 | ||||
| -rw-r--r-- | test/unit/test_util.cpp | 63 |
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 ¶mName = opParamvalue->parameter()->nameStr(); + const auto ¶meterValue = 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 ¶mName = opParamvalue->parameter()->nameStr(); + const auto ¶meterValue = 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 ¶mName = opParamvalue->parameter()->nameStr(); + const auto ¶meterValue = 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 ¶mName = opParamvalue->parameter()->nameStr(); + const auto ¶meterValue = 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 ¶mName = opParamvalue->parameter()->nameStr(); + const auto ¶meterValue = 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 ¶mName = opParamvalue->parameter()->nameStr(); + const auto ¶meterValue = 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 ¶mName = opParamvalue->parameter()->nameStr(); + const auto ¶meterValue = 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 ¶mName = + *(opParamvalue->parameter()->name()->description()); + const auto ¶meterValue = 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 ¶mName = + *(opParamvalue->parameter()->name()->description()); + const auto ¶meterValue = 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 ¶mName = opParamvalue->parameter()->nameStr(); + const auto ¶meterValue = 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 ¶mName = opParamvalue->parameter()->nameStr(); + const auto ¶meterValue = 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 ¶mName = opParamvalue->parameter()->nameStr(); + const auto ¶meterValue = 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 ¶mName = opParamvalue->parameter()->nameStr(); + const auto ¶meterValue = 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 ¶mName = opParamvalue->parameter()->nameStr(); + const auto ¶meterValue = 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 ¶mName = opParamvalue->parameter()->nameStr(); + const auto ¶meterValue = 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 ¶mName = opParamvalue->parameter()->nameStr(); + const auto ¶meterValue = 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 ¶m : 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 ¶mName = opParamvalue->parameter()->nameStr(); + const auto ¶meterValue = 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"); +} |
