aboutsummaryrefslogtreecommitdiff
path: root/test/unit/test_io.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'test/unit/test_io.cpp')
-rw-r--r--test/unit/test_io.cpp7148
1 files changed, 7148 insertions, 0 deletions
diff --git a/test/unit/test_io.cpp b/test/unit/test_io.cpp
new file mode 100644
index 00000000..7d26d82a
--- /dev/null
+++ b/test/unit/test_io.cpp
@@ -0,0 +1,7148 @@
+/******************************************************************************
+ *
+ * Project: PROJ
+ * Purpose: Test ISO19111:2018 implementation
+ * Author: Even Rouault <even dot rouault at spatialys dot com>
+ *
+ ******************************************************************************
+ * Copyright (c) 2018, Even Rouault <even dot rouault at spatialys dot com>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included
+ * in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+ * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ ****************************************************************************/
+
+#include "gtest_include.h"
+
+// to be able to use internal::toString
+#define FROM_PROJ_CPP
+
+#include "proj/common.hpp"
+#include "proj/coordinateoperation.hpp"
+#include "proj/coordinatesystem.hpp"
+#include "proj/crs.hpp"
+#include "proj/datum.hpp"
+#include "proj/io.hpp"
+#include "proj/metadata.hpp"
+#include "proj/util.hpp"
+
+#include "proj/internal/internal.hpp"
+
+#include <string>
+
+using namespace osgeo::proj::common;
+using namespace osgeo::proj::crs;
+using namespace osgeo::proj::cs;
+using namespace osgeo::proj::datum;
+using namespace osgeo::proj::internal;
+using namespace osgeo::proj::io;
+using namespace osgeo::proj::metadata;
+using namespace osgeo::proj::operation;
+using namespace osgeo::proj::util;
+
+// ---------------------------------------------------------------------------
+
+TEST(io, wkt_parsing) {
+ {
+ auto n = WKTNode::createFrom("MYNODE[]");
+ EXPECT_EQ(n->value(), "MYNODE");
+ EXPECT_TRUE(n->children().empty());
+ EXPECT_EQ(n->toString(), "MYNODE");
+ }
+ {
+ auto n = WKTNode::createFrom(" MYNODE [ ] ");
+ EXPECT_EQ(n->value(), "MYNODE");
+ EXPECT_TRUE(n->children().empty());
+ }
+ EXPECT_THROW(WKTNode::createFrom(""), ParsingException);
+ EXPECT_THROW(WKTNode::createFrom("x"), ParsingException);
+ EXPECT_THROW(WKTNode::createFrom("x,"), ParsingException);
+ EXPECT_THROW(WKTNode::createFrom("x["), ParsingException);
+ EXPECT_THROW(WKTNode::createFrom("["), ParsingException);
+
+ {
+ auto n = WKTNode::createFrom("MYNODE[\"x\"]");
+ EXPECT_EQ(n->value(), "MYNODE");
+ ASSERT_EQ(n->children().size(), 1);
+ EXPECT_EQ(n->children()[0]->value(), "\"x\"");
+ EXPECT_EQ(n->toString(), "MYNODE[\"x\"]");
+ }
+
+ EXPECT_THROW(WKTNode::createFrom("MYNODE[\"x\""), ParsingException);
+
+ {
+ auto n = WKTNode::createFrom("MYNODE[ \"x\" ]");
+ EXPECT_EQ(n->value(), "MYNODE");
+ ASSERT_EQ(n->children().size(), 1);
+ EXPECT_EQ(n->children()[0]->value(), "\"x\"");
+ }
+
+ {
+ auto n = WKTNode::createFrom("MYNODE[\"x[\",1]");
+ EXPECT_EQ(n->value(), "MYNODE");
+ ASSERT_EQ(n->children().size(), 2);
+ EXPECT_EQ(n->children()[0]->value(), "\"x[\"");
+ EXPECT_EQ(n->children()[1]->value(), "1");
+ EXPECT_EQ(n->toString(), "MYNODE[\"x[\",1]");
+ }
+
+ EXPECT_THROW(WKTNode::createFrom("MYNODE[\"x\","), ParsingException);
+
+ {
+ auto n = WKTNode::createFrom("A[B[y]]");
+ EXPECT_EQ(n->value(), "A");
+ ASSERT_EQ(n->children().size(), 1);
+ EXPECT_EQ(n->children()[0]->value(), "B");
+ ASSERT_EQ(n->children()[0]->children().size(), 1);
+ EXPECT_EQ(n->children()[0]->children()[0]->value(), "y");
+ EXPECT_EQ(n->toString(), "A[B[y]]");
+ }
+
+ EXPECT_THROW(WKTNode::createFrom("A[B["), ParsingException);
+
+ std::string str;
+ for (int i = 0; i < 17; i++) {
+ str = "A[" + str + "]";
+ }
+ EXPECT_THROW(WKTNode::createFrom(str), ParsingException);
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(io, wkt_parsing_with_parenthesis) {
+
+ auto n = WKTNode::createFrom("A(\"x\",B(\"y\"))");
+ EXPECT_EQ(n->toString(), "A[\"x\",B[\"y\"]]");
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(io, wkt_parsing_with_double_quotes_inside) {
+
+ auto n = WKTNode::createFrom("A[\"xy\"\"z\"]");
+ EXPECT_EQ(n->children()[0]->value(), "\"xy\"z\"");
+ EXPECT_EQ(n->toString(), "A[\"xy\"\"z\"]");
+
+ EXPECT_THROW(WKTNode::createFrom("A[\"x\""), ParsingException);
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(io, wkt_parsing_with_printed_quotes) {
+ static const std::string startPrintedQuote("\xE2\x80\x9C");
+ static const std::string endPrintedQuote("\xE2\x80\x9D");
+
+ auto n = WKTNode::createFrom("A[" + startPrintedQuote + "x" +
+ endPrintedQuote + "]");
+ EXPECT_EQ(n->children()[0]->value(), "\"x\"");
+ EXPECT_EQ(n->toString(), "A[\"x\"]");
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(wkt_parse, sphere) {
+ auto obj = WKTParser().createFromWKT(
+ "ELLIPSOID[\"Sphere\",6378137,0,LENGTHUNIT[\"metre\",1]]");
+ auto ellipsoid = nn_dynamic_pointer_cast<Ellipsoid>(obj);
+ ASSERT_TRUE(ellipsoid != nullptr);
+ EXPECT_TRUE(ellipsoid->isSphere());
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(wkt_parse, datum_with_ANCHOR) {
+ auto obj = WKTParser().createFromWKT(
+ "DATUM[\"WGS_1984 with anchor\",\n"
+ " ELLIPSOID[\"WGS 84\",6378137,298.257223563,\n"
+ " LENGTHUNIT[\"metre\",1,\n"
+ " ID[\"EPSG\",9001]],\n"
+ " ID[\"EPSG\",7030]],\n"
+ " ANCHOR[\"My anchor\"]]");
+ auto datum = nn_dynamic_pointer_cast<GeodeticReferenceFrame>(obj);
+ ASSERT_TRUE(datum != nullptr);
+ EXPECT_EQ(datum->ellipsoid()->celestialBody(), "Earth");
+ EXPECT_EQ(datum->primeMeridian()->nameStr(), "Greenwich");
+ auto anchor = datum->anchorDefinition();
+ EXPECT_TRUE(anchor.has_value());
+ EXPECT_EQ(*anchor, "My anchor");
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(wkt_parse, datum_no_pm_not_earth) {
+ auto obj = WKTParser().createFromWKT("DATUM[\"unnamed\",\n"
+ " ELLIPSOID[\"unnamed\",1,0,\n"
+ " LENGTHUNIT[\"metre\",1]]]");
+ auto datum = nn_dynamic_pointer_cast<GeodeticReferenceFrame>(obj);
+ ASSERT_TRUE(datum != nullptr);
+ EXPECT_EQ(datum->ellipsoid()->celestialBody(), "Non-Earth body");
+ EXPECT_EQ(datum->primeMeridian()->nameStr(), "Reference meridian");
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(wkt_parse, dynamic_geodetic_reference_frame) {
+ auto obj = WKTParser().createFromWKT(
+ "GEOGCRS[\"WGS 84 (G1762)\","
+ "DYNAMIC[FRAMEEPOCH[2005.0]],"
+ "TRF[\"World Geodetic System 1984 (G1762)\","
+ " ELLIPSOID[\"WGS 84\",6378137,298.257223563],"
+ " ANCHOR[\"My anchor\"]],"
+ "CS[ellipsoidal,3],"
+ " AXIS[\"(lat)\",north,ANGLEUNIT[\"degree\",0.0174532925199433]],"
+ " AXIS[\"(lon)\",east,ANGLEUNIT[\"degree\",0.0174532925199433]],"
+ " AXIS[\"ellipsoidal height (h)\",up,LENGTHUNIT[\"metre\",1.0]]"
+ "]");
+ auto crs = nn_dynamic_pointer_cast<GeodeticCRS>(obj);
+ ASSERT_TRUE(crs != nullptr);
+ auto dgrf =
+ std::dynamic_pointer_cast<DynamicGeodeticReferenceFrame>(crs->datum());
+ ASSERT_TRUE(dgrf != nullptr);
+ auto anchor = dgrf->anchorDefinition();
+ EXPECT_TRUE(anchor.has_value());
+ EXPECT_EQ(*anchor, "My anchor");
+ EXPECT_TRUE(dgrf->frameReferenceEpoch() ==
+ Measure(2005.0, UnitOfMeasure::YEAR));
+ auto model = dgrf->deformationModelName();
+ EXPECT_TRUE(!model.has_value());
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(wkt_parse, dynamic_geodetic_reference_frame_with_model) {
+ auto obj = WKTParser().createFromWKT(
+ "GEOGCRS[\"WGS 84 (G1762)\","
+ "DYNAMIC[FRAMEEPOCH[2005.0],MODEL[\"my_model\"]],"
+ "TRF[\"World Geodetic System 1984 (G1762)\","
+ " ELLIPSOID[\"WGS 84\",6378137,298.257223563],"
+ " ANCHOR[\"My anchor\"]],"
+ "CS[ellipsoidal,3],"
+ " AXIS[\"(lat)\",north,ANGLEUNIT[\"degree\",0.0174532925199433]],"
+ " AXIS[\"(lon)\",east,ANGLEUNIT[\"degree\",0.0174532925199433]],"
+ " AXIS[\"ellipsoidal height (h)\",up,LENGTHUNIT[\"metre\",1.0]]"
+ "]");
+ auto crs = nn_dynamic_pointer_cast<GeodeticCRS>(obj);
+ ASSERT_TRUE(crs != nullptr);
+ auto dgrf =
+ std::dynamic_pointer_cast<DynamicGeodeticReferenceFrame>(crs->datum());
+ ASSERT_TRUE(dgrf != nullptr);
+ auto anchor = dgrf->anchorDefinition();
+ EXPECT_TRUE(anchor.has_value());
+ EXPECT_EQ(*anchor, "My anchor");
+ EXPECT_TRUE(dgrf->frameReferenceEpoch() ==
+ Measure(2005.0, UnitOfMeasure::YEAR));
+ auto model = dgrf->deformationModelName();
+ EXPECT_TRUE(model.has_value());
+ EXPECT_EQ(*model, "my_model");
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(wkt_parse, dynamic_geodetic_reference_frame_with_velocitygrid) {
+ auto obj = WKTParser().createFromWKT(
+ "GEOGCRS[\"WGS 84 (G1762)\","
+ "DYNAMIC[FRAMEEPOCH[2005.0],VELOCITYGRID[\"my_model\"]],"
+ "TRF[\"World Geodetic System 1984 (G1762)\","
+ " ELLIPSOID[\"WGS 84\",6378137,298.257223563],"
+ " ANCHOR[\"My anchor\"]],"
+ "CS[ellipsoidal,3],"
+ " AXIS[\"(lat)\",north,ANGLEUNIT[\"degree\",0.0174532925199433]],"
+ " AXIS[\"(lon)\",east,ANGLEUNIT[\"degree\",0.0174532925199433]],"
+ " AXIS[\"ellipsoidal height (h)\",up,LENGTHUNIT[\"metre\",1.0]]"
+ "]");
+ auto crs = nn_dynamic_pointer_cast<GeodeticCRS>(obj);
+ ASSERT_TRUE(crs != nullptr);
+ auto dgrf =
+ std::dynamic_pointer_cast<DynamicGeodeticReferenceFrame>(crs->datum());
+ ASSERT_TRUE(dgrf != nullptr);
+ auto anchor = dgrf->anchorDefinition();
+ EXPECT_TRUE(anchor.has_value());
+ EXPECT_EQ(*anchor, "My anchor");
+ EXPECT_TRUE(dgrf->frameReferenceEpoch() ==
+ Measure(2005.0, UnitOfMeasure::YEAR));
+ auto model = dgrf->deformationModelName();
+ EXPECT_TRUE(model.has_value());
+ EXPECT_EQ(*model, "my_model");
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(wkt_parse, geogcrs_with_ensemble) {
+ auto obj = WKTParser().createFromWKT(
+ "GEOGCRS[\"WGS 84\","
+ "ENSEMBLE[\"WGS 84 ensemble\","
+ " MEMBER[\"WGS 84 (TRANSIT)\"],"
+ " MEMBER[\"WGS 84 (G730)\"],"
+ " MEMBER[\"WGS 84 (G834)\"],"
+ " MEMBER[\"WGS 84 (G1150)\"],"
+ " MEMBER[\"WGS 84 (G1674)\"],"
+ " MEMBER[\"WGS 84 (G1762)\"],"
+ " ELLIPSOID[\"WGS "
+ "84\",6378137,298.2572236,LENGTHUNIT[\"metre\",1.0]],"
+ " ENSEMBLEACCURACY[2]"
+ "],"
+ "CS[ellipsoidal,3],"
+ " AXIS[\"(lat)\",north,ANGLEUNIT[\"degree\",0.0174532925199433]],"
+ " AXIS[\"(lon)\",east,ANGLEUNIT[\"degree\",0.0174532925199433]],"
+ " AXIS[\"ellipsoidal height (h)\",up,LENGTHUNIT[\"metre\",1.0]]"
+ "]");
+ auto crs = nn_dynamic_pointer_cast<GeodeticCRS>(obj);
+ ASSERT_TRUE(crs != nullptr);
+ ASSERT_TRUE(crs->datum() == nullptr);
+ ASSERT_TRUE(crs->datumEnsemble() != nullptr);
+ EXPECT_EQ(crs->datumEnsemble()->datums().size(), 6);
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(wkt_parse, invalid_geogcrs_with_ensemble) {
+ auto wkt =
+ "GEOGCRS[\"WGS 84\","
+ "ENSEMBLE[\"WGS 84 ensemble\","
+ " MEMBER[\"WGS 84 (TRANSIT)\"],"
+ " MEMBER[\"WGS 84 (G730)\"],"
+ " MEMBER[\"WGS 84 (G834)\"],"
+ " MEMBER[\"WGS 84 (G1150)\"],"
+ " MEMBER[\"WGS 84 (G1674)\"],"
+ " MEMBER[\"WGS 84 (G1762)\"],"
+ " ENSEMBLEACCURACY[2]"
+ "],"
+ "CS[ellipsoidal,3],"
+ " AXIS[\"(lat)\",north,ANGLEUNIT[\"degree\",0.0174532925199433]],"
+ " AXIS[\"(lon)\",east,ANGLEUNIT[\"degree\",0.0174532925199433]],"
+ " AXIS[\"ellipsoidal height (h)\",up,LENGTHUNIT[\"metre\",1.0]]"
+ "]";
+ EXPECT_THROW(WKTParser().createFromWKT(wkt), ParsingException);
+}
+
+// ---------------------------------------------------------------------------
+
+static void checkEPSG_4326(GeographicCRSPtr crs, bool latLong = true,
+ bool checkEPSGCodes = true) {
+ if (checkEPSGCodes) {
+ ASSERT_EQ(crs->identifiers().size(), 1);
+ EXPECT_EQ(crs->identifiers()[0]->code(), "4326");
+ EXPECT_EQ(*(crs->identifiers()[0]->codeSpace()), "EPSG");
+ }
+ EXPECT_EQ(crs->nameStr(), "WGS 84");
+
+ auto cs = crs->coordinateSystem();
+ ASSERT_EQ(cs->axisList().size(), 2);
+ if (latLong) {
+ EXPECT_EQ(cs->axisList()[0]->nameStr(), "Latitude");
+ EXPECT_EQ(cs->axisList()[0]->abbreviation(), "lat");
+ EXPECT_EQ(cs->axisList()[0]->direction(), AxisDirection::NORTH);
+
+ EXPECT_EQ(cs->axisList()[1]->nameStr(), "Longitude");
+ EXPECT_EQ(cs->axisList()[1]->abbreviation(), "lon");
+ EXPECT_EQ(cs->axisList()[1]->direction(), AxisDirection::EAST);
+ } else {
+ EXPECT_EQ(cs->axisList()[0]->nameStr(), "Longitude");
+ EXPECT_EQ(cs->axisList()[0]->abbreviation(), "lon");
+ EXPECT_EQ(cs->axisList()[0]->direction(), AxisDirection::EAST);
+
+ EXPECT_EQ(cs->axisList()[1]->nameStr(), "Latitude");
+ EXPECT_EQ(cs->axisList()[1]->abbreviation(), "lat");
+ EXPECT_EQ(cs->axisList()[1]->direction(), AxisDirection::NORTH);
+ }
+
+ auto datum = crs->datum();
+ if (checkEPSGCodes) {
+ ASSERT_EQ(datum->identifiers().size(), 1);
+ EXPECT_EQ(datum->identifiers()[0]->code(), "6326");
+ EXPECT_EQ(*(datum->identifiers()[0]->codeSpace()), "EPSG");
+ }
+ EXPECT_EQ(datum->nameStr(), "World Geodetic System 1984");
+
+ auto ellipsoid = datum->ellipsoid();
+ EXPECT_EQ(ellipsoid->semiMajorAxis().value(), 6378137.0);
+ EXPECT_EQ(ellipsoid->semiMajorAxis().unit(), UnitOfMeasure::METRE);
+ EXPECT_EQ(ellipsoid->inverseFlattening()->value(), 298.257223563);
+ if (checkEPSGCodes) {
+ ASSERT_EQ(ellipsoid->identifiers().size(), 1);
+ EXPECT_EQ(ellipsoid->identifiers()[0]->code(), "7030");
+ EXPECT_EQ(*(ellipsoid->identifiers()[0]->codeSpace()), "EPSG");
+ }
+ EXPECT_EQ(ellipsoid->nameStr(), "WGS 84");
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(wkt_parse, wkt1_EPSG_4326) {
+ auto obj = WKTParser().createFromWKT(
+ "GEOGCS[\"WGS 84\","
+ " DATUM[\"WGS_1984\","
+ " SPHEROID[\"WGS 84\",6378137,298.257223563,"
+ " AUTHORITY[\"EPSG\",\"7030\"]],"
+ " AUTHORITY[\"EPSG\",\"6326\"]],"
+ " PRIMEM[\"Greenwich\",0,"
+ " AUTHORITY[\"EPSG\",\"8901\"]],"
+ " UNIT[\"degree\",0.0174532925199433,"
+ " AUTHORITY[\"EPSG\",\"9122\"]],"
+ " AUTHORITY[\"EPSG\",\"4326\"]]");
+ auto crs = nn_dynamic_pointer_cast<GeographicCRS>(obj);
+ ASSERT_TRUE(crs != nullptr);
+ checkEPSG_4326(crs, false /* longlat order */);
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(wkt_parse, wkt1_EPSG_4807_grad_mess) {
+ auto obj = WKTParser().createFromWKT(
+ "GEOGCS[\"NTF (Paris)\",\n"
+ " DATUM[\"Nouvelle_Triangulation_Francaise_Paris\",\n"
+ " SPHEROID[\"Clarke 1880 (IGN)\",6378249.2,293.466021293627,\n"
+ " AUTHORITY[\"EPSG\",\"6807\"]],\n"
+ " AUTHORITY[\"EPSG\",\"6807\"]],\n"
+ /* WKT1_GDAL weirdness: PRIMEM is converted to degree */
+ " PRIMEM[\"Paris\",2.33722917,\n"
+ " AUTHORITY[\"EPSG\",\"8903\"]],\n"
+ " UNIT[\"grad\",0.015707963267949,\n"
+ " AUTHORITY[\"EPSG\",9105]],\n"
+ " AXIS[\"latitude\",NORTH],\n"
+ " AXIS[\"longitude\",EAST],\n"
+ " AUTHORITY[\"EPSG\",\"4807\"]]");
+ auto crs = nn_dynamic_pointer_cast<GeographicCRS>(obj);
+ ASSERT_TRUE(crs != nullptr);
+
+ auto datum = crs->datum();
+ auto primem = datum->primeMeridian();
+ EXPECT_EQ(primem->longitude().unit(), UnitOfMeasure::GRAD);
+ // Check that we have corrected the value that was in degree into grad.
+ EXPECT_EQ(primem->longitude().value(), 2.5969213);
+}
+
+// ---------------------------------------------------------------------------
+
+static std::string contentWKT2_EPSG_4326(
+ "[\"WGS 84\",\n"
+ " DATUM[\"World Geodetic System 1984\",\n"
+ " ELLIPSOID[\"WGS 84\",6378137,298.257223563,\n"
+ " LENGTHUNIT[\"metre\",1,\n"
+ " ID[\"EPSG\",9001]],\n"
+ " ID[\"EPSG\",7030]],\n"
+ " ID[\"EPSG\",6326]],\n"
+ " PRIMEM[\"Greenwich\",0,\n"
+ " ANGLEUNIT[\"degree\",0.0174532925199433,\n"
+ " ID[\"EPSG\",9122]],\n"
+ " ID[\"EPSG\",8901]],\n"
+ " CS[ellipsoidal,2],\n"
+ " AXIS[\"latitude\",north,\n"
+ " ORDER[1],\n"
+ " ANGLEUNIT[\"degree\",0.0174532925199433,\n"
+ " ID[\"EPSG\",9122]]],\n"
+ " AXIS[\"longitude\",east,\n"
+ " ORDER[2],\n"
+ " ANGLEUNIT[\"degree\",0.0174532925199433,\n"
+ " ID[\"EPSG\",9122]]],\n"
+ " ID[\"EPSG\",4326]]");
+
+// ---------------------------------------------------------------------------
+
+TEST(wkt_parse, wkt2_GEODCRS_EPSG_4326) {
+ auto obj = WKTParser().createFromWKT("GEODCRS" + contentWKT2_EPSG_4326);
+ auto crs = nn_dynamic_pointer_cast<GeographicCRS>(obj);
+ ASSERT_TRUE(crs != nullptr);
+ checkEPSG_4326(crs);
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(wkt_parse, wkt2_long_GEODETICCRS_EPSG_4326) {
+ auto obj = WKTParser().createFromWKT("GEODETICCRS" + contentWKT2_EPSG_4326);
+ auto crs = nn_dynamic_pointer_cast<GeographicCRS>(obj);
+ ASSERT_TRUE(crs != nullptr);
+ checkEPSG_4326(crs);
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(wkt_parse, wkt2_2018_GEOGCRS_EPSG_4326) {
+ auto obj = WKTParser().createFromWKT("GEOGCRS" + contentWKT2_EPSG_4326);
+ auto crs = nn_dynamic_pointer_cast<GeographicCRS>(obj);
+ ASSERT_TRUE(crs != nullptr);
+ checkEPSG_4326(crs);
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(wkt_parse, wkt2_2018_long_GEOGRAPHICCRS_EPSG_4326) {
+ auto obj =
+ WKTParser().createFromWKT("GEOGRAPHICCRS" + contentWKT2_EPSG_4326);
+ auto crs = nn_dynamic_pointer_cast<GeographicCRS>(obj);
+ ASSERT_TRUE(crs != nullptr);
+ checkEPSG_4326(crs);
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(wkt_parse, wkt2_simplified_EPSG_4326) {
+ auto obj = WKTParser().createFromWKT(
+ "GEODCRS[\"WGS 84\",\n"
+ " DATUM[\"World Geodetic System 1984\",\n"
+ " ELLIPSOID[\"WGS 84\",6378137,298.257223563]],\n"
+ " CS[ellipsoidal,2],\n"
+ " AXIS[\"latitude (lat)\",north],\n" // test "name
+ // (abbreviation)"
+ " AXIS[\"longitude (lon)\",east],\n"
+ " UNIT[\"degree\",0.0174532925199433],\n"
+ " ID[\"EPSG\",4326]]");
+ auto crs = nn_dynamic_pointer_cast<GeographicCRS>(obj);
+ ASSERT_TRUE(crs != nullptr);
+ checkEPSG_4326(crs, true /* latlong */, false /* no EPSG codes */);
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(wkt_parse, wkt2_GEODETICDATUM) {
+ auto obj = WKTParser().createFromWKT(
+ "GEODCRS[\"WGS 84\",\n"
+ " GEODETICDATUM[\"World Geodetic System 1984\",\n"
+ " ELLIPSOID[\"WGS 84\",6378137,298.257223563]],\n"
+ " CS[ellipsoidal,2],\n"
+ " AXIS[\"(lat)\",north],\n" // test "(abbreviation)"
+ " AXIS[\"(lon)\",east],\n"
+ " UNIT[\"degree\",0.0174532925199433],\n"
+ " ID[\"EPSG\",4326]]");
+ auto crs = nn_dynamic_pointer_cast<GeographicCRS>(obj);
+ ASSERT_TRUE(crs != nullptr);
+ checkEPSG_4326(crs, true /* latlong */, false /* no EPSG codes */);
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(wkt_parse, wkt2_TRF) {
+ auto obj = WKTParser().createFromWKT(
+ "GEODCRS[\"WGS 84\",\n"
+ " TRF[\"World Geodetic System 1984\",\n"
+ " ELLIPSOID[\"WGS 84\",6378137,298.257223563]],\n"
+ " CS[ellipsoidal,2],\n"
+ " AXIS[\"latitude\",north],\n"
+ " AXIS[\"longitude\",east],\n"
+ " UNIT[\"degree\",0.0174532925199433],\n"
+ " ID[\"EPSG\",4326]]");
+ auto crs = nn_dynamic_pointer_cast<GeographicCRS>(obj);
+ ASSERT_TRUE(crs != nullptr);
+ checkEPSG_4326(crs, true /* latlong */, false /* no EPSG codes */);
+}
+
+// ---------------------------------------------------------------------------
+
+static void checkEPSG_4979(GeographicCRSPtr crs) {
+ ASSERT_EQ(crs->identifiers().size(), 1);
+ EXPECT_EQ(crs->identifiers()[0]->code(), "4979");
+ EXPECT_EQ(*(crs->identifiers()[0]->codeSpace()), "EPSG");
+ EXPECT_EQ(crs->nameStr(), "WGS 84");
+
+ auto cs = crs->coordinateSystem();
+ ASSERT_EQ(cs->axisList().size(), 3);
+ EXPECT_EQ(cs->axisList()[0]->nameStr(), "Latitude");
+ EXPECT_EQ(cs->axisList()[0]->abbreviation(), "lat");
+ EXPECT_EQ(cs->axisList()[0]->direction(), AxisDirection::NORTH);
+
+ EXPECT_EQ(cs->axisList()[1]->nameStr(), "Longitude");
+ EXPECT_EQ(cs->axisList()[1]->abbreviation(), "lon");
+ EXPECT_EQ(cs->axisList()[1]->direction(), AxisDirection::EAST);
+
+ EXPECT_EQ(cs->axisList()[2]->nameStr(), "Ellipsoidal height");
+ EXPECT_EQ(cs->axisList()[2]->abbreviation(), "h");
+ EXPECT_EQ(cs->axisList()[2]->direction(), AxisDirection::UP);
+
+ auto datum = crs->datum();
+ EXPECT_EQ(datum->nameStr(), "World Geodetic System 1984");
+
+ auto ellipsoid = datum->ellipsoid();
+ EXPECT_EQ(ellipsoid->semiMajorAxis().value(), 6378137.0);
+ EXPECT_EQ(ellipsoid->semiMajorAxis().unit(), UnitOfMeasure::METRE);
+ EXPECT_EQ(ellipsoid->inverseFlattening()->value(), 298.257223563);
+ EXPECT_EQ(ellipsoid->nameStr(), "WGS 84");
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(wkt_parse, wkt2_EPSG_4979) {
+ auto obj = WKTParser().createFromWKT(
+ "GEODCRS[\"WGS 84\",\n"
+ " DATUM[\"World Geodetic System 1984\",\n"
+ " ELLIPSOID[\"WGS 84\",6378137,298.257223563]],\n"
+ " CS[ellipsoidal,3],\n"
+ " AXIS[\"latitude\",north,\n"
+ " UNIT[\"degree\",0.0174532925199433]],\n"
+ " AXIS[\"longitude\",east,\n"
+ " UNIT[\"degree\",0.0174532925199433]],\n"
+ " AXIS[\"ellipsoidal height\",up,\n"
+ " UNIT[\"metre\",1]],\n"
+ " ID[\"EPSG\",4979]]");
+ auto crs = nn_dynamic_pointer_cast<GeographicCRS>(obj);
+ ASSERT_TRUE(crs != nullptr);
+ checkEPSG_4979(crs);
+}
+
+// ---------------------------------------------------------------------------
+
+static void checkGeocentric(GeodeticCRSPtr crs) {
+ // Explicitly check this is NOT a GeographicCRS
+ EXPECT_TRUE(!dynamic_cast<GeographicCRS *>(crs.get()));
+
+ EXPECT_EQ(crs->nameStr(), "WGS 84 (geocentric)");
+
+ EXPECT_TRUE(crs->isGeocentric());
+ auto cs = crs->coordinateSystem();
+ ASSERT_EQ(cs->axisList().size(), 3);
+
+ EXPECT_EQ(cs->axisList()[0]->nameStr(), "Geocentric X");
+ EXPECT_EQ(cs->axisList()[0]->abbreviation(), "X");
+ EXPECT_EQ(cs->axisList()[0]->direction(), AxisDirection::GEOCENTRIC_X);
+
+ EXPECT_EQ(cs->axisList()[1]->nameStr(), "Geocentric Y");
+ EXPECT_EQ(cs->axisList()[1]->abbreviation(), "Y");
+ EXPECT_EQ(cs->axisList()[1]->direction(), AxisDirection::GEOCENTRIC_Y);
+
+ EXPECT_EQ(cs->axisList()[2]->nameStr(), "Geocentric Z");
+ EXPECT_EQ(cs->axisList()[2]->abbreviation(), "Z");
+ EXPECT_EQ(cs->axisList()[2]->direction(), AxisDirection::GEOCENTRIC_Z);
+
+ auto datum = crs->datum();
+ EXPECT_EQ(datum->nameStr(), "World Geodetic System 1984");
+
+ auto ellipsoid = datum->ellipsoid();
+ EXPECT_EQ(ellipsoid->semiMajorAxis().value(), 6378137.0);
+ EXPECT_EQ(ellipsoid->semiMajorAxis().unit(), UnitOfMeasure::METRE);
+ EXPECT_EQ(ellipsoid->inverseFlattening()->value(), 298.257223563);
+ EXPECT_EQ(ellipsoid->nameStr(), "WGS 84");
+
+ auto primem = datum->primeMeridian();
+ ASSERT_EQ(primem->longitude().unit(), UnitOfMeasure::DEGREE);
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(wkt_parse, wkt2_geocentric) {
+ auto wkt = "GEODCRS[\"WGS 84 (geocentric)\",\n"
+ " DATUM[\"World Geodetic System 1984\",\n"
+ " ELLIPSOID[\"WGS 84\",6378137,298.257223563,\n"
+ " LENGTHUNIT[\"metre\",1,\n"
+ " ID[\"EPSG\",9001]],\n"
+ " ID[\"EPSG\",7030]],\n"
+ " ID[\"EPSG\",6326]],\n"
+ " PRIMEM[\"Greenwich\",0,\n"
+ " ANGLEUNIT[\"degree\",0.0174532925199433,\n"
+ " ID[\"EPSG\",9122]],\n"
+ " ID[\"EPSG\",8901]],\n"
+ " CS[Cartesian,3],\n"
+ " AXIS[\"(X)\",geocentricX,\n"
+ " ORDER[1],\n"
+ " LENGTHUNIT[\"metre\",1,\n"
+ " ID[\"EPSG\",9001]]],\n"
+ " AXIS[\"(Y)\",geocentricY,\n"
+ " ORDER[2],\n"
+ " LENGTHUNIT[\"metre\",1,\n"
+ " ID[\"EPSG\",9001]]],\n"
+ " AXIS[\"(Z)\",geocentricZ,\n"
+ " ORDER[3],\n"
+ " LENGTHUNIT[\"metre\",1,\n"
+ " ID[\"EPSG\",9001]]],\n"
+ " ID[\"EPSG\",4328]]";
+ auto obj = WKTParser().createFromWKT(wkt);
+ auto crs = nn_dynamic_pointer_cast<GeodeticCRS>(obj);
+ ASSERT_TRUE(crs != nullptr);
+ checkGeocentric(crs);
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(wkt_parse, wkt2_simplified_geocentric) {
+ auto wkt = "GEODCRS[\"WGS 84 (geocentric)\",\n"
+ " DATUM[\"World Geodetic System 1984\",\n"
+ " ELLIPSOID[\"WGS 84\",6378137,298.257223563]],\n"
+ " CS[Cartesian,3],\n"
+ " AXIS[\"(X)\",geocentricX],\n"
+ " AXIS[\"(Y)\",geocentricY],\n"
+ " AXIS[\"(Z)\",geocentricZ],\n"
+ " UNIT[\"metre\",1],\n"
+ " ID[\"EPSG\",4328]]";
+ auto obj = WKTParser().createFromWKT(wkt);
+ auto crs = nn_dynamic_pointer_cast<GeodeticCRS>(obj);
+ ASSERT_TRUE(crs != nullptr);
+ checkGeocentric(crs);
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(wkt_parse, wkt1_geocentric) {
+ auto wkt = "GEOCCS[\"WGS 84 (geocentric)\",\n"
+ " DATUM[\"WGS_1984\",\n"
+ " SPHEROID[\"WGS 84\",6378137,298.257223563,\n"
+ " AUTHORITY[\"EPSG\",\"7030\"]],\n"
+ " AUTHORITY[\"EPSG\",\"6326\"]],\n"
+ " PRIMEM[\"Greenwich\",0,\n"
+ " AUTHORITY[\"EPSG\",\"8901\"]],\n"
+ " UNIT[\"metre\",1,\n"
+ " AUTHORITY[\"EPSG\",9001]],\n"
+ " AXIS[\"Geocentric X\",OTHER],\n"
+ " AXIS[\"Geocentric Y\",OTHER],\n"
+ " AXIS[\"Geocentric Z\",NORTH],\n"
+ " AUTHORITY[\"EPSG\",\"4328\"]]";
+ auto obj = WKTParser().createFromWKT(wkt);
+ auto crs = nn_dynamic_pointer_cast<GeodeticCRS>(obj);
+ ASSERT_TRUE(crs != nullptr);
+ checkGeocentric(crs);
+}
+
+// ---------------------------------------------------------------------------
+
+static void checkProjected(ProjectedCRSPtr crs, bool checkEPSGCodes = true) {
+ EXPECT_EQ(crs->nameStr(), "WGS 84 / UTM zone 31N");
+ ASSERT_EQ(crs->identifiers().size(), 1);
+ EXPECT_EQ(crs->identifiers()[0]->code(), "32631");
+ EXPECT_EQ(*(crs->identifiers()[0]->codeSpace()), "EPSG");
+
+ auto geogCRS = nn_dynamic_pointer_cast<GeographicCRS>(crs->baseCRS());
+ ASSERT_TRUE(geogCRS != nullptr);
+ checkEPSG_4326(NN_CHECK_ASSERT(geogCRS), true, checkEPSGCodes);
+
+ auto conversion = crs->derivingConversion();
+ EXPECT_EQ(conversion->nameStr(), "UTM zone 31N");
+ auto method = conversion->method();
+ EXPECT_EQ(method->nameStr(), "Transverse Mercator");
+ auto values = conversion->parameterValues();
+ ASSERT_EQ(values.size(), 5);
+ {
+ const auto &opParamvalue =
+ nn_dynamic_pointer_cast<OperationParameterValue>(values[0]);
+ ASSERT_TRUE(opParamvalue);
+ const auto &paramName = opParamvalue->parameter()->nameStr();
+ const auto &parameterValue = opParamvalue->parameterValue();
+ EXPECT_EQ(paramName, "Latitude of natural origin");
+ EXPECT_EQ(parameterValue->type(), ParameterValue::Type::MEASURE);
+ auto measure = parameterValue->value();
+ EXPECT_EQ(measure.unit(), UnitOfMeasure::DEGREE);
+ EXPECT_EQ(measure.value(), 0);
+ }
+ {
+ const auto &opParamvalue =
+ nn_dynamic_pointer_cast<OperationParameterValue>(values[1]);
+ ASSERT_TRUE(opParamvalue);
+ const auto &paramName = opParamvalue->parameter()->nameStr();
+ const auto &parameterValue = opParamvalue->parameterValue();
+ EXPECT_EQ(paramName, "Longitude of natural origin");
+ EXPECT_EQ(parameterValue->type(), ParameterValue::Type::MEASURE);
+ auto measure = parameterValue->value();
+ EXPECT_EQ(measure.unit(), UnitOfMeasure::DEGREE);
+ EXPECT_EQ(measure.value(), 3);
+ }
+ {
+ const auto &opParamvalue =
+ nn_dynamic_pointer_cast<OperationParameterValue>(values[2]);
+ ASSERT_TRUE(opParamvalue);
+ const auto &paramName = opParamvalue->parameter()->nameStr();
+ const auto &parameterValue = opParamvalue->parameterValue();
+ EXPECT_EQ(paramName, "Scale factor at natural origin");
+ EXPECT_EQ(parameterValue->type(), ParameterValue::Type::MEASURE);
+ auto measure = parameterValue->value();
+ EXPECT_EQ(measure.unit(), UnitOfMeasure::SCALE_UNITY);
+ EXPECT_EQ(measure.value(), 0.9996);
+ }
+ {
+ const auto &opParamvalue =
+ nn_dynamic_pointer_cast<OperationParameterValue>(values[3]);
+ ASSERT_TRUE(opParamvalue);
+ const auto &paramName = opParamvalue->parameter()->nameStr();
+ const auto &parameterValue = opParamvalue->parameterValue();
+ EXPECT_EQ(paramName, "False easting");
+ EXPECT_EQ(parameterValue->type(), ParameterValue::Type::MEASURE);
+ auto measure = parameterValue->value();
+ EXPECT_EQ(measure.unit(), UnitOfMeasure::METRE);
+ EXPECT_EQ(measure.value(), 500000);
+ }
+ {
+ const auto &opParamvalue =
+ nn_dynamic_pointer_cast<OperationParameterValue>(values[4]);
+ ASSERT_TRUE(opParamvalue);
+ const auto &paramName = opParamvalue->parameter()->nameStr();
+ const auto &parameterValue = opParamvalue->parameterValue();
+ EXPECT_EQ(paramName, "False northing");
+ EXPECT_EQ(parameterValue->type(), ParameterValue::Type::MEASURE);
+ auto measure = parameterValue->value();
+ EXPECT_EQ(measure.unit(), UnitOfMeasure::METRE);
+ EXPECT_EQ(measure.value(), 0);
+ }
+
+ auto cs = crs->coordinateSystem();
+ ASSERT_EQ(cs->axisList().size(), 2);
+
+ EXPECT_EQ(cs->axisList()[0]->nameStr(), "Easting");
+ EXPECT_EQ(cs->axisList()[0]->abbreviation(), "E");
+ EXPECT_EQ(cs->axisList()[0]->direction(), AxisDirection::EAST);
+
+ EXPECT_EQ(cs->axisList()[1]->nameStr(), "Northing");
+ EXPECT_EQ(cs->axisList()[1]->abbreviation(), "N");
+ EXPECT_EQ(cs->axisList()[1]->direction(), AxisDirection::NORTH);
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(wkt_parse, wkt1_projected) {
+ auto wkt = "PROJCS[\"WGS 84 / UTM zone 31N\",\n"
+ " GEOGCS[\"WGS 84\",\n"
+ " DATUM[\"WGS_1984\",\n"
+ " SPHEROID[\"WGS 84\",6378137,298.257223563,\n"
+ " AUTHORITY[\"EPSG\",\"7030\"]],\n"
+ " AUTHORITY[\"EPSG\",\"6326\"]],\n"
+ " PRIMEM[\"Greenwich\",0,\n"
+ " AUTHORITY[\"EPSG\",\"8901\"]],\n"
+ " UNIT[\"degree\",0.0174532925199433,\n"
+ " AUTHORITY[\"EPSG\",9122]],\n"
+ " AXIS[\"latitude\",NORTH],\n"
+ " AXIS[\"longitude\",EAST],\n"
+ " AUTHORITY[\"EPSG\",\"4326\"]],\n"
+ " PROJECTION[\"Transverse_Mercator\"],\n"
+ " PARAMETER[\"latitude_of_origin\",0],\n"
+ " PARAMETER[\"central_meridian\",3],\n"
+ " PARAMETER[\"scale_factor\",0.9996],\n"
+ " PARAMETER[\"false_easting\",500000],\n"
+ " PARAMETER[\"false_northing\",0],\n"
+ " UNIT[\"metre\",1,\n"
+ " AUTHORITY[\"EPSG\",9001]],\n"
+ " AXIS[\"(E)\",EAST],\n"
+ " AXIS[\"(N)\",NORTH],\n"
+ " AUTHORITY[\"EPSG\",\"32631\"]]";
+ auto obj = WKTParser().createFromWKT(wkt);
+ auto crs = nn_dynamic_pointer_cast<ProjectedCRS>(obj);
+ ASSERT_TRUE(crs != nullptr);
+ checkProjected(crs);
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(wkt_parse, wkt1_projected_no_axis) {
+ auto wkt = "PROJCS[\"WGS 84 / UTM zone 31N\",\n"
+ " GEOGCS[\"WGS 84\",\n"
+ " DATUM[\"WGS_1984\",\n"
+ " SPHEROID[\"WGS 84\",6378137,298.257223563,\n"
+ " AUTHORITY[\"EPSG\",\"7030\"]],\n"
+ " AUTHORITY[\"EPSG\",\"6326\"]],\n"
+ " PRIMEM[\"Greenwich\",0,\n"
+ " AUTHORITY[\"EPSG\",\"8901\"]],\n"
+ " UNIT[\"degree\",0.0174532925199433,\n"
+ " AUTHORITY[\"EPSG\",9122]],\n"
+ " AXIS[\"latitude\",NORTH],\n"
+ " AXIS[\"longitude\",EAST],\n"
+ " AUTHORITY[\"EPSG\",\"4326\"]],\n"
+ " PROJECTION[\"Transverse_Mercator\"],\n"
+ " PARAMETER[\"latitude_of_origin\",0],\n"
+ " PARAMETER[\"central_meridian\",3],\n"
+ " PARAMETER[\"scale_factor\",0.9996],\n"
+ " PARAMETER[\"false_easting\",500000],\n"
+ " PARAMETER[\"false_northing\",0],\n"
+ " UNIT[\"metre\",1,\n"
+ " AUTHORITY[\"EPSG\",9001]],\n"
+ " AUTHORITY[\"EPSG\",\"32631\"]]";
+ auto obj = WKTParser().createFromWKT(wkt);
+ auto crs = nn_dynamic_pointer_cast<ProjectedCRS>(obj);
+ ASSERT_TRUE(crs != nullptr);
+ checkProjected(crs);
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(wkt_parse, wkt1_krovak_south_west) {
+ auto wkt =
+ "PROJCS[\"S-JTSK / Krovak\","
+ " GEOGCS[\"S-JTSK\","
+ " DATUM[\"System_Jednotne_Trigonometricke_Site_Katastralni\","
+ " SPHEROID[\"Bessel 1841\",6377397.155,299.1528128,"
+ " AUTHORITY[\"EPSG\",\"7004\"]],"
+ " AUTHORITY[\"EPSG\",\"6156\"]],"
+ " PRIMEM[\"Greenwich\",0,"
+ " AUTHORITY[\"EPSG\",\"8901\"]],"
+ " UNIT[\"degree\",0.0174532925199433,"
+ " AUTHORITY[\"EPSG\",\"9122\"]],"
+ " AUTHORITY[\"EPSG\",\"4156\"]],"
+ " PROJECTION[\"Krovak\"],"
+ " PARAMETER[\"latitude_of_center\",49.5],"
+ " PARAMETER[\"longitude_of_center\",24.83333333333333],"
+ " PARAMETER[\"azimuth\",30.28813972222222],"
+ " PARAMETER[\"pseudo_standard_parallel_1\",78.5],"
+ " PARAMETER[\"scale_factor\",0.9999],"
+ " PARAMETER[\"false_easting\",0],"
+ " PARAMETER[\"false_northing\",0],"
+ " UNIT[\"metre\",1,"
+ " AUTHORITY[\"EPSG\",\"9001\"]],"
+ " AXIS[\"X\",SOUTH],"
+ " AXIS[\"Y\",WEST],"
+ " AUTHORITY[\"EPSG\",\"5513\"]]";
+
+ auto obj = WKTParser().createFromWKT(wkt);
+ auto crs = nn_dynamic_pointer_cast<ProjectedCRS>(obj);
+ ASSERT_TRUE(crs != nullptr);
+
+ EXPECT_EQ(crs->derivingConversion()->method()->nameStr(), "Krovak");
+
+ auto expected_wkt2 =
+ "PROJCRS[\"S-JTSK / Krovak\",\n"
+ " BASEGEODCRS[\"S-JTSK\",\n"
+ " DATUM[\"System_Jednotne_Trigonometricke_Site_Katastralni\",\n"
+ " ELLIPSOID[\"Bessel 1841\",6377397.155,299.1528128,\n"
+ " LENGTHUNIT[\"metre\",1]]],\n"
+ " PRIMEM[\"Greenwich\",0,\n"
+ " ANGLEUNIT[\"degree\",0.0174532925199433]]],\n"
+ " CONVERSION[\"unnamed\",\n"
+ " METHOD[\"Krovak\",\n"
+ " ID[\"EPSG\",9819]],\n"
+ " PARAMETER[\"Latitude of projection centre\",49.5,\n"
+ " ANGLEUNIT[\"degree\",0.0174532925199433],\n"
+ " ID[\"EPSG\",8811]],\n"
+ " PARAMETER[\"Longitude of origin\",24.8333333333333,\n"
+ " ANGLEUNIT[\"degree\",0.0174532925199433],\n"
+ " ID[\"EPSG\",8833]],\n"
+ " PARAMETER[\"Co-latitude of cone axis\",30.2881397222222,\n"
+ " ANGLEUNIT[\"degree\",0.0174532925199433],\n"
+ " ID[\"EPSG\",1036]],\n"
+ " PARAMETER[\"Latitude of pseudo standard parallel\",78.5,\n"
+ " ANGLEUNIT[\"degree\",0.0174532925199433],\n"
+ " ID[\"EPSG\",8818]],\n"
+ " PARAMETER[\"Scale factor on pseudo standard "
+ "parallel\",0.9999,\n"
+ " SCALEUNIT[\"unity\",1],\n"
+ " ID[\"EPSG\",8819]],\n"
+ " PARAMETER[\"False easting\",0,\n"
+ " LENGTHUNIT[\"metre\",1],\n"
+ " ID[\"EPSG\",8806]],\n"
+ " PARAMETER[\"False northing\",0,\n"
+ " LENGTHUNIT[\"metre\",1],\n"
+ " ID[\"EPSG\",8807]]],\n"
+ " CS[Cartesian,2],\n"
+ " AXIS[\"x\",south,\n"
+ " ORDER[1],\n"
+ " LENGTHUNIT[\"metre\",1]],\n"
+ " AXIS[\"y\",west,\n"
+ " ORDER[2],\n"
+ " LENGTHUNIT[\"metre\",1]],\n"
+ " ID[\"EPSG\",5513]]";
+
+ EXPECT_EQ(crs->exportToWKT(WKTFormatter::create().get()), expected_wkt2);
+
+ auto projString =
+ crs->exportToPROJString(PROJStringFormatter::create().get());
+ auto expectedPROJString =
+ "+proj=pipeline +step +proj=unitconvert +xy_in=deg +xy_out=rad "
+ "+step +proj=krovak +axis=swu +lat_0=49.5 "
+ "+lon_0=24.8333333333333 +k=0.9999 +x_0=0 +y_0=0 +ellps=bessel";
+ EXPECT_EQ(projString, expectedPROJString);
+
+ obj = PROJStringParser().createFromPROJString(projString);
+ crs = nn_dynamic_pointer_cast<ProjectedCRS>(obj);
+ ASSERT_TRUE(crs != nullptr);
+ auto wkt2 = crs->exportToWKT(WKTFormatter::create().get());
+ EXPECT_TRUE(
+ wkt2.find("PARAMETER[\"Latitude of pseudo standard parallel\",78.5,") !=
+ std::string::npos)
+ << wkt2;
+ EXPECT_TRUE(
+ wkt2.find("PARAMETER[\"Co-latitude of cone axis\",30.2881397222222,") !=
+ std::string::npos)
+ << wkt2;
+ EXPECT_EQ(crs->exportToPROJString(PROJStringFormatter::create().get()),
+ expectedPROJString);
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(wkt_parse, wkt1_krovak_north_oriented) {
+ auto wkt =
+ "PROJCS[\"S-JTSK / Krovak East North\","
+ " GEOGCS[\"S-JTSK\","
+ " DATUM[\"System_Jednotne_Trigonometricke_Site_Katastralni\","
+ " SPHEROID[\"Bessel 1841\",6377397.155,299.1528128,"
+ " AUTHORITY[\"EPSG\",\"7004\"]],"
+ " AUTHORITY[\"EPSG\",\"6156\"]],"
+ " PRIMEM[\"Greenwich\",0,"
+ " AUTHORITY[\"EPSG\",\"8901\"]],"
+ " UNIT[\"degree\",0.0174532925199433,"
+ " AUTHORITY[\"EPSG\",\"9122\"]],"
+ " AUTHORITY[\"EPSG\",\"4156\"]],"
+ " PROJECTION[\"Krovak\"],"
+ " PARAMETER[\"latitude_of_center\",49.5],"
+ " PARAMETER[\"longitude_of_center\",24.83333333333333],"
+ " PARAMETER[\"azimuth\",30.28813972222222],"
+ " PARAMETER[\"pseudo_standard_parallel_1\",78.5],"
+ " PARAMETER[\"scale_factor\",0.9999],"
+ " PARAMETER[\"false_easting\",0],"
+ " PARAMETER[\"false_northing\",0],"
+ " UNIT[\"metre\",1,"
+ " AUTHORITY[\"EPSG\",\"9001\"]],"
+ " AXIS[\"X\",EAST],"
+ " AXIS[\"Y\",NORTH],"
+ " AUTHORITY[\"EPSG\",\"5514\"]]";
+
+ auto obj = WKTParser().createFromWKT(wkt);
+ auto crs = nn_dynamic_pointer_cast<ProjectedCRS>(obj);
+ ASSERT_TRUE(crs != nullptr);
+
+ EXPECT_EQ(crs->derivingConversion()->method()->nameStr(),
+ "Krovak (North Orientated)");
+
+ EXPECT_EQ(
+ crs->exportToWKT(WKTFormatter::create().get()),
+ "PROJCRS[\"S-JTSK / Krovak East North\",\n"
+ " BASEGEODCRS[\"S-JTSK\",\n"
+ " DATUM[\"System_Jednotne_Trigonometricke_Site_Katastralni\",\n"
+ " ELLIPSOID[\"Bessel 1841\",6377397.155,299.1528128,\n"
+ " LENGTHUNIT[\"metre\",1]]],\n"
+ " PRIMEM[\"Greenwich\",0,\n"
+ " ANGLEUNIT[\"degree\",0.0174532925199433]]],\n"
+ " CONVERSION[\"unnamed\",\n"
+ " METHOD[\"Krovak (North Orientated)\",\n"
+ " ID[\"EPSG\",1041]],\n"
+ " PARAMETER[\"Latitude of projection centre\",49.5,\n"
+ " ANGLEUNIT[\"degree\",0.0174532925199433],\n"
+ " ID[\"EPSG\",8811]],\n"
+ " PARAMETER[\"Longitude of origin\",24.8333333333333,\n"
+ " ANGLEUNIT[\"degree\",0.0174532925199433],\n"
+ " ID[\"EPSG\",8833]],\n"
+ " PARAMETER[\"Co-latitude of cone axis\",30.2881397222222,\n"
+ " ANGLEUNIT[\"degree\",0.0174532925199433],\n"
+ " ID[\"EPSG\",1036]],\n"
+ " PARAMETER[\"Latitude of pseudo standard parallel\",78.5,\n"
+ " ANGLEUNIT[\"degree\",0.0174532925199433],\n"
+ " ID[\"EPSG\",8818]],\n"
+ " PARAMETER[\"Scale factor on pseudo standard "
+ "parallel\",0.9999,\n"
+ " SCALEUNIT[\"unity\",1],\n"
+ " ID[\"EPSG\",8819]],\n"
+ " PARAMETER[\"False easting\",0,\n"
+ " LENGTHUNIT[\"metre\",1],\n"
+ " ID[\"EPSG\",8806]],\n"
+ " PARAMETER[\"False northing\",0,\n"
+ " LENGTHUNIT[\"metre\",1],\n"
+ " ID[\"EPSG\",8807]]],\n"
+ " CS[Cartesian,2],\n"
+ " AXIS[\"x\",east,\n"
+ " ORDER[1],\n"
+ " LENGTHUNIT[\"metre\",1]],\n"
+ " AXIS[\"y\",north,\n"
+ " ORDER[2],\n"
+ " LENGTHUNIT[\"metre\",1]],\n"
+ " ID[\"EPSG\",5514]]");
+
+ EXPECT_EQ(crs->exportToPROJString(PROJStringFormatter::create().get()),
+ "+proj=pipeline +step +proj=unitconvert +xy_in=deg +xy_out=rad "
+ "+step +proj=krovak +lat_0=49.5 +lon_0=24.8333333333333 "
+ "+k=0.9999 +x_0=0 +y_0=0 +ellps=bessel");
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(wkt_parse, wkt2_projected) {
+ auto wkt = "PROJCRS[\"WGS 84 / UTM zone 31N\",\n"
+ " BASEGEODCRS[\"WGS 84\",\n"
+ " DATUM[\"World Geodetic System 1984\",\n"
+ " ELLIPSOID[\"WGS 84\",6378137,298.257223563,\n"
+ " LENGTHUNIT[\"metre\",1,\n"
+ " ID[\"EPSG\",9001]],\n"
+ " ID[\"EPSG\",7030]],\n"
+ " ID[\"EPSG\",6326]],\n"
+ " PRIMEM[\"Greenwich\",0,\n"
+ " ANGLEUNIT[\"degree\",0.0174532925199433,\n"
+ " ID[\"EPSG\",9122]],\n"
+ " ID[\"EPSG\",8901]]],\n"
+ " CONVERSION[\"UTM zone 31N\",\n"
+ " METHOD[\"Transverse Mercator\",\n"
+ " ID[\"EPSG\",9807]],\n"
+ " PARAMETER[\"Latitude of natural origin\",0,\n"
+ " ANGLEUNIT[\"degree\",0.0174532925199433,\n"
+ " ID[\"EPSG\",9122]],\n"
+ " ID[\"EPSG\",8801]],\n"
+ " PARAMETER[\"Longitude of natural origin\",3,\n"
+ " ANGLEUNIT[\"degree\",0.0174532925199433,\n"
+ " ID[\"EPSG\",9122]],\n"
+ " ID[\"EPSG\",8802]],\n"
+ " PARAMETER[\"Scale factor at natural origin\",0.9996,\n"
+ " SCALEUNIT[\"unity\",1,\n"
+ " ID[\"EPSG\",9201]],\n"
+ " ID[\"EPSG\",8805]],\n"
+ " PARAMETER[\"False easting\",500000,\n"
+ " LENGTHUNIT[\"metre\",1,\n"
+ " ID[\"EPSG\",9001]],\n"
+ " ID[\"EPSG\",8806]],\n"
+ " PARAMETER[\"False northing\",0,\n"
+ " LENGTHUNIT[\"metre\",1,\n"
+ " ID[\"EPSG\",9001]],\n"
+ " ID[\"EPSG\",8807]],\n"
+ " ID[\"EPSG\",16031]],\n"
+ " CS[Cartesian,2],\n"
+ " AXIS[\"(E)\",east,\n"
+ " ORDER[1],\n"
+ " LENGTHUNIT[\"metre\",1,\n"
+ " ID[\"EPSG\",9001]]],\n"
+ " AXIS[\"(N)\",north,\n"
+ " ORDER[2],\n"
+ " LENGTHUNIT[\"metre\",1,\n"
+ " ID[\"EPSG\",9001]]],\n"
+ " ID[\"EPSG\",32631]]";
+ auto obj = WKTParser().createFromWKT(wkt);
+ auto crs = nn_dynamic_pointer_cast<ProjectedCRS>(obj);
+ ASSERT_TRUE(crs != nullptr);
+ checkProjected(crs, /*checkEPSGCodes = */ false);
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(wkt_parse, wkt2_2018_simplified_projected) {
+ auto wkt = "PROJCRS[\"WGS 84 / UTM zone 31N\",\n"
+ " BASEGEOGCRS[\"WGS 84\",\n"
+ " DATUM[\"World Geodetic System 1984\",\n"
+ " ELLIPSOID[\"WGS 84\",6378137,298.257223563]],\n"
+ " UNIT[\"degree\",0.0174532925199433]],\n"
+ " CONVERSION[\"UTM zone 31N\",\n"
+ " METHOD[\"Transverse Mercator\"],\n"
+ " PARAMETER[\"Latitude of natural origin\",0],\n"
+ " PARAMETER[\"Longitude of natural origin\",3],\n"
+ " PARAMETER[\"Scale factor at natural origin\",0.9996],\n"
+ " PARAMETER[\"False easting\",500000],\n"
+ " PARAMETER[\"False northing\",0]],\n"
+ " CS[Cartesian,2],\n"
+ " AXIS[\"(E)\",east],\n"
+ " AXIS[\"(N)\",north],\n"
+ " UNIT[\"metre\",1],\n"
+ " ID[\"EPSG\",32631]]";
+ auto obj = WKTParser().createFromWKT(wkt);
+ auto crs = nn_dynamic_pointer_cast<ProjectedCRS>(obj);
+ ASSERT_TRUE(crs != nullptr);
+ checkProjected(crs, /*checkEPSGCodes = */ false);
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(crs, projected_angular_unit_from_primem) {
+ auto obj = WKTParser().createFromWKT(
+ "PROJCRS[\"NTF (Paris) / Lambert Nord France\",\n"
+ " BASEGEODCRS[\"NTF (Paris)\",\n"
+ " DATUM[\"Nouvelle Triangulation Francaise (Paris)\",\n"
+ " ELLIPSOID[\"Clarke 1880 "
+ "(IGN)\",6378249.2,293.4660213,LENGTHUNIT[\"metre\",1.0]]],\n"
+ " PRIMEM[\"Paris\",2.5969213,ANGLEUNIT[\"grad\",0.015707963268]]],\n"
+ " CONVERSION[\"Lambert Nord France\",\n"
+ " METHOD[\"Lambert Conic Conformal (1SP)\",ID[\"EPSG\",9801]],\n"
+ " PARAMETER[\"Latitude of natural "
+ "origin\",55,ANGLEUNIT[\"grad\",0.015707963268]],\n"
+ " PARAMETER[\"Longitude of natural "
+ "origin\",0,ANGLEUNIT[\"grad\",0.015707963268]],\n"
+ " PARAMETER[\"Scale factor at natural "
+ "origin\",0.999877341,SCALEUNIT[\"unity\",1.0]],\n"
+ " PARAMETER[\"False easting\",600000,LENGTHUNIT[\"metre\",1.0]],\n"
+ " PARAMETER[\"False northing\",200000,LENGTHUNIT[\"metre\",1.0]]],\n"
+ " CS[cartesian,2],\n"
+ " AXIS[\"easting (X)\",east,ORDER[1]],\n"
+ " AXIS[\"northing (Y)\",north,ORDER[2]],\n"
+ " LENGTHUNIT[\"metre\",1.0],\n"
+ " ID[\"EPSG\",27561]]");
+ auto crs = nn_dynamic_pointer_cast<ProjectedCRS>(obj);
+ ASSERT_TRUE(crs != nullptr);
+ EXPECT_EQ(crs->baseCRS()->coordinateSystem()->axisList()[0]->unit(),
+ UnitOfMeasure::GRAD);
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(wkt_parse, cs_with_MERIDIAN) {
+ auto wkt =
+ "PROJCRS[\"dummy\",\n"
+ " BASEGEOGCRS[\"WGS 84\",\n"
+ " DATUM[\"World Geodetic System 1984\",\n"
+ " ELLIPSOID[\"WGS 84\",6378137,298.257223563]],\n"
+ " UNIT[\"degree\",0.0174532925199433]],\n"
+ " CONVERSION[\"dummy\",\n"
+ " METHOD[\"dummy\"]]\n"
+ " CS[Cartesian,2],\n"
+ " AXIS[\"easting "
+ "(X)\",south,MERIDIAN[90,ANGLEUNIT[\"degree\",0.0174532925199433]]],\n"
+ " AXIS[\"northing (Y)\",north],\n"
+ " UNIT[\"metre\",1],\n"
+ " ID[\"EPSG\",32631]]";
+ auto obj = WKTParser().createFromWKT(wkt);
+ auto crs = nn_dynamic_pointer_cast<ProjectedCRS>(obj);
+ ASSERT_TRUE(crs != nullptr);
+ ASSERT_EQ(crs->coordinateSystem()->axisList().size(), 2);
+ auto axis = crs->coordinateSystem()->axisList()[0];
+ auto meridian = axis->meridian();
+ ASSERT_TRUE(meridian != nullptr);
+ EXPECT_EQ(meridian->longitude().value(), 90.0);
+ EXPECT_EQ(meridian->longitude().unit(), UnitOfMeasure::DEGREE);
+ ASSERT_TRUE(crs->coordinateSystem()->axisList()[1]->meridian() == nullptr);
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(wkt_parse, cs_with_multiple_ID) {
+ auto wkt = "GEODCRS[\"WGS 84\",\n"
+ " DATUM[\"World Geodetic System 1984\",\n"
+ " ELLIPSOID[\"WGS 84\",6378137,298.257223563]],\n"
+ " CS[Cartesian,3],\n"
+ " AXIS[\"(X)\",geocentricX],\n"
+ " AXIS[\"(Y)\",geocentricY],\n"
+ " AXIS[\"(Z)\",geocentricZ],\n"
+ " UNIT[\"metre\",1],\n"
+ " ID[\"authorityA\",\"codeA\"],\n"
+ " ID[\"authorityB\",\"codeB\"]]";
+
+ auto obj = WKTParser().createFromWKT(wkt);
+ auto crs = nn_dynamic_pointer_cast<GeodeticCRS>(obj);
+ ASSERT_TRUE(crs != nullptr);
+ EXPECT_EQ(crs->nameStr(), "WGS 84");
+ ASSERT_EQ(crs->identifiers().size(), 2);
+ EXPECT_EQ(crs->identifiers()[0]->code(), "codeA");
+ EXPECT_EQ(*(crs->identifiers()[0]->codeSpace()), "authorityA");
+ EXPECT_EQ(crs->identifiers()[1]->code(), "codeB");
+ EXPECT_EQ(*(crs->identifiers()[1]->codeSpace()), "authorityB");
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(wkt_parse, vertcrs_WKT2) {
+ auto wkt = "VERTCRS[\"ODN height\",\n"
+ " VDATUM[\"Ordnance Datum Newlyn\"],\n"
+ " CS[vertical,1],\n"
+ " AXIS[\"gravity-related height (H)\",up,\n"
+ " LENGTHUNIT[\"metre\",1]],\n"
+ " ID[\"EPSG\",5701]]";
+
+ auto obj = WKTParser().createFromWKT(wkt);
+ auto crs = nn_dynamic_pointer_cast<VerticalCRS>(obj);
+ ASSERT_TRUE(crs != nullptr);
+ EXPECT_EQ(crs->nameStr(), "ODN height");
+ ASSERT_EQ(crs->identifiers().size(), 1);
+ EXPECT_EQ(crs->identifiers()[0]->code(), "5701");
+ EXPECT_EQ(*(crs->identifiers()[0]->codeSpace()), "EPSG");
+
+ auto datum = crs->datum();
+ EXPECT_EQ(datum->nameStr(), "Ordnance Datum Newlyn");
+ // ASSERT_EQ(datum->identifiers().size(), 1);
+ // EXPECT_EQ(datum->identifiers()[0]->code(), "5101");
+ // EXPECT_EQ(*(datum->identifiers()[0]->codeSpace()), "EPSG");
+
+ auto cs = crs->coordinateSystem();
+ ASSERT_EQ(cs->axisList().size(), 1);
+ EXPECT_EQ(cs->axisList()[0]->nameStr(), "Gravity-related height");
+ EXPECT_EQ(cs->axisList()[0]->abbreviation(), "H");
+ EXPECT_EQ(cs->axisList()[0]->direction(), AxisDirection::UP);
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(wkt_parse, vertcrs_VRF_WKT2) {
+ auto wkt = "VERTCRS[\"ODN height\",\n"
+ " VRF[\"Ordnance Datum Newlyn\"],\n"
+ " CS[vertical,1],\n"
+ " AXIS[\"gravity-related height (H)\",up,\n"
+ " LENGTHUNIT[\"metre\",1]],\n"
+ " ID[\"EPSG\",5701]]";
+
+ auto obj = WKTParser().createFromWKT(wkt);
+ auto crs = nn_dynamic_pointer_cast<VerticalCRS>(obj);
+ ASSERT_TRUE(crs != nullptr);
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(wkt_parse, vertcrs_WKT1_GDAL) {
+ auto wkt = "VERT_CS[\"ODN height\",\n"
+ " VERT_DATUM[\"Ordnance Datum Newlyn\",2005,\n"
+ " AUTHORITY[\"EPSG\",\"5101\"]],\n"
+ " UNIT[\"metre\",1,\n"
+ " AUTHORITY[\"EPSG\",9001]],\n"
+ " AXIS[\"gravity-related height\",UP],\n"
+ " AUTHORITY[\"EPSG\",\"5701\"]]";
+
+ auto obj = WKTParser().createFromWKT(wkt);
+ auto crs = nn_dynamic_pointer_cast<VerticalCRS>(obj);
+ ASSERT_TRUE(crs != nullptr);
+ EXPECT_EQ(crs->nameStr(), "ODN height");
+ ASSERT_EQ(crs->identifiers().size(), 1);
+ EXPECT_EQ(crs->identifiers()[0]->code(), "5701");
+ EXPECT_EQ(*(crs->identifiers()[0]->codeSpace()), "EPSG");
+
+ auto datum = crs->datum();
+ EXPECT_EQ(datum->nameStr(), "Ordnance Datum Newlyn");
+ ASSERT_EQ(datum->identifiers().size(), 1);
+ EXPECT_EQ(datum->identifiers()[0]->code(), "5101");
+ EXPECT_EQ(*(datum->identifiers()[0]->codeSpace()), "EPSG");
+
+ auto cs = crs->coordinateSystem();
+ ASSERT_EQ(cs->axisList().size(), 1);
+ EXPECT_EQ(cs->axisList()[0]->nameStr(), "Gravity-related height");
+ EXPECT_EQ(cs->axisList()[0]->abbreviation(), ""); // "H" in WKT2
+ EXPECT_EQ(cs->axisList()[0]->direction(), AxisDirection::UP);
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(wkt_parse, vertcrs_WKT1_GDAL_minimum) {
+ auto wkt = "VERT_CS[\"ODN height\",\n"
+ " VERT_DATUM[\"Ordnance Datum Newlyn\",2005],\n"
+ " UNIT[\"metre\",1]]";
+
+ auto obj = WKTParser().createFromWKT(wkt);
+ auto crs = nn_dynamic_pointer_cast<VerticalCRS>(obj);
+ EXPECT_EQ(crs->nameStr(), "ODN height");
+
+ auto datum = crs->datum();
+ EXPECT_EQ(datum->nameStr(), "Ordnance Datum Newlyn");
+
+ auto cs = crs->coordinateSystem();
+ ASSERT_EQ(cs->axisList().size(), 1);
+ EXPECT_EQ(cs->axisList()[0]->nameStr(), "Gravity-related height");
+ EXPECT_EQ(cs->axisList()[0]->direction(), AxisDirection::UP);
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(wkt_parse, dynamic_vertical_reference_frame) {
+ auto obj = WKTParser().createFromWKT(
+ "VERTCRS[\"RH2000\","
+ " DYNAMIC[FRAMEEPOCH[2000.0],MODEL[\"NKG2016LU\"]],"
+ " VDATUM[\"Rikets Hojdsystem 2000\",ANCHOR[\"my anchor\"]],"
+ " CS[vertical,1],"
+ " AXIS[\"gravity-related height (H)\",up],"
+ " LENGTHUNIT[\"metre\",1.0]"
+ "]");
+ auto crs = nn_dynamic_pointer_cast<VerticalCRS>(obj);
+ ASSERT_TRUE(crs != nullptr);
+ auto dgrf =
+ std::dynamic_pointer_cast<DynamicVerticalReferenceFrame>(crs->datum());
+ ASSERT_TRUE(dgrf != nullptr);
+ auto anchor = dgrf->anchorDefinition();
+ EXPECT_TRUE(anchor.has_value());
+ EXPECT_EQ(*anchor, "my anchor");
+ EXPECT_TRUE(dgrf->frameReferenceEpoch() ==
+ Measure(2000.0, UnitOfMeasure::YEAR));
+ auto model = dgrf->deformationModelName();
+ EXPECT_TRUE(model.has_value());
+ EXPECT_EQ(*model, "NKG2016LU");
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(wkt_parse, vertcrs_with_ensemble) {
+ auto obj = WKTParser().createFromWKT(
+ "VERTCRS[\"unnamed\",\n"
+ " ENSEMBLE[\"unnamed\",\n"
+ " MEMBER[\"vdatum1\"],\n"
+ " MEMBER[\"vdatum2\"],\n"
+ " ENSEMBLEACCURACY[100]],\n"
+ " CS[vertical,1],\n"
+ " AXIS[\"gravity-related height (H)\",up,\n"
+ " LENGTHUNIT[\"metre\",1]]]");
+ auto crs = nn_dynamic_pointer_cast<VerticalCRS>(obj);
+ ASSERT_TRUE(crs != nullptr);
+ ASSERT_TRUE(crs->datum() == nullptr);
+ ASSERT_TRUE(crs->datumEnsemble() != nullptr);
+ EXPECT_EQ(crs->datumEnsemble()->datums().size(), 2);
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(wkt_parse, vdatum_with_ANCHOR) {
+ auto obj = WKTParser().createFromWKT("VDATUM[\"Ordnance Datum Newlyn\",\n"
+ " ANCHOR[\"my anchor\"],\n"
+ " ID[\"EPSG\",5101]]");
+ auto datum = nn_dynamic_pointer_cast<VerticalReferenceFrame>(obj);
+ ASSERT_TRUE(datum != nullptr);
+ auto anchor = datum->anchorDefinition();
+ EXPECT_TRUE(anchor.has_value());
+ EXPECT_EQ(*anchor, "my anchor");
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(wkt_parse, COMPOUNDCRS) {
+ auto obj = WKTParser().createFromWKT(
+ "COMPOUNDCRS[\"horizontal + vertical\",\n"
+ " PROJCRS[\"WGS 84 / UTM zone 31N\",\n"
+ " BASEGEODCRS[\"WGS 84\",\n"
+ " DATUM[\"World Geodetic System 1984\",\n"
+ " ELLIPSOID[\"WGS 84\",6378137,298.257223563,\n"
+ " LENGTHUNIT[\"metre\",1]]],\n"
+ " PRIMEM[\"Greenwich\",0,\n"
+ " ANGLEUNIT[\"degree\",0.0174532925199433]]],\n"
+ " CONVERSION[\"UTM zone 31N\",\n"
+ " METHOD[\"Transverse Mercator\",\n"
+ " ID[\"EPSG\",9807]],\n"
+ " PARAMETER[\"Latitude of natural origin\",0,\n"
+ " ANGLEUNIT[\"degree\",0.0174532925199433],\n"
+ " ID[\"EPSG\",8801]],\n"
+ " PARAMETER[\"Longitude of natural origin\",3,\n"
+ " ANGLEUNIT[\"degree\",0.0174532925199433],\n"
+ " ID[\"EPSG\",8802]],\n"
+ " PARAMETER[\"Scale factor at natural origin\",0.9996,\n"
+ " SCALEUNIT[\"unity\",1],\n"
+ " ID[\"EPSG\",8805]],\n"
+ " PARAMETER[\"False easting\",500000,\n"
+ " LENGTHUNIT[\"metre\",1],\n"
+ " ID[\"EPSG\",8806]],\n"
+ " PARAMETER[\"False northing\",0,\n"
+ " LENGTHUNIT[\"metre\",1],\n"
+ " ID[\"EPSG\",8807]]],\n"
+ " CS[Cartesian,2],\n"
+ " AXIS[\"(E)\",east,\n"
+ " ORDER[1],\n"
+ " LENGTHUNIT[\"metre\",1]],\n"
+ " AXIS[\"(N)\",north,\n"
+ " ORDER[2],\n"
+ " LENGTHUNIT[\"metre\",1]]],\n"
+ " VERTCRS[\"ODN height\",\n"
+ " VDATUM[\"Ordnance Datum Newlyn\"],\n"
+ " CS[vertical,1],\n"
+ " AXIS[\"gravity-related height (H)\",up,\n"
+ " LENGTHUNIT[\"metre\",1]]],\n"
+ " ID[\"codespace\",\"code\"]]");
+ auto crs = nn_dynamic_pointer_cast<CompoundCRS>(obj);
+ ASSERT_TRUE(crs != nullptr);
+ EXPECT_EQ(crs->nameStr(), "horizontal + vertical");
+ EXPECT_EQ(crs->componentReferenceSystems().size(), 2);
+ ASSERT_EQ(crs->identifiers().size(), 1);
+ EXPECT_EQ(crs->identifiers()[0]->code(), "code");
+ EXPECT_EQ(*(crs->identifiers()[0]->codeSpace()), "codespace");
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(wkt_parse, COMPD_CS) {
+ auto obj = WKTParser().createFromWKT(
+ "COMPD_CS[\"horizontal + vertical\",\n"
+ " PROJCS[\"WGS 84 / UTM zone 31N\",\n"
+ " GEOGCS[\"WGS 84\",\n"
+ " DATUM[\"World Geodetic System 1984\",\n"
+ " SPHEROID[\"WGS 84\",6378137,298.257223563,\n"
+ " AUTHORITY[\"EPSG\",\"7030\"]],\n"
+ " AUTHORITY[\"EPSG\",\"6326\"]],\n"
+ " PRIMEM[\"Greenwich\",0,\n"
+ " AUTHORITY[\"EPSG\",\"8901\"]],\n"
+ " UNIT[\"degree\",0.0174532925199433,\n"
+ " AUTHORITY[\"EPSG\",9122]],\n"
+ " AXIS[\"Latitude\",NORTH],\n"
+ " AXIS[\"Longitude\",EAST],\n"
+ " AUTHORITY[\"EPSG\",\"4326\"]],\n"
+ " PROJECTION[\"Transverse_Mercator\"],\n"
+ " PARAMETER[\"latitude_of_origin\",0],\n"
+ " PARAMETER[\"central_meridian\",3],\n"
+ " PARAMETER[\"scale_factor\",0.9996],\n"
+ " PARAMETER[\"false_easting\",500000],\n"
+ " PARAMETER[\"false_northing\",0],\n"
+ " UNIT[\"metre\",1,\n"
+ " AUTHORITY[\"EPSG\",9001]],\n"
+ " AXIS[\"Easting\",EAST],\n"
+ " AXIS[\"Northing\",NORTH],\n"
+ " AUTHORITY[\"EPSG\",\"32631\"]],\n"
+ " VERT_CS[\"ODN height\",\n"
+ " VERT_DATUM[\"Ordnance Datum Newlyn\",2005,\n"
+ " AUTHORITY[\"EPSG\",\"5101\"]],\n"
+ " UNIT[\"metre\",1,\n"
+ " AUTHORITY[\"EPSG\",9001]],\n"
+ " AXIS[\"Gravity-related height\",UP],\n"
+ " AUTHORITY[\"EPSG\",\"5701\"]],\n"
+ " AUTHORITY[\"codespace\",\"code\"]]");
+ auto crs = nn_dynamic_pointer_cast<CompoundCRS>(obj);
+ ASSERT_TRUE(crs != nullptr);
+ EXPECT_EQ(crs->nameStr(), "horizontal + vertical");
+ EXPECT_EQ(crs->componentReferenceSystems().size(), 2);
+ ASSERT_EQ(crs->identifiers().size(), 1);
+ EXPECT_EQ(crs->identifiers()[0]->code(), "code");
+ EXPECT_EQ(*(crs->identifiers()[0]->codeSpace()), "codespace");
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(wkt_parse, COORDINATEOPERATION) {
+
+ std::string src_wkt;
+ {
+ auto formatter = WKTFormatter::create();
+ formatter->setOutputId(false);
+ src_wkt = GeographicCRS::EPSG_4326->exportToWKT(formatter.get());
+ }
+
+ std::string dst_wkt;
+ {
+ auto formatter = WKTFormatter::create();
+ formatter->setOutputId(false);
+ dst_wkt = GeographicCRS::EPSG_4807->exportToWKT(formatter.get());
+ }
+
+ std::string interpolation_wkt;
+ {
+ auto formatter = WKTFormatter::create();
+ formatter->setOutputId(false);
+ interpolation_wkt =
+ GeographicCRS::EPSG_4979->exportToWKT(formatter.get());
+ }
+
+ auto wkt =
+ "COORDINATEOPERATION[\"transformationName\",\n"
+ " SOURCECRS[" +
+ src_wkt + "],\n"
+ " TARGETCRS[" +
+ dst_wkt +
+ "],\n"
+ " METHOD[\"operationMethodName\",\n"
+ " ID[\"codeSpaceOperationMethod\",\"codeOperationMethod\"]],\n"
+ " PARAMETERFILE[\"paramName\",\"foo.bin\"],\n"
+ " INTERPOLATIONCRS[" +
+ interpolation_wkt +
+ "],\n"
+ " OPERATIONACCURACY[0.1],\n"
+ " ID[\"codeSpaceTransformation\",\"codeTransformation\"],\n"
+ " REMARK[\"my remarks\"]]";
+
+ auto obj = WKTParser().createFromWKT(wkt);
+ auto transf = nn_dynamic_pointer_cast<Transformation>(obj);
+ ASSERT_TRUE(transf != nullptr);
+ EXPECT_EQ(transf->nameStr(), "transformationName");
+ ASSERT_EQ(transf->identifiers().size(), 1);
+ EXPECT_EQ(transf->identifiers()[0]->code(), "codeTransformation");
+ EXPECT_EQ(*(transf->identifiers()[0]->codeSpace()),
+ "codeSpaceTransformation");
+ ASSERT_EQ(transf->coordinateOperationAccuracies().size(), 1);
+ EXPECT_EQ(transf->coordinateOperationAccuracies()[0]->value(), "0.1");
+ EXPECT_EQ(transf->sourceCRS()->nameStr(),
+ GeographicCRS::EPSG_4326->nameStr());
+ EXPECT_EQ(transf->targetCRS()->nameStr(),
+ GeographicCRS::EPSG_4807->nameStr());
+ ASSERT_TRUE(transf->interpolationCRS() != nullptr);
+ EXPECT_EQ(transf->interpolationCRS()->nameStr(),
+ GeographicCRS::EPSG_4979->nameStr());
+ EXPECT_EQ(transf->method()->nameStr(), "operationMethodName");
+ EXPECT_EQ(transf->parameterValues().size(), 1);
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(wkt_parse, CONCATENATEDOPERATION) {
+
+ auto transf_1 = Transformation::create(
+ PropertyMap().set(IdentifiedObject::NAME_KEY, "transf_1"),
+ nn_static_pointer_cast<CRS>(GeographicCRS::EPSG_4326),
+ nn_static_pointer_cast<CRS>(GeographicCRS::EPSG_4807), nullptr,
+ PropertyMap().set(IdentifiedObject::NAME_KEY, "operationMethodName"),
+ std::vector<OperationParameterNNPtr>{OperationParameter::create(
+ PropertyMap().set(IdentifiedObject::NAME_KEY, "paramName"))},
+ std::vector<ParameterValueNNPtr>{
+ ParameterValue::createFilename("foo.bin")},
+ std::vector<PositionalAccuracyNNPtr>());
+
+ auto transf_2 = Transformation::create(
+ PropertyMap().set(IdentifiedObject::NAME_KEY, "transf_2"),
+ nn_static_pointer_cast<CRS>(GeographicCRS::EPSG_4807),
+ nn_static_pointer_cast<CRS>(GeographicCRS::EPSG_4979), nullptr,
+ PropertyMap().set(IdentifiedObject::NAME_KEY, "operationMethodName"),
+ std::vector<OperationParameterNNPtr>{OperationParameter::create(
+ PropertyMap().set(IdentifiedObject::NAME_KEY, "paramName"))},
+ std::vector<ParameterValueNNPtr>{
+ ParameterValue::createFilename("foo.bin")},
+ std::vector<PositionalAccuracyNNPtr>());
+
+ auto concat_in = ConcatenatedOperation::create(
+ PropertyMap()
+ .set(Identifier::CODESPACE_KEY, "codeSpace")
+ .set(Identifier::CODE_KEY, "code")
+ .set(IdentifiedObject::NAME_KEY, "name")
+ .set(IdentifiedObject::REMARKS_KEY, "my remarks"),
+ std::vector<CoordinateOperationNNPtr>{transf_1, transf_2},
+ std::vector<PositionalAccuracyNNPtr>{
+ PositionalAccuracy::create("0.1")});
+
+ auto wkt = concat_in->exportToWKT(
+ WKTFormatter::create(WKTFormatter::Convention::WKT2_2018).get());
+
+ auto obj = WKTParser().createFromWKT(wkt);
+ auto concat = nn_dynamic_pointer_cast<ConcatenatedOperation>(obj);
+ ASSERT_TRUE(concat != nullptr);
+ EXPECT_EQ(concat->nameStr(), "name");
+ ASSERT_EQ(concat->identifiers().size(), 1);
+ EXPECT_EQ(concat->identifiers()[0]->code(), "code");
+ EXPECT_EQ(*(concat->identifiers()[0]->codeSpace()), "codeSpace");
+ ASSERT_EQ(concat->operations().size(), 2);
+ ASSERT_EQ(concat->operations()[0]->nameStr(), transf_1->nameStr());
+ ASSERT_EQ(concat->operations()[1]->nameStr(), transf_2->nameStr());
+ ASSERT_TRUE(concat->sourceCRS() != nullptr);
+ ASSERT_TRUE(concat->targetCRS() != nullptr);
+ ASSERT_EQ(concat->sourceCRS()->nameStr(), transf_1->sourceCRS()->nameStr());
+ ASSERT_EQ(concat->targetCRS()->nameStr(), transf_2->targetCRS()->nameStr());
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(wkt_parse, BOUNDCRS_transformation_from_names) {
+
+ auto projcrs = ProjectedCRS::create(
+ PropertyMap().set(IdentifiedObject::NAME_KEY, "my PROJCRS"),
+ GeographicCRS::create(
+ PropertyMap().set(IdentifiedObject::NAME_KEY, "my GEOGCRS"),
+ GeodeticReferenceFrame::EPSG_6326,
+ EllipsoidalCS::createLatitudeLongitude(UnitOfMeasure::DEGREE)),
+ Conversion::createUTM(PropertyMap(), 31, true),
+ CartesianCS::createEastingNorthing(UnitOfMeasure::METRE));
+
+ auto wkt =
+ "BOUNDCRS[SOURCECRS[" +
+ projcrs->exportToWKT(WKTFormatter::create().get()) + "],\n" +
+ "TARGETCRS[" +
+ GeographicCRS::EPSG_4326->exportToWKT(WKTFormatter::create().get()) +
+ "],\n"
+ " ABRIDGEDTRANSFORMATION[\"Transformation to WGS84\",\n"
+ " METHOD[\"Coordinate Frame\"],\n"
+ " PARAMETER[\"X-axis translation\",1],\n"
+ " PARAMETER[\"Y-axis translation\",2],\n"
+ " PARAMETER[\"Z-axis translation\",3],\n"
+ " PARAMETER[\"X-axis rotation\",-4],\n"
+ " PARAMETER[\"Y-axis rotation\",-5],\n"
+ " PARAMETER[\"Z-axis rotation\",-6],\n"
+ " PARAMETER[\"Scale difference\",1.000007]]]";
+
+ auto obj = WKTParser().createFromWKT(wkt);
+ auto crs = nn_dynamic_pointer_cast<BoundCRS>(obj);
+ ASSERT_TRUE(crs != nullptr);
+
+ EXPECT_EQ(crs->baseCRS()->nameStr(), projcrs->nameStr());
+
+ EXPECT_EQ(crs->hubCRS()->nameStr(), GeographicCRS::EPSG_4326->nameStr());
+
+ ASSERT_TRUE(crs->transformation()->sourceCRS() != nullptr);
+ EXPECT_EQ(crs->transformation()->sourceCRS()->nameStr(),
+ projcrs->baseCRS()->nameStr());
+
+ auto params = crs->transformation()->getTOWGS84Parameters();
+ auto expected = std::vector<double>{1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0};
+ ASSERT_EQ(params.size(), expected.size());
+ for (int i = 0; i < 7; i++) {
+ EXPECT_NEAR(params[i], expected[i], 1e-10);
+ }
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(wkt_parse, BOUNDCRS_transformation_from_codes) {
+
+ auto projcrs = ProjectedCRS::create(
+ PropertyMap().set(IdentifiedObject::NAME_KEY, "my PROJCRS"),
+ GeographicCRS::create(
+ PropertyMap().set(IdentifiedObject::NAME_KEY, "my GEOGCRS"),
+ GeodeticReferenceFrame::EPSG_6326,
+ EllipsoidalCS::createLatitudeLongitude(UnitOfMeasure::DEGREE)),
+ Conversion::createUTM(PropertyMap(), 31, true),
+ CartesianCS::createEastingNorthing(UnitOfMeasure::METRE));
+
+ auto wkt =
+ "BOUNDCRS[SOURCECRS[" +
+ projcrs->exportToWKT(WKTFormatter::create().get()) + "],\n" +
+ "TARGETCRS[" +
+ GeographicCRS::EPSG_4326->exportToWKT(WKTFormatter::create().get()) +
+ "],\n"
+ " ABRIDGEDTRANSFORMATION[\"Transformation to WGS84\",\n"
+ " METHOD[\"bla\",ID[\"EPSG\",1032]],\n"
+ " PARAMETER[\"tx\",1,ID[\"EPSG\",8605]],\n"
+ " PARAMETER[\"ty\",2,ID[\"EPSG\",8606]],\n"
+ " PARAMETER[\"tz\",3,ID[\"EPSG\",8607]],\n"
+ " PARAMETER[\"rotx\",-4,ID[\"EPSG\",8608]],\n"
+ " PARAMETER[\"roty\",-5,ID[\"EPSG\",8609]],\n"
+ " PARAMETER[\"rotz\",-6,ID[\"EPSG\",8610]],\n"
+ " PARAMETER[\"scale\",1.000007,ID[\"EPSG\",8611]]]]";
+
+ auto obj = WKTParser().createFromWKT(wkt);
+ auto crs = nn_dynamic_pointer_cast<BoundCRS>(obj);
+ ASSERT_TRUE(crs != nullptr);
+
+ EXPECT_EQ(crs->baseCRS()->nameStr(), projcrs->nameStr());
+
+ EXPECT_EQ(crs->hubCRS()->nameStr(), GeographicCRS::EPSG_4326->nameStr());
+
+ ASSERT_TRUE(crs->transformation()->sourceCRS() != nullptr);
+ EXPECT_EQ(crs->transformation()->sourceCRS()->nameStr(),
+ projcrs->baseCRS()->nameStr());
+
+ auto params = crs->transformation()->getTOWGS84Parameters();
+ auto expected = std::vector<double>{1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0};
+ ASSERT_EQ(params.size(), expected.size());
+ for (int i = 0; i < 7; i++) {
+ EXPECT_NEAR(params[i], expected[i], 1e-10);
+ }
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(wkt_parse, geogcs_TOWGS84_3terms) {
+ auto wkt = "GEOGCS[\"my GEOGCRS\",\n"
+ " DATUM[\"WGS_1984\",\n"
+ " SPHEROID[\"WGS 84\",6378137,298.257223563],\n"
+ " TOWGS84[1,2,3]],\n"
+ " PRIMEM[\"Greenwich\",0],\n"
+ " UNIT[\"degree\",0.0174532925199433]]";
+
+ auto obj = WKTParser().createFromWKT(wkt);
+ auto crs = nn_dynamic_pointer_cast<BoundCRS>(obj);
+ ASSERT_TRUE(crs != nullptr);
+
+ EXPECT_EQ(crs->baseCRS()->nameStr(), "my GEOGCRS");
+
+ EXPECT_EQ(crs->hubCRS()->nameStr(), GeographicCRS::EPSG_4326->nameStr());
+
+ ASSERT_TRUE(crs->transformation()->sourceCRS() != nullptr);
+ EXPECT_EQ(crs->transformation()->sourceCRS()->nameStr(), "my GEOGCRS");
+
+ auto params = crs->transformation()->getTOWGS84Parameters();
+ auto expected = std::vector<double>{1.0, 2.0, 3.0, 0.0, 0.0, 0.0, 0.0};
+ ASSERT_EQ(params.size(), expected.size());
+ for (int i = 0; i < 7; i++) {
+ EXPECT_NEAR(params[i], expected[i], 1e-10);
+ }
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(wkt_parse, projcs_TOWGS84_7terms) {
+ auto wkt = "PROJCS[\"my PROJCRS\",\n"
+ " GEOGCS[\"my GEOGCRS\",\n"
+ " DATUM[\"WGS_1984\",\n"
+ " SPHEROID[\"WGS 84\",6378137,298.257223563,\n"
+ " AUTHORITY[\"EPSG\",\"7030\"]],\n"
+ " TOWGS84[1,2,3,4,5,6,7],\n"
+ " AUTHORITY[\"EPSG\",\"6326\"]],\n"
+ " PRIMEM[\"Greenwich\",0,\n"
+ " AUTHORITY[\"EPSG\",\"8901\"]],\n"
+ " UNIT[\"degree\",0.0174532925199433,\n"
+ " AUTHORITY[\"EPSG\",9122]],\n"
+ " AXIS[\"Latitude\",NORTH],\n"
+ " AXIS[\"Longitude\",EAST]],\n"
+ " PROJECTION[\"Transverse_Mercator\"],\n"
+ " PARAMETER[\"latitude_of_origin\",0],\n"
+ " PARAMETER[\"central_meridian\",3],\n"
+ " PARAMETER[\"scale_factor\",0.9996],\n"
+ " PARAMETER[\"false_easting\",500000],\n"
+ " PARAMETER[\"false_northing\",0],\n"
+ " UNIT[\"metre\",1,\n"
+ " AUTHORITY[\"EPSG\",9001]],\n"
+ " AXIS[\"Easting\",EAST],\n"
+ " AXIS[\"Northing\",NORTH]]";
+
+ auto obj = WKTParser().createFromWKT(wkt);
+ auto crs = nn_dynamic_pointer_cast<BoundCRS>(obj);
+ ASSERT_TRUE(crs != nullptr);
+
+ EXPECT_EQ(crs->baseCRS()->nameStr(), "my PROJCRS");
+
+ EXPECT_EQ(crs->hubCRS()->nameStr(), GeographicCRS::EPSG_4326->nameStr());
+
+ ASSERT_TRUE(crs->transformation()->sourceCRS() != nullptr);
+ EXPECT_EQ(crs->transformation()->sourceCRS()->nameStr(), "my GEOGCRS");
+
+ auto params = crs->transformation()->getTOWGS84Parameters();
+ auto expected = std::vector<double>{1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0};
+ ASSERT_EQ(params.size(), expected.size());
+ for (int i = 0; i < 7; i++) {
+ EXPECT_NEAR(params[i], expected[i], 1e-10);
+ }
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(wkt_parse, WKT1_VERT_DATUM_EXTENSION) {
+ auto wkt = "VERT_CS[\"EGM2008 geoid height\",\n"
+ " VERT_DATUM[\"EGM2008 geoid\",2005,\n"
+ " EXTENSION[\"PROJ4_GRIDS\",\"egm08_25.gtx\"],\n"
+ " AUTHORITY[\"EPSG\",\"1027\"]],\n"
+ " UNIT[\"metre\",1,\n"
+ " AUTHORITY[\"EPSG\",\"9001\"]],\n"
+ " AXIS[\"Up\",UP],\n"
+ " AUTHORITY[\"EPSG\",\"3855\"]]";
+
+ auto obj = WKTParser().createFromWKT(wkt);
+ auto crs = nn_dynamic_pointer_cast<BoundCRS>(obj);
+ ASSERT_TRUE(crs != nullptr);
+
+ EXPECT_EQ(crs->baseCRS()->nameStr(), "EGM2008 geoid height");
+
+ EXPECT_EQ(crs->hubCRS()->nameStr(), GeographicCRS::EPSG_4979->nameStr());
+
+ ASSERT_TRUE(crs->transformation()->sourceCRS() != nullptr);
+ EXPECT_EQ(crs->transformation()->sourceCRS()->nameStr(),
+ crs->baseCRS()->nameStr());
+
+ ASSERT_TRUE(crs->transformation()->targetCRS() != nullptr);
+ EXPECT_EQ(crs->transformation()->targetCRS()->nameStr(),
+ crs->hubCRS()->nameStr());
+
+ EXPECT_EQ(crs->transformation()->nameStr(),
+ "EGM2008 geoid height to WGS84 ellipsoidal height");
+ EXPECT_EQ(crs->transformation()->method()->nameStr(),
+ "GravityRelatedHeight to Geographic3D");
+ ASSERT_EQ(crs->transformation()->parameterValues().size(), 1);
+ {
+ const auto &opParamvalue =
+ nn_dynamic_pointer_cast<OperationParameterValue>(
+ crs->transformation()->parameterValues()[0]);
+ ASSERT_TRUE(opParamvalue);
+ const auto &paramName = opParamvalue->parameter()->nameStr();
+ const auto &parameterValue = opParamvalue->parameterValue();
+ EXPECT_TRUE(opParamvalue->parameter()->getEPSGCode() == 8666);
+ EXPECT_EQ(paramName, "Geoid (height correction) model file");
+ EXPECT_EQ(parameterValue->type(), ParameterValue::Type::FILENAME);
+ EXPECT_EQ(parameterValue->valueFile(), "egm08_25.gtx");
+ }
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(wkt_parse, WKT1_DATUM_EXTENSION) {
+ auto wkt =
+ "PROJCS[\"unnamed\",\n"
+ " GEOGCS[\"International 1909 (Hayford)\",\n"
+ " DATUM[\"unknown\",\n"
+ " SPHEROID[\"intl\",6378388,297],\n"
+ " EXTENSION[\"PROJ4_GRIDS\",\"nzgd2kgrid0005.gsb\"]],\n"
+ " PRIMEM[\"Greenwich\",0],\n"
+ " UNIT[\"degree\",0.0174532925199433]],\n"
+ " PROJECTION[\"New_Zealand_Map_Grid\"],\n"
+ " PARAMETER[\"latitude_of_origin\",-41],\n"
+ " PARAMETER[\"central_meridian\",173],\n"
+ " PARAMETER[\"false_easting\",2510000],\n"
+ " PARAMETER[\"false_northing\",6023150],\n"
+ " UNIT[\"Meter\",1]]";
+
+ auto obj = WKTParser().createFromWKT(wkt);
+ auto crs = nn_dynamic_pointer_cast<BoundCRS>(obj);
+ ASSERT_TRUE(crs != nullptr);
+
+ EXPECT_EQ(crs->baseCRS()->nameStr(), "unnamed");
+
+ EXPECT_EQ(crs->hubCRS()->nameStr(), GeographicCRS::EPSG_4326->nameStr());
+
+ ASSERT_TRUE(crs->transformation()->sourceCRS() != nullptr);
+ EXPECT_EQ(crs->transformation()->sourceCRS()->nameStr(),
+ crs->baseCRS()->nameStr());
+
+ ASSERT_TRUE(crs->transformation()->targetCRS() != nullptr);
+ EXPECT_EQ(crs->transformation()->targetCRS()->nameStr(),
+ crs->hubCRS()->nameStr());
+
+ EXPECT_EQ(crs->transformation()->nameStr(),
+ "International 1909 (Hayford) to WGS84");
+ EXPECT_EQ(crs->transformation()->method()->nameStr(), "NTv2");
+ ASSERT_EQ(crs->transformation()->parameterValues().size(), 1);
+ {
+ const auto &opParamvalue =
+ nn_dynamic_pointer_cast<OperationParameterValue>(
+ crs->transformation()->parameterValues()[0]);
+ ASSERT_TRUE(opParamvalue);
+ const auto &paramName = opParamvalue->parameter()->nameStr();
+ const auto &parameterValue = opParamvalue->parameterValue();
+ EXPECT_TRUE(opParamvalue->parameter()->getEPSGCode() == 8656);
+ EXPECT_EQ(paramName, "Latitude and longitude difference file");
+ EXPECT_EQ(parameterValue->type(), ParameterValue::Type::FILENAME);
+ EXPECT_EQ(parameterValue->valueFile(), "nzgd2kgrid0005.gsb");
+ }
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(wkt_parse, DerivedGeographicCRS_WKT2) {
+ auto wkt = "GEODCRS[\"WMO Atlantic Pole\",\n"
+ " BASEGEODCRS[\"WGS 84\",\n"
+ " DATUM[\"World Geodetic System 1984\",\n"
+ " ELLIPSOID[\"WGS 84\",6378137,298.257223563,\n"
+ " LENGTHUNIT[\"metre\",1]]],\n"
+ " PRIMEM[\"Greenwich\",0,\n"
+ " ANGLEUNIT[\"degree\",0.0174532925199433]]],\n"
+ " DERIVINGCONVERSION[\"Atlantic pole\",\n"
+ " METHOD[\"Pole rotation\"],\n"
+ " PARAMETER[\"Latitude of rotated pole\",52,\n"
+ " ANGLEUNIT[\"degree\",0.0174532925199433,\n"
+ " ID[\"EPSG\",9122]]],\n"
+ " PARAMETER[\"Longitude of rotated pole\",-30,\n"
+ " ANGLEUNIT[\"degree\",0.0174532925199433,\n"
+ " ID[\"EPSG\",9122]]],\n"
+ " PARAMETER[\"Axis rotation\",-25,\n"
+ " ANGLEUNIT[\"degree\",0.0174532925199433,\n"
+ " ID[\"EPSG\",9122]]]],\n"
+ " CS[ellipsoidal,2],\n"
+ " AXIS[\"latitude\",north,\n"
+ " ORDER[1],\n"
+ " ANGLEUNIT[\"degree\",0.0174532925199433,\n"
+ " ID[\"EPSG\",9122]]],\n"
+ " AXIS[\"longitude\",east,\n"
+ " ORDER[2],\n"
+ " ANGLEUNIT[\"degree\",0.0174532925199433,\n"
+ " ID[\"EPSG\",9122]]]]";
+
+ auto obj = WKTParser().createFromWKT(wkt);
+ auto crs = nn_dynamic_pointer_cast<DerivedGeographicCRS>(obj);
+ ASSERT_TRUE(crs != nullptr);
+
+ EXPECT_EQ(crs->nameStr(), "WMO Atlantic Pole");
+
+ EXPECT_EQ(crs->baseCRS()->nameStr(), "WGS 84");
+ EXPECT_TRUE(nn_dynamic_pointer_cast<GeographicCRS>(crs->baseCRS()) !=
+ nullptr);
+
+ EXPECT_EQ(crs->derivingConversion()->nameStr(), "Atlantic pole");
+
+ EXPECT_TRUE(nn_dynamic_pointer_cast<EllipsoidalCS>(
+ crs->coordinateSystem()) != nullptr);
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(wkt_parse, DerivedGeographicCRS_WKT2_2018) {
+ auto wkt = "GEOGCRS[\"WMO Atlantic Pole\",\n"
+ " BASEGEOGCRS[\"WGS 84\",\n"
+ " DATUM[\"World Geodetic System 1984\",\n"
+ " ELLIPSOID[\"WGS 84\",6378137,298.257223563,\n"
+ " LENGTHUNIT[\"metre\",1]]],\n"
+ " PRIMEM[\"Greenwich\",0,\n"
+ " ANGLEUNIT[\"degree\",0.0174532925199433]]],\n"
+ " DERIVINGCONVERSION[\"Atlantic pole\",\n"
+ " METHOD[\"Pole rotation\"],\n"
+ " PARAMETER[\"Latitude of rotated pole\",52,\n"
+ " ANGLEUNIT[\"degree\",0.0174532925199433,\n"
+ " ID[\"EPSG\",9122]]],\n"
+ " PARAMETER[\"Longitude of rotated pole\",-30,\n"
+ " ANGLEUNIT[\"degree\",0.0174532925199433,\n"
+ " ID[\"EPSG\",9122]]],\n"
+ " PARAMETER[\"Axis rotation\",-25,\n"
+ " ANGLEUNIT[\"degree\",0.0174532925199433,\n"
+ " ID[\"EPSG\",9122]]]],\n"
+ " CS[ellipsoidal,2],\n"
+ " AXIS[\"latitude\",north,\n"
+ " ORDER[1],\n"
+ " ANGLEUNIT[\"degree\",0.0174532925199433,\n"
+ " ID[\"EPSG\",9122]]],\n"
+ " AXIS[\"longitude\",east,\n"
+ " ORDER[2],\n"
+ " ANGLEUNIT[\"degree\",0.0174532925199433,\n"
+ " ID[\"EPSG\",9122]]]]";
+
+ auto obj = WKTParser().createFromWKT(wkt);
+ auto crs = nn_dynamic_pointer_cast<DerivedGeographicCRS>(obj);
+ ASSERT_TRUE(crs != nullptr);
+
+ EXPECT_EQ(crs->nameStr(), "WMO Atlantic Pole");
+
+ EXPECT_EQ(crs->baseCRS()->nameStr(), "WGS 84");
+ EXPECT_TRUE(nn_dynamic_pointer_cast<GeographicCRS>(crs->baseCRS()) !=
+ nullptr);
+
+ EXPECT_EQ(crs->derivingConversion()->nameStr(), "Atlantic pole");
+
+ EXPECT_TRUE(nn_dynamic_pointer_cast<EllipsoidalCS>(
+ crs->coordinateSystem()) != nullptr);
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(wkt_parse, DerivedGeodeticCRS) {
+ auto wkt = "GEODCRS[\"Derived geodetic CRS\",\n"
+ " BASEGEODCRS[\"WGS 84\",\n"
+ " DATUM[\"World Geodetic System 1984\",\n"
+ " ELLIPSOID[\"WGS 84\",6378137,298.257223563,\n"
+ " LENGTHUNIT[\"metre\",1]]],\n"
+ " PRIMEM[\"Greenwich\",0,\n"
+ " ANGLEUNIT[\"degree\",0.0174532925199433]]],\n"
+ " DERIVINGCONVERSION[\"Some conversion\",\n"
+ " METHOD[\"Some method\"]],\n"
+ " CS[Cartesian,3],\n"
+ " AXIS[\"(X)\",geocentricX,\n"
+ " ORDER[1],\n"
+ " LENGTHUNIT[\"metre\",1,\n"
+ " ID[\"EPSG\",9001]]],\n"
+ " AXIS[\"(Y)\",geocentricY,\n"
+ " ORDER[2],\n"
+ " LENGTHUNIT[\"metre\",1,\n"
+ " ID[\"EPSG\",9001]]],\n"
+ " AXIS[\"(Z)\",geocentricZ,\n"
+ " ORDER[3],\n"
+ " LENGTHUNIT[\"metre\",1,\n"
+ " ID[\"EPSG\",9001]]]]";
+ ;
+
+ auto obj = WKTParser().createFromWKT(wkt);
+ auto crs = nn_dynamic_pointer_cast<DerivedGeodeticCRS>(obj);
+ ASSERT_TRUE(crs != nullptr);
+
+ EXPECT_EQ(crs->nameStr(), "Derived geodetic CRS");
+
+ EXPECT_EQ(crs->baseCRS()->nameStr(), "WGS 84");
+ EXPECT_TRUE(nn_dynamic_pointer_cast<GeographicCRS>(crs->baseCRS()) !=
+ nullptr);
+
+ EXPECT_EQ(crs->derivingConversion()->nameStr(), "Some conversion");
+
+ EXPECT_TRUE(nn_dynamic_pointer_cast<CartesianCS>(crs->coordinateSystem()) !=
+ nullptr);
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(wkt_parse, DerivedProjectedCRS) {
+ auto wkt =
+ "DERIVEDPROJCRS[\"derived projectedCRS\",\n"
+ " BASEPROJCRS[\"WGS 84 / UTM zone 31N\",\n"
+ " BASEGEOGCRS[\"WGS 84\",\n"
+ " DATUM[\"World Geodetic System 1984\",\n"
+ " ELLIPSOID[\"WGS 84\",6378137,298.257223563,\n"
+ " LENGTHUNIT[\"metre\",1]]],\n"
+ " PRIMEM[\"Greenwich\",0,\n"
+ " ANGLEUNIT[\"degree\",0.0174532925199433]]],\n"
+ " CONVERSION[\"UTM zone 31N\",\n"
+ " METHOD[\"Transverse Mercator\",\n"
+ " ID[\"EPSG\",9807]],\n"
+ " PARAMETER[\"Latitude of natural origin\",0,\n"
+ " ANGLEUNIT[\"degree\",0.0174532925199433],\n"
+ " ID[\"EPSG\",8801]],\n"
+ " PARAMETER[\"Longitude of natural origin\",3,\n"
+ " ANGLEUNIT[\"degree\",0.0174532925199433],\n"
+ " ID[\"EPSG\",8802]],\n"
+ " PARAMETER[\"Scale factor at natural origin\",0.9996,\n"
+ " SCALEUNIT[\"unity\",1],\n"
+ " ID[\"EPSG\",8805]],\n"
+ " PARAMETER[\"False easting\",500000,\n"
+ " LENGTHUNIT[\"metre\",1],\n"
+ " ID[\"EPSG\",8806]],\n"
+ " PARAMETER[\"False northing\",0,\n"
+ " LENGTHUNIT[\"metre\",1],\n"
+ " ID[\"EPSG\",8807]]]],\n"
+ " DERIVINGCONVERSION[\"unnamed\",\n"
+ " METHOD[\"PROJ unimplemented\"]],\n"
+ " CS[Cartesian,2],\n"
+ " AXIS[\"(E)\",east,\n"
+ " ORDER[1],\n"
+ " LENGTHUNIT[\"metre\",1,\n"
+ " ID[\"EPSG\",9001]]],\n"
+ " AXIS[\"(N)\",north,\n"
+ " ORDER[2],\n"
+ " LENGTHUNIT[\"metre\",1,\n"
+ " ID[\"EPSG\",9001]]]]";
+
+ auto obj = WKTParser().createFromWKT(wkt);
+ auto crs = nn_dynamic_pointer_cast<DerivedProjectedCRS>(obj);
+ ASSERT_TRUE(crs != nullptr);
+
+ EXPECT_EQ(crs->nameStr(), "derived projectedCRS");
+
+ EXPECT_EQ(crs->baseCRS()->nameStr(), "WGS 84 / UTM zone 31N");
+ EXPECT_TRUE(nn_dynamic_pointer_cast<ProjectedCRS>(crs->baseCRS()) !=
+ nullptr);
+
+ EXPECT_EQ(crs->derivingConversion()->nameStr(), "unnamed");
+
+ EXPECT_TRUE(nn_dynamic_pointer_cast<CartesianCS>(crs->coordinateSystem()) !=
+ nullptr);
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(wkt_parse, DerivedProjectedCRS_ordinal) {
+ auto wkt = "DERIVEDPROJCRS[\"derived projectedCRS\",\n"
+ " BASEPROJCRS[\"BASEPROJCRS\",\n"
+ " BASEGEOGCRS[\"WGS 84\",\n"
+ " DATUM[\"World Geodetic System 1984\",\n"
+ " ELLIPSOID[\"WGS 84\",6378137,298.257223563,\n"
+ " LENGTHUNIT[\"metre\",1]]],\n"
+ " PRIMEM[\"Greenwich\",0,\n"
+ " ANGLEUNIT[\"degree\",0.0174532925199433],\n"
+ " ID[\"EPSG\",8901]]],\n"
+ " CONVERSION[\"unnamed\",\n"
+ " METHOD[\"PROJ unimplemented\"]]],\n"
+ " DERIVINGCONVERSION[\"unnamed\",\n"
+ " METHOD[\"PROJ unimplemented\"]],\n"
+ " CS[ordinal,2],\n"
+ " AXIS[\"inline (I)\",northNorthWest,\n"
+ " ORDER[1]],\n"
+ " AXIS[\"crossline (J)\",westSouthWest,\n"
+ " ORDER[2]]]";
+
+ auto obj = WKTParser().createFromWKT(wkt);
+ auto crs = nn_dynamic_pointer_cast<DerivedProjectedCRS>(obj);
+ ASSERT_TRUE(crs != nullptr);
+ EXPECT_TRUE(nn_dynamic_pointer_cast<OrdinalCS>(crs->coordinateSystem()) !=
+ nullptr);
+
+ EXPECT_EQ(
+ crs->exportToWKT(
+ WKTFormatter::create(WKTFormatter::Convention::WKT2_2018).get()),
+ wkt);
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(wkt_parse, TemporalDatum) {
+ auto wkt = "TDATUM[\"Gregorian calendar\",\n"
+ " CALENDAR[\"my calendar\"],\n"
+ " TIMEORIGIN[0000-01-01]]";
+
+ auto obj = WKTParser().createFromWKT(wkt);
+ auto tdatum = nn_dynamic_pointer_cast<TemporalDatum>(obj);
+ ASSERT_TRUE(tdatum != nullptr);
+
+ EXPECT_EQ(tdatum->nameStr(), "Gregorian calendar");
+ EXPECT_EQ(tdatum->temporalOrigin().toString(), "0000-01-01");
+ EXPECT_EQ(tdatum->calendar(), "my calendar");
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(wkt_parse, TemporalDatum_no_calendar) {
+ auto wkt = "TDATUM[\"Gregorian calendar\",\n"
+ " TIMEORIGIN[0000-01-01]]";
+
+ auto obj = WKTParser().createFromWKT(wkt);
+ auto tdatum = nn_dynamic_pointer_cast<TemporalDatum>(obj);
+ ASSERT_TRUE(tdatum != nullptr);
+
+ EXPECT_EQ(tdatum->nameStr(), "Gregorian calendar");
+ EXPECT_EQ(tdatum->temporalOrigin().toString(), "0000-01-01");
+ EXPECT_EQ(tdatum->calendar(), "proleptic Gregorian");
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(wkt_parse, dateTimeTemporalCRS_WKT2) {
+ auto wkt = "TIMECRS[\"Temporal CRS\",\n"
+ " TDATUM[\"Gregorian calendar\",\n"
+ " TIMEORIGIN[0000-01-01]],\n"
+ " CS[temporal,1],\n"
+ " AXIS[\"time (T)\",future]]";
+
+ auto obj = WKTParser().createFromWKT(wkt);
+ auto crs = nn_dynamic_pointer_cast<TemporalCRS>(obj);
+ ASSERT_TRUE(crs != nullptr);
+
+ EXPECT_EQ(crs->nameStr(), "Temporal CRS");
+ auto tdatum = crs->datum();
+ EXPECT_EQ(tdatum->nameStr(), "Gregorian calendar");
+ EXPECT_EQ(tdatum->temporalOrigin().toString(), "0000-01-01");
+ EXPECT_EQ(tdatum->calendar(), "proleptic Gregorian");
+ EXPECT_TRUE(nn_dynamic_pointer_cast<DateTimeTemporalCS>(
+ crs->coordinateSystem()) != nullptr);
+ ASSERT_EQ(crs->coordinateSystem()->axisList().size(), 1);
+ EXPECT_EQ(crs->coordinateSystem()->axisList()[0]->unit().type(),
+ UnitOfMeasure::Type::NONE);
+ EXPECT_EQ(crs->coordinateSystem()->axisList()[0]->unit().name(), "");
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(wkt_parse, dateTimeTemporalCRS_WKT2_2018) {
+ auto wkt = "TIMECRS[\"Temporal CRS\",\n"
+ " TDATUM[\"Gregorian calendar\",\n"
+ " CALENDAR[\"proleptic Gregorian\"],\n"
+ " TIMEORIGIN[0000-01-01]],\n"
+ " CS[TemporalDateTime,1],\n"
+ " AXIS[\"time (T)\",future]]";
+
+ auto obj = WKTParser().createFromWKT(wkt);
+ auto crs = nn_dynamic_pointer_cast<TemporalCRS>(obj);
+ ASSERT_TRUE(crs != nullptr);
+
+ EXPECT_EQ(crs->nameStr(), "Temporal CRS");
+ auto tdatum = crs->datum();
+ EXPECT_EQ(tdatum->nameStr(), "Gregorian calendar");
+ EXPECT_EQ(tdatum->temporalOrigin().toString(), "0000-01-01");
+ EXPECT_EQ(tdatum->calendar(), "proleptic Gregorian");
+ EXPECT_TRUE(nn_dynamic_pointer_cast<DateTimeTemporalCS>(
+ crs->coordinateSystem()) != nullptr);
+ ASSERT_EQ(crs->coordinateSystem()->axisList().size(), 1);
+ EXPECT_EQ(crs->coordinateSystem()->axisList()[0]->unit().type(),
+ UnitOfMeasure::Type::NONE);
+ EXPECT_EQ(crs->coordinateSystem()->axisList()[0]->unit().name(), "");
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(wkt_parse, temporalCountCRSWithConvFactor_WKT2_2018) {
+ auto wkt = "TIMECRS[\"GPS milliseconds\",\n"
+ " TDATUM[\"GPS time origin\",\n"
+ " TIMEORIGIN[1980-01-01T00:00:00.0Z]],\n"
+ " CS[TemporalCount,1],\n"
+ " AXIS[\"(T)\",future,\n"
+ " TIMEUNIT[\"milliseconds (ms)\",0.001]]]";
+
+ auto obj = WKTParser().createFromWKT(wkt);
+ auto crs = nn_dynamic_pointer_cast<TemporalCRS>(obj);
+ ASSERT_TRUE(crs != nullptr);
+
+ EXPECT_EQ(crs->nameStr(), "GPS milliseconds");
+ auto tdatum = crs->datum();
+ EXPECT_EQ(tdatum->nameStr(), "GPS time origin");
+ EXPECT_EQ(tdatum->temporalOrigin().toString(), "1980-01-01T00:00:00.0Z");
+ EXPECT_EQ(tdatum->calendar(), "proleptic Gregorian");
+ EXPECT_TRUE(nn_dynamic_pointer_cast<TemporalCountCS>(
+ crs->coordinateSystem()) != nullptr);
+ ASSERT_EQ(crs->coordinateSystem()->axisList().size(), 1);
+ EXPECT_EQ(crs->coordinateSystem()->axisList()[0]->unit().name(),
+ "milliseconds (ms)");
+ EXPECT_EQ(crs->coordinateSystem()->axisList()[0]->unit().conversionToSI(),
+ 0.001);
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(wkt_parse, temporalCountCRSWithoutConvFactor_WKT2_2018) {
+ auto wkt = "TIMECRS[\"Calendar hours from 1979-12-29\",\n"
+ " TDATUM[\"29 December 1979\",\n"
+ " CALENDAR[\"proleptic Gregorian\"],\n"
+ " TIMEORIGIN[1979-12-29T00]],\n"
+ " CS[TemporalCount,1],\n"
+ " AXIS[\"time\",future,\n"
+ " TIMEUNIT[\"hour\"]]]";
+
+ auto obj = WKTParser().createFromWKT(wkt);
+ auto crs = nn_dynamic_pointer_cast<TemporalCRS>(obj);
+ ASSERT_TRUE(crs != nullptr);
+
+ EXPECT_EQ(crs->nameStr(), "Calendar hours from 1979-12-29");
+ auto tdatum = crs->datum();
+ EXPECT_EQ(tdatum->nameStr(), "29 December 1979");
+ EXPECT_EQ(tdatum->temporalOrigin().toString(), "1979-12-29T00");
+ EXPECT_TRUE(nn_dynamic_pointer_cast<TemporalCountCS>(
+ crs->coordinateSystem()) != nullptr);
+ ASSERT_EQ(crs->coordinateSystem()->axisList().size(), 1);
+ EXPECT_EQ(crs->coordinateSystem()->axisList()[0]->unit().name(), "hour");
+ EXPECT_EQ(crs->coordinateSystem()->axisList()[0]->unit().conversionToSI(),
+ 0.0);
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(wkt_parse, temporalMeasureCRSWithoutConvFactor_WKT2_2018) {
+ auto wkt = "TIMECRS[\"Decimal Years CE\",\n"
+ " TIMEDATUM[\"Common Era\",\n"
+ " TIMEORIGIN[0000]],\n"
+ " CS[TemporalMeasure,1],\n"
+ " AXIS[\"decimal years (a)\",future,\n"
+ " TIMEUNIT[\"year\"]]]";
+
+ auto obj = WKTParser().createFromWKT(wkt);
+ auto crs = nn_dynamic_pointer_cast<TemporalCRS>(obj);
+ ASSERT_TRUE(crs != nullptr);
+
+ EXPECT_EQ(crs->nameStr(), "Decimal Years CE");
+ auto tdatum = crs->datum();
+ EXPECT_EQ(tdatum->nameStr(), "Common Era");
+ EXPECT_EQ(tdatum->temporalOrigin().toString(), "0000");
+ EXPECT_TRUE(nn_dynamic_pointer_cast<TemporalMeasureCS>(
+ crs->coordinateSystem()) != nullptr);
+ auto cs = crs->coordinateSystem();
+ ASSERT_EQ(cs->axisList().size(), 1);
+ auto axis = cs->axisList()[0];
+ EXPECT_EQ(axis->nameStr(), "Decimal years");
+ EXPECT_EQ(axis->unit().name(), "year");
+ EXPECT_EQ(axis->unit().conversionToSI(), 0.0);
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(wkt_parse, EDATUM) {
+ auto wkt = "EDATUM[\"Engineering datum\",\n"
+ " ANCHOR[\"my anchor\"]]";
+
+ auto obj = WKTParser().createFromWKT(wkt);
+ auto edatum = nn_dynamic_pointer_cast<EngineeringDatum>(obj);
+ ASSERT_TRUE(edatum != nullptr);
+
+ EXPECT_EQ(edatum->nameStr(), "Engineering datum");
+ auto anchor = edatum->anchorDefinition();
+ EXPECT_TRUE(anchor.has_value());
+ EXPECT_EQ(*anchor, "my anchor");
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(wkt_parse, ENGINEERINGDATUM) {
+ auto wkt = "ENGINEERINGDATUM[\"Engineering datum\"]";
+
+ auto obj = WKTParser().createFromWKT(wkt);
+ auto edatum = nn_dynamic_pointer_cast<EngineeringDatum>(obj);
+ ASSERT_TRUE(edatum != nullptr);
+
+ EXPECT_EQ(edatum->nameStr(), "Engineering datum");
+ auto anchor = edatum->anchorDefinition();
+ EXPECT_TRUE(!anchor.has_value());
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(wkt_parse, ENGCRS) {
+ auto wkt = "ENGCRS[\"Engineering CRS\",\n"
+ " EDATUM[\"Engineering datum\"],\n"
+ " CS[Cartesian,2],\n"
+ " AXIS[\"(E)\",east,\n"
+ " ORDER[1],\n"
+ " LENGTHUNIT[\"metre\",1,\n"
+ " ID[\"EPSG\",9001]]],\n"
+ " AXIS[\"(N)\",north,\n"
+ " ORDER[2],\n"
+ " LENGTHUNIT[\"metre\",1,\n"
+ " ID[\"EPSG\",9001]]]]";
+
+ auto obj = WKTParser().createFromWKT(wkt);
+ auto crs = nn_dynamic_pointer_cast<EngineeringCRS>(obj);
+ ASSERT_TRUE(crs != nullptr);
+
+ EXPECT_EQ(crs->nameStr(), "Engineering CRS");
+ EXPECT_EQ(crs->datum()->nameStr(), "Engineering datum");
+ auto cs = crs->coordinateSystem();
+ ASSERT_EQ(cs->axisList().size(), 2);
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(wkt_parse, ENGINEERINGCRS) {
+ auto wkt = "ENGINEERINGCRS[\"Engineering CRS\",\n"
+ " ENGINEERINGDATUM[\"Engineering datum\"],\n"
+ " CS[Cartesian,2],\n"
+ " AXIS[\"(E)\",east,\n"
+ " ORDER[1],\n"
+ " LENGTHUNIT[\"metre\",1,\n"
+ " ID[\"EPSG\",9001]]],\n"
+ " AXIS[\"(N)\",north,\n"
+ " ORDER[2],\n"
+ " LENGTHUNIT[\"metre\",1,\n"
+ " ID[\"EPSG\",9001]]]]";
+
+ auto obj = WKTParser().createFromWKT(wkt);
+ auto crs = nn_dynamic_pointer_cast<EngineeringCRS>(obj);
+ ASSERT_TRUE(crs != nullptr);
+
+ EXPECT_EQ(crs->nameStr(), "Engineering CRS");
+ EXPECT_EQ(crs->datum()->nameStr(), "Engineering datum");
+ auto cs = crs->coordinateSystem();
+ ASSERT_EQ(cs->axisList().size(), 2);
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(wkt_parse, LOCAL_CS_short) {
+ auto wkt = "LOCAL_CS[\"Engineering CRS\"]";
+
+ auto obj = WKTParser().createFromWKT(wkt);
+ auto crs = nn_dynamic_pointer_cast<EngineeringCRS>(obj);
+ ASSERT_TRUE(crs != nullptr);
+
+ EXPECT_EQ(crs->nameStr(), "Engineering CRS");
+ EXPECT_FALSE(!crs->datum()->nameStr().empty());
+ auto cs = crs->coordinateSystem();
+ ASSERT_EQ(cs->axisList().size(), 2);
+
+ EXPECT_EQ(
+ crs->exportToWKT(
+ WKTFormatter::create(WKTFormatter::Convention::WKT1_GDAL).get()),
+ wkt);
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(wkt_parse, LOCAL_CS_long_one_aix) {
+ auto wkt = "LOCAL_CS[\"Engineering CRS\",\n"
+ " LOCAL_DATUM[\"Engineering datum\",12345],\n"
+ " AXIS[\"height\",up]]";
+
+ auto obj = WKTParser().createFromWKT(wkt);
+ auto crs = nn_dynamic_pointer_cast<EngineeringCRS>(obj);
+ ASSERT_TRUE(crs != nullptr);
+
+ EXPECT_EQ(crs->nameStr(), "Engineering CRS");
+ EXPECT_EQ(crs->datum()->nameStr(), "Engineering datum");
+ auto cs = crs->coordinateSystem();
+ ASSERT_EQ(cs->axisList().size(), 1);
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(wkt_parse, LOCAL_CS_long_two_axis) {
+ auto wkt = "LOCAL_CS[\"Engineering CRS\",\n"
+ " LOCAL_DATUM[\"Engineering datum\",12345],\n"
+ " AXIS[\"Easting\",EAST],\n"
+ " AXIS[\"Northing\",NORTH]]";
+
+ auto obj = WKTParser().createFromWKT(wkt);
+ auto crs = nn_dynamic_pointer_cast<EngineeringCRS>(obj);
+ ASSERT_TRUE(crs != nullptr);
+
+ EXPECT_EQ(crs->nameStr(), "Engineering CRS");
+ EXPECT_EQ(crs->datum()->nameStr(), "Engineering datum");
+ auto cs = crs->coordinateSystem();
+ ASSERT_EQ(cs->axisList().size(), 2);
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(wkt_parse, PDATUM) {
+ auto wkt = "PDATUM[\"Parametric datum\",\n"
+ " ANCHOR[\"my anchor\"]]";
+
+ auto obj = WKTParser().createFromWKT(wkt);
+ auto datum = nn_dynamic_pointer_cast<ParametricDatum>(obj);
+ ASSERT_TRUE(datum != nullptr);
+
+ EXPECT_EQ(datum->nameStr(), "Parametric datum");
+ auto anchor = datum->anchorDefinition();
+ EXPECT_TRUE(anchor.has_value());
+ EXPECT_EQ(*anchor, "my anchor");
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(wkt_parse, PARAMETRICDATUM) {
+ auto wkt = "PARAMETRICDATUM[\"Parametric datum\",\n"
+ " ANCHOR[\"my anchor\"]]";
+
+ auto obj = WKTParser().createFromWKT(wkt);
+ auto datum = nn_dynamic_pointer_cast<ParametricDatum>(obj);
+ ASSERT_TRUE(datum != nullptr);
+
+ EXPECT_EQ(datum->nameStr(), "Parametric datum");
+ auto anchor = datum->anchorDefinition();
+ EXPECT_TRUE(anchor.has_value());
+ EXPECT_EQ(*anchor, "my anchor");
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(wkt_parse, PARAMETRICCRS) {
+ auto wkt =
+ "PARAMETRICCRS[\"WMO standard atmosphere layer 0\","
+ " PDATUM[\"Mean Sea Level\",ANCHOR[\"1013.25 hPa at 15°C\"]],"
+ " CS[parametric,1],"
+ " AXIS[\"pressure (hPa)\",up],"
+ " PARAMETRICUNIT[\"HectoPascal\",100.0]"
+ " ]";
+
+ auto obj = WKTParser().createFromWKT(wkt);
+ auto crs = nn_dynamic_pointer_cast<ParametricCRS>(obj);
+ ASSERT_TRUE(crs != nullptr);
+
+ EXPECT_EQ(crs->nameStr(), "WMO standard atmosphere layer 0");
+ EXPECT_EQ(crs->datum()->nameStr(), "Mean Sea Level");
+ auto cs = crs->coordinateSystem();
+ EXPECT_TRUE(nn_dynamic_pointer_cast<ParametricCS>(cs) != nullptr);
+ ASSERT_EQ(cs->axisList().size(), 1);
+ auto axis = cs->axisList()[0];
+ EXPECT_EQ(axis->nameStr(), "Pressure");
+ EXPECT_EQ(axis->unit().name(), "HectoPascal");
+ EXPECT_EQ(axis->unit().type(), UnitOfMeasure::Type::PARAMETRIC);
+ EXPECT_EQ(axis->unit().conversionToSI(), 100.0);
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(wkt_parse, PARAMETRICCRS_PARAMETRICDATUM) {
+ auto wkt = "PARAMETRICCRS[\"WMO standard atmosphere layer 0\","
+ " PARAMETRICDATUM[\"Mean Sea Level\"],"
+ " CS[parametric,1],"
+ " AXIS[\"pressure (hPa)\",up],"
+ " PARAMETRICUNIT[\"HectoPascal\",100.0]"
+ " ]";
+
+ auto obj = WKTParser().createFromWKT(wkt);
+ auto crs = nn_dynamic_pointer_cast<ParametricCRS>(obj);
+ ASSERT_TRUE(crs != nullptr);
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(wkt_parse, DerivedVerticalCRS) {
+ auto wkt = "VERTCRS[\"Derived vertCRS\",\n"
+ " BASEVERTCRS[\"ODN height\",\n"
+ " VDATUM[\"Ordnance Datum Newlyn\"]],\n"
+ " DERIVINGCONVERSION[\"unnamed\",\n"
+ " METHOD[\"PROJ unimplemented\"]],\n"
+ " CS[vertical,1],\n"
+ " AXIS[\"gravity-related height (H)\",up,\n"
+ " LENGTHUNIT[\"metre\",1,\n"
+ " ID[\"EPSG\",9001]]]]";
+
+ auto obj = WKTParser().createFromWKT(wkt);
+ auto crs = nn_dynamic_pointer_cast<DerivedVerticalCRS>(obj);
+ ASSERT_TRUE(crs != nullptr);
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(wkt_parse, DerivedEngineeringCRS) {
+
+ auto wkt = "ENGCRS[\"Derived EngineeringCRS\",\n"
+ " BASEENGCRS[\"Engineering CRS\",\n"
+ " EDATUM[\"Engineering datum\"]],\n"
+ " DERIVINGCONVERSION[\"unnamed\",\n"
+ " METHOD[\"PROJ unimplemented\"]],\n"
+ " CS[Cartesian,2],\n"
+ " AXIS[\"(E)\",east,\n"
+ " ORDER[1],\n"
+ " LENGTHUNIT[\"metre\",1,\n"
+ " ID[\"EPSG\",9001]]],\n"
+ " AXIS[\"(N)\",north,\n"
+ " ORDER[2],\n"
+ " LENGTHUNIT[\"metre\",1,\n"
+ " ID[\"EPSG\",9001]]]]";
+
+ auto obj = WKTParser().createFromWKT(wkt);
+ auto crs = nn_dynamic_pointer_cast<DerivedEngineeringCRS>(obj);
+ ASSERT_TRUE(crs != nullptr);
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(wkt_parse, DerivedParametricCRS) {
+
+ auto wkt = "PARAMETRICCRS[\"Derived ParametricCRS\",\n"
+ " BASEPARAMCRS[\"Parametric CRS\",\n"
+ " PDATUM[\"Parametric datum\"]],\n"
+ " DERIVINGCONVERSION[\"unnamed\",\n"
+ " METHOD[\"PROJ unimplemented\"]],\n"
+ " CS[parametric,1],\n"
+ " AXIS[\"pressure (hPa)\",up,\n"
+ " PARAMETRICUNIT[\"HectoPascal\",100]]]";
+
+ auto obj = WKTParser().createFromWKT(wkt);
+ auto crs = nn_dynamic_pointer_cast<DerivedParametricCRS>(obj);
+ ASSERT_TRUE(crs != nullptr);
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(wkt_parse, DerivedTemporalCRS) {
+
+ auto wkt = "TIMECRS[\"Derived TemporalCRS\",\n"
+ " BASETIMECRS[\"Temporal CRS\",\n"
+ " TDATUM[\"Gregorian calendar\",\n"
+ " CALENDAR[\"proleptic Gregorian\"],\n"
+ " TIMEORIGIN[0000-01-01]]],\n"
+ " DERIVINGCONVERSION[\"unnamed\",\n"
+ " METHOD[\"PROJ unimplemented\"]],\n"
+ " CS[TemporalDateTime,1],\n"
+ " AXIS[\"time (T)\",future]]";
+
+ auto obj = WKTParser().createFromWKT(wkt);
+ auto crs = nn_dynamic_pointer_cast<DerivedTemporalCRS>(obj);
+ ASSERT_TRUE(crs != nullptr);
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(wkt_parse, ensemble) {
+ auto wkt = "ENSEMBLE[\"test\",\n"
+ " MEMBER[\"World Geodetic System 1984\",\n"
+ " ID[\"EPSG\",6326]],\n"
+ " MEMBER[\"other datum\"],\n"
+ " ELLIPSOID[\"WGS 84\",6378137,298.257223563,\n"
+ " LENGTHUNIT[\"metre\",1],\n"
+ " ID[\"EPSG\",7030]],\n"
+ " ENSEMBLEACCURACY[100]]";
+
+ auto obj = WKTParser().createFromWKT(wkt);
+ auto ensemble = nn_dynamic_pointer_cast<DatumEnsemble>(obj);
+ ASSERT_TRUE(ensemble != nullptr);
+
+ ASSERT_EQ(ensemble->datums().size(), 2);
+ auto firstDatum =
+ nn_dynamic_pointer_cast<GeodeticReferenceFrame>(ensemble->datums()[0]);
+ ASSERT_TRUE(firstDatum != nullptr);
+ EXPECT_EQ(firstDatum->nameStr(), "World Geodetic System 1984");
+ ASSERT_EQ(firstDatum->identifiers().size(), 1);
+ EXPECT_EQ(firstDatum->identifiers()[0]->code(), "6326");
+ EXPECT_EQ(*(firstDatum->identifiers()[0]->codeSpace()), "EPSG");
+
+ EXPECT_EQ(firstDatum->ellipsoid()->nameStr(), "WGS 84");
+
+ EXPECT_EQ(ensemble->positionalAccuracy()->value(), "100");
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(wkt_parse, ensemble_vdatum) {
+ auto wkt = "ENSEMBLE[\"unnamed\",\n"
+ " MEMBER[\"vdatum1\"],\n"
+ " MEMBER[\"vdatum2\"],\n"
+ " ENSEMBLEACCURACY[100]]";
+
+ auto obj = WKTParser().createFromWKT(wkt);
+ auto ensemble = nn_dynamic_pointer_cast<DatumEnsemble>(obj);
+ ASSERT_TRUE(ensemble != nullptr);
+
+ ASSERT_EQ(ensemble->datums().size(), 2);
+ auto firstDatum =
+ nn_dynamic_pointer_cast<VerticalReferenceFrame>(ensemble->datums()[0]);
+ ASSERT_TRUE(firstDatum != nullptr);
+ EXPECT_EQ(firstDatum->nameStr(), "vdatum1");
+
+ EXPECT_EQ(ensemble->positionalAccuracy()->value(), "100");
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(wkt_parse, esri_geogcs_datum_spheroid_name_hardcoded_substitution) {
+ auto wkt = "GEOGCS[\"GCS_WGS_1984\",DATUM[\"D_WGS_1984\","
+ "SPHEROID[\"WGS_1984\",6378137.0,298.257223563]],"
+ "PRIMEM[\"Greenwich\",0.0],"
+ "UNIT[\"Degree\",0.0174532925199433]]";
+
+ // Test substitutions of CRS, datum and ellipsoid names from ESRI names
+ // to EPSG names.
+ auto obj = WKTParser().createFromWKT(wkt);
+ auto crs = nn_dynamic_pointer_cast<GeodeticCRS>(obj);
+ ASSERT_TRUE(crs != nullptr);
+
+ EXPECT_EQ(crs->nameStr(), "WGS 84");
+ EXPECT_EQ(crs->datum()->nameStr(), "World Geodetic System 1984");
+ EXPECT_EQ(crs->ellipsoid()->nameStr(), "WGS 84");
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(wkt_parse, esri_geogcs_datum_spheroid_name_from_db_substitution) {
+ auto wkt = "GEOGCS[\"GCS_WGS_1966\",DATUM[\"D_WGS_1966\","
+ "SPHEROID[\"WGS_1966\",6378145.0,298.25]],"
+ "PRIMEM[\"Greenwich\",0.0],"
+ "UNIT[\"Degree\",0.0174532925199433]]";
+
+ // Test substitutions of CRS, datum and ellipsoid names from ESRI names
+ // to EPSG names.
+ auto obj = WKTParser()
+ .attachDatabaseContext(DatabaseContext::create())
+ .createFromWKT(wkt);
+ auto crs = nn_dynamic_pointer_cast<GeodeticCRS>(obj);
+ ASSERT_TRUE(crs != nullptr);
+
+ EXPECT_EQ(crs->nameStr(), "WGS 66");
+ EXPECT_EQ(crs->datum()->nameStr(), "World Geodetic System 1966");
+ EXPECT_EQ(crs->ellipsoid()->nameStr(), "WGS_1966");
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(wkt_parse, esri_datum_name_with_prime_meridian) {
+ auto wkt = "GEOGCS[\"GCS_NTF_Paris\",DATUM[\"D_NTF\","
+ "SPHEROID[\"Clarke_1880_IGN\",6378249.2,293.4660212936265]],"
+ "PRIMEM[\"Paris\",2.337229166666667],"
+ "UNIT[\"Grad\",0.01570796326794897]]";
+
+ // D_NTF normally translates to "Nouvelle Triangulation Francaise",
+ // but as we have a non-Greenwich prime meridian, we also test if
+ // "Nouvelle Triangulation Francaise (Paris)" is not a registered datum name
+ auto obj = WKTParser()
+ .attachDatabaseContext(DatabaseContext::create())
+ .createFromWKT(wkt);
+ auto crs = nn_dynamic_pointer_cast<GeodeticCRS>(obj);
+ ASSERT_TRUE(crs != nullptr);
+
+ EXPECT_EQ(crs->nameStr(), "NTF (Paris)");
+ EXPECT_EQ(crs->datum()->nameStr(),
+ "Nouvelle Triangulation Francaise (Paris)");
+ EXPECT_EQ(crs->ellipsoid()->nameStr(), "Clarke 1880 (IGN)");
+}
+
+// ---------------------------------------------------------------------------
+
+static const struct {
+ const char *esriProjectionName;
+ std::vector<std::pair<const char *, double>> esriParams;
+ const char *wkt2ProjectionName;
+ std::vector<std::pair<const char *, double>> wkt2Params;
+} esriProjDefs[] = {
+
+ {"Plate_Carree",
+ {{"False_Easting", 1}, {"False_Northing", 2}, {"Central_Meridian", 3}},
+ "Equidistant Cylindrical",
+ {
+ {"Latitude of 1st standard parallel", 0},
+ {"Longitude of natural origin", 3},
+ {"False easting", 1},
+ {"False northing", 2},
+ }},
+
+ {"Equidistant_Cylindrical",
+ {
+ {"False_Easting", 1},
+ {"False_Northing", 2},
+ {"Central_Meridian", 3},
+ {"Standard_Parallel_1", 4},
+ },
+ "Equidistant Cylindrical",
+ {
+ {"Latitude of 1st standard parallel", 4},
+ {"Longitude of natural origin", 3},
+ {"False easting", 1},
+ {"False northing", 2},
+ }},
+
+ {"Miller_Cylindrical",
+ {{"False_Easting", 1}, {"False_Northing", 2}, {"Central_Meridian", 3}},
+ "Miller Cylindrical",
+ {
+ {"Longitude of natural origin", 3},
+ {"False easting", 1},
+ {"False northing", 2},
+ }},
+
+ {"Mercator",
+ {{"False_Easting", 1},
+ {"False_Northing", 2},
+ {"Central_Meridian", 3},
+ {"Standard_Parallel_1", 4}},
+ "Mercator (variant B)",
+ {
+ {"Latitude of 1st standard parallel", 4},
+ {"Longitude of natural origin", 3},
+ {"False easting", 1},
+ {"False northing", 2},
+ }},
+
+ {"Gauss_Kruger",
+ {{"False_Easting", 1},
+ {"False_Northing", 2},
+ {"Central_Meridian", 3},
+ {"Scale_Factor", 4},
+ {"Latitude_Of_Origin", 5}},
+ "Transverse Mercator",
+ {
+ {"Latitude of natural origin", 5},
+ {"Longitude of natural origin", 3},
+ {"Scale factor at natural origin", 4},
+ {"False easting", 1},
+ {"False northing", 2},
+ }},
+
+ {"Transverse_Mercator",
+ {{"False_Easting", 1},
+ {"False_Northing", 2},
+ {"Central_Meridian", 3},
+ {"Scale_Factor", 4},
+ {"Latitude_Of_Origin", 5}},
+ "Transverse Mercator",
+ {
+ {"Latitude of natural origin", 5},
+ {"Longitude of natural origin", 3},
+ {"Scale factor at natural origin", 4},
+ {"False easting", 1},
+ {"False northing", 2},
+ }},
+
+ {"Albers",
+ {{"False_Easting", 1},
+ {"False_Northing", 2},
+ {"Central_Meridian", 3},
+ {"Standard_Parallel_1", 4},
+ {"Standard_Parallel_2", 5},
+ {"Latitude_Of_Origin", 6}},
+ "Albers Equal Area",
+ {
+ {"Latitude of false origin", 6},
+ {"Longitude of false origin", 3},
+ {"Latitude of 1st standard parallel", 4},
+ {"Latitude of 2nd standard parallel", 5},
+ {"Easting at false origin", 1},
+ {"Northing at false origin", 2},
+ }},
+
+ {"Sinusoidal",
+ {{"False_Easting", 1}, {"False_Northing", 2}, {"Central_Meridian", 3}},
+ "Sinusoidal",
+ {
+ {"Longitude of natural origin", 3},
+ {"False easting", 1},
+ {"False northing", 2},
+ }},
+
+ {"Mollweide",
+ {{"False_Easting", 1}, {"False_Northing", 2}, {"Central_Meridian", 3}},
+ "Mollweide",
+ {
+ {"Longitude of natural origin", 3},
+ {"False easting", 1},
+ {"False northing", 2},
+ }},
+
+ {"Eckert_I",
+ {{"False_Easting", 1}, {"False_Northing", 2}, {"Central_Meridian", 3}},
+ "Eckert I",
+ {
+ {"Longitude of natural origin", 3},
+ {"False easting", 1},
+ {"False northing", 2},
+ }},
+
+ // skipping Eckert_II to Eckert_VI
+
+ {"Gall_Stereographic",
+ {{"False_Easting", 1}, {"False_Northing", 2}, {"Central_Meridian", 3}},
+ "Gall Stereographic",
+ {
+ {"Longitude of natural origin", 3},
+ {"False easting", 1},
+ {"False northing", 2},
+ }},
+
+ {"Winkel_I",
+ {
+ {"False_Easting", 1},
+ {"False_Northing", 2},
+ {"Central_Meridian", 3},
+ {"Standard_Parallel_1", 4},
+ },
+ "Winkel I",
+ {
+ {"Longitude of natural origin", 3},
+ {"Latitude of 1st standard parallel", 4},
+ {"False easting", 1},
+ {"False northing", 2},
+ }},
+
+ {"Winkel_II",
+ {
+ {"False_Easting", 1},
+ {"False_Northing", 2},
+ {"Central_Meridian", 3},
+ {"Standard_Parallel_1", 4},
+ },
+ "Winkel II",
+ {
+ {"Longitude of natural origin", 3},
+ {"Latitude of 1st standard parallel", 4},
+ {"False easting", 1},
+ {"False northing", 2},
+ }},
+
+ {"Lambert_Conformal_Conic",
+ {{"False_Easting", 1},
+ {"False_Northing", 2},
+ {"Central_Meridian", 3},
+ {"Standard_Parallel_1", 4},
+ {"Scale_Factor", 5},
+ {"Latitude_Of_Origin", 4}},
+ "Lambert Conic Conformal (1SP)",
+ {
+ {"Latitude of natural origin", 4},
+ {"Longitude of natural origin", 3},
+ {"Scale factor at natural origin", 5},
+ {"False easting", 1},
+ {"False northing", 2},
+ }},
+
+ {"Lambert_Conformal_Conic",
+ {{"False_Easting", 1},
+ {"False_Northing", 2},
+ {"Central_Meridian", 3},
+ {"Standard_Parallel_1", 4},
+ {"Standard_Parallel_2", 5},
+ {"Latitude_Of_Origin", 6}},
+ "Lambert Conic Conformal (2SP)",
+ {
+ {"Latitude of false origin", 6},
+ {"Longitude of false origin", 3},
+ {"Latitude of 1st standard parallel", 4},
+ {"Latitude of 2nd standard parallel", 5},
+ {"Easting at false origin", 1},
+ {"Northing at false origin", 2},
+ }},
+
+ // Unusual variant of above with Scale_Factor=1
+ {"Lambert_Conformal_Conic",
+ {{"False_Easting", 1},
+ {"False_Northing", 2},
+ {"Central_Meridian", 3},
+ {"Standard_Parallel_1", 4},
+ {"Standard_Parallel_2", 5},
+ {"Scale_Factor", 1.0},
+ {"Latitude_Of_Origin", 6}},
+ "Lambert Conic Conformal (2SP)",
+ {
+ {"Latitude of false origin", 6},
+ {"Longitude of false origin", 3},
+ {"Latitude of 1st standard parallel", 4},
+ {"Latitude of 2nd standard parallel", 5},
+ {"Easting at false origin", 1},
+ {"Northing at false origin", 2},
+ }},
+
+ {"Polyconic",
+ {{"False_Easting", 1},
+ {"False_Northing", 2},
+ {"Central_Meridian", 3},
+ {"Latitude_Of_Origin", 4}},
+ "American Polyconic",
+ {
+ {"Latitude of natural origin", 4},
+ {"Longitude of natural origin", 3},
+ {"False easting", 1},
+ {"False northing", 2},
+ }},
+
+ {"Quartic_Authalic",
+ {{"False_Easting", 1}, {"False_Northing", 2}, {"Central_Meridian", 3}},
+ "Quartic Authalic",
+ {
+ {"Longitude of natural origin", 3},
+ {"False easting", 1},
+ {"False northing", 2},
+ }},
+
+ {"Loximuthal",
+ {{"False_Easting", 1},
+ {"False_Northing", 2},
+ {"Central_Meridian", 3},
+ {"Central_Parallel", 4}},
+ "Loximuthal",
+ {
+ {"Latitude of natural origin", 4},
+ {"Longitude of natural origin", 3},
+ {"False easting", 1},
+ {"False northing", 2},
+ }},
+
+ {"Bonne",
+ {{"False_Easting", 1},
+ {"False_Northing", 2},
+ {"Central_Meridian", 3},
+ {"Standard_Parallel_1", 4}},
+ "Bonne",
+ {
+ {"Latitude of natural origin", 4},
+ {"Longitude of natural origin", 3},
+ {"False easting", 1},
+ {"False northing", 2},
+ }},
+
+ {"Hotine_Oblique_Mercator_Two_Point_Natural_Origin",
+ {{"False_Easting", 1},
+ {"False_Northing", 2},
+ {"Latitude_Of_1st_Point", 3},
+ {"Latitude_Of_2nd_Point", 4},
+ {"Scale_Factor", 5},
+ {"Longitude_Of_1st_Point", 6},
+ {"Longitude_Of_2nd_Point", 7},
+ {"Latitude_Of_Center", 8}},
+ "Hotine Oblique Mercator Two Point Natural Origin",
+ {
+ {"Latitude of projection centre", 8},
+ {"Latitude of 1st point", 3},
+ {"Longitude of 1st point", 6},
+ {"Latitude of 2nd point", 4},
+ {"Longitude of 2nd point", 7},
+ {"Scale factor on initial line", 5},
+ {"Easting at projection centre", 1},
+ {"Northing at projection centre", 2},
+ }},
+
+ {"Stereographic",
+ {{"False_Easting", 1},
+ {"False_Northing", 2},
+ {"Central_Meridian", 3},
+ {"Scale_Factor", 4},
+ {"Latitude_Of_Origin", 5}},
+ "Stereographic",
+ {
+ {"Latitude of natural origin", 5},
+ {"Longitude of natural origin", 3},
+ {"Scale factor at natural origin", 4},
+ {"False easting", 1},
+ {"False northing", 2},
+ }},
+
+ {"Stereographic",
+ {{"False_Easting", 1},
+ {"False_Northing", 2},
+ {"Central_Meridian", 3},
+ {"Scale_Factor", 4},
+ {"Latitude_Of_Origin", 90}},
+ "Polar Stereographic (variant A)",
+ {
+ {"Latitude of natural origin", 90},
+ {"Longitude of natural origin", 3},
+ {"Scale factor at natural origin", 4},
+ {"False easting", 1},
+ {"False northing", 2},
+ }},
+
+ {"Stereographic",
+ {{"False_Easting", 1},
+ {"False_Northing", 2},
+ {"Central_Meridian", 3},
+ {"Scale_Factor", 4},
+ {"Latitude_Of_Origin", -90}},
+ "Polar Stereographic (variant A)",
+ {
+ {"Latitude of natural origin", -90},
+ {"Longitude of natural origin", 3},
+ {"Scale factor at natural origin", 4},
+ {"False easting", 1},
+ {"False northing", 2},
+ }},
+
+ {"Equidistant_Conic",
+ {{"False_Easting", 1},
+ {"False_Northing", 2},
+ {"Central_Meridian", 3},
+ {"Standard_Parallel_1", 4},
+ {"Standard_Parallel_2", 5},
+ {"Latitude_Of_Origin", 6}},
+ "Equidistant Conic",
+ {
+ {"Latitude of natural origin", 6},
+ {"Longitude of natural origin", 3},
+ {"Latitude of 1st standard parallel", 4},
+ {"Latitude of 2nd standard parallel", 5},
+ {"False easting", 1},
+ {"False northing", 2},
+ }},
+
+ {"Cassini",
+ {{"False_Easting", 1},
+ {"False_Northing", 2},
+ {"Central_Meridian", 3},
+ {"Scale_Factor", 1},
+ {"Latitude_Of_Origin", 4}},
+ "Cassini-Soldner",
+ {
+ {"Latitude of natural origin", 4},
+ {"Longitude of natural origin", 3},
+ {"False easting", 1},
+ {"False northing", 2},
+ }},
+
+ {"Van_der_Grinten_I",
+ {{"False_Easting", 1}, {"False_Northing", 2}, {"Central_Meridian", 3}},
+ "Van Der Grinten",
+ {
+ {"Longitude of natural origin", 3},
+ {"False easting", 1},
+ {"False northing", 2},
+ }},
+
+ {"Robinson",
+ {{"False_Easting", 1}, {"False_Northing", 2}, {"Central_Meridian", 3}},
+ "Robinson",
+ {
+ {"Longitude of natural origin", 3},
+ {"False easting", 1},
+ {"False northing", 2},
+ }},
+
+ {"Two_Point_Equidistant",
+ {{"False_Easting", 1},
+ {"False_Northing", 2},
+ {"Latitude_Of_1st_Point", 3},
+ {"Latitude_Of_2nd_Point", 4},
+ {"Longitude_Of_1st_Point", 5},
+ {"Longitude_Of_2nd_Point", 6}},
+ "Two Point Equidistant",
+ {
+ {"Latitude of 1st point", 3},
+ {"Longitude of 1st point", 5},
+ {"Latitude of 2nd point", 4},
+ {"Longitude of 2nd point", 6},
+ {"False easting", 1},
+ {"False northing", 2},
+ }},
+
+ {"Azimuthal_Equidistant",
+ {{"False_Easting", 1},
+ {"False_Northing", 2},
+ {"Central_Meridian", 3},
+ {"Latitude_Of_Origin", 4}},
+ "Modified Azimuthal Equidistant",
+ {
+ {"Latitude of natural origin", 4},
+ {"Longitude of natural origin", 3},
+ {"False easting", 1},
+ {"False northing", 2},
+ }},
+
+ {"Lambert_Azimuthal_Equal_Area",
+ {{"False_Easting", 1},
+ {"False_Northing", 2},
+ {"Central_Meridian", 3},
+ {"Latitude_Of_Origin", 4}},
+ "Lambert Azimuthal Equal Area",
+ {
+ {"Latitude of natural origin", 4},
+ {"Longitude of natural origin", 3},
+ {"False easting", 1},
+ {"False northing", 2},
+ }},
+
+ {"Cylindrical_Equal_Area",
+ {{"False_Easting", 1},
+ {"False_Northing", 2},
+ {"Central_Meridian", 3},
+ {"Standard_Parallel_1", 4}},
+ "Lambert Cylindrical Equal Area (Spherical)",
+ {
+ {"Latitude of 1st standard parallel", 4},
+ {"Longitude of natural origin", 3},
+ {"False easting", 1},
+ {"False northing", 2},
+ }},
+
+ // Untested: Hotine_Oblique_Mercator_Two_Point_Center
+
+ {"Hotine_Oblique_Mercator_Azimuth_Natural_Origin",
+ {{"False_Easting", 1},
+ {"False_Northing", 2},
+ {"Scale_Factor", 3},
+ {"Azimuth", 4},
+ {"Longitude_Of_Center", 5},
+ {"Latitude_Of_Center", 6}},
+ "Hotine Oblique Mercator (variant A)",
+ {
+ {"Latitude of projection centre", 6},
+ {"Longitude of projection centre", 5},
+ {"Azimuth of initial line", 4},
+ {"Angle from Rectified to Skew Grid", 4},
+ {"Scale factor on initial line", 3},
+ {"False easting", 1},
+ {"False northing", 2},
+ }},
+
+ {"Hotine_Oblique_Mercator_Azimuth_Center",
+ {{"False_Easting", 1},
+ {"False_Northing", 2},
+ {"Scale_Factor", 3},
+ {"Azimuth", 4},
+ {"Longitude_Of_Center", 5},
+ {"Latitude_Of_Center", 6}},
+ "Hotine Oblique Mercator (variant B)",
+ {
+ {"Latitude of projection centre", 6},
+ {"Longitude of projection centre", 5},
+ {"Azimuth of initial line", 4},
+ {"Angle from Rectified to Skew Grid", 4},
+ {"Scale factor on initial line", 3},
+ {"Easting at projection centre", 1},
+ {"Northing at projection centre", 2},
+ }},
+
+ {"Double_Stereographic",
+ {{"False_Easting", 1},
+ {"False_Northing", 2},
+ {"Central_Meridian", 3},
+ {"Scale_Factor", 4},
+ {"Latitude_Of_Origin", 5}},
+ "Oblique Stereographic",
+ {
+ {"Latitude of natural origin", 5},
+ {"Longitude of natural origin", 3},
+ {"Scale factor at natural origin", 4},
+ {"False easting", 1},
+ {"False northing", 2},
+ }},
+
+ {"Krovak",
+ {{"False_Easting", 1},
+ {"False_Northing", 2},
+ {"Pseudo_Standard_Parallel_1", 3},
+ {"Scale_Factor", 4},
+ {"Azimuth", 5},
+ {"Longitude_Of_Center", 6},
+ {"Latitude_Of_Center", 7},
+ {"X_Scale", 1},
+ {"Y_Scale", 1},
+ {"XY_Plane_Rotation", 0}},
+ "Krovak",
+ {
+ {"Latitude of projection centre", 7},
+ {"Longitude of origin", 6},
+ {"Co-latitude of cone axis", 5},
+ {"Latitude of pseudo standard parallel", 3},
+ {"Scale factor on pseudo standard parallel", 4},
+ {"False easting", 1},
+ {"False northing", 2},
+ }},
+
+ {"Krovak",
+ {{"False_Easting", 1},
+ {"False_Northing", 2},
+ {"Pseudo_Standard_Parallel_1", 3},
+ {"Scale_Factor", 4},
+ {"Azimuth", 5},
+ {"Longitude_Of_Center", 6},
+ {"Latitude_Of_Center", 7},
+ {"X_Scale", -1},
+ {"Y_Scale", 1},
+ {"XY_Plane_Rotation", 90}},
+ "Krovak (North Orientated)",
+ {
+ {"Latitude of projection centre", 7},
+ {"Longitude of origin", 6},
+ {"Co-latitude of cone axis", 5},
+ {"Latitude of pseudo standard parallel", 3},
+ {"Scale factor on pseudo standard parallel", 4},
+ {"False easting", 1},
+ {"False northing", 2},
+ }},
+
+ {"New_Zealand_Map_Grid",
+ {{"False_Easting", 1},
+ {"False_Northing", 2},
+ {"Longitude_Of_Origin", 3},
+ {"Latitude_Of_Origin", 4}},
+ "New Zealand Map Grid",
+ {
+ {"Latitude of natural origin", 4},
+ {"Longitude of natural origin", 3},
+ {"False easting", 1},
+ {"False northing", 2},
+ }},
+
+ {"Orthographic",
+ {{"False_Easting", 1},
+ {"False_Northing", 2},
+ {"Longitude_Of_Center", 3},
+ {"Latitude_Of_Center", 4}},
+ "Orthographic",
+ {
+ {"Latitude of natural origin", 4},
+ {"Longitude of natural origin", 3},
+ {"False easting", 1},
+ {"False northing", 2},
+ }},
+
+ {"Winkel_Tripel",
+ {{"False_Easting", 1},
+ {"False_Northing", 2},
+ {"Central_Meridian", 3},
+ {"Standard_Parallel_1", 4}},
+ "Winkel Tripel",
+ {
+ {"Longitude of natural origin", 3},
+ {"Latitude of 1st standard parallel", 4},
+ {"False easting", 1},
+ {"False northing", 2},
+ }},
+
+ {"Aitoff",
+ {{"False_Easting", 1}, {"False_Northing", 2}, {"Central_Meridian", 3}},
+ "Aitoff",
+ {
+ {"Longitude of natural origin", 3},
+ {"False easting", 1},
+ {"False northing", 2},
+ }},
+
+ {"Craster_Parabolic",
+ {{"False_Easting", 1}, {"False_Northing", 2}, {"Central_Meridian", 3}},
+ "Craster Parabolic",
+ {
+ {"Longitude of natural origin", 3},
+ {"False easting", 1},
+ {"False northing", 2},
+ }},
+
+ {"Gnomonic",
+ {{"False_Easting", 1},
+ {"False_Northing", 2},
+ {"Longitude_Of_Center", 3},
+ {"Latitude_Of_Center", 4}},
+ "Gnomonic",
+ {
+ {"Latitude of natural origin", 4},
+ {"Longitude of natural origin", 3},
+ {"False easting", 1},
+ {"False northing", 2},
+ }},
+
+ {"Stereographic_North_Pole",
+ {{"False_Easting", 1},
+ {"False_Northing", 2},
+ {"Central_Meridian", 3},
+ {"Standard_Parallel_1", 4}},
+ "Polar Stereographic (variant B)",
+ {
+ {"Latitude of standard parallel", 4},
+ {"Longitude of origin", 3},
+ {"False easting", 1},
+ {"False northing", 2},
+ }},
+
+ {"Stereographic_South_Pole",
+ {{"False_Easting", 1},
+ {"False_Northing", 2},
+ {"Central_Meridian", 3},
+ {"Standard_Parallel_1", -4}},
+ "Polar Stereographic (variant B)",
+ {
+ {"Latitude of standard parallel", -4},
+ {"Longitude of origin", 3},
+ {"False easting", 1},
+ {"False northing", 2},
+ }},
+
+ {"Rectified_Skew_Orthomorphic_Natural_Origin",
+ {
+ {"False_Easting", 1},
+ {"False_Northing", 2},
+ {"Scale_Factor", 3},
+ {"Azimuth", 4},
+ {"Longitude_Of_Center", 5},
+ {"Latitude_Of_Center", 6},
+ {"XY_Plane_Rotation", 7},
+ },
+ "Hotine Oblique Mercator (variant A)",
+ {
+ {"Latitude of projection centre", 6},
+ {"Longitude of projection centre", 5},
+ {"Azimuth of initial line", 4},
+ {"Angle from Rectified to Skew Grid", 7},
+ {"Scale factor on initial line", 3},
+ {"False easting", 1},
+ {"False northing", 2},
+ }},
+
+ // Temptative mapping
+ {"Rectified_Skew_Orthomorphic_Center",
+ {
+ {"False_Easting", 1},
+ {"False_Northing", 2},
+ {"Scale_Factor", 3},
+ {"Azimuth", 4},
+ {"Longitude_Of_Center", 5},
+ {"Latitude_Of_Center", 6},
+ {"XY_Plane_Rotation", 7},
+ },
+ "Hotine Oblique Mercator (variant B)",
+ {
+ {"Latitude of projection centre", 6},
+ {"Longitude of projection centre", 5},
+ {"Azimuth of initial line", 4},
+ {"Angle from Rectified to Skew Grid", 7},
+ {"Scale factor on initial line", 3},
+ {"Easting at projection centre", 1},
+ {"Northing at projection centre", 2},
+ }},
+
+ {"Goode_Homolosine",
+ {{"False_Easting", 1}, {"False_Northing", 2}, {"Central_Meridian", 3}},
+ "Goode Homolosine",
+ {
+ {"Longitude of natural origin", 3},
+ {"False easting", 1},
+ {"False northing", 2},
+ }},
+
+ {"Equidistant_Cylindrical_Ellipsoidal",
+ {
+ {"False_Easting", 1},
+ {"False_Northing", 2},
+ {"Central_Meridian", 3},
+ {"Standard_Parallel_1", 4},
+ },
+ "Equidistant Cylindrical",
+ {
+ {"Latitude of 1st standard parallel", 4},
+ {"Longitude of natural origin", 3},
+ {"False easting", 1},
+ {"False northing", 2},
+ }},
+
+ {"Laborde_Oblique_Mercator",
+ {{"False_Easting", 1},
+ {"False_Northing", 2},
+ {"Scale_Factor", 3},
+ {"Azimuth", 4},
+ {"Longitude_Of_Center", 5},
+ {"Latitude_Of_Center", 6}},
+ "Laborde Oblique Mercator",
+ {
+ {"Latitude of projection centre", 6},
+ {"Longitude of projection centre", 5},
+ {"Azimuth of initial line", 4},
+ {"Scale factor on initial line", 3},
+ {"False easting", 1},
+ {"False northing", 2},
+ }},
+
+ {"Gnomonic_Ellipsoidal",
+ {{"False_Easting", 1},
+ {"False_Northing", 2},
+ {"Longitude_Of_Center", 3},
+ {"Latitude_Of_Center", 4}},
+ "Gnomonic",
+ {
+ {"Latitude of natural origin", 4},
+ {"Longitude of natural origin", 3},
+ {"False easting", 1},
+ {"False northing", 2},
+ }},
+
+ {"Wagner_IV",
+ {{"False_Easting", 1},
+ {"False_Northing", 2},
+ {"Central_Meridian", 3},
+ {"Latitude_Of_Center", 0}},
+ "Wagner IV",
+ {
+ {"Longitude of natural origin", 3},
+ {"False easting", 1},
+ {"False northing", 2},
+ }},
+
+ {"Wagner_V",
+ {{"False_Easting", 1}, {"False_Northing", 2}, {"Central_Meridian", 3}},
+ "Wagner V",
+ {
+ {"Longitude of natural origin", 3},
+ {"False easting", 1},
+ {"False northing", 2},
+ }},
+
+ {"Wagner_VII",
+ {{"False_Easting", 1}, {"False_Northing", 2}, {"Central_Meridian", 3}},
+ "Wagner VII",
+ {
+ {"Longitude of natural origin", 3},
+ {"False easting", 1},
+ {"False northing", 2},
+ }},
+
+ {"Geostationary_Satellite",
+ {
+ {"False_Easting", 1},
+ {"False_Northing", 2},
+ {"Longitude_Of_Center", 3},
+ {"Height", 4},
+ {"Option", 0.0},
+ },
+ "Geostationary Satellite (Sweep Y)",
+ {
+ {"Longitude of natural origin", 3},
+ {"Satellite Height", 4},
+ {"False easting", 1},
+ {"False northing", 2},
+ }},
+
+ {"Mercator_Auxiliary_Sphere",
+ {{"False_Easting", 1},
+ {"False_Northing", 2},
+ {"Central_Meridian", 3},
+ {"Standard_Parallel_1", 4},
+ {"Auxiliary_Sphere_Type", 0}},
+ "Popular Visualisation Pseudo Mercator",
+ {
+ {"Latitude of natural origin", 4},
+ {"Longitude of natural origin", 3},
+ {"False easting", 1},
+ {"False northing", 2},
+ }},
+
+ {
+ "Unknown_Method",
+ {{"False_Easting", 1},
+ {"False_Northing", 2},
+ {"Longitude_Of_Origin", 3},
+ {"Latitude_Of_Origin", 4}},
+ "Unknown_Method",
+ {{"False_Easting", 1},
+ {"False_Northing", 2},
+ {"Longitude_Of_Origin", 3},
+ {"Latitude_Of_Origin", 4}},
+ },
+
+};
+
+TEST(wkt_parse, esri_projcs) {
+
+ for (const auto &projDef : esriProjDefs) {
+ std::string wkt("PROJCS[\"unnamed\",GEOGCS[\"GCS_WGS_1984\","
+ "DATUM[\"D_WGS_1984\",SPHEROID[\"WGS_1984\","
+ "6378137.0,298.257223563]],PRIMEM[\"Greenwich\",0.0],"
+ "UNIT[\"Degree\",0.0174532925199433]],PROJECTION[\"");
+ wkt += projDef.esriProjectionName;
+ wkt += "\"],";
+ for (const auto &param : projDef.esriParams) {
+ wkt += "PARAMETER[\"";
+ wkt += param.first;
+ wkt += "\",";
+ wkt += toString(param.second);
+ wkt += "],";
+ }
+ wkt += "UNIT[\"Meter\",1.0]]";
+
+ auto obj = WKTParser().createFromWKT(wkt);
+ auto crs = nn_dynamic_pointer_cast<ProjectedCRS>(obj);
+ ASSERT_TRUE(crs != nullptr);
+
+ auto conv = crs->derivingConversion();
+ auto method = conv->method();
+ EXPECT_EQ(method->nameStr(), projDef.wkt2ProjectionName) << wkt;
+ auto values = conv->parameterValues();
+ EXPECT_EQ(values.size(), projDef.wkt2Params.size()) << wkt;
+ if (values.size() == projDef.wkt2Params.size()) {
+ for (size_t i = 0; i < values.size(); i++) {
+ const auto &opParamvalue =
+ nn_dynamic_pointer_cast<OperationParameterValue>(values[i]);
+ ASSERT_TRUE(opParamvalue);
+ const auto &paramName = opParamvalue->parameter()->nameStr();
+ const auto &parameterValue = opParamvalue->parameterValue();
+ EXPECT_EQ(paramName, projDef.wkt2Params[i].first) << wkt;
+ EXPECT_EQ(parameterValue->type(),
+ ParameterValue::Type::MEASURE);
+ auto measure = parameterValue->value();
+ EXPECT_EQ(measure.value(), projDef.wkt2Params[i].second) << wkt;
+ }
+ }
+ }
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(wkt_parse, wkt1_esri_krovak_south_west) {
+ auto wkt = "PROJCS[\"S-JTSK_Krovak\",GEOGCS[\"GCS_S_JTSK\","
+ "DATUM[\"D_S_JTSK\","
+ "SPHEROID[\"Bessel_1841\",6377397.155,299.1528128]],"
+ "PRIMEM[\"Greenwich\",0.0],UNIT[\"Degree\",0.0174532925199433]],"
+ "PROJECTION[\"Krovak\"],PARAMETER[\"False_Easting\",0.0],"
+ "PARAMETER[\"False_Northing\",0.0],"
+ "PARAMETER[\"Pseudo_Standard_Parallel_1\",78.5],"
+ "PARAMETER[\"Scale_Factor\",0.9999],"
+ "PARAMETER[\"Azimuth\",30.28813975277778],"
+ "PARAMETER[\"Longitude_Of_Center\",24.83333333333333],"
+ "PARAMETER[\"Latitude_Of_Center\",49.5],"
+ "PARAMETER[\"X_Scale\",1.0],"
+ "PARAMETER[\"Y_Scale\",1.0],"
+ "PARAMETER[\"XY_Plane_Rotation\",0.0],UNIT[\"Meter\",1.0]]";
+
+ auto obj = WKTParser()
+ .attachDatabaseContext(DatabaseContext::create())
+ .createFromWKT(wkt);
+ auto crs = nn_dynamic_pointer_cast<ProjectedCRS>(obj);
+ ASSERT_TRUE(crs != nullptr);
+
+ EXPECT_EQ(crs->derivingConversion()->method()->nameStr(), "Krovak");
+
+ auto expected_wkt2 =
+ "PROJCRS[\"S-JTSK / Krovak\",\n"
+ " BASEGEODCRS[\"S-JTSK\",\n"
+ " DATUM[\"System of the Unified Trigonometrical Cadastral "
+ "Network\",\n"
+ " ELLIPSOID[\"Bessel 1841\",6377397.155,299.1528128,\n"
+ " LENGTHUNIT[\"metre\",1]],\n"
+ " ID[\"EPSG\",6156]],\n"
+ " PRIMEM[\"Greenwich\",0,\n"
+ " ANGLEUNIT[\"Degree\",0.0174532925199433]]],\n"
+ " CONVERSION[\"unnamed\",\n"
+ " METHOD[\"Krovak\",\n"
+ " ID[\"EPSG\",9819]],\n"
+ " PARAMETER[\"Latitude of projection centre\",49.5,\n"
+ " ANGLEUNIT[\"Degree\",0.0174532925199433],\n"
+ " ID[\"EPSG\",8811]],\n"
+ " PARAMETER[\"Longitude of origin\",24.8333333333333,\n"
+ " ANGLEUNIT[\"Degree\",0.0174532925199433],\n"
+ " ID[\"EPSG\",8833]],\n"
+ " PARAMETER[\"Co-latitude of cone axis\",30.2881397527778,\n"
+ " ANGLEUNIT[\"Degree\",0.0174532925199433],\n"
+ " ID[\"EPSG\",1036]],\n"
+ " PARAMETER[\"Latitude of pseudo standard parallel\",78.5,\n"
+ " ANGLEUNIT[\"Degree\",0.0174532925199433],\n"
+ " ID[\"EPSG\",8818]],\n"
+ " PARAMETER[\"Scale factor on pseudo standard "
+ "parallel\",0.9999,\n"
+ " SCALEUNIT[\"unity\",1],\n"
+ " ID[\"EPSG\",8819]],\n"
+ " PARAMETER[\"False easting\",0,\n"
+ " LENGTHUNIT[\"metre\",1],\n"
+ " ID[\"EPSG\",8806]],\n"
+ " PARAMETER[\"False northing\",0,\n"
+ " LENGTHUNIT[\"metre\",1],\n"
+ " ID[\"EPSG\",8807]]],\n"
+ " CS[Cartesian,2],\n"
+ " AXIS[\"southing\",south,\n"
+ " ORDER[1],\n"
+ " LENGTHUNIT[\"metre\",1,\n"
+ " ID[\"EPSG\",9001]]],\n"
+ " AXIS[\"westing\",west,\n"
+ " ORDER[2],\n"
+ " LENGTHUNIT[\"metre\",1,\n"
+ " ID[\"EPSG\",9001]]]]";
+
+ EXPECT_EQ(crs->exportToWKT(WKTFormatter::create().get()), expected_wkt2);
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(wkt_parse, wkt1_esri_normalize_unit) {
+ auto wkt = "PROJCS[\"Accra_Ghana_Grid\",GEOGCS[\"GCS_Accra\","
+ "DATUM[\"D_Accra\",SPHEROID[\"War_Office\",6378300.0,296.0]],"
+ "PRIMEM[\"Greenwich\",0.0],UNIT[\"Degree\",0.0174532925199433]],"
+ "PROJECTION[\"Transverse_Mercator\"],"
+ "PARAMETER[\"False_Easting\",900000.0],"
+ "PARAMETER[\"False_Northing\",0.0],"
+ "PARAMETER[\"Central_Meridian\",-1.0],"
+ "PARAMETER[\"Scale_Factor\",0.99975],"
+ "PARAMETER[\"Latitude_Of_Origin\",4.666666666666667],"
+ "UNIT[\"Foot_Gold_Coast\",0.3047997101815088]]";
+
+ auto obj = WKTParser()
+ .attachDatabaseContext(DatabaseContext::create())
+ .createFromWKT(wkt);
+ auto crs = nn_dynamic_pointer_cast<ProjectedCRS>(obj);
+ ASSERT_TRUE(crs != nullptr);
+
+ EXPECT_EQ(crs->coordinateSystem()->axisList()[0]->unit().name(),
+ "Gold Coast foot");
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(wkt_parse, wkt1_esri_ups_north) {
+ auto wkt = "PROJCS[\"UPS_North\",GEOGCS[\"GCS_WGS_1984\","
+ "DATUM[\"D_WGS_1984\","
+ "SPHEROID[\"WGS_1984\",6378137.0,298.257223563]],"
+ "PRIMEM[\"Greenwich\",0.0],UNIT[\"Degree\",0.0174532925199433]],"
+ "PROJECTION[\"Stereographic\"],"
+ "PARAMETER[\"False_Easting\",2000000.0],"
+ "PARAMETER[\"False_Northing\",2000000.0],"
+ "PARAMETER[\"Central_Meridian\",0.0],"
+ "PARAMETER[\"Scale_Factor\",0.994],"
+ "PARAMETER[\"Latitude_Of_Origin\",90.0],"
+ "UNIT[\"Meter\",1.0]]";
+
+ auto obj = WKTParser()
+ .attachDatabaseContext(DatabaseContext::create())
+ .createFromWKT(wkt);
+ auto crs = nn_dynamic_pointer_cast<ProjectedCRS>(obj);
+ ASSERT_TRUE(crs != nullptr);
+
+ EXPECT_EQ(crs->nameStr(), "WGS 84 / UPS North (E,N)");
+ EXPECT_EQ(crs->coordinateSystem()->axisList()[0]->direction(),
+ AxisDirection::SOUTH);
+ EXPECT_EQ(crs->coordinateSystem()->axisList()[0]->abbreviation(), "E");
+ EXPECT_EQ(crs->coordinateSystem()->axisList()[1]->direction(),
+ AxisDirection::SOUTH);
+ EXPECT_EQ(crs->coordinateSystem()->axisList()[1]->abbreviation(), "N");
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(wkt_parse, wkt1_esri_ups_south) {
+ auto wkt = "PROJCS[\"UPS_South\",GEOGCS[\"GCS_WGS_1984\","
+ "DATUM[\"D_WGS_1984\","
+ "SPHEROID[\"WGS_1984\",6378137.0,298.257223563]],"
+ "PRIMEM[\"Greenwich\",0.0],UNIT[\"Degree\",0.0174532925199433]],"
+ "PROJECTION[\"Stereographic\"],"
+ "PARAMETER[\"False_Easting\",2000000.0],"
+ "PARAMETER[\"False_Northing\",2000000.0],"
+ "PARAMETER[\"Central_Meridian\",0.0],"
+ "PARAMETER[\"Scale_Factor\",0.994],"
+ "PARAMETER[\"Latitude_Of_Origin\",-90.0],"
+ "UNIT[\"Meter\",1.0]]";
+
+ auto obj = WKTParser()
+ .attachDatabaseContext(DatabaseContext::create())
+ .createFromWKT(wkt);
+ auto crs = nn_dynamic_pointer_cast<ProjectedCRS>(obj);
+ ASSERT_TRUE(crs != nullptr);
+
+ EXPECT_EQ(crs->nameStr(), "WGS 84 / UPS South (E,N)");
+ EXPECT_EQ(crs->coordinateSystem()->axisList()[0]->direction(),
+ AxisDirection::NORTH);
+ EXPECT_EQ(crs->coordinateSystem()->axisList()[0]->abbreviation(), "E");
+ EXPECT_EQ(crs->coordinateSystem()->axisList()[1]->direction(),
+ AxisDirection::NORTH);
+ EXPECT_EQ(crs->coordinateSystem()->axisList()[1]->abbreviation(), "N");
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(wkt_parse, invalid) {
+ EXPECT_THROW(WKTParser().createFromWKT(""), ParsingException);
+ EXPECT_THROW(WKTParser().createFromWKT("A"), ParsingException);
+ EXPECT_THROW(WKTParser().createFromWKT("UNKNOWN[\"foo\"]"),
+ ParsingException);
+ EXPECT_THROW(WKTParser().createFromWKT("INVALID["), ParsingException);
+ EXPECT_THROW(WKTParser().createFromWKT("INVALID[]"), ParsingException);
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(wkt_parse, invalid_SPHEROID) {
+ EXPECT_NO_THROW(WKTParser().createFromWKT("SPHEROID[\"x\",1,0.5]"));
+ EXPECT_THROW(WKTParser().createFromWKT("SPHEROID[\"x\"]"),
+ ParsingException); // major axis not number
+ EXPECT_THROW(WKTParser().createFromWKT("SPHEROID[\"x\",\"1\",0.5]"),
+ ParsingException); // major axis not number
+ EXPECT_THROW(WKTParser().createFromWKT("SPHEROID[\"x\",1,\"0.5\"]"),
+ ParsingException); // reverse flatting not number
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(wkt_parse, invalid_DATUM) {
+ EXPECT_NO_THROW(
+ WKTParser().createFromWKT("DATUM[\"x\",SPHEROID[\"x\",1,0.5]]"));
+ EXPECT_THROW(WKTParser().createFromWKT("DATUM[\"x\"]"), ParsingException);
+ EXPECT_THROW(WKTParser().createFromWKT("DATUM[\"x\",FOO[]]"),
+ ParsingException);
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(wkt_parse, invalid_ENSEMBLE) {
+ EXPECT_THROW(WKTParser().createFromWKT("ENSEMBLE[]"), ParsingException);
+ EXPECT_THROW(WKTParser().createFromWKT("ENSEMBLE[\"x\"]"),
+ ParsingException);
+ EXPECT_THROW(WKTParser().createFromWKT(
+ "ENSEMBLE[\"x\",MEMBER[\"vdatum1\"],MEMBER[\"vdatum2\"]]"),
+ ParsingException);
+ EXPECT_THROW(
+ WKTParser().createFromWKT("ENSEMBLE[\"x\",MEMBER[],MEMBER[\"vdatum2\"],"
+ "ENSEMBLEACCURACY[\"100\"]]"),
+ ParsingException);
+ EXPECT_THROW(
+ WKTParser().createFromWKT("ENSEMBLE[\"x\",MEMBER[\"vdatum1\"],MEMBER["
+ "\"vdatum2\"],ENSEMBLEACCURACY[]]"),
+ ParsingException);
+ EXPECT_THROW(
+ WKTParser().createFromWKT("ENSEMBLE[\"x\",ENSEMBLEACCURACY[\"100\"]]"),
+ ParsingException);
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(wkt_parse, invalid_GEOGCS) {
+ EXPECT_NO_THROW(WKTParser().createFromWKT(
+ "GEOGCS[\"x\",DATUM[\"x\",SPHEROID[\"x\",1,0.5]],PRIMEM[\"x\",0],UNIT["
+ "\"degree\",0.0174532925199433]]"));
+
+ // missing PRIMEM
+ EXPECT_THROW(
+ WKTParser().createFromWKT("GEOGCS[\"x\",DATUM[\"x\",SPHEROID[\"x\",1,0."
+ "5]],UNIT[\"degree\",0.0174532925199433]]"),
+ ParsingException);
+
+ // missing UNIT
+ EXPECT_THROW(
+ WKTParser().createFromWKT(
+ "GEOGCS[\"x\",DATUM[\"x\",SPHEROID[\"x\",1,0.5]],PRIMEM[\"x\",0]]"),
+ ParsingException);
+
+ EXPECT_THROW(WKTParser().createFromWKT("GEOGCS[\"x\"]"), ParsingException);
+ EXPECT_THROW(WKTParser().createFromWKT("GEOGCS[\"x\",FOO[]]"),
+ ParsingException);
+
+ // not enough children for DATUM
+ EXPECT_THROW(
+ WKTParser().createFromWKT("GEOGCS[\"x\",DATUM[\"x\"],PRIMEM[\"x\",0],"
+ "UNIT[\"degree\",0.0174532925199433]]"),
+ ParsingException);
+
+ // not enough children for AUTHORITY
+ EXPECT_THROW(WKTParser().createFromWKT("GEOGCS[\"x\",DATUM[\"x\",SPHEROID["
+ "\"x\",1,0.5]],PRIMEM[\"x\",0],UNIT["
+ "\"degree\",0.0174532925199433],"
+ "AUTHORITY[\"x\"]]"),
+ ParsingException);
+
+ // not enough children for AUTHORITY, but ignored
+ EXPECT_NO_THROW(WKTParser().setStrict(false).createFromWKT(
+ "GEOGCS[\"x\",DATUM[\"x\",SPHEROID[\"x\",1,0.5]],PRIMEM[\"x\",0],UNIT["
+ "\"degree\",0.0174532925199433],AUTHORITY[\"x\"]]"));
+
+ EXPECT_NO_THROW(WKTParser().createFromWKT(
+ "GEOGCS[\"x\",DATUM[\"x\",SPHEROID[\"x\",1,0.5]],PRIMEM[\"x\",0],UNIT["
+ "\"degree\",0.0174532925199433]]"));
+
+ // PRIMEM not numeric
+ EXPECT_THROW(
+ WKTParser().createFromWKT(
+ "GEOGCS[\"x\",DATUM[\"x\",SPHEROID[\"x\",1,0."
+ "5]],PRIMEM[\"x\",\"a\"],UNIT[\"degree\",0.0174532925199433]]"),
+ ParsingException);
+
+ // not enough children for PRIMEM
+ EXPECT_THROW(WKTParser().createFromWKT("GEOGCS[\"x\",DATUM[\"x\",SPHEROID["
+ "\"x\",1,0.5]],PRIMEM[\"x\"],UNIT["
+ "\"degree\",0.0174532925199433]]"),
+ ParsingException);
+
+ EXPECT_NO_THROW(WKTParser().createFromWKT(
+ "GEOGCS[\"x\",DATUM[\"x\",SPHEROID[\"x\",1,0.5]],PRIMEM[\"x\",0],UNIT["
+ "\"degree\",0.0174532925199433],AXIS[\"latitude\","
+ "NORTH],AXIS[\"longitude\",EAST]]"));
+
+ // one axis only
+ EXPECT_THROW(WKTParser().createFromWKT(
+ "GEOGCS[\"x\",DATUM[\"x\",SPHEROID[\"x\",1,0."
+ "5]],PRIMEM[\"x\",0],UNIT[\"degree\",0.0174532925199433],"
+ "AXIS[\"latitude\",NORTH]]"),
+ ParsingException);
+
+ // invalid axis
+ EXPECT_THROW(WKTParser().createFromWKT("GEOGCS[\"x\",DATUM[\"x\",SPHEROID["
+ "\"x\",1,0.5]],PRIMEM[\"x\",0],UNIT["
+ "\"degree\",0.0174532925199433],"
+ "AXIS[\"latitude\","
+ "NORTH],AXIS[\"longitude\"]]"),
+ ParsingException);
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(wkt_parse, invalid_UNIT) {
+ std::string startWKT("GEODCRS[\"x\",DATUM[\"x\",SPHEROID[\"x\",1,0.5]],CS["
+ "ellipsoidal,2],AXIS[\"latitude\",north],AXIS["
+ "\"longitude\",east,");
+
+ EXPECT_NO_THROW(WKTParser().createFromWKT(
+ startWKT + "UNIT[\"degree\",0.0174532925199433]]]"));
+
+ // not enough children
+ EXPECT_THROW(WKTParser().createFromWKT(startWKT + "UNIT[\"x\"]]]"),
+ ParsingException);
+
+ // invalid conversion factor
+ EXPECT_THROW(
+ WKTParser().createFromWKT(startWKT + "UNIT[\"x\",\"invalid\"]]]"),
+ ParsingException);
+
+ // invalid ID
+ EXPECT_THROW(
+ WKTParser().createFromWKT(startWKT + "UNIT[\"x\",1,ID[\"x\"]]]]"),
+ ParsingException);
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(wkt_parse, invalid_GEOCCS) {
+ EXPECT_NO_THROW(
+ WKTParser().createFromWKT("GEOCCS[\"x\",DATUM[\"x\",SPHEROID[\"x\",1,0."
+ "5]],PRIMEM[\"x\",0],UNIT[\"metre\",1]]"));
+
+ // missing PRIMEM
+ EXPECT_THROW(WKTParser().createFromWKT("GEOCCS[\"x\",DATUM[\"x\",SPHEROID["
+ "\"x\",1,0.5]],UNIT[\"metre\",1]]"),
+ ParsingException);
+
+ // missing UNIT
+ EXPECT_THROW(
+ WKTParser().createFromWKT(
+ "GEOCCS[\"x\",DATUM[\"x\",SPHEROID[\"x\",1,0.5]],PRIMEM[\"x\",0]]"),
+ ParsingException);
+
+ // ellipsoidal CS is invalid in a GEOCCS
+ EXPECT_THROW(WKTParser().createFromWKT("GEOCCS[\"x\",DATUM[\"x\",SPHEROID["
+ "\"x\",1,0.5]],PRIMEM[\"x\",0],UNIT["
+ "\"degree\",0.0174532925199433],"
+ "AXIS[\"latitude\","
+ "NORTH],AXIS[\"longitude\",EAST]]"),
+ ParsingException);
+
+ // 3 axis required
+ EXPECT_THROW(WKTParser().createFromWKT(
+ "GEOCCS[\"x\",DATUM[\"x\",SPHEROID[\"x\",1,0.5]],PRIMEM["
+ "\"x\",0],UNIT[\"metre\",1],AXIS["
+ "\"Geocentric X\",OTHER],AXIS[\"Geocentric Y\",OTHER]]"),
+ ParsingException);
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(wkt_parse, invalid_CS_of_GEODCRS) {
+
+ std::string startWKT("GEODCRS[\"x\",DATUM[\"x\",SPHEROID[\"x\",1,0.5]]");
+
+ // missing CS
+ EXPECT_THROW(WKTParser().createFromWKT(startWKT + "]"), ParsingException);
+
+ // CS: not enough children
+ EXPECT_THROW(WKTParser().createFromWKT(startWKT + ",CS[x]]"),
+ ParsingException);
+
+ // CS: invalid type
+ EXPECT_THROW(WKTParser().createFromWKT(startWKT + ",CS[x,2]]"),
+ ParsingException);
+
+ // CS: invalid number of axis
+ EXPECT_THROW(WKTParser().createFromWKT(startWKT + ",CS[ellipsoidal,0]]"),
+ ParsingException);
+
+ // CS: number of axis is not a number
+ EXPECT_THROW(
+ WKTParser().createFromWKT(startWKT + ",CS[ellipsoidal,\"x\"]]"),
+ ParsingException);
+
+ // CS: invalid CS type
+ EXPECT_THROW(WKTParser().createFromWKT(startWKT +
+ ",CS[invalid,2],AXIS[\"latitude\","
+ "north],AXIS[\"longitude\",east]]"),
+ ParsingException);
+
+ // CS: OK
+ EXPECT_NO_THROW(WKTParser().createFromWKT(
+ startWKT + ",CS[ellipsoidal,2],AXIS[\"latitude\",north],AXIS["
+ "\"longitude\",east]]"));
+
+ // CS: Cartesian with 2 axis unexpected
+ EXPECT_THROW(WKTParser().createFromWKT(startWKT +
+ ",CS[Cartesian,2],AXIS[\"latitude\","
+ "north],AXIS[\"longitude\",east]]"),
+ ParsingException);
+
+ // CS: missing axis
+ EXPECT_THROW(WKTParser().createFromWKT(
+ startWKT + ",CS[ellipsoidal,2],AXIS[\"latitude\",north]]"),
+ ParsingException);
+
+ // not enough children in AXIS
+ EXPECT_THROW(
+ WKTParser().createFromWKT(
+ startWKT +
+ ",CS[ellipsoidal,2],AXIS[\"latitude\",north],AXIS[\"longitude\"]]"),
+ ParsingException);
+
+ // not enough children in ORDER
+ EXPECT_THROW(WKTParser().createFromWKT(
+ startWKT +
+ ",CS[ellipsoidal,2],AXIS[\"latitude\",north,ORDER[]],AXIS["
+ "\"longitude\",east]]"),
+ ParsingException);
+
+ // invalid value in ORDER
+ EXPECT_THROW(
+ WKTParser().createFromWKT(
+ startWKT +
+ ",CS[ellipsoidal,2],AXIS[\"latitude\",north,ORDER[\"x\"]],AXIS["
+ "\"longitude\",east]]"),
+ ParsingException);
+
+ // unexpected ORDER value
+ EXPECT_THROW(
+ WKTParser().createFromWKT(
+ startWKT +
+ ",CS[ellipsoidal,2],AXIS[\"latitude\",north,ORDER[2]],AXIS["
+ "\"longitude\",east]]"),
+ ParsingException);
+
+ // Invalid CS type
+ EXPECT_THROW(WKTParser().createFromWKT(
+ startWKT +
+ ",CS[vertical,1],\n"
+ " AXIS[\"gravity-related height (H)\",up],\n"
+ " UNIT[\"metre\",1]]"),
+ ParsingException);
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(wkt_parse, invalid_CS_of_GEOGRAPHICCRS) {
+ // A GeographicCRS must have an ellipsoidal CS
+ EXPECT_THROW(WKTParser().createFromWKT(
+ "GEOGRAPHICCRS[\"x\",DATUM[\"x\",SPHEROID[\"x\",1,0.5]],"
+ "CS[Cartesian,3],AXIS[\"(X)\",geocentricX],AXIS[\"(Y)\","
+ "geocentricY],AXIS[\"(Z)\",geocentricZ]]"),
+ ParsingException);
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(wkt_parse, invalid_DYNAMIC) {
+ std::string prefix("GEOGCRS[\"WGS 84 (G1762)\",");
+ std::string suffix(
+ "TRF[\"World Geodetic System 1984 (G1762)\","
+ "ELLIPSOID[\"WGS 84\",6378137,298.257223563]],"
+ "CS[ellipsoidal,3],"
+ " AXIS[\"(lat)\",north,ANGLEUNIT[\"degree\",0.0174532925199433]],"
+ " AXIS[\"(lon)\",east,ANGLEUNIT[\"degree\",0.0174532925199433]],"
+ " AXIS[\"ellipsoidal height (h)\",up,LENGTHUNIT[\"metre\",1.0]]"
+ "]");
+
+ EXPECT_NO_THROW(WKTParser().createFromWKT(
+ prefix + "DYNAMIC[FRAMEEPOCH[2015]]," + suffix));
+
+ EXPECT_THROW(WKTParser().createFromWKT(prefix + "DYNAMIC[]," + suffix),
+ ParsingException);
+ EXPECT_THROW(
+ WKTParser().createFromWKT(prefix + "DYNAMIC[FRAMEEPOCH[]]," + suffix),
+ ParsingException);
+ EXPECT_THROW(WKTParser().createFromWKT(
+ prefix + "DYNAMIC[FRAMEEPOCH[\"invalid\"]]," + suffix),
+ ParsingException);
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(wkt_parse, invalid_PROJCRS) {
+ // missing BASEGEODCRS
+ EXPECT_THROW(
+ WKTParser().createFromWKT("PROJCRS[\"WGS 84 / UTM zone 31N\"]"),
+ ParsingException);
+
+ std::string startWKT("PROJCRS[\"WGS 84 / UTM zone 31N\",BASEGEOGCRS[\"WGS "
+ "84\",DATUM[\"WGS_1984\",ELLIPSOID[\"WGS "
+ "84\",6378137,298.257223563]],UNIT[\"degree\",0."
+ "0174532925199433]]");
+
+ // missing CONVERSION
+ EXPECT_THROW(WKTParser().createFromWKT(startWKT + "]"), ParsingException);
+
+ // not enough children in CONVERSION
+ EXPECT_THROW(WKTParser().createFromWKT(startWKT + ",CONVERSION[\"x\"]]"),
+ ParsingException);
+
+ // not enough children in METHOD
+ EXPECT_THROW(
+ WKTParser().createFromWKT(startWKT + ",CONVERSION[\"x\",METHOD[]]]"),
+ ParsingException);
+
+ // not enough children in PARAMETER
+ EXPECT_THROW(
+ WKTParser().createFromWKT(
+ startWKT + ",CONVERSION[\"x\",METHOD[\"y\"],PARAMETER[\"z\"]]]"),
+ ParsingException);
+
+ // non numeric value for PARAMETER
+ EXPECT_THROW(
+ WKTParser().createFromWKT(
+ startWKT +
+ ",CONVERSION[\"x\",METHOD[\"y\"],PARAMETER[\"z\",\"foo\"]]]"),
+ ParsingException);
+
+ // missing CS
+ EXPECT_THROW(WKTParser().createFromWKT(startWKT +
+ ",CONVERSION[\"x\",METHOD[\"y\"]]]"),
+ ParsingException);
+
+ // CS is not Cartesian
+ EXPECT_THROW(WKTParser().createFromWKT(
+ startWKT + ",CONVERSION[\"x\",METHOD[\"y\"]],CS["
+ "ellipsoidal,2],AXIS[\"latitude\",north],AXIS["
+ "\"longitude\",east]]"),
+ ParsingException);
+
+ // not enough children in MERIDIAN
+ EXPECT_THROW(WKTParser().createFromWKT(
+ startWKT + ",CONVERSION[\"x\",METHOD[\"y\"]],CS["
+ "Cartesian,2],AXIS[\"easting (X)\",south,"
+ "MERIDIAN[90]],AXIS["
+ "\"northing (Y)\",south]]"),
+ ParsingException);
+
+ // non numeric angle value for MERIDIAN
+ EXPECT_THROW(
+ WKTParser().createFromWKT(
+ startWKT +
+ ",CONVERSION[\"x\",METHOD[\"y\"]],CS["
+ "Cartesian,2],AXIS[\"easting (X)\",south,"
+ "MERIDIAN[\"x\",UNIT[\"degree\",0.0174532925199433]]],AXIS["
+ "\"northing (Y)\",south]]"),
+ ParsingException);
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(wkt_parse, invalid_PROJCS) {
+
+ std::string startWKT(
+ "PROJCS[\"WGS 84 / UTM zone 31N\",\n"
+ " GEOGCS[\"WGS 84\",\n"
+ " DATUM[\"WGS_1984\",\n"
+ " SPHEROID[\"WGS 84\",6378137,298.257223563,\n"
+ " AUTHORITY[\"EPSG\",\"7030\"]],\n"
+ " AUTHORITY[\"EPSG\",\"6326\"]],\n"
+ " PRIMEM[\"x\",0],\n"
+ " UNIT[\"degree\",0.0174532925199433,\n"
+ " AUTHORITY[\"EPSG\",9122]],\n"
+ " AXIS[\"latitude\",NORTH],\n"
+ " AXIS[\"longitude\",EAST],\n"
+ " AUTHORITY[\"EPSG\",\"4326\"]]\n");
+
+ // missing PROJECTION
+ EXPECT_THROW(WKTParser().createFromWKT(startWKT + "]"), ParsingException);
+
+ // not enough children in PROJECTION
+ EXPECT_THROW(WKTParser().createFromWKT(startWKT +
+ ",PROJECTION[],UNIT[\"metre\",1]]"),
+ ParsingException);
+
+ // not enough children in PARAMETER
+ EXPECT_THROW(WKTParser().createFromWKT(
+ startWKT +
+ ",PROJECTION[\"x\"],PARAMETER[\"z\"],UNIT[\"metre\",1]]"),
+ ParsingException);
+
+ // not enough children in PARAMETER
+ EXPECT_THROW(
+ WKTParser().createFromWKT(
+ startWKT +
+ ",PROJECTION[\"x\"],PARAMETER[\"z\",\"foo\"],UNIT[\"metre\",1]]"),
+ ParsingException);
+
+ EXPECT_NO_THROW(WKTParser().createFromWKT(
+ startWKT + ",PROJECTION[\"x\"],UNIT[\"metre\",1]]"));
+
+ // missing UNIT
+ EXPECT_THROW(WKTParser().createFromWKT(startWKT + ",PROJECTION[\"x\"]]"),
+ ParsingException);
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(wkt_parse, invalid_VERTCRS) {
+
+ // missing VDATUM
+ EXPECT_THROW(WKTParser().createFromWKT(
+ "VERTCRS[\"foo\",CS[vertical,1],AXIS[\"x\",up]]"),
+ ParsingException);
+
+ // missing CS
+ EXPECT_THROW(WKTParser().createFromWKT("VERTCRS[\"foo\",VDATUM[\"bar\"]]"),
+ ParsingException);
+
+ // CS is not of type vertical
+ EXPECT_THROW(WKTParser().createFromWKT("VERTCRS[\"foo\",VDATUM[\"bar\"],CS["
+ "ellipsoidal,2],AXIS[\"latitude\","
+ "north],AXIS["
+ "\"longitude\",east]]"),
+ ParsingException);
+
+ // verticalCS should have only 1 axis
+ EXPECT_THROW(
+ WKTParser().createFromWKT("VERTCRS[\"foo\",VDATUM[\"bar\"],CS[vertical,"
+ "2],AXIS[\"latitude\",north],AXIS["
+ "\"longitude\",east]]"),
+ ParsingException);
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(wkt_parse, invalid_VERT_CS) {
+
+ EXPECT_NO_THROW(WKTParser().createFromWKT(
+ "VERT_CS[\"x\",VERT_DATUM[\"y\",2005],UNIT[\"metre\",1]]"));
+
+ // Missing VERT_DATUM
+ EXPECT_THROW(WKTParser().createFromWKT("VERT_CS[\"x\",UNIT[\"metre\",1]]"),
+ ParsingException);
+
+ // Missing UNIT
+ EXPECT_THROW(
+ WKTParser().createFromWKT("VERT_CS[\"x\",VERT_DATUM[\"y\",2005]]"),
+ ParsingException);
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(wkt_parse, invalid_COORDINATEOPERATION) {
+
+ std::string src_wkt;
+ {
+ auto formatter = WKTFormatter::create();
+ formatter->setOutputId(false);
+ src_wkt = GeographicCRS::EPSG_4326->exportToWKT(formatter.get());
+ }
+
+ std::string dst_wkt;
+ {
+ auto formatter = WKTFormatter::create();
+ formatter->setOutputId(false);
+ dst_wkt = GeographicCRS::EPSG_4807->exportToWKT(formatter.get());
+ }
+
+ std::string interpolation_wkt;
+ {
+ auto formatter = WKTFormatter::create();
+ formatter->setOutputId(false);
+ interpolation_wkt =
+ GeographicCRS::EPSG_4979->exportToWKT(formatter.get());
+ }
+
+ // Valid
+ {
+ auto wkt = "COORDINATEOPERATION[\"transformationName\",\n"
+ " SOURCECRS[" +
+ src_wkt + "],\n"
+ " TARGETCRS[" +
+ dst_wkt + "],\n"
+ " METHOD[\"operationMethodName\"],\n"
+ " PARAMETERFILE[\"paramName\",\"foo.bin\"]]";
+
+ EXPECT_NO_THROW(WKTParser().createFromWKT(wkt));
+ }
+
+ // Missing SOURCECRS
+ {
+ auto wkt = "COORDINATEOPERATION[\"transformationName\",\n"
+ " TARGETCRS[" +
+ dst_wkt + "],\n"
+ " METHOD[\"operationMethodName\"],\n"
+ " PARAMETERFILE[\"paramName\",\"foo.bin\"]]";
+
+ EXPECT_THROW(WKTParser().createFromWKT(wkt), ParsingException);
+ }
+
+ // Invalid content in SOURCECRS
+ {
+ auto wkt = "COORDINATEOPERATION[\"transformationName\",\n"
+ " SOURCECRS[FOO],\n"
+ " TARGETCRS[" +
+ dst_wkt + "],\n"
+ " METHOD[\"operationMethodName\"],\n"
+ " PARAMETERFILE[\"paramName\",\"foo.bin\"]]";
+
+ EXPECT_THROW(WKTParser().createFromWKT(wkt), ParsingException);
+ }
+
+ // Missing TARGETCRS
+ {
+ auto wkt = "COORDINATEOPERATION[\"transformationName\",\n"
+ " SOURCECRS[" +
+ src_wkt + "],\n"
+ " METHOD[\"operationMethodName\"],\n"
+ " PARAMETERFILE[\"paramName\",\"foo.bin\"]]";
+
+ EXPECT_THROW(WKTParser().createFromWKT(wkt), ParsingException);
+ }
+
+ // Invalid content in TARGETCRS
+ {
+ auto wkt = "COORDINATEOPERATION[\"transformationName\",\n"
+ " SOURCECRS[" +
+ src_wkt + "],\n"
+ " TARGETCRS[FOO],\n"
+ " METHOD[\"operationMethodName\"],\n"
+ " PARAMETERFILE[\"paramName\",\"foo.bin\"]]";
+
+ EXPECT_THROW(WKTParser().createFromWKT(wkt), ParsingException);
+ }
+
+ // Missing METHOD
+ {
+ auto wkt = "COORDINATEOPERATION[\"transformationName\",\n"
+ " SOURCECRS[" +
+ src_wkt + "],\n"
+ " TARGETCRS[" +
+ dst_wkt + "]]";
+
+ EXPECT_THROW(WKTParser().createFromWKT(wkt), ParsingException);
+ }
+
+ // Invalid METHOD
+ {
+ auto wkt = "COORDINATEOPERATION[\"transformationName\",\n"
+ " SOURCECRS[" +
+ src_wkt + "],\n"
+ " TARGETCRS[" +
+ dst_wkt + "],\n"
+ " METHOD[],\n"
+ " PARAMETERFILE[\"paramName\",\"foo.bin\"]]";
+
+ EXPECT_THROW(WKTParser().createFromWKT(wkt), ParsingException);
+ }
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(wkt_parse, invalid_CONCATENATEDOPERATION) {
+
+ // No STEP
+ EXPECT_THROW(WKTParser().createFromWKT("CONCATENATEDOPERATION[\"name\"]"),
+ ParsingException);
+
+ auto transf_1 = Transformation::create(
+ PropertyMap().set(IdentifiedObject::NAME_KEY, "transf_1"),
+ nn_static_pointer_cast<CRS>(GeographicCRS::EPSG_4326),
+ nn_static_pointer_cast<CRS>(GeographicCRS::EPSG_4807), nullptr,
+ PropertyMap().set(IdentifiedObject::NAME_KEY, "operationMethodName"),
+ std::vector<OperationParameterNNPtr>{OperationParameter::create(
+ PropertyMap().set(IdentifiedObject::NAME_KEY, "paramName"))},
+ std::vector<ParameterValueNNPtr>{
+ ParameterValue::createFilename("foo.bin")},
+ std::vector<PositionalAccuracyNNPtr>());
+
+ // One single STEP
+ {
+ auto wkt =
+ "CONCATENATEDOPERATION[\"name\",\n"
+ " SOURCECRS[" +
+ transf_1->sourceCRS()->exportToWKT(WKTFormatter::create().get()) +
+ "],\n"
+ " TARGETCRS[" +
+ transf_1->targetCRS()->exportToWKT(WKTFormatter::create().get()) +
+ "],\n"
+ " STEP[" +
+ transf_1->exportToWKT(WKTFormatter::create().get()) +
+ "],\n"
+ " ID[\"codeSpace\",\"code\"],\n"
+ " REMARK[\"my remarks\"]]";
+
+ EXPECT_THROW(WKTParser().createFromWKT(wkt), ParsingException);
+ }
+
+ // empty STEP
+ {
+ auto wkt =
+ "CONCATENATEDOPERATION[\"name\",\n"
+ " SOURCECRS[" +
+ transf_1->sourceCRS()->exportToWKT(WKTFormatter::create().get()) +
+ "],\n"
+ " TARGETCRS[" +
+ transf_1->targetCRS()->exportToWKT(WKTFormatter::create().get()) +
+ "],\n"
+ " STEP[],\n"
+ " STEP[],\n"
+ " ID[\"codeSpace\",\"code\"],\n"
+ " REMARK[\"my remarks\"]]";
+ EXPECT_THROW(WKTParser().createFromWKT(wkt), ParsingException);
+ }
+
+ // Invalid content in STEP
+ {
+ auto wkt =
+ "CONCATENATEDOPERATION[\"name\",\n"
+ " SOURCECRS[" +
+ transf_1->sourceCRS()->exportToWKT(WKTFormatter::create().get()) +
+ "],\n"
+ " TARGETCRS[" +
+ transf_1->targetCRS()->exportToWKT(WKTFormatter::create().get()) +
+ "],\n"
+ " STEP[" +
+ transf_1->sourceCRS()->exportToWKT(WKTFormatter::create().get()) +
+ "],\n"
+ " STEP[" +
+ transf_1->sourceCRS()->exportToWKT(WKTFormatter::create().get()) +
+ "],\n"
+ " ID[\"codeSpace\",\"code\"],\n"
+ " REMARK[\"my remarks\"]]";
+
+ EXPECT_THROW(WKTParser().createFromWKT(wkt), ParsingException);
+ }
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(wkt_parse, invalid_BOUNDCRS) {
+
+ auto projcrs = ProjectedCRS::create(
+ PropertyMap().set(IdentifiedObject::NAME_KEY, "my PROJCRS"),
+ GeographicCRS::create(
+ PropertyMap().set(IdentifiedObject::NAME_KEY, "my GEOGCRS"),
+ GeodeticReferenceFrame::EPSG_6326,
+ EllipsoidalCS::createLatitudeLongitude(UnitOfMeasure::DEGREE)),
+ Conversion::createUTM(PropertyMap(), 31, true),
+ CartesianCS::createEastingNorthing(UnitOfMeasure::METRE));
+
+ EXPECT_NO_THROW(WKTParser().createFromWKT(
+ "BOUNDCRS[SOURCECRS[" +
+ projcrs->exportToWKT(WKTFormatter::create().get()) + "],\n" +
+ "TARGETCRS[" +
+ GeographicCRS::EPSG_4326->exportToWKT(WKTFormatter::create().get()) +
+ "],\n"
+ " ABRIDGEDTRANSFORMATION[\"foo\",\n"
+ " METHOD[\"bar\"]]]"));
+
+ // Missing SOURCECRS
+ EXPECT_THROW(
+ WKTParser().createFromWKT("BOUNDCRS[TARGETCRS[" +
+ GeographicCRS::EPSG_4326->exportToWKT(
+ WKTFormatter::create().get()) +
+ "],\n"
+ " ABRIDGEDTRANSFORMATION[\"foo\",\n"
+ " METHOD[\"bar\"]]]"),
+ ParsingException);
+
+ // Invalid SOURCECRS
+ EXPECT_THROW(
+ WKTParser().createFromWKT("BOUNDCRS[SOURCECRS[foo], TARGETCRS[" +
+ GeographicCRS::EPSG_4326->exportToWKT(
+ WKTFormatter::create().get()) +
+ "],\n"
+ " ABRIDGEDTRANSFORMATION[\"foo\",\n"
+ " METHOD[\"bar\"]]]"),
+ ParsingException);
+
+ // Missing TARGETCRS
+ EXPECT_THROW(WKTParser().createFromWKT(
+ "BOUNDCRS[SOURCECRS[" +
+ projcrs->exportToWKT(WKTFormatter::create().get()) +
+ "],\n"
+ " ABRIDGEDTRANSFORMATION[\"foo\",\n"
+ " METHOD[\"bar\"]]]"),
+ ParsingException);
+
+ // Invalid TARGETCRS
+ EXPECT_THROW(WKTParser().createFromWKT(
+ "BOUNDCRS[SOURCECRS[" +
+ projcrs->exportToWKT(WKTFormatter::create().get()) +
+ "],TARGETCRS[\"foo\"],\n"
+ " ABRIDGEDTRANSFORMATION[\"foo\",\n"
+ " METHOD[\"bar\"]]]"),
+ ParsingException);
+
+ // Missing ABRIDGEDTRANSFORMATION
+ EXPECT_THROW(WKTParser().createFromWKT(
+ "BOUNDCRS[SOURCECRS[" +
+ projcrs->exportToWKT(WKTFormatter::create().get()) +
+ "],\n" + "TARGETCRS[" +
+ GeographicCRS::EPSG_4326->exportToWKT(
+ WKTFormatter::create().get()) +
+ "]]"),
+ ParsingException);
+
+ // Missing METHOD
+ EXPECT_THROW(WKTParser().createFromWKT(
+ "BOUNDCRS[SOURCECRS[" +
+ projcrs->exportToWKT(WKTFormatter::create().get()) +
+ "],\n" + "TARGETCRS[" +
+ GeographicCRS::EPSG_4326->exportToWKT(
+ WKTFormatter::create().get()) +
+ "],"
+ "ABRIDGEDTRANSFORMATION[\"foo\"]]"),
+ ParsingException);
+
+ // Invalid METHOD
+ EXPECT_THROW(WKTParser().createFromWKT(
+ "BOUNDCRS[SOURCECRS[" +
+ projcrs->exportToWKT(WKTFormatter::create().get()) +
+ "],\n" + "TARGETCRS[" +
+ GeographicCRS::EPSG_4326->exportToWKT(
+ WKTFormatter::create().get()) +
+ "],"
+ "ABRIDGEDTRANSFORMATION[\"foo\",METHOD[]]]"),
+ ParsingException);
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(wkt_parse, invalid_TOWGS84) {
+ EXPECT_THROW(WKTParser().createFromWKT(
+ "GEOGCS[\"WGS 84\","
+ " DATUM[\"WGS_1984\","
+ " SPHEROID[\"WGS 84\",6378137,298.257223563],"
+ " TOWGS84[0]],"
+ " PRIMEM[\"Greenwich\",0],"
+ " UNIT[\"degree\",0.0174532925199433]]"),
+ ParsingException);
+
+ EXPECT_THROW(WKTParser().createFromWKT(
+ "GEOGCS[\"WGS 84\","
+ " DATUM[\"WGS_1984\","
+ " SPHEROID[\"WGS 84\",6378137,298.257223563],"
+ " TOWGS84[0,0,0,0,0,0,\"foo\"]],"
+ " PRIMEM[\"Greenwich\",0],"
+ " UNIT[\"degree\",0.0174532925199433]]"),
+ ParsingException);
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(wkt_parse, invalid_DerivedGeographicCRS) {
+
+ EXPECT_NO_THROW(WKTParser().createFromWKT(
+ "GEODCRS[\"WMO Atlantic Pole\",\n"
+ " BASEGEODCRS[\"WGS 84\",\n"
+ " DATUM[\"World Geodetic System 1984\",\n"
+ " ELLIPSOID[\"WGS 84\",6378137,298.257223563]]],\n"
+ " DERIVINGCONVERSION[\"foo\",\n"
+ " METHOD[\"bar\"]],\n"
+ " CS[ellipsoidal,2],\n"
+ " AXIS[\"latitude\",north],\n"
+ " AXIS[\"longitude\",east]]"));
+
+ // Missing DERIVINGCONVERSION
+ EXPECT_THROW(
+ WKTParser().createFromWKT(
+ "GEODCRS[\"WMO Atlantic Pole\",\n"
+ " BASEGEODCRS[\"WGS 84\",\n"
+ " DATUM[\"World Geodetic System 1984\",\n"
+ " ELLIPSOID[\"WGS 84\",6378137,298.257223563]]],\n"
+ " CS[ellipsoidal,2],\n"
+ " AXIS[\"latitude\",north],\n"
+ " AXIS[\"longitude\",east]]"),
+ ParsingException);
+
+ // Missing CS
+ EXPECT_THROW(
+ WKTParser().createFromWKT(
+ "GEODCRS[\"WMO Atlantic Pole\",\n"
+ " BASEGEODCRS[\"WGS 84\",\n"
+ " DATUM[\"World Geodetic System 1984\",\n"
+ " ELLIPSOID[\"WGS 84\",6378137,298.257223563]]],\n"
+ " DERIVINGCONVERSION[\"foo\",\n"
+ " METHOD[\"bar\"]]]"),
+ ParsingException);
+
+ // CS should be ellipsoidal given root node is GEOGCRS
+ EXPECT_THROW(
+ WKTParser().createFromWKT(
+ "GEOGCRS[\"WMO Atlantic Pole\",\n"
+ " BASEGEOGCRS[\"WGS 84\",\n"
+ " DATUM[\"World Geodetic System 1984\",\n"
+ " ELLIPSOID[\"WGS 84\",6378137,298.257223563]]],\n"
+ " DERIVINGCONVERSION[\"foo\",\n"
+ " METHOD[\"bar\"]],\n"
+ " CS[Cartesian,3],\n"
+ " AXIS[\"(X)\",geocentricX],\n"
+ " AXIS[\"(Y)\",geocentricY],\n"
+ " AXIS[\"(Z)\",geocentricZ],\n"
+ " UNIT[\"metre\",1]]"),
+ ParsingException);
+
+ // CS should have 3 axis
+ EXPECT_THROW(
+ WKTParser().createFromWKT(
+ "GEODCRS[\"WMO Atlantic Pole\",\n"
+ " BASEGEODCRS[\"WGS 84\",\n"
+ " DATUM[\"World Geodetic System 1984\",\n"
+ " ELLIPSOID[\"WGS 84\",6378137,298.257223563]]],\n"
+ " DERIVINGCONVERSION[\"foo\",\n"
+ " METHOD[\"bar\"]],\n"
+ " CS[Cartesian,2],\n"
+ " AXIS[\"(X)\",geocentricX],\n"
+ " AXIS[\"(Y)\",geocentricY],\n"
+ " UNIT[\"metre\",1]]"),
+ ParsingException);
+
+ // Invalid CS type
+ EXPECT_THROW(
+ WKTParser().createFromWKT(
+ "GEODCRS[\"WMO Atlantic Pole\",\n"
+ " BASEGEODCRS[\"WGS 84\",\n"
+ " DATUM[\"World Geodetic System 1984\",\n"
+ " ELLIPSOID[\"WGS 84\",6378137,298.257223563]]],\n"
+ " DERIVINGCONVERSION[\"foo\",\n"
+ " METHOD[\"bar\"]],\n"
+ " CS[vertical,1],\n"
+ " AXIS[\"gravity-related height (H)\",up],\n"
+ " UNIT[\"metre\",1]]"),
+ ParsingException);
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(wkt_parse, invalid_TemporalCRS) {
+
+ EXPECT_NO_THROW(
+ WKTParser().createFromWKT("TIMECRS[\"Temporal CRS\",\n"
+ " TDATUM[\"Gregorian calendar\",\n"
+ " TIMEORIGIN[0000-01-01]],\n"
+ " CS[temporal,1],\n"
+ " AXIS[\"time (T)\",future]]"));
+
+ // Missing TDATUM
+ EXPECT_THROW(
+ WKTParser().createFromWKT("TIMECRS[\"Temporal CRS\",\n"
+ " CS[temporal,1],\n"
+ " AXIS[\"time (T)\",future]]"),
+ ParsingException);
+
+ // Missing CS
+ EXPECT_THROW(
+ WKTParser().createFromWKT("TIMECRS[\"Temporal CRS\",\n"
+ " TDATUM[\"Gregorian calendar\",\n"
+ " TIMEORIGIN[0000-01-01]]]"),
+ ParsingException);
+
+ // CS should be temporal
+ EXPECT_THROW(
+ WKTParser().createFromWKT("TIMECRS[\"Temporal CRS\",\n"
+ " TDATUM[\"Gregorian calendar\",\n"
+ " TIMEORIGIN[0000-01-01]],\n"
+ " CS[Cartesian,2],\n"
+ " AXIS[\"(X)\",geocentricX],\n"
+ " AXIS[\"(Y)\",geocentricY],\n"
+ " UNIT[\"metre\",1]]"),
+ ParsingException);
+
+ // CS should have 1 axis
+ EXPECT_THROW(
+ WKTParser().createFromWKT("TIMECRS[\"Temporal CRS\",\n"
+ " TDATUM[\"Gregorian calendar\",\n"
+ " TIMEORIGIN[0000-01-01]],\n"
+ " CS[temporal,2],\n"
+ " AXIS[\"time (T)\",future],\n"
+ " AXIS[\"time2 (T)\",future]]"),
+ ParsingException);
+
+ // CS should have 1 axis
+ EXPECT_THROW(
+ WKTParser().createFromWKT("TIMECRS[\"Temporal CRS\",\n"
+ " TDATUM[\"Gregorian calendar\",\n"
+ " TIMEORIGIN[0000-01-01]],\n"
+ " CS[TemporalDateTime,2],\n"
+ " AXIS[\"time (T)\",future],\n"
+ " AXIS[\"time2 (T)\",future]]"),
+ ParsingException);
+
+ // CS should have 1 axis
+ EXPECT_THROW(
+ WKTParser().createFromWKT("TIMECRS[\"Temporal CRS\",\n"
+ " TDATUM[\"Gregorian calendar\",\n"
+ " TIMEORIGIN[0000-01-01]],\n"
+ " CS[TemporalCount,2],\n"
+ " AXIS[\"time (T)\",future],\n"
+ " AXIS[\"time2 (T)\",future]]"),
+ ParsingException);
+
+ // CS should have 1 axis
+ EXPECT_THROW(
+ WKTParser().createFromWKT("TIMECRS[\"Temporal CRS\",\n"
+ " TDATUM[\"Gregorian calendar\",\n"
+ " TIMEORIGIN[0000-01-01]],\n"
+ " CS[TemporalMeasure,2],\n"
+ " AXIS[\"time (T)\",future],\n"
+ " AXIS[\"time2 (T)\",future]]"),
+ ParsingException);
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(wkt_parse, invalid_EngineeingCRS) {
+
+ EXPECT_NO_THROW(
+ WKTParser().createFromWKT("ENGCRS[\"name\",\n"
+ " EDATUM[\"name\"],\n"
+ " CS[temporal,1],\n"
+ " AXIS[\"time (T)\",future]]"));
+
+ // Missing EDATUM
+ EXPECT_THROW(
+ WKTParser().createFromWKT("ENGCRS[\"name\",\n"
+ " CS[temporal,1],\n"
+ " AXIS[\"time (T)\",future]]"),
+ ParsingException);
+
+ // Missing CS
+ EXPECT_THROW(WKTParser().createFromWKT("ENGCRS[\"name\",\n"
+ " EDATUM[\"name\"]]"),
+ ParsingException);
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(wkt_parse, invalid_LOCAL_CS) {
+
+ EXPECT_THROW(
+ WKTParser().createFromWKT("LOCAL_CS[\"name\",\n"
+ " LOCAL_DATUM[\"name\",1234],\n"
+ " AXIS[\"Geodetic latitude\",NORTH],\n"
+ " AXIS[\"Geodetic longitude\",EAST],\n"
+ " AXIS[\"Ellipsoidal height\",UP]]"),
+ ParsingException);
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(wkt_parse, invalid_ParametricCRS) {
+
+ EXPECT_NO_THROW(
+ WKTParser().createFromWKT("PARAMETRICCRS[\"name\",\n"
+ " PDATUM[\"name\"],\n"
+ " CS[parametric,1],\n"
+ " AXIS[\"time (T)\",future]]"));
+
+ // Missing PDATUM
+ EXPECT_THROW(
+ WKTParser().createFromWKT("PARAMETRICCRS[\"name\",\n"
+ " CS[parametric,1],\n"
+ " AXIS[\"time (T)\",future]]"),
+ ParsingException);
+
+ // Missing CS
+ EXPECT_THROW(WKTParser().createFromWKT("PARAMETRICCRS[\"name\",\n"
+ " PDATUM[\"name\"]]"),
+ ParsingException);
+
+ // Invalid number of axis for CS
+ EXPECT_THROW(
+ WKTParser().createFromWKT("PARAMETRICCRS[\"name\",\n"
+ " PDATUM[\"name\"],\n"
+ " CS[parametric,2],\n"
+ " AXIS[\"time (T)\",future],\n"
+ " AXIS[\"time (T)\",future]]"),
+ ParsingException);
+
+ // Invalid CS type
+ EXPECT_THROW(
+ WKTParser().createFromWKT("PARAMETRICCRS[\"name\",\n"
+ " PDATUM[\"name\"],\n"
+ " CS[temporal,1],\n"
+ " AXIS[\"time (T)\",future]]"),
+ ParsingException);
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(wkt_parse, invalid_DERIVEDPROJCRS) {
+ EXPECT_NO_THROW(WKTParser().createFromWKT(
+ "DERIVEDPROJCRS[\"derived projectedCRS\",\n"
+ " BASEPROJCRS[\"BASEPROJCRS\",\n"
+ " BASEGEOGCRS[\"WGS 84\",\n"
+ " DATUM[\"World Geodetic System 1984\",\n"
+ " ELLIPSOID[\"WGS 84\",6378137,298.257223563,\n"
+ " LENGTHUNIT[\"metre\",1]]]],\n"
+ " CONVERSION[\"unnamed\",\n"
+ " METHOD[\"PROJ unimplemented\"]]],\n"
+ " DERIVINGCONVERSION[\"unnamed\",\n"
+ " METHOD[\"PROJ unimplemented\"]],\n"
+ " CS[Cartesian,2],\n"
+ " AXIS[\"(E)\",east],\n"
+ " AXIS[\"(N)\",north]]"));
+
+ EXPECT_THROW(
+ WKTParser().createFromWKT("DERIVEDPROJCRS[\"derived projectedCRS\",\n"
+ " DERIVINGCONVERSION[\"unnamed\",\n"
+ " METHOD[\"PROJ unimplemented\"]],\n"
+ " CS[Cartesian,2],\n"
+ " AXIS[\"(E)\",east],\n"
+ " AXIS[\"(N)\",north]]"),
+ ParsingException);
+
+ // Missing DERIVINGCONVERSION
+ EXPECT_THROW(
+ WKTParser().createFromWKT(
+ "DERIVEDPROJCRS[\"derived projectedCRS\",\n"
+ " BASEPROJCRS[\"BASEPROJCRS\",\n"
+ " BASEGEOGCRS[\"WGS 84\",\n"
+ " DATUM[\"World Geodetic System 1984\",\n"
+ " ELLIPSOID[\"WGS 84\",6378137,298.257223563,\n"
+ " LENGTHUNIT[\"metre\",1]]]],\n"
+ " CONVERSION[\"unnamed\",\n"
+ " METHOD[\"PROJ unimplemented\"]]],\n"
+ " CS[Cartesian,2],\n"
+ " AXIS[\"(E)\",east],\n"
+ " AXIS[\"(N)\",north]]"),
+ ParsingException);
+
+ // Missing CS
+ EXPECT_THROW(
+ WKTParser().createFromWKT(
+ "DERIVEDPROJCRS[\"derived projectedCRS\",\n"
+ " BASEPROJCRS[\"BASEPROJCRS\",\n"
+ " BASEGEOGCRS[\"WGS 84\",\n"
+ " DATUM[\"World Geodetic System 1984\",\n"
+ " ELLIPSOID[\"WGS 84\",6378137,298.257223563,\n"
+ " LENGTHUNIT[\"metre\",1]]]],\n"
+ " CONVERSION[\"unnamed\",\n"
+ " METHOD[\"PROJ unimplemented\"]]],\n"
+ " DERIVINGCONVERSION[\"unnamed\",\n"
+ " METHOD[\"PROJ unimplemented\"]]]"),
+ ParsingException);
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(wkt_parse, invalid_DerivedVerticalCRS) {
+ EXPECT_NO_THROW(WKTParser().createFromWKT(
+ "VERTCRS[\"Derived vertCRS\",\n"
+ " BASEVERTCRS[\"ODN height\",\n"
+ " VDATUM[\"Ordnance Datum Newlyn\"]],\n"
+ " DERIVINGCONVERSION[\"unnamed\",\n"
+ " METHOD[\"PROJ unimplemented\"]],\n"
+ " CS[vertical,1],\n"
+ " AXIS[\"gravity-related height (H)\",up]]"));
+
+ // Missing DERIVINGCONVERSION
+ EXPECT_THROW(WKTParser().createFromWKT(
+ "VERTCRS[\"Derived vertCRS\",\n"
+ " BASEVERTCRS[\"ODN height\",\n"
+ " VDATUM[\"Ordnance Datum Newlyn\"]],\n"
+ " CS[vertical,1],\n"
+ " AXIS[\"gravity-related height (H)\",up]]"),
+ ParsingException);
+
+ // Missing CS
+ EXPECT_THROW(WKTParser().createFromWKT(
+ "VERTCRS[\"Derived vertCRS\",\n"
+ " BASEVERTCRS[\"ODN height\",\n"
+ " VDATUM[\"Ordnance Datum Newlyn\"]],\n"
+ " DERIVINGCONVERSION[\"unnamed\",\n"
+ " METHOD[\"PROJ unimplemented\"]]]"),
+ ParsingException);
+
+ // Wrong CS type
+ EXPECT_THROW(WKTParser().createFromWKT(
+ "VERTCRS[\"Derived vertCRS\",\n"
+ " BASEVERTCRS[\"ODN height\",\n"
+ " VDATUM[\"Ordnance Datum Newlyn\"]],\n"
+ " DERIVINGCONVERSION[\"unnamed\",\n"
+ " METHOD[\"PROJ unimplemented\"]],\n"
+ " CS[parametric,1],\n"
+ " AXIS[\"gravity-related height (H)\",up]]"),
+ ParsingException);
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(wkt_parse, invalid_DerivedEngineeringCRS) {
+ EXPECT_NO_THROW(
+ WKTParser().createFromWKT("ENGCRS[\"Derived EngineeringCRS\",\n"
+ " BASEENGCRS[\"Engineering CRS\",\n"
+ " EDATUM[\"Engineering datum\"]],\n"
+ " DERIVINGCONVERSION[\"unnamed\",\n"
+ " METHOD[\"PROJ unimplemented\"]],\n"
+ " CS[Cartesian,2],\n"
+ " AXIS[\"(E)\",east],\n"
+ " AXIS[\"(N)\",north],\n"
+ " LENGTHUNIT[\"metre\",1]]"));
+
+ // Missing DERIVINGCONVERSION
+ EXPECT_THROW(
+ WKTParser().createFromWKT("ENGCRS[\"Derived EngineeringCRS\",\n"
+ " BASEENGCRS[\"Engineering CRS\",\n"
+ " EDATUM[\"Engineering datum\"]],\n"
+ " CS[Cartesian,2],\n"
+ " AXIS[\"(E)\",east],\n"
+ " AXIS[\"(N)\",north],\n"
+ " LENGTHUNIT[\"metre\",1]]"),
+ ParsingException);
+
+ // Missing CS
+ EXPECT_THROW(
+ WKTParser().createFromWKT("ENGCRS[\"Derived EngineeringCRS\",\n"
+ " BASEENGCRS[\"Engineering CRS\",\n"
+ " EDATUM[\"Engineering datum\"]],\n"
+ " DERIVINGCONVERSION[\"unnamed\",\n"
+ " METHOD[\"PROJ unimplemented\"]]]"),
+ ParsingException);
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(wkt_parse, invalid_DerivedParametricCRS) {
+ EXPECT_NO_THROW(WKTParser().createFromWKT(
+ "PARAMETRICCRS[\"Derived ParametricCRS\",\n"
+ " BASEPARAMCRS[\"Parametric CRS\",\n"
+ " PDATUM[\"Parametric datum\"]],\n"
+ " DERIVINGCONVERSION[\"unnamed\",\n"
+ " METHOD[\"PROJ unimplemented\"]],\n"
+ " CS[parametric,1],\n"
+ " AXIS[\"pressure (hPa)\",up,\n"
+ " PARAMETRICUNIT[\"HectoPascal\",100]]]"));
+
+ // Missing DERIVINGCONVERSION
+ EXPECT_THROW(WKTParser().createFromWKT(
+ "PARAMETRICCRS[\"Derived ParametricCRS\",\n"
+ " BASEPARAMCRS[\"Parametric CRS\",\n"
+ " PDATUM[\"Parametric datum\"]],\n"
+ " CS[parametric,1],\n"
+ " AXIS[\"pressure (hPa)\",up,\n"
+ " PARAMETRICUNIT[\"HectoPascal\",100]]]"),
+ ParsingException);
+
+ // Missing CS
+ EXPECT_THROW(
+ WKTParser().createFromWKT("PARAMETRICCRS[\"Derived ParametricCRS\",\n"
+ " BASEPARAMCRS[\"Parametric CRS\",\n"
+ " PDATUM[\"Parametric datum\"]],\n"
+ " DERIVINGCONVERSION[\"unnamed\",\n"
+ " METHOD[\"PROJ unimplemented\"]]]"),
+ ParsingException);
+
+ // Wrong CS type
+ EXPECT_THROW(
+ WKTParser().createFromWKT("PARAMETRICCRS[\"Derived ParametricCRS\",\n"
+ " BASEPARAMCRS[\"Parametric CRS\",\n"
+ " PDATUM[\"Parametric datum\"]],\n"
+ " DERIVINGCONVERSION[\"unnamed\",\n"
+ " METHOD[\"PROJ unimplemented\"]],\n"
+ " CS[TemporalDateTime,1],\n"
+ " AXIS[\"time (T)\",future]]"),
+ ParsingException);
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(wkt_parse, invalid_DerivedTemporalCRS) {
+ EXPECT_NO_THROW(WKTParser().createFromWKT(
+ "TIMECRS[\"Derived TemporalCRS\",\n"
+ " BASETIMECRS[\"Temporal CRS\",\n"
+ " TDATUM[\"Gregorian calendar\",\n"
+ " CALENDAR[\"proleptic Gregorian\"],\n"
+ " TIMEORIGIN[0000-01-01]]],\n"
+ " DERIVINGCONVERSION[\"unnamed\",\n"
+ " METHOD[\"PROJ unimplemented\"]],\n"
+ " CS[TemporalDateTime,1],\n"
+ " AXIS[\"time (T)\",future]]"));
+
+ // Missing DERIVINGCONVERSION
+ EXPECT_THROW(WKTParser().createFromWKT(
+ "TIMECRS[\"Derived TemporalCRS\",\n"
+ " BASETIMECRS[\"Temporal CRS\",\n"
+ " TDATUM[\"Gregorian calendar\",\n"
+ " CALENDAR[\"proleptic Gregorian\"],\n"
+ " TIMEORIGIN[0000-01-01]]],\n"
+ " CS[TemporalDateTime,1],\n"
+ " AXIS[\"time (T)\",future]]"),
+ ParsingException);
+
+ // Missing CS
+ EXPECT_THROW(WKTParser().createFromWKT(
+ "TIMECRS[\"Derived TemporalCRS\",\n"
+ " BASETIMECRS[\"Temporal CRS\",\n"
+ " TDATUM[\"Gregorian calendar\",\n"
+ " CALENDAR[\"proleptic Gregorian\"],\n"
+ " TIMEORIGIN[0000-01-01]]],\n"
+ " DERIVINGCONVERSION[\"unnamed\",\n"
+ " METHOD[\"PROJ unimplemented\"]]]"),
+ ParsingException);
+
+ // Wrong CS type
+ EXPECT_THROW(WKTParser().createFromWKT(
+ "TIMECRS[\"Derived TemporalCRS\",\n"
+ " BASETIMECRS[\"Temporal CRS\",\n"
+ " TDATUM[\"Gregorian calendar\",\n"
+ " CALENDAR[\"proleptic Gregorian\"],\n"
+ " TIMEORIGIN[0000-01-01]]],\n"
+ " DERIVINGCONVERSION[\"unnamed\",\n"
+ " METHOD[\"PROJ unimplemented\"]],\n"
+ " CS[parametric,1],\n"
+ " AXIS[\"pressure (hPa)\",up,\n"
+ " PARAMETRICUNIT[\"HectoPascal\",100]]]"),
+ ParsingException);
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(io, projstringformatter) {
+
+ {
+ auto fmt = PROJStringFormatter::create();
+ fmt->addStep("my_proj");
+ EXPECT_EQ(fmt->toString(), "+proj=my_proj");
+ }
+
+ {
+ auto fmt = PROJStringFormatter::create();
+ fmt->addStep("my_proj");
+ fmt->setCurrentStepInverted(true);
+ EXPECT_EQ(fmt->toString(), "+proj=pipeline +step +inv +proj=my_proj");
+ }
+
+ {
+ auto fmt = PROJStringFormatter::create();
+ fmt->addStep("my_proj1");
+ fmt->addStep("my_proj2");
+ EXPECT_EQ(fmt->toString(),
+ "+proj=pipeline +step +proj=my_proj1 +step +proj=my_proj2");
+ }
+
+ {
+ auto fmt = PROJStringFormatter::create();
+ fmt->addStep("my_proj1");
+ fmt->setCurrentStepInverted(true);
+ fmt->addStep("my_proj2");
+ EXPECT_EQ(
+ fmt->toString(),
+ "+proj=pipeline +step +inv +proj=my_proj1 +step +proj=my_proj2");
+ }
+
+ {
+ auto fmt = PROJStringFormatter::create();
+ fmt->startInversion();
+ fmt->addStep("my_proj1");
+ fmt->setCurrentStepInverted(true);
+ fmt->addStep("my_proj2");
+ fmt->stopInversion();
+ EXPECT_EQ(
+ fmt->toString(),
+ "+proj=pipeline +step +inv +proj=my_proj2 +step +proj=my_proj1");
+ }
+
+ {
+ auto fmt = PROJStringFormatter::create();
+ fmt->startInversion();
+ fmt->addStep("my_proj1");
+ fmt->setCurrentStepInverted(true);
+ fmt->startInversion();
+ fmt->addStep("my_proj2");
+ fmt->stopInversion();
+ fmt->stopInversion();
+ EXPECT_EQ(fmt->toString(),
+ "+proj=pipeline +step +proj=my_proj2 +step +proj=my_proj1");
+ }
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(io, projstringformatter_helmert_3_param_noop) {
+ auto fmt = PROJStringFormatter::create();
+ fmt->addStep("helmert");
+ fmt->addParam("x", 0);
+ fmt->addParam("y", 0);
+ fmt->addParam("z", 0);
+ EXPECT_EQ(fmt->toString(), "");
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(io, projstringformatter_helmert_7_param_noop) {
+ auto fmt = PROJStringFormatter::create();
+ fmt->addStep("helmert");
+ fmt->addParam("x", 0);
+ fmt->addParam("y", 0);
+ fmt->addParam("z", 0);
+ fmt->addParam("rx", 0);
+ fmt->addParam("ry", 0);
+ fmt->addParam("rz", 0);
+ fmt->addParam("s", 0);
+ fmt->addParam("convention", "position_vector");
+ EXPECT_EQ(fmt->toString(), "");
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(io, projstringformatter_merge_consecutive_helmert_3_param) {
+ auto fmt = PROJStringFormatter::create();
+ fmt->addStep("helmert");
+ fmt->addParam("x", 10);
+ fmt->addParam("y", 20);
+ fmt->addParam("z", 30);
+ fmt->addStep("helmert");
+ fmt->addParam("x", -1);
+ fmt->addParam("y", -2);
+ fmt->addParam("z", -3);
+ EXPECT_EQ(fmt->toString(), "+proj=helmert +x=9 +y=18 +z=27");
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(io, projstringformatter_merge_consecutive_helmert_3_param_noop) {
+ auto fmt = PROJStringFormatter::create();
+ fmt->addStep("helmert");
+ fmt->addParam("x", 10);
+ fmt->addParam("y", 20);
+ fmt->addParam("z", 30);
+ fmt->addStep("helmert");
+ fmt->addParam("x", -10);
+ fmt->addParam("y", -20);
+ fmt->addParam("z", -30);
+ EXPECT_EQ(fmt->toString(), "");
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(io, projstringformatter_cart_grs80_wgs84) {
+ auto fmt = PROJStringFormatter::create();
+ fmt->addStep("cart");
+ fmt->addParam("ellps", "WGS84");
+ fmt->addStep("cart");
+ fmt->setCurrentStepInverted(true);
+ fmt->addParam("ellps", "GRS80");
+ EXPECT_EQ(fmt->toString(), "");
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(io, projparse_longlat) {
+
+ auto expected = "GEODCRS[\"unknown\",\n"
+ " DATUM[\"World Geodetic System 1984\",\n"
+ " ELLIPSOID[\"WGS 84\",6378137,298.257223563,\n"
+ " LENGTHUNIT[\"metre\",1]],\n"
+ " ID[\"EPSG\",6326]],\n"
+ " PRIMEM[\"Greenwich\",0,\n"
+ " ANGLEUNIT[\"degree\",0.0174532925199433],\n"
+ " ID[\"EPSG\",8901]],\n"
+ " CS[ellipsoidal,2],\n"
+ " AXIS[\"longitude\",east,\n"
+ " ORDER[1],\n"
+ " ANGLEUNIT[\"degree\",0.0174532925199433,\n"
+ " ID[\"EPSG\",9122]]],\n"
+ " AXIS[\"latitude\",north,\n"
+ " ORDER[2],\n"
+ " ANGLEUNIT[\"degree\",0.0174532925199433,\n"
+ " ID[\"EPSG\",9122]]]]";
+ {
+ auto obj = PROJStringParser().createFromPROJString("+proj=longlat");
+ auto crs = nn_dynamic_pointer_cast<GeographicCRS>(obj);
+ ASSERT_TRUE(crs != nullptr);
+ WKTFormatterNNPtr f(WKTFormatter::create());
+ crs->exportToWKT(f.get());
+ EXPECT_EQ(f->toString(), expected);
+ }
+
+ {
+ auto obj = PROJStringParser().createFromPROJString(
+ "+proj=longlat +datum=WGS84");
+ auto crs = nn_dynamic_pointer_cast<GeographicCRS>(obj);
+ ASSERT_TRUE(crs != nullptr);
+ WKTFormatterNNPtr f(WKTFormatter::create());
+ crs->exportToWKT(f.get());
+ EXPECT_EQ(f->toString(), expected);
+ }
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(io, projparse_longlat_datum_NAD83) {
+ auto obj =
+ PROJStringParser().createFromPROJString("+proj=longlat +datum=NAD83");
+ auto crs = nn_dynamic_pointer_cast<GeographicCRS>(obj);
+ ASSERT_TRUE(crs != nullptr);
+ WKTFormatterNNPtr f(WKTFormatter::create());
+ crs->exportToWKT(f.get());
+ EXPECT_EQ(f->toString(),
+ "GEODCRS[\"unknown\",\n"
+ " DATUM[\"North American Datum 1983\",\n"
+ " ELLIPSOID[\"GRS 1980\",6378137,298.257222101,\n"
+ " LENGTHUNIT[\"metre\",1]],\n"
+ " ID[\"EPSG\",6269]],\n"
+ " PRIMEM[\"Greenwich\",0,\n"
+ " ANGLEUNIT[\"degree\",0.0174532925199433],\n"
+ " ID[\"EPSG\",8901]],\n"
+ " CS[ellipsoidal,2],\n"
+ " AXIS[\"longitude\",east,\n"
+ " ORDER[1],\n"
+ " ANGLEUNIT[\"degree\",0.0174532925199433,\n"
+ " ID[\"EPSG\",9122]]],\n"
+ " AXIS[\"latitude\",north,\n"
+ " ORDER[2],\n"
+ " ANGLEUNIT[\"degree\",0.0174532925199433,\n"
+ " ID[\"EPSG\",9122]]]]");
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(io, projparse_longlat_datum_NAD27) {
+ auto obj =
+ PROJStringParser().createFromPROJString("+proj=longlat +datum=NAD27");
+ auto crs = nn_dynamic_pointer_cast<GeographicCRS>(obj);
+ ASSERT_TRUE(crs != nullptr);
+ WKTFormatterNNPtr f(WKTFormatter::create());
+ crs->exportToWKT(f.get());
+ EXPECT_EQ(f->toString(),
+ "GEODCRS[\"unknown\",\n"
+ " DATUM[\"North American Datum 1927\",\n"
+ " ELLIPSOID[\"Clarke 1866\",6378206.4,294.978698213898,\n"
+ " LENGTHUNIT[\"metre\",1]],\n"
+ " ID[\"EPSG\",6267]],\n"
+ " PRIMEM[\"Greenwich\",0,\n"
+ " ANGLEUNIT[\"degree\",0.0174532925199433],\n"
+ " ID[\"EPSG\",8901]],\n"
+ " CS[ellipsoidal,2],\n"
+ " AXIS[\"longitude\",east,\n"
+ " ORDER[1],\n"
+ " ANGLEUNIT[\"degree\",0.0174532925199433,\n"
+ " ID[\"EPSG\",9122]]],\n"
+ " AXIS[\"latitude\",north,\n"
+ " ORDER[2],\n"
+ " ANGLEUNIT[\"degree\",0.0174532925199433,\n"
+ " ID[\"EPSG\",9122]]]]");
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(io, projparse_longlat_datum_other) {
+ auto obj = PROJStringParser().createFromPROJString(
+ "+proj=longlat +datum=carthage");
+ auto crs = nn_dynamic_pointer_cast<GeographicCRS>(obj);
+ ASSERT_TRUE(crs != nullptr);
+ WKTFormatterNNPtr f(WKTFormatter::create());
+ crs->exportToWKT(f.get());
+ EXPECT_EQ(f->toString(),
+ "GEODCRS[\"unknown\",\n"
+ " DATUM[\"Carthage\",\n"
+ " ELLIPSOID[\"Clarke 1880 (IGN)\",6378249.2,293.4660213,\n"
+ " LENGTHUNIT[\"metre\",1]],\n"
+ " ID[\"EPSG\",6223]],\n"
+ " PRIMEM[\"Greenwich\",0,\n"
+ " ANGLEUNIT[\"degree\",0.0174532925199433],\n"
+ " ID[\"EPSG\",8901]],\n"
+ " CS[ellipsoidal,2],\n"
+ " AXIS[\"longitude\",east,\n"
+ " ORDER[1],\n"
+ " ANGLEUNIT[\"degree\",0.0174532925199433,\n"
+ " ID[\"EPSG\",9122]]],\n"
+ " AXIS[\"latitude\",north,\n"
+ " ORDER[2],\n"
+ " ANGLEUNIT[\"degree\",0.0174532925199433,\n"
+ " ID[\"EPSG\",9122]]]]");
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(io, projparse_longlat_ellps_WGS84) {
+ auto obj =
+ PROJStringParser().createFromPROJString("+proj=longlat +ellps=WGS84");
+ auto crs = nn_dynamic_pointer_cast<GeographicCRS>(obj);
+ ASSERT_TRUE(crs != nullptr);
+ WKTFormatterNNPtr f(WKTFormatter::create());
+ f->simulCurNodeHasId();
+ crs->exportToWKT(f.get());
+ auto expected = "GEODCRS[\"unknown\",\n"
+ " DATUM[\"Unknown based on WGS84 ellipsoid\",\n"
+ " ELLIPSOID[\"WGS 84\",6378137,298.257223563,\n"
+ " LENGTHUNIT[\"metre\",1]]],\n"
+ " PRIMEM[\"Greenwich\",0,\n"
+ " ANGLEUNIT[\"degree\",0.0174532925199433]],\n"
+ " CS[ellipsoidal,2],\n"
+ " AXIS[\"longitude\",east,\n"
+ " ORDER[1],\n"
+ " ANGLEUNIT[\"degree\",0.0174532925199433]],\n"
+ " AXIS[\"latitude\",north,\n"
+ " ORDER[2],\n"
+ " ANGLEUNIT[\"degree\",0.0174532925199433]]]";
+ EXPECT_EQ(f->toString(), expected);
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(io, projparse_longlat_ellps_GRS80) {
+ auto obj =
+ PROJStringParser().createFromPROJString("+proj=longlat +ellps=GRS80");
+ auto crs = nn_dynamic_pointer_cast<GeographicCRS>(obj);
+ ASSERT_TRUE(crs != nullptr);
+ WKTFormatterNNPtr f(WKTFormatter::create());
+ f->simulCurNodeHasId();
+ crs->exportToWKT(f.get());
+ auto expected = "GEODCRS[\"unknown\",\n"
+ " DATUM[\"Unknown based on GRS80 ellipsoid\",\n"
+ " ELLIPSOID[\"GRS 1980\",6378137,298.257222101,\n"
+ " LENGTHUNIT[\"metre\",1]]],\n"
+ " PRIMEM[\"Greenwich\",0,\n"
+ " ANGLEUNIT[\"degree\",0.0174532925199433]],\n"
+ " CS[ellipsoidal,2],\n"
+ " AXIS[\"longitude\",east,\n"
+ " ORDER[1],\n"
+ " ANGLEUNIT[\"degree\",0.0174532925199433]],\n"
+ " AXIS[\"latitude\",north,\n"
+ " ORDER[2],\n"
+ " ANGLEUNIT[\"degree\",0.0174532925199433]]]";
+ EXPECT_EQ(f->toString(), expected);
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(io, projparse_longlat_a_b) {
+ auto obj =
+ PROJStringParser().createFromPROJString("+proj=longlat +a=2 +b=1.5");
+ auto crs = nn_dynamic_pointer_cast<GeographicCRS>(obj);
+ ASSERT_TRUE(crs != nullptr);
+ WKTFormatterNNPtr f(WKTFormatter::create());
+ f->simulCurNodeHasId();
+ crs->exportToWKT(f.get());
+ auto expected = "GEODCRS[\"unknown\",\n"
+ " DATUM[\"unknown\",\n"
+ " ELLIPSOID[\"unknown\",2,4,\n"
+ " LENGTHUNIT[\"metre\",1]]],\n"
+ " PRIMEM[\"Reference meridian\",0,\n"
+ " ANGLEUNIT[\"degree\",0.0174532925199433]],\n"
+ " CS[ellipsoidal,2],\n"
+ " AXIS[\"longitude\",east,\n"
+ " ORDER[1],\n"
+ " ANGLEUNIT[\"degree\",0.0174532925199433]],\n"
+ " AXIS[\"latitude\",north,\n"
+ " ORDER[2],\n"
+ " ANGLEUNIT[\"degree\",0.0174532925199433]]]";
+ EXPECT_EQ(f->toString(), expected);
+ EXPECT_EQ(crs->ellipsoid()->celestialBody(), "Non-Earth body");
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(io, projparse_longlat_a_rf_WGS84) {
+ auto obj = PROJStringParser().createFromPROJString(
+ "+proj=longlat +a=6378137 +rf=298.257223563");
+ auto crs = nn_dynamic_pointer_cast<GeographicCRS>(obj);
+ ASSERT_TRUE(crs != nullptr);
+ WKTFormatterNNPtr f(WKTFormatter::create());
+ f->simulCurNodeHasId();
+ crs->exportToWKT(f.get());
+ auto expected = "GEODCRS[\"unknown\",\n"
+ " DATUM[\"unknown\",\n"
+ " ELLIPSOID[\"WGS 84\",6378137,298.257223563,\n"
+ " LENGTHUNIT[\"metre\",1]]],\n"
+ " PRIMEM[\"Greenwich\",0,\n"
+ " ANGLEUNIT[\"degree\",0.0174532925199433]],\n"
+ " CS[ellipsoidal,2],\n"
+ " AXIS[\"longitude\",east,\n"
+ " ORDER[1],\n"
+ " ANGLEUNIT[\"degree\",0.0174532925199433]],\n"
+ " AXIS[\"latitude\",north,\n"
+ " ORDER[2],\n"
+ " ANGLEUNIT[\"degree\",0.0174532925199433]]]";
+ EXPECT_EQ(f->toString(), expected);
+ EXPECT_EQ(crs->ellipsoid()->celestialBody(), Ellipsoid::EARTH);
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(io, projparse_longlat_a_rf) {
+ auto obj =
+ PROJStringParser().createFromPROJString("+proj=longlat +a=2 +rf=4");
+ auto crs = nn_dynamic_pointer_cast<GeographicCRS>(obj);
+ ASSERT_TRUE(crs != nullptr);
+ WKTFormatterNNPtr f(WKTFormatter::create());
+ f->simulCurNodeHasId();
+ crs->exportToWKT(f.get());
+ auto expected = "GEODCRS[\"unknown\",\n"
+ " DATUM[\"unknown\",\n"
+ " ELLIPSOID[\"unknown\",2,4,\n"
+ " LENGTHUNIT[\"metre\",1]]],\n"
+ " PRIMEM[\"Reference meridian\",0,\n"
+ " ANGLEUNIT[\"degree\",0.0174532925199433]],\n"
+ " CS[ellipsoidal,2],\n"
+ " AXIS[\"longitude\",east,\n"
+ " ORDER[1],\n"
+ " ANGLEUNIT[\"degree\",0.0174532925199433]],\n"
+ " AXIS[\"latitude\",north,\n"
+ " ORDER[2],\n"
+ " ANGLEUNIT[\"degree\",0.0174532925199433]]]";
+ EXPECT_EQ(f->toString(), expected);
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(io, projparse_longlat_R) {
+ auto obj = PROJStringParser().createFromPROJString("+proj=longlat +R=2");
+ auto crs = nn_dynamic_pointer_cast<GeographicCRS>(obj);
+ ASSERT_TRUE(crs != nullptr);
+ WKTFormatterNNPtr f(WKTFormatter::create());
+ f->simulCurNodeHasId();
+ crs->exportToWKT(f.get());
+ auto expected = "GEODCRS[\"unknown\",\n"
+ " DATUM[\"unknown\",\n"
+ " ELLIPSOID[\"unknown\",2,0,\n"
+ " LENGTHUNIT[\"metre\",1]]],\n"
+ " PRIMEM[\"Reference meridian\",0,\n"
+ " ANGLEUNIT[\"degree\",0.0174532925199433]],\n"
+ " CS[ellipsoidal,2],\n"
+ " AXIS[\"longitude\",east,\n"
+ " ORDER[1],\n"
+ " ANGLEUNIT[\"degree\",0.0174532925199433]],\n"
+ " AXIS[\"latitude\",north,\n"
+ " ORDER[2],\n"
+ " ANGLEUNIT[\"degree\",0.0174532925199433]]]";
+ EXPECT_EQ(f->toString(), expected);
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(io, projparse_longlat_pm_paris) {
+ auto obj =
+ PROJStringParser().createFromPROJString("+proj=longlat +pm=paris");
+ auto crs = nn_dynamic_pointer_cast<GeographicCRS>(obj);
+ ASSERT_TRUE(crs != nullptr);
+ WKTFormatterNNPtr f(WKTFormatter::create());
+ f->simulCurNodeHasId();
+ crs->exportToWKT(f.get());
+ auto expected = "GEODCRS[\"unknown\",\n"
+ " DATUM[\"Unknown based on WGS84 ellipsoid\",\n"
+ " ELLIPSOID[\"WGS 84\",6378137,298.257223563,\n"
+ " LENGTHUNIT[\"metre\",1]]],\n"
+ " PRIMEM[\"Paris\",2.5969213,\n"
+ " ANGLEUNIT[\"grad\",0.015707963267949]],\n"
+ " CS[ellipsoidal,2],\n"
+ " AXIS[\"longitude\",east,\n"
+ " ORDER[1],\n"
+ " ANGLEUNIT[\"degree\",0.0174532925199433]],\n"
+ " AXIS[\"latitude\",north,\n"
+ " ORDER[2],\n"
+ " ANGLEUNIT[\"degree\",0.0174532925199433]]]";
+
+ EXPECT_EQ(f->toString(), expected);
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(io, projparse_longlat_pm_ferro) {
+ auto obj = PROJStringParser().createFromPROJString(
+ "+proj=longlat +ellps=bessel +pm=ferro");
+ auto crs = nn_dynamic_pointer_cast<GeographicCRS>(obj);
+ ASSERT_TRUE(crs != nullptr);
+ WKTFormatterNNPtr f(WKTFormatter::create());
+ f->simulCurNodeHasId();
+ crs->exportToWKT(f.get());
+ auto expected =
+ "GEODCRS[\"unknown\",\n"
+ " DATUM[\"Unknown based on Bessel 1841 ellipsoid\",\n"
+ " ELLIPSOID[\"Bessel 1841\",6377397.155,299.1528128,\n"
+ " LENGTHUNIT[\"metre\",1]]],\n"
+ " PRIMEM[\"Ferro\",-17.6666666666667,\n"
+ " ANGLEUNIT[\"degree\",0.0174532925199433]],\n"
+ " CS[ellipsoidal,2],\n"
+ " AXIS[\"longitude\",east,\n"
+ " ORDER[1],\n"
+ " ANGLEUNIT[\"degree\",0.0174532925199433]],\n"
+ " AXIS[\"latitude\",north,\n"
+ " ORDER[2],\n"
+ " ANGLEUNIT[\"degree\",0.0174532925199433]]]";
+
+ EXPECT_EQ(f->toString(), expected);
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(io, projparse_longlat_pm_numeric) {
+ auto obj = PROJStringParser().createFromPROJString("+proj=longlat +pm=2.5");
+ auto crs = nn_dynamic_pointer_cast<GeographicCRS>(obj);
+ ASSERT_TRUE(crs != nullptr);
+ WKTFormatterNNPtr f(WKTFormatter::create());
+ f->simulCurNodeHasId();
+ crs->exportToWKT(f.get());
+ auto expected = "GEODCRS[\"unknown\",\n"
+ " DATUM[\"Unknown based on WGS84 ellipsoid\",\n"
+ " ELLIPSOID[\"WGS 84\",6378137,298.257223563,\n"
+ " LENGTHUNIT[\"metre\",1]]],\n"
+ " PRIMEM[\"unknown\",2.5,\n"
+ " ANGLEUNIT[\"degree\",0.0174532925199433]],\n"
+ " CS[ellipsoidal,2],\n"
+ " AXIS[\"longitude\",east,\n"
+ " ORDER[1],\n"
+ " ANGLEUNIT[\"degree\",0.0174532925199433]],\n"
+ " AXIS[\"latitude\",north,\n"
+ " ORDER[2],\n"
+ " ANGLEUNIT[\"degree\",0.0174532925199433]]]";
+
+ EXPECT_EQ(f->toString(), expected);
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(io, projparse_longlat_complex) {
+ std::string input =
+ "+proj=pipeline +step +proj=longlat +ellps=clrk80ign "
+ "+pm=paris +step +proj=unitconvert +xy_in=rad +xy_out=grad +step "
+ "+proj=axisswap +order=2,1";
+ auto obj = PROJStringParser().createFromPROJString(input);
+ auto crs = nn_dynamic_pointer_cast<GeographicCRS>(obj);
+ ASSERT_TRUE(crs != nullptr);
+ EXPECT_EQ(
+ crs->exportToPROJString(
+ PROJStringFormatter::create(PROJStringFormatter::Convention::PROJ_5)
+ .get()),
+ input);
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(io, projparse_longlat_towgs84_3_terms) {
+ auto obj = PROJStringParser().createFromPROJString(
+ "+proj=longlat +ellps=GRS80 +towgs84=1.2,2,3");
+ auto crs = nn_dynamic_pointer_cast<BoundCRS>(obj);
+ ASSERT_TRUE(crs != nullptr);
+ WKTFormatterNNPtr f(WKTFormatter::create());
+ f->simulCurNodeHasId();
+ crs->exportToWKT(f.get());
+
+ auto wkt = f->toString();
+ EXPECT_TRUE(wkt.find("METHOD[\"Geocentric translations") !=
+ std::string::npos)
+ << wkt;
+ EXPECT_TRUE(wkt.find("PARAMETER[\"X-axis translation\",1.2") !=
+ std::string::npos)
+ << wkt;
+ EXPECT_TRUE(wkt.find("PARAMETER[\"Y-axis translation\",2") !=
+ std::string::npos)
+ << wkt;
+ EXPECT_TRUE(wkt.find("PARAMETER[\"Z-axis translation\",3") !=
+ std::string::npos)
+ << wkt;
+
+ EXPECT_EQ(
+ crs->exportToPROJString(
+ PROJStringFormatter::create(PROJStringFormatter::Convention::PROJ_4)
+ .get()),
+ "+proj=longlat +ellps=GRS80 +towgs84=1.2,2,3,0,0,0,0");
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(io, projparse_longlat_towgs84_7_terms) {
+ auto obj = PROJStringParser().createFromPROJString(
+ "+proj=longlat +ellps=GRS80 +towgs84=1.2,2,3,4,5,6,7");
+ auto crs = nn_dynamic_pointer_cast<BoundCRS>(obj);
+ ASSERT_TRUE(crs != nullptr);
+ WKTFormatterNNPtr f(WKTFormatter::create());
+ f->simulCurNodeHasId();
+ crs->exportToWKT(f.get());
+
+ auto wkt = f->toString();
+ EXPECT_TRUE(wkt.find("METHOD[\"Position Vector transformation") !=
+ std::string::npos)
+ << wkt;
+ EXPECT_TRUE(wkt.find("PARAMETER[\"X-axis translation\",1.2") !=
+ std::string::npos)
+ << wkt;
+ EXPECT_TRUE(wkt.find("PARAMETER[\"Y-axis translation\",2") !=
+ std::string::npos)
+ << wkt;
+ EXPECT_TRUE(wkt.find("PARAMETER[\"Z-axis translation\",3") !=
+ std::string::npos)
+ << wkt;
+ EXPECT_TRUE(wkt.find("PARAMETER[\"Scale difference\",1.000007") !=
+ std::string::npos)
+ << wkt;
+
+ EXPECT_EQ(
+ crs->exportToPROJString(
+ PROJStringFormatter::create(PROJStringFormatter::Convention::PROJ_4)
+ .get()),
+ "+proj=longlat +ellps=GRS80 +towgs84=1.2,2,3,4,5,6,7");
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(io, projparse_longlat_nadgrids) {
+ auto obj = PROJStringParser().createFromPROJString(
+ "+proj=longlat +ellps=GRS80 +nadgrids=foo.gsb");
+ auto crs = nn_dynamic_pointer_cast<BoundCRS>(obj);
+ ASSERT_TRUE(crs != nullptr);
+ WKTFormatterNNPtr f(WKTFormatter::create());
+ f->simulCurNodeHasId();
+ crs->exportToWKT(f.get());
+
+ auto wkt = f->toString();
+ EXPECT_TRUE(wkt.find("METHOD[\"NTv2\"") != std::string::npos) << wkt;
+ EXPECT_TRUE(wkt.find("PARAMETERFILE[\"Latitude and longitude difference "
+ "file\",\"foo.gsb\"]") != std::string::npos)
+ << wkt;
+
+ EXPECT_EQ(
+ crs->exportToPROJString(
+ PROJStringFormatter::create(PROJStringFormatter::Convention::PROJ_4)
+ .get()),
+ "+proj=longlat +ellps=GRS80 +nadgrids=foo.gsb");
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(io, projparse_longlat_geoidgrids) {
+ auto obj = PROJStringParser().createFromPROJString(
+ "+proj=longlat +ellps=GRS80 +geoidgrids=foo.gtx");
+ auto crs = nn_dynamic_pointer_cast<CompoundCRS>(obj);
+ ASSERT_TRUE(crs != nullptr);
+ WKTFormatterNNPtr f(WKTFormatter::create());
+ f->simulCurNodeHasId();
+ crs->exportToWKT(f.get());
+
+ auto wkt = f->toString();
+ EXPECT_TRUE(
+ wkt.find(
+ "ABRIDGEDTRANSFORMATION[\"unknown to WGS84 ellipsoidal height\"") !=
+ std::string::npos)
+ << wkt;
+ EXPECT_TRUE(wkt.find("PARAMETERFILE[\"Geoid (height correction) model "
+ "file\",\"foo.gtx\"]") != std::string::npos)
+ << wkt;
+
+ EXPECT_EQ(
+ crs->exportToPROJString(
+ PROJStringFormatter::create(PROJStringFormatter::Convention::PROJ_4)
+ .get()),
+ "+proj=longlat +ellps=GRS80 +geoidgrids=foo.gtx +vunits=m");
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(io, projparse_longlat_geoidgrids_vunits) {
+ auto obj = PROJStringParser().createFromPROJString(
+ "+proj=longlat +ellps=GRS80 +geoidgrids=foo.gtx +vunits=ft");
+ auto crs = nn_dynamic_pointer_cast<CompoundCRS>(obj);
+ ASSERT_TRUE(crs != nullptr);
+ WKTFormatterNNPtr f(WKTFormatter::create());
+ f->simulCurNodeHasId();
+ f->setMultiLine(false);
+ crs->exportToWKT(f.get());
+
+ auto wkt = f->toString();
+ EXPECT_TRUE(wkt.find("AXIS[\"gravity-related height "
+ "(H)\",up,LENGTHUNIT[\"foot\",0.3048]") !=
+ std::string::npos)
+ << wkt;
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(io, projparse_longlat_vunits) {
+ auto obj = PROJStringParser().createFromPROJString(
+ "+proj=longlat +ellps=GRS80 +vunits=ft");
+ auto crs = nn_dynamic_pointer_cast<GeographicCRS>(obj);
+ ASSERT_TRUE(crs != nullptr);
+ WKTFormatterNNPtr f(WKTFormatter::create());
+ f->simulCurNodeHasId();
+ f->setMultiLine(false);
+ crs->exportToWKT(f.get());
+
+ auto wkt = f->toString();
+ EXPECT_TRUE(wkt.find("AXIS[\"ellipsoidal height "
+ "(h)\",up,ORDER[3],LENGTHUNIT[\"foot\",0.3048]") !=
+ std::string::npos)
+ << wkt;
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(io, projparse_vunits) {
+ auto obj = PROJStringParser().createFromPROJString("+vunits=ft");
+ auto crs = nn_dynamic_pointer_cast<VerticalCRS>(obj);
+ ASSERT_TRUE(crs != nullptr);
+ EXPECT_EQ(crs->exportToPROJString(PROJStringFormatter::create().get()),
+ "+vunits=ft");
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(io, projparse_vto_meter) {
+ auto obj = PROJStringParser().createFromPROJString("+vto_meter=2");
+ auto crs = nn_dynamic_pointer_cast<VerticalCRS>(obj);
+ ASSERT_TRUE(crs != nullptr);
+ EXPECT_EQ(crs->exportToPROJString(PROJStringFormatter::create().get()),
+ "+vto_meter=2");
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(io, projparse_longlat_axis_enu) {
+ auto obj = PROJStringParser().createFromPROJString(
+ "+proj=longlat +ellps=GRS80 +axis=enu");
+ auto crs = nn_dynamic_pointer_cast<GeographicCRS>(obj);
+ ASSERT_TRUE(crs != nullptr);
+ WKTFormatterNNPtr f(WKTFormatter::create());
+ f->simulCurNodeHasId();
+ f->setMultiLine(false);
+ crs->exportToWKT(f.get());
+
+ auto wkt = f->toString();
+ EXPECT_TRUE(wkt.find("AXIS[\"longitude\",east,ORDER[1]") !=
+ std::string::npos)
+ << wkt;
+ EXPECT_TRUE(wkt.find("AXIS[\"latitude\",north,ORDER[2]") !=
+ std::string::npos)
+ << wkt;
+
+ EXPECT_EQ(crs->exportToPROJString(PROJStringFormatter::create().get()),
+ "+proj=pipeline +step +proj=longlat +ellps=GRS80 +step "
+ "+proj=unitconvert +xy_in=rad +xy_out=deg");
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(io, projparse_longlat_axis_neu) {
+ auto obj = PROJStringParser().createFromPROJString(
+ "+proj=longlat +ellps=GRS80 +axis=neu");
+ auto crs = nn_dynamic_pointer_cast<GeographicCRS>(obj);
+ ASSERT_TRUE(crs != nullptr);
+ WKTFormatterNNPtr f(WKTFormatter::create());
+ f->simulCurNodeHasId();
+ f->setMultiLine(false);
+ crs->exportToWKT(f.get());
+
+ auto wkt = f->toString();
+ EXPECT_TRUE(wkt.find("AXIS[\"latitude\",north,ORDER[1]") !=
+ std::string::npos)
+ << wkt;
+ EXPECT_TRUE(wkt.find("AXIS[\"longitude\",east,ORDER[2]") !=
+ std::string::npos)
+ << wkt;
+
+ EXPECT_EQ(crs->exportToPROJString(PROJStringFormatter::create().get()),
+ "+proj=pipeline +step +proj=longlat +ellps=GRS80 +step "
+ "+proj=unitconvert +xy_in=rad +xy_out=deg +step +proj=axisswap "
+ "+order=2,1");
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(io, projparse_longlat_axis_swu) {
+ auto obj = PROJStringParser().createFromPROJString(
+ "+proj=longlat +ellps=GRS80 +axis=swu");
+ auto crs = nn_dynamic_pointer_cast<GeographicCRS>(obj);
+ ASSERT_TRUE(crs != nullptr);
+ WKTFormatterNNPtr f(WKTFormatter::create());
+ f->simulCurNodeHasId();
+ f->setMultiLine(false);
+ crs->exportToWKT(f.get());
+
+ auto wkt = f->toString();
+ EXPECT_TRUE(wkt.find("AXIS[\"latitude\",south,ORDER[1]") !=
+ std::string::npos)
+ << wkt;
+ EXPECT_TRUE(wkt.find("AXIS[\"longitude\",west,ORDER[2]") !=
+ std::string::npos)
+ << wkt;
+
+ EXPECT_EQ(crs->exportToPROJString(PROJStringFormatter::create().get()),
+ "+proj=pipeline +step +proj=longlat +ellps=GRS80 +step "
+ "+proj=unitconvert +xy_in=rad +xy_out=deg +step +proj=axisswap "
+ "+order=-2,-1");
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(io, projparse_longlat_unitconvert_deg) {
+ auto obj = PROJStringParser().createFromPROJString(
+ "+proj=pipeline +step +proj=longlat +ellps=GRS80 +step "
+ "+proj=unitconvert +xy_in=rad +xy_out=deg");
+ auto crs = nn_dynamic_pointer_cast<GeographicCRS>(obj);
+ ASSERT_TRUE(crs != nullptr);
+
+ EXPECT_EQ(crs->exportToPROJString(PROJStringFormatter::create().get()),
+ "+proj=pipeline +step +proj=longlat +ellps=GRS80 +step "
+ "+proj=unitconvert +xy_in=rad +xy_out=deg");
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(io, projparse_longlat_unitconvert_grad) {
+ auto obj = PROJStringParser().createFromPROJString(
+ "+proj=pipeline +step +proj=longlat +ellps=GRS80 +step "
+ "+proj=unitconvert +xy_in=rad +xy_out=grad");
+ auto crs = nn_dynamic_pointer_cast<GeographicCRS>(obj);
+ ASSERT_TRUE(crs != nullptr);
+
+ EXPECT_EQ(crs->exportToPROJString(PROJStringFormatter::create().get()),
+ "+proj=pipeline +step +proj=longlat +ellps=GRS80 +step "
+ "+proj=unitconvert +xy_in=rad +xy_out=grad");
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(io, projparse_longlat_unitconvert_rad) {
+ auto obj = PROJStringParser().createFromPROJString(
+ "+proj=pipeline +step +proj=longlat +ellps=GRS80 +step "
+ "+proj=unitconvert +xy_in=rad +xy_out=rad");
+ auto crs = nn_dynamic_pointer_cast<GeographicCRS>(obj);
+ ASSERT_TRUE(crs != nullptr);
+
+ EXPECT_EQ(crs->exportToPROJString(PROJStringFormatter::create().get()),
+ "+proj=pipeline +step +proj=longlat +ellps=GRS80 +step "
+ "+proj=unitconvert +xy_in=rad +xy_out=rad");
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(io, projparse_longlat_axisswap) {
+ for (auto order1 : {"1", "-1", "2", "-2"}) {
+ for (auto order2 : {"1", "-1", "2", "-2"}) {
+ if (std::abs(atoi(order1) * atoi(order2)) == 2 &&
+ !(atoi(order1) == 1 && atoi(order2) == 2)) {
+ auto str = "+proj=pipeline +step +proj=longlat +ellps=GRS80 "
+ "+step +proj=axisswap +order=" +
+ std::string(order1) + "," + order2;
+ auto obj = PROJStringParser().createFromPROJString(str);
+ auto crs = nn_dynamic_pointer_cast<GeographicCRS>(obj);
+ ASSERT_TRUE(crs != nullptr);
+
+ EXPECT_EQ(
+ crs->exportToPROJString(
+ PROJStringFormatter::create().get()),
+ "+proj=pipeline +step +proj=longlat +ellps=GRS80 +step "
+ "+proj=unitconvert +xy_in=rad +xy_out=deg +step "
+ "+proj=axisswap +order=" +
+ std::string(order1) + "," + order2);
+ }
+ }
+ }
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(io, projparse_tmerc) {
+ auto obj = PROJStringParser().createFromPROJString(
+ "+proj=tmerc +x_0=1 +lat_0=1 +k_0=2");
+ auto crs = nn_dynamic_pointer_cast<ProjectedCRS>(obj);
+ ASSERT_TRUE(crs != nullptr);
+ WKTFormatterNNPtr f(WKTFormatter::create());
+ f->simulCurNodeHasId();
+ crs->exportToWKT(f.get());
+ auto expected = "PROJCRS[\"unknown\",\n"
+ " BASEGEODCRS[\"unknown\",\n"
+ " DATUM[\"World Geodetic System 1984\",\n"
+ " ELLIPSOID[\"WGS 84\",6378137,298.257223563,\n"
+ " LENGTHUNIT[\"metre\",1]]],\n"
+ " PRIMEM[\"Greenwich\",0,\n"
+ " ANGLEUNIT[\"degree\",0.0174532925199433]]],\n"
+ " CONVERSION[\"unknown\",\n"
+ " METHOD[\"Transverse Mercator\",\n"
+ " ID[\"EPSG\",9807]],\n"
+ " PARAMETER[\"Latitude of natural origin\",1,\n"
+ " ANGLEUNIT[\"degree\",0.0174532925199433],\n"
+ " ID[\"EPSG\",8801]],\n"
+ " PARAMETER[\"Longitude of natural origin\",0,\n"
+ " ANGLEUNIT[\"degree\",0.0174532925199433],\n"
+ " ID[\"EPSG\",8802]],\n"
+ " PARAMETER[\"Scale factor at natural origin\",2,\n"
+ " SCALEUNIT[\"unity\",1],\n"
+ " ID[\"EPSG\",8805]],\n"
+ " PARAMETER[\"False easting\",1,\n"
+ " LENGTHUNIT[\"metre\",1],\n"
+ " ID[\"EPSG\",8806]],\n"
+ " PARAMETER[\"False northing\",0,\n"
+ " LENGTHUNIT[\"metre\",1],\n"
+ " ID[\"EPSG\",8807]]],\n"
+ " CS[Cartesian,2],\n"
+ " AXIS[\"(E)\",east,\n"
+ " ORDER[1],\n"
+ " LENGTHUNIT[\"metre\",1]],\n"
+ " AXIS[\"(N)\",north,\n"
+ " ORDER[2],\n"
+ " LENGTHUNIT[\"metre\",1]]]";
+
+ EXPECT_EQ(f->toString(), expected);
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(io, projparse_tmerc_south_oriented) {
+ auto obj = PROJStringParser().createFromPROJString(
+ "+proj=tmerc +axis=wsu +x_0=1 +lat_0=1 +k_0=2");
+ auto crs = nn_dynamic_pointer_cast<ProjectedCRS>(obj);
+ ASSERT_TRUE(crs != nullptr);
+ WKTFormatterNNPtr f(WKTFormatter::create());
+ f->simulCurNodeHasId();
+ crs->exportToWKT(f.get());
+ auto expected =
+ "PROJCRS[\"unknown\",\n"
+ " BASEGEODCRS[\"unknown\",\n"
+ " DATUM[\"World Geodetic System 1984\",\n"
+ " ELLIPSOID[\"WGS 84\",6378137,298.257223563,\n"
+ " LENGTHUNIT[\"metre\",1]]],\n"
+ " PRIMEM[\"Greenwich\",0,\n"
+ " ANGLEUNIT[\"degree\",0.0174532925199433]]],\n"
+ " CONVERSION[\"unknown\",\n"
+ " METHOD[\"Transverse Mercator (South Orientated)\",\n"
+ " ID[\"EPSG\",9808]],\n"
+ " PARAMETER[\"Latitude of natural origin\",1,\n"
+ " ANGLEUNIT[\"degree\",0.0174532925199433],\n"
+ " ID[\"EPSG\",8801]],\n"
+ " PARAMETER[\"Longitude of natural origin\",0,\n"
+ " ANGLEUNIT[\"degree\",0.0174532925199433],\n"
+ " ID[\"EPSG\",8802]],\n"
+ " PARAMETER[\"Scale factor at natural origin\",2,\n"
+ " SCALEUNIT[\"unity\",1],\n"
+ " ID[\"EPSG\",8805]],\n"
+ " PARAMETER[\"False easting\",1,\n"
+ " LENGTHUNIT[\"metre\",1],\n"
+ " ID[\"EPSG\",8806]],\n"
+ " PARAMETER[\"False northing\",0,\n"
+ " LENGTHUNIT[\"metre\",1],\n"
+ " ID[\"EPSG\",8807]]],\n"
+ " CS[Cartesian,2],\n"
+ " AXIS[\"westing\",west,\n"
+ " ORDER[1],\n"
+ " LENGTHUNIT[\"metre\",1]],\n"
+ " AXIS[\"southing\",south,\n"
+ " ORDER[2],\n"
+ " LENGTHUNIT[\"metre\",1]]]";
+
+ EXPECT_EQ(f->toString(), expected);
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(io, projparse_lcc_as_lcc1sp) {
+ auto obj = PROJStringParser().createFromPROJString(
+ "+proj=lcc +lat_0=45 +lat_1=45");
+ auto crs = nn_dynamic_pointer_cast<ProjectedCRS>(obj);
+ ASSERT_TRUE(crs != nullptr);
+ WKTFormatterNNPtr f(WKTFormatter::create());
+ f->simulCurNodeHasId();
+ f->setMultiLine(false);
+ crs->exportToWKT(f.get());
+ auto wkt = f->toString();
+ EXPECT_TRUE(wkt.find("Lambert Conic Conformal (1SP)") != std::string::npos)
+ << wkt;
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(io, projparse_lcc_as_lcc2sp) {
+ auto obj = PROJStringParser().createFromPROJString(
+ "+proj=lcc +lat_0=45 +lat_1=46");
+ auto crs = nn_dynamic_pointer_cast<ProjectedCRS>(obj);
+ ASSERT_TRUE(crs != nullptr);
+ WKTFormatterNNPtr f(WKTFormatter::create());
+ f->simulCurNodeHasId();
+ f->setMultiLine(false);
+ crs->exportToWKT(f.get());
+ auto wkt = f->toString();
+ EXPECT_TRUE(wkt.find("Lambert Conic Conformal (2SP)") != std::string::npos)
+ << wkt;
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(io, projparse_lcc_as_lcc2sp_michigan) {
+ auto obj = PROJStringParser().createFromPROJString(
+ "+proj=lcc +lat_0=45 +lat_1=46 +k_0=1.02");
+ auto crs = nn_dynamic_pointer_cast<ProjectedCRS>(obj);
+ ASSERT_TRUE(crs != nullptr);
+ WKTFormatterNNPtr f(WKTFormatter::create());
+ f->simulCurNodeHasId();
+ f->setMultiLine(false);
+ crs->exportToWKT(f.get());
+ auto wkt = f->toString();
+ EXPECT_TRUE(wkt.find("Lambert Conic Conformal (2SP Michigan)") !=
+ std::string::npos)
+ << wkt;
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(io, projparse_aeqd_guam) {
+ auto obj = PROJStringParser().createFromPROJString("+proj=aeqd +guam");
+ auto crs = nn_dynamic_pointer_cast<ProjectedCRS>(obj);
+ ASSERT_TRUE(crs != nullptr);
+ WKTFormatterNNPtr f(WKTFormatter::create());
+ f->simulCurNodeHasId();
+ f->setMultiLine(false);
+ crs->exportToWKT(f.get());
+ auto wkt = f->toString();
+ EXPECT_TRUE(wkt.find("Guam Projection") != std::string::npos) << wkt;
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(io, projparse_cea_ellipsoidal) {
+ auto obj =
+ PROJStringParser().createFromPROJString("+proj=cea +ellps=GRS80");
+ auto crs = nn_dynamic_pointer_cast<ProjectedCRS>(obj);
+ ASSERT_TRUE(crs != nullptr);
+ WKTFormatterNNPtr f(WKTFormatter::create());
+ f->simulCurNodeHasId();
+ f->setMultiLine(false);
+ crs->exportToWKT(f.get());
+ auto wkt = f->toString();
+ EXPECT_TRUE(
+ wkt.find(
+ "METHOD[\"Lambert Cylindrical Equal Area\",ID[\"EPSG\",9835]]") !=
+ std::string::npos)
+ << wkt;
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(io, projparse_geos_sweep_x) {
+ auto obj = PROJStringParser().createFromPROJString("+proj=geos +sweep=x");
+ auto crs = nn_dynamic_pointer_cast<ProjectedCRS>(obj);
+ ASSERT_TRUE(crs != nullptr);
+ WKTFormatterNNPtr f(WKTFormatter::create());
+ f->simulCurNodeHasId();
+ f->setMultiLine(false);
+ crs->exportToWKT(f.get());
+ auto wkt = f->toString();
+ EXPECT_TRUE(wkt.find("Geostationary Satellite (Sweep X)") !=
+ std::string::npos)
+ << wkt;
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(io, projparse_geos_sweep_y) {
+ auto obj = PROJStringParser().createFromPROJString("+proj=geos");
+ auto crs = nn_dynamic_pointer_cast<ProjectedCRS>(obj);
+ ASSERT_TRUE(crs != nullptr);
+ WKTFormatterNNPtr f(WKTFormatter::create());
+ f->simulCurNodeHasId();
+ f->setMultiLine(false);
+ crs->exportToWKT(f.get());
+ auto wkt = f->toString();
+ EXPECT_TRUE(wkt.find("Geostationary Satellite (Sweep Y)") !=
+ std::string::npos)
+ << wkt;
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(io, projparse_omerc_nouoff) {
+ auto obj = PROJStringParser().createFromPROJString(
+ "+proj=omerc +no_uoff +alpha=2 +gamma=3");
+ auto crs = nn_dynamic_pointer_cast<ProjectedCRS>(obj);
+ ASSERT_TRUE(crs != nullptr);
+ WKTFormatterNNPtr f(WKTFormatter::create());
+ f->simulCurNodeHasId();
+ f->setMultiLine(false);
+ crs->exportToWKT(f.get());
+ auto wkt = f->toString();
+ EXPECT_TRUE(wkt.find("METHOD[\"Hotine Oblique Mercator (variant "
+ "A)\",ID[\"EPSG\",9812]]") != std::string::npos)
+ << wkt;
+ EXPECT_TRUE(wkt.find("PARAMETER[\"Azimuth of initial line\",2") !=
+ std::string::npos)
+ << wkt;
+ EXPECT_TRUE(wkt.find("PARAMETER[\"Angle from Rectified to Skew Grid\",3") !=
+ std::string::npos)
+ << wkt;
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(io, projparse_omerc_tpno) {
+ auto obj = PROJStringParser().createFromPROJString(
+ "+proj=omerc +lat_1=1 +lat_2=2 +lon_1=3 +lon_2=4");
+ auto crs = nn_dynamic_pointer_cast<ProjectedCRS>(obj);
+ ASSERT_TRUE(crs != nullptr);
+ WKTFormatterNNPtr f(WKTFormatter::create());
+ f->simulCurNodeHasId();
+ f->setMultiLine(false);
+ crs->exportToWKT(f.get());
+ auto wkt = f->toString();
+ EXPECT_TRUE(
+ wkt.find(
+ "METHOD[\"Hotine Oblique Mercator Two Point Natural Origin\"]") !=
+ std::string::npos)
+ << wkt;
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(io, projparse_omerc_variant_b) {
+ auto obj = PROJStringParser().createFromPROJString("+proj=omerc +alpha=2");
+ auto crs = nn_dynamic_pointer_cast<ProjectedCRS>(obj);
+ ASSERT_TRUE(crs != nullptr);
+ WKTFormatterNNPtr f(WKTFormatter::create());
+ f->simulCurNodeHasId();
+ f->setMultiLine(false);
+ crs->exportToWKT(f.get());
+ auto wkt = f->toString();
+ EXPECT_TRUE(wkt.find("METHOD[\"Hotine Oblique Mercator (variant "
+ "B)\",ID[\"EPSG\",9815]]") != std::string::npos)
+ << wkt;
+ EXPECT_TRUE(wkt.find("PARAMETER[\"Angle from Rectified to Skew Grid\",2") !=
+ std::string::npos)
+ << wkt;
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(io, projparse_somerc) {
+ auto obj = PROJStringParser().createFromPROJString(
+ "+proj=somerc +lat_0=1 +lon_0=2 +k_0=3 +x_0=4 +y_0=5");
+ auto crs = nn_dynamic_pointer_cast<ProjectedCRS>(obj);
+ ASSERT_TRUE(crs != nullptr);
+ WKTFormatterNNPtr f(WKTFormatter::create());
+ f->simulCurNodeHasId();
+ f->setMultiLine(false);
+ crs->exportToWKT(f.get());
+ auto wkt = f->toString();
+ EXPECT_TRUE(wkt.find("METHOD[\"Hotine Oblique Mercator (variant "
+ "B)\",ID[\"EPSG\",9815]]") != std::string::npos)
+ << wkt;
+ EXPECT_TRUE(wkt.find("\"Latitude of projection centre\",1") !=
+ std::string::npos)
+ << wkt;
+ EXPECT_TRUE(wkt.find("\"Longitude of projection centre\",2") !=
+ std::string::npos)
+ << wkt;
+ EXPECT_TRUE(wkt.find("\"Scale factor on initial line\",3") !=
+ std::string::npos)
+ << wkt;
+ EXPECT_TRUE(wkt.find("\"Azimuth of initial line\",90") != std::string::npos)
+ << wkt;
+ EXPECT_TRUE(wkt.find("\"Angle from Rectified to Skew Grid\",90") !=
+ std::string::npos)
+ << wkt;
+ EXPECT_TRUE(wkt.find("\"Easting at projection centre\",4") !=
+ std::string::npos)
+ << wkt;
+ EXPECT_TRUE(wkt.find("\"Northing at projection centre\",5") !=
+ std::string::npos)
+ << wkt;
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(io, projparse_krovak) {
+ auto obj = PROJStringParser().createFromPROJString("+proj=krovak");
+ auto crs = nn_dynamic_pointer_cast<ProjectedCRS>(obj);
+ ASSERT_TRUE(crs != nullptr);
+ WKTFormatterNNPtr f(WKTFormatter::create());
+ f->simulCurNodeHasId();
+ f->setMultiLine(false);
+ crs->exportToWKT(f.get());
+ auto wkt = f->toString();
+ EXPECT_TRUE(
+ wkt.find("METHOD[\"Krovak (North Orientated)\",ID[\"EPSG\",1041]]") !=
+ std::string::npos)
+ << wkt;
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(io, projparse_krovak_axis_swu) {
+ auto obj =
+ PROJStringParser().createFromPROJString("+proj=krovak +axis=swu");
+ auto crs = nn_dynamic_pointer_cast<ProjectedCRS>(obj);
+ ASSERT_TRUE(crs != nullptr);
+ WKTFormatterNNPtr f(WKTFormatter::create());
+ f->simulCurNodeHasId();
+ f->setMultiLine(false);
+ crs->exportToWKT(f.get());
+ auto wkt = f->toString();
+ EXPECT_TRUE(wkt.find("METHOD[\"Krovak\",ID[\"EPSG\",9819]]") !=
+ std::string::npos)
+ << wkt;
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(io, projparse_etmerc) {
+ auto obj = PROJStringParser().createFromPROJString("+proj=etmerc");
+ auto crs = nn_dynamic_pointer_cast<ProjectedCRS>(obj);
+ ASSERT_TRUE(crs != nullptr);
+ WKTFormatterNNPtr f(WKTFormatter::create());
+ f->simulCurNodeHasId();
+ f->setMultiLine(false);
+ crs->exportToWKT(f.get());
+ auto wkt = f->toString();
+ EXPECT_TRUE(wkt.find("METHOD[\"Transverse Mercator\",ID[\"EPSG\",9807]]") !=
+ std::string::npos)
+ << wkt;
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(io, projparse_merc_variant_B) {
+ auto obj = PROJStringParser().createFromPROJString("+proj=merc +lat_ts=1");
+ auto crs = nn_dynamic_pointer_cast<ProjectedCRS>(obj);
+ ASSERT_TRUE(crs != nullptr);
+ WKTFormatterNNPtr f(WKTFormatter::create());
+ f->simulCurNodeHasId();
+ f->setMultiLine(false);
+ crs->exportToWKT(f.get());
+ auto wkt = f->toString();
+ EXPECT_TRUE(
+ wkt.find("METHOD[\"Mercator (variant B)\",ID[\"EPSG\",9805]]") !=
+ std::string::npos)
+ << wkt;
+ EXPECT_TRUE(wkt.find("PARAMETER[\"Latitude of 1st standard parallel\",1") !=
+ std::string::npos)
+ << wkt;
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(io, projparse_merc_google_mercator) {
+ auto obj = PROJStringParser().createFromPROJString(
+ "+proj=merc +a=6378137 +b=6378137 +lat_ts=0 +lon_0=0 +x_0=0 +y_0=0 "
+ "+k=1 +units=m +nadgrids=@null");
+ auto crs = nn_dynamic_pointer_cast<ProjectedCRS>(obj);
+ ASSERT_TRUE(crs != nullptr);
+ WKTFormatterNNPtr f(WKTFormatter::create());
+ f->simulCurNodeHasId();
+ f->setMultiLine(false);
+ crs->exportToWKT(f.get());
+ auto wkt = f->toString();
+ EXPECT_TRUE(wkt.find("METHOD[\"Popular Visualisation Pseudo "
+ "Mercator\",ID[\"EPSG\",1024]") != std::string::npos)
+ << wkt;
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(io, projparse_merc_stere_polar_variant_B) {
+ auto obj = PROJStringParser().createFromPROJString(
+ "+proj=stere +lat_0=90 +lat_ts=70");
+ auto crs = nn_dynamic_pointer_cast<ProjectedCRS>(obj);
+ ASSERT_TRUE(crs != nullptr);
+ WKTFormatterNNPtr f(WKTFormatter::create());
+ f->simulCurNodeHasId();
+ f->setMultiLine(false);
+ crs->exportToWKT(f.get());
+ auto wkt = f->toString();
+ EXPECT_TRUE(
+ wkt.find(
+ "METHOD[\"Polar Stereographic (variant B)\",ID[\"EPSG\",9829]]") !=
+ std::string::npos)
+ << wkt;
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(io, projparse_merc_stere_polar_variant_A) {
+ auto obj = PROJStringParser().createFromPROJString(
+ "+proj=stere +lat_0=-90 +k=0.994");
+ auto crs = nn_dynamic_pointer_cast<ProjectedCRS>(obj);
+ ASSERT_TRUE(crs != nullptr);
+ WKTFormatterNNPtr f(WKTFormatter::create());
+ f->simulCurNodeHasId();
+ f->setMultiLine(false);
+ crs->exportToWKT(f.get());
+ auto wkt = f->toString();
+ EXPECT_TRUE(
+ wkt.find(
+ "METHOD[\"Polar Stereographic (variant A)\",ID[\"EPSG\",9810]]") !=
+ std::string::npos)
+ << wkt;
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(io, projparse_merc_stere) {
+ auto obj = PROJStringParser().createFromPROJString("+proj=stere +lat_0=30");
+ auto crs = nn_dynamic_pointer_cast<ProjectedCRS>(obj);
+ ASSERT_TRUE(crs != nullptr);
+ WKTFormatterNNPtr f(WKTFormatter::create());
+ f->simulCurNodeHasId();
+ f->setMultiLine(false);
+ crs->exportToWKT(f.get());
+ auto wkt = f->toString();
+ EXPECT_TRUE(wkt.find("METHOD[\"Stereographic\"]") != std::string::npos)
+ << wkt;
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(io, projparse_utm) {
+ auto obj = PROJStringParser().createFromPROJString("+proj=utm +zone=1");
+ auto crs = nn_dynamic_pointer_cast<ProjectedCRS>(obj);
+ ASSERT_TRUE(crs != nullptr);
+ WKTFormatterNNPtr f(WKTFormatter::create());
+ f->simulCurNodeHasId();
+ f->setMultiLine(false);
+ crs->exportToWKT(f.get());
+ auto wkt = f->toString();
+ EXPECT_TRUE(wkt.find("CONVERSION[\"UTM zone 1N\",METHOD[\"Transverse "
+ "Mercator\",ID[\"EPSG\",9807]]") != std::string::npos)
+ << wkt;
+ EXPECT_TRUE(wkt.find("\"Longitude of natural origin\",-177,") !=
+ std::string::npos)
+ << wkt;
+ EXPECT_TRUE(wkt.find("\"False northing\",0,") != std::string::npos) << wkt;
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(io, projparse_utm_south) {
+ auto obj =
+ PROJStringParser().createFromPROJString("+proj=utm +zone=1 +south");
+ auto crs = nn_dynamic_pointer_cast<ProjectedCRS>(obj);
+ ASSERT_TRUE(crs != nullptr);
+ WKTFormatterNNPtr f(WKTFormatter::create());
+ f->simulCurNodeHasId();
+ f->setMultiLine(false);
+ crs->exportToWKT(f.get());
+ auto wkt = f->toString();
+ EXPECT_TRUE(wkt.find("CONVERSION[\"UTM zone 1S\",METHOD[\"Transverse "
+ "Mercator\",ID[\"EPSG\",9807]]") != std::string::npos)
+ << wkt;
+ EXPECT_TRUE(wkt.find("\"Longitude of natural origin\",-177,") !=
+ std::string::npos)
+ << wkt;
+ EXPECT_TRUE(wkt.find("\"False northing\",10000000,") != std::string::npos)
+ << wkt;
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(io, projparse_non_earth_ellipsoid) {
+ std::string input("+proj=merc +lon_0=0 +k=1 +x_0=0 +y_0=0 +R=1");
+ auto obj = PROJStringParser().createFromPROJString(input);
+ auto crs = nn_dynamic_pointer_cast<ProjectedCRS>(obj);
+ ASSERT_TRUE(crs != nullptr);
+ EXPECT_EQ(
+ crs->exportToPROJString(
+ PROJStringFormatter::create(PROJStringFormatter::Convention::PROJ_4)
+ .get()),
+ input);
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(io, projparse_axisswap_unitconvert_longlat_proj) {
+ std::string input =
+ "+proj=pipeline +step +proj=axisswap +order=2,1 +step "
+ "+proj=unitconvert +xy_in=grad +xy_out=rad +step +inv +proj=longlat "
+ "+ellps=clrk80ign +pm=paris +step +proj=lcc +lat_1=49.5 "
+ "+lat_0=49.5 +lon_0=0 +k_0=0.999877341 +x_0=600000 +y_0=200000 "
+ "+ellps=clrk80ign +pm=paris";
+ auto obj = PROJStringParser().createFromPROJString(input);
+ auto crs = nn_dynamic_pointer_cast<ProjectedCRS>(obj);
+ ASSERT_TRUE(crs != nullptr);
+ EXPECT_EQ(
+ crs->exportToPROJString(
+ PROJStringFormatter::create(PROJStringFormatter::Convention::PROJ_5)
+ .get()),
+ input);
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(io, projparse_axisswap_unitconvert_proj_axisswap) {
+ std::string input =
+ "+proj=pipeline +step +proj=axisswap +order=2,1 +step "
+ "+proj=unitconvert +xy_in=deg +xy_out=rad +step +proj=igh "
+ "+lon_0=0 +x_0=0 +y_0=0 +ellps=GRS80 +step +proj=axisswap +order=2,1";
+ auto obj = PROJStringParser().createFromPROJString(input);
+ auto crs = nn_dynamic_pointer_cast<ProjectedCRS>(obj);
+ ASSERT_TRUE(crs != nullptr);
+ EXPECT_EQ(
+ crs->exportToPROJString(
+ PROJStringFormatter::create(PROJStringFormatter::Convention::PROJ_5)
+ .get()),
+ input);
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(io, projparse_axisswap_unitconvert_proj_unitconvert) {
+ std::string input =
+ "+proj=pipeline +step +proj=axisswap +order=2,1 +step "
+ "+proj=unitconvert +xy_in=deg +xy_out=rad +step +proj=igh "
+ "+lon_0=0 +x_0=0 +y_0=0 +ellps=GRS80 +step +proj=unitconvert +xy_in=m "
+ "+z_in=m +xy_out=ft +z_out=ft";
+ auto obj = PROJStringParser().createFromPROJString(input);
+ auto crs = nn_dynamic_pointer_cast<ProjectedCRS>(obj);
+ ASSERT_TRUE(crs != nullptr);
+ EXPECT_EQ(
+ crs->exportToPROJString(
+ PROJStringFormatter::create(PROJStringFormatter::Convention::PROJ_5)
+ .get()),
+ input);
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(io, projparse_axisswap_unitconvert_proj_unitconvert_numeric_axisswap) {
+ std::string input =
+ "+proj=pipeline +step +proj=axisswap +order=2,1 +step "
+ "+proj=unitconvert +xy_in=deg +xy_out=rad +step +proj=igh "
+ "+lon_0=0 +x_0=0 +y_0=0 +ellps=GRS80 +step +proj=unitconvert +xy_in=m "
+ "+z_in=m +xy_out=2.5 +z_out=2.5 +step +proj=axisswap +order=-2,-1";
+ auto obj = PROJStringParser().createFromPROJString(input);
+ auto crs = nn_dynamic_pointer_cast<ProjectedCRS>(obj);
+ ASSERT_TRUE(crs != nullptr);
+ EXPECT_EQ(
+ crs->exportToPROJString(
+ PROJStringFormatter::create(PROJStringFormatter::Convention::PROJ_5)
+ .get()),
+ input);
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(io, projparse_projected_units) {
+ auto obj = PROJStringParser().createFromPROJString(
+ "+proj=tmerc +x_0=0.304800609601219 +units=us-ft");
+ auto crs = nn_dynamic_pointer_cast<ProjectedCRS>(obj);
+ ASSERT_TRUE(crs != nullptr);
+ WKTFormatterNNPtr f(WKTFormatter::create());
+ f->simulCurNodeHasId();
+ f->setMultiLine(false);
+ crs->exportToWKT(f.get());
+ auto wkt = f->toString();
+ EXPECT_TRUE(wkt.find("PARAMETER[\"False easting\",1,LENGTHUNIT[\"US survey "
+ "foot\",0.304800609601219]") != std::string::npos)
+ << wkt;
+ EXPECT_TRUE(wkt.find("AXIS[\"(E)\",east,ORDER[1],LENGTHUNIT[\"US survey "
+ "foot\",0.304800609601219]") != std::string::npos)
+ << wkt;
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(io, projparse_projected_to_meter_known) {
+ auto obj = PROJStringParser().createFromPROJString(
+ "+proj=tmerc +to_meter=0.304800609601219");
+ auto crs = nn_dynamic_pointer_cast<ProjectedCRS>(obj);
+ ASSERT_TRUE(crs != nullptr);
+ WKTFormatterNNPtr f(WKTFormatter::create());
+ f->simulCurNodeHasId();
+ f->setMultiLine(false);
+ crs->exportToWKT(f.get());
+ auto wkt = f->toString();
+ EXPECT_TRUE(wkt.find("PARAMETER[\"False easting\",0,LENGTHUNIT[\"US survey "
+ "foot\",0.304800609601219]") != std::string::npos)
+ << wkt;
+ EXPECT_TRUE(wkt.find("AXIS[\"(E)\",east,ORDER[1],LENGTHUNIT[\"US survey "
+ "foot\",0.304800609601219]") != std::string::npos)
+ << wkt;
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(io, projparse_projected_to_meter_unknown) {
+ auto obj =
+ PROJStringParser().createFromPROJString("+proj=tmerc +to_meter=0.1234");
+ auto crs = nn_dynamic_pointer_cast<ProjectedCRS>(obj);
+ ASSERT_TRUE(crs != nullptr);
+ WKTFormatterNNPtr f(WKTFormatter::create());
+ f->simulCurNodeHasId();
+ f->setMultiLine(false);
+ crs->exportToWKT(f.get());
+ auto wkt = f->toString();
+ EXPECT_TRUE(
+ wkt.find(
+ "PARAMETER[\"False easting\",0,LENGTHUNIT[\"unknown\",0.1234]") !=
+ std::string::npos)
+ << wkt;
+ EXPECT_TRUE(
+ wkt.find("AXIS[\"(E)\",east,ORDER[1],LENGTHUNIT[\"unknown\",0.1234]") !=
+ std::string::npos)
+ << wkt;
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(io, projparse_projected_vunits) {
+ auto obj =
+ PROJStringParser().createFromPROJString("+proj=tmerc +vunits=ft");
+ auto crs = nn_dynamic_pointer_cast<CompoundCRS>(obj);
+ ASSERT_TRUE(crs != nullptr);
+ WKTFormatterNNPtr f(WKTFormatter::create());
+ f->simulCurNodeHasId();
+ f->setMultiLine(false);
+ crs->exportToWKT(f.get());
+ auto wkt = f->toString();
+ EXPECT_TRUE(wkt.find("CS[Cartesian,2]") != std::string::npos) << wkt;
+ EXPECT_TRUE(wkt.find("CS[vertical,1],AXIS[\"gravity-related height "
+ "(H)\",up,LENGTHUNIT[\"foot\",0.3048]") !=
+ std::string::npos)
+ << wkt;
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(io, projparse_projected_unknown) {
+ auto obj = PROJStringParser().createFromPROJString(
+ "+proj=mbt_s +unused_flag +lat_0=45 +lon_0=0 +k=1 +x_0=10 +y_0=0");
+ auto crs = nn_dynamic_pointer_cast<ProjectedCRS>(obj);
+ ASSERT_TRUE(crs != nullptr);
+
+ {
+ WKTFormatterNNPtr f(WKTFormatter::create());
+ f->simulCurNodeHasId();
+ f->setMultiLine(false);
+ crs->exportToWKT(f.get());
+ auto wkt = f->toString();
+ EXPECT_TRUE(
+ wkt.find("CONVERSION[\"unknown\",METHOD[\"PROJ mbt_s "
+ "unused_flag\"],PARAMETER[\"lat_0\",45,ANGLEUNIT["
+ "\"degree\",0.0174532925199433]],PARAMETER[\"lon_0\","
+ "0,ANGLEUNIT[\"degree\",0.0174532925199433]],"
+ "PARAMETER[\"k\",1,SCALEUNIT[\"unity\",1]],PARAMETER["
+ "\"x_0\",10,LENGTHUNIT[\"metre\",1]],PARAMETER[\"y_0\","
+ "0,LENGTHUNIT[\"metre\",1]]]") != std::string::npos)
+ << wkt;
+ }
+
+ std::string expected_wkt1 =
+ "PROJCS[\"unknown\",GEOGCS[\"unknown\",DATUM[\"WGS_1984\",SPHEROID["
+ "\"WGS "
+ "84\",6378137,298.257223563,AUTHORITY[\"EPSG\",\"7030\"]],AUTHORITY["
+ "\"EPSG\",\"6326\"]],PRIMEM[\"Greenwich\",0,AUTHORITY[\"EPSG\","
+ "\"8901\"]],UNIT[\"degree\",0.0174532925199433,AUTHORITY[\"EPSG\","
+ "\"9122\"]],AXIS[\"Longitude\",EAST],AXIS[\"Latitude\",NORTH]],"
+ "PROJECTION[\"custom_proj4\"],UNIT[\"metre\",1,AUTHORITY[\"EPSG\","
+ "\"9001\"]],AXIS[\"Easting\",EAST],AXIS[\"Northing\",NORTH],EXTENSION["
+ "\"PROJ4\",\"+proj=mbt_s +datum=WGS84 +unused_flag +lat_0=45 "
+ "+lon_0=0 +k=1 +x_0=10 +y_0=0 +wktext +no_defs\"]]";
+
+ {
+ WKTFormatterNNPtr f(
+ WKTFormatter::create(WKTFormatter::Convention::WKT1_GDAL));
+ f->simulCurNodeHasId();
+ f->setMultiLine(false);
+ crs->exportToWKT(f.get());
+ auto wkt = f->toString();
+ EXPECT_EQ(wkt, expected_wkt1);
+ }
+
+ EXPECT_EQ(
+ crs->exportToPROJString(
+ PROJStringFormatter::create(PROJStringFormatter::Convention::PROJ_4)
+ .get()),
+ "+proj=mbt_s +unused_flag +lat_0=45 +lon_0=0 +k=1 +x_0=10 "
+ "+y_0=0 +datum=WGS84");
+
+ EXPECT_EQ(
+ crs->exportToPROJString(
+ PROJStringFormatter::create(PROJStringFormatter::Convention::PROJ_5)
+ .get()),
+ "+proj=pipeline +step +proj=unitconvert +xy_in=deg +xy_out=rad "
+ "+step +proj=mbt_s +unused_flag +lat_0=45 +lon_0=0 +k=1 "
+ "+x_0=10 +y_0=0 +ellps=WGS84");
+
+ {
+ auto obj2 = WKTParser().createFromWKT(expected_wkt1);
+ auto crs2 = nn_dynamic_pointer_cast<ProjectedCRS>(obj2);
+ ASSERT_TRUE(crs2 != nullptr);
+
+ WKTFormatterNNPtr f(WKTFormatter::create());
+ f->simulCurNodeHasId();
+ f->setMultiLine(false);
+ crs2->exportToWKT(f.get());
+ auto wkt = f->toString();
+ EXPECT_TRUE(
+ wkt.find("CONVERSION[\"unknown\",METHOD[\"PROJ mbt_s "
+ "unused_flag\"],PARAMETER[\"lat_0\",45,ANGLEUNIT["
+ "\"degree\",0.0174532925199433]],PARAMETER[\"lon_0\","
+ "0,ANGLEUNIT[\"degree\",0.0174532925199433]],"
+ "PARAMETER[\"k\",1,SCALEUNIT[\"unity\",1]],PARAMETER["
+ "\"x_0\",10,LENGTHUNIT[\"metre\",1]],PARAMETER[\"y_0\","
+ "0,LENGTHUNIT[\"metre\",1]]]") != std::string::npos)
+ << wkt;
+ }
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(io, projparse_geocent) {
+ auto obj =
+ PROJStringParser().createFromPROJString("+proj=geocent +ellps=WGS84");
+ auto crs = nn_dynamic_pointer_cast<GeodeticCRS>(obj);
+ ASSERT_TRUE(crs != nullptr);
+ WKTFormatterNNPtr f(WKTFormatter::create());
+ f->simulCurNodeHasId();
+ f->setMultiLine(false);
+ crs->exportToWKT(f.get());
+ auto wkt = f->toString();
+ EXPECT_EQ(wkt, "GEODCRS[\"unknown\",DATUM[\"Unknown based on WGS84 "
+ "ellipsoid\",ELLIPSOID[\"WGS "
+ "84\",6378137,298.257223563,LENGTHUNIT[\"metre\",1]]],"
+ "PRIMEM[\"Greenwich\",0,ANGLEUNIT[\"degree\",0."
+ "0174532925199433]],CS[Cartesian,3],AXIS[\"(X)\","
+ "geocentricX,ORDER[1],LENGTHUNIT[\"metre\",1]],AXIS[\"(Y)\","
+ "geocentricY,ORDER[2],LENGTHUNIT[\"metre\",1]],AXIS[\"(Z)\","
+ "geocentricZ,ORDER[3],LENGTHUNIT[\"metre\",1]]]");
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(io, projparse_cart_unit) {
+ std::string input(
+ "+proj=pipeline +step +proj=cart +ellps=WGS84 +step "
+ "+proj=unitconvert +xy_in=m +z_in=m +xy_out=km +z_out=km");
+ auto obj = PROJStringParser().createFromPROJString(input);
+ auto crs = nn_dynamic_pointer_cast<GeodeticCRS>(obj);
+ ASSERT_TRUE(crs != nullptr);
+ EXPECT_EQ(
+ crs->exportToPROJString(
+ PROJStringFormatter::create(PROJStringFormatter::Convention::PROJ_5)
+ .get()),
+ input);
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(io, projparse_cart_unit_numeric) {
+ std::string input(
+ "+proj=pipeline +step +proj=cart +ellps=WGS84 +step "
+ "+proj=unitconvert +xy_in=m +z_in=m +xy_out=500 +z_out=500");
+ auto obj = PROJStringParser().createFromPROJString(input);
+ auto crs = nn_dynamic_pointer_cast<GeodeticCRS>(obj);
+ ASSERT_TRUE(crs != nullptr);
+ EXPECT_EQ(
+ crs->exportToPROJString(
+ PROJStringFormatter::create(PROJStringFormatter::Convention::PROJ_5)
+ .get()),
+ input);
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(io, projparse_ob_tran_longlat) {
+ std::string input(
+ "+proj=pipeline +step +proj=axisswap +order=2,1 +step "
+ "+proj=unitconvert +xy_in=deg +xy_out=rad +step +proj=ob_tran "
+ "+o_proj=longlat +o_lat_p=52 +o_lon_p=-30 +lon_0=-25 +ellps=WGS84 "
+ "+step +proj=unitconvert +xy_in=rad +xy_out=deg +step "
+ "+proj=axisswap +order=2,1");
+ auto obj = PROJStringParser().createFromPROJString(input);
+ auto crs = nn_dynamic_pointer_cast<DerivedGeographicCRS>(obj);
+ ASSERT_TRUE(crs != nullptr);
+ EXPECT_EQ(
+ crs->exportToPROJString(
+ PROJStringFormatter::create(PROJStringFormatter::Convention::PROJ_5)
+ .get()),
+ input);
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(io, projparse_helmert_translation) {
+ std::string projString("+proj=helmert +x=1 +y=2 +z=3");
+ auto obj = PROJStringParser().createFromPROJString(projString);
+ auto transf = nn_dynamic_pointer_cast<Transformation>(obj);
+ ASSERT_TRUE(transf != nullptr);
+ EXPECT_EQ(
+ transf->exportToPROJString(
+ PROJStringFormatter::create(PROJStringFormatter::Convention::PROJ_5)
+ .get()),
+ projString);
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(io, projparse_helmert_translation_inv) {
+ std::string projString(
+ "+proj=pipeline +step +inv +proj=helmert +x=1 +y=2 +z=3");
+ auto obj = PROJStringParser().createFromPROJString(projString);
+ auto transf = nn_dynamic_pointer_cast<Transformation>(obj);
+ ASSERT_TRUE(transf != nullptr);
+ EXPECT_EQ(
+ transf->exportToPROJString(
+ PROJStringFormatter::create(PROJStringFormatter::Convention::PROJ_5)
+ .get()),
+ "+proj=helmert +x=-1 +y=-2 +z=-3");
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(io, projparse_helmert_position_vector) {
+ std::string projString("+proj=helmert +x=1 +y=2 +z=3 +rx=4 +ry=5 +rz=6 "
+ "+s=7 +convention=position_vector");
+ auto obj = PROJStringParser().createFromPROJString(projString);
+ auto transf = nn_dynamic_pointer_cast<Transformation>(obj);
+ ASSERT_TRUE(transf != nullptr);
+ EXPECT_EQ(
+ transf->exportToPROJString(
+ PROJStringFormatter::create(PROJStringFormatter::Convention::PROJ_5)
+ .get()),
+ projString);
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(io, projparse_helmert_position_vector_inv) {
+ std::string projString("+proj=pipeline +step +inv +proj=helmert +x=1 +y=2 "
+ "+z=3 +rx=4 +ry=5 +rz=6 "
+ "+s=7 +convention=position_vector");
+ auto obj = PROJStringParser().createFromPROJString(projString);
+ auto transf = nn_dynamic_pointer_cast<CoordinateOperation>(obj);
+ ASSERT_TRUE(transf != nullptr);
+ EXPECT_EQ(
+ transf->exportToPROJString(
+ PROJStringFormatter::create(PROJStringFormatter::Convention::PROJ_5)
+ .get()),
+ projString);
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(io, projparse_helmert_time_dependent_position_vector) {
+ std::string projString("+proj=helmert +x=1 +y=2 +z=3 +rx=4 +ry=5 +rz=6 "
+ "+s=7 +dx=0.1 +dy=0.2 +dz=0.3 +drx=0.4 +dry=0.5 "
+ "+drz=0.6 +ds=0.7 +t_epoch=2018.5 "
+ "+convention=position_vector");
+ auto obj = PROJStringParser().createFromPROJString(projString);
+ auto transf = nn_dynamic_pointer_cast<Transformation>(obj);
+ ASSERT_TRUE(transf != nullptr);
+ EXPECT_EQ(
+ transf->exportToPROJString(
+ PROJStringFormatter::create(PROJStringFormatter::Convention::PROJ_5)
+ .get()),
+ projString);
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(io, projparse_helmert_coordinate_frame) {
+ std::string projString("+proj=helmert +x=1 +y=2 +z=3 +rx=4 +ry=5 +rz=6 "
+ "+s=7 +convention=coordinate_frame");
+ auto obj = PROJStringParser().createFromPROJString(projString);
+ auto transf = nn_dynamic_pointer_cast<Transformation>(obj);
+ ASSERT_TRUE(transf != nullptr);
+ EXPECT_EQ(
+ transf->exportToPROJString(
+ PROJStringFormatter::create(PROJStringFormatter::Convention::PROJ_5)
+ .get()),
+ projString);
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(io, projparse_helmert_time_dependent_coordinate_frame) {
+ std::string projString("+proj=helmert +x=1 +y=2 +z=3 +rx=4 +ry=5 +rz=6 "
+ "+s=7 +dx=0.1 +dy=0.2 +dz=0.3 +drx=0.4 +dry=0.5 "
+ "+drz=0.6 +ds=0.7 +t_epoch=2018.5 "
+ "+convention=coordinate_frame");
+ auto obj = PROJStringParser().createFromPROJString(projString);
+ auto transf = nn_dynamic_pointer_cast<Transformation>(obj);
+ ASSERT_TRUE(transf != nullptr);
+ EXPECT_EQ(
+ transf->exportToPROJString(
+ PROJStringFormatter::create(PROJStringFormatter::Convention::PROJ_5)
+ .get()),
+ projString);
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(io, projparse_helmert_complex_pipeline) {
+ std::string projString(
+ "+proj=pipeline +step +proj=cart "
+ "+ellps=WGS84 +step +proj=helmert +x=-1 +y=-2 +z=-3 +rx=-4 "
+ "+ry=-5 +rz=-6 +s=-7 +convention=position_vector +step +inv "
+ "+proj=cart +ellps=clrk80ign");
+ auto obj = PROJStringParser().createFromPROJString(projString);
+ auto transf = nn_dynamic_pointer_cast<Transformation>(obj);
+ ASSERT_TRUE(transf != nullptr);
+ EXPECT_EQ(
+ transf->exportToPROJString(
+ PROJStringFormatter::create(PROJStringFormatter::Convention::PROJ_5)
+ .get()),
+ "+proj=pipeline +step +proj=unitconvert +xy_in=deg +xy_out=rad "
+ "+step +proj=cart +ellps=WGS84 +step +proj=helmert +x=-1 +y=-2 "
+ "+z=-3 +rx=-4 +ry=-5 +rz=-6 +s=-7 +convention=position_vector "
+ "+step +inv +proj=cart +ellps=clrk80ign +step +proj=unitconvert "
+ "+xy_in=rad +xy_out=deg");
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(io, projparse_helmert_complex_pipeline_2) {
+ std::string projString(
+ "+proj=pipeline +step +proj=axisswap +order=2,1 +step "
+ "+proj=unitconvert +xy_in=deg +xy_out=rad +step +proj=cart "
+ "+ellps=WGS84 +step +proj=helmert +x=-1 +y=-2 +z=-3 +rx=-4 "
+ "+ry=-5 +rz=-6 +s=-7 +convention=position_vector +step +inv "
+ "+proj=cart +ellps=clrk80ign +step "
+ "+proj=longlat +ellps=clrk80ign +pm=paris +step "
+ "+proj=unitconvert +xy_in=rad +xy_out=grad +step +proj=axisswap "
+ "+order=2,1");
+ auto obj = PROJStringParser().createFromPROJString(projString);
+ auto transf = nn_dynamic_pointer_cast<Transformation>(obj);
+ ASSERT_TRUE(transf != nullptr);
+ EXPECT_EQ(
+ transf->exportToPROJString(
+ PROJStringFormatter::create(PROJStringFormatter::Convention::PROJ_5)
+ .get()),
+ projString);
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(io, projparse_helmert_errors) {
+ // Missing convention
+ EXPECT_THROW(PROJStringParser().createFromPROJString("+proj=helmert +rx=4"),
+ ParsingException);
+
+ EXPECT_THROW(PROJStringParser().createFromPROJString(
+ "+proj=helmert +convention=unhandled"),
+ ParsingException);
+
+ EXPECT_THROW(PROJStringParser().createFromPROJString(
+ "+proj=helmert +unhandled_keyword"),
+ ParsingException);
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(io, projparse_molodensky) {
+ std::string projString("+proj=molodensky +ellps=WGS84 +dx=84.87 +dy=96.49 "
+ "+dz=116.95 +da=251 +df=1.41927e-05");
+ auto obj = PROJStringParser().createFromPROJString(projString);
+ auto transf = nn_dynamic_pointer_cast<Transformation>(obj);
+ ASSERT_TRUE(transf != nullptr);
+
+ WKTFormatterNNPtr f(WKTFormatter::create());
+ f->simulCurNodeHasId();
+ f->setMultiLine(false);
+ transf->exportToWKT(f.get());
+ auto wkt = f->toString();
+ EXPECT_EQ(
+ wkt,
+ "COORDINATEOPERATION[\"unknown\",SOURCECRS[GEODCRS[\"unknown\",DATUM["
+ "\"Unknown based on WGS84 ellipsoid\",ELLIPSOID[\"WGS "
+ "84\",6378137,298.257223563,LENGTHUNIT[\"metre\",1]]],PRIMEM["
+ "\"Greenwich\",0,ANGLEUNIT[\"degree\",0.0174532925199433]],CS["
+ "ellipsoidal,2],AXIS[\"longitude\",east,ORDER[1],ANGLEUNIT[\"degree\","
+ "0.0174532925199433]],AXIS[\"latitude\",north,ORDER[2],ANGLEUNIT["
+ "\"degree\",0.0174532925199433]]]],TARGETCRS[GEODCRS[\"unknown\",DATUM["
+ "\"unknown\",ELLIPSOID[\"unknown\",6378388,297.000000198989,LENGTHUNIT["
+ "\"metre\",1]]],PRIMEM[\"Greenwich\",0,ANGLEUNIT[\"degree\",0."
+ "0174532925199433]],CS[ellipsoidal,2],AXIS[\"longitude\",east,ORDER[1],"
+ "ANGLEUNIT[\"degree\",0.0174532925199433]],AXIS[\"latitude\",north,"
+ "ORDER[2],ANGLEUNIT[\"degree\",0.0174532925199433]]]],METHOD["
+ "\"Molodensky\",ID[\"EPSG\",9604]],PARAMETER[\"X-axis "
+ "translation\",84.87,LENGTHUNIT[\"metre\",1],ID[\"EPSG\",8605]],"
+ "PARAMETER[\"Y-axis "
+ "translation\",96.49,LENGTHUNIT[\"metre\",1],ID[\"EPSG\",8606]],"
+ "PARAMETER[\"Z-axis "
+ "translation\",116.95,LENGTHUNIT[\"metre\",1],ID[\"EPSG\",8607]],"
+ "PARAMETER[\"Semi-major axis length "
+ "difference\",251,LENGTHUNIT[\"metre\",1],ID[\"EPSG\",8654]],PARAMETER["
+ "\"Flattening difference\",1.41927e-05,ID[\"EPSG\",8655]]]");
+
+ EXPECT_EQ(
+ transf->exportToPROJString(
+ PROJStringFormatter::create(PROJStringFormatter::Convention::PROJ_5)
+ .get()),
+ "+proj=pipeline +step +proj=unitconvert +xy_in=deg +xy_out=rad "
+ "+step +proj=molodensky +ellps=WGS84 +dx=84.87 +dy=96.49 "
+ "+dz=116.95 +da=251 +df=1.41927e-05 +step +proj=longlat "
+ "+a=6378388 +rf=297.000000198989 +step +proj=unitconvert "
+ "+xy_in=rad +xy_out=deg");
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(io, projparse_molodensky_inv) {
+ std::string projString("+proj=pipeline +step +inv +proj=molodensky "
+ "+ellps=WGS84 +dx=84.87 +dy=96.49 "
+ "+dz=116.95 +da=251 +df=1.41927e-05");
+ auto obj = PROJStringParser().createFromPROJString(projString);
+ auto transf = nn_dynamic_pointer_cast<CoordinateOperation>(obj);
+ ASSERT_TRUE(transf != nullptr);
+ EXPECT_EQ(
+ transf->exportToPROJString(
+ PROJStringFormatter::create(PROJStringFormatter::Convention::PROJ_5)
+ .get()),
+ "+proj=pipeline +step +proj=unitconvert +xy_in=deg +xy_out=rad "
+ "+step +inv +proj=longlat +a=6378388 +rf=297.000000198989 +step "
+ "+proj=molodensky +a=6378388 +rf=297.000000198989 +dx=-84.87 "
+ "+dy=-96.49 +dz=-116.95 +da=-251 +df=-1.41927e-05 +step "
+ "+proj=unitconvert +xy_in=rad +xy_out=deg");
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(io, projparse_molodensky_abridged) {
+ std::string projString("+proj=molodensky +ellps=WGS84 +dx=84.87 +dy=96.49 "
+ "+dz=116.95 +da=251 +df=1.41927e-05 +abridged");
+ auto obj = PROJStringParser().createFromPROJString(projString);
+ auto transf = nn_dynamic_pointer_cast<Transformation>(obj);
+ ASSERT_TRUE(transf != nullptr);
+ EXPECT_EQ(
+ transf->exportToPROJString(
+ PROJStringFormatter::create(PROJStringFormatter::Convention::PROJ_5)
+ .get()),
+ "+proj=pipeline +step +proj=unitconvert +xy_in=deg +xy_out=rad "
+ "+step +proj=molodensky +ellps=WGS84 +dx=84.87 +dy=96.49 "
+ "+dz=116.95 +da=251 +df=1.41927e-05 +abridged +step +proj=longlat "
+ "+a=6378388 +rf=297.000000198989 +step "
+ "+proj=unitconvert +xy_in=rad +xy_out=deg");
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(io, projparse_molodensky_complex_pipeline) {
+ std::string projString(
+ "+proj=pipeline +step +proj=unitconvert +xy_in=deg +xy_out=rad "
+ "+proj=longlat +ellps=WGS84 +step +proj=molodensky +ellps=WGS84 "
+ "+dx=84.87 +dy=96.49 "
+ "+dz=116.95 +da=251 +df=1.41927e-05 +step +proj=longlat "
+ "+ellps=GRS80 +step +proj=unitconvert "
+ "+xy_in=rad +xy_out=deg");
+ auto obj = PROJStringParser().createFromPROJString(projString);
+ auto transf = nn_dynamic_pointer_cast<Transformation>(obj);
+ ASSERT_TRUE(transf != nullptr);
+
+ WKTFormatterNNPtr f(WKTFormatter::create());
+ f->simulCurNodeHasId();
+ f->setMultiLine(false);
+ transf->exportToWKT(f.get());
+ auto wkt = f->toString();
+ EXPECT_TRUE(wkt.find("SOURCECRS[GEODCRS[\"unknown\",DATUM[\"Unknown based "
+ "on WGS84 ellipsoid\"") != std::string::npos)
+ << wkt;
+ EXPECT_TRUE(wkt.find("TARGETCRS[GEODCRS[\"unknown\",DATUM[\"Unknown based "
+ "on GRS80 ellipsoid\"") != std::string::npos)
+ << wkt;
+
+ EXPECT_EQ(
+ transf->exportToPROJString(
+ PROJStringFormatter::create(PROJStringFormatter::Convention::PROJ_5)
+ .get()),
+ "+proj=pipeline +step +proj=unitconvert +xy_in=deg +xy_out=rad "
+ "+step +proj=molodensky +ellps=WGS84 +dx=84.87 +dy=96.49 "
+ "+dz=116.95 +da=251 +df=1.41927e-05 +step +proj=unitconvert "
+ "+xy_in=rad +xy_out=deg");
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(io, projparse_longlat_title) {
+ std::string projString("+title=Ile d'Amsterdam 1963 +proj=longlat "
+ "+towgs84=109.7530,-528.1330,-362.2440 "
+ "+a=6378388.0000 +rf=297.0000000000000 +units=m "
+ "+no_defs");
+ auto obj = PROJStringParser().createFromPROJString(projString);
+ auto crs = nn_dynamic_pointer_cast<BoundCRS>(obj);
+ ASSERT_TRUE(crs != nullptr);
+ auto baseCRS = nn_dynamic_pointer_cast<GeographicCRS>(crs->baseCRS());
+ ASSERT_TRUE(baseCRS != nullptr);
+ EXPECT_EQ(baseCRS->nameStr(), "Ile d'Amsterdam 1963");
+ EXPECT_EQ(baseCRS->datum()->nameStr(), "Ile d'Amsterdam 1963");
+ EXPECT_EQ(
+ crs->exportToPROJString(
+ PROJStringFormatter::create(PROJStringFormatter::Convention::PROJ_4)
+ .get()),
+ "+proj=longlat +ellps=intl +towgs84=109.753,-528.133,-362.244,0,0,0,0");
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(io, projparse_projected_title) {
+ std::string projString(
+ "+title=Amsterdam 1963 +proj=tmerc "
+ "+towgs84=109.7530,-528.1330,-362.2440 +a=6378388.0000 "
+ "+rf=297.0000000000000 +lat_0=0.000000000 +lon_0=75.000000000 "
+ "+k_0=0.99960000 +x_0=500000.000 +y_0=10000000.000 +units=m +no_defs");
+ auto obj = PROJStringParser().createFromPROJString(projString);
+ auto crs = nn_dynamic_pointer_cast<BoundCRS>(obj);
+ ASSERT_TRUE(crs != nullptr);
+ auto baseCRS = nn_dynamic_pointer_cast<ProjectedCRS>(crs->baseCRS());
+ ASSERT_TRUE(baseCRS != nullptr);
+ EXPECT_EQ(baseCRS->nameStr(), "Amsterdam 1963");
+ EXPECT_EQ(baseCRS->baseCRS()->nameStr(), "unknown");
+ EXPECT_EQ(
+ crs->exportToPROJString(
+ PROJStringFormatter::create(PROJStringFormatter::Convention::PROJ_4)
+ .get()),
+ "+proj=utm +zone=43 +south +ellps=intl "
+ "+towgs84=109.753,-528.133,-362.244,0,0,0,0");
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(io, projparse_init) {
+
+ {
+ auto obj = PROJStringParser().createFromPROJString("init=epsg:4326");
+ auto co = nn_dynamic_pointer_cast<CoordinateOperation>(obj);
+ ASSERT_TRUE(co != nullptr);
+ EXPECT_EQ(co->exportToPROJString(PROJStringFormatter::create().get()),
+ "+init=epsg:4326");
+ }
+
+ {
+ auto obj = PROJStringParser().createFromPROJString(
+ "title=mytitle init=epsg:4326 ellps=WGS84");
+ auto co = nn_dynamic_pointer_cast<CoordinateOperation>(obj);
+ ASSERT_TRUE(co != nullptr);
+ EXPECT_EQ(co->nameStr(), "mytitle");
+ EXPECT_EQ(co->exportToPROJString(PROJStringFormatter::create().get()),
+ "+init=epsg:4326 +ellps=WGS84");
+ }
+
+ {
+ auto obj = PROJStringParser().createFromPROJString(
+ "proj=pipeline step init=epsg:4326 step proj=longlat");
+ auto co = nn_dynamic_pointer_cast<CoordinateOperation>(obj);
+ ASSERT_TRUE(co != nullptr);
+ EXPECT_EQ(co->exportToPROJString(PROJStringFormatter::create().get()),
+ "+proj=pipeline +step +init=epsg:4326 +step +proj=longlat");
+ }
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(io, projparse_errors) {
+ EXPECT_THROW(PROJStringParser().createFromPROJString(""), ParsingException);
+
+ EXPECT_THROW(PROJStringParser().createFromPROJString("foo"),
+ ParsingException);
+
+ EXPECT_THROW(PROJStringParser().createFromPROJString("inv"),
+ ParsingException);
+
+ EXPECT_THROW(PROJStringParser().createFromPROJString("step"),
+ ParsingException);
+
+ EXPECT_THROW(PROJStringParser().createFromPROJString("proj=unknown"),
+ ParsingException);
+
+ EXPECT_THROW(
+ PROJStringParser().createFromPROJString(
+ "proj=pipeline step proj=unitconvert step proj=longlat a=invalid"),
+ ParsingException);
+
+ EXPECT_THROW(PROJStringParser().createFromPROJString(
+ "proj=pipeline step proj=pipeline"),
+ ParsingException);
+
+ EXPECT_THROW(PROJStringParser().createFromPROJString(
+ "proj=pipeline step init=epsg:4326 init=epsg:4326"),
+ ParsingException);
+
+ EXPECT_THROW(PROJStringParser().createFromPROJString(
+ "proj=pipeline step init=epsg:4326 proj=longlat"),
+ ParsingException);
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(io, projparse_longlat_errors) {
+ EXPECT_THROW(
+ PROJStringParser().createFromPROJString("+proj=longlat +datum=unknown"),
+ ParsingException);
+
+ EXPECT_THROW(
+ PROJStringParser().createFromPROJString("+proj=longlat +ellps=unknown"),
+ ParsingException);
+
+ EXPECT_THROW(PROJStringParser().createFromPROJString(
+ "+proj=longlat +a=invalid +b=1"),
+ ParsingException);
+
+ EXPECT_THROW(PROJStringParser().createFromPROJString(
+ "+proj=longlat +a=1 +b=invalid"),
+ ParsingException);
+
+ EXPECT_THROW(PROJStringParser().createFromPROJString(
+ "+proj=longlat +a=invalid +rf=1"),
+ ParsingException);
+
+ EXPECT_THROW(PROJStringParser().createFromPROJString(
+ "+proj=longlat +a=1 +rf=invalid"),
+ ParsingException);
+
+ EXPECT_THROW(
+ PROJStringParser().createFromPROJString("+proj=longlat +R=invalid"),
+ ParsingException);
+
+ EXPECT_THROW(PROJStringParser().createFromPROJString("+proj=longlat +a=1"),
+ ParsingException);
+
+ EXPECT_THROW(PROJStringParser().createFromPROJString("+proj=longlat +b=1"),
+ ParsingException);
+
+ EXPECT_THROW(PROJStringParser().createFromPROJString("+proj=longlat +rf=1"),
+ ParsingException);
+
+ EXPECT_THROW(
+ PROJStringParser().createFromPROJString("+proj=longlat +pm=unknown"),
+ ParsingException);
+
+ EXPECT_THROW(
+ PROJStringParser().createFromPROJString(
+ "+proj=longlat +ellps=GRS80 +towgs84=1.2,2,3,4,5,6,invalid"),
+ ParsingException);
+
+ EXPECT_THROW(
+ PROJStringParser().createFromPROJString("+proj=longlat +axis=foo"),
+ ParsingException);
+
+ EXPECT_THROW(PROJStringParser().createFromPROJString(
+ "+proj=pipeline +step +proj=longlat +ellps=GRS80 +step "
+ "+proj=unitconvert +xy_in=rad +xy_out=foo"),
+ ParsingException);
+
+ EXPECT_THROW(PROJStringParser().createFromPROJString(
+ "+proj=pipeline +step +proj=longlat +ellps=GRS80 +step "
+ "+proj=axisswap"),
+ ParsingException);
+
+ EXPECT_THROW(PROJStringParser().createFromPROJString(
+ "+proj=pipeline +step +proj=longlat +ellps=GRS80 +step "
+ "+proj=axisswap +order=0,0"),
+ ParsingException);
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(io, projparse_projected_errors) {
+ EXPECT_THROW(
+ PROJStringParser().createFromPROJString("+proj=tmerc +units=foo"),
+ ParsingException);
+ EXPECT_THROW(
+ PROJStringParser().createFromPROJString("+proj=tmerc +x_0=foo"),
+ ParsingException);
+ EXPECT_THROW(
+ PROJStringParser().createFromPROJString("+proj=tmerc +lat_0=foo"),
+ ParsingException);
+ // Inconsitent pm values between geogCRS and projectedCRS
+ EXPECT_THROW(PROJStringParser().createFromPROJString(
+ "+proj=pipeline +step +proj=longlat +ellps=WGS84 "
+ "+proj=tmerc +ellps=WGS84 +lat_0=foo +pm=paris"),
+ ParsingException);
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(io, createFromUserInput) {
+ auto dbContext = DatabaseContext::create();
+ EXPECT_THROW(createFromUserInput("foo", nullptr), ParsingException);
+ EXPECT_THROW(createFromUserInput("GEOGCRS", nullptr), ParsingException);
+ EXPECT_THROW(createFromUserInput("+proj=unhandled", nullptr),
+ ParsingException);
+ EXPECT_THROW(createFromUserInput("EPSG:4326", nullptr), ParsingException);
+ EXPECT_THROW(
+ createFromUserInput("urn:ogc:def:unhandled:EPSG::4326", dbContext),
+ ParsingException);
+
+ EXPECT_NO_THROW(createFromUserInput("+proj=longlat", nullptr));
+ EXPECT_NO_THROW(createFromUserInput("EPSG:4326", dbContext));
+ EXPECT_NO_THROW(
+ createFromUserInput("urn:ogc:def:crs:EPSG::4326", dbContext));
+ EXPECT_NO_THROW(createFromUserInput(
+ "urn:ogc:def:coordinateOperation:EPSG::1671", dbContext));
+ EXPECT_NO_THROW(
+ createFromUserInput("urn:ogc:def:datum:EPSG::6326", dbContext));
+ EXPECT_NO_THROW(
+ createFromUserInput("urn:ogc:def:meridian:EPSG::8901", dbContext));
+ EXPECT_NO_THROW(
+ createFromUserInput("urn:ogc:def:ellipsoid:EPSG::7030", dbContext));
+ EXPECT_NO_THROW(createFromUserInput(
+ "GEOGCRS[\"WGS 84\",\n"
+ " DATUM[\"World Geodetic System 1984\",\n"
+ " ELLIPSOID[\"WGS 84\",6378137,298.257223563]],\n"
+ " CS[ellipsoidal,3],\n"
+ " AXIS[\"latitude\",north,\n"
+ " UNIT[\"degree\",0.0174532925199433]],\n"
+ " AXIS[\"longitude\",east,\n"
+ " UNIT[\"degree\",0.0174532925199433]],\n"
+ " AXIS[\"ellipsoidal height\",up,\n"
+ " UNIT[\"metre\",1]],\n"
+ " ID[\"EPSG\",4979]]",
+ nullptr));
+
+ // Search names in the database
+ EXPECT_THROW(createFromUserInput("foobar", dbContext), ParsingException);
+ EXPECT_NO_THROW(createFromUserInput("WGS 84", dbContext));
+ EXPECT_NO_THROW(createFromUserInput("WGS84", dbContext));
+ EXPECT_NO_THROW(createFromUserInput("UTM zone 31N", dbContext));
+ EXPECT_THROW(createFromUserInput("UTM zone 31", dbContext),
+ ParsingException);
+ EXPECT_NO_THROW(createFromUserInput("WGS84 UTM zone 31N", dbContext));
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(io, guessDialect) {
+ EXPECT_EQ(WKTParser().guessDialect("LOCAL_CS[\"foo\"]"),
+ WKTParser::WKTGuessedDialect::WKT1_GDAL);
+
+ EXPECT_EQ(WKTParser().guessDialect(
+ "GEOGCS[\"GCS_WGS_1984\",DATUM[\"D_WGS_1984\",SPHEROID[\"WGS_"
+ "1984\",6378137.0,298.257223563]],PRIMEM[\"Greenwich\",0.0],"
+ "UNIT[\"Degree\",0.0174532925199433]]"),
+ WKTParser::WKTGuessedDialect::WKT1_ESRI);
+
+ EXPECT_EQ(WKTParser().guessDialect(
+ "GEOGCRS[\"WGS 84\",\n"
+ " DATUM[\"World Geodetic System 1984\",\n"
+ " ELLIPSOID[\"WGS 84\",6378137,298.257223563]],\n"
+ " CS[ellipsoidal,2],\n"
+ " AXIS[\"geodetic latitude (Lat)\",north],\n"
+ " AXIS[\"geodetic longitude (Lon)\",east],\n"
+ " UNIT[\"degree\",0.0174532925199433]]"),
+ WKTParser::WKTGuessedDialect::WKT2_2018);
+
+ EXPECT_EQ(
+ WKTParser().guessDialect("TIMECRS[\"Temporal CRS\",\n"
+ " TDATUM[\"Gregorian calendar\",\n"
+ " CALENDAR[\"proleptic Gregorian\"],\n"
+ " TIMEORIGIN[0000-01-01]],\n"
+ " CS[TemporalDateTime,1],\n"
+ " AXIS[\"time (T)\",future]]"),
+ WKTParser::WKTGuessedDialect::WKT2_2018);
+
+ EXPECT_EQ(WKTParser().guessDialect(
+ "GEODCRS[\"WGS 84\",\n"
+ " DATUM[\"World Geodetic System 1984\",\n"
+ " ELLIPSOID[\"WGS 84\",6378137,298.257223563]],\n"
+ " CS[ellipsoidal,2],\n"
+ " AXIS[\"geodetic latitude (Lat)\",north],\n"
+ " AXIS[\"geodetic longitude (Lon)\",east],\n"
+ " UNIT[\"degree\",0.0174532925199433]]"),
+ WKTParser::WKTGuessedDialect::WKT2_2015);
+
+ EXPECT_EQ(WKTParser().guessDialect("foo"),
+ WKTParser::WKTGuessedDialect::NOT_WKT);
+}