diff options
Diffstat (limited to 'src/crs.cpp')
| -rw-r--r-- | src/crs.cpp | 4490 |
1 files changed, 4490 insertions, 0 deletions
diff --git a/src/crs.cpp b/src/crs.cpp new file mode 100644 index 00000000..dab704b4 --- /dev/null +++ b/src/crs.cpp @@ -0,0 +1,4490 @@ +/****************************************************************************** + * + * Project: PROJ + * Purpose: 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. + ****************************************************************************/ + +#ifndef FROM_PROJ_CPP +#define FROM_PROJ_CPP +#endif + +//! @cond Doxygen_Suppress +#define DO_NOT_DEFINE_EXTERN_DERIVED_CRS_TEMPLATE +//! @endcond + +#include "proj/crs.hpp" +#include "proj/common.hpp" +#include "proj/coordinateoperation.hpp" +#include "proj/coordinatesystem.hpp" +#include "proj/io.hpp" +#include "proj/util.hpp" + +#include "proj/internal/coordinatesystem_internal.hpp" +#include "proj/internal/internal.hpp" +#include "proj/internal/io_internal.hpp" + +#include <cassert> +#include <cstring> +#include <iostream> +#include <memory> +#include <string> +#include <vector> + +using namespace NS_PROJ::internal; + +#if 0 +namespace dropbox{ namespace oxygen { +template<> nn<NS_PROJ::crs::CRSPtr>::~nn() = default; +template<> nn<NS_PROJ::crs::SingleCRSPtr>::~nn() = default; +template<> nn<NS_PROJ::crs::GeodeticCRSPtr>::~nn() = default; +template<> nn<NS_PROJ::crs::GeographicCRSPtr>::~nn() = default; +template<> nn<NS_PROJ::crs::DerivedCRSPtr>::~nn() = default; +template<> nn<NS_PROJ::crs::ProjectedCRSPtr>::~nn() = default; +template<> nn<NS_PROJ::crs::VerticalCRSPtr>::~nn() = default; +template<> nn<NS_PROJ::crs::CompoundCRSPtr>::~nn() = default; +template<> nn<NS_PROJ::crs::TemporalCRSPtr>::~nn() = default; +template<> nn<NS_PROJ::crs::EngineeringCRSPtr>::~nn() = default; +template<> nn<NS_PROJ::crs::ParametricCRSPtr>::~nn() = default; +template<> nn<NS_PROJ::crs::BoundCRSPtr>::~nn() = default; +template<> nn<NS_PROJ::crs::DerivedGeodeticCRSPtr>::~nn() = default; +template<> nn<NS_PROJ::crs::DerivedGeographicCRSPtr>::~nn() = default; +template<> nn<NS_PROJ::crs::DerivedProjectedCRSPtr>::~nn() = default; +template<> nn<NS_PROJ::crs::DerivedVerticalCRSPtr>::~nn() = default; +template<> nn<NS_PROJ::crs::DerivedTemporalCRSPtr>::~nn() = default; +template<> nn<NS_PROJ::crs::DerivedEngineeringCRSPtr>::~nn() = default; +template<> nn<NS_PROJ::crs::DerivedParametricCRSPtr>::~nn() = default; +}} +#endif + +NS_PROJ_START + +namespace crs { + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +struct CRS::Private { + BoundCRSPtr canonicalBoundCRS_{}; +}; +//! @endcond + +// --------------------------------------------------------------------------- + +CRS::CRS() : d(internal::make_unique<Private>()) {} + +// --------------------------------------------------------------------------- + +CRS::CRS(const CRS &other) + : ObjectUsage(other), d(internal::make_unique<Private>(*(other.d))) {} + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +CRS::~CRS() = default; +//! @endcond + +// --------------------------------------------------------------------------- + +/** \brief Return the BoundCRS potentially attached to this CRS. + * + * In the case this method is called on a object returned by + * BoundCRS::baseCRSWithCanonicalBoundCRS(), this method will return this + * BoundCRS + * + * @return a BoundCRSPtr, that might be null. + */ +const BoundCRSPtr &CRS::canonicalBoundCRS() PROJ_CONST_DEFN { + return d->canonicalBoundCRS_; +} + +// --------------------------------------------------------------------------- + +/** \brief Return the GeodeticCRS of the CRS. + * + * Returns the GeodeticCRS contained in a CRS. This works currently with + * input parameters of type GeodeticCRS or derived, ProjectedCRS, + * CompoundCRS or BoundCRS. + * + * @return a GeodeticCRSPtr, that might be null. + */ +GeodeticCRSPtr CRS::extractGeodeticCRS() const { + auto raw = extractGeodeticCRSRaw(); + if (raw) { + return std::dynamic_pointer_cast<GeodeticCRS>( + raw->shared_from_this().as_nullable()); + } + return nullptr; +} + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +const GeodeticCRS *CRS::extractGeodeticCRSRaw() const { + auto geodCRS = dynamic_cast<const GeodeticCRS *>(this); + if (geodCRS) { + return geodCRS; + } + auto projCRS = dynamic_cast<const ProjectedCRS *>(this); + if (projCRS) { + return projCRS->baseCRS()->extractGeodeticCRSRaw(); + } + auto compoundCRS = dynamic_cast<const CompoundCRS *>(this); + if (compoundCRS) { + for (const auto &subCrs : compoundCRS->componentReferenceSystems()) { + auto retGeogCRS = subCrs->extractGeodeticCRSRaw(); + if (retGeogCRS) { + return retGeogCRS; + } + } + } + auto boundCRS = dynamic_cast<const BoundCRS *>(this); + if (boundCRS) { + return boundCRS->baseCRS()->extractGeodeticCRSRaw(); + } + return nullptr; +} +//! @endcond + +// --------------------------------------------------------------------------- + +/** \brief Return the GeographicCRS of the CRS. + * + * Returns the GeographicCRS contained in a CRS. This works currently with + * input parameters of type GeographicCRS or derived, ProjectedCRS, + * CompoundCRS or BoundCRS. + * + * @return a GeographicCRSPtr, that might be null. + */ +GeographicCRSPtr CRS::extractGeographicCRS() const { + auto raw = extractGeodeticCRSRaw(); + if (raw) { + return std::dynamic_pointer_cast<GeographicCRS>( + raw->shared_from_this().as_nullable()); + } + return nullptr; +} + +// --------------------------------------------------------------------------- + +/** \brief Return the VerticalCRS of the CRS. + * + * Returns the VerticalCRS contained in a CRS. This works currently with + * input parameters of type VerticalCRS or derived, CompoundCRS or BoundCRS. + * + * @return a VerticalCRSPtr, that might be null. + */ +VerticalCRSPtr CRS::extractVerticalCRS() const { + auto vertCRS = dynamic_cast<const VerticalCRS *>(this); + if (vertCRS) { + return std::dynamic_pointer_cast<VerticalCRS>( + shared_from_this().as_nullable()); + } + auto compoundCRS = dynamic_cast<const CompoundCRS *>(this); + if (compoundCRS) { + for (const auto &subCrs : compoundCRS->componentReferenceSystems()) { + auto retVertCRS = subCrs->extractVerticalCRS(); + if (retVertCRS) { + return retVertCRS; + } + } + } + auto boundCRS = dynamic_cast<const BoundCRS *>(this); + if (boundCRS) { + return boundCRS->baseCRS()->extractVerticalCRS(); + } + return nullptr; +} + +// --------------------------------------------------------------------------- + +/** \brief Returns potentially + * a BoundCRS, with a transformation to EPSG:4326, wrapping this CRS + * + * If no such BoundCRS is possible, the object will be returned. + * + * The purpose of this method is to be able to format a PROJ.4 string with + * a +towgs84 parameter or a WKT1:GDAL string with a TOWGS node. + * + * This method will fetch the GeographicCRS of this CRS and find a + * transformation to EPSG:4326 using the domain of the validity of the main CRS. + * + * @return a CRS. + */ +CRSNNPtr CRS::createBoundCRSToWGS84IfPossible( + const io::DatabaseContextPtr &dbContext) const { + auto thisAsCRS = NN_NO_CHECK( + std::static_pointer_cast<CRS>(shared_from_this().as_nullable())); + auto boundCRS = util::nn_dynamic_pointer_cast<BoundCRS>(thisAsCRS); + if (!boundCRS) { + boundCRS = canonicalBoundCRS(); + } + if (boundCRS) { + if (boundCRS->hubCRS()->_isEquivalentTo( + GeographicCRS::EPSG_4326.get(), + util::IComparable::Criterion::EQUIVALENT)) { + return NN_NO_CHECK(boundCRS); + } + } + + auto geodCRS = util::nn_dynamic_pointer_cast<GeodeticCRS>(thisAsCRS); + auto geogCRS = extractGeographicCRS(); + auto hubCRS = util::nn_static_pointer_cast<CRS>(GeographicCRS::EPSG_4326); + if (geodCRS && !geogCRS) { + hubCRS = util::nn_static_pointer_cast<CRS>(GeodeticCRS::EPSG_4978); + } else if (!geogCRS || + geogCRS->_isEquivalentTo( + GeographicCRS::EPSG_4326.get(), + util::IComparable::Criterion::EQUIVALENT)) { + return thisAsCRS; + } else { + geodCRS = geogCRS; + } + auto l_domains = domains(); + metadata::ExtentPtr extent; + if (!l_domains.empty()) { + extent = l_domains[0]->domainOfValidity(); + } + + try { + auto authFactory = dbContext + ? io::AuthorityFactory::create( + NN_NO_CHECK(dbContext), std::string()) + .as_nullable() + : nullptr; + auto ctxt = operation::CoordinateOperationContext::create(authFactory, + extent, 0.0); + // ctxt->setSpatialCriterion( + // operation::CoordinateOperationContext::SpatialCriterion::PARTIAL_INTERSECTION); + auto list = + operation::CoordinateOperationFactory::create()->createOperations( + NN_NO_CHECK(geodCRS), hubCRS, ctxt); + for (const auto &op : list) { + auto transf = + util::nn_dynamic_pointer_cast<operation::Transformation>(op); + if (transf) { + try { + transf->getTOWGS84Parameters(); + } catch (const std::exception &) { + continue; + } + return util::nn_static_pointer_cast<CRS>( + BoundCRS::create(thisAsCRS, hubCRS, NN_NO_CHECK(transf))); + } else { + auto concatenated = + dynamic_cast<const operation::ConcatenatedOperation *>( + op.get()); + if (concatenated) { + // Case for EPSG:4807 / "NTF (Paris)" that is made of a + // longitude rotation followed by a Helmert + // The prime meridian shift will be accounted elsewhere + const auto &subops = concatenated->operations(); + if (subops.size() == 2) { + auto firstOpIsTransformation = + dynamic_cast<const operation::Transformation *>( + subops[0].get()); + auto firstOpIsConversion = + dynamic_cast<const operation::Conversion *>( + subops[0].get()); + if ((firstOpIsTransformation && + firstOpIsTransformation->isLongitudeRotation()) || + (dynamic_cast<DerivedCRS *>(thisAsCRS.get()) && + firstOpIsConversion)) { + transf = util::nn_dynamic_pointer_cast< + operation::Transformation>(subops[1]); + if (transf) { + try { + transf->getTOWGS84Parameters(); + } catch (const std::exception &) { + continue; + } + return util::nn_static_pointer_cast<CRS>( + BoundCRS::create(thisAsCRS, hubCRS, + NN_NO_CHECK(transf))); + } + } + } + } + } + } + } catch (const std::exception &) { + } + return thisAsCRS; +} + +// --------------------------------------------------------------------------- + +/** \brief Returns a CRS whose coordinate system does not contain a vertical + * component + * + * @return a CRS. + */ +CRSNNPtr CRS::stripVerticalComponent() const { + auto self = NN_NO_CHECK( + std::dynamic_pointer_cast<CRS>(shared_from_this().as_nullable())); + + auto geogCRS = dynamic_cast<const GeographicCRS *>(this); + if (geogCRS) { + const auto &axisList = geogCRS->coordinateSystem()->axisList(); + if (axisList.size() == 3) { + auto cs = cs::EllipsoidalCS::create(util::PropertyMap(), + axisList[0], axisList[1]); + return util::nn_static_pointer_cast<CRS>(GeographicCRS::create( + util::PropertyMap().set(common::IdentifiedObject::NAME_KEY, + nameStr()), + geogCRS->datum(), geogCRS->datumEnsemble(), cs)); + } + } + auto projCRS = dynamic_cast<const ProjectedCRS *>(this); + if (projCRS) { + const auto &axisList = projCRS->coordinateSystem()->axisList(); + if (axisList.size() == 3) { + auto cs = cs::CartesianCS::create(util::PropertyMap(), axisList[0], + axisList[1]); + return util::nn_static_pointer_cast<CRS>(ProjectedCRS::create( + util::PropertyMap().set(common::IdentifiedObject::NAME_KEY, + nameStr()), + projCRS->baseCRS(), projCRS->derivingConversion(), cs)); + } + } + return self; +} + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress + +/** \brief Return a shallow clone of this object. */ +CRSNNPtr CRS::shallowClone() const { return _shallowClone(); } + +//! @endcond + +// --------------------------------------------------------------------------- + +/** \brief Identify the CRS with reference CRSs. + * + * The candidate CRSs are either hard-coded, or looked in the database when + * authorityFactory is not null. + * + * The method returns a list of matching reference CRS, and the percentage + * (0-100) of confidence in the match. + * 100% means that the name of the reference entry + * perfectly matches the CRS name, and both are equivalent. In which case a + * single result is returned. + * 90% means that CRS are equivalent, but the names are not exactly the same. + * 70% means that CRS are equivalent), but the names do not match at all. + * 25% means that the CRS are not equivalent, but there is some similarity in + * the names. + * Other confidence values may be returned by some specialized implementations. + * + * This is implemented for GeodeticCRS, ProjectedCRS, VerticalCRS and + * CompoundCRS. + * + * @param authorityFactory Authority factory (or null, but degraded + * functionality) + * @return a list of matching reference CRS, and the percentage (0-100) of + * confidence in the match. + */ +std::list<std::pair<CRSNNPtr, int>> +CRS::identify(const io::AuthorityFactoryPtr &authorityFactory) const { + return _identify(authorityFactory); +} + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress + +std::list<std::pair<CRSNNPtr, int>> +CRS::_identify(const io::AuthorityFactoryPtr &) const { + return {}; +} + +//! @endcond + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +struct SingleCRS::Private { + datum::DatumPtr datum{}; + datum::DatumEnsemblePtr datumEnsemble{}; + cs::CoordinateSystemNNPtr coordinateSystem; + + Private(const datum::DatumPtr &datumIn, + const datum::DatumEnsemblePtr &datumEnsembleIn, + const cs::CoordinateSystemNNPtr &csIn) + : datum(datumIn), datumEnsemble(datumEnsembleIn), + coordinateSystem(csIn) { + if ((datum ? 1 : 0) + (datumEnsemble ? 1 : 0) != 1) { + throw util::Exception("datum or datumEnsemble should be set"); + } + } +}; +//! @endcond + +// --------------------------------------------------------------------------- + +SingleCRS::SingleCRS(const datum::DatumPtr &datumIn, + const datum::DatumEnsemblePtr &datumEnsembleIn, + const cs::CoordinateSystemNNPtr &csIn) + : d(internal::make_unique<Private>(datumIn, datumEnsembleIn, csIn)) {} + +// --------------------------------------------------------------------------- + +SingleCRS::SingleCRS(const SingleCRS &other) + : CRS(other), d(internal::make_unique<Private>(*other.d)) {} + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +SingleCRS::~SingleCRS() = default; +//! @endcond + +// --------------------------------------------------------------------------- + +/** \brief Return the datum::Datum associated with the CRS. + * + * This might be null, in which case datumEnsemble() return will not be null. + * + * @return a Datum that might be null. + */ +const datum::DatumPtr &SingleCRS::datum() PROJ_CONST_DEFN { return d->datum; } + +// --------------------------------------------------------------------------- + +/** \brief Return the datum::DatumEnsemble associated with the CRS. + * + * This might be null, in which case datum() return will not be null. + * + * @return a DatumEnsemble that might be null. + */ +const datum::DatumEnsemblePtr &SingleCRS::datumEnsemble() PROJ_CONST_DEFN { + return d->datumEnsemble; +} + +// --------------------------------------------------------------------------- + +/** \brief Return the cs::CoordinateSystem associated with the CRS. + * + * This might be null, in which case datumEnsemble() return will not be null. + * + * @return a CoordinateSystem that might be null. + */ +const cs::CoordinateSystemNNPtr &SingleCRS::coordinateSystem() PROJ_CONST_DEFN { + return d->coordinateSystem; +} + +// --------------------------------------------------------------------------- + +bool SingleCRS::baseIsEquivalentTo( + const util::IComparable *other, + util::IComparable::Criterion criterion) const { + auto otherSingleCRS = dynamic_cast<const SingleCRS *>(other); + if (otherSingleCRS == nullptr || + (criterion == util::IComparable::Criterion::STRICT && + !ObjectUsage::_isEquivalentTo(other, criterion))) { + return false; + } + const auto &thisDatum = d->datum; + const auto &otherDatum = otherSingleCRS->d->datum; + if (thisDatum) { + if (!thisDatum->_isEquivalentTo(otherDatum.get(), criterion)) { + return false; + } + } else { + if (otherDatum) { + return false; + } + } + + // TODO test DatumEnsemble + return d->coordinateSystem->_isEquivalentTo( + otherSingleCRS->d->coordinateSystem.get(), criterion); +} + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +void SingleCRS::exportDatumOrDatumEnsembleToWkt( + io::WKTFormatter *formatter) const // throw(io::FormattingException) +{ + const auto &l_datum = d->datum; + if (l_datum) { + l_datum->_exportToWKT(formatter); + } else { + const auto &l_datumEnsemble = d->datumEnsemble; + assert(l_datumEnsemble); + l_datumEnsemble->_exportToWKT(formatter); + } +} +//! @endcond + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +struct GeodeticCRS::Private { + std::vector<operation::PointMotionOperationNNPtr> velocityModel{}; + datum::GeodeticReferenceFramePtr datum_; + + explicit Private(const datum::GeodeticReferenceFramePtr &datumIn) + : datum_(datumIn) {} +}; + +// --------------------------------------------------------------------------- + +static const datum::DatumEnsemblePtr & +checkEnsembleForGeodeticCRS(const datum::GeodeticReferenceFramePtr &datumIn, + const datum::DatumEnsemblePtr &ensemble) { + const char *msg = "One of Datum or DatumEnsemble should be defined"; + if (datumIn) { + if (!ensemble) { + return ensemble; + } + msg = "Datum and DatumEnsemble should not be defined"; + } else if (ensemble) { + const auto &datums = ensemble->datums(); + assert(!datums.empty()); + auto grfFirst = + dynamic_cast<datum::GeodeticReferenceFrame *>(datums[0].get()); + if (grfFirst) { + return ensemble; + } + msg = "Ensemble should contain GeodeticReferenceFrame"; + } + throw util::Exception(msg); +} + +//! @endcond + +// --------------------------------------------------------------------------- + +GeodeticCRS::GeodeticCRS(const datum::GeodeticReferenceFramePtr &datumIn, + const datum::DatumEnsemblePtr &datumEnsembleIn, + const cs::EllipsoidalCSNNPtr &csIn) + : SingleCRS(datumIn, checkEnsembleForGeodeticCRS(datumIn, datumEnsembleIn), + csIn), + d(internal::make_unique<Private>(datumIn)) {} + +// --------------------------------------------------------------------------- + +GeodeticCRS::GeodeticCRS(const datum::GeodeticReferenceFramePtr &datumIn, + const datum::DatumEnsemblePtr &datumEnsembleIn, + const cs::SphericalCSNNPtr &csIn) + : SingleCRS(datumIn, checkEnsembleForGeodeticCRS(datumIn, datumEnsembleIn), + csIn), + d(internal::make_unique<Private>(datumIn)) {} + +// --------------------------------------------------------------------------- + +GeodeticCRS::GeodeticCRS(const datum::GeodeticReferenceFramePtr &datumIn, + const datum::DatumEnsemblePtr &datumEnsembleIn, + const cs::CartesianCSNNPtr &csIn) + : SingleCRS(datumIn, checkEnsembleForGeodeticCRS(datumIn, datumEnsembleIn), + csIn), + d(internal::make_unique<Private>(datumIn)) {} + +// --------------------------------------------------------------------------- + +GeodeticCRS::GeodeticCRS(const GeodeticCRS &other) + : SingleCRS(other), d(internal::make_unique<Private>(*other.d)) {} + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +GeodeticCRS::~GeodeticCRS() = default; +//! @endcond + +// --------------------------------------------------------------------------- + +CRSNNPtr GeodeticCRS::_shallowClone() const { + auto crs(GeodeticCRS::nn_make_shared<GeodeticCRS>(*this)); + crs->assignSelf(crs); + return crs; +} + +// --------------------------------------------------------------------------- + +/** \brief Return the datum::GeodeticReferenceFrame associated with the CRS. + * + * @return a GeodeticReferenceFrame or null (in which case datumEnsemble() + * should return a non-null pointer.) + */ +const datum::GeodeticReferenceFramePtr &GeodeticCRS::datum() PROJ_CONST_DEFN { + return d->datum_; +} + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +static datum::GeodeticReferenceFrame *oneDatum(const GeodeticCRS *crs) { + const auto &l_datumEnsemble = crs->datumEnsemble(); + assert(l_datumEnsemble); + const auto &l_datums = l_datumEnsemble->datums(); + return static_cast<datum::GeodeticReferenceFrame *>(l_datums[0].get()); +} +//! @endcond + +// --------------------------------------------------------------------------- + +/** \brief Return the PrimeMeridian associated with the GeodeticReferenceFrame + * or with one of the GeodeticReferenceFrame of the datumEnsemble(). + * + * @return the PrimeMeridian. + */ +const datum::PrimeMeridianNNPtr &GeodeticCRS::primeMeridian() PROJ_CONST_DEFN { + if (d->datum_) { + return d->datum_->primeMeridian(); + } + return oneDatum(this)->primeMeridian(); +} + +// --------------------------------------------------------------------------- + +/** \brief Return the ellipsoid associated with the GeodeticReferenceFrame + * or with one of the GeodeticReferenceFrame of the datumEnsemble(). + * + * @return the PrimeMeridian. + */ +const datum::EllipsoidNNPtr &GeodeticCRS::ellipsoid() PROJ_CONST_DEFN { + if (d->datum_) { + return d->datum_->ellipsoid(); + } + return oneDatum(this)->ellipsoid(); +} + +// --------------------------------------------------------------------------- + +/** \brief Return the velocity model associated with the CRS. + * + * @return a velocity model. might be null. + */ +const std::vector<operation::PointMotionOperationNNPtr> & +GeodeticCRS::velocityModel() PROJ_CONST_DEFN { + return d->velocityModel; +} + +// --------------------------------------------------------------------------- + +/** \brief Return whether the CRS is a geocentric one. + * + * A geocentric CRS is a geodetic CRS that has a Cartesian coordinate system + * with three axis, whose direction is respectively + * cs::AxisDirection::GEOCENTRIC_X, + * cs::AxisDirection::GEOCENTRIC_Y and cs::AxisDirection::GEOCENTRIC_Z. + * + * @return true if the CRS is a geocentric CRS. + */ +bool GeodeticCRS::isGeocentric() PROJ_CONST_DEFN { + const auto &cs = coordinateSystem(); + const auto &axisList = cs->axisList(); + return axisList.size() == 3 && + dynamic_cast<cs::CartesianCS *>(cs.get()) != nullptr && + &axisList[0]->direction() == &cs::AxisDirection::GEOCENTRIC_X && + &axisList[1]->direction() == &cs::AxisDirection::GEOCENTRIC_Y && + &axisList[2]->direction() == &cs::AxisDirection::GEOCENTRIC_Z; +} + +// --------------------------------------------------------------------------- + +/** \brief Instanciate a GeodeticCRS from a datum::GeodeticReferenceFrame and a + * cs::SphericalCS. + * + * @param properties See \ref general_properties. + * At minimum the name should be defined. + * @param datum The datum of the CRS. + * @param cs a SphericalCS. + * @return new GeodeticCRS. + */ +GeodeticCRSNNPtr +GeodeticCRS::create(const util::PropertyMap &properties, + const datum::GeodeticReferenceFrameNNPtr &datum, + const cs::SphericalCSNNPtr &cs) { + return create(properties, datum.as_nullable(), nullptr, cs); +} + +// --------------------------------------------------------------------------- + +/** \brief Instanciate a GeodeticCRS from a datum::GeodeticReferenceFrame or + * datum::DatumEnsemble and a cs::SphericalCS. + * + * One and only one of datum or datumEnsemble should be set to a non-null value. + * + * @param properties See \ref general_properties. + * At minimum the name should be defined. + * @param datum The datum of the CRS, or nullptr + * @param datumEnsemble The datum ensemble of the CRS, or nullptr. + * @param cs a SphericalCS. + * @return new GeodeticCRS. + */ +GeodeticCRSNNPtr +GeodeticCRS::create(const util::PropertyMap &properties, + const datum::GeodeticReferenceFramePtr &datum, + const datum::DatumEnsemblePtr &datumEnsemble, + const cs::SphericalCSNNPtr &cs) { + auto crs( + GeodeticCRS::nn_make_shared<GeodeticCRS>(datum, datumEnsemble, cs)); + crs->assignSelf(crs); + crs->setProperties(properties); + return crs; +} + +// --------------------------------------------------------------------------- + +/** \brief Instanciate a GeodeticCRS from a datum::GeodeticReferenceFrame and a + * cs::CartesianCS. + * + * @param properties See \ref general_properties. + * At minimum the name should be defined. + * @param datum The datum of the CRS. + * @param cs a CartesianCS. + * @return new GeodeticCRS. + */ +GeodeticCRSNNPtr +GeodeticCRS::create(const util::PropertyMap &properties, + const datum::GeodeticReferenceFrameNNPtr &datum, + const cs::CartesianCSNNPtr &cs) { + return create(properties, datum.as_nullable(), nullptr, cs); +} + +// --------------------------------------------------------------------------- + +/** \brief Instanciate a GeodeticCRS from a datum::GeodeticReferenceFrame or + * datum::DatumEnsemble and a cs::CartesianCS. + * + * One and only one of datum or datumEnsemble should be set to a non-null value. + * + * @param properties See \ref general_properties. + * At minimum the name should be defined. + * @param datum The datum of the CRS, or nullptr + * @param datumEnsemble The datum ensemble of the CRS, or nullptr. + * @param cs a CartesianCS + * @return new GeodeticCRS. + */ +GeodeticCRSNNPtr +GeodeticCRS::create(const util::PropertyMap &properties, + const datum::GeodeticReferenceFramePtr &datum, + const datum::DatumEnsemblePtr &datumEnsemble, + const cs::CartesianCSNNPtr &cs) { + auto crs( + GeodeticCRS::nn_make_shared<GeodeticCRS>(datum, datumEnsemble, cs)); + crs->assignSelf(crs); + crs->setProperties(properties); + return crs; +} + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +void GeodeticCRS::_exportToWKT(io::WKTFormatter *formatter) const { + const bool isWKT2 = formatter->version() == io::WKTFormatter::Version::WKT2; + formatter->startNode(isWKT2 ? ((formatter->use2018Keywords() && + dynamic_cast<const GeographicCRS *>(this)) + ? io::WKTConstants::GEOGCRS + : io::WKTConstants::GEODCRS) + : isGeocentric() ? io::WKTConstants::GEOCCS + : io::WKTConstants::GEOGCS, + !identifiers().empty()); + auto l_name = nameStr(); + const auto &cs = coordinateSystem(); + const auto &axisList = cs->axisList(); + + if (formatter->useESRIDialect()) { + if (axisList.size() != 2) { + io::FormattingException::Throw( + "Only export of Geographic 2D CRS is supported in ESRI_WKT1"); + } + + if (l_name == "WGS 84") { + l_name = "GCS_WGS_1984"; + } else { + bool aliasFound = false; + const auto &dbContext = formatter->databaseContext(); + if (dbContext) { + auto l_alias = dbContext->getAliasFromOfficialName( + l_name, "geodetic_crs", "ESRI"); + if (!l_alias.empty()) { + l_name = l_alias; + aliasFound = true; + } + } + if (!aliasFound) { + l_name = io::WKTFormatter::morphNameToESRI(l_name); + if (!starts_with(l_name, "GCS_")) { + l_name = "GCS_" + l_name; + } + } + } + } + if (!isWKT2 && !formatter->useESRIDialect() && isDeprecated()) { + l_name += " (deprecated)"; + } + formatter->addQuotedString(l_name); + + const auto &unit = axisList[0]->unit(); + formatter->pushAxisAngularUnit(common::UnitOfMeasure::create(unit)); + exportDatumOrDatumEnsembleToWkt(formatter); + primeMeridian()->_exportToWKT(formatter); + formatter->popAxisAngularUnit(); + if (!isWKT2) { + unit._exportToWKT(formatter); + } + cs->_exportToWKT(formatter); + ObjectUsage::baseExportToWKT(formatter); + formatter->endNode(); +} +//! @endcond + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +void GeodeticCRS::addGeocentricUnitConversionIntoPROJString( + io::PROJStringFormatter *formatter) const { + + const auto &axisList = coordinateSystem()->axisList(); + const auto &unit = axisList[0]->unit(); + if (unit != common::UnitOfMeasure::METRE) { + if (formatter->convention() == + io::PROJStringFormatter::Convention::PROJ_4) { + io::FormattingException::Throw("GeodeticCRS::exportToPROJString(): " + "non-meter unit not supported for " + "PROJ.4"); + } + + formatter->addStep("unitconvert"); + formatter->addParam("xy_in", "m"); + formatter->addParam("z_in", "m"); + { + auto projUnit = unit.exportToPROJString(); + if (!projUnit.empty()) { + formatter->addParam("xy_out", projUnit); + formatter->addParam("z_out", projUnit); + return; + } + } + + const auto &toSI = unit.conversionToSI(); + formatter->addParam("xy_out", toSI); + formatter->addParam("z_out", toSI); + } +} +//! @endcond + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +void GeodeticCRS::_exportToPROJString( + io::PROJStringFormatter *formatter) const // throw(io::FormattingException) +{ + if (!isGeocentric()) { + io::FormattingException::Throw( + "GeodeticCRS::exportToPROJString() only " + "supports geocentric coordinate systems"); + } + + if (formatter->convention() == + io::PROJStringFormatter::Convention::PROJ_4) { + formatter->addStep("geocent"); + } else { + formatter->addStep("cart"); + } + ellipsoid()->_exportToPROJString(formatter); + if (formatter->convention() == + io::PROJStringFormatter::Convention::PROJ_4) { + const auto &TOWGS84Params = formatter->getTOWGS84Parameters(); + if (TOWGS84Params.size() == 7) { + formatter->addParam("towgs84", TOWGS84Params); + } + } + addGeocentricUnitConversionIntoPROJString(formatter); +} +//! @endcond + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +void GeodeticCRS::addDatumInfoToPROJString( + io::PROJStringFormatter *formatter) const // throw(io::FormattingException) +{ + const auto &TOWGS84Params = formatter->getTOWGS84Parameters(); + bool datumWritten = false; + const auto &nadgrids = formatter->getHDatumExtension(); + const auto &l_datum = datum(); + if (formatter->convention() == + io::PROJStringFormatter::Convention::PROJ_4 && + l_datum && TOWGS84Params.empty() && nadgrids.empty()) { + if (l_datum->_isEquivalentTo( + datum::GeodeticReferenceFrame::EPSG_6326.get(), + util::IComparable::Criterion::EQUIVALENT)) { + datumWritten = true; + formatter->addParam("datum", "WGS84"); + } else if (l_datum->_isEquivalentTo( + datum::GeodeticReferenceFrame::EPSG_6267.get(), + util::IComparable::Criterion::EQUIVALENT)) { + datumWritten = true; + formatter->addParam("datum", "NAD27"); + } else if (l_datum->_isEquivalentTo( + datum::GeodeticReferenceFrame::EPSG_6269.get(), + util::IComparable::Criterion::EQUIVALENT)) { + datumWritten = true; + formatter->addParam("datum", "NAD83"); + } + } + if (!datumWritten) { + ellipsoid()->_exportToPROJString(formatter); + primeMeridian()->_exportToPROJString(formatter); + } + if (TOWGS84Params.size() == 7) { + formatter->addParam("towgs84", TOWGS84Params); + } + if (!nadgrids.empty()) { + formatter->addParam("nadgrids", nadgrids); + } +} +//! @endcond + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +static util::IComparable::Criterion +getStandardCriterion(util::IComparable::Criterion criterion) { + return criterion == util::IComparable::Criterion:: + EQUIVALENT_EXCEPT_AXIS_ORDER_GEOGCRS + ? util::IComparable::Criterion::EQUIVALENT + : criterion; +} +//! @endcond + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +bool GeodeticCRS::_isEquivalentTo( + const util::IComparable *other, + util::IComparable::Criterion criterion) const { + const auto standardCriterion = getStandardCriterion(criterion); + auto otherGeodCRS = dynamic_cast<const GeodeticCRS *>(other); + // TODO test velocityModel + return otherGeodCRS != nullptr && + SingleCRS::baseIsEquivalentTo(other, standardCriterion); +} +//! @endcond + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +static util::PropertyMap createMapNameEPSGCode(const char *name, int code) { + return util::PropertyMap() + .set(common::IdentifiedObject::NAME_KEY, name) + .set(metadata::Identifier::CODESPACE_KEY, metadata::Identifier::EPSG) + .set(metadata::Identifier::CODE_KEY, code); +} +//! @endcond + +// --------------------------------------------------------------------------- + +GeodeticCRSNNPtr GeodeticCRS::createEPSG_4978() { + return create( + createMapNameEPSGCode("WGS 84", 4978), + datum::GeodeticReferenceFrame::EPSG_6326, + cs::CartesianCS::createGeocentric(common::UnitOfMeasure::METRE)); +} + +// --------------------------------------------------------------------------- + +/** \brief Identify the CRS with reference CRSs. + * + * The candidate CRSs are either hard-coded, or looked in the database when + * authorityFactory is not null. + * + * The method returns a list of matching reference CRS, and the percentage + * (0-100) of confidence in the match. + * 100% means that the name of the reference entry + * perfectly matches the CRS name, and both are equivalent. In which case a + * single result is returned. + * 90% means that CRS are equivalent, but the names are not exactly the same. + * 70% means that CRS are equivalent (equivalent datum and coordinate system), + * but the names do not match at all. + * 60% means that ellipsoid, prime meridian and coordinate systems are + * equivalent, but the CRS and datum names do not match. + * 25% means that the CRS are not equivalent, but there is some similarity in + * the names. + * + * @param authorityFactory Authority factory (or null, but degraded + * functionality) + * @return a list of matching reference CRS, and the percentage (0-100) of + * confidence in the match. + */ +std::list<std::pair<GeodeticCRSNNPtr, int>> +GeodeticCRS::identify(const io::AuthorityFactoryPtr &authorityFactory) const { + typedef std::pair<GeodeticCRSNNPtr, int> Pair; + std::list<Pair> res; + const auto &thisName(nameStr()); + + const GeographicCRSNNPtr candidatesCRS[] = {GeographicCRS::EPSG_4326, + GeographicCRS::EPSG_4267, + GeographicCRS::EPSG_4269}; + for (const auto &crs : candidatesCRS) { + const bool nameEquivalent = metadata::Identifier::isEquivalentName( + thisName.c_str(), crs->nameStr().c_str()); + const bool nameEqual = thisName == crs->nameStr(); + const bool isEq = _isEquivalentTo( + crs.get(), util::IComparable::Criterion::EQUIVALENT); + if (nameEquivalent && isEq && (!authorityFactory || nameEqual)) { + res.emplace_back(util::nn_static_pointer_cast<GeodeticCRS>(crs), + nameEqual ? 100 : 90); + return res; + } else if (nameEqual && !isEq && !authorityFactory) { + res.emplace_back(util::nn_static_pointer_cast<GeodeticCRS>(crs), + 25); + return res; + } else if (isEq && !authorityFactory) { + res.emplace_back(util::nn_static_pointer_cast<GeodeticCRS>(crs), + 70); + return res; + } + } + + std::string geodetic_crs_type; + if (isGeocentric()) { + geodetic_crs_type = "geocentric"; + } else { + auto geogCRS = dynamic_cast<const GeographicCRS *>(this); + if (geogCRS) { + if (coordinateSystem()->axisList().size() == 2) { + geodetic_crs_type = "geographic 2D"; + } else { + geodetic_crs_type = "geographic 3D"; + } + } + } + + if (authorityFactory) { + + const auto &thisDatum(datum()); + + auto searchByDatum = [this, &authorityFactory, &res, &thisDatum, + &geodetic_crs_type]() { + for (const auto &id : thisDatum->identifiers()) { + try { + auto tempRes = authorityFactory->createGeodeticCRSFromDatum( + *id->codeSpace(), id->code(), geodetic_crs_type); + for (const auto &crs : tempRes) { + if (_isEquivalentTo( + crs.get(), + util::IComparable::Criterion::EQUIVALENT)) { + res.emplace_back(crs, 70); + } + } + } catch (const std::exception &) { + } + } + }; + + const auto &thisEllipsoid(ellipsoid()); + auto searchByEllipsoid = [this, &authorityFactory, &res, &thisDatum, + &thisEllipsoid, &geodetic_crs_type]() { + const auto ellipsoids = + thisEllipsoid->identifiers().empty() + ? authorityFactory->createEllipsoidFromExisting( + thisEllipsoid) + : std::list<datum::EllipsoidNNPtr>{thisEllipsoid}; + for (const auto &ellps : ellipsoids) { + for (const auto &id : ellps->identifiers()) { + try { + auto tempRes = + authorityFactory->createGeodeticCRSFromEllipsoid( + *id->codeSpace(), id->code(), + geodetic_crs_type); + for (const auto &crs : tempRes) { + const auto &crsDatum(crs->datum()); + if (crsDatum && + crsDatum->ellipsoid()->_isEquivalentTo( + ellps.get(), + util::IComparable::Criterion::EQUIVALENT) && + crsDatum->primeMeridian()->_isEquivalentTo( + thisDatum->primeMeridian().get(), + util::IComparable::Criterion::EQUIVALENT) && + coordinateSystem()->_isEquivalentTo( + crs->coordinateSystem().get(), + util::IComparable::Criterion::EQUIVALENT) + + ) { + res.emplace_back(crs, 60); + } + } + } catch (const std::exception &) { + } + } + } + }; + + const bool unsignificantName = thisName.empty() || + ci_equal(thisName, "unknown") || + ci_equal(thisName, "unnamed"); + + if (unsignificantName) { + if (thisDatum) { + if (!thisDatum->identifiers().empty()) { + searchByDatum(); + } else { + searchByEllipsoid(); + } + } + } else if (!identifiers().empty()) { + // If the CRS has already an id, check in the database for the + // official object, and verify that they are equivalent. + for (const auto &id : identifiers()) { + try { + auto crs = io::AuthorityFactory::create( + authorityFactory->databaseContext(), + *id->codeSpace()) + ->createGeodeticCRS(id->code()); + bool match = _isEquivalentTo( + crs.get(), util::IComparable::Criterion::EQUIVALENT); + res.emplace_back(crs, match ? 100 : 25); + return res; + } catch (const std::exception &) { + } + } + } else { + for (int ipass = 0; ipass < 2; ipass++) { + const bool approximateMatch = ipass == 1; + auto objects = authorityFactory->createObjectsFromName( + thisName, {io::AuthorityFactory::ObjectType::GEODETIC_CRS}, + approximateMatch); + for (const auto &obj : objects) { + auto crs = util::nn_dynamic_pointer_cast<GeodeticCRS>(obj); + assert(crs); + auto crsNN = NN_NO_CHECK(crs); + if (_isEquivalentTo( + crs.get(), + util::IComparable::Criterion::EQUIVALENT)) { + if (crs->nameStr() == thisName) { + res.clear(); + res.emplace_back(crsNN, 100); + return res; + } + const bool eqName = + metadata::Identifier::isEquivalentName( + thisName.c_str(), crs->nameStr().c_str()); + res.emplace_back(crsNN, eqName ? 90 : 70); + } else { + res.emplace_back(crsNN, 25); + } + } + if (!res.empty()) { + break; + } + } + if (res.empty() && thisDatum) { + if (!thisDatum->identifiers().empty()) { + searchByDatum(); + } else { + searchByEllipsoid(); + } + } + } + + const auto &thisCS(coordinateSystem()); + // Sort results + res.sort([&thisName, &thisDatum, &thisCS](const Pair &a, + const Pair &b) { + // First consider confidence + if (a.second > b.second) { + return true; + } + if (a.second < b.second) { + return false; + } + + // Then consider exact name matching + const auto &aName(a.first->nameStr()); + const auto &bName(b.first->nameStr()); + if (aName == thisName && bName != thisName) { + return true; + } + if (bName == thisName && aName != thisName) { + return false; + } + + // Then datum matching + const auto &aDatum(a.first->datum()); + const auto &bDatum(b.first->datum()); + if (thisDatum && aDatum && bDatum) { + const auto thisEquivADatum(thisDatum->_isEquivalentTo( + aDatum.get(), util::IComparable::Criterion::EQUIVALENT)); + const auto thisEquivBDatum(thisDatum->_isEquivalentTo( + bDatum.get(), util::IComparable::Criterion::EQUIVALENT)); + + if (thisEquivADatum && !thisEquivBDatum) { + return true; + } + if (!thisEquivADatum && thisEquivBDatum) { + return false; + } + } + + // Then coordinate system matching + const auto &aCS(a.first->coordinateSystem()); + const auto &bCS(b.first->coordinateSystem()); + const auto thisEquivACs(thisCS->_isEquivalentTo( + aCS.get(), util::IComparable::Criterion::EQUIVALENT)); + const auto thisEquivBCs(thisCS->_isEquivalentTo( + bCS.get(), util::IComparable::Criterion::EQUIVALENT)); + if (thisEquivACs && !thisEquivBCs) { + return true; + } + if (!thisEquivACs && thisEquivBCs) { + return false; + } + + // Then dimension of the coordinate system matching + const auto thisCSAxisListSize = thisCS->axisList().size(); + const auto aCSAxistListSize = aCS->axisList().size(); + const auto bCSAxistListSize = bCS->axisList().size(); + if (thisCSAxisListSize == aCSAxistListSize && + thisCSAxisListSize != bCSAxistListSize) { + return true; + } + if (thisCSAxisListSize != aCSAxistListSize && + thisCSAxisListSize == bCSAxistListSize) { + return false; + } + + if (aDatum && bDatum) { + // Favor the CRS whole ellipsoid names matches the ellipsoid + // name (WGS84...) + const bool aEllpsNameEqCRSName = + metadata::Identifier::isEquivalentName( + aDatum->ellipsoid()->nameStr().c_str(), + a.first->nameStr().c_str()); + const bool bEllpsNameEqCRSName = + metadata::Identifier::isEquivalentName( + bDatum->ellipsoid()->nameStr().c_str(), + b.first->nameStr().c_str()); + if (aEllpsNameEqCRSName && !bEllpsNameEqCRSName) { + return true; + } + if (bEllpsNameEqCRSName && !aEllpsNameEqCRSName) { + return false; + } + } + + // Arbitrary final sorting criterion + return aName < bName; + }); + + // If there are results with 90% confidence, only keep those + if (res.size() >= 2 && res.front().second == 90) { + std::list<Pair> newRes; + for (const auto &pair : res) { + if (pair.second == 90) { + newRes.push_back(pair); + } else { + break; + } + } + return newRes; + } + } + return res; +} + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress + +std::list<std::pair<CRSNNPtr, int>> +GeodeticCRS::_identify(const io::AuthorityFactoryPtr &authorityFactory) const { + typedef std::pair<CRSNNPtr, int> Pair; + std::list<Pair> res; + auto resTemp = identify(authorityFactory); + for (const auto &pair : resTemp) { + res.emplace_back(pair.first, pair.second); + } + return res; +} + +//! @endcond + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +struct GeographicCRS::Private { + cs::EllipsoidalCSNNPtr coordinateSystem_; + explicit Private(const cs::EllipsoidalCSNNPtr &csIn) + : coordinateSystem_(csIn) {} +}; +//! @endcond + +// --------------------------------------------------------------------------- + +GeographicCRS::GeographicCRS(const datum::GeodeticReferenceFramePtr &datumIn, + const datum::DatumEnsemblePtr &datumEnsembleIn, + const cs::EllipsoidalCSNNPtr &csIn) + : SingleCRS(datumIn, datumEnsembleIn, csIn), + GeodeticCRS(datumIn, + checkEnsembleForGeodeticCRS(datumIn, datumEnsembleIn), csIn), + d(internal::make_unique<Private>(csIn)) {} + +// --------------------------------------------------------------------------- + +GeographicCRS::GeographicCRS(const GeographicCRS &other) + : SingleCRS(other), GeodeticCRS(other), + d(internal::make_unique<Private>(*other.d)) {} + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +GeographicCRS::~GeographicCRS() = default; +//! @endcond + +// --------------------------------------------------------------------------- + +CRSNNPtr GeographicCRS::_shallowClone() const { + auto crs(GeographicCRS::nn_make_shared<GeographicCRS>(*this)); + crs->assignSelf(crs); + return crs; +} + +// --------------------------------------------------------------------------- + +/** \brief Return the cs::EllipsoidalCS associated with the CRS. + * + * @return a EllipsoidalCS. + */ +const cs::EllipsoidalCSNNPtr & +GeographicCRS::coordinateSystem() PROJ_CONST_DEFN { + return d->coordinateSystem_; +} + +// --------------------------------------------------------------------------- + +/** \brief Instanciate a GeographicCRS from a datum::GeodeticReferenceFrameNNPtr + * and a + * cs::EllipsoidalCS. + * + * @param properties See \ref general_properties. + * At minimum the name should be defined. + * @param datum The datum of the CRS. + * @param cs a EllipsoidalCS. + * @return new GeographicCRS. + */ +GeographicCRSNNPtr +GeographicCRS::create(const util::PropertyMap &properties, + const datum::GeodeticReferenceFrameNNPtr &datum, + const cs::EllipsoidalCSNNPtr &cs) { + return create(properties, datum.as_nullable(), nullptr, cs); +} + +// --------------------------------------------------------------------------- + +/** \brief Instanciate a GeographicCRS from a datum::GeodeticReferenceFramePtr + * or + * datum::DatumEnsemble and a + * cs::EllipsoidalCS. + * + * One and only one of datum or datumEnsemble should be set to a non-null value. + * + * @param properties See \ref general_properties. + * At minimum the name should be defined. + * @param datum The datum of the CRS, or nullptr + * @param datumEnsemble The datum ensemble of the CRS, or nullptr. + * @param cs a EllipsoidalCS. + * @return new GeographicCRS. + */ +GeographicCRSNNPtr +GeographicCRS::create(const util::PropertyMap &properties, + const datum::GeodeticReferenceFramePtr &datum, + const datum::DatumEnsemblePtr &datumEnsemble, + const cs::EllipsoidalCSNNPtr &cs) { + GeographicCRSNNPtr crs( + GeographicCRS::nn_make_shared<GeographicCRS>(datum, datumEnsemble, cs)); + crs->assignSelf(crs); + crs->setProperties(properties); + return crs; +} + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress + +/** \brief Return whether the current GeographicCRS is the 2D part of the + * other 3D GeographicCRS. + */ +bool GeographicCRS::is2DPartOf3D(util::nn<const GeographicCRS *> other) + PROJ_CONST_DEFN { + const auto &axis = d->coordinateSystem_->axisList(); + const auto &otherAxis = other->d->coordinateSystem_->axisList(); + if (!(axis.size() == 2 && otherAxis.size() == 3)) { + return false; + } + const auto &firstAxis = axis[0]; + const auto &secondAxis = axis[1]; + const auto &otherFirstAxis = otherAxis[0]; + const auto &otherSecondAxis = otherAxis[1]; + if (!(firstAxis->_isEquivalentTo(otherFirstAxis.get()) && + secondAxis->_isEquivalentTo(otherSecondAxis.get()))) { + return false; + } + const auto &thisDatum = GeodeticCRS::getPrivate()->datum_; + const auto &otherDatum = other->GeodeticCRS::getPrivate()->datum_; + if (thisDatum && otherDatum) { + return thisDatum->_isEquivalentTo(otherDatum.get()); + } + return false; +} + +//! @endcond + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +bool GeographicCRS::_isEquivalentTo( + const util::IComparable *other, + util::IComparable::Criterion criterion) const { + auto otherGeogCRS = dynamic_cast<const GeographicCRS *>(other); + if (otherGeogCRS == nullptr) { + return false; + } + const auto standardCriterion = getStandardCriterion(criterion); + if (GeodeticCRS::_isEquivalentTo(other, standardCriterion)) { + return true; + } + if (criterion != + util::IComparable::Criterion::EQUIVALENT_EXCEPT_AXIS_ORDER_GEOGCRS) { + return false; + } + const auto axisOrder = coordinateSystem()->axisOrder(); + if (axisOrder == cs::EllipsoidalCS::AxisOrder::LONG_EAST_LAT_NORTH || + axisOrder == cs::EllipsoidalCS::AxisOrder::LAT_NORTH_LONG_EAST) { + const auto &unit = coordinateSystem()->axisList()[0]->unit(); + return GeographicCRS::create( + util::PropertyMap().set(common::IdentifiedObject::NAME_KEY, + nameStr()), + datum(), datumEnsemble(), + axisOrder == + cs::EllipsoidalCS::AxisOrder::LONG_EAST_LAT_NORTH + ? cs::EllipsoidalCS::createLatitudeLongitude(unit) + : cs::EllipsoidalCS::createLongitudeLatitude(unit)) + ->GeodeticCRS::_isEquivalentTo(other, standardCriterion); + } + return false; +} +//! @endcond + +// --------------------------------------------------------------------------- + +GeographicCRSNNPtr GeographicCRS::createEPSG_4267() { + return create(createMapNameEPSGCode("NAD27", 4267), + datum::GeodeticReferenceFrame::EPSG_6267, + cs::EllipsoidalCS::createLatitudeLongitude( + common::UnitOfMeasure::DEGREE)); +} + +// --------------------------------------------------------------------------- + +GeographicCRSNNPtr GeographicCRS::createEPSG_4269() { + return create(createMapNameEPSGCode("NAD83", 4269), + datum::GeodeticReferenceFrame::EPSG_6269, + cs::EllipsoidalCS::createLatitudeLongitude( + common::UnitOfMeasure::DEGREE)); +} + +// --------------------------------------------------------------------------- + +GeographicCRSNNPtr GeographicCRS::createEPSG_4326() { + return create(createMapNameEPSGCode("WGS 84", 4326), + datum::GeodeticReferenceFrame::EPSG_6326, + cs::EllipsoidalCS::createLatitudeLongitude( + common::UnitOfMeasure::DEGREE)); +} + +// --------------------------------------------------------------------------- + +GeographicCRSNNPtr GeographicCRS::createOGC_CRS84() { + util::PropertyMap propertiesCRS; + propertiesCRS + .set(metadata::Identifier::CODESPACE_KEY, metadata::Identifier::OGC) + .set(metadata::Identifier::CODE_KEY, "CRS84") + .set(common::IdentifiedObject::NAME_KEY, "WGS 84 (CRS84)"); + return create(propertiesCRS, datum::GeodeticReferenceFrame::EPSG_6326, + cs::EllipsoidalCS::createLongitudeLatitude( // Long Lat ! + common::UnitOfMeasure::DEGREE)); +} + +// --------------------------------------------------------------------------- + +GeographicCRSNNPtr GeographicCRS::createEPSG_4979() { + return create( + createMapNameEPSGCode("WGS 84", 4979), + datum::GeodeticReferenceFrame::EPSG_6326, + cs::EllipsoidalCS::createLatitudeLongitudeEllipsoidalHeight( + common::UnitOfMeasure::DEGREE, common::UnitOfMeasure::METRE)); +} + +// --------------------------------------------------------------------------- + +GeographicCRSNNPtr GeographicCRS::createEPSG_4807() { + auto ellps(datum::Ellipsoid::createFlattenedSphere( + createMapNameEPSGCode("Clarke 1880 (IGN)", 7011), + common::Length(6378249.2), common::Scale(293.4660212936269))); + + auto cs(cs::EllipsoidalCS::createLatitudeLongitude( + common::UnitOfMeasure::GRAD)); + + auto datum(datum::GeodeticReferenceFrame::create( + createMapNameEPSGCode("Nouvelle Triangulation Francaise (Paris)", 6807), + ellps, util::optional<std::string>(), datum::PrimeMeridian::PARIS)); + + return create(createMapNameEPSGCode("NTF (Paris)", 4807), datum, cs); +} + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +void GeographicCRS::addAngularUnitConvertAndAxisSwap( + io::PROJStringFormatter *formatter) const { + const auto &axisList = coordinateSystem()->axisList(); + + if (formatter->convention() == + io::PROJStringFormatter::Convention::PROJ_5) { + + 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]; + strcpy(orderStr, order[0]); + strcat(orderStr, ","); + strcat(orderStr, order[1]); + formatter->addParam("order", orderStr); + } + } +} +//! @endcond + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +void GeographicCRS::_exportToPROJString( + io::PROJStringFormatter *formatter) const // throw(io::FormattingException) +{ + if (!formatter->omitProjLongLatIfPossible() || + primeMeridian()->longitude().getSIValue() != 0.0 || + !formatter->getTOWGS84Parameters().empty() || + !formatter->getHDatumExtension().empty()) { + formatter->addStep("longlat"); + addDatumInfoToPROJString(formatter); + } + + addAngularUnitConvertAndAxisSwap(formatter); +} +//! @endcond + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +struct VerticalCRS::Private { + std::vector<operation::TransformationNNPtr> geoidModel{}; + std::vector<operation::PointMotionOperationNNPtr> velocityModel{}; +}; +//! @endcond + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +static const datum::DatumEnsemblePtr & +checkEnsembleForVerticalCRS(const datum::VerticalReferenceFramePtr &datumIn, + const datum::DatumEnsemblePtr &ensemble) { + const char *msg = "One of Datum or DatumEnsemble should be defined"; + if (datumIn) { + if (!ensemble) { + return ensemble; + } + msg = "Datum and DatumEnsemble should not be defined"; + } else if (ensemble) { + const auto &datums = ensemble->datums(); + assert(!datums.empty()); + auto grfFirst = + dynamic_cast<datum::VerticalReferenceFrame *>(datums[0].get()); + if (grfFirst) { + return ensemble; + } + msg = "Ensemble should contain VerticalReferenceFrame"; + } + throw util::Exception(msg); +} +//! @endcond + +// --------------------------------------------------------------------------- + +VerticalCRS::VerticalCRS(const datum::VerticalReferenceFramePtr &datumIn, + const datum::DatumEnsemblePtr &datumEnsembleIn, + const cs::VerticalCSNNPtr &csIn) + : SingleCRS(datumIn, checkEnsembleForVerticalCRS(datumIn, datumEnsembleIn), + csIn), + d(internal::make_unique<Private>()) {} + +// --------------------------------------------------------------------------- + +VerticalCRS::VerticalCRS(const VerticalCRS &other) + : SingleCRS(other), d(internal::make_unique<Private>(*other.d)) {} + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +VerticalCRS::~VerticalCRS() = default; +//! @endcond + +// --------------------------------------------------------------------------- + +CRSNNPtr VerticalCRS::_shallowClone() const { + auto crs(VerticalCRS::nn_make_shared<VerticalCRS>(*this)); + crs->assignSelf(crs); + return crs; +} + +// --------------------------------------------------------------------------- + +/** \brief Return the datum::VerticalReferenceFrame associated with the CRS. + * + * @return a VerticalReferenceFrame. + */ +const datum::VerticalReferenceFramePtr VerticalCRS::datum() const { + return std::static_pointer_cast<datum::VerticalReferenceFrame>( + SingleCRS::getPrivate()->datum); +} + +// --------------------------------------------------------------------------- + +/** \brief Return the geoid model associated with the CRS. + * + * Geoid height model or height correction model linked to a geoid-based + * vertical CRS. + * + * @return a geoid model. might be null + */ +const std::vector<operation::TransformationNNPtr> & +VerticalCRS::geoidModel() PROJ_CONST_DEFN { + return d->geoidModel; +} + +// --------------------------------------------------------------------------- + +/** \brief Return the velocity model associated with the CRS. + * + * @return a velocity model. might be null. + */ +const std::vector<operation::PointMotionOperationNNPtr> & +VerticalCRS::velocityModel() PROJ_CONST_DEFN { + return d->velocityModel; +} + +// --------------------------------------------------------------------------- + +/** \brief Return the cs::VerticalCS associated with the CRS. + * + * @return a VerticalCS. + */ +const cs::VerticalCSNNPtr VerticalCRS::coordinateSystem() const { + return util::nn_static_pointer_cast<cs::VerticalCS>( + SingleCRS::getPrivate()->coordinateSystem); +} + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +void VerticalCRS::_exportToWKT(io::WKTFormatter *formatter) const { + const bool isWKT2 = formatter->version() == io::WKTFormatter::Version::WKT2; + formatter->startNode(isWKT2 ? io::WKTConstants::VERTCRS + : io::WKTConstants::VERT_CS, + !identifiers().empty()); + formatter->addQuotedString(nameStr()); + exportDatumOrDatumEnsembleToWkt(formatter); + const auto &cs = SingleCRS::getPrivate()->coordinateSystem; + const auto &axisList = cs->axisList(); + if (!isWKT2) { + axisList[0]->unit()._exportToWKT(formatter); + } + cs->_exportToWKT(formatter); + ObjectUsage::baseExportToWKT(formatter); + formatter->endNode(); +} +//! @endcond + +// --------------------------------------------------------------------------- + +void VerticalCRS::_exportToPROJString( + io::PROJStringFormatter *formatter) const // throw(io::FormattingException) +{ + auto geoidgrids = formatter->getVDatumExtension(); + if (!geoidgrids.empty()) { + formatter->addParam("geoidgrids", geoidgrids); + } + + auto &axisList = coordinateSystem()->axisList(); + if (!axisList.empty()) { + auto projUnit = axisList[0]->unit().exportToPROJString(); + if (projUnit.empty()) { + formatter->addParam("vto_meter", + axisList[0]->unit().conversionToSI()); + } else { + formatter->addParam("vunits", projUnit); + } + } +} + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +void VerticalCRS::addLinearUnitConvert( + io::PROJStringFormatter *formatter) const { + auto &axisList = coordinateSystem()->axisList(); + + if (formatter->convention() == + io::PROJStringFormatter::Convention::PROJ_5) { + if (!axisList.empty()) { + auto projUnit = axisList[0]->unit().exportToPROJString(); + if (axisList[0]->unit().conversionToSI() != 1.0) { + formatter->addStep("unitconvert"); + formatter->addParam("z_in", "m"); + auto projVUnit = axisList[0]->unit().exportToPROJString(); + if (projVUnit.empty()) { + formatter->addParam("z_out", + axisList[0]->unit().conversionToSI()); + } else { + formatter->addParam("z_out", projVUnit); + } + } + } + } +} +//! @endcond + +// --------------------------------------------------------------------------- + +/** \brief Instanciate a VerticalCRS from a datum::VerticalReferenceFrame and a + * cs::VerticalCS. + * + * @param properties See \ref general_properties. + * At minimum the name should be defined. + * @param datumIn The datum of the CRS. + * @param csIn a VerticalCS. + * @return new VerticalCRS. + */ +VerticalCRSNNPtr +VerticalCRS::create(const util::PropertyMap &properties, + const datum::VerticalReferenceFrameNNPtr &datumIn, + const cs::VerticalCSNNPtr &csIn) { + return create(properties, datumIn.as_nullable(), nullptr, csIn); +} + +// --------------------------------------------------------------------------- + +/** \brief Instanciate a VerticalCRS from a datum::VerticalReferenceFrame or + * datum::DatumEnsemble and a cs::VerticalCS. + * + * One and only one of datum or datumEnsemble should be set to a non-null value. + * + * @param properties See \ref general_properties. + * At minimum the name should be defined. + * @param datumIn The datum of the CRS, or nullptr + * @param datumEnsembleIn The datum ensemble of the CRS, or nullptr. + * @param csIn a VerticalCS. + * @return new VerticalCRS. + */ +VerticalCRSNNPtr +VerticalCRS::create(const util::PropertyMap &properties, + const datum::VerticalReferenceFramePtr &datumIn, + const datum::DatumEnsemblePtr &datumEnsembleIn, + const cs::VerticalCSNNPtr &csIn) { + auto crs(VerticalCRS::nn_make_shared<VerticalCRS>(datumIn, datumEnsembleIn, + csIn)); + crs->assignSelf(crs); + crs->setProperties(properties); + return crs; +} + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +bool VerticalCRS::_isEquivalentTo( + const util::IComparable *other, + util::IComparable::Criterion criterion) const { + auto otherVertCRS = dynamic_cast<const VerticalCRS *>(other); + // TODO test geoidModel and velocityModel + return otherVertCRS != nullptr && + SingleCRS::baseIsEquivalentTo(other, criterion); +} +//! @endcond + +// --------------------------------------------------------------------------- + +/** \brief Identify the CRS with reference CRSs. + * + * The candidate CRSs are looked in the database when + * authorityFactory is not null. + * + * The method returns a list of matching reference CRS, and the percentage + * (0-100) of confidence in the match. + * 100% means that the name of the reference entry + * perfectly matches the CRS name, and both are equivalent. In which case a + * single result is returned. + * 90% means that CRS are equivalent, but the names are not exactly the same. + * 70% means that CRS are equivalent (equivalent datum and coordinate system), + * but the names do not match at all. + * 25% means that the CRS are not equivalent, but there is some similarity in + * the names. + * + * @param authorityFactory Authority factory (if null, will return an empty + * list) + * @return a list of matching reference CRS, and the percentage (0-100) of + * confidence in the match. + */ +std::list<std::pair<VerticalCRSNNPtr, int>> +VerticalCRS::identify(const io::AuthorityFactoryPtr &authorityFactory) const { + typedef std::pair<VerticalCRSNNPtr, int> Pair; + std::list<Pair> res; + + const auto &thisName(nameStr()); + + if (authorityFactory) { + + const bool unsignificantName = thisName.empty() || + ci_equal(thisName, "unknown") || + ci_equal(thisName, "unnamed"); + if (!identifiers().empty()) { + // If the CRS has already an id, check in the database for the + // official object, and verify that they are equivalent. + for (const auto &id : identifiers()) { + try { + auto crs = io::AuthorityFactory::create( + authorityFactory->databaseContext(), + *id->codeSpace()) + ->createVerticalCRS(id->code()); + bool match = _isEquivalentTo( + crs.get(), util::IComparable::Criterion::EQUIVALENT); + res.emplace_back(crs, match ? 100 : 25); + return res; + } catch (const std::exception &) { + } + } + } else if (!unsignificantName) { + for (int ipass = 0; ipass < 2; ipass++) { + const bool approximateMatch = ipass == 1; + auto objects = authorityFactory->createObjectsFromName( + thisName, {io::AuthorityFactory::ObjectType::VERTICAL_CRS}, + approximateMatch); + for (const auto &obj : objects) { + auto crs = util::nn_dynamic_pointer_cast<VerticalCRS>(obj); + assert(crs); + auto crsNN = NN_NO_CHECK(crs); + if (_isEquivalentTo( + crs.get(), + util::IComparable::Criterion::EQUIVALENT)) { + if (crs->nameStr() == thisName) { + res.clear(); + res.emplace_back(crsNN, 100); + return res; + } + res.emplace_back(crsNN, 90); + } else { + res.emplace_back(crsNN, 25); + } + } + if (!res.empty()) { + break; + } + } + } + + // Sort results + res.sort([&thisName](const Pair &a, const Pair &b) { + // First consider confidence + if (a.second > b.second) { + return true; + } + if (a.second < b.second) { + return false; + } + + // Then consider exact name matching + const auto &aName(a.first->nameStr()); + const auto &bName(b.first->nameStr()); + if (aName == thisName && bName != thisName) { + return true; + } + if (bName == thisName && aName != thisName) { + return false; + } + + // Arbitrary final sorting criterion + return aName < bName; + }); + + // Keep only results of the highest confidence + if (res.size() >= 2) { + const auto highestConfidence = res.front().second; + std::list<Pair> newRes; + for (const auto &pair : res) { + if (pair.second == highestConfidence) { + newRes.push_back(pair); + } else { + break; + } + } + return newRes; + } + } + + return res; +} + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress + +std::list<std::pair<CRSNNPtr, int>> +VerticalCRS::_identify(const io::AuthorityFactoryPtr &authorityFactory) const { + typedef std::pair<CRSNNPtr, int> Pair; + std::list<Pair> res; + auto resTemp = identify(authorityFactory); + for (const auto &pair : resTemp) { + res.emplace_back(pair.first, pair.second); + } + return res; +} + +//! @endcond + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +struct DerivedCRS::Private { + SingleCRSNNPtr baseCRS_; + operation::ConversionNNPtr derivingConversion_; + + Private(const SingleCRSNNPtr &baseCRSIn, + const operation::ConversionNNPtr &derivingConversionIn) + : baseCRS_(baseCRSIn), derivingConversion_(derivingConversionIn) {} + + // For the conversion make a _shallowClone(), so that we can later set + // its targetCRS to this. + Private(const Private &other) + : baseCRS_(other.baseCRS_), + derivingConversion_(other.derivingConversion_->shallowClone()) {} +}; + +//! @endcond + +// --------------------------------------------------------------------------- + +// DerivedCRS is an abstract class, that virtually inherits from SingleCRS +// Consequently the base constructor in SingleCRS will never be called by +// that constructor. clang -Wabstract-vbase-init and VC++ underline this, but +// other +// compilers will complain if we don't call the base constructor. + +DerivedCRS::DerivedCRS(const SingleCRSNNPtr &baseCRSIn, + const operation::ConversionNNPtr &derivingConversionIn, + const cs::CoordinateSystemNNPtr & +#if !defined(COMPILER_WARNS_ABOUT_ABSTRACT_VBASE_INIT) + cs +#endif + ) + : +#if !defined(COMPILER_WARNS_ABOUT_ABSTRACT_VBASE_INIT) + SingleCRS(baseCRSIn->datum(), baseCRSIn->datumEnsemble(), cs), +#endif + d(internal::make_unique<Private>(baseCRSIn, derivingConversionIn)) { +} + +// --------------------------------------------------------------------------- + +DerivedCRS::DerivedCRS(const DerivedCRS &other) + : +#if !defined(COMPILER_WARNS_ABOUT_ABSTRACT_VBASE_INIT) + SingleCRS(other), +#endif + d(internal::make_unique<Private>(*other.d)) { +} + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +DerivedCRS::~DerivedCRS() = default; +//! @endcond + +// --------------------------------------------------------------------------- + +/** \brief Return the base CRS of a DerivedCRS. + * + * @return the base CRS. + */ +const SingleCRSNNPtr &DerivedCRS::baseCRS() PROJ_CONST_DEFN { + return d->baseCRS_; +} + +// --------------------------------------------------------------------------- + +/** \brief Return the deriving conversion from the base CRS to this CRS. + * + * @return the deriving conversion. + */ +const operation::ConversionNNPtr DerivedCRS::derivingConversion() const { + return d->derivingConversion_->shallowClone(); +} + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +const operation::ConversionNNPtr & +DerivedCRS::derivingConversionRef() PROJ_CONST_DEFN { + return d->derivingConversion_; +} +//! @endcond + +// --------------------------------------------------------------------------- + +bool DerivedCRS::_isEquivalentTo(const util::IComparable *other, + util::IComparable::Criterion criterion) const { + auto otherDerivedCRS = dynamic_cast<const DerivedCRS *>(other); + const auto standardCriterion = getStandardCriterion(criterion); + if (otherDerivedCRS == nullptr || + !SingleCRS::baseIsEquivalentTo(other, standardCriterion)) { + return false; + } + return d->baseCRS_->_isEquivalentTo(otherDerivedCRS->d->baseCRS_.get(), + criterion) && + d->derivingConversion_->_isEquivalentTo( + otherDerivedCRS->d->derivingConversion_.get(), + standardCriterion); +} + +// --------------------------------------------------------------------------- + +void DerivedCRS::setDerivingConversionCRS() { + derivingConversionRef()->setWeakSourceTargetCRS( + baseCRS().as_nullable(), + std::static_pointer_cast<CRS>(shared_from_this().as_nullable())); +} + +// --------------------------------------------------------------------------- + +void DerivedCRS::baseExportToPROJString( + io::PROJStringFormatter *formatter) const // throw(io::FormattingException) +{ + d->derivingConversion_->_exportToPROJString(formatter); +} + +// --------------------------------------------------------------------------- + +void DerivedCRS::baseExportToWKT(io::WKTFormatter *&formatter, + const std::string &keyword, + const std::string &baseKeyword) const { + formatter->startNode(keyword, !identifiers().empty()); + formatter->addQuotedString(nameStr()); + + const auto &l_baseCRS = d->baseCRS_; + formatter->startNode(baseKeyword, !l_baseCRS->identifiers().empty()); + formatter->addQuotedString(l_baseCRS->nameStr()); + l_baseCRS->exportDatumOrDatumEnsembleToWkt(formatter); + formatter->endNode(); + + formatter->setUseDerivingConversion(true); + derivingConversionRef()->_exportToWKT(formatter); + formatter->setUseDerivingConversion(false); + + coordinateSystem()->_exportToWKT(formatter); + ObjectUsage::baseExportToWKT(formatter); + formatter->endNode(); +} + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +struct ProjectedCRS::Private { + GeodeticCRSNNPtr baseCRS_; + cs::CartesianCSNNPtr cs_; + Private(const GeodeticCRSNNPtr &baseCRSIn, const cs::CartesianCSNNPtr &csIn) + : baseCRS_(baseCRSIn), cs_(csIn) {} + + inline const GeodeticCRSNNPtr &baseCRS() const { return baseCRS_; } + + inline const cs::CartesianCSNNPtr &coordinateSystem() const { return cs_; } +}; +//! @endcond + +// --------------------------------------------------------------------------- + +ProjectedCRS::ProjectedCRS( + const GeodeticCRSNNPtr &baseCRSIn, + const operation::ConversionNNPtr &derivingConversionIn, + const cs::CartesianCSNNPtr &csIn) + : SingleCRS(baseCRSIn->datum(), baseCRSIn->datumEnsemble(), csIn), + DerivedCRS(baseCRSIn, derivingConversionIn, csIn), + d(internal::make_unique<Private>(baseCRSIn, csIn)) {} + +// --------------------------------------------------------------------------- + +ProjectedCRS::ProjectedCRS(const ProjectedCRS &other) + : SingleCRS(other), DerivedCRS(other), + d(internal::make_unique<Private>(other.baseCRS(), + other.coordinateSystem())) {} + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +ProjectedCRS::~ProjectedCRS() = default; +//! @endcond + +// --------------------------------------------------------------------------- + +CRSNNPtr ProjectedCRS::_shallowClone() const { + auto crs(ProjectedCRS::nn_make_shared<ProjectedCRS>(*this)); + crs->assignSelf(crs); + crs->setDerivingConversionCRS(); + return crs; +} + +// --------------------------------------------------------------------------- + +/** \brief Return the base CRS (a GeodeticCRS, which is generally a + * GeographicCRS) of the ProjectedCRS. + * + * @return the base CRS. + */ +const GeodeticCRSNNPtr &ProjectedCRS::baseCRS() PROJ_CONST_DEFN { + return d->baseCRS(); +} + +// --------------------------------------------------------------------------- + +/** \brief Return the cs::CartesianCS associated with the CRS. + * + * @return a CartesianCS + */ +const cs::CartesianCSNNPtr &ProjectedCRS::coordinateSystem() PROJ_CONST_DEFN { + return d->coordinateSystem(); +} + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +void ProjectedCRS::_exportToWKT(io::WKTFormatter *formatter) const { + const bool isWKT2 = formatter->version() == io::WKTFormatter::Version::WKT2; + + if (!isWKT2 && !formatter->useESRIDialect() && + starts_with(nameStr(), "Popular Visualisation CRS / Mercator")) { + formatter->startNode(io::WKTConstants::PROJCS, !identifiers().empty()); + formatter->addQuotedString(nameStr()); + formatter->setTOWGS84Parameters({0, 0, 0, 0, 0, 0, 0}); + baseCRS()->_exportToWKT(formatter); + formatter->setTOWGS84Parameters({}); + + formatter->startNode(io::WKTConstants::PROJECTION, false); + formatter->addQuotedString("Mercator_1SP"); + formatter->endNode(); + + formatter->startNode(io::WKTConstants::PARAMETER, false); + formatter->addQuotedString("central_meridian"); + formatter->add(0.0); + formatter->endNode(); + + formatter->startNode(io::WKTConstants::PARAMETER, false); + formatter->addQuotedString("scale_factor"); + formatter->add(1.0); + formatter->endNode(); + + formatter->startNode(io::WKTConstants::PARAMETER, false); + formatter->addQuotedString("false_easting"); + formatter->add(0.0); + formatter->endNode(); + + formatter->startNode(io::WKTConstants::PARAMETER, false); + formatter->addQuotedString("false_northing"); + formatter->add(0.0); + formatter->endNode(); + + const auto &axisList = d->coordinateSystem()->axisList(); + axisList[0]->unit()._exportToWKT(formatter); + d->coordinateSystem()->_exportToWKT(formatter); + derivingConversionRef()->addWKTExtensionNode(formatter); + ObjectUsage::baseExportToWKT(formatter); + formatter->endNode(); + return; + } + + formatter->startNode(isWKT2 ? io::WKTConstants::PROJCRS + : io::WKTConstants::PROJCS, + !identifiers().empty()); + auto l_name = nameStr(); + if (formatter->useESRIDialect()) { + bool aliasFound = false; + const auto &dbContext = formatter->databaseContext(); + if (dbContext) { + auto l_alias = dbContext->getAliasFromOfficialName( + l_name, "projected_crs", "ESRI"); + if (!l_alias.empty()) { + l_name = l_alias; + aliasFound = true; + } + } + if (!aliasFound) { + l_name = io::WKTFormatter::morphNameToESRI(l_name); + } + } + if (!isWKT2 && !formatter->useESRIDialect() && isDeprecated()) { + l_name += " (deprecated)"; + } + formatter->addQuotedString(l_name); + + const auto &l_baseCRS = d->baseCRS(); + const auto &geodeticCRSAxisList = l_baseCRS->coordinateSystem()->axisList(); + + if (isWKT2) { + formatter->startNode( + (formatter->use2018Keywords() && + dynamic_cast<const GeographicCRS *>(l_baseCRS.get())) + ? io::WKTConstants::BASEGEOGCRS + : io::WKTConstants::BASEGEODCRS, + !l_baseCRS->identifiers().empty()); + formatter->addQuotedString(l_baseCRS->nameStr()); + l_baseCRS->exportDatumOrDatumEnsembleToWkt(formatter); + // insert ellipsoidal cs unit when the units of the map + // projection angular parameters are not explicitly given within those + // parameters. See + // http://docs.opengeospatial.org/is/12-063r5/12-063r5.html#61 + if (formatter->primeMeridianOrParameterUnitOmittedIfSameAsAxis()) { + geodeticCRSAxisList[0]->unit()._exportToWKT(formatter); + } + l_baseCRS->primeMeridian()->_exportToWKT(formatter); + formatter->endNode(); + } else { + l_baseCRS->_exportToWKT(formatter); + } + + const auto &axisList = d->coordinateSystem()->axisList(); + formatter->pushAxisLinearUnit( + common::UnitOfMeasure::create(axisList[0]->unit())); + + formatter->pushAxisAngularUnit( + common::UnitOfMeasure::create(geodeticCRSAxisList[0]->unit())); + + derivingConversionRef()->_exportToWKT(formatter); + + formatter->popAxisAngularUnit(); + + formatter->popAxisLinearUnit(); + + if (!isWKT2) { + axisList[0]->unit()._exportToWKT(formatter); + } + + d->coordinateSystem()->_exportToWKT(formatter); + + if (!isWKT2 && !formatter->useESRIDialect()) { + derivingConversionRef()->addWKTExtensionNode(formatter); + } + + ObjectUsage::baseExportToWKT(formatter); + formatter->endNode(); + return; +} +//! @endcond + +// --------------------------------------------------------------------------- + +void ProjectedCRS::_exportToPROJString( + io::PROJStringFormatter *formatter) const // throw(io::FormattingException) +{ + baseExportToPROJString(formatter); +} + +// --------------------------------------------------------------------------- + +/** \brief Instanciate a ProjectedCRS from a base CRS, a deriving + * operation::Conversion + * and a coordinate system. + * + * @param properties See \ref general_properties. + * At minimum the name should be defined. + * @param baseCRSIn The base CRS, a GeodeticCRS that is generally a + * GeographicCRS. + * @param derivingConversionIn The deriving operation::Conversion (typically + * using a map + * projection method) + * @param csIn The coordniate system. + * @return new ProjectedCRS. + */ +ProjectedCRSNNPtr +ProjectedCRS::create(const util::PropertyMap &properties, + const GeodeticCRSNNPtr &baseCRSIn, + const operation::ConversionNNPtr &derivingConversionIn, + const cs::CartesianCSNNPtr &csIn) { + auto crs = ProjectedCRS::nn_make_shared<ProjectedCRS>( + baseCRSIn, derivingConversionIn, csIn); + crs->assignSelf(crs); + crs->setProperties(properties); + crs->setDerivingConversionCRS(); + return crs; +} + +// --------------------------------------------------------------------------- + +bool ProjectedCRS::_isEquivalentTo( + const util::IComparable *other, + util::IComparable::Criterion criterion) const { + auto otherProjCRS = dynamic_cast<const ProjectedCRS *>(other); + return otherProjCRS != nullptr && + DerivedCRS::_isEquivalentTo(other, criterion); +} + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +void ProjectedCRS::addUnitConvertAndAxisSwap(io::PROJStringFormatter *formatter, + bool axisSpecFound) const { + const auto &axisList = d->coordinateSystem()->axisList(); + const auto &unit = axisList[0]->unit(); + if (unit != common::UnitOfMeasure::METRE) { + auto projUnit = unit.exportToPROJString(); + const double toSI = unit.conversionToSI(); + if (formatter->convention() == + io::PROJStringFormatter::Convention::PROJ_5) { + formatter->addStep("unitconvert"); + formatter->addParam("xy_in", "m"); + formatter->addParam("z_in", "m"); + if (projUnit.empty()) { + formatter->addParam("xy_out", toSI); + formatter->addParam("z_out", toSI); + } else { + formatter->addParam("xy_out", projUnit); + formatter->addParam("z_out", projUnit); + } + } else { + if (projUnit.empty()) { + formatter->addParam("to_meter", toSI); + } else { + formatter->addParam("units", projUnit); + } + } + } + + if (formatter->convention() == + io::PROJStringFormatter::Convention::PROJ_5 && + !axisSpecFound) { + const auto &dir0 = axisList[0]->direction(); + const auto &dir1 = axisList[1]->direction(); + if (!(&dir0 == &cs::AxisDirection::EAST && + &dir1 == &cs::AxisDirection::NORTH) && + // For polar projections, that have south+south direction, + // we don't want to mess with axes. + dir0 != dir1) { + + const char *order[2] = {nullptr, nullptr}; + 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] = "1"; + else if (&dir == &cs::AxisDirection::SOUTH) + order[i] = "-2"; + else if (&dir == &cs::AxisDirection::NORTH) + order[i] = "2"; + } + + if (order[0] && order[1]) { + formatter->addStep("axisswap"); + char orderStr[10]; + strcpy(orderStr, order[0]); + strcat(orderStr, ","); + strcat(orderStr, order[1]); + formatter->addParam("order", orderStr); + } + } else { + const auto &name0 = axisList[0]->nameStr(); + const auto &name1 = axisList[1]->nameStr(); + const bool northingEasting = ci_starts_with(name0, "northing") && + ci_starts_with(name1, "easting"); + // case of EPSG:32661 ["WGS 84 / UPS North (N,E)]" + // case of EPSG:32761 ["WGS 84 / UPS South (N,E)]" + if (((&dir0 == &cs::AxisDirection::SOUTH && + &dir1 == &cs::AxisDirection::SOUTH) || + (&dir0 == &cs::AxisDirection::NORTH && + &dir1 == &cs::AxisDirection::NORTH)) && + northingEasting) { + formatter->addStep("axisswap"); + formatter->addParam("order", "2,1"); + } + } + } +} +//! @endcond + +// --------------------------------------------------------------------------- + +/** \brief Identify the CRS with reference CRSs. + * + * The candidate CRSs are either hard-coded, or looked in the database when + * authorityFactory is not null. + * + * The method returns a list of matching reference CRS, and the percentage + * (0-100) of confidence in the match. + * 100% means that the name of the reference entry + * perfectly matches the CRS name, and both are equivalent. In which case a + * single result is returned. + * 90% means that CRS are equivalent, but the names are not exactly the same. + * 70% means that CRS are equivalent (equivalent base CRS, conversion and + * coordinate system), but the names do not match at all. + * 50% means that CRS have similarity (equivalent base CRS and conversion), + * but the coordinate system do not match (e.g. different axis ordering or + * axis unit). + * 25% means that the CRS are not equivalent, but there is some similarity in + * the names. + * + * For the purpose of this function, equivalence is tested with the + * util::IComparable::Criterion::EQUIVALENT_EXCEPT_AXIS_ORDER_GEOGCRS, that is + * to say that the axis order of the base GeographicCRS is ignored. + * + * @param authorityFactory Authority factory (or null, but degraded + * functionality) + * @return a list of matching reference CRS, and the percentage (0-100) of + * confidence in the match. + */ +std::list<std::pair<ProjectedCRSNNPtr, int>> +ProjectedCRS::identify(const io::AuthorityFactoryPtr &authorityFactory) const { + typedef std::pair<ProjectedCRSNNPtr, int> Pair; + std::list<Pair> res; + + const auto &thisName(nameStr()); + + std::list<std::pair<GeodeticCRSNNPtr, int>> baseRes; + const auto &l_baseCRS(baseCRS()); + auto geogCRS = dynamic_cast<const GeographicCRS *>(l_baseCRS.get()); + if (geogCRS && + geogCRS->coordinateSystem()->axisOrder() == + cs::EllipsoidalCS::AxisOrder::LONG_EAST_LAT_NORTH) { + baseRes = + GeographicCRS::create( + util::PropertyMap().set(common::IdentifiedObject::NAME_KEY, + geogCRS->nameStr()), + geogCRS->datum(), geogCRS->datumEnsemble(), + cs::EllipsoidalCS::createLatitudeLongitude( + geogCRS->coordinateSystem()->axisList()[0]->unit())) + ->identify(authorityFactory); + } else { + baseRes = l_baseCRS->identify(authorityFactory); + } + + int zone = 0; + bool north = false; + + auto computeConfidence = [&thisName](const std::string &crsName) { + return crsName == thisName ? 100 + : metadata::Identifier::isEquivalentName( + crsName.c_str(), thisName.c_str()) + ? 90 + : 70; + }; + auto computeUTMCRSName = [](const char *base, int l_zone, bool l_north) { + return base + toString(l_zone) + (l_north ? "N" : "S"); + }; + + const auto &conv = derivingConversionRef(); + const auto &cs = coordinateSystem(); + if (baseRes.size() == 1 && baseRes.front().second >= 70 && + conv->isUTM(zone, north) && + cs->_isEquivalentTo( + cs::CartesianCS::createEastingNorthing(common::UnitOfMeasure::METRE) + .get())) { + if (baseRes.front().first->_isEquivalentTo( + GeographicCRS::EPSG_4326.get(), + util::IComparable::Criterion::EQUIVALENT)) { + std::string crsName( + computeUTMCRSName("WGS 84 / UTM zone ", zone, north)); + res.emplace_back( + ProjectedCRS::create( + createMapNameEPSGCode(crsName.c_str(), + (north ? 32600 : 32700) + zone), + GeographicCRS::EPSG_4326, conv->identify(), cs), + computeConfidence(crsName)); + return res; + } else if (((zone >= 1 && zone <= 22) || zone == 59 || zone == 60) && + north && + baseRes.front().first->_isEquivalentTo( + GeographicCRS::EPSG_4267.get(), + util::IComparable::Criterion::EQUIVALENT)) { + std::string crsName( + computeUTMCRSName("NAD27 / UTM zone ", zone, north)); + res.emplace_back( + ProjectedCRS::create( + createMapNameEPSGCode(crsName.c_str(), + (zone >= 59) ? 3370 + zone - 59 + : 26700 + zone), + GeographicCRS::EPSG_4267, conv->identify(), cs), + computeConfidence(crsName)); + return res; + } else if (((zone >= 1 && zone <= 23) || zone == 59 || zone == 60) && + north && + baseRes.front().first->_isEquivalentTo( + GeographicCRS::EPSG_4269.get(), + util::IComparable::Criterion::EQUIVALENT)) { + std::string crsName( + computeUTMCRSName("NAD83 / UTM zone ", zone, north)); + res.emplace_back( + ProjectedCRS::create( + createMapNameEPSGCode(crsName.c_str(), + (zone >= 59) ? 3372 + zone - 59 + : 26900 + zone), + GeographicCRS::EPSG_4269, conv->identify(), cs), + computeConfidence(crsName)); + return res; + } + } + + if (authorityFactory) { + + const bool unsignificantName = thisName.empty() || + ci_equal(thisName, "unknown") || + ci_equal(thisName, "unnamed"); + bool foundEquivalentName = false; + + if (!identifiers().empty()) { + // If the CRS has already an id, check in the database for the + // official object, and verify that they are equivalent. + for (const auto &id : identifiers()) { + try { + auto crs = io::AuthorityFactory::create( + authorityFactory->databaseContext(), + *id->codeSpace()) + ->createProjectedCRS(id->code()); + bool match = _isEquivalentTo( + crs.get(), util::IComparable::Criterion::EQUIVALENT); + res.emplace_back(crs, match ? 100 : 25); + return res; + } catch (const std::exception &) { + } + } + } else if (!unsignificantName) { + for (int ipass = 0; ipass < 2; ipass++) { + const bool approximateMatch = ipass == 1; + auto objects = authorityFactory->createObjectsFromName( + thisName, {io::AuthorityFactory::ObjectType::PROJECTED_CRS}, + approximateMatch); + for (const auto &obj : objects) { + auto crs = util::nn_dynamic_pointer_cast<ProjectedCRS>(obj); + assert(crs); + auto crsNN = NN_NO_CHECK(crs); + const bool eqName = metadata::Identifier::isEquivalentName( + thisName.c_str(), crs->nameStr().c_str()); + foundEquivalentName |= eqName; + if (_isEquivalentTo( + crs.get(), + util::IComparable::Criterion::EQUIVALENT)) { + if (crs->nameStr() == thisName) { + res.clear(); + res.emplace_back(crsNN, 100); + return res; + } + res.emplace_back(crsNN, eqName ? 90 : 70); + } else { + res.emplace_back(crsNN, 25); + } + } + if (!res.empty()) { + break; + } + } + } + + const auto lambdaSort = [&thisName](const Pair &a, const Pair &b) { + // First consider confidence + if (a.second > b.second) { + return true; + } + if (a.second < b.second) { + return false; + } + + // Then consider exact name matching + const auto &aName(a.first->nameStr()); + const auto &bName(b.first->nameStr()); + if (aName == thisName && bName != thisName) { + return true; + } + if (bName == thisName && aName != thisName) { + return false; + } + + // Arbitrary final sorting criterion + return aName < bName; + }; + + // Sort results + res.sort(lambdaSort); + + if (identifiers().empty() && !foundEquivalentName && + (res.empty() || res.front().second < 50)) { + std::set<std::pair<std::string, std::string>> alreadyKnown; + for (const auto &pair : res) { + const auto &ids = pair.first->identifiers(); + assert(!ids.empty()); + alreadyKnown.insert(std::pair<std::string, std::string>( + *(ids[0]->codeSpace()), ids[0]->code())); + } + + auto self = NN_NO_CHECK(std::dynamic_pointer_cast<ProjectedCRS>( + shared_from_this().as_nullable())); + auto candidates = + authorityFactory->createProjectedCRSFromExisting(self); + const auto &ellipsoid = l_baseCRS->ellipsoid(); + for (const auto &crs : candidates) { + const auto &ids = crs->identifiers(); + assert(!ids.empty()); + if (alreadyKnown.find(std::pair<std::string, std::string>( + *(ids[0]->codeSpace()), ids[0]->code())) != + alreadyKnown.end()) { + continue; + } + + if (_isEquivalentTo(crs.get(), + util::IComparable::Criterion::EQUIVALENT)) { + res.emplace_back(crs, unsignificantName ? 90 : 70); + } else if (ellipsoid->_isEquivalentTo( + crs->baseCRS()->ellipsoid().get(), + util::IComparable::Criterion::EQUIVALENT) && + derivingConversionRef()->_isEquivalentTo( + crs->derivingConversionRef().get(), + util::IComparable::Criterion::EQUIVALENT)) { + if (coordinateSystem()->_isEquivalentTo( + crs->coordinateSystem().get(), + util::IComparable::Criterion::EQUIVALENT)) { + res.emplace_back(crs, 70); + } else { + res.emplace_back(crs, 50); + } + } else { + res.emplace_back(crs, 25); + } + } + + res.sort(lambdaSort); + } + + // Keep only results of the highest confidence + if (res.size() >= 2) { + const auto highestConfidence = res.front().second; + std::list<Pair> newRes; + for (const auto &pair : res) { + if (pair.second == highestConfidence) { + newRes.push_back(pair); + } else { + break; + } + } + return newRes; + } + } + + return res; +} + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress + +std::list<std::pair<CRSNNPtr, int>> +ProjectedCRS::_identify(const io::AuthorityFactoryPtr &authorityFactory) const { + typedef std::pair<CRSNNPtr, int> Pair; + std::list<Pair> res; + auto resTemp = identify(authorityFactory); + for (const auto &pair : resTemp) { + res.emplace_back(pair.first, pair.second); + } + return res; +} + +//! @endcond + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +struct CompoundCRS::Private { + std::vector<CRSNNPtr> components_{}; +}; +//! @endcond + +// --------------------------------------------------------------------------- + +CompoundCRS::CompoundCRS(const std::vector<CRSNNPtr> &components) + : CRS(), d(internal::make_unique<Private>()) { + d->components_ = components; +} + +// --------------------------------------------------------------------------- + +CompoundCRS::CompoundCRS(const CompoundCRS &other) + : CRS(other), d(internal::make_unique<Private>(*other.d)) {} + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +CompoundCRS::~CompoundCRS() = default; +//! @endcond + +// --------------------------------------------------------------------------- + +CRSNNPtr CompoundCRS::_shallowClone() const { + auto crs(CompoundCRS::nn_make_shared<CompoundCRS>(*this)); + crs->assignSelf(crs); + return crs; +} + +// --------------------------------------------------------------------------- + +/** \brief Return the components of a CompoundCRS. + * + * @return the components. + */ +const std::vector<CRSNNPtr> & +CompoundCRS::componentReferenceSystems() PROJ_CONST_DEFN { + return d->components_; +} + +// --------------------------------------------------------------------------- + +/** \brief Instanciate a CompoundCRS from a vector of CRS. + * + * @param properties See \ref general_properties. + * At minimum the name should be defined. + * @param components the component CRS of the CompoundCRS. + * @return new CompoundCRS. + */ +CompoundCRSNNPtr CompoundCRS::create(const util::PropertyMap &properties, + const std::vector<CRSNNPtr> &components) { + auto compoundCRS(CompoundCRS::nn_make_shared<CompoundCRS>(components)); + compoundCRS->assignSelf(compoundCRS); + compoundCRS->setProperties(properties); + if (properties.find(common::IdentifiedObject::NAME_KEY) == + properties.end()) { + std::string name; + for (const auto &crs : components) { + if (!name.empty()) { + name += " + "; + } + const auto &l_name = crs->nameStr(); + if (!l_name.empty()) { + name += l_name; + } else { + name += "unnamed"; + } + } + util::PropertyMap propertyName; + propertyName.set(common::IdentifiedObject::NAME_KEY, name); + compoundCRS->setProperties(propertyName); + } + + return compoundCRS; +} + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +void CompoundCRS::_exportToWKT(io::WKTFormatter *formatter) const { + const bool isWKT2 = formatter->version() == io::WKTFormatter::Version::WKT2; + formatter->startNode(isWKT2 ? io::WKTConstants::COMPOUNDCRS + : io::WKTConstants::COMPD_CS, + !identifiers().empty()); + formatter->addQuotedString(nameStr()); + for (const auto &crs : componentReferenceSystems()) { + crs->_exportToWKT(formatter); + } + ObjectUsage::baseExportToWKT(formatter); + formatter->endNode(); +} +//! @endcond + +// --------------------------------------------------------------------------- + +void CompoundCRS::_exportToPROJString( + io::PROJStringFormatter *formatter) const // throw(io::FormattingException) +{ + for (const auto &crs : componentReferenceSystems()) { + auto crs_exportable = + dynamic_cast<const IPROJStringExportable *>(crs.get()); + if (crs_exportable) { + crs_exportable->_exportToPROJString(formatter); + } + } +} + +// --------------------------------------------------------------------------- + +bool CompoundCRS::_isEquivalentTo( + const util::IComparable *other, + util::IComparable::Criterion criterion) const { + auto otherCompoundCRS = dynamic_cast<const CompoundCRS *>(other); + if (otherCompoundCRS == nullptr || + (criterion == util::IComparable::Criterion::STRICT && + !ObjectUsage::_isEquivalentTo(other, criterion))) { + return false; + } + const auto &components = componentReferenceSystems(); + const auto &otherComponents = otherCompoundCRS->componentReferenceSystems(); + if (components.size() != otherComponents.size()) { + return false; + } + for (size_t i = 0; i < components.size(); i++) { + if (!components[i]->_isEquivalentTo(otherComponents[i].get(), + criterion)) { + return false; + } + } + return true; +} + +// --------------------------------------------------------------------------- + +/** \brief Identify the CRS with reference CRSs. + * + * The candidate CRSs are looked in the database when + * authorityFactory is not null. + * + * The method returns a list of matching reference CRS, and the percentage + * (0-100) of confidence in the match. + * 100% means that the name of the reference entry + * perfectly matches the CRS name, and both are equivalent. In which case a + * single result is returned. + * 90% means that CRS are equivalent, but the names are not exactly the same. + * 70% means that CRS are equivalent (equivalent horizontal and vertical CRS), + * but the names do not match at all. + * 25% means that the CRS are not equivalent, but there is some similarity in + * the names. + * + * @param authorityFactory Authority factory (if null, will return an empty + * list) + * @return a list of matching reference CRS, and the percentage (0-100) of + * confidence in the match. + */ +std::list<std::pair<CompoundCRSNNPtr, int>> +CompoundCRS::identify(const io::AuthorityFactoryPtr &authorityFactory) const { + typedef std::pair<CompoundCRSNNPtr, int> Pair; + std::list<Pair> res; + + const auto &thisName(nameStr()); + + if (authorityFactory) { + + const bool unsignificantName = thisName.empty() || + ci_equal(thisName, "unknown") || + ci_equal(thisName, "unnamed"); + bool foundEquivalentName = false; + + if (!identifiers().empty()) { + // If the CRS has already an id, check in the database for the + // official object, and verify that they are equivalent. + for (const auto &id : identifiers()) { + try { + auto crs = io::AuthorityFactory::create( + authorityFactory->databaseContext(), + *id->codeSpace()) + ->createCompoundCRS(id->code()); + bool match = _isEquivalentTo( + crs.get(), util::IComparable::Criterion::EQUIVALENT); + res.emplace_back(crs, match ? 100 : 25); + return res; + } catch (const std::exception &) { + } + } + } else if (!unsignificantName) { + for (int ipass = 0; ipass < 2; ipass++) { + const bool approximateMatch = ipass == 1; + auto objects = authorityFactory->createObjectsFromName( + thisName, {io::AuthorityFactory::ObjectType::COMPOUND_CRS}, + approximateMatch); + for (const auto &obj : objects) { + auto crs = util::nn_dynamic_pointer_cast<CompoundCRS>(obj); + assert(crs); + auto crsNN = NN_NO_CHECK(crs); + const bool eqName = metadata::Identifier::isEquivalentName( + thisName.c_str(), crs->nameStr().c_str()); + foundEquivalentName |= eqName; + if (_isEquivalentTo( + crs.get(), + util::IComparable::Criterion::EQUIVALENT)) { + if (crs->nameStr() == thisName) { + res.clear(); + res.emplace_back(crsNN, 100); + return res; + } + res.emplace_back(crsNN, eqName ? 90 : 70); + } else { + res.emplace_back(crsNN, 25); + } + } + if (!res.empty()) { + break; + } + } + } + + const auto lambdaSort = [&thisName](const Pair &a, const Pair &b) { + // First consider confidence + if (a.second > b.second) { + return true; + } + if (a.second < b.second) { + return false; + } + + // Then consider exact name matching + const auto &aName(a.first->nameStr()); + const auto &bName(b.first->nameStr()); + if (aName == thisName && bName != thisName) { + return true; + } + if (bName == thisName && aName != thisName) { + return false; + } + + // Arbitrary final sorting criterion + return aName < bName; + }; + + // Sort results + res.sort(lambdaSort); + + if (identifiers().empty() && !foundEquivalentName && + (res.empty() || res.front().second < 50)) { + std::set<std::pair<std::string, std::string>> alreadyKnown; + for (const auto &pair : res) { + const auto &ids = pair.first->identifiers(); + assert(!ids.empty()); + alreadyKnown.insert(std::pair<std::string, std::string>( + *(ids[0]->codeSpace()), ids[0]->code())); + } + + auto self = NN_NO_CHECK(std::dynamic_pointer_cast<CompoundCRS>( + shared_from_this().as_nullable())); + auto candidates = + authorityFactory->createCompoundCRSFromExisting(self); + for (const auto &crs : candidates) { + const auto &ids = crs->identifiers(); + assert(!ids.empty()); + if (alreadyKnown.find(std::pair<std::string, std::string>( + *(ids[0]->codeSpace()), ids[0]->code())) != + alreadyKnown.end()) { + continue; + } + + if (_isEquivalentTo(crs.get(), + util::IComparable::Criterion::EQUIVALENT)) { + res.emplace_back(crs, unsignificantName ? 90 : 70); + } else { + res.emplace_back(crs, 25); + } + } + + res.sort(lambdaSort); + } + + // Keep only results of the highest confidence + if (res.size() >= 2) { + const auto highestConfidence = res.front().second; + std::list<Pair> newRes; + for (const auto &pair : res) { + if (pair.second == highestConfidence) { + newRes.push_back(pair); + } else { + break; + } + } + return newRes; + } + } + + return res; +} + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress + +std::list<std::pair<CRSNNPtr, int>> +CompoundCRS::_identify(const io::AuthorityFactoryPtr &authorityFactory) const { + typedef std::pair<CRSNNPtr, int> Pair; + std::list<Pair> res; + auto resTemp = identify(authorityFactory); + for (const auto &pair : resTemp) { + res.emplace_back(pair.first, pair.second); + } + return res; +} + +//! @endcond + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +struct PROJ_INTERNAL BoundCRS::Private { + CRSNNPtr baseCRS_; + CRSNNPtr hubCRS_; + operation::TransformationNNPtr transformation_; + + Private(const CRSNNPtr &baseCRSIn, const CRSNNPtr &hubCRSIn, + const operation::TransformationNNPtr &transformationIn); + + inline const CRSNNPtr &baseCRS() const { return baseCRS_; } + inline const CRSNNPtr &hubCRS() const { return hubCRS_; } + inline const operation::TransformationNNPtr &transformation() const { + return transformation_; + } +}; + +BoundCRS::Private::Private( + const CRSNNPtr &baseCRSIn, const CRSNNPtr &hubCRSIn, + const operation::TransformationNNPtr &transformationIn) + : baseCRS_(baseCRSIn), hubCRS_(hubCRSIn), + transformation_(transformationIn) {} + +//! @endcond + +// --------------------------------------------------------------------------- + +BoundCRS::BoundCRS(const CRSNNPtr &baseCRSIn, const CRSNNPtr &hubCRSIn, + const operation::TransformationNNPtr &transformationIn) + : d(internal::make_unique<Private>(baseCRSIn, hubCRSIn, transformationIn)) { +} + +// --------------------------------------------------------------------------- + +BoundCRS::BoundCRS(const BoundCRS &other) + : CRS(other), + d(internal::make_unique<Private>(other.d->baseCRS(), other.d->hubCRS(), + other.d->transformation())) {} + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +BoundCRS::~BoundCRS() = default; +//! @endcond + +// --------------------------------------------------------------------------- + +BoundCRSNNPtr BoundCRS::shallowCloneAsBoundCRS() const { + auto crs(BoundCRS::nn_make_shared<BoundCRS>(*this)); + crs->assignSelf(crs); + return crs; +} + +// --------------------------------------------------------------------------- + +CRSNNPtr BoundCRS::_shallowClone() const { return shallowCloneAsBoundCRS(); } + +// --------------------------------------------------------------------------- + +/** \brief Return the base CRS. + * + * This is the CRS into which coordinates of the BoundCRS are expressed. + * + * @return the base CRS. + */ +const CRSNNPtr &BoundCRS::baseCRS() PROJ_CONST_DEFN { return d->baseCRS_; } + +// --------------------------------------------------------------------------- + +// The only legit caller is BoundCRS::baseCRSWithCanonicalBoundCRS() +void CRS::setCanonicalBoundCRS(const BoundCRSNNPtr &boundCRS) { + + d->canonicalBoundCRS_ = boundCRS; +} + +// --------------------------------------------------------------------------- + +/** \brief Return a shallow clone of the base CRS that points to a + * shallow clone of this BoundCRS. + * + * The base CRS is the CRS into which coordinates of the BoundCRS are expressed. + * + * The returned CRS will actually be a shallow clone of the actual base CRS, + * with the extra property that CRS::canonicalBoundCRS() will point to a + * shallow clone of this BoundCRS. Use this only if you want to work with + * the base CRS object rather than the BoundCRS, but wanting to be able to + * retrieve the BoundCRS later. + * + * @return the base CRS. + */ +CRSNNPtr BoundCRS::baseCRSWithCanonicalBoundCRS() const { + auto baseCRSClone = baseCRS()->_shallowClone(); + baseCRSClone->setCanonicalBoundCRS(shallowCloneAsBoundCRS()); + return baseCRSClone; +} + +// --------------------------------------------------------------------------- + +/** \brief Return the target / hub CRS. + * + * @return the hub CRS. + */ +const CRSNNPtr &BoundCRS::hubCRS() PROJ_CONST_DEFN { return d->hubCRS_; } + +// --------------------------------------------------------------------------- + +/** \brief Return the transformation to the hub RS. + * + * @return transformation. + */ +const operation::TransformationNNPtr & +BoundCRS::transformation() PROJ_CONST_DEFN { + return d->transformation_; +} + +// --------------------------------------------------------------------------- + +/** \brief Instanciate a BoundCRS from a base CRS, a hub CRS and a + * transformation. + * + * @param baseCRSIn base CRS. + * @param hubCRSIn hub CRS. + * @param transformationIn transformation from base CRS to hub CRS. + * @return new BoundCRS. + */ +BoundCRSNNPtr +BoundCRS::create(const CRSNNPtr &baseCRSIn, const CRSNNPtr &hubCRSIn, + const operation::TransformationNNPtr &transformationIn) { + auto crs = BoundCRS::nn_make_shared<BoundCRS>(baseCRSIn, hubCRSIn, + transformationIn); + crs->assignSelf(crs); + const auto &l_name = baseCRSIn->nameStr(); + if (!l_name.empty()) { + crs->setProperties(util::PropertyMap().set( + common::IdentifiedObject::NAME_KEY, l_name)); + } + return crs; +} + +// --------------------------------------------------------------------------- + +/** \brief Instanciate a BoundCRS from a base CRS and TOWGS84 parameters + * + * @param baseCRSIn base CRS. + * @param TOWGS84Parameters a vector of 3 or 7 double values representing WKT1 + * TOWGS84 parameter. + * @return new BoundCRS. + */ +BoundCRSNNPtr +BoundCRS::createFromTOWGS84(const CRSNNPtr &baseCRSIn, + const std::vector<double> &TOWGS84Parameters) { + return create( + baseCRSIn, GeographicCRS::EPSG_4326, + operation::Transformation::createTOWGS84(baseCRSIn, TOWGS84Parameters)); +} + +// --------------------------------------------------------------------------- + +/** \brief Instanciate a BoundCRS from a base CRS and nadgrids parameters + * + * @param baseCRSIn base CRS. + * @param filename Horizontal grid filename + * @return new BoundCRS. + */ +BoundCRSNNPtr BoundCRS::createFromNadgrids(const CRSNNPtr &baseCRSIn, + const std::string &filename) { + const CRSPtr sourceGeographicCRS = baseCRSIn->extractGeographicCRS(); + auto transformationSourceCRS = + sourceGeographicCRS ? sourceGeographicCRS : baseCRSIn.as_nullable(); + std::string transformationName = transformationSourceCRS->nameStr(); + transformationName += " to WGS84"; + + return create( + baseCRSIn, GeographicCRS::EPSG_4326, + operation::Transformation::createNTv2( + util::PropertyMap().set(common::IdentifiedObject::NAME_KEY, + transformationName), + baseCRSIn, GeographicCRS::EPSG_4326, filename, + std::vector<metadata::PositionalAccuracyNNPtr>())); +} + +// --------------------------------------------------------------------------- + +bool BoundCRS::isTOWGS84Compatible() const { + return dynamic_cast<GeodeticCRS *>(d->hubCRS().get()) != nullptr && + ci_equal(d->hubCRS()->nameStr(), "WGS 84"); +} + +// --------------------------------------------------------------------------- + +std::string BoundCRS::getHDatumPROJ4GRIDS() const { + if (ci_equal(d->hubCRS()->nameStr(), "WGS 84")) { + return d->transformation()->getNTv2Filename(); + } + return std::string(); +} + +// --------------------------------------------------------------------------- + +std::string BoundCRS::getVDatumPROJ4GRIDS() const { + if (dynamic_cast<VerticalCRS *>(d->baseCRS().get()) && + ci_equal(d->hubCRS()->nameStr(), "WGS 84")) { + return d->transformation()->getHeightToGeographic3DFilename(); + } + return std::string(); +} + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +void BoundCRS::_exportToWKT(io::WKTFormatter *formatter) const { + const bool isWKT2 = formatter->version() == io::WKTFormatter::Version::WKT2; + if (isWKT2) { + formatter->startNode(io::WKTConstants::BOUNDCRS, false); + formatter->startNode(io::WKTConstants::SOURCECRS, false); + d->baseCRS()->_exportToWKT(formatter); + formatter->endNode(); + formatter->startNode(io::WKTConstants::TARGETCRS, false); + d->hubCRS()->_exportToWKT(formatter); + formatter->endNode(); + formatter->setAbridgedTransformation(true); + d->transformation()->_exportToWKT(formatter); + formatter->setAbridgedTransformation(false); + formatter->endNode(); + } else { + + auto vdatumProj4GridName = getVDatumPROJ4GRIDS(); + if (!vdatumProj4GridName.empty()) { + formatter->setVDatumExtension(vdatumProj4GridName); + d->baseCRS()->_exportToWKT(formatter); + formatter->setVDatumExtension(std::string()); + return; + } + + auto hdatumProj4GridName = getHDatumPROJ4GRIDS(); + if (!hdatumProj4GridName.empty()) { + formatter->setHDatumExtension(hdatumProj4GridName); + d->baseCRS()->_exportToWKT(formatter); + formatter->setHDatumExtension(std::string()); + return; + } + + if (!isTOWGS84Compatible()) { + io::FormattingException::Throw( + "Cannot export BoundCRS with non-WGS 84 hub CRS in WKT1"); + } + auto params = d->transformation()->getTOWGS84Parameters(); + if (!formatter->useESRIDialect()) { + formatter->setTOWGS84Parameters(params); + } + d->baseCRS()->_exportToWKT(formatter); + formatter->setTOWGS84Parameters(std::vector<double>()); + } +} +//! @endcond + +// --------------------------------------------------------------------------- + +void BoundCRS::_exportToPROJString( + io::PROJStringFormatter *formatter) const // throw(io::FormattingException) +{ + if (formatter->convention() == + io::PROJStringFormatter::Convention::PROJ_5) { + io::FormattingException::Throw( + "BoundCRS cannot be exported as a PROJ.5 string, but its baseCRS " + "might"); + } + + auto crs_exportable = + dynamic_cast<const io::IPROJStringExportable *>(d->baseCRS_.get()); + if (!crs_exportable) { + io::FormattingException::Throw( + "baseCRS of BoundCRS cannot be exported as a PROJ string"); + } + + auto vdatumProj4GridName = getVDatumPROJ4GRIDS(); + if (!vdatumProj4GridName.empty()) { + formatter->setVDatumExtension(vdatumProj4GridName); + crs_exportable->_exportToPROJString(formatter); + formatter->setVDatumExtension(std::string()); + } else { + auto hdatumProj4GridName = getHDatumPROJ4GRIDS(); + if (!hdatumProj4GridName.empty()) { + formatter->setHDatumExtension(hdatumProj4GridName); + crs_exportable->_exportToPROJString(formatter); + formatter->setHDatumExtension(std::string()); + } else { + if (isTOWGS84Compatible()) { + auto params = transformation()->getTOWGS84Parameters(); + formatter->setTOWGS84Parameters(params); + } + crs_exportable->_exportToPROJString(formatter); + formatter->setTOWGS84Parameters(std::vector<double>()); + } + } +} + +// --------------------------------------------------------------------------- + +bool BoundCRS::_isEquivalentTo(const util::IComparable *other, + util::IComparable::Criterion criterion) const { + auto otherBoundCRS = dynamic_cast<const BoundCRS *>(other); + if (otherBoundCRS == nullptr || + (criterion == util::IComparable::Criterion::STRICT && + !ObjectUsage::_isEquivalentTo(other, criterion))) { + return false; + } + return d->baseCRS_->_isEquivalentTo(otherBoundCRS->d->baseCRS_.get(), + criterion) && + d->hubCRS_->_isEquivalentTo(otherBoundCRS->d->hubCRS_.get(), + criterion) && + d->transformation_->_isEquivalentTo( + otherBoundCRS->d->transformation_.get(), criterion); +} + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress + +std::list<std::pair<CRSNNPtr, int>> +BoundCRS::_identify(const io::AuthorityFactoryPtr &authorityFactory) const { + typedef std::pair<CRSNNPtr, int> Pair; + std::list<Pair> res; + if (authorityFactory && + d->hubCRS_->_isEquivalentTo(GeographicCRS::EPSG_4326.get(), + util::IComparable::Criterion::EQUIVALENT)) { + auto resTemp = d->baseCRS_->identify(authorityFactory); + for (const auto &pair : resTemp) { + const auto &candidateBaseCRS = pair.first; + auto projCRS = + dynamic_cast<const ProjectedCRS *>(candidateBaseCRS.get()); + auto geodCRS = projCRS ? projCRS->baseCRS().as_nullable() + : util::nn_dynamic_pointer_cast<GeodeticCRS>( + candidateBaseCRS); + if (geodCRS) { + auto context = operation::CoordinateOperationContext::create( + authorityFactory, nullptr, 0.0); + auto ops = + operation::CoordinateOperationFactory::create() + ->createOperations(NN_NO_CHECK(geodCRS), + GeographicCRS::EPSG_4326, context); + std::string refTransfPROJString; + bool refTransfPROJStringValid = false; + try { + refTransfPROJString = + d->transformation_->exportToPROJString( + io::PROJStringFormatter::create().get()); + refTransfPROJStringValid = true; + if (refTransfPROJString == "+proj=axisswap +order=2,1") { + refTransfPROJString.clear(); + } + } catch (const std::exception &) { + } + for (const auto &op : ops) { + std::string opTransfPROJString; + bool opTransfPROJStringValid = false; + try { + opTransfPROJString = op->exportToPROJString( + io::PROJStringFormatter::create().get()); + opTransfPROJStringValid = true; + } catch (const std::exception &) { + } + if ((refTransfPROJStringValid && opTransfPROJStringValid && + refTransfPROJString == opTransfPROJString) || + op->_isEquivalentTo( + d->transformation_.get(), + util::IComparable::Criterion::EQUIVALENT)) { + res.emplace_back( + create(candidateBaseCRS, d->hubCRS_, + NN_NO_CHECK(util::nn_dynamic_pointer_cast< + operation::Transformation>(op))), + pair.second); + break; + } + } + } + } + } + return res; +} + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +struct DerivedGeodeticCRS::Private {}; +//! @endcond + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +DerivedGeodeticCRS::~DerivedGeodeticCRS() = default; +//! @endcond + +// --------------------------------------------------------------------------- + +DerivedGeodeticCRS::DerivedGeodeticCRS( + const GeodeticCRSNNPtr &baseCRSIn, + const operation::ConversionNNPtr &derivingConversionIn, + const cs::CartesianCSNNPtr &csIn) + : SingleCRS(baseCRSIn->datum(), baseCRSIn->datumEnsemble(), csIn), + GeodeticCRS(baseCRSIn->datum(), baseCRSIn->datumEnsemble(), csIn), + DerivedCRS(baseCRSIn, derivingConversionIn, csIn), d(nullptr) {} + +// --------------------------------------------------------------------------- + +DerivedGeodeticCRS::DerivedGeodeticCRS( + const GeodeticCRSNNPtr &baseCRSIn, + const operation::ConversionNNPtr &derivingConversionIn, + const cs::SphericalCSNNPtr &csIn) + : SingleCRS(baseCRSIn->datum(), baseCRSIn->datumEnsemble(), csIn), + GeodeticCRS(baseCRSIn->datum(), baseCRSIn->datumEnsemble(), csIn), + DerivedCRS(baseCRSIn, derivingConversionIn, csIn), d(nullptr) {} + +// --------------------------------------------------------------------------- + +DerivedGeodeticCRS::DerivedGeodeticCRS(const DerivedGeodeticCRS &other) + : SingleCRS(other), GeodeticCRS(other), DerivedCRS(other), d(nullptr) {} + +// --------------------------------------------------------------------------- + +CRSNNPtr DerivedGeodeticCRS::_shallowClone() const { + auto crs(DerivedGeodeticCRS::nn_make_shared<DerivedGeodeticCRS>(*this)); + crs->assignSelf(crs); + crs->setDerivingConversionCRS(); + return crs; +} + +// --------------------------------------------------------------------------- + +/** \brief Return the base CRS (a GeodeticCRS) of a DerivedGeodeticCRS. + * + * @return the base CRS. + */ +const GeodeticCRSNNPtr DerivedGeodeticCRS::baseCRS() const { + return NN_NO_CHECK(util::nn_dynamic_pointer_cast<GeodeticCRS>( + DerivedCRS::getPrivate()->baseCRS_)); +} + +// --------------------------------------------------------------------------- + +/** \brief Instanciate a DerivedGeodeticCRS from a base CRS, a deriving + * conversion and a cs::CartesianCS. + * + * @param properties See \ref general_properties. + * At minimum the name should be defined. + * @param baseCRSIn base CRS. + * @param derivingConversionIn the deriving conversion from the base CRS to this + * CRS. + * @param csIn the coordinate system. + * @return new DerivedGeodeticCRS. + */ +DerivedGeodeticCRSNNPtr DerivedGeodeticCRS::create( + const util::PropertyMap &properties, const GeodeticCRSNNPtr &baseCRSIn, + const operation::ConversionNNPtr &derivingConversionIn, + const cs::CartesianCSNNPtr &csIn) { + auto crs(DerivedGeodeticCRS::nn_make_shared<DerivedGeodeticCRS>( + baseCRSIn, derivingConversionIn, csIn)); + crs->assignSelf(crs); + crs->setProperties(properties); + crs->setDerivingConversionCRS(); + return crs; +} + +// --------------------------------------------------------------------------- + +/** \brief Instanciate a DerivedGeodeticCRS from a base CRS, a deriving + * conversion and a cs::SphericalCS. + * + * @param properties See \ref general_properties. + * At minimum the name should be defined. + * @param baseCRSIn base CRS. + * @param derivingConversionIn the deriving conversion from the base CRS to this + * CRS. + * @param csIn the coordinate system. + * @return new DerivedGeodeticCRS. + */ +DerivedGeodeticCRSNNPtr DerivedGeodeticCRS::create( + const util::PropertyMap &properties, const GeodeticCRSNNPtr &baseCRSIn, + const operation::ConversionNNPtr &derivingConversionIn, + const cs::SphericalCSNNPtr &csIn) { + auto crs(DerivedGeodeticCRS::nn_make_shared<DerivedGeodeticCRS>( + baseCRSIn, derivingConversionIn, csIn)); + crs->assignSelf(crs); + crs->setProperties(properties); + crs->setDerivingConversionCRS(); + return crs; +} + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +void DerivedGeodeticCRS::_exportToWKT(io::WKTFormatter *formatter) const { + const bool isWKT2 = formatter->version() == io::WKTFormatter::Version::WKT2; + if (!isWKT2) { + io::FormattingException::Throw( + "DerivedGeodeticCRS can only be exported to WKT2"); + } + formatter->startNode(io::WKTConstants::GEODCRS, !identifiers().empty()); + formatter->addQuotedString(nameStr()); + + auto l_baseCRS = baseCRS(); + formatter->startNode((formatter->use2018Keywords() && + dynamic_cast<const GeographicCRS *>(l_baseCRS.get())) + ? io::WKTConstants::BASEGEOGCRS + : io::WKTConstants::BASEGEODCRS, + !baseCRS()->identifiers().empty()); + formatter->addQuotedString(l_baseCRS->nameStr()); + auto l_datum = l_baseCRS->datum(); + if (l_datum) { + l_datum->_exportToWKT(formatter); + } else { + auto l_datumEnsemble = datumEnsemble(); + assert(l_datumEnsemble); + l_datumEnsemble->_exportToWKT(formatter); + } + l_baseCRS->primeMeridian()->_exportToWKT(formatter); + formatter->endNode(); + + formatter->setUseDerivingConversion(true); + derivingConversionRef()->_exportToWKT(formatter); + formatter->setUseDerivingConversion(false); + + coordinateSystem()->_exportToWKT(formatter); + ObjectUsage::baseExportToWKT(formatter); + formatter->endNode(); +} +//! @endcond + +// --------------------------------------------------------------------------- + +void DerivedGeodeticCRS::_exportToPROJString( + io::PROJStringFormatter *formatter) const // throw(io::FormattingException) +{ + baseExportToPROJString(formatter); +} + +// --------------------------------------------------------------------------- + +bool DerivedGeodeticCRS::_isEquivalentTo( + const util::IComparable *other, + util::IComparable::Criterion criterion) const { + auto otherDerivedCRS = dynamic_cast<const DerivedGeodeticCRS *>(other); + return otherDerivedCRS != nullptr && + DerivedCRS::_isEquivalentTo(other, criterion); +} + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress + +std::list<std::pair<CRSNNPtr, int>> +DerivedGeodeticCRS::_identify(const io::AuthorityFactoryPtr &factory) const { + return CRS::_identify(factory); +} + +//! @endcond + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +struct DerivedGeographicCRS::Private {}; +//! @endcond + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +DerivedGeographicCRS::~DerivedGeographicCRS() = default; +//! @endcond + +// --------------------------------------------------------------------------- + +DerivedGeographicCRS::DerivedGeographicCRS( + const GeodeticCRSNNPtr &baseCRSIn, + const operation::ConversionNNPtr &derivingConversionIn, + const cs::EllipsoidalCSNNPtr &csIn) + : SingleCRS(baseCRSIn->datum(), baseCRSIn->datumEnsemble(), csIn), + GeographicCRS(baseCRSIn->datum(), baseCRSIn->datumEnsemble(), csIn), + DerivedCRS(baseCRSIn, derivingConversionIn, csIn), d(nullptr) {} + +// --------------------------------------------------------------------------- + +DerivedGeographicCRS::DerivedGeographicCRS(const DerivedGeographicCRS &other) + : SingleCRS(other), GeographicCRS(other), DerivedCRS(other), d(nullptr) {} + +// --------------------------------------------------------------------------- + +CRSNNPtr DerivedGeographicCRS::_shallowClone() const { + auto crs(DerivedGeographicCRS::nn_make_shared<DerivedGeographicCRS>(*this)); + crs->assignSelf(crs); + crs->setDerivingConversionCRS(); + return crs; +} + +// --------------------------------------------------------------------------- + +/** \brief Return the base CRS (a GeodeticCRS) of a DerivedGeographicCRS. + * + * @return the base CRS. + */ +const GeodeticCRSNNPtr DerivedGeographicCRS::baseCRS() const { + return NN_NO_CHECK(util::nn_dynamic_pointer_cast<GeodeticCRS>( + DerivedCRS::getPrivate()->baseCRS_)); +} + +// --------------------------------------------------------------------------- + +/** \brief Instanciate a DerivedGeographicCRS from a base CRS, a deriving + * conversion and a cs::EllipsoidalCS. + * + * @param properties See \ref general_properties. + * At minimum the name should be defined. + * @param baseCRSIn base CRS. + * @param derivingConversionIn the deriving conversion from the base CRS to this + * CRS. + * @param csIn the coordinate system. + * @return new DerivedGeographicCRS. + */ +DerivedGeographicCRSNNPtr DerivedGeographicCRS::create( + const util::PropertyMap &properties, const GeodeticCRSNNPtr &baseCRSIn, + const operation::ConversionNNPtr &derivingConversionIn, + const cs::EllipsoidalCSNNPtr &csIn) { + auto crs(DerivedGeographicCRS::nn_make_shared<DerivedGeographicCRS>( + baseCRSIn, derivingConversionIn, csIn)); + crs->assignSelf(crs); + crs->setProperties(properties); + crs->setDerivingConversionCRS(); + return crs; +} + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +void DerivedGeographicCRS::_exportToWKT(io::WKTFormatter *formatter) const { + const bool isWKT2 = formatter->version() == io::WKTFormatter::Version::WKT2; + if (!isWKT2) { + io::FormattingException::Throw( + "DerivedGeographicCRS can only be exported to WKT2"); + } + formatter->startNode(formatter->use2018Keywords() + ? io::WKTConstants::GEOGCRS + : io::WKTConstants::GEODCRS, + !identifiers().empty()); + formatter->addQuotedString(nameStr()); + + auto l_baseCRS = baseCRS(); + formatter->startNode((formatter->use2018Keywords() && + dynamic_cast<const GeographicCRS *>(l_baseCRS.get())) + ? io::WKTConstants::BASEGEOGCRS + : io::WKTConstants::BASEGEODCRS, + !l_baseCRS->identifiers().empty()); + formatter->addQuotedString(l_baseCRS->nameStr()); + l_baseCRS->exportDatumOrDatumEnsembleToWkt(formatter); + l_baseCRS->primeMeridian()->_exportToWKT(formatter); + formatter->endNode(); + + formatter->setUseDerivingConversion(true); + derivingConversionRef()->_exportToWKT(formatter); + formatter->setUseDerivingConversion(false); + + coordinateSystem()->_exportToWKT(formatter); + ObjectUsage::baseExportToWKT(formatter); + formatter->endNode(); +} +//! @endcond + +// --------------------------------------------------------------------------- + +void DerivedGeographicCRS::_exportToPROJString( + io::PROJStringFormatter *formatter) const // throw(io::FormattingException) +{ + baseExportToPROJString(formatter); +} + +// --------------------------------------------------------------------------- + +bool DerivedGeographicCRS::_isEquivalentTo( + const util::IComparable *other, + util::IComparable::Criterion criterion) const { + auto otherDerivedCRS = dynamic_cast<const DerivedGeographicCRS *>(other); + return otherDerivedCRS != nullptr && + DerivedCRS::_isEquivalentTo(other, criterion); +} + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress + +std::list<std::pair<CRSNNPtr, int>> +DerivedGeographicCRS::_identify(const io::AuthorityFactoryPtr &factory) const { + return CRS::_identify(factory); +} + +//! @endcond + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +struct DerivedProjectedCRS::Private {}; +//! @endcond + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +DerivedProjectedCRS::~DerivedProjectedCRS() = default; +//! @endcond + +// --------------------------------------------------------------------------- + +DerivedProjectedCRS::DerivedProjectedCRS( + const ProjectedCRSNNPtr &baseCRSIn, + const operation::ConversionNNPtr &derivingConversionIn, + const cs::CoordinateSystemNNPtr &csIn) + : SingleCRS(baseCRSIn->datum(), baseCRSIn->datumEnsemble(), csIn), + DerivedCRS(baseCRSIn, derivingConversionIn, csIn), d(nullptr) {} + +// --------------------------------------------------------------------------- + +DerivedProjectedCRS::DerivedProjectedCRS(const DerivedProjectedCRS &other) + : SingleCRS(other), DerivedCRS(other), d(nullptr) {} + +// --------------------------------------------------------------------------- + +CRSNNPtr DerivedProjectedCRS::_shallowClone() const { + auto crs(DerivedProjectedCRS::nn_make_shared<DerivedProjectedCRS>(*this)); + crs->assignSelf(crs); + crs->setDerivingConversionCRS(); + return crs; +} + +// --------------------------------------------------------------------------- + +/** \brief Return the base CRS (a ProjectedCRS) of a DerivedProjectedCRS. + * + * @return the base CRS. + */ +const ProjectedCRSNNPtr DerivedProjectedCRS::baseCRS() const { + return NN_NO_CHECK(util::nn_dynamic_pointer_cast<ProjectedCRS>( + DerivedCRS::getPrivate()->baseCRS_)); +} + +// --------------------------------------------------------------------------- + +/** \brief Instanciate a DerivedProjectedCRS from a base CRS, a deriving + * conversion and a cs::CS. + * + * @param properties See \ref general_properties. + * At minimum the name should be defined. + * @param baseCRSIn base CRS. + * @param derivingConversionIn the deriving conversion from the base CRS to this + * CRS. + * @param csIn the coordinate system. + * @return new DerivedProjectedCRS. + */ +DerivedProjectedCRSNNPtr DerivedProjectedCRS::create( + const util::PropertyMap &properties, const ProjectedCRSNNPtr &baseCRSIn, + const operation::ConversionNNPtr &derivingConversionIn, + const cs::CoordinateSystemNNPtr &csIn) { + auto crs(DerivedProjectedCRS::nn_make_shared<DerivedProjectedCRS>( + baseCRSIn, derivingConversionIn, csIn)); + crs->assignSelf(crs); + crs->setProperties(properties); + crs->setDerivingConversionCRS(); + return crs; +} + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +void DerivedProjectedCRS::_exportToWKT(io::WKTFormatter *formatter) const { + const bool isWKT2 = formatter->version() == io::WKTFormatter::Version::WKT2; + if (!isWKT2 || !formatter->use2018Keywords()) { + io::FormattingException::Throw( + "DerivedProjectedCRS can only be exported to WKT2:2018"); + } + formatter->startNode(io::WKTConstants::DERIVEDPROJCRS, + !identifiers().empty()); + formatter->addQuotedString(nameStr()); + + { + auto l_baseProjCRS = baseCRS(); + formatter->startNode(io::WKTConstants::BASEPROJCRS, + !l_baseProjCRS->identifiers().empty()); + formatter->addQuotedString(l_baseProjCRS->nameStr()); + + auto l_baseGeodCRS = l_baseProjCRS->baseCRS(); + auto &geodeticCRSAxisList = + l_baseGeodCRS->coordinateSystem()->axisList(); + + formatter->startNode( + dynamic_cast<const GeographicCRS *>(l_baseGeodCRS.get()) + ? io::WKTConstants::BASEGEOGCRS + : io::WKTConstants::BASEGEODCRS, + !l_baseGeodCRS->identifiers().empty()); + formatter->addQuotedString(l_baseGeodCRS->nameStr()); + l_baseGeodCRS->exportDatumOrDatumEnsembleToWkt(formatter); + // insert ellipsoidal cs unit when the units of the map + // projection angular parameters are not explicitly given within those + // parameters. See + // http://docs.opengeospatial.org/is/12-063r5/12-063r5.html#61 + if (formatter->primeMeridianOrParameterUnitOmittedIfSameAsAxis() && + !geodeticCRSAxisList.empty()) { + geodeticCRSAxisList[0]->unit()._exportToWKT(formatter); + } + l_baseGeodCRS->primeMeridian()->_exportToWKT(formatter); + formatter->endNode(); + + l_baseProjCRS->derivingConversionRef()->_exportToWKT(formatter); + formatter->endNode(); + } + + formatter->setUseDerivingConversion(true); + derivingConversionRef()->_exportToWKT(formatter); + formatter->setUseDerivingConversion(false); + + coordinateSystem()->_exportToWKT(formatter); + ObjectUsage::baseExportToWKT(formatter); + formatter->endNode(); +} +//! @endcond + +// --------------------------------------------------------------------------- + +void DerivedProjectedCRS::_exportToPROJString( + io::PROJStringFormatter *formatter) const // throw(io::FormattingException) +{ + baseExportToPROJString(formatter); +} + +// --------------------------------------------------------------------------- + +bool DerivedProjectedCRS::_isEquivalentTo( + const util::IComparable *other, + util::IComparable::Criterion criterion) const { + auto otherDerivedCRS = dynamic_cast<const DerivedProjectedCRS *>(other); + return otherDerivedCRS != nullptr && + DerivedCRS::_isEquivalentTo(other, criterion); +} + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +struct TemporalCRS::Private {}; +//! @endcond + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +TemporalCRS::~TemporalCRS() = default; +//! @endcond + +// --------------------------------------------------------------------------- + +TemporalCRS::TemporalCRS(const datum::TemporalDatumNNPtr &datumIn, + const cs::TemporalCSNNPtr &csIn) + : SingleCRS(datumIn.as_nullable(), nullptr, csIn), d(nullptr) {} + +// --------------------------------------------------------------------------- + +TemporalCRS::TemporalCRS(const TemporalCRS &other) + : SingleCRS(other), d(nullptr) {} + +// --------------------------------------------------------------------------- + +CRSNNPtr TemporalCRS::_shallowClone() const { + auto crs(TemporalCRS::nn_make_shared<TemporalCRS>(*this)); + crs->assignSelf(crs); + return crs; +} + +// --------------------------------------------------------------------------- + +/** \brief Return the datum::TemporalDatum associated with the CRS. + * + * @return a TemporalDatum + */ +const datum::TemporalDatumNNPtr TemporalCRS::datum() const { + return NN_NO_CHECK(std::static_pointer_cast<datum::TemporalDatum>( + SingleCRS::getPrivate()->datum)); +} + +// --------------------------------------------------------------------------- + +/** \brief Return the cs::TemporalCS associated with the CRS. + * + * @return a TemporalCS + */ +const cs::TemporalCSNNPtr TemporalCRS::coordinateSystem() const { + return util::nn_static_pointer_cast<cs::TemporalCS>( + SingleCRS::getPrivate()->coordinateSystem); +} + +// --------------------------------------------------------------------------- + +/** \brief Instanciate a TemporalCRS from a datum and a coordinate system. + * + * @param properties See \ref general_properties. + * At minimum the name should be defined. + * @param datumIn the datum. + * @param csIn the coordinate system. + * @return new TemporalCRS. + */ +TemporalCRSNNPtr TemporalCRS::create(const util::PropertyMap &properties, + const datum::TemporalDatumNNPtr &datumIn, + const cs::TemporalCSNNPtr &csIn) { + auto crs(TemporalCRS::nn_make_shared<TemporalCRS>(datumIn, csIn)); + crs->assignSelf(crs); + crs->setProperties(properties); + return crs; +} + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +void TemporalCRS::_exportToWKT(io::WKTFormatter *formatter) const { + const bool isWKT2 = formatter->version() == io::WKTFormatter::Version::WKT2; + if (!isWKT2) { + io::FormattingException::Throw( + "TemporalCRS can only be exported to WKT2"); + } + formatter->startNode(io::WKTConstants::TIMECRS, !identifiers().empty()); + formatter->addQuotedString(nameStr()); + datum()->_exportToWKT(formatter); + coordinateSystem()->_exportToWKT(formatter); + ObjectUsage::baseExportToWKT(formatter); + formatter->endNode(); +} +//! @endcond + +// --------------------------------------------------------------------------- + +bool TemporalCRS::_isEquivalentTo( + const util::IComparable *other, + util::IComparable::Criterion criterion) const { + auto otherTemporalCRS = dynamic_cast<const TemporalCRS *>(other); + return otherTemporalCRS != nullptr && + SingleCRS::baseIsEquivalentTo(other, criterion); +} + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +struct EngineeringCRS::Private {}; +//! @endcond + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +EngineeringCRS::~EngineeringCRS() = default; +//! @endcond + +// --------------------------------------------------------------------------- + +EngineeringCRS::EngineeringCRS(const datum::EngineeringDatumNNPtr &datumIn, + const cs::CoordinateSystemNNPtr &csIn) + : SingleCRS(datumIn.as_nullable(), nullptr, csIn), d(nullptr) {} + +// --------------------------------------------------------------------------- + +EngineeringCRS::EngineeringCRS(const EngineeringCRS &other) + : SingleCRS(other), d(nullptr) {} + +// --------------------------------------------------------------------------- + +CRSNNPtr EngineeringCRS::_shallowClone() const { + auto crs(EngineeringCRS::nn_make_shared<EngineeringCRS>(*this)); + crs->assignSelf(crs); + return crs; +} + +// --------------------------------------------------------------------------- + +/** \brief Return the datum::EngineeringDatum associated with the CRS. + * + * @return a EngineeringDatum + */ +const datum::EngineeringDatumNNPtr EngineeringCRS::datum() const { + return NN_NO_CHECK(std::static_pointer_cast<datum::EngineeringDatum>( + SingleCRS::getPrivate()->datum)); +} + +// --------------------------------------------------------------------------- + +/** \brief Instanciate a EngineeringCRS from a datum and a coordinate system. + * + * @param properties See \ref general_properties. + * At minimum the name should be defined. + * @param datumIn the datum. + * @param csIn the coordinate system. + * @return new EngineeringCRS. + */ +EngineeringCRSNNPtr +EngineeringCRS::create(const util::PropertyMap &properties, + const datum::EngineeringDatumNNPtr &datumIn, + const cs::CoordinateSystemNNPtr &csIn) { + auto crs(EngineeringCRS::nn_make_shared<EngineeringCRS>(datumIn, csIn)); + crs->assignSelf(crs); + crs->setProperties(properties); + return crs; +} + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +void EngineeringCRS::_exportToWKT(io::WKTFormatter *formatter) const { + const bool isWKT2 = formatter->version() == io::WKTFormatter::Version::WKT2; + formatter->startNode(isWKT2 ? io::WKTConstants::ENGCRS + : io::WKTConstants::LOCAL_CS, + !identifiers().empty()); + formatter->addQuotedString(nameStr()); + if (isWKT2 || !datum()->nameStr().empty()) { + datum()->_exportToWKT(formatter); + coordinateSystem()->_exportToWKT(formatter); + } + ObjectUsage::baseExportToWKT(formatter); + formatter->endNode(); +} +//! @endcond + +// --------------------------------------------------------------------------- + +bool EngineeringCRS::_isEquivalentTo( + const util::IComparable *other, + util::IComparable::Criterion criterion) const { + auto otherEngineeringCRS = dynamic_cast<const EngineeringCRS *>(other); + return otherEngineeringCRS != nullptr && + SingleCRS::baseIsEquivalentTo(other, criterion); +} + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +struct ParametricCRS::Private {}; +//! @endcond + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +ParametricCRS::~ParametricCRS() = default; +//! @endcond + +// --------------------------------------------------------------------------- + +ParametricCRS::ParametricCRS(const datum::ParametricDatumNNPtr &datumIn, + const cs::ParametricCSNNPtr &csIn) + : SingleCRS(datumIn.as_nullable(), nullptr, csIn), d(nullptr) {} + +// --------------------------------------------------------------------------- + +ParametricCRS::ParametricCRS(const ParametricCRS &other) + : SingleCRS(other), d(nullptr) {} + +// --------------------------------------------------------------------------- + +CRSNNPtr ParametricCRS::_shallowClone() const { + auto crs(ParametricCRS::nn_make_shared<ParametricCRS>(*this)); + crs->assignSelf(crs); + return crs; +} + +// --------------------------------------------------------------------------- + +/** \brief Return the datum::ParametricDatum associated with the CRS. + * + * @return a ParametricDatum + */ +const datum::ParametricDatumNNPtr ParametricCRS::datum() const { + return NN_NO_CHECK(std::static_pointer_cast<datum::ParametricDatum>( + SingleCRS::getPrivate()->datum)); +} + +// --------------------------------------------------------------------------- + +/** \brief Return the cs::TemporalCS associated with the CRS. + * + * @return a TemporalCS + */ +const cs::ParametricCSNNPtr ParametricCRS::coordinateSystem() const { + return util::nn_static_pointer_cast<cs::ParametricCS>( + SingleCRS::getPrivate()->coordinateSystem); +} + +// --------------------------------------------------------------------------- + +/** \brief Instanciate a ParametricCRS from a datum and a coordinate system. + * + * @param properties See \ref general_properties. + * At minimum the name should be defined. + * @param datumIn the datum. + * @param csIn the coordinate system. + * @return new ParametricCRS. + */ +ParametricCRSNNPtr +ParametricCRS::create(const util::PropertyMap &properties, + const datum::ParametricDatumNNPtr &datumIn, + const cs::ParametricCSNNPtr &csIn) { + auto crs(ParametricCRS::nn_make_shared<ParametricCRS>(datumIn, csIn)); + crs->assignSelf(crs); + crs->setProperties(properties); + return crs; +} + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +void ParametricCRS::_exportToWKT(io::WKTFormatter *formatter) const { + const bool isWKT2 = formatter->version() == io::WKTFormatter::Version::WKT2; + if (!isWKT2) { + io::FormattingException::Throw( + "ParametricCRS can only be exported to WKT2"); + } + formatter->startNode(io::WKTConstants::PARAMETRICCRS, + !identifiers().empty()); + formatter->addQuotedString(nameStr()); + datum()->_exportToWKT(formatter); + coordinateSystem()->_exportToWKT(formatter); + ObjectUsage::baseExportToWKT(formatter); + formatter->endNode(); +} +//! @endcond + +// --------------------------------------------------------------------------- + +bool ParametricCRS::_isEquivalentTo( + const util::IComparable *other, + util::IComparable::Criterion criterion) const { + auto otherParametricCRS = dynamic_cast<const ParametricCRS *>(other); + return otherParametricCRS != nullptr && + SingleCRS::baseIsEquivalentTo(other, criterion); +} + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +struct DerivedVerticalCRS::Private {}; +//! @endcond + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +DerivedVerticalCRS::~DerivedVerticalCRS() = default; +//! @endcond + +// --------------------------------------------------------------------------- + +DerivedVerticalCRS::DerivedVerticalCRS( + const VerticalCRSNNPtr &baseCRSIn, + const operation::ConversionNNPtr &derivingConversionIn, + const cs::VerticalCSNNPtr &csIn) + : SingleCRS(baseCRSIn->datum(), baseCRSIn->datumEnsemble(), csIn), + VerticalCRS(baseCRSIn->datum(), baseCRSIn->datumEnsemble(), csIn), + DerivedCRS(baseCRSIn, derivingConversionIn, csIn), d(nullptr) {} + +// --------------------------------------------------------------------------- + +DerivedVerticalCRS::DerivedVerticalCRS(const DerivedVerticalCRS &other) + : SingleCRS(other), VerticalCRS(other), DerivedCRS(other), d(nullptr) {} + +// --------------------------------------------------------------------------- + +CRSNNPtr DerivedVerticalCRS::_shallowClone() const { + auto crs(DerivedVerticalCRS::nn_make_shared<DerivedVerticalCRS>(*this)); + crs->assignSelf(crs); + crs->setDerivingConversionCRS(); + return crs; +} + +// --------------------------------------------------------------------------- + +/** \brief Return the base CRS (a VerticalCRS) of a DerivedVerticalCRS. + * + * @return the base CRS. + */ +const VerticalCRSNNPtr DerivedVerticalCRS::baseCRS() const { + return NN_NO_CHECK(util::nn_dynamic_pointer_cast<VerticalCRS>( + DerivedCRS::getPrivate()->baseCRS_)); +} + +// --------------------------------------------------------------------------- + +/** \brief Instanciate a DerivedVerticalCRS from a base CRS, a deriving + * conversion and a cs::VerticalCS. + * + * @param properties See \ref general_properties. + * At minimum the name should be defined. + * @param baseCRSIn base CRS. + * @param derivingConversionIn the deriving conversion from the base CRS to this + * CRS. + * @param csIn the coordinate system. + * @return new DerivedVerticalCRS. + */ +DerivedVerticalCRSNNPtr DerivedVerticalCRS::create( + const util::PropertyMap &properties, const VerticalCRSNNPtr &baseCRSIn, + const operation::ConversionNNPtr &derivingConversionIn, + const cs::VerticalCSNNPtr &csIn) { + auto crs(DerivedVerticalCRS::nn_make_shared<DerivedVerticalCRS>( + baseCRSIn, derivingConversionIn, csIn)); + crs->assignSelf(crs); + crs->setProperties(properties); + crs->setDerivingConversionCRS(); + return crs; +} + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +void DerivedVerticalCRS::_exportToWKT(io::WKTFormatter *formatter) const { + const bool isWKT2 = formatter->version() == io::WKTFormatter::Version::WKT2; + if (!isWKT2) { + io::FormattingException::Throw( + "DerivedVerticalCRS can only be exported to WKT2"); + } + baseExportToWKT(formatter, io::WKTConstants::VERTCRS, + io::WKTConstants::BASEVERTCRS); +} +//! @endcond + +// --------------------------------------------------------------------------- + +void DerivedVerticalCRS::_exportToPROJString( + io::PROJStringFormatter *formatter) const // throw(io::FormattingException) +{ + baseExportToPROJString(formatter); +} + +// --------------------------------------------------------------------------- + +bool DerivedVerticalCRS::_isEquivalentTo( + const util::IComparable *other, + util::IComparable::Criterion criterion) const { + auto otherDerivedCRS = dynamic_cast<const DerivedVerticalCRS *>(other); + return otherDerivedCRS != nullptr && + DerivedCRS::_isEquivalentTo(other, criterion); +} + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress + +std::list<std::pair<CRSNNPtr, int>> +DerivedVerticalCRS::_identify(const io::AuthorityFactoryPtr &factory) const { + return CRS::_identify(factory); +} + +//! @endcond + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +template <class DerivedCRSTraits> +struct DerivedCRSTemplate<DerivedCRSTraits>::Private {}; +//! @endcond + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +template <class DerivedCRSTraits> +DerivedCRSTemplate<DerivedCRSTraits>::~DerivedCRSTemplate() = default; +//! @endcond + +// --------------------------------------------------------------------------- + +template <class DerivedCRSTraits> +DerivedCRSTemplate<DerivedCRSTraits>::DerivedCRSTemplate( + const BaseNNPtr &baseCRSIn, + const operation::ConversionNNPtr &derivingConversionIn, const CSNNPtr &csIn) + : SingleCRS(baseCRSIn->datum().as_nullable(), nullptr, csIn), + BaseType(baseCRSIn->datum(), csIn), + DerivedCRS(baseCRSIn, derivingConversionIn, csIn), d(nullptr) {} + +// --------------------------------------------------------------------------- + +template <class DerivedCRSTraits> +DerivedCRSTemplate<DerivedCRSTraits>::DerivedCRSTemplate( + const DerivedCRSTemplate &other) + : SingleCRS(other), BaseType(other), DerivedCRS(other), d(nullptr) {} + +// --------------------------------------------------------------------------- + +template <class DerivedCRSTraits> +const typename DerivedCRSTemplate<DerivedCRSTraits>::BaseNNPtr +DerivedCRSTemplate<DerivedCRSTraits>::baseCRS() const { + auto l_baseCRS = DerivedCRS::getPrivate()->baseCRS_; + return NN_NO_CHECK(util::nn_dynamic_pointer_cast<BaseType>(l_baseCRS)); +} + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress + +template <class DerivedCRSTraits> +CRSNNPtr DerivedCRSTemplate<DerivedCRSTraits>::_shallowClone() const { + auto crs(DerivedCRSTemplate::nn_make_shared<DerivedCRSTemplate>(*this)); + crs->assignSelf(crs); + crs->setDerivingConversionCRS(); + return crs; +} + +// --------------------------------------------------------------------------- + +template <class DerivedCRSTraits> +typename DerivedCRSTemplate<DerivedCRSTraits>::NNPtr +DerivedCRSTemplate<DerivedCRSTraits>::create( + const util::PropertyMap &properties, const BaseNNPtr &baseCRSIn, + const operation::ConversionNNPtr &derivingConversionIn, + const CSNNPtr &csIn) { + auto crs(DerivedCRSTemplate::nn_make_shared<DerivedCRSTemplate>( + baseCRSIn, derivingConversionIn, csIn)); + crs->assignSelf(crs); + crs->setProperties(properties); + crs->setDerivingConversionCRS(); + return crs; +} + +// --------------------------------------------------------------------------- + +static void DerivedCRSTemplateCheckExportToWKT(io::WKTFormatter *&formatter, + const std::string &crsName, + bool wkt2_2018_only) { + const bool isWKT2 = formatter->version() == io::WKTFormatter::Version::WKT2; + if (!isWKT2 || (wkt2_2018_only && !formatter->use2018Keywords())) { + io::FormattingException::Throw(crsName + + " can only be exported to WKT2" + + (wkt2_2018_only ? ":2018" : "")); + } +} + +// --------------------------------------------------------------------------- + +template <class DerivedCRSTraits> +void DerivedCRSTemplate<DerivedCRSTraits>::_exportToWKT( + io::WKTFormatter *formatter) const { + DerivedCRSTemplateCheckExportToWKT(formatter, DerivedCRSTraits::CRSName(), + DerivedCRSTraits::wkt2_2018_only); + baseExportToWKT(formatter, DerivedCRSTraits::WKTKeyword(), + DerivedCRSTraits::WKTBaseKeyword()); +} + +// --------------------------------------------------------------------------- + +template <class DerivedCRSTraits> +bool DerivedCRSTemplate<DerivedCRSTraits>::_isEquivalentTo( + const util::IComparable *other, + util::IComparable::Criterion criterion) const { + auto otherDerivedCRS = dynamic_cast<const DerivedCRSTemplate *>(other); + return otherDerivedCRS != nullptr && + DerivedCRS::_isEquivalentTo(other, criterion); +} + +//! @endcond + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +static const std::string STRING_DerivedEngineeringCRS("DerivedEngineeringCRS"); +const std::string &DerivedEngineeringCRSTraits::CRSName() { + return STRING_DerivedEngineeringCRS; +} +const std::string &DerivedEngineeringCRSTraits::WKTKeyword() { + return io::WKTConstants::ENGCRS; +} +const std::string &DerivedEngineeringCRSTraits::WKTBaseKeyword() { + return io::WKTConstants::BASEENGCRS; +} + +template class DerivedCRSTemplate<DerivedEngineeringCRSTraits>; +//! @endcond + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +static const std::string STRING_DerivedParametricCRS("DerivedParametricCRS"); +const std::string &DerivedParametricCRSTraits::CRSName() { + return STRING_DerivedParametricCRS; +} +const std::string &DerivedParametricCRSTraits::WKTKeyword() { + return io::WKTConstants::PARAMETRICCRS; +} +const std::string &DerivedParametricCRSTraits::WKTBaseKeyword() { + return io::WKTConstants::BASEPARAMCRS; +} + +template class DerivedCRSTemplate<DerivedParametricCRSTraits>; +//! @endcond + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress +static const std::string STRING_DerivedTemporalCRS("DerivedTemporalCRS"); +const std::string &DerivedTemporalCRSTraits::CRSName() { + return STRING_DerivedTemporalCRS; +} +const std::string &DerivedTemporalCRSTraits::WKTKeyword() { + return io::WKTConstants::TIMECRS; +} +const std::string &DerivedTemporalCRSTraits::WKTBaseKeyword() { + return io::WKTConstants::BASETIMECRS; +} + +template class DerivedCRSTemplate<DerivedTemporalCRSTraits>; +//! @endcond + +// --------------------------------------------------------------------------- + +} // namespace crs +NS_PROJ_END |
