diff options
| -rw-r--r-- | include/proj/crs.hpp | 2 | ||||
| -rw-r--r-- | include/proj/internal/Makefile.am | 1 | ||||
| -rw-r--r-- | include/proj/internal/crs_internal.hpp | 41 | ||||
| -rw-r--r-- | src/iso19111/crs.cpp | 150 | ||||
| -rw-r--r-- | src/iso19111/operation/concatenatedoperation.cpp | 36 | ||||
| -rw-r--r-- | test/unit/test_operation.cpp | 17 |
6 files changed, 190 insertions, 57 deletions
diff --git a/include/proj/crs.hpp b/include/proj/crs.hpp index 5a8e75ae..ce950c47 100644 --- a/include/proj/crs.hpp +++ b/include/proj/crs.hpp @@ -142,6 +142,8 @@ class PROJ_GCC_DLL CRS : public common::ObjectUsage, PROJ_INTERNAL bool mustAxisOrderBeSwitchedForVisualization() const; + PROJ_INTERNAL CRSNNPtr applyAxisOrderReversal(const char *nameSuffix) const; + PROJ_FOR_TEST CRSNNPtr normalizeForVisualization() const; PROJ_INTERNAL CRSNNPtr allowNonConformantWKT1Export() const; diff --git a/include/proj/internal/Makefile.am b/include/proj/internal/Makefile.am index 59325667..25b71cc5 100644 --- a/include/proj/internal/Makefile.am +++ b/include/proj/internal/Makefile.am @@ -1,6 +1,7 @@ SUBDIRS = vendor noinst_HEADERS = \ + crs_internal.hpp \ coordinatesystem_internal.hpp \ internal.hpp \ io_internal.hpp \ diff --git a/include/proj/internal/crs_internal.hpp b/include/proj/internal/crs_internal.hpp new file mode 100644 index 00000000..9a68c8b0 --- /dev/null +++ b/include/proj/internal/crs_internal.hpp @@ -0,0 +1,41 @@ +/****************************************************************************** + * + * Project: PROJ + * Purpose: ISO19111:2019 implementation + * Author: Even Rouault <even dot rouault at spatialys dot com> + * + ****************************************************************************** + * Copyright (c) 2021, 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 +#error This file should only be included from a PROJ cpp file +#endif + +#ifndef CRS_INTERNAL_HH_INCLUDED +#define CRS_INTERNAL_HH_INCLUDED + +#define NORMALIZED_AXIS_ORDER_SUFFIX_STR \ + " (with axis order normalized for visualization)" + +#define AXIS_ORDER_REVERSED_SUFFIX_STR " (with axis order reversed)" + +#endif // CRS_INTERNAL_HH_INCLUDED diff --git a/src/iso19111/crs.cpp b/src/iso19111/crs.cpp index b7d57767..6d213bc6 100644 --- a/src/iso19111/crs.cpp +++ b/src/iso19111/crs.cpp @@ -43,6 +43,7 @@ #include "proj/util.hpp" #include "proj/internal/coordinatesystem_internal.hpp" +#include "proj/internal/crs_internal.hpp" #include "proj/internal/internal.hpp" #include "proj/internal/io_internal.hpp" @@ -887,51 +888,63 @@ void CRS::setProperties( //! @cond Doxygen_Suppress -CRSNNPtr CRS::normalizeForVisualization() const { +// --------------------------------------------------------------------------- - const auto createProperties = [this](const std::string &newName = - std::string()) { - auto props = util::PropertyMap().set( - common::IdentifiedObject::NAME_KEY, - !newName.empty() - ? newName - : nameStr() + - " (with axis order normalized for visualization)"); - const auto &l_domains = domains(); - if (!l_domains.empty()) { - auto array = util::ArrayOfBaseObject::create(); - for (const auto &domain : l_domains) { - array->add(domain); +CRSNNPtr CRS::applyAxisOrderReversal(const char *nameSuffix) const { + + const auto createProperties = + [this, nameSuffix](const std::string &newNameIn = std::string()) { + std::string newName(newNameIn); + if (newName.empty()) { + newName = nameStr(); + if (ends_with(newName, NORMALIZED_AXIS_ORDER_SUFFIX_STR)) { + newName.resize(newName.size() - + strlen(NORMALIZED_AXIS_ORDER_SUFFIX_STR)); + } else if (ends_with(newName, AXIS_ORDER_REVERSED_SUFFIX_STR)) { + newName.resize(newName.size() - + strlen(AXIS_ORDER_REVERSED_SUFFIX_STR)); + } else { + newName += nameSuffix; + } } - if (!array->empty()) { - props.set(common::ObjectUsage::OBJECT_DOMAIN_KEY, array); + auto props = util::PropertyMap().set( + common::IdentifiedObject::NAME_KEY, newName); + const auto &l_domains = domains(); + if (!l_domains.empty()) { + auto array = util::ArrayOfBaseObject::create(); + for (const auto &domain : l_domains) { + array->add(domain); + } + if (!array->empty()) { + props.set(common::ObjectUsage::OBJECT_DOMAIN_KEY, array); + } } - } - const auto &l_identifiers = identifiers(); - const auto &l_remarks = remarks(); - if (l_identifiers.size() == 1) { - std::string remarks("Axis order reversed compared to "); - remarks += *(l_identifiers[0]->codeSpace()); - remarks += ':'; - remarks += l_identifiers[0]->code(); - if (!l_remarks.empty()) { - remarks += ". "; - remarks += l_remarks; + const auto &l_identifiers = identifiers(); + const auto &l_remarks = remarks(); + if (l_identifiers.size() == 1) { + std::string remarks("Axis order reversed compared to "); + if (!starts_with(l_remarks, remarks)) { + remarks += *(l_identifiers[0]->codeSpace()); + remarks += ':'; + remarks += l_identifiers[0]->code(); + if (!l_remarks.empty()) { + remarks += ". "; + remarks += l_remarks; + } + props.set(common::IdentifiedObject::REMARKS_KEY, remarks); + } + } else if (!l_remarks.empty()) { + props.set(common::IdentifiedObject::REMARKS_KEY, l_remarks); } - props.set(common::IdentifiedObject::REMARKS_KEY, remarks); - } else if (!l_remarks.empty()) { - props.set(common::IdentifiedObject::REMARKS_KEY, l_remarks); - } - return props; - }; + return props; + }; const CompoundCRS *compoundCRS = dynamic_cast<const CompoundCRS *>(this); if (compoundCRS) { const auto &comps = compoundCRS->componentReferenceSystems(); - if (!comps.empty() && - comps[0]->mustAxisOrderBeSwitchedForVisualization()) { + if (!comps.empty()) { std::vector<CRSNNPtr> newComps; - newComps.emplace_back(comps[0]->normalizeForVisualization()); + newComps.emplace_back(comps[0]->applyAxisOrderReversal(nameSuffix)); std::string l_name = newComps.back()->nameStr(); for (size_t i = 1; i < comps.size(); i++) { newComps.emplace_back(comps[i]); @@ -946,16 +959,53 @@ CRSNNPtr CRS::normalizeForVisualization() const { const GeographicCRS *geogCRS = dynamic_cast<const GeographicCRS *>(this); if (geogCRS) { const auto &axisList = geogCRS->coordinateSystem()->axisList(); + auto cs = + axisList.size() == 2 + ? cs::EllipsoidalCS::create(util::PropertyMap(), axisList[1], + axisList[0]) + : cs::EllipsoidalCS::create(util::PropertyMap(), axisList[1], + axisList[0], axisList[2]); + return util::nn_static_pointer_cast<CRS>( + GeographicCRS::create(createProperties(), geogCRS->datum(), + geogCRS->datumEnsemble(), cs)); + } + + const ProjectedCRS *projCRS = dynamic_cast<const ProjectedCRS *>(this); + if (projCRS) { + const auto &axisList = projCRS->coordinateSystem()->axisList(); + auto cs = + axisList.size() == 2 + ? cs::CartesianCS::create(util::PropertyMap(), axisList[1], + axisList[0]) + : cs::CartesianCS::create(util::PropertyMap(), axisList[1], + axisList[0], axisList[2]); + return util::nn_static_pointer_cast<CRS>( + ProjectedCRS::create(createProperties(), projCRS->baseCRS(), + projCRS->derivingConversion(), cs)); + } + + throw util::UnsupportedOperationException( + "axis order reversal not supported on this type of CRS"); +} + +// --------------------------------------------------------------------------- + +CRSNNPtr CRS::normalizeForVisualization() const { + + const CompoundCRS *compoundCRS = dynamic_cast<const CompoundCRS *>(this); + if (compoundCRS) { + const auto &comps = compoundCRS->componentReferenceSystems(); + if (!comps.empty() && + comps[0]->mustAxisOrderBeSwitchedForVisualization()) { + return applyAxisOrderReversal(NORMALIZED_AXIS_ORDER_SUFFIX_STR); + } + } + + const GeographicCRS *geogCRS = dynamic_cast<const GeographicCRS *>(this); + if (geogCRS) { + const auto &axisList = geogCRS->coordinateSystem()->axisList(); if (mustAxisOrderBeSwitchedForVisualizationInternal(axisList)) { - auto cs = axisList.size() == 2 - ? cs::EllipsoidalCS::create(util::PropertyMap(), - axisList[1], axisList[0]) - : cs::EllipsoidalCS::create(util::PropertyMap(), - axisList[1], axisList[0], - axisList[2]); - return util::nn_static_pointer_cast<CRS>( - GeographicCRS::create(createProperties(), geogCRS->datum(), - geogCRS->datumEnsemble(), cs)); + return applyAxisOrderReversal(NORMALIZED_AXIS_ORDER_SUFFIX_STR); } } @@ -963,15 +1013,7 @@ CRSNNPtr CRS::normalizeForVisualization() const { if (projCRS) { const auto &axisList = projCRS->coordinateSystem()->axisList(); if (mustAxisOrderBeSwitchedForVisualizationInternal(axisList)) { - auto cs = - axisList.size() == 2 - ? cs::CartesianCS::create(util::PropertyMap(), axisList[1], - axisList[0]) - : cs::CartesianCS::create(util::PropertyMap(), axisList[1], - axisList[0], axisList[2]); - return util::nn_static_pointer_cast<CRS>( - ProjectedCRS::create(createProperties(), projCRS->baseCRS(), - projCRS->derivingConversion(), cs)); + return applyAxisOrderReversal(NORMALIZED_AXIS_ORDER_SUFFIX_STR); } } diff --git a/src/iso19111/operation/concatenatedoperation.cpp b/src/iso19111/operation/concatenatedoperation.cpp index 185ebb4a..7da561b4 100644 --- a/src/iso19111/operation/concatenatedoperation.cpp +++ b/src/iso19111/operation/concatenatedoperation.cpp @@ -37,6 +37,7 @@ #include "proj/metadata.hpp" #include "proj/util.hpp" +#include "proj/internal/crs_internal.hpp" #include "proj/internal/internal.hpp" #include "proj/internal/io_internal.hpp" @@ -245,15 +246,44 @@ void ConcatenatedOperation::fixStepsDirection( return false; }; + // Apply axis order reversal operation on first operation if needed + // to set CRSs on it + if (operationsInOut.size() >= 1) { + auto &op = operationsInOut.front(); + auto l_sourceCRS = op->sourceCRS(); + auto l_targetCRS = op->targetCRS(); + auto conv = dynamic_cast<const Conversion *>(op.get()); + if (conv && !l_sourceCRS && !l_targetCRS && + isAxisOrderReversal(conv->method()->getEPSGCode())) { + auto reversedCRS = concatOpSourceCRS->applyAxisOrderReversal( + NORMALIZED_AXIS_ORDER_SUFFIX_STR); + op->setCRSs(concatOpSourceCRS, reversedCRS, nullptr); + } + } + + // Apply axis order reversal operation on last operation if needed + // to set CRSs on it + if (operationsInOut.size() >= 2) { + auto &op = operationsInOut.back(); + auto l_sourceCRS = op->sourceCRS(); + auto l_targetCRS = op->targetCRS(); + auto conv = dynamic_cast<const Conversion *>(op.get()); + if (conv && !l_sourceCRS && !l_targetCRS && + isAxisOrderReversal(conv->method()->getEPSGCode())) { + auto reversedCRS = concatOpTargetCRS->applyAxisOrderReversal( + NORMALIZED_AXIS_ORDER_SUFFIX_STR); + op->setCRSs(reversedCRS, concatOpTargetCRS, nullptr); + } + } + for (size_t i = 0; i < operationsInOut.size(); ++i) { auto &op = operationsInOut[i]; auto l_sourceCRS = op->sourceCRS(); auto l_targetCRS = op->targetCRS(); auto conv = dynamic_cast<const Conversion *>(op.get()); if (conv && i == 0 && !l_sourceCRS && !l_targetCRS) { - auto derivedCRS = - dynamic_cast<const crs::DerivedCRS *>(concatOpSourceCRS.get()); - if (derivedCRS) { + if (auto derivedCRS = dynamic_cast<const crs::DerivedCRS *>( + concatOpSourceCRS.get())) { if (i + 1 < operationsInOut.size()) { // use the sourceCRS of the next operation as our target CRS l_targetCRS = operationsInOut[i + 1]->sourceCRS(); diff --git a/test/unit/test_operation.cpp b/test/unit/test_operation.cpp index f76647e2..0e27aa9b 100644 --- a/test/unit/test_operation.cpp +++ b/test/unit/test_operation.cpp @@ -5429,6 +5429,20 @@ TEST(operation, normalizeForVisualization) { auto authFactory = AuthorityFactory::create(DatabaseContext::create(), "EPSG"); + const auto checkThroughWKTRoundtrip = [](const CoordinateOperationNNPtr + &opRef) { + auto wkt = opRef->exportToWKT( + WKTFormatter::create(WKTFormatter::Convention::WKT2_2019).get()); + auto objFromWkt = WKTParser().createFromWKT(wkt); + auto opFromWkt = + nn_dynamic_pointer_cast<CoordinateOperation>(objFromWkt); + ASSERT_TRUE(opFromWkt != nullptr); + EXPECT_TRUE(opRef->_isEquivalentTo(opFromWkt.get())); + EXPECT_EQ( + opFromWkt->exportToPROJString(PROJStringFormatter::create().get()), + opRef->exportToPROJString(PROJStringFormatter::create().get())); + }; + // Source(geographic) must be inverted { auto src = authFactory->createCoordinateReferenceSystem("4326"); @@ -5443,6 +5457,7 @@ TEST(operation, normalizeForVisualization) { "+proj=pipeline " "+step +proj=unitconvert +xy_in=deg +xy_out=rad " "+step +proj=utm +zone=31 +ellps=WGS84"); + checkThroughWKTRoundtrip(opNormalized); } // Target(geographic) must be inverted @@ -5459,6 +5474,7 @@ TEST(operation, normalizeForVisualization) { "+proj=pipeline " "+step +inv +proj=utm +zone=31 +ellps=WGS84 " "+step +proj=unitconvert +xy_in=rad +xy_out=deg"); + checkThroughWKTRoundtrip(opNormalized); } // Source(geographic) and target(projected) must be inverted @@ -5475,6 +5491,7 @@ TEST(operation, normalizeForVisualization) { "+proj=pipeline " "+step +proj=unitconvert +xy_in=deg +xy_out=rad " "+step +proj=utm +zone=28 +ellps=GRS80"); + checkThroughWKTRoundtrip(opNormalized); } // No inversion |
