Skip to content

Commit

Permalink
Merge pull request #2441 from rouault/fix_obtran_towgs84
Browse files Browse the repository at this point in the history
createOperation(): make it work properly when one of the CRS is a BoundCRS of a DerivedGeographicCRS (+proj=ob_tran +o_proj=lonlat +towgs84=....)
  • Loading branch information
rouault committed Nov 21, 2020
2 parents 3b3ec2b + c2edd46 commit 3f40583
Show file tree
Hide file tree
Showing 7 changed files with 174 additions and 12 deletions.
5 changes: 5 additions & 0 deletions include/proj/crs.hpp
Expand Up @@ -340,6 +340,11 @@ class PROJ_GCC_DLL GeodeticCRS : virtual public SingleCRS,
PROJ_INTERNAL std::list<std::pair<CRSNNPtr, int>>
_identify(const io::AuthorityFactoryPtr &authorityFactory) const override;

PROJ_INTERNAL bool
_isEquivalentToNoTypeCheck(const util::IComparable *other,
util::IComparable::Criterion criterion,
const io::DatabaseContextPtr &dbContext) const;

INLINED_MAKE_SHARED

private:
Expand Down
8 changes: 8 additions & 0 deletions include/proj/util.hpp
Expand Up @@ -213,6 +213,14 @@ template <typename T> using nn_shared_ptr = nn<std::shared_ptr<T>>;
// To avoid formatting differences between clang-format 3.8 and 7
#define PROJ_NOEXCEPT noexcept

//! @cond Doxygen_Suppress
// isOfExactType<MyType>(*p) checks that the type of *p is exactly MyType
template <typename TemplateT, typename ObjectT>
inline bool isOfExactType(const ObjectT &o) {
return typeid(TemplateT).hash_code() == typeid(o).hash_code();
}
//! @endcond

/** \brief Loose transposition of [std::optional]
* (https://en.cppreference.com/w/cpp/utility/optional) available from C++17. */
template <class T> class optional {
Expand Down
53 changes: 52 additions & 1 deletion src/iso19111/coordinateoperation.cpp
Expand Up @@ -64,6 +64,30 @@
// #define DEBUG_CONCATENATED_OPERATION
#if defined(DEBUG_SORT) || defined(DEBUG_CONCATENATED_OPERATION)
#include <iostream>

void dumpWKT(const NS_PROJ::crs::CRS *crs);
void dumpWKT(const NS_PROJ::crs::CRS *crs) {
auto f(NS_PROJ::io::WKTFormatter::create(
NS_PROJ::io::WKTFormatter::Convention::WKT2_2019));
std::cerr << crs->exportToWKT(f.get()) << std::endl;
}

void dumpWKT(const NS_PROJ::crs::CRSPtr &crs);
void dumpWKT(const NS_PROJ::crs::CRSPtr &crs) { dumpWKT(crs.get()); }

void dumpWKT(const NS_PROJ::crs::CRSNNPtr &crs);
void dumpWKT(const NS_PROJ::crs::CRSNNPtr &crs) {
dumpWKT(crs.as_nullable().get());
}

void dumpWKT(const NS_PROJ::crs::GeographicCRSPtr &crs);
void dumpWKT(const NS_PROJ::crs::GeographicCRSPtr &crs) { dumpWKT(crs.get()); }

void dumpWKT(const NS_PROJ::crs::GeographicCRSNNPtr &crs);
void dumpWKT(const NS_PROJ::crs::GeographicCRSNNPtr &crs) {
dumpWKT(crs.as_nullable().get());
}

#endif

using namespace NS_PROJ::internal;
Expand Down Expand Up @@ -9184,6 +9208,14 @@ static void setupPROJGeodeticSourceCRS(io::PROJStringFormatter *formatter,
formatter->startInversion();
sourceCRSGeog->_exportToPROJString(formatter);
formatter->stopInversion();
if (util::isOfExactType<crs::DerivedGeographicCRS>(
*(sourceCRSGeog.get()))) {
// The export of a DerivedGeographicCRS in non-CRS mode adds
// unit conversion and axis swapping. We must compensate for that
formatter->startInversion();
sourceCRSGeog->addAngularUnitConvertAndAxisSwap(formatter);
formatter->stopInversion();
}

if (addPushV3) {
formatter->addStep("push");
Expand Down Expand Up @@ -9217,7 +9249,12 @@ static void setupPROJGeodeticTargetCRS(io::PROJStringFormatter *formatter,
formatter->addStep("pop");
formatter->addParam("v_3");
}

if (util::isOfExactType<crs::DerivedGeographicCRS>(
*(targetCRSGeog.get()))) {
// The export of a DerivedGeographicCRS in non-CRS mode adds
// unit conversion and axis swapping. We must compensate for that
targetCRSGeog->addAngularUnitConvertAndAxisSwap(formatter);
}
targetCRSGeog->_exportToPROJString(formatter);
} else {
auto targetCRSGeod = dynamic_cast<const crs::GeodeticCRS *>(crs.get());
Expand Down Expand Up @@ -14290,6 +14327,20 @@ void CoordinateOperationFactory::Private::createOperationsBoundToGeog(
const auto &hubSrc = boundSrc->hubCRS();
auto hubSrcGeog = dynamic_cast<const crs::GeographicCRS *>(hubSrc.get());
auto geogCRSOfBaseOfBoundSrc = boundSrc->baseCRS()->extractGeographicCRS();
{
// If geogCRSOfBaseOfBoundSrc is a DerivedGeographicCRS, use its base
// instead (if it is a GeographicCRS)
auto derivedGeogCRS =
std::dynamic_pointer_cast<crs::DerivedGeographicCRS>(
geogCRSOfBaseOfBoundSrc);
if (derivedGeogCRS) {
auto baseCRS = std::dynamic_pointer_cast<crs::GeographicCRS>(
derivedGeogCRS->baseCRS().as_nullable());
if (baseCRS) {
geogCRSOfBaseOfBoundSrc = baseCRS;
}
}
}

const auto &authFactory = context.context->getAuthorityFactory();
const auto dbContext =
Expand Down
27 changes: 18 additions & 9 deletions src/iso19111/crs.cpp
Expand Up @@ -1924,13 +1924,21 @@ getStandardCriterion(util::IComparable::Criterion criterion) {

//! @cond Doxygen_Suppress
bool GeodeticCRS::_isEquivalentTo(
const util::IComparable *other, util::IComparable::Criterion criterion,
const io::DatabaseContextPtr &dbContext) const {
if (other == nullptr || !util::isOfExactType<GeodeticCRS>(*other)) {
return false;
}
return _isEquivalentToNoTypeCheck(other, criterion, dbContext);
}

bool GeodeticCRS::_isEquivalentToNoTypeCheck(
const util::IComparable *other, util::IComparable::Criterion criterion,
const io::DatabaseContextPtr &dbContext) const {
const auto standardCriterion = getStandardCriterion(criterion);
auto otherGeodCRS = dynamic_cast<const GeodeticCRS *>(other);

// TODO test velocityModel
return otherGeodCRS != nullptr &&
SingleCRS::baseIsEquivalentTo(other, standardCriterion, dbContext);
return SingleCRS::baseIsEquivalentTo(other, standardCriterion, dbContext);
}
//! @endcond

Expand Down Expand Up @@ -2486,12 +2494,13 @@ bool GeographicCRS::is2DPartOf3D(util::nn<const GeographicCRS *> other,
bool GeographicCRS::_isEquivalentTo(
const util::IComparable *other, util::IComparable::Criterion criterion,
const io::DatabaseContextPtr &dbContext) const {
auto otherGeogCRS = dynamic_cast<const GeographicCRS *>(other);
if (otherGeogCRS == nullptr) {
if (other == nullptr || !util::isOfExactType<GeographicCRS>(*other)) {
return false;
}

const auto standardCriterion = getStandardCriterion(criterion);
if (GeodeticCRS::_isEquivalentTo(other, standardCriterion, dbContext)) {
if (GeodeticCRS::_isEquivalentToNoTypeCheck(other, standardCriterion,
dbContext)) {
return true;
}
if (criterion !=
Expand All @@ -2510,7 +2519,8 @@ bool GeographicCRS::_isEquivalentTo(
cs::EllipsoidalCS::AxisOrder::LONG_EAST_LAT_NORTH
? cs::EllipsoidalCS::createLatitudeLongitude(unit)
: cs::EllipsoidalCS::createLongitudeLatitude(unit))
->GeodeticCRS::_isEquivalentTo(other, standardCriterion, dbContext);
->GeodeticCRS::_isEquivalentToNoTypeCheck(other, standardCriterion,
dbContext);
}
return false;
}
Expand Down Expand Up @@ -3889,8 +3899,7 @@ ProjectedCRS::create(const util::PropertyMap &properties,
bool ProjectedCRS::_isEquivalentTo(
const util::IComparable *other, util::IComparable::Criterion criterion,
const io::DatabaseContextPtr &dbContext) const {
auto otherProjCRS = dynamic_cast<const ProjectedCRS *>(other);
return otherProjCRS != nullptr &&
return other != nullptr && util::isOfExactType<ProjectedCRS>(*other) &&
DerivedCRS::_isEquivalentTo(other, criterion, dbContext);
}

Expand Down
4 changes: 2 additions & 2 deletions src/iso19111/io.cpp
Expand Up @@ -9478,8 +9478,8 @@ CRSNNPtr PROJStringParser::Private::buildProjectedCRS(
std::string methodName = "PROJ " + step.name;
for (const auto &param : step.paramValues) {
if (is_in_stringlist(param.key,
"wktext,no_defs,datum,ellps,a,b,R,towgs84,"
"nadgrids,geoidgrids,"
"wktext,no_defs,datum,ellps,a,b,R,f,rf,"
"towgs84,nadgrids,geoidgrids,"
"units,to_meter,vunits,vto_meter,type")) {
continue;
}
Expand Down
24 changes: 24 additions & 0 deletions test/unit/test_crs.cpp
Expand Up @@ -4750,6 +4750,18 @@ static DerivedGeographicCRSNNPtr createDerivedGeographicCRS() {

// ---------------------------------------------------------------------------

TEST(crs, derivedGeographicCRS_basic) {

auto derivedCRS = createDerivedGeographicCRS();
EXPECT_TRUE(derivedCRS->isEquivalentTo(derivedCRS.get()));
EXPECT_FALSE(derivedCRS->isEquivalentTo(
derivedCRS->baseCRS().get(), IComparable::Criterion::EQUIVALENT));
EXPECT_FALSE(derivedCRS->baseCRS()->isEquivalentTo(
derivedCRS.get(), IComparable::Criterion::EQUIVALENT));
}

// ---------------------------------------------------------------------------

TEST(crs, derivedGeographicCRS_WKT2) {

auto expected = "GEODCRS[\"WMO Atlantic Pole\",\n"
Expand Down Expand Up @@ -4950,6 +4962,18 @@ static DerivedGeodeticCRSNNPtr createDerivedGeodeticCRS() {

// ---------------------------------------------------------------------------

TEST(crs, derivedGeodeticCRS_basic) {

auto derivedCRS = createDerivedGeodeticCRS();
EXPECT_TRUE(derivedCRS->isEquivalentTo(derivedCRS.get()));
EXPECT_FALSE(derivedCRS->isEquivalentTo(
derivedCRS->baseCRS().get(), IComparable::Criterion::EQUIVALENT));
EXPECT_FALSE(derivedCRS->baseCRS()->isEquivalentTo(
derivedCRS.get(), IComparable::Criterion::EQUIVALENT));
}

// ---------------------------------------------------------------------------

TEST(crs, derivedGeodeticCRS_WKT2) {

auto expected = "GEODCRS[\"Derived geodetic CRS\",\n"
Expand Down
65 changes: 65 additions & 0 deletions test/unit/test_operation.cpp
Expand Up @@ -10033,6 +10033,71 @@ TEST(operation, createOperation_ossfuzz_18587) {

// ---------------------------------------------------------------------------

TEST(operation, derivedGeographicCRS_with_to_wgs84_to_geographicCRS) {
auto objSrc = PROJStringParser().createFromPROJString(
"+proj=ob_tran +o_proj=latlon +lat_0=0 +lon_0=180 +o_lat_p=18.0 "
"+o_lon_p=-200.0 +ellps=WGS84 +towgs84=1,2,3 +type=crs");
auto src = nn_dynamic_pointer_cast<CRS>(objSrc);
ASSERT_TRUE(src != nullptr);
auto objDst = PROJStringParser().createFromPROJString(
"+proj=longlat +datum=WGS84 +type=crs");
auto dst = nn_dynamic_pointer_cast<CRS>(objDst);
ASSERT_TRUE(dst != nullptr);

{
auto op = CoordinateOperationFactory::create()->createOperation(
NN_CHECK_ASSERT(src), NN_CHECK_ASSERT(dst));
ASSERT_TRUE(op != nullptr);
std::string pipeline(
"+proj=pipeline "
"+step +proj=unitconvert +xy_in=deg +xy_out=rad "
"+step +inv +proj=ob_tran +o_proj=latlon +lat_0=0 +lon_0=180 "
"+o_lat_p=18 +o_lon_p=-200 +ellps=WGS84 "
"+step +proj=push +v_3 "
"+step +proj=cart +ellps=WGS84 "
"+step +proj=helmert +x=1 +y=2 +z=3 "
"+step +inv +proj=cart +ellps=WGS84 "
"+step +proj=pop +v_3 "
"+step +proj=unitconvert +xy_in=rad +xy_out=deg");
EXPECT_EQ(op->exportToPROJString(PROJStringFormatter::create().get()),
pipeline);

auto op2 = CoordinateOperationFactory::create()->createOperation(
NN_CHECK_ASSERT(src),
nn_static_pointer_cast<CRS>(GeographicCRS::EPSG_4326));
ASSERT_TRUE(op2 != nullptr);
EXPECT_EQ(op2->exportToPROJString(PROJStringFormatter::create().get()),
pipeline + " +step +proj=axisswap +order=2,1");
}

{
auto op = CoordinateOperationFactory::create()->createOperation(
NN_CHECK_ASSERT(dst), NN_CHECK_ASSERT(src));
ASSERT_TRUE(op != nullptr);
std::string pipeline(
"+step +proj=unitconvert +xy_in=deg +xy_out=rad "
"+step +proj=push +v_3 "
"+step +proj=cart +ellps=WGS84 "
"+step +proj=helmert +x=-1 +y=-2 +z=-3 "
"+step +inv +proj=cart +ellps=WGS84 "
"+step +proj=pop +v_3 "
"+step +proj=ob_tran +o_proj=latlon +lat_0=0 +lon_0=180 "
"+o_lat_p=18 +o_lon_p=-200 +ellps=WGS84 "
"+step +proj=unitconvert +xy_in=rad +xy_out=deg");
EXPECT_EQ(op->exportToPROJString(PROJStringFormatter::create().get()),
"+proj=pipeline " + pipeline);

auto op2 = CoordinateOperationFactory::create()->createOperation(
nn_static_pointer_cast<CRS>(GeographicCRS::EPSG_4326),
NN_CHECK_ASSERT(src));
ASSERT_TRUE(op2 != nullptr);
EXPECT_EQ(op2->exportToPROJString(PROJStringFormatter::create().get()),
"+proj=pipeline +step +proj=axisswap +order=2,1 " + pipeline);
}
}

// ---------------------------------------------------------------------------

TEST(operation, mercator_variant_A_to_variant_B) {
auto projCRS = ProjectedCRS::create(
PropertyMap(), GeographicCRS::EPSG_4326,
Expand Down

0 comments on commit 3f40583

Please sign in to comment.