aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorKristian Evers <kristianevers@gmail.com>2019-09-16 09:23:00 +0200
committerGitHub <noreply@github.com>2019-09-16 09:23:00 +0200
commit355f43ca41ac4e9a01037e6481ce611da9c13805 (patch)
treea6bc06f084d92b806f266d588a9d66bd49dfa261 /src
parent1aaca77c2aa548a7be16fc2a2a00a5ef8e867e2a (diff)
parent83a14b87ae9707b9233b0e630f02d4a38ee5ea30 (diff)
downloadPROJ-355f43ca41ac4e9a01037e6481ce611da9c13805.tar.gz
PROJ-355f43ca41ac4e9a01037e6481ce611da9c13805.zip
[6.2 backport] cs2cs: autopromote CRS to 3D when there's a mix… (#1615)
[6.2 backport] cs2cs: autopromote CRS to 3D when there's a mix of 2D and 3D (fixes #1563)
Diffstat (limited to 'src')
-rw-r--r--src/apps/cs2cs.cpp26
-rw-r--r--src/iso19111/c_api.cpp147
-rw-r--r--src/iso19111/crs.cpp100
-rw-r--r--src/proj_experimental.h9
4 files changed, 276 insertions, 6 deletions
diff --git a/src/apps/cs2cs.cpp b/src/apps/cs2cs.cpp
index 3099c3e8..ed885dc6 100644
--- a/src/apps/cs2cs.cpp
+++ b/src/apps/cs2cs.cpp
@@ -43,6 +43,7 @@
// PROJ include order is sensitive
// clang-format off
#include "proj.h"
+#include "proj_experimental.h"
#include "proj_internal.h"
#include "emess.h"
#include "utils.h"
@@ -591,12 +592,33 @@ int main(int argc, char **argv) {
}
srcIsGeog = true;
}
+ proj_destroy(src);
+ proj_destroy(dst);
+
+ src = proj_create(nullptr, pj_add_type_crs_if_needed(fromStr).c_str());
+ dst = proj_create(nullptr, pj_add_type_crs_if_needed(toStr).c_str());
+
+ if( proj_get_type(src) == PJ_TYPE_COMPOUND_CRS ||
+ proj_get_type(dst) == PJ_TYPE_COMPOUND_CRS ) {
+ auto src3D = proj_crs_promote_to_3D(nullptr, nullptr, src);
+ if( src3D ) {
+ proj_destroy(src);
+ src = src3D;
+ }
+
+ auto dst3D = proj_crs_promote_to_3D(nullptr, nullptr, dst);
+ if( dst3D ) {
+ proj_destroy(dst);
+ dst = dst3D;
+ }
+ }
+
+ transformation = proj_create_crs_to_crs_from_pj(nullptr, src, dst,
+ nullptr, nullptr);
proj_destroy(src);
proj_destroy(dst);
- transformation = proj_create_crs_to_crs(nullptr, fromStr.c_str(),
- toStr.c_str(), nullptr);
if (!transformation) {
emess(3, "cannot initialize transformation\ncause: %s",
pj_strerrno(pj_errno));
diff --git a/src/iso19111/c_api.cpp b/src/iso19111/c_api.cpp
index 2a2fc748..1e5c106a 100644
--- a/src/iso19111/c_api.cpp
+++ b/src/iso19111/c_api.cpp
@@ -3148,7 +3148,7 @@ PJ *proj_crs_alter_cs_linear_unit(PJ_CONTEXT *ctx, const PJ *obj,
// ---------------------------------------------------------------------------
-/** \brief Return a copy of the CRS with the lineaer units of the parameters
+/** \brief Return a copy of the CRS with the linear units of the parameters
* of its conversion modified.
*
* The CRS must be or contain a ProjectedCRS, VerticalCRS or a GeocentricCRS.
@@ -3197,6 +3197,151 @@ PJ *proj_crs_alter_parameters_linear_unit(PJ_CONTEXT *ctx, const PJ *obj,
// ---------------------------------------------------------------------------
+/** \brief Create a 3D CRS from an existing 2D CRS.
+ *
+ * The new axis will be ellipsoidal height, oriented upwards, and with metre
+ * units.
+ *
+ * See osgeo::proj::crs::CRS::promoteTo3D().
+ *
+ * The returned object must be unreferenced with proj_destroy() after
+ * use.
+ * It should be used by at most one thread at a time.
+ *
+ * @param ctx PROJ context, or NULL for default context
+ * @param crs_3D_name CRS name. Or NULL (in which case the name of crs_2D
+ * will be used)
+ * @param crs_2D 2D CRS to be "promoted" to 3D. Must not be NULL.
+ *
+ * @return Object that must be unreferenced with
+ * proj_destroy(), or NULL in case of error.
+ * @since 7.0
+ */
+PJ *proj_crs_promote_to_3D(PJ_CONTEXT *ctx, const char *crs_3D_name,
+ const PJ *crs_2D) {
+ SANITIZE_CTX(ctx);
+ auto cpp_2D_crs = dynamic_cast<const CRS *>(crs_2D->iso_obj.get());
+ if (!cpp_2D_crs) {
+ proj_log_error(ctx, __FUNCTION__, "crs_2D is not a CRS");
+ return nullptr;
+ }
+ try {
+ auto dbContext = getDBcontextNoException(ctx, __FUNCTION__);
+ return pj_obj_create(
+ ctx, cpp_2D_crs->promoteTo3D(crs_3D_name ? std::string(crs_3D_name)
+ : cpp_2D_crs->nameStr(),
+ dbContext));
+ } catch (const std::exception &e) {
+ proj_log_error(ctx, __FUNCTION__, e.what());
+ if (ctx->cpp_context) {
+ ctx->cpp_context->autoCloseDbIfNeeded();
+ }
+ return nullptr;
+ }
+}
+
+// ---------------------------------------------------------------------------
+
+/** \brief Create a projected 3D CRS from an existing projected 2D CRS.
+ *
+ * The passed projected_2D_crs is used so that its name is replaced by
+ * crs_name and its base geographic CRS is replaced by geog_3D_crs. The vertical
+ * axis of geog_3D_crs (ellipsoidal height) will be added as the 3rd axis of
+ * the resulting projected 3D CRS.
+ * Normally, the passed geog_3D_crs should be the 3D counterpart of the original
+ * 2D base geographic CRS of projected_2D_crs, but such no check is done.
+ *
+ * It is also possible to invoke this function with a NULL geog_3D_crs. In which
+ * case, the existing base geographic 2D CRS of projected_2D_crs will be
+ * automatically promoted to 3D by assuming a 3rd axis being an ellipsoidal
+ * height, oriented upwards, and with metre units. This is equivalent to using
+ * proj_crs_promote_to_3D().
+ *
+ * The returned object must be unreferenced with proj_destroy() after
+ * use.
+ * It should be used by at most one thread at a time.
+ *
+ * @param ctx PROJ context, or NULL for default context
+ * @param crs_name CRS name. Or NULL (in which case the name of projected_2D_crs
+ * will be used)
+ * @param projected_2D_crs Projected 2D CRS to be "promoted" to 3D. Must not be
+ * NULL.
+ * @param geog_3D_crs Base geographic 3D CRS for the new CRS. May be NULL.
+ *
+ * @return Object that must be unreferenced with
+ * proj_destroy(), or NULL in case of error.
+ * @since 7.0
+ */
+PJ *proj_crs_create_projected_3D_crs_from_2D(PJ_CONTEXT *ctx,
+ const char *crs_name,
+ const PJ *projected_2D_crs,
+ const PJ *geog_3D_crs) {
+ SANITIZE_CTX(ctx);
+ auto cpp_projected_2D_crs =
+ dynamic_cast<const ProjectedCRS *>(projected_2D_crs->iso_obj.get());
+ if (!cpp_projected_2D_crs) {
+ proj_log_error(ctx, __FUNCTION__,
+ "projected_2D_crs is not a Projected CRS");
+ return nullptr;
+ }
+ const auto &oldCS = cpp_projected_2D_crs->coordinateSystem();
+ const auto &oldCSAxisList = oldCS->axisList();
+
+ if (geog_3D_crs && geog_3D_crs->iso_obj) {
+ auto cpp_geog_3D_CRS =
+ std::dynamic_pointer_cast<GeographicCRS>(geog_3D_crs->iso_obj);
+ if (!cpp_geog_3D_CRS) {
+ proj_log_error(ctx, __FUNCTION__,
+ "geog_3D_crs is not a Geographic CRS");
+ return nullptr;
+ }
+
+ const auto &geogCS = cpp_geog_3D_CRS->coordinateSystem();
+ const auto &geogCSAxisList = geogCS->axisList();
+ if (geogCSAxisList.size() != 3) {
+ proj_log_error(ctx, __FUNCTION__,
+ "geog_3D_crs is not a Geographic 3D CRS");
+ return nullptr;
+ }
+ try {
+ auto newCS =
+ cs::CartesianCS::create(PropertyMap(), oldCSAxisList[0],
+ oldCSAxisList[1], geogCSAxisList[2]);
+ return pj_obj_create(
+ ctx,
+ ProjectedCRS::create(
+ createPropertyMapName(
+ crs_name ? crs_name
+ : cpp_projected_2D_crs->nameStr().c_str()),
+ NN_NO_CHECK(cpp_geog_3D_CRS),
+ cpp_projected_2D_crs->derivingConversionRef(), newCS));
+ } catch (const std::exception &e) {
+ proj_log_error(ctx, __FUNCTION__, e.what());
+ if (ctx->cpp_context) {
+ ctx->cpp_context->autoCloseDbIfNeeded();
+ }
+ return nullptr;
+ }
+ } else {
+ try {
+ auto dbContext = getDBcontextNoException(ctx, __FUNCTION__);
+ return pj_obj_create(ctx,
+ cpp_projected_2D_crs->promoteTo3D(
+ crs_name ? std::string(crs_name)
+ : cpp_projected_2D_crs->nameStr(),
+ dbContext));
+ } catch (const std::exception &e) {
+ proj_log_error(ctx, __FUNCTION__, e.what());
+ if (ctx->cpp_context) {
+ ctx->cpp_context->autoCloseDbIfNeeded();
+ }
+ return nullptr;
+ }
+ }
+}
+
+// ---------------------------------------------------------------------------
+
/** \brief Instantiate a EngineeringCRS with just a name
*
* The returned object must be unreferenced with proj_destroy() after
diff --git a/src/iso19111/crs.cpp b/src/iso19111/crs.cpp
index 60b316f1..eea7cce8 100644
--- a/src/iso19111/crs.cpp
+++ b/src/iso19111/crs.cpp
@@ -756,6 +756,96 @@ CRS::getNonDeprecated(const io::DatabaseContextNNPtr &dbContext) const {
// ---------------------------------------------------------------------------
+/** \brief Return a variant of this CRS "promoted" to a 3D one, if not already
+ * the case.
+ *
+ * The new axis will be ellipsoidal height, oriented upwards, and with metre
+ * units.
+ *
+ * @param newName Name of the new CRS. If empty, nameStr() will be used.
+ * @param dbContext Database context to look for potentially already registered
+ * 3D CRS. May be nullptr.
+ * @return a new CRS promoted to 3D, or the current one if already 3D or not
+ * applicable.
+ * @since 7.0
+ */
+CRSNNPtr CRS::promoteTo3D(const std::string &newName,
+ const io::DatabaseContextPtr &dbContext) const {
+
+ const auto geogCRS = dynamic_cast<const GeographicCRS *>(this);
+ if (geogCRS) {
+ const auto &axisList = geogCRS->coordinateSystem()->axisList();
+ if (axisList.size() == 2) {
+ const auto &l_identifiers = identifiers();
+ // First check if there is a Geographic 3D CRS in the database
+ // of the same name.
+ // This is the common practice in the EPSG dataset.
+ if (dbContext && l_identifiers.size() == 1) {
+ auto authFactory = io::AuthorityFactory::create(
+ NN_NO_CHECK(dbContext), *(l_identifiers[0]->codeSpace()));
+ auto res = authFactory->createObjectsFromName(
+ nameStr(),
+ {io::AuthorityFactory::ObjectType::GEOGRAPHIC_3D_CRS},
+ false);
+ if (!res.empty()) {
+ const auto &firstRes = res.front();
+ if (geogCRS->is2DPartOf3D(NN_NO_CHECK(
+ dynamic_cast<GeographicCRS *>(firstRes.get())))) {
+ return NN_NO_CHECK(
+ util::nn_dynamic_pointer_cast<CRS>(firstRes));
+ }
+ }
+ }
+
+ auto upAxis = cs::CoordinateSystemAxis::create(
+ util::PropertyMap().set(IdentifiedObject::NAME_KEY,
+ cs::AxisName::Ellipsoidal_height),
+ cs::AxisAbbreviation::h, cs::AxisDirection::UP,
+ common::UnitOfMeasure::METRE);
+ auto cs = cs::EllipsoidalCS::create(
+ util::PropertyMap(), axisList[0], axisList[1], upAxis);
+ return util::nn_static_pointer_cast<CRS>(GeographicCRS::create(
+ util::PropertyMap().set(common::IdentifiedObject::NAME_KEY,
+ !newName.empty() ? newName : nameStr()),
+ geogCRS->datum(), geogCRS->datumEnsemble(), cs));
+ }
+ }
+
+ const auto projCRS = dynamic_cast<const ProjectedCRS *>(this);
+ if (projCRS) {
+ const auto &axisList = projCRS->coordinateSystem()->axisList();
+ if (axisList.size() == 2) {
+ auto base3DCRS =
+ projCRS->baseCRS()->promoteTo3D(std::string(), dbContext);
+ auto upAxis = cs::CoordinateSystemAxis::create(
+ util::PropertyMap().set(IdentifiedObject::NAME_KEY,
+ cs::AxisName::Ellipsoidal_height),
+ cs::AxisAbbreviation::h, cs::AxisDirection::UP,
+ common::UnitOfMeasure::METRE);
+ auto cs = cs::CartesianCS::create(util::PropertyMap(), axisList[0],
+ axisList[1], upAxis);
+ return util::nn_static_pointer_cast<CRS>(ProjectedCRS::create(
+ util::PropertyMap().set(common::IdentifiedObject::NAME_KEY,
+ !newName.empty() ? newName : nameStr()),
+ NN_NO_CHECK(
+ util::nn_dynamic_pointer_cast<GeodeticCRS>(base3DCRS)),
+ projCRS->derivingConversionRef(), cs));
+ }
+ }
+
+ const auto boundCRS = dynamic_cast<const BoundCRS *>(this);
+ if (boundCRS) {
+ return BoundCRS::create(
+ boundCRS->baseCRS()->promoteTo3D(newName, dbContext),
+ boundCRS->hubCRS(), boundCRS->transformation());
+ }
+
+ return NN_NO_CHECK(
+ std::static_pointer_cast<CRS>(shared_from_this().as_nullable()));
+}
+
+// ---------------------------------------------------------------------------
+
//! @cond Doxygen_Suppress
std::list<std::pair<CRSNNPtr, int>>
@@ -1898,14 +1988,18 @@ bool GeographicCRS::is2DPartOf3D(util::nn<const GeographicCRS *> other)
const auto &secondAxis = axis[1];
const auto &otherFirstAxis = otherAxis[0];
const auto &otherSecondAxis = otherAxis[1];
- if (!(firstAxis->_isEquivalentTo(otherFirstAxis.get()) &&
- secondAxis->_isEquivalentTo(otherSecondAxis.get()))) {
+ if (!(firstAxis->_isEquivalentTo(
+ otherFirstAxis.get(), util::IComparable::Criterion::EQUIVALENT) &&
+ secondAxis->_isEquivalentTo(
+ otherSecondAxis.get(),
+ util::IComparable::Criterion::EQUIVALENT))) {
return false;
}
const auto &thisDatum = GeodeticCRS::getPrivate()->datum_;
const auto &otherDatum = other->GeodeticCRS::getPrivate()->datum_;
if (thisDatum && otherDatum) {
- return thisDatum->_isEquivalentTo(otherDatum.get());
+ return thisDatum->_isEquivalentTo(
+ otherDatum.get(), util::IComparable::Criterion::EQUIVALENT);
}
return false;
}
diff --git a/src/proj_experimental.h b/src/proj_experimental.h
index 0e97ac9f..3e419b70 100644
--- a/src/proj_experimental.h
+++ b/src/proj_experimental.h
@@ -210,6 +210,15 @@ PJ PROJ_DLL *proj_crs_alter_parameters_linear_unit(
const char *unit_code,
int convert_to_new_unit);
+PJ PROJ_DLL *proj_crs_promote_to_3D(PJ_CONTEXT *ctx,
+ const char* crs_3D_name,
+ const PJ* crs_2D);
+
+PJ PROJ_DLL *proj_crs_create_projected_3D_crs_from_2D(PJ_CONTEXT *ctx,
+ const char* crs_name,
+ const PJ* projected_2D_crs,
+ const PJ* geog_3D_crs);
+
PJ PROJ_DLL *proj_create_engineering_crs(PJ_CONTEXT *ctx,
const char *crsName);