aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--include/proj/coordinateoperation.hpp2
-rw-r--r--include/proj/crs.hpp4
-rw-r--r--src/iso19111/c_api.cpp32
-rw-r--r--src/iso19111/coordinateoperation.cpp40
-rw-r--r--src/iso19111/crs.cpp98
-rw-r--r--src/proj.h1
-rw-r--r--test/unit/test_c_api.cpp27
-rw-r--r--test/unit/test_operation.cpp120
8 files changed, 324 insertions, 0 deletions
diff --git a/include/proj/coordinateoperation.hpp b/include/proj/coordinateoperation.hpp
index 4397ed60..9e7812c7 100644
--- a/include/proj/coordinateoperation.hpp
+++ b/include/proj/coordinateoperation.hpp
@@ -152,6 +152,8 @@ class PROJ_GCC_DLL CoordinateOperation : public common::ObjectUsage,
PROJ_DLL static const std::string OPERATION_VERSION_KEY;
+ PROJ_DLL CoordinateOperationNNPtr normalizeForVisualization() const;
+
protected:
PROJ_INTERNAL CoordinateOperation();
PROJ_INTERNAL CoordinateOperation(const CoordinateOperation &other);
diff --git a/include/proj/crs.hpp b/include/proj/crs.hpp
index ed3463cd..33bf3b61 100644
--- a/include/proj/crs.hpp
+++ b/include/proj/crs.hpp
@@ -126,6 +126,10 @@ class PROJ_GCC_DLL CRS : public common::ObjectUsage {
PROJ_FOR_TEST CRSNNPtr
alterCSLinearUnit(const common::UnitOfMeasure &unit) const;
+ PROJ_INTERNAL bool mustAxisOrderBeSwitchedForVisualization() const;
+
+ PROJ_INTERNAL CRSNNPtr normalizeForVisualization() const;
+
//! @endcond
protected:
diff --git a/src/iso19111/c_api.cpp b/src/iso19111/c_api.cpp
index e62d3f56..1f4cecde 100644
--- a/src/iso19111/c_api.cpp
+++ b/src/iso19111/c_api.cpp
@@ -6767,3 +6767,35 @@ int proj_cs_get_axis_info(PJ_CONTEXT *ctx, const PJ *cs, int index,
}
return true;
}
+
+// ---------------------------------------------------------------------------
+
+/** \brief Returns a PJ* object whose axis order is the one expected for
+ * visualization purposes.
+ *
+ * The input object must be a coordinate operation, that has been created with
+ * proj_create_crs_to_crs().
+ * If the axis order of its source or target CRS is northing,easting, then an
+ * axis swap operation will be inserted.
+ *
+ * @param ctx PROJ context, or NULL for default context
+ * @param obj Object of type CoordinateOperation,
+ * created with proj_create_crs_to_crs() (must not be NULL)
+ * @return a new PJ* object to free with proj_destroy() in case of success, or
+ * nullptr in case of error
+ */
+PJ *proj_normalize_for_visualization(PJ_CONTEXT *ctx, const PJ *obj) {
+ auto co = dynamic_cast<const CoordinateOperation *>(obj->iso_obj.get());
+ if (!co) {
+ proj_log_error(ctx, __FUNCTION__, "Object is not a CoordinateOperation "
+ "created with "
+ "proj_create_crs_to_crs");
+ return nullptr;
+ }
+ try {
+ return pj_obj_create(ctx, co->normalizeForVisualization());
+ } catch (const std::exception &e) {
+ proj_log_debug(ctx, __FUNCTION__, e.what());
+ return nullptr;
+ }
+}
diff --git a/src/iso19111/coordinateoperation.cpp b/src/iso19111/coordinateoperation.cpp
index 1abb74b3..58e97272 100644
--- a/src/iso19111/coordinateoperation.cpp
+++ b/src/iso19111/coordinateoperation.cpp
@@ -765,6 +765,46 @@ void CoordinateOperation::setProperties(
// ---------------------------------------------------------------------------
+/** \brief Return a variation of the current coordinate operation whose axis
+ * order is the one expected for visualization purposes.
+ */
+CoordinateOperationNNPtr
+CoordinateOperation::normalizeForVisualization() const {
+ auto l_sourceCRS = sourceCRS();
+ auto l_targetCRS = targetCRS();
+ if (!l_sourceCRS || !l_targetCRS) {
+ throw util::UnsupportedOperationException(
+ "Cannot retrieve source or target CRS");
+ }
+ const bool swapSource =
+ l_sourceCRS->mustAxisOrderBeSwitchedForVisualization();
+ const bool swapTarget =
+ l_targetCRS->mustAxisOrderBeSwitchedForVisualization();
+ auto l_this = NN_NO_CHECK(std::dynamic_pointer_cast<CoordinateOperation>(
+ shared_from_this().as_nullable()));
+ if (!swapSource && !swapTarget) {
+ return l_this;
+ }
+ std::vector<CoordinateOperationNNPtr> subOps;
+ if (swapSource) {
+ auto op = Conversion::createAxisOrderReversal(false);
+ op->setCRSs(l_sourceCRS->normalizeForVisualization(),
+ NN_NO_CHECK(l_sourceCRS), nullptr);
+ subOps.emplace_back(op);
+ }
+ subOps.emplace_back(l_this);
+ if (swapTarget) {
+ auto op = Conversion::createAxisOrderReversal(false);
+ op->setCRSs(NN_NO_CHECK(l_targetCRS),
+ l_targetCRS->normalizeForVisualization(), nullptr);
+ subOps.emplace_back(op);
+ }
+ return util::nn_static_pointer_cast<CoordinateOperation>(
+ ConcatenatedOperation::createComputeMetadata(subOps, true));
+}
+
+// ---------------------------------------------------------------------------
+
//! @cond Doxygen_Suppress
struct OperationMethod::Private {
util::optional<std::string> formula_{};
diff --git a/src/iso19111/crs.cpp b/src/iso19111/crs.cpp
index b51d03c9..1ef7dcda 100644
--- a/src/iso19111/crs.cpp
+++ b/src/iso19111/crs.cpp
@@ -591,6 +591,104 @@ CRSNNPtr CRS::alterId(const std::string &authName,
// ---------------------------------------------------------------------------
+//! @cond Doxygen_Suppress
+
+static bool isAxisListNorthEast(
+ const std::vector<cs::CoordinateSystemAxisNNPtr> &axisList) {
+ const auto &dir0 = axisList[0]->direction();
+ const auto &dir1 = axisList[1]->direction();
+ return (&dir0 == &cs::AxisDirection::NORTH &&
+ &dir1 == &cs::AxisDirection::EAST);
+}
+// ---------------------------------------------------------------------------
+
+bool CRS::mustAxisOrderBeSwitchedForVisualization() const {
+
+ const CompoundCRS *compoundCRS = dynamic_cast<const CompoundCRS *>(this);
+ if (compoundCRS) {
+ const auto &comps = compoundCRS->componentReferenceSystems();
+ if (!comps.empty()) {
+ return comps[0]->mustAxisOrderBeSwitchedForVisualization();
+ }
+ }
+
+ const GeographicCRS *geogCRS = dynamic_cast<const GeographicCRS *>(this);
+ if (geogCRS) {
+ return isAxisListNorthEast(geogCRS->coordinateSystem()->axisList());
+ }
+
+ const ProjectedCRS *projCRS = dynamic_cast<const ProjectedCRS *>(this);
+ if (projCRS) {
+ return isAxisListNorthEast(projCRS->coordinateSystem()->axisList());
+ }
+
+ return false;
+}
+
+//! @endcond
+
+// ---------------------------------------------------------------------------
+
+//! @cond Doxygen_Suppress
+
+CRSNNPtr CRS::normalizeForVisualization() const {
+ auto props = util::PropertyMap().set(
+ common::IdentifiedObject::NAME_KEY,
+ nameStr() + " (with axis order normalized for visualization)");
+
+ const CompoundCRS *compoundCRS = dynamic_cast<const CompoundCRS *>(this);
+ if (compoundCRS) {
+ const auto &comps = compoundCRS->componentReferenceSystems();
+ if (!comps.empty()) {
+ std::vector<CRSNNPtr> newComps;
+ newComps.emplace_back(comps[0]->normalizeForVisualization());
+ for (size_t i = 1; i < comps.size(); i++) {
+ newComps.emplace_back(comps[i]);
+ }
+ return util::nn_static_pointer_cast<CRS>(
+ CompoundCRS::create(props, newComps));
+ }
+ }
+
+ const GeographicCRS *geogCRS = dynamic_cast<const GeographicCRS *>(this);
+ if (geogCRS) {
+ const auto &axisList = geogCRS->coordinateSystem()->axisList();
+ if (isAxisListNorthEast(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(
+ props, geogCRS->datum(), geogCRS->datumEnsemble(), cs));
+ }
+ }
+
+ const ProjectedCRS *projCRS = dynamic_cast<const ProjectedCRS *>(this);
+ if (projCRS) {
+ const auto &axisList = projCRS->coordinateSystem()->axisList();
+ if (isAxisListNorthEast(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(props, projCRS->baseCRS(),
+ projCRS->derivingConversionRef(), cs));
+ }
+ }
+
+ return NN_NO_CHECK(
+ std::static_pointer_cast<CRS>(shared_from_this().as_nullable()));
+}
+
+//! @endcond
+
+// ---------------------------------------------------------------------------
+
/** \brief Identify the CRS with reference CRSs.
*
* The candidate CRSs are either hard-coded, or looked in the database when
diff --git a/src/proj.h b/src/proj.h
index 6f53bb07..a412f266 100644
--- a/src/proj.h
+++ b/src/proj.h
@@ -356,6 +356,7 @@ int PROJ_DLL proj_context_get_use_proj4_init_rules(PJ_CONTEXT *ctx, int from_leg
PJ PROJ_DLL *proj_create (PJ_CONTEXT *ctx, const char *definition);
PJ PROJ_DLL *proj_create_argv (PJ_CONTEXT *ctx, int argc, char **argv);
PJ PROJ_DLL *proj_create_crs_to_crs(PJ_CONTEXT *ctx, const char *source_crs, const char *target_crs, PJ_AREA *area);
+PJ PROJ_DLL *proj_normalize_for_visualization(PJ_CONTEXT *ctx, const PJ* obj);
PJ PROJ_DLL *proj_destroy (PJ *P);
diff --git a/test/unit/test_c_api.cpp b/test/unit/test_c_api.cpp
index 486ab0c7..12e98d7e 100644
--- a/test/unit/test_c_api.cpp
+++ b/test/unit/test_c_api.cpp
@@ -3220,4 +3220,31 @@ TEST_F(CApi, proj_get_crs_info_list_from_database) {
proj_crs_info_list_destroy(list);
}
}
+
+// ---------------------------------------------------------------------------
+
+TEST_F(CApi, proj_normalize_for_visualization) {
+
+ {
+ auto P = proj_create(m_ctxt, "+proj=utm +zone=31 +ellps=WGS84");
+ ObjectKeeper keeper_P(P);
+ ASSERT_NE(P, nullptr);
+ auto Pnormalized = proj_normalize_for_visualization(m_ctxt, P);
+ ObjectKeeper keeper_Pnormalized(Pnormalized);
+ EXPECT_EQ(Pnormalized, nullptr);
+ }
+
+ auto P = proj_create_crs_to_crs(m_ctxt, "EPSG:4326", "EPSG:32631", nullptr);
+ ObjectKeeper keeper_P(P);
+ ASSERT_NE(P, nullptr);
+ auto Pnormalized = proj_normalize_for_visualization(m_ctxt, P);
+ ObjectKeeper keeper_Pnormalized(Pnormalized);
+ ASSERT_NE(Pnormalized, nullptr);
+ auto projstr = proj_as_proj_string(m_ctxt, Pnormalized, PJ_PROJ_5, nullptr);
+ ASSERT_NE(projstr, nullptr);
+ EXPECT_EQ(std::string(projstr),
+ "+proj=pipeline +step +proj=unitconvert +xy_in=deg +xy_out=rad "
+ "+step +proj=utm +zone=31 +ellps=WGS84");
+}
+
} // namespace
diff --git a/test/unit/test_operation.cpp b/test/unit/test_operation.cpp
index 0f719a6d..a1774416 100644
--- a/test/unit/test_operation.cpp
+++ b/test/unit/test_operation.cpp
@@ -7648,3 +7648,123 @@ TEST(operation, validateParameters) {
EXPECT_EQ(validation, expected);
}
}
+
+// ---------------------------------------------------------------------------
+
+TEST(operation, normalizeForVisualization) {
+
+ auto authFactory =
+ AuthorityFactory::create(DatabaseContext::create(), "EPSG");
+
+ // Source(geographic) must be inverted
+ {
+ auto src = authFactory->createCoordinateReferenceSystem("4326");
+ auto dst = authFactory->createCoordinateReferenceSystem("32631");
+ auto op =
+ CoordinateOperationFactory::create()->createOperation(src, dst);
+ ASSERT_TRUE(op != nullptr);
+ auto opNormalized = op->normalizeForVisualization();
+ EXPECT_FALSE(opNormalized->_isEquivalentTo(op.get()));
+ EXPECT_EQ(opNormalized->exportToPROJString(
+ PROJStringFormatter::create().get()),
+ "+proj=pipeline "
+ "+step +proj=unitconvert +xy_in=deg +xy_out=rad "
+ "+step +proj=utm +zone=31 +ellps=WGS84");
+ }
+
+ // Target(geographic) must be inverted
+ {
+ auto src = authFactory->createCoordinateReferenceSystem("32631");
+ auto dst = authFactory->createCoordinateReferenceSystem("4326");
+ auto op =
+ CoordinateOperationFactory::create()->createOperation(src, dst);
+ ASSERT_TRUE(op != nullptr);
+ auto opNormalized = op->normalizeForVisualization();
+ EXPECT_FALSE(opNormalized->_isEquivalentTo(op.get()));
+ EXPECT_EQ(opNormalized->exportToPROJString(
+ PROJStringFormatter::create().get()),
+ "+proj=pipeline "
+ "+step +inv +proj=utm +zone=31 +ellps=WGS84 "
+ "+step +proj=unitconvert +xy_in=rad +xy_out=deg");
+ }
+
+ // Source(geographic) and target(projected) must be inverted
+ {
+ auto src = authFactory->createCoordinateReferenceSystem("4326");
+ auto dst = authFactory->createCoordinateReferenceSystem("3040");
+ auto op =
+ CoordinateOperationFactory::create()->createOperation(src, dst);
+ ASSERT_TRUE(op != nullptr);
+ auto opNormalized = op->normalizeForVisualization();
+ EXPECT_FALSE(opNormalized->_isEquivalentTo(op.get()));
+ EXPECT_EQ(opNormalized->exportToPROJString(
+ PROJStringFormatter::create().get()),
+ "+proj=pipeline "
+ "+step +proj=unitconvert +xy_in=deg +xy_out=rad "
+ "+step +proj=utm +zone=28 +ellps=GRS80");
+ }
+
+ // No inversion
+ {
+ auto src = authFactory->createCoordinateReferenceSystem("32631");
+ auto dst = authFactory->createCoordinateReferenceSystem("32632");
+ auto op =
+ CoordinateOperationFactory::create()->createOperation(src, dst);
+ ASSERT_TRUE(op != nullptr);
+ auto opNormalized = op->normalizeForVisualization();
+ EXPECT_TRUE(opNormalized->_isEquivalentTo(op.get()));
+ }
+
+ // Source(compoundCRS) and target(geographic 3D) must be inverted
+ {
+ auto ctxt =
+ CoordinateOperationContext::create(authFactory, nullptr, 0.0);
+ ctxt->setUsePROJAlternativeGridNames(false);
+ auto src = CompoundCRS::create(
+ PropertyMap(),
+ std::vector<CRSNNPtr>{
+ authFactory->createCoordinateReferenceSystem("4326"),
+ // EGM2008 height
+ authFactory->createCoordinateReferenceSystem("3855")});
+ auto list = CoordinateOperationFactory::create()->createOperations(
+ src,
+ authFactory->createCoordinateReferenceSystem("4979"), // WGS 84 3D
+ ctxt);
+ ASSERT_EQ(list.size(), 2U);
+ auto op = list[1];
+ auto opNormalized = op->normalizeForVisualization();
+ EXPECT_FALSE(opNormalized->_isEquivalentTo(op.get()));
+ EXPECT_EQ(opNormalized->exportToPROJString(
+ PROJStringFormatter::create(
+ PROJStringFormatter::Convention::PROJ_5,
+ authFactory->databaseContext())
+ .get()),
+ "+proj=pipeline "
+ "+step +proj=unitconvert +xy_in=deg +xy_out=rad "
+ "+step +proj=vgridshift +grids=egm08_25.gtx +multiplier=1 "
+ "+step +proj=unitconvert +xy_in=rad +xy_out=deg");
+ }
+
+ // Source(boundCRS) and target(geographic) must be inverted
+ {
+ auto src = BoundCRS::createFromTOWGS84(
+ GeographicCRS::EPSG_4269, std::vector<double>{1, 2, 3, 4, 5, 6, 7});
+ auto dst = authFactory->createCoordinateReferenceSystem("4326");
+ auto op =
+ CoordinateOperationFactory::create()->createOperation(src, dst);
+ ASSERT_TRUE(op != nullptr);
+ auto opNormalized = op->normalizeForVisualization();
+ EXPECT_FALSE(opNormalized->_isEquivalentTo(op.get()));
+ EXPECT_EQ(opNormalized->exportToPROJString(
+ PROJStringFormatter::create().get()),
+ "+proj=pipeline "
+ "+step +proj=unitconvert +xy_in=deg +xy_out=rad "
+ "+step +proj=push +v_3 "
+ "+step +proj=cart +ellps=GRS80 "
+ "+step +proj=helmert +x=1 +y=2 +z=3 +rx=4 +ry=5 +rz=6 +s=7 "
+ "+convention=position_vector "
+ "+step +inv +proj=cart +ellps=WGS84 "
+ "+step +proj=pop +v_3 "
+ "+step +proj=unitconvert +xy_in=rad +xy_out=deg");
+ }
+}