diff options
Diffstat (limited to 'src/iso19111')
| -rw-r--r-- | src/iso19111/coordinatesystem.cpp | 21 | ||||
| -rw-r--r-- | src/iso19111/crs.cpp | 176 | ||||
| -rw-r--r-- | src/iso19111/io.cpp | 185 | ||||
| -rw-r--r-- | src/iso19111/operation/conversion.cpp | 116 | ||||
| -rw-r--r-- | src/iso19111/operation/coordinateoperationfactory.cpp | 143 | ||||
| -rw-r--r-- | src/iso19111/operation/parammappings.cpp | 2 | ||||
| -rw-r--r-- | src/iso19111/operation/transformation.cpp | 49 |
7 files changed, 543 insertions, 149 deletions
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<CoordinateSystemAxisNNPtr> axis{axis1, axis2}; + auto cs(SphericalCS::nn_make_shared<SphericalCS>(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..be593878 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::SphericalCS *>(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. * @@ -1971,6 +1996,61 @@ void GeodeticCRS::addGeocentricUnitConversionIntoPROJString( // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress +void GeodeticCRS::addAngularUnitConvertAndAxisSwap( + io::PROJStringFormatter *formatter) const { + const auto &axisList = coordinateSystem()->axisList(); + + formatter->addStep("unitconvert"); + formatter->addParam("xy_in", "rad"); + if (axisList.size() == 3 && !formatter->omitZUnitConversion()) { + formatter->addParam("z_in", "m"); + } + { + const auto &unitHoriz = axisList[0]->unit(); + const auto projUnit = unitHoriz.exportToPROJString(); + if (projUnit.empty()) { + formatter->addParam("xy_out", unitHoriz.conversionToSI()); + } else { + formatter->addParam("xy_out", projUnit); + } + } + if (axisList.size() == 3 && !formatter->omitZUnitConversion()) { + const auto &unitZ = axisList[2]->unit(); + auto projVUnit = unitZ.exportToPROJString(); + if (projVUnit.empty()) { + formatter->addParam("z_out", unitZ.conversionToSI()); + } else { + formatter->addParam("z_out", projVUnit); + } + } + + const char *order[2] = {nullptr, nullptr}; + const char *one = "1"; + const char *two = "2"; + for (int i = 0; i < 2; i++) { + const auto &dir = axisList[i]->direction(); + if (&dir == &cs::AxisDirection::WEST) { + order[i] = "-1"; + } else if (&dir == &cs::AxisDirection::EAST) { + order[i] = one; + } else if (&dir == &cs::AxisDirection::SOUTH) { + order[i] = "-2"; + } else if (&dir == &cs::AxisDirection::NORTH) { + order[i] = two; + } + } + if (order[0] && order[1] && (order[0] != one || order[1] != two)) { + formatter->addStep("axisswap"); + char orderStr[10]; + sprintf(orderStr, "%.2s,%.2s", order[0], order[1]); + formatter->addParam("order", orderStr); + } +} +//! @endcond + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress void GeodeticCRS::_exportToPROJString( io::PROJStringFormatter *formatter) const // throw(io::FormattingException) { @@ -1982,19 +2062,38 @@ void GeodeticCRS::_exportToPROJString( return; } - if (!isGeocentric()) { - io::FormattingException::Throw( - "GeodeticCRS::exportToPROJString() only " - "supports geocentric coordinate systems"); - } + if (isGeocentric()) { + if (!formatter->getCRSExport()) { + formatter->addStep("cart"); + } else { + formatter->addStep("geocent"); + } - if (!formatter->getCRSExport()) { - formatter->addStep("cart"); + addDatumInfoToPROJString(formatter); + addGeocentricUnitConversionIntoPROJString(formatter); + } else if (isSphericalPlanetocentric()) { + if (!formatter->getCRSExport()) { + formatter->addStep("geoc"); + addDatumInfoToPROJString(formatter); + addAngularUnitConvertAndAxisSwap(formatter); + } else { + io::FormattingException::Throw( + "GeodeticCRS::exportToPROJString() not supported on spherical " + "planetocentric coordinate systems"); + // The below code now works as input to PROJ, but I'm not sure we + // want to propagate this, given that we got cs2cs doing conversion + // in the wrong direction in past versions. + /*formatter->addStep("longlat"); + formatter->addParam("geoc"); + + addDatumInfoToPROJString(formatter);*/ + } } else { - formatter->addStep("geocent"); + io::FormattingException::Throw( + "GeodeticCRS::exportToPROJString() only " + "supports geocentric or spherical planetocentric " + "coordinate systems"); } - addDatumInfoToPROJString(formatter); - addGeocentricUnitConversionIntoPROJString(formatter); } //! @endcond @@ -2859,61 +2958,6 @@ GeographicCRS::demoteTo2D(const std::string &newName, // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress -void GeographicCRS::addAngularUnitConvertAndAxisSwap( - io::PROJStringFormatter *formatter) const { - const auto &axisList = coordinateSystem()->axisList(); - - formatter->addStep("unitconvert"); - formatter->addParam("xy_in", "rad"); - if (axisList.size() == 3 && !formatter->omitZUnitConversion()) { - formatter->addParam("z_in", "m"); - } - { - const auto &unitHoriz = axisList[0]->unit(); - const auto projUnit = unitHoriz.exportToPROJString(); - if (projUnit.empty()) { - formatter->addParam("xy_out", unitHoriz.conversionToSI()); - } else { - formatter->addParam("xy_out", projUnit); - } - } - if (axisList.size() == 3 && !formatter->omitZUnitConversion()) { - const auto &unitZ = axisList[2]->unit(); - auto projVUnit = unitZ.exportToPROJString(); - if (projVUnit.empty()) { - formatter->addParam("z_out", unitZ.conversionToSI()); - } else { - formatter->addParam("z_out", projVUnit); - } - } - - const char *order[2] = {nullptr, nullptr}; - const char *one = "1"; - const char *two = "2"; - for (int i = 0; i < 2; i++) { - const auto &dir = axisList[i]->direction(); - if (&dir == &cs::AxisDirection::WEST) { - order[i] = "-1"; - } else if (&dir == &cs::AxisDirection::EAST) { - order[i] = one; - } else if (&dir == &cs::AxisDirection::SOUTH) { - order[i] = "-2"; - } else if (&dir == &cs::AxisDirection::NORTH) { - order[i] = two; - } - } - if (order[0] && order[1] && (order[0] != one || order[1] != two)) { - formatter->addStep("axisswap"); - char orderStr[10]; - sprintf(orderStr, "%.2s,%.2s", order[0], order[1]); - formatter->addParam("order", orderStr); - } -} -//! @endcond - -// --------------------------------------------------------------------------- - -//! @cond Doxygen_Suppress void GeographicCRS::_exportToPROJString( io::PROJStringFormatter *formatter) const // throw(io::FormattingException) { diff --git a/src/iso19111/io.cpp b/src/iso19111/io.cpp index a1b06ae7..5d7e951e 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"); @@ -8630,10 +8639,10 @@ struct PROJStringParser::Private { PrimeMeridianNNPtr buildPrimeMeridian(Step &step); GeodeticReferenceFrameNNPtr buildDatum(Step &step, const std::string &title); - GeographicCRSNNPtr buildGeographicCRS(int iStep, int iUnitConvert, - int iAxisSwap, bool ignorePROJAxis); + GeodeticCRSNNPtr buildGeodeticCRS(int iStep, int iUnitConvert, + int iAxisSwap, bool ignorePROJAxis); GeodeticCRSNNPtr buildGeocentricCRS(int iStep, int iUnitConvert); - CRSNNPtr buildProjectedCRS(int iStep, GeographicCRSNNPtr geogCRS, + CRSNNPtr buildProjectedCRS(int iStep, const GeodeticCRSNNPtr &geogCRS, int iUnitConvert, int iAxisSwap); CRSNNPtr buildBoundOrCompoundCRSIfNeeded(int iStep, CRSNNPtr crs); UnitOfMeasure buildUnit(Step &step, const std::string &unitsParamName, @@ -8647,6 +8656,9 @@ struct PROJStringParser::Private { EllipsoidalCSNNPtr buildEllipsoidalCS(int iStep, int iUnitConvert, int iAxisSwap, bool ignorePROJAxis); + + SphericalCSNNPtr buildSphericalCS(int iStep, int iUnitConvert, + int iAxisSwap, bool ignorePROJAxis); }; // --------------------------------------------------------------------------- @@ -9273,10 +9285,13 @@ PROJStringParser::Private::processAxisSwap(Step &step, assert(iAxisSwap < 0 || ci_equal(steps_[iAxisSwap].name, "axisswap")); const bool isGeographic = unit.type() == UnitOfMeasure::Type::ANGULAR; + const bool isSpherical = isGeographic && hasParamValue(step, "geoc"); const auto &eastName = - isGeographic ? AxisName::Longitude : AxisName::Easting; - const auto &eastAbbev = - isGeographic ? AxisAbbreviation::lon : AxisAbbreviation::E; + isSpherical ? "Planetocentric longitude" + : isGeographic ? AxisName::Longitude : AxisName::Easting; + const auto &eastAbbev = isSpherical ? "V" + : isGeographic ? AxisAbbreviation::lon + : AxisAbbreviation::E; const auto &eastDir = isGeographic ? AxisDirection::EAST : (axisType == AxisType::NORTH_POLE) @@ -9292,9 +9307,11 @@ PROJStringParser::Private::processAxisSwap(Step &step, : nullMeridian); const auto &northName = - isGeographic ? AxisName::Latitude : AxisName::Northing; - const auto &northAbbev = - isGeographic ? AxisAbbreviation::lat : AxisAbbreviation::N; + isSpherical ? "Planetocentric latitude" + : isGeographic ? AxisName::Latitude : AxisName::Northing; + const auto &northAbbev = isSpherical ? "U" + : isGeographic ? AxisAbbreviation::lat + : AxisAbbreviation::N; const auto &northDir = isGeographic ? AxisDirection::NORTH : (axisType == AxisType::NORTH_POLE) @@ -9311,15 +9328,19 @@ PROJStringParser::Private::processAxisSwap(Step &step, .as_nullable() : nullMeridian); - CoordinateSystemAxisNNPtr west = - createAxis(isGeographic ? AxisName::Longitude : AxisName::Westing, - isGeographic ? AxisAbbreviation::lon : std::string(), - AxisDirection::WEST, unit); + CoordinateSystemAxisNNPtr west = createAxis( + isSpherical ? "Planetocentric longitude" + : isGeographic ? AxisName::Longitude : AxisName::Westing, + isSpherical ? "V" + : isGeographic ? AxisAbbreviation::lon : std::string(), + AxisDirection::WEST, unit); - CoordinateSystemAxisNNPtr south = - createAxis(isGeographic ? AxisName::Latitude : AxisName::Southing, - isGeographic ? AxisAbbreviation::lat : std::string(), - AxisDirection::SOUTH, unit); + CoordinateSystemAxisNNPtr south = createAxis( + isSpherical ? "Planetocentric latitude" + : isGeographic ? AxisName::Latitude : AxisName::Southing, + isSpherical ? "U" + : isGeographic ? AxisAbbreviation::lat : std::string(), + AxisDirection::SOUTH, unit); std::vector<CoordinateSystemAxisNNPtr> axis{east, north}; @@ -9419,6 +9440,42 @@ EllipsoidalCSNNPtr PROJStringParser::Private::buildEllipsoidalCS( // --------------------------------------------------------------------------- +SphericalCSNNPtr PROJStringParser::Private::buildSphericalCS( + int iStep, int iUnitConvert, int iAxisSwap, bool ignorePROJAxis) { + auto &step = steps_[iStep]; + assert(iUnitConvert < 0 || + ci_equal(steps_[iUnitConvert].name, "unitconvert")); + + UnitOfMeasure angularUnit = UnitOfMeasure::DEGREE; + if (iUnitConvert >= 0) { + auto &stepUnitConvert = steps_[iUnitConvert]; + const std::string *xy_in = &getParamValue(stepUnitConvert, "xy_in"); + const std::string *xy_out = &getParamValue(stepUnitConvert, "xy_out"); + if (stepUnitConvert.inverted) { + std::swap(xy_in, xy_out); + } + if (iUnitConvert < iStep) { + std::swap(xy_in, xy_out); + } + if (xy_in->empty() || xy_out->empty() || *xy_in != "rad" || + (*xy_out != "rad" && *xy_out != "deg" && *xy_out != "grad")) { + throw ParsingException("unhandled values for xy_in and/or xy_out"); + } + if (*xy_out == "rad") { + angularUnit = UnitOfMeasure::RADIAN; + } else if (*xy_out == "grad") { + angularUnit = UnitOfMeasure::GRAD; + } + } + + std::vector<CoordinateSystemAxisNNPtr> axis = processAxisSwap( + step, angularUnit, iAxisSwap, AxisType::REGULAR, ignorePROJAxis); + + return SphericalCS::create(emptyPropertyMap, axis[0], axis[1]); +} + +// --------------------------------------------------------------------------- + static double getNumericValue(const std::string ¶mValue, bool *pHasError = nullptr) { try { @@ -9438,7 +9495,7 @@ namespace { template <class T> inline void ignoreRetVal(T) {} } // namespace -GeographicCRSNNPtr PROJStringParser::Private::buildGeographicCRS( +GeodeticCRSNNPtr PROJStringParser::Private::buildGeodeticCRS( int iStep, int iUnitConvert, int iAxisSwap, bool ignorePROJAxis) { auto &step = steps_[iStep]; @@ -9453,8 +9510,6 @@ GeographicCRSNNPtr PROJStringParser::Private::buildGeographicCRS( auto props = PropertyMap().set(IdentifiedObject::NAME_KEY, title.empty() ? "unknown" : title); - auto cs = - buildEllipsoidalCS(iStep, iUnitConvert, iAxisSwap, ignorePROJAxis); if (l_isGeographicStep && (hasUnusedParameters(step) || @@ -9463,7 +9518,17 @@ GeographicCRSNNPtr PROJStringParser::Private::buildGeographicCRS( } props.set("IMPLICIT_CS", true); - return GeographicCRS::create(props, datum, cs); + if (!hasParamValue(step, "geoc")) { + auto cs = + buildEllipsoidalCS(iStep, iUnitConvert, iAxisSwap, ignorePROJAxis); + + return GeographicCRS::create(props, datum, cs); + } else { + auto cs = + buildSphericalCS(iStep, iUnitConvert, iAxisSwap, ignorePROJAxis); + + return GeodeticCRS::create(props, datum, cs); + } } // --------------------------------------------------------------------------- @@ -9614,8 +9679,10 @@ static bool is_in_stringlist(const std::string &str, const char *stringlist) { // --------------------------------------------------------------------------- -CRSNNPtr PROJStringParser::Private::buildProjectedCRS( - int iStep, GeographicCRSNNPtr geogCRS, int iUnitConvert, int iAxisSwap) { +CRSNNPtr +PROJStringParser::Private::buildProjectedCRS(int iStep, + const GeodeticCRSNNPtr &geodCRS, + int iUnitConvert, int iAxisSwap) { auto &step = steps_[iStep]; const auto mappings = getMappingsFromPROJName(step.name); const MethodMapping *mapping = mappings.empty() ? nullptr : mappings[0]; @@ -9641,7 +9708,7 @@ CRSNNPtr PROJStringParser::Private::buildProjectedCRS( } if (mapping) { - mapping = selectSphericalOrEllipsoidal(mapping, geogCRS); + mapping = selectSphericalOrEllipsoidal(mapping, geodCRS); } assert(isProjectedStep(step.name)); @@ -9651,7 +9718,7 @@ CRSNNPtr PROJStringParser::Private::buildProjectedCRS( const auto &title = title_; if (!buildPrimeMeridian(step)->longitude()._isEquivalentTo( - geogCRS->primeMeridian()->longitude(), + geodCRS->primeMeridian()->longitude(), util::IComparable::Criterion::EQUIVALENT)) { throw ParsingException("inconsistent pm values between projectedCRS " "and its base geographicalCRS"); @@ -9917,7 +9984,7 @@ CRSNNPtr PROJStringParser::Private::buildProjectedCRS( } if (k >= 0 && k <= 1) { const double es = - geogCRS->ellipsoid()->squaredEccentricity(); + geodCRS->ellipsoid()->squaredEccentricity(); if (es < 0 || es == 1) { throw ParsingException("Invalid flattening"); } @@ -10028,10 +10095,16 @@ CRSNNPtr PROJStringParser::Private::buildProjectedCRS( {"PROJ ob_tran o_proj=longlat", "PROJ ob_tran o_proj=lonlat", "PROJ ob_tran o_proj=latlon", "PROJ ob_tran o_proj=latlong"}) { if (starts_with(methodName, substr)) { - return DerivedGeographicCRS::create( - PropertyMap().set(IdentifiedObject::NAME_KEY, "unnamed"), - geogCRS, NN_NO_CHECK(conv), - buildEllipsoidalCS(iStep, iUnitConvert, iAxisSwap, false)); + auto geogCRS = + util::nn_dynamic_pointer_cast<GeographicCRS>(geodCRS); + if (geogCRS) { + return DerivedGeographicCRS::create( + PropertyMap().set(IdentifiedObject::NAME_KEY, + "unnamed"), + NN_NO_CHECK(geogCRS), NN_NO_CHECK(conv), + buildEllipsoidalCS(iStep, iUnitConvert, iAxisSwap, + false)); + } } } } @@ -10039,11 +10112,11 @@ CRSNNPtr PROJStringParser::Private::buildProjectedCRS( std::vector<CoordinateSystemAxisNNPtr> axis = processAxisSwap(step, unit, iAxisSwap, axisType, false); - auto csGeogCRS = geogCRS->coordinateSystem(); - auto cs = csGeogCRS->axisList().size() == 2 + auto csGeodCRS = geodCRS->coordinateSystem(); + auto cs = csGeodCRS->axisList().size() == 2 ? CartesianCS::create(emptyPropertyMap, axis[0], axis[1]) : CartesianCS::create(emptyPropertyMap, axis[0], axis[1], - csGeogCRS->axisList()[2]); + csGeodCRS->axisList()[2]); auto props = PropertyMap().set(IdentifiedObject::NAME_KEY, title.empty() ? "unknown" : title); @@ -10057,7 +10130,7 @@ CRSNNPtr PROJStringParser::Private::buildProjectedCRS( bWebMercator ? createPseudoMercator( props.set(IdentifiedObject::NAME_KEY, webMercatorName), cs) - : ProjectedCRS::create(props, geogCRS, NN_NO_CHECK(conv), cs); + : ProjectedCRS::create(props, geodCRS, NN_NO_CHECK(conv), cs); return crs; } @@ -10528,8 +10601,8 @@ PROJStringParser::createFromPROJString(const std::string &projString) { // First run is dry run to mark all recognized/unrecognized tokens for (int iter = 0; iter < 2; iter++) { auto obj = d->buildBoundOrCompoundCRSIfNeeded( - 0, d->buildGeographicCRS(iFirstGeogStep, iFirstUnitConvert, - iFirstAxisSwap, false)); + 0, d->buildGeodeticCRS(iFirstGeogStep, iFirstUnitConvert, + iFirstAxisSwap, false)); if (iter == 1) { return nn_static_pointer_cast<BaseObject>(obj); } @@ -10546,14 +10619,14 @@ PROJStringParser::createFromPROJString(const std::string &projString) { iProjStep, d->buildProjectedCRS( iProjStep, - d->buildGeographicCRS(iFirstGeogStep, - iFirstUnitConvert < iFirstGeogStep - ? iFirstUnitConvert - : -1, - iFirstAxisSwap < iFirstGeogStep - ? iFirstAxisSwap - : -1, - true), + d->buildGeodeticCRS(iFirstGeogStep, + iFirstUnitConvert < iFirstGeogStep + ? iFirstUnitConvert + : -1, + iFirstAxisSwap < iFirstGeogStep + ? iFirstAxisSwap + : -1, + true), iFirstUnitConvert < iFirstGeogStep ? iSecondUnitConvert : iFirstUnitConvert, iFirstAxisSwap < iFirstGeogStep ? iSecondAxisSwap diff --git a/src/iso19111/operation/conversion.cpp b/src/iso19111/operation/conversion.cpp index 3d09c9fa..f7bb8ec8 100644 --- a/src/iso19111/operation/conversion.cpp +++ b/src/iso19111/operation/conversion.cpp @@ -79,6 +79,7 @@ constexpr double UTM_SCALE_FACTOR = 0.9996; constexpr double UTM_FALSE_EASTING = 500000.0; constexpr double UTM_NORTH_FALSE_NORTHING = 0.0; constexpr double UTM_SOUTH_FALSE_NORTHING = 10000000.0; + //! @endcond // --------------------------------------------------------------------------- @@ -264,7 +265,8 @@ createConversion(const util::PropertyMap &properties, const std::vector<ParameterValueNNPtr> &values) { std::vector<OperationParameterNNPtr> parameters; - for (int i = 0; mapping->params[i] != nullptr; i++) { + for (int i = 0; mapping->params != nullptr && mapping->params[i] != nullptr; + i++) { const auto *param = mapping->params[i]; auto paramProperties = util::PropertyMap().set( common::IdentifiedObject::NAME_KEY, param->wkt2_name); @@ -2402,6 +2404,28 @@ Conversion::createGeographicGeocentric(const crs::CRSNNPtr &sourceCRS, // --------------------------------------------------------------------------- +/** \brief Instantiate a conversion between a GeographicCRS and a spherical + * planetocentric GeodeticCRS + * + * This method peforms conversion between geodetic latitude and geocentric + * latitude + * + * @return a new Conversion. + */ +ConversionNNPtr +Conversion::createGeographicGeocentricLatitude(const crs::CRSNNPtr &sourceCRS, + const crs::CRSNNPtr &targetCRS) { + auto properties = util::PropertyMap().set( + common::IdentifiedObject::NAME_KEY, + buildOpName("Conversion", sourceCRS, targetCRS)); + auto conv = create( + properties, PROJ_WKT2_NAME_METHOD_GEOGRAPHIC_GEOCENTRIC_LATITUDE, {}); + conv->setCRSs(sourceCRS, targetCRS, nullptr); + return conv; +} + +// --------------------------------------------------------------------------- + InverseConversion::InverseConversion(const ConversionNNPtr &forward) : Conversion( OperationMethod::create(createPropertiesForInverse(forward->method()), @@ -2508,6 +2532,15 @@ CoordinateOperationNNPtr Conversion::inverse() const { return conv; } + if (method()->nameStr() == + PROJ_WKT2_NAME_METHOD_GEOGRAPHIC_GEOCENTRIC_LATITUDE) { + auto conv = + create(createPropertiesForInverse(this, false, false), + PROJ_WKT2_NAME_METHOD_GEOGRAPHIC_GEOCENTRIC_LATITUDE, {}); + conv->setCRSs(this, true); + return conv; + } + return InverseConversion::create(NN_NO_CHECK( util::nn_dynamic_pointer_cast<Conversion>(shared_from_this()))); } @@ -3446,6 +3479,77 @@ void Conversion::_exportToPROJString( auto l_sourceCRS = sourceCRS(); auto l_targetCRS = targetCRS(); + if (methodName == PROJ_WKT2_NAME_METHOD_GEOGRAPHIC_GEOCENTRIC_LATITUDE) { + + const auto extractGeodeticCRSIfGeodeticCRSOrEquivalent = + [](const crs::CRSPtr &crs) { + auto geodCRS = std::dynamic_pointer_cast<crs::GeodeticCRS>(crs); + if (!geodCRS) { + auto compoundCRS = + std::dynamic_pointer_cast<crs::CompoundCRS>(crs); + if (compoundCRS) { + const auto &components = + compoundCRS->componentReferenceSystems(); + if (!components.empty()) { + geodCRS = + util::nn_dynamic_pointer_cast<crs::GeodeticCRS>( + components[0]); + if (!geodCRS) { + auto boundCRS = util::nn_dynamic_pointer_cast< + crs::BoundCRS>(components[0]); + if (boundCRS) { + geodCRS = util::nn_dynamic_pointer_cast< + crs::GeodeticCRS>(boundCRS->baseCRS()); + } + } + } + } else { + auto boundCRS = + std::dynamic_pointer_cast<crs::BoundCRS>(crs); + if (boundCRS) { + geodCRS = + util::nn_dynamic_pointer_cast<crs::GeodeticCRS>( + boundCRS->baseCRS()); + } + } + } + return geodCRS; + }; + + auto sourceCRSGeod = dynamic_cast<const crs::GeodeticCRS *>( + extractGeodeticCRSIfGeodeticCRSOrEquivalent(l_sourceCRS).get()); + auto targetCRSGeod = dynamic_cast<const crs::GeodeticCRS *>( + extractGeodeticCRSIfGeodeticCRSOrEquivalent(l_targetCRS).get()); + if (sourceCRSGeod && targetCRSGeod) { + auto sourceCRSGeog = + dynamic_cast<const crs::GeographicCRS *>(sourceCRSGeod); + auto targetCRSGeog = + dynamic_cast<const crs::GeographicCRS *>(targetCRSGeod); + bool isSrcGeocentricLat = + sourceCRSGeod->isSphericalPlanetocentric(); + bool isSrcGeographic = sourceCRSGeog != nullptr; + bool isTargetGeocentricLat = + targetCRSGeod->isSphericalPlanetocentric(); + bool isTargetGeographic = targetCRSGeog != nullptr; + if ((isSrcGeocentricLat && isTargetGeographic) || + (isSrcGeographic && isTargetGeocentricLat)) { + + formatter->startInversion(); + sourceCRSGeod->_exportToPROJString(formatter); + formatter->stopInversion(); + + targetCRSGeod->_exportToPROJString(formatter); + + return; + } + } + + throw io::FormattingException("Invalid nature of source and/or " + "targetCRS for Geographic latitude / " + "Geocentric latitude" + "conversion"); + } + crs::GeographicCRSPtr srcGeogCRS; if (!formatter->getCRSExport() && l_sourceCRS && applySourceCRSModifiers) { @@ -3459,11 +3563,15 @@ void Conversion::_exportToPROJString( } } - srcGeogCRS = std::dynamic_pointer_cast<crs::GeographicCRS>(horiz); - if (srcGeogCRS) { + auto srcGeodCRS = dynamic_cast<const crs::GeodeticCRS *>(horiz.get()); + if (srcGeodCRS) { + srcGeogCRS = std::dynamic_pointer_cast<crs::GeographicCRS>(horiz); + } + if (srcGeodCRS && + (srcGeogCRS || srcGeodCRS->isSphericalPlanetocentric())) { formatter->setOmitProjLongLatIfPossible(true); formatter->startInversion(); - srcGeogCRS->_exportToPROJString(formatter); + srcGeodCRS->_exportToPROJString(formatter); formatter->stopInversion(); formatter->setOmitProjLongLatIfPossible(false); } diff --git a/src/iso19111/operation/coordinateoperationfactory.cpp b/src/iso19111/operation/coordinateoperationfactory.cpp index fc64bd2e..1b1cae9b 100644 --- a/src/iso19111/operation/coordinateoperationfactory.cpp +++ b/src/iso19111/operation/coordinateoperationfactory.cpp @@ -558,6 +558,17 @@ struct CoordinateOperationFactory::Private { const crs::GeodeticCRS *geodDst, std::vector<CoordinateOperationNNPtr> &res); + static void createOperationsFromSphericalPlanetocentric( + const crs::CRSNNPtr &sourceCRS, const crs::CRSNNPtr &targetCRS, + Private::Context &context, const crs::GeodeticCRS *geodSrc, + std::vector<CoordinateOperationNNPtr> &res); + + static void createOperationsFromBoundOfSphericalPlanetocentric( + const crs::CRSNNPtr &sourceCRS, const crs::CRSNNPtr &targetCRS, + Private::Context &context, const crs::BoundCRS *boundSrc, + const crs::GeodeticCRSNNPtr &geodSrcBase, + std::vector<CoordinateOperationNNPtr> &res); + static void createOperationsDerivedTo( const crs::CRSNNPtr &sourceCRS, const crs::CRSNNPtr &targetCRS, Private::Context &context, const crs::DerivedCRS *derivedSrc, @@ -2982,6 +2993,14 @@ CoordinateOperationFactory::Private::createOperations( } } + if (geodSrc && geodSrc->isSphericalPlanetocentric()) { + createOperationsFromSphericalPlanetocentric(sourceCRS, targetCRS, + context, geodSrc, res); + return res; + } else if (geodDst && geodDst->isSphericalPlanetocentric()) { + return applyInverse(createOperations(targetCRS, sourceCRS, context)); + } + // Special case if both CRS are geodetic if (geodSrc && geodDst && !derivedSrc && !derivedDst) { createOperationsGeodToGeod(sourceCRS, targetCRS, context, geodSrc, @@ -2989,6 +3008,24 @@ CoordinateOperationFactory::Private::createOperations( return res; } + if (boundSrc) { + auto geodSrcBase = util::nn_dynamic_pointer_cast<crs::GeodeticCRS>( + boundSrc->baseCRS()); + if (geodSrcBase && geodSrcBase->isSphericalPlanetocentric()) { + createOperationsFromBoundOfSphericalPlanetocentric( + sourceCRS, targetCRS, context, boundSrc, + NN_NO_CHECK(geodSrcBase), res); + return res; + } + } else if (boundDst) { + auto geodDstBase = util::nn_dynamic_pointer_cast<crs::GeodeticCRS>( + boundDst->baseCRS()); + if (geodDstBase && geodDstBase->isSphericalPlanetocentric()) { + return applyInverse( + createOperations(targetCRS, sourceCRS, context)); + } + } + // If the source is a derived CRS, then chain the inverse of its // deriving conversion, with transforms from its baseCRS to the // targetCRS @@ -3895,6 +3932,112 @@ void CoordinateOperationFactory::Private::createOperationsGeodToGeod( // --------------------------------------------------------------------------- +void CoordinateOperationFactory::Private:: + createOperationsFromSphericalPlanetocentric( + const crs::CRSNNPtr &sourceCRS, const crs::CRSNNPtr &targetCRS, + Private::Context &context, const crs::GeodeticCRS *geodSrc, + std::vector<CoordinateOperationNNPtr> &res) { + + ENTER_FUNCTION(); + + const auto IsSameDatum = [&context, + &geodSrc](const crs::GeodeticCRS *geodDst) { + const auto &authFactory = context.context->getAuthorityFactory(); + const auto dbContext = + authFactory ? authFactory->databaseContext().as_nullable() + : nullptr; + + return geodSrc->datumNonNull(dbContext)->_isEquivalentTo( + geodDst->datumNonNull(dbContext).get(), + util::IComparable::Criterion::EQUIVALENT); + }; + auto geogDst = dynamic_cast<const crs::GeographicCRS *>(targetCRS.get()); + if (geogDst && IsSameDatum(geogDst)) { + res.emplace_back(Conversion::createGeographicGeocentricLatitude( + sourceCRS, targetCRS)); + return; + } + + // Create an intermediate geographic CRS with the same datum as the + // source spherical planetocentric one + std::string interm_crs_name(geodSrc->nameStr()); + interm_crs_name += " (geographic)"; + auto interm_crs = + util::nn_static_pointer_cast<crs::CRS>(crs::GeographicCRS::create( + addDomains(util::PropertyMap().set( + common::IdentifiedObject::NAME_KEY, interm_crs_name), + geodSrc), + geodSrc->datum(), geodSrc->datumEnsemble(), + cs::EllipsoidalCS::createLatitudeLongitude( + common::UnitOfMeasure::DEGREE))); + + auto opFirst = + Conversion::createGeographicGeocentricLatitude(sourceCRS, interm_crs); + auto opsSecond = createOperations(interm_crs, targetCRS, context); + for (const auto &opSecond : opsSecond) { + try { + res.emplace_back(ConcatenatedOperation::createComputeMetadata( + {opFirst, opSecond}, disallowEmptyIntersection)); + } catch (const InvalidOperationEmptyIntersection &) { + } + } +} + +// --------------------------------------------------------------------------- + +void CoordinateOperationFactory::Private:: + createOperationsFromBoundOfSphericalPlanetocentric( + const crs::CRSNNPtr &sourceCRS, const crs::CRSNNPtr &targetCRS, + Private::Context &context, const crs::BoundCRS *boundSrc, + const crs::GeodeticCRSNNPtr &geodSrcBase, + std::vector<CoordinateOperationNNPtr> &res) { + + ENTER_FUNCTION(); + + // Create an intermediate geographic CRS with the same datum as the + // source spherical planetocentric one + std::string interm_crs_name(geodSrcBase->nameStr()); + interm_crs_name += " (geographic)"; + auto intermGeog = + util::nn_static_pointer_cast<crs::CRS>(crs::GeographicCRS::create( + addDomains(util::PropertyMap().set( + common::IdentifiedObject::NAME_KEY, interm_crs_name), + geodSrcBase.get()), + geodSrcBase->datum(), geodSrcBase->datumEnsemble(), + cs::EllipsoidalCS::createLatitudeLongitude( + common::UnitOfMeasure::DEGREE))); + + // Create an intermediate boundCRS wrapping the above intermediate + // geographic CRS + auto transf = boundSrc->transformation()->shallowClone(); + // keep a reference to the target before patching it with itself + // (this is due to our abuse of passing shared_ptr by reference + auto transfTarget = transf->targetCRS(); + setCRSs(transf.get(), intermGeog, transfTarget); + + auto intermBoundCRS = + crs::BoundCRS::create(intermGeog, boundSrc->hubCRS(), transf); + + auto opFirst = + Conversion::createGeographicGeocentricLatitude(geodSrcBase, intermGeog); + setCRSs(opFirst.get(), sourceCRS, intermBoundCRS); + auto opsSecond = createOperations(intermBoundCRS, targetCRS, context); + for (const auto &opSecond : opsSecond) { + try { + auto opSecondClone = opSecond->shallowClone(); + // In theory, we should not need that setCRSs() forcing, but due + // how BoundCRS transformations are implemented currently, we + // need it in practice. + setCRSs(opSecondClone.get(), intermBoundCRS, targetCRS); + res.emplace_back(ConcatenatedOperation::createComputeMetadata( + {opFirst, opSecondClone}, disallowEmptyIntersection)); + } catch (const InvalidOperationEmptyIntersection &) { + } + } +} + +// --------------------------------------------------------------------------- + void CoordinateOperationFactory::Private::createOperationsDerivedTo( const crs::CRSNNPtr & /*sourceCRS*/, const crs::CRSNNPtr &targetCRS, Private::Context &context, const crs::DerivedCRS *derivedSrc, diff --git a/src/iso19111/operation/parammappings.cpp b/src/iso19111/operation/parammappings.cpp index 9acc9e93..df5ae7c5 100644 --- a/src/iso19111/operation/parammappings.cpp +++ b/src/iso19111/operation/parammappings.cpp @@ -1340,6 +1340,8 @@ static const MethodMapping otherMethodMappings[] = { {EPSG_NAME_METHOD_GEOGRAPHIC_GEOCENTRIC, EPSG_CODE_METHOD_GEOGRAPHIC_GEOCENTRIC, nullptr, nullptr, nullptr, nullptr}, + {PROJ_WKT2_NAME_METHOD_GEOGRAPHIC_GEOCENTRIC_LATITUDE, 0, nullptr, nullptr, + nullptr, nullptr}, {EPSG_NAME_METHOD_LONGITUDE_ROTATION, EPSG_CODE_METHOD_LONGITUDE_ROTATION, nullptr, nullptr, nullptr, paramsLongitudeRotation}, {EPSG_NAME_METHOD_AFFINE_PARAMETRIC_TRANSFORMATION, diff --git a/src/iso19111/operation/transformation.cpp b/src/iso19111/operation/transformation.cpp index 273b636f..a30e1248 100644 --- a/src/iso19111/operation/transformation.cpp +++ b/src/iso19111/operation/transformation.cpp @@ -474,13 +474,16 @@ static void getTransformationType(const crs::CRSNNPtr &sourceCRSIn, dynamic_cast<const crs::GeographicCRS *>(sourceCRSIn.get()); auto targetCRSGeog = dynamic_cast<const crs::GeographicCRS *>(targetCRSIn.get()); - if (!sourceCRSGeog || !targetCRSGeog) { + if (!(sourceCRSGeog || + (sourceCRSGeod && sourceCRSGeod->isSphericalPlanetocentric())) || + !(targetCRSGeog || + (targetCRSGeod && targetCRSGeod->isSphericalPlanetocentric()))) { throw InvalidOperation("Inconsistent CRS type"); } const auto nSrcAxisCount = - sourceCRSGeog->coordinateSystem()->axisList().size(); + sourceCRSGeod->coordinateSystem()->axisList().size(); const auto nTargetAxisCount = - targetCRSGeog->coordinateSystem()->axisList().size(); + targetCRSGeod->coordinateSystem()->axisList().size(); isGeog2D = nSrcAxisCount == 2 && nTargetAxisCount == 2; isGeog3D = !isGeog2D && nSrcAxisCount >= 2 && nTargetAxisCount >= 2; } @@ -1003,36 +1006,36 @@ TransformationNNPtr Transformation::createTOWGS84( "Invalid number of elements in TOWGS84Parameters"); } - crs::CRSPtr transformSourceCRS = sourceCRSIn->extractGeodeticCRS(); - if (!transformSourceCRS) { + auto transformSourceGeodCRS = sourceCRSIn->extractGeodeticCRS(); + if (!transformSourceGeodCRS) { throw InvalidOperation( "Cannot find GeodeticCRS in sourceCRS of TOWGS84 transformation"); } util::PropertyMap properties; properties.set(common::IdentifiedObject::NAME_KEY, - concat("Transformation from ", transformSourceCRS->nameStr(), - " to WGS84")); - - auto targetCRS = - dynamic_cast<const crs::GeographicCRS *>(transformSourceCRS.get()) - ? util::nn_static_pointer_cast<crs::CRS>( - crs::GeographicCRS::EPSG_4326) - : util::nn_static_pointer_cast<crs::CRS>( - crs::GeodeticCRS::EPSG_4978); - + concat("Transformation from ", + transformSourceGeodCRS->nameStr(), " to WGS84")); + + auto targetCRS = dynamic_cast<const crs::GeographicCRS *>( + transformSourceGeodCRS.get()) || + transformSourceGeodCRS->isSphericalPlanetocentric() + ? util::nn_static_pointer_cast<crs::CRS>( + crs::GeographicCRS::EPSG_4326) + : util::nn_static_pointer_cast<crs::CRS>( + crs::GeodeticCRS::EPSG_4978); + + crs::CRSNNPtr transformSourceCRS = NN_NO_CHECK(transformSourceGeodCRS); if (TOWGS84Parameters.size() == 3) { return createGeocentricTranslations( - properties, NN_NO_CHECK(transformSourceCRS), targetCRS, - TOWGS84Parameters[0], TOWGS84Parameters[1], TOWGS84Parameters[2], - {}); + properties, transformSourceCRS, targetCRS, TOWGS84Parameters[0], + TOWGS84Parameters[1], TOWGS84Parameters[2], {}); } - return createPositionVector(properties, NN_NO_CHECK(transformSourceCRS), - targetCRS, TOWGS84Parameters[0], - TOWGS84Parameters[1], TOWGS84Parameters[2], - TOWGS84Parameters[3], TOWGS84Parameters[4], - TOWGS84Parameters[5], TOWGS84Parameters[6], {}); + return createPositionVector( + properties, transformSourceCRS, targetCRS, TOWGS84Parameters[0], + TOWGS84Parameters[1], TOWGS84Parameters[2], TOWGS84Parameters[3], + TOWGS84Parameters[4], TOWGS84Parameters[5], TOWGS84Parameters[6], {}); } // --------------------------------------------------------------------------- |
