aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorEven Rouault <even.rouault@spatialys.com>2021-10-06 19:26:09 +0200
committerEven Rouault <even.rouault@spatialys.com>2021-10-06 19:26:09 +0200
commitf28d36cee9ec099ae5fea3873988204a7ebda520 (patch)
tree9adde1d0f8936b3439bb2b42e5c075f9d8df6362
parent3a67ea87fac5b835df7966fa801881eaf7503e78 (diff)
downloadPROJ-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.hpp2
-rw-r--r--src/iso19111/crs.cpp35
-rw-r--r--src/iso19111/io.cpp25
-rw-r--r--test/unit/test_crs.cpp33
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));
}