From 5527b10ed140e20fac8e183317514fd59e4c8b99 Mon Sep 17 00:00:00 2001 From: Even Rouault Date: Tue, 7 Sep 2021 19:57:36 +0200 Subject: Support importing/exporting WKT & PROJJSON of 2D axis spherical planetocentric geodetic CRS --- include/proj/coordinatesystem.hpp | 5 ++ include/proj/crs.hpp | 2 + scripts/reference_exported_symbols.txt | 2 + src/iso19111/coordinatesystem.cpp | 21 +++++++++ src/iso19111/crs.cpp | 27 ++++++++++- src/iso19111/io.cpp | 33 ++++++++----- test/unit/test_io.cpp | 84 ++++++++++++++++++++++++++++++++++ 7 files changed, 161 insertions(+), 13 deletions(-) diff --git a/include/proj/coordinatesystem.hpp b/include/proj/coordinatesystem.hpp index e1650168..b40b038d 100644 --- a/include/proj/coordinatesystem.hpp +++ b/include/proj/coordinatesystem.hpp @@ -314,6 +314,11 @@ class PROJ_GCC_DLL SphericalCS final : public CoordinateSystem { const CoordinateSystemAxisNNPtr &axis2, const CoordinateSystemAxisNNPtr &axis3); + PROJ_DLL static SphericalCSNNPtr + create(const util::PropertyMap &properties, + const CoordinateSystemAxisNNPtr &axis1, + const CoordinateSystemAxisNNPtr &axis2); + protected: PROJ_INTERNAL explicit SphericalCS( const std::vector &axisIn); diff --git a/include/proj/crs.hpp b/include/proj/crs.hpp index dcab094a..7fde88c8 100644 --- a/include/proj/crs.hpp +++ b/include/proj/crs.hpp @@ -270,6 +270,8 @@ class PROJ_GCC_DLL GeodeticCRS : virtual public SingleCRS, PROJ_DLL bool isGeocentric() PROJ_PURE_DECL; + PROJ_DLL bool isSphericalPlanetocentric() PROJ_PURE_DECL; + PROJ_DLL static GeodeticCRSNNPtr create(const util::PropertyMap &properties, const datum::GeodeticReferenceFrameNNPtr &datum, diff --git a/scripts/reference_exported_symbols.txt b/scripts/reference_exported_symbols.txt index d49a59b2..6cb734e2 100644 --- a/scripts/reference_exported_symbols.txt +++ b/scripts/reference_exported_symbols.txt @@ -152,6 +152,7 @@ osgeo::proj::crs::GeodeticCRS::ellipsoid() const osgeo::proj::crs::GeodeticCRS::~GeodeticCRS() osgeo::proj::crs::GeodeticCRS::identify(std::shared_ptr const&) const osgeo::proj::crs::GeodeticCRS::isGeocentric() const +osgeo::proj::crs::GeodeticCRS::isSphericalPlanetocentric() const osgeo::proj::crs::GeodeticCRS::primeMeridian() const osgeo::proj::crs::GeodeticCRS::velocityModel() const osgeo::proj::crs::GeographicCRS::coordinateSystem() const @@ -225,6 +226,7 @@ osgeo::proj::cs::OrdinalCS::create(osgeo::proj::util::PropertyMap const&, std::v osgeo::proj::cs::OrdinalCS::~OrdinalCS() osgeo::proj::cs::ParametricCS::create(osgeo::proj::util::PropertyMap const&, dropbox::oxygen::nn > const&) osgeo::proj::cs::ParametricCS::~ParametricCS() +osgeo::proj::cs::SphericalCS::create(osgeo::proj::util::PropertyMap const&, dropbox::oxygen::nn > const&, dropbox::oxygen::nn > const&) osgeo::proj::cs::SphericalCS::create(osgeo::proj::util::PropertyMap const&, dropbox::oxygen::nn > const&, dropbox::oxygen::nn > const&, dropbox::oxygen::nn > const&) osgeo::proj::cs::SphericalCS::~SphericalCS() osgeo::proj::cs::TemporalCountCS::create(osgeo::proj::util::PropertyMap const&, dropbox::oxygen::nn > const&) diff --git a/src/iso19111/coordinatesystem.cpp b/src/iso19111/coordinatesystem.cpp index 498e3035..f9db5406 100644 --- a/src/iso19111/coordinatesystem.cpp +++ b/src/iso19111/coordinatesystem.cpp @@ -667,6 +667,27 @@ SphericalCSNNPtr SphericalCS::create(const util::PropertyMap &properties, // --------------------------------------------------------------------------- +/** \brief Instantiate a SphericalCS with 2 axis. + * + * This is an extension to ISO19111 to support (planet)-ocentric CS with + * geocentric latitude. + * + * @param properties See \ref general_properties. + * @param axis1 The first axis. + * @param axis2 The second axis. + * @return a new SphericalCS. + */ +SphericalCSNNPtr SphericalCS::create(const util::PropertyMap &properties, + const CoordinateSystemAxisNNPtr &axis1, + const CoordinateSystemAxisNNPtr &axis2) { + std::vector axis{axis1, axis2}; + auto cs(SphericalCS::nn_make_shared(axis)); + cs->setProperties(properties); + return cs; +} + +// --------------------------------------------------------------------------- + //! @cond Doxygen_Suppress EllipsoidalCS::~EllipsoidalCS() = default; //! @endcond diff --git a/src/iso19111/crs.cpp b/src/iso19111/crs.cpp index 7c8fcd81..6f511043 100644 --- a/src/iso19111/crs.cpp +++ b/src/iso19111/crs.cpp @@ -1608,7 +1608,7 @@ GeodeticCRS::velocityModel() PROJ_PURE_DEFN { // --------------------------------------------------------------------------- -/** \brief Return whether the CRS is a geocentric one. +/** \brief Return whether the CRS is a Cartesian geocentric one. * * A geocentric CRS is a geodetic CRS that has a Cartesian coordinate system * with three axis, whose direction is respectively @@ -1629,6 +1629,31 @@ bool GeodeticCRS::isGeocentric() PROJ_PURE_DEFN { // --------------------------------------------------------------------------- +/** \brief Return whether the CRS is a Spherical planetocentric one. + * + * A Spherical planetocentric CRS is a geodetic CRS that has a spherical + * (angular) coordinate system with 2 axis, which represent geocentric latitude/ + * longitude or longitude/geocentric latitude. + * + * Such CRS are typically used in use case that apply to non-Earth bodies. + * + * @return true if the CRS is a Spherical planetocentric CRS. + * + * @since 8.2 + */ +bool GeodeticCRS::isSphericalPlanetocentric() PROJ_PURE_DEFN { + const auto &cs = coordinateSystem(); + const auto &axisList = cs->axisList(); + return axisList.size() == 2 && + dynamic_cast(cs.get()) != nullptr && + ((ci_equal(axisList[0]->nameStr(), "planetocentric latitude") && + ci_equal(axisList[1]->nameStr(), "planetocentric longitude")) || + (ci_equal(axisList[0]->nameStr(), "planetocentric longitude") && + ci_equal(axisList[1]->nameStr(), "planetocentric latitude"))); +} + +// --------------------------------------------------------------------------- + /** \brief Instantiate a GeodeticCRS from a datum::GeodeticReferenceFrame and a * cs::SphericalCS. * diff --git a/src/iso19111/io.cpp b/src/iso19111/io.cpp index a1b06ae7..1373c583 100644 --- a/src/iso19111/io.cpp +++ b/src/iso19111/io.cpp @@ -2798,7 +2798,11 @@ WKTParser::Private::buildCS(const WKTNodeNNPtr &node, /* maybe null */ return VerticalCS::create(csMap, axisList[0]); } } else if (ci_equal(csType, "spherical")) { - if (axisCount == 3) { + if (axisCount == 2) { + // Extension to ISO19111 to support (planet)-ocentric CS with + // geocentric latitude + return SphericalCS::create(csMap, axisList[0], axisList[1]); + } else if (axisCount == 3) { return SphericalCS::create(csMap, axisList[0], axisList[1], axisList[2]); } @@ -5945,62 +5949,67 @@ CoordinateSystemNNPtr JSONParser::buildCS(const json &j) { axisList.emplace_back(buildAxis(axis)); } const PropertyMap &csMap = emptyPropertyMap; + const auto axisCount = axisList.size(); if (subtype == "ellipsoidal") { - if (axisList.size() == 2) { + if (axisCount == 2) { return EllipsoidalCS::create(csMap, axisList[0], axisList[1]); } - if (axisList.size() == 3) { + if (axisCount == 3) { return EllipsoidalCS::create(csMap, axisList[0], axisList[1], axisList[2]); } throw ParsingException("Expected 2 or 3 axis"); } if (subtype == "Cartesian") { - if (axisList.size() == 2) { + if (axisCount == 2) { return CartesianCS::create(csMap, axisList[0], axisList[1]); } - if (axisList.size() == 3) { + if (axisCount == 3) { return CartesianCS::create(csMap, axisList[0], axisList[1], axisList[2]); } throw ParsingException("Expected 2 or 3 axis"); } if (subtype == "vertical") { - if (axisList.size() == 1) { + if (axisCount == 1) { return VerticalCS::create(csMap, axisList[0]); } throw ParsingException("Expected 1 axis"); } if (subtype == "spherical") { - if (axisList.size() == 3) { + if (axisCount == 2) { + // Extension to ISO19111 to support (planet)-ocentric CS with + // geocentric latitude + return SphericalCS::create(csMap, axisList[0], axisList[1]); + } else if (axisCount == 3) { return SphericalCS::create(csMap, axisList[0], axisList[1], axisList[2]); } - throw ParsingException("Expected 3 axis"); + throw ParsingException("Expected 2 or 3 axis"); } if (subtype == "ordinal") { return OrdinalCS::create(csMap, axisList); } if (subtype == "parametric") { - if (axisList.size() == 1) { + if (axisCount == 1) { return ParametricCS::create(csMap, axisList[0]); } throw ParsingException("Expected 1 axis"); } if (subtype == "TemporalDateTime") { - if (axisList.size() == 1) { + if (axisCount == 1) { return DateTimeTemporalCS::create(csMap, axisList[0]); } throw ParsingException("Expected 1 axis"); } if (subtype == "TemporalCount") { - if (axisList.size() == 1) { + if (axisCount == 1) { return TemporalCountCS::create(csMap, axisList[0]); } throw ParsingException("Expected 1 axis"); } if (subtype == "TemporalMeasure") { - if (axisList.size() == 1) { + if (axisCount == 1) { return TemporalMeasureCS::create(csMap, axisList[0]); } throw ParsingException("Expected 1 axis"); diff --git a/test/unit/test_io.cpp b/test/unit/test_io.cpp index 72ac1868..96e3a849 100644 --- a/test/unit/test_io.cpp +++ b/test/unit/test_io.cpp @@ -894,6 +894,37 @@ TEST(wkt_parse, wkt2_EPSG_4979) { // --------------------------------------------------------------------------- +TEST(wkt_parse, wkt2_spherical_planetocentric) { + const auto wkt = + "GEODCRS[\"Mercury (2015) / Ocentric\",\n" + " DATUM[\"Mercury (2015)\",\n" + " ELLIPSOID[\"Mercury (2015)\",2440530,1075.12334801762,\n" + " LENGTHUNIT[\"metre\",1]],\n" + " ANCHOR[\"Hun Kal: 20.0\"]],\n" + " PRIMEM[\"Reference Meridian\",0,\n" + " ANGLEUNIT[\"degree\",0.0174532925199433]],\n" + " CS[spherical,2],\n" + " AXIS[\"planetocentric latitude (U)\",north,\n" + " ORDER[1],\n" + " ANGLEUNIT[\"degree\",0.0174532925199433]],\n" + " AXIS[\"planetocentric longitude (V)\",east,\n" + " ORDER[2],\n" + " ANGLEUNIT[\"degree\",0.0174532925199433]],\n" + " ID[\"IAU\",19902,2015],\n" + " REMARK[\"Source of IAU Coordinate systems: " + "doi://10.1007/s10569-017-9805-5\"]]"; + auto obj = WKTParser().createFromWKT(wkt); + auto crs = nn_dynamic_pointer_cast(obj); + ASSERT_TRUE(crs != nullptr); + EXPECT_TRUE(crs->isSphericalPlanetocentric()); + EXPECT_EQ( + crs->exportToWKT( + WKTFormatter::create(WKTFormatter::Convention::WKT2_2019).get()), + wkt); +} + +// --------------------------------------------------------------------------- + static void checkGeocentric(GeodeticCRSPtr crs) { // Explicitly check this is NOT a GeographicCRS EXPECT_TRUE(!dynamic_cast(crs.get())); @@ -11476,6 +11507,59 @@ TEST(json_import, geographic_crs) { // --------------------------------------------------------------------------- +TEST(json_import, spherical_planetocentric) { + const auto json = "{\n" + " \"$schema\": \"foo\",\n" + " \"type\": \"GeodeticCRS\",\n" + " \"name\": \"Mercury (2015) / Ocentric\",\n" + " \"datum\": {\n" + " \"type\": \"GeodeticReferenceFrame\",\n" + " \"name\": \"Mercury (2015)\",\n" + " \"anchor\": \"Hun Kal: 20.0\",\n" + " \"ellipsoid\": {\n" + " \"name\": \"Mercury (2015)\",\n" + " \"semi_major_axis\": 2440530,\n" + " \"inverse_flattening\": 1075.12334801762\n" + " },\n" + " \"prime_meridian\": {\n" + " \"name\": \"Reference Meridian\",\n" + " \"longitude\": 0\n" + " }\n" + " },\n" + " \"coordinate_system\": {\n" + " \"subtype\": \"spherical\",\n" + " \"axis\": [\n" + " {\n" + " \"name\": \"Planetocentric latitude\",\n" + " \"abbreviation\": \"U\",\n" + " \"direction\": \"north\",\n" + " \"unit\": \"degree\"\n" + " },\n" + " {\n" + " \"name\": \"Planetocentric longitude\",\n" + " \"abbreviation\": \"V\",\n" + " \"direction\": \"east\",\n" + " \"unit\": \"degree\"\n" + " }\n" + " ]\n" + " },\n" + " \"id\": {\n" + " \"authority\": \"IAU\",\n" + " \"code\": 19902\n" + " },\n" + " \"remarks\": \"Source of IAU Coordinate systems: " + "doi://10.1007/s10569-017-9805-5\"\n" + "}"; + auto obj = createFromUserInput(json, nullptr); + auto gcrs = nn_dynamic_pointer_cast(obj); + ASSERT_TRUE(gcrs != nullptr); + EXPECT_TRUE(gcrs->isSphericalPlanetocentric()); + EXPECT_EQ(gcrs->exportToJSON(&(JSONFormatter::create()->setSchema("foo"))), + json); +} + +// --------------------------------------------------------------------------- + TEST(json_import, geographic_crs_errors) { EXPECT_THROW( createFromUserInput( -- cgit v1.2.3