diff options
| author | Even Rouault <even.rouault@spatialys.com> | 2021-10-06 19:26:09 +0200 |
|---|---|---|
| committer | Even Rouault <even.rouault@spatialys.com> | 2021-10-06 19:26:09 +0200 |
| commit | f28d36cee9ec099ae5fea3873988204a7ebda520 (patch) | |
| tree | 9adde1d0f8936b3439bb2b42e5c075f9d8df6362 | |
| parent | 3a67ea87fac5b835df7966fa801881eaf7503e78 (diff) | |
| download | PROJ-f28d36cee9ec099ae5fea3873988204a7ebda520.tar.gz PROJ-f28d36cee9ec099ae5fea3873988204a7ebda520.zip | |
CRS::_isEquivalentTo(): be tolerant to different order of PROJ step options (fixes #2886)
| -rw-r--r-- | include/proj/io.hpp | 2 | ||||
| -rw-r--r-- | src/iso19111/crs.cpp | 35 | ||||
| -rw-r--r-- | src/iso19111/io.cpp | 25 | ||||
| -rw-r--r-- | test/unit/test_crs.cpp | 33 |
4 files changed, 91 insertions, 4 deletions
diff --git a/include/proj/io.hpp b/include/proj/io.hpp index 47703442..235688cb 100644 --- a/include/proj/io.hpp +++ b/include/proj/io.hpp @@ -460,6 +460,8 @@ class PROJ_GCC_DLL PROJStringFormatter { PROJ_INTERNAL void setLegacyCRSToCRSContext(bool legacyContext); PROJ_INTERNAL bool getLegacyCRSToCRSContext() const; + PROJ_INTERNAL PROJStringFormatter &setNormalizeOutput(); + PROJ_INTERNAL const DatabaseContextPtr &databaseContext() const; PROJ_INTERNAL Convention convention() const; diff --git a/src/iso19111/crs.cpp b/src/iso19111/crs.cpp index 731bf7f9..b7d57767 100644 --- a/src/iso19111/crs.cpp +++ b/src/iso19111/crs.cpp @@ -1394,6 +1394,7 @@ bool SingleCRS::baseIsEquivalentTo( return false; } + // Check datum if (criterion == util::IComparable::Criterion::STRICT) { const auto &thisDatum = d->datum; const auto &otherDatum = otherSingleCRS->d->datum; @@ -1428,10 +1429,36 @@ bool SingleCRS::baseIsEquivalentTo( } } - return d->coordinateSystem->_isEquivalentTo( - otherSingleCRS->d->coordinateSystem.get(), criterion, - dbContext) && - getExtensionProj4() == otherSingleCRS->getExtensionProj4(); + // Check coordinate system + if (!(d->coordinateSystem->_isEquivalentTo( + otherSingleCRS->d->coordinateSystem.get(), criterion, dbContext))) { + return false; + } + + // Now compare PROJ4 extensions + + const auto &thisProj4 = getExtensionProj4(); + const auto &otherProj4 = otherSingleCRS->getExtensionProj4(); + + if (thisProj4.empty() && otherProj4.empty()) { + return true; + } + + if (!(thisProj4.empty() ^ otherProj4.empty())) { + return true; + } + + // Asks for a "normalized" output during toString(), aimed at comparing two + // strings for equivalence. + auto formatter1 = io::PROJStringFormatter::create(); + formatter1->setNormalizeOutput(); + formatter1->ingestPROJString(thisProj4); + + auto formatter2 = io::PROJStringFormatter::create(); + formatter2->setNormalizeOutput(); + formatter2->ingestPROJString(otherProj4); + + return formatter1->toString() == formatter2->toString(); } // --------------------------------------------------------------------------- diff --git a/src/iso19111/io.cpp b/src/iso19111/io.cpp index 1f193559..24201ee1 100644 --- a/src/iso19111/io.cpp +++ b/src/iso19111/io.cpp @@ -7436,6 +7436,7 @@ struct PROJStringFormatter::Private { bool crsExport_ = false; bool legacyCRSToCRSContext_ = false; bool multiLine_ = false; + bool normalizeOutput_ = false; int indentWidth_ = 2; int indentLevel_ = 0; int maxLineLength_ = 80; @@ -7535,6 +7536,17 @@ const std::string &PROJStringFormatter::toString() const { d->result_.clear(); auto &steps = d->steps_; + + if (d->normalizeOutput_) { + // Sort +key=value options of each step in lexicographic order. + for (auto &step : steps) { + std::sort(step.paramValues.begin(), step.paramValues.end(), + [](const Step::KeyValue &a, const Step::KeyValue &b) { + return a.key < b.key; + }); + } + } + for (auto iter = steps.begin(); iter != steps.end();) { // Remove no-op helmert auto &step = *iter; @@ -8689,6 +8701,19 @@ bool PROJStringFormatter::getLegacyCRSToCRSContext() const { // --------------------------------------------------------------------------- +/** Asks for a "normalized" output during toString(), aimed at comparing two + * strings for equivalence. + * + * This consists for now in sorting the +key=value option in lexicographic + * order. + */ +PROJStringFormatter &PROJStringFormatter::setNormalizeOutput() { + d->normalizeOutput_ = true; + return *this; +} + +// --------------------------------------------------------------------------- + const DatabaseContextPtr &PROJStringFormatter::databaseContext() const { return d->dbContext_; } diff --git a/test/unit/test_crs.cpp b/test/unit/test_crs.cpp index a1838896..987ffe1b 100644 --- a/test/unit/test_crs.cpp +++ b/test/unit/test_crs.cpp @@ -6726,4 +6726,37 @@ TEST(crs, projected_is_equivalent_to_with_proj4_extension) { // Check equivalence of the CRS from PROJ.4 and WKT EXPECT_TRUE(crs1->isEquivalentTo(crs_from_wkt.get(), IComparable::Criterion::EQUIVALENT)); + + ASSERT_TRUE(crs_from_wkt != nullptr); + // Same as above but with different option order + const auto obj2 = PROJStringParser().createFromPROJString( + "+proj=omerc +lat_0=50 +no_rot +alpha=50.0 +a=6378144.0 +b=6356759.0 " + "+lon_0=8.0 +type=crs"); + const auto crs2 = nn_dynamic_pointer_cast<ProjectedCRS>(obj2); + ASSERT_TRUE(crs2 != nullptr); + + // Check equivalence of the 2 PROJ.4 based CRS + EXPECT_TRUE( + crs1->isEquivalentTo(crs2.get(), IComparable::Criterion::EQUIVALENT)); + + // Without +no_rot --> no PROJ.4 extension + const auto objNoRot = PROJStringParser().createFromPROJString( + "+proj=omerc +lat_0=50 +alpha=50.0 +a=6378144.0 +b=6356759.0 " + "+lon_0=8.0 +type=crs"); + const auto crsNoRot = nn_dynamic_pointer_cast<ProjectedCRS>(objNoRot); + ASSERT_TRUE(crsNoRot != nullptr); + EXPECT_FALSE(crs1->isEquivalentTo(crsNoRot.get(), + IComparable::Criterion::EQUIVALENT)); + EXPECT_FALSE(crsNoRot->isEquivalentTo(crs1.get(), + IComparable::Criterion::EQUIVALENT)); + + // Change alpha value + const auto objDifferent = PROJStringParser().createFromPROJString( + "+proj=omerc +lat_0=50 +alpha=49.0 +no_rot +a=6378144.0 +b=6356759.0 " + "+lon_0=8.0 +type=crs"); + const auto crsDifferent = + nn_dynamic_pointer_cast<ProjectedCRS>(objDifferent); + ASSERT_TRUE(crsDifferent != nullptr); + EXPECT_FALSE(crs1->isEquivalentTo(crsDifferent.get(), + IComparable::Criterion::EQUIVALENT)); } |
