diff options
| author | Kristian Evers <kristianevers@gmail.com> | 2019-09-16 09:23:00 +0200 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2019-09-16 09:23:00 +0200 |
| commit | 355f43ca41ac4e9a01037e6481ce611da9c13805 (patch) | |
| tree | a6bc06f084d92b806f266d588a9d66bd49dfa261 /src | |
| parent | 1aaca77c2aa548a7be16fc2a2a00a5ef8e867e2a (diff) | |
| parent | 83a14b87ae9707b9233b0e630f02d4a38ee5ea30 (diff) | |
| download | PROJ-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.cpp | 26 | ||||
| -rw-r--r-- | src/iso19111/c_api.cpp | 147 | ||||
| -rw-r--r-- | src/iso19111/crs.cpp | 100 | ||||
| -rw-r--r-- | src/proj_experimental.h | 9 |
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); |
