diff options
| -rw-r--r-- | data/sql/commit.sql | 16 | ||||
| -rw-r--r-- | data/sql/customizations.sql | 17 | ||||
| -rw-r--r-- | data/sql/proj_db_table_defs.sql | 17 | ||||
| -rw-r--r-- | include/proj/io.hpp | 4 | ||||
| -rw-r--r-- | scripts/reference_exported_symbols.txt | 1 | ||||
| -rw-r--r-- | src/iso19111/c_api.cpp | 76 | ||||
| -rw-r--r-- | src/iso19111/coordinateoperation.cpp | 143 | ||||
| -rw-r--r-- | src/iso19111/factory.cpp | 22 | ||||
| -rw-r--r-- | src/proj_experimental.h | 13 | ||||
| -rw-r--r-- | test/unit/test_c_api.cpp | 107 | ||||
| -rw-r--r-- | test/unit/test_operation.cpp | 82 |
11 files changed, 489 insertions, 9 deletions
diff --git a/data/sql/commit.sql b/data/sql/commit.sql index eb49828a..a708df0f 100644 --- a/data/sql/commit.sql +++ b/data/sql/commit.sql @@ -29,6 +29,22 @@ FOR EACH ROW BEGIN WHERE (SELECT 1 FROM object_view LIMIT 1) = 0; SELECT RAISE(ABORT, 'corrupt definition of authority_list') WHERE (SELECT 1 FROM authority_list LIMIT 1) = 0; + + -- check geoid_model table + SELECT RAISE(ABORT, 'missing GEOID99 in geoid_model') + WHERE NOT EXISTS(SELECT 1 FROM geoid_model WHERE name = 'GEOID99'); + SELECT RAISE(ABORT, 'missing GEOID03 in geoid_model') + WHERE NOT EXISTS(SELECT 1 FROM geoid_model WHERE name = 'GEOID03'); + SELECT RAISE(ABORT, 'missing GEOID06 in geoid_model') + WHERE NOT EXISTS(SELECT 1 FROM geoid_model WHERE name = 'GEOID06'); + SELECT RAISE(ABORT, 'missing GEOID09 in geoid_model') + WHERE NOT EXISTS(SELECT 1 FROM geoid_model WHERE name = 'GEOID09'); + SELECT RAISE(ABORT, 'missing GEOID12A in geoid_model') + WHERE NOT EXISTS(SELECT 1 FROM geoid_model WHERE name = 'GEOID12A'); + SELECT RAISE(ABORT, 'missing GEOID12B in geoid_model') + WHERE NOT EXISTS(SELECT 1 FROM geoid_model WHERE name = 'GEOID12B'); + SELECT RAISE(ABORT, 'missing GEOID18 in geoid_model') + WHERE NOT EXISTS(SELECT 1 FROM geoid_model WHERE name = 'GEOID18'); END; INSERT INTO dummy DEFAULT VALUES; DROP TRIGGER final_checks; diff --git a/data/sql/customizations.sql b/data/sql/customizations.sql index 99891dc1..b0804ebd 100644 --- a/data/sql/customizations.sql +++ b/data/sql/customizations.sql @@ -95,3 +95,20 @@ INSERT INTO "helmert_transformation" VALUES('PROJ','WGS84_TO_WGS84_TRANSIT','WGS DELETE FROM supersession WHERE superseded_table_name = 'grid_transformation' AND superseded_auth_name = 'EPSG' AND superseded_code = '6326'; DELETE FROM supersession WHERE superseded_table_name = 'grid_transformation' AND superseded_auth_name = 'EPSG' AND superseded_code = '7646'; DELETE FROM supersession WHERE superseded_table_name = 'grid_transformation' AND superseded_auth_name = 'EPSG' AND superseded_code = '7647'; + +---- Geoid models ----- + +INSERT INTO "geoid_model" SELECT 'GEOID99', auth_name, code FROM grid_transformation WHERE auth_name = 'EPSG' AND grid_name LIKE 'g1999%' AND deprecated = 0; + +INSERT INTO "geoid_model" SELECT 'GEOID03', auth_name, code FROM grid_transformation WHERE auth_name = 'EPSG' AND grid_name LIKE 'geoid03%' AND deprecated = 0; + +INSERT INTO "geoid_model" SELECT 'GEOID06', auth_name, code FROM grid_transformation WHERE auth_name = 'EPSG' AND grid_name LIKE 'geoid06%' AND deprecated = 0; + +INSERT INTO "geoid_model" SELECT 'GEOID09', auth_name, code FROM grid_transformation WHERE auth_name = 'EPSG' AND grid_name LIKE 'geoid09%' AND deprecated = 0; + +-- Geoid12A and Geoid12B are identical +INSERT INTO "geoid_model" SELECT 'GEOID12A', auth_name, code FROM grid_transformation WHERE auth_name = 'EPSG' AND grid_name LIKE 'g2012b%' AND deprecated = 0; + +INSERT INTO "geoid_model" SELECT 'GEOID12B', auth_name, code FROM grid_transformation WHERE auth_name = 'EPSG' AND grid_name LIKE 'g2012b%' AND deprecated = 0; + +INSERT INTO "geoid_model" SELECT 'GEOID18', auth_name, code FROM grid_transformation WHERE auth_name = 'EPSG' AND grid_name LIKE 'g2018%' AND deprecated = 0; diff --git a/data/sql/proj_db_table_defs.sql b/data/sql/proj_db_table_defs.sql index 8a02acf4..a97e75a8 100644 --- a/data/sql/proj_db_table_defs.sql +++ b/data/sql/proj_db_table_defs.sql @@ -1365,6 +1365,23 @@ FOR EACH ROW BEGIN END; + +CREATE TABLE geoid_model( + name TEXT NOT NULL, + operation_auth_name TEXT NOT NULL, + operation_code TEXT NOT NULL, + CONSTRAINT pk_geoid_model PRIMARY KEY (name, operation_auth_name, operation_code) + -- CONSTRATINT fk_geoid_model_operation FOREIGN KEY (operation_auth_name, operation_code) REFERENCES coordinate_operation(auth_name, code) +); + +CREATE TRIGGER geoid_model_insert_trigger +BEFORE INSERT ON geoid_model +FOR EACH ROW BEGIN + SELECT RAISE(ABORT, 'insert on geoid_model violates constraint: (operation_auth_name, operation_code) must already exist in coordinate_operation_with_conversion_view') + WHERE NOT EXISTS (SELECT 1 FROM coordinate_operation_with_conversion_view covwv WHERE covwv.auth_name = NEW.operation_auth_name AND covwv.code = NEW.operation_code); +END; + + CREATE TABLE alias_name( table_name TEXT NOT NULL CHECK (table_name IN ( 'unit_of_measure', 'celestial_body', 'ellipsoid', diff --git a/include/proj/io.hpp b/include/proj/io.hpp index f8ee1c97..17f0fea5 100644 --- a/include/proj/io.hpp +++ b/include/proj/io.hpp @@ -1118,6 +1118,10 @@ class PROJ_GCC_DLL AuthorityFactory { getPreferredHubGeodeticReferenceFrames( const std::string &geodeticReferenceFrameCode) const; + PROJ_INTERNAL std::vector<operation::CoordinateOperationNNPtr> + getTransformationsForGeoid(const std::string &geoidName, + bool usePROJAlternativeGridNames) const; + //! @endcond protected: diff --git a/scripts/reference_exported_symbols.txt b/scripts/reference_exported_symbols.txt index cb568566..67d4837f 100644 --- a/scripts/reference_exported_symbols.txt +++ b/scripts/reference_exported_symbols.txt @@ -899,6 +899,7 @@ proj_create_operations proj_create_projected_crs proj_create_transformation proj_create_vertical_crs +proj_create_vertical_crs_ex proj_crs_alter_cs_angular_unit proj_crs_alter_cs_linear_unit proj_crs_alter_geodetic_crs diff --git a/src/iso19111/c_api.cpp b/src/iso19111/c_api.cpp index f5f7ba55..32c8df45 100644 --- a/src/iso19111/c_api.cpp +++ b/src/iso19111/c_api.cpp @@ -2603,13 +2603,19 @@ int proj_coordoperation_get_method_info(PJ_CONTEXT *ctx, // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress -static PropertyMap createPropertyMapName(const char *c_name) { +static PropertyMap createPropertyMapName(const char *c_name, + const char *auth_name = nullptr, + const char *code = nullptr) { std::string name(c_name ? c_name : "unnamed"); PropertyMap properties; if (ends_with(name, " (deprecated)")) { name.resize(name.size() - strlen(" (deprecated)")); properties.set(common::IdentifiedObject::DEPRECATED_KEY, true); } + if (auth_name && code) { + properties.set(metadata::Identifier::CODESPACE_KEY, auth_name); + properties.set(metadata::Identifier::CODE_KEY, code); + } return properties.set(common::IdentifiedObject::NAME_KEY, name); } @@ -2934,15 +2940,73 @@ PJ *proj_create_vertical_crs(PJ_CONTEXT *ctx, const char *crs_name, const char *datum_name, const char *linear_units, double linear_units_conv) { + return proj_create_vertical_crs_ex( + ctx, crs_name, datum_name, nullptr, nullptr, linear_units, + linear_units_conv, nullptr, nullptr, nullptr, nullptr, nullptr); +} + +// --------------------------------------------------------------------------- + +/** \brief Create a VerticalCRS + * + * 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 Name of the GeographicCRS. Or NULL + * @param datum_name Name of the VerticalReferenceFrame. Or NULL + * @param datum_auth_name Authority name of the VerticalReferenceFrame. Or NULL + * @param datum_code Code of the VerticalReferenceFrame. Or NULL + * @param linear_units Name of the linear units. Or NULL for Metre + * @param linear_units_conv Conversion factor from the linear unit to metre. Or + * 0 for Metre if linear_units == NULL. Otherwise should be not NULL + * @param geoid_model_name Geoid model name, or NULL. Can be a name from the + * geoid_model name or a string "PROJ foo.gtx" + * @param geoid_model_auth_name Authority name of the transformation for + * the geoid model. or NULL + * @param geoid_model_code Code of the transformation for + * the geoid model. or NULL + * @param geoid_geog_crs Geographic CRS for the geoid transformation, or NULL. + * @param options should be set to NULL for now + * @return Object of type VerticalCRS that must be unreferenced with + * proj_destroy(), or NULL in case of error. + */ +PJ *proj_create_vertical_crs_ex( + PJ_CONTEXT *ctx, const char *crs_name, const char *datum_name, + const char *datum_auth_name, const char *datum_code, + const char *linear_units, double linear_units_conv, + const char *geoid_model_name, const char *geoid_model_auth_name, + const char *geoid_model_code, const PJ *geoid_geog_crs, + const char *const *options) { SANITIZE_CTX(ctx); + (void)options; try { const UnitOfMeasure linearUnit( createLinearUnit(linear_units, linear_units_conv)); - auto datum = - VerticalReferenceFrame::create(createPropertyMapName(datum_name)); - auto vertCRS = VerticalCRS::create( - createPropertyMapName(crs_name), datum, - cs::VerticalCS::createGravityRelatedHeight(linearUnit)); + auto datum = VerticalReferenceFrame::create( + createPropertyMapName(datum_name, datum_auth_name, datum_code)); + auto props = createPropertyMapName(crs_name); + auto cs = cs::VerticalCS::createGravityRelatedHeight(linearUnit); + if (geoid_model_name) { + auto propsModel = createPropertyMapName( + geoid_model_name, geoid_model_auth_name, geoid_model_code); + const auto vertCRSWithoutGeoid = + VerticalCRS::create(props, datum, cs); + const auto interpCRS = + geoid_geog_crs && std::dynamic_pointer_cast<GeographicCRS>( + geoid_geog_crs->iso_obj) + ? std::dynamic_pointer_cast<CRS>(geoid_geog_crs->iso_obj) + : nullptr; + const auto model(Transformation::create( + propsModel, vertCRSWithoutGeoid, GeographicCRS::EPSG_4979, + interpCRS, + OperationMethod::create(PropertyMap(), + std::vector<OperationParameterNNPtr>()), + {}, {})); + props.set("GEOID_MODEL", model); + } + auto vertCRS = VerticalCRS::create(props, datum, cs); return pj_obj_create(ctx, vertCRS); } catch (const std::exception &e) { proj_log_error(ctx, __FUNCTION__, e.what()); diff --git a/src/iso19111/coordinateoperation.cpp b/src/iso19111/coordinateoperation.cpp index d0a1761c..e8f195e3 100644 --- a/src/iso19111/coordinateoperation.cpp +++ b/src/iso19111/coordinateoperation.cpp @@ -10442,6 +10442,12 @@ struct CoordinateOperationFactory::Private { std::vector<CoordinateOperationNNPtr> &res); static std::vector<CoordinateOperationNNPtr> + createOperationsGeogToVertFromGeoid(const crs::CRSNNPtr &sourceCRS, + const crs::CRSNNPtr &targetCRS, + const crs::VerticalCRS *vertDst, + Context &context); + + static std::vector<CoordinateOperationNNPtr> createOperationsGeogToVertWithIntermediateVert( const crs::CRSNNPtr &sourceCRS, const crs::CRSNNPtr &targetCRS, const crs::VerticalCRS *vertDst, Context &context); @@ -12775,6 +12781,18 @@ bool CoordinateOperationFactory::Private::createOperationsFromDatabase( ENTER_FUNCTION(); + if (geogSrc && vertDst) { + res = createOperationsGeogToVertFromGeoid(sourceCRS, targetCRS, vertDst, + context); + } else if (geogDst && vertSrc) { + res = applyInverse(createOperationsGeogToVertFromGeoid( + targetCRS, sourceCRS, vertSrc, context)); + } + + if (!res.empty()) { + return true; + } + res = findOpsInRegistryDirect(sourceCRS, targetCRS, context); // If we get at least a result with perfect accuracy, do not @@ -12898,6 +12916,116 @@ findCandidateVertCRSForDatum(const io::AuthorityFactoryPtr &authFactory, // --------------------------------------------------------------------------- +std::vector<CoordinateOperationNNPtr> +CoordinateOperationFactory::Private::createOperationsGeogToVertFromGeoid( + const crs::CRSNNPtr &sourceCRS, const crs::CRSNNPtr &targetCRS, + const crs::VerticalCRS *vertDst, Private::Context &context) { + + ENTER_FUNCTION(); + + const auto useTransf = [&targetCRS, &context, + vertDst](const CoordinateOperationNNPtr &op) { + const auto targetOp = + dynamic_cast<const crs::VerticalCRS *>(op->targetCRS().get()); + assert(targetOp); + if (targetOp->_isEquivalentTo( + vertDst, util::IComparable::Criterion::EQUIVALENT)) { + return op; + } + std::vector<CoordinateOperationNNPtr> tmp; + createOperationsVertToVert(NN_NO_CHECK(op->targetCRS()), targetCRS, + context, targetOp, vertDst, tmp); + assert(!tmp.empty()); + auto ret = ConcatenatedOperation::createComputeMetadata( + {op, tmp.front()}, !allowEmptyIntersection); + return ret; + }; + + const auto getProjGeoidTransformation = [&sourceCRS, &targetCRS, &vertDst]( + const CoordinateOperationNNPtr &model, + const std::string &projFilename) { + + const auto getNameVertCRSMetre = [](const std::string &name) { + if (name.empty()) + return std::string("unnamed"); + auto ret(name); + bool haveOriginalUnit = false; + if (name.back() == ')') { + const auto pos = ret.rfind(" ("); + if (pos != std::string::npos) { + haveOriginalUnit = true; + ret = ret.substr(0, pos); + } + } + const auto pos = ret.rfind(" depth"); + if (pos != std::string::npos) { + ret = ret.substr(0, pos) + " height"; + } + if (!haveOriginalUnit) { + ret += " (metre)"; + } + return ret; + }; + + const auto &axis = vertDst->coordinateSystem()->axisList()[0]; + const auto geogSrcCRS = + dynamic_cast<crs::GeographicCRS *>(model->interpolationCRS().get()) + ? NN_NO_CHECK(model->interpolationCRS()) + : sourceCRS; + const auto vertCRSMetre = + axis->unit() == common::UnitOfMeasure::METRE && + axis->direction() == cs::AxisDirection::UP + ? targetCRS + : util::nn_static_pointer_cast<crs::CRS>( + crs::VerticalCRS::create( + util::PropertyMap().set( + common::IdentifiedObject::NAME_KEY, + getNameVertCRSMetre(targetCRS->nameStr())), + vertDst->datum(), vertDst->datumEnsemble(), + cs::VerticalCS::createGravityRelatedHeight( + common::UnitOfMeasure::METRE))); + const auto properties = util::PropertyMap().set( + common::IdentifiedObject::NAME_KEY, + buildOpName("Transformation", vertCRSMetre, geogSrcCRS)); + return Transformation::createGravityRelatedHeightToGeographic3D( + properties, vertCRSMetre, geogSrcCRS, nullptr, projFilename, {}); + }; + + std::vector<CoordinateOperationNNPtr> res; + const auto &authFactory = context.context->getAuthorityFactory(); + if (authFactory) { + const auto &models = vertDst->geoidModel(); + for (const auto &model : models) { + const auto &modelName = model->nameStr(); + const auto transformations = + starts_with(modelName, "PROJ ") + ? std::vector< + CoordinateOperationNNPtr>{getProjGeoidTransformation( + model, modelName.substr(strlen("PROJ ")))} + : authFactory->getTransformationsForGeoid( + modelName, + context.context->getUsePROJAlternativeGridNames()); + for (const auto &transf : transformations) { + if (dynamic_cast<crs::GeographicCRS *>( + transf->sourceCRS().get()) && + dynamic_cast<crs::VerticalCRS *>( + transf->targetCRS().get())) { + res.push_back(useTransf(transf)); + } else if (dynamic_cast<crs::GeographicCRS *>( + transf->targetCRS().get()) && + dynamic_cast<crs::VerticalCRS *>( + transf->sourceCRS().get())) { + res.push_back(useTransf(transf->inverse())); + } + } + } + } + + return res; +} + +// --------------------------------------------------------------------------- + std::vector<CoordinateOperationNNPtr> CoordinateOperationFactory::Private:: createOperationsGeogToVertWithIntermediateVert( const crs::CRSNNPtr &sourceCRS, const crs::CRSNNPtr &targetCRS, @@ -14223,16 +14351,25 @@ getResolvedCRS(const crs::CRSNNPtr &crs, } else { auto outCrs = tryToIdentifyByName( io::AuthorityFactory::ObjectType::COMPOUND_CRS); + const auto &components = compoundCrs->componentReferenceSystems(); if (outCrs.get() != crs.get()) { - return outCrs; + bool hasGeoid = false; + if (components.size() == 2) { + auto vertCRS = + dynamic_cast<crs::VerticalCRS *>(components[1].get()); + if (vertCRS && !vertCRS->geoidModel().empty()) { + hasGeoid = true; + } + } + if (!hasGeoid) { + return outCrs; + } } if (approxExtent || !extentOut) { // If we still did not get a reliable extent, then try to // resolve the components of the compoundCRS, and take the // intersection of their extent. extentOut = metadata::ExtentPtr(); - const auto &components = - compoundCrs->componentReferenceSystems(); for (const auto &component : components) { metadata::ExtentPtr componentExtent; getResolvedCRS(component, context, componentExtent); diff --git a/src/iso19111/factory.cpp b/src/iso19111/factory.cpp index 307c3a07..d9917996 100644 --- a/src/iso19111/factory.cpp +++ b/src/iso19111/factory.cpp @@ -5380,6 +5380,28 @@ AuthorityFactory::getPreferredHubGeodeticReferenceFrames( // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress +std::vector<operation::CoordinateOperationNNPtr> +AuthorityFactory::getTransformationsForGeoid( + const std::string &geoidName, bool usePROJAlternativeGridNames) const { + std::vector<operation::CoordinateOperationNNPtr> res; + + const std::string sql("SELECT operation_auth_name, operation_code FROM " + "geoid_model WHERE name = ?"); + auto sqlRes = d->run(sql, {geoidName}); + for (const auto &row : sqlRes) { + const auto &auth_name = row[0]; + const auto &code = row[1]; + res.emplace_back(d->createFactory(auth_name)->createCoordinateOperation( + code, usePROJAlternativeGridNames)); + } + + return res; +} +//! @endcond + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress FactoryException::FactoryException(const char *message) : Exception(message) {} // --------------------------------------------------------------------------- diff --git a/src/proj_experimental.h b/src/proj_experimental.h index c6d5bc45..0886ba28 100644 --- a/src/proj_experimental.h +++ b/src/proj_experimental.h @@ -248,6 +248,19 @@ PJ PROJ_DLL *proj_create_vertical_crs(PJ_CONTEXT *ctx, const char *linear_units, double linear_units_conv); +PJ PROJ_DLL *proj_create_vertical_crs_ex(PJ_CONTEXT *ctx, + const char *crs_name, + const char *datum_name, + const char *datum_auth_name, + const char* datum_code, + const char *linear_units, + double linear_units_conv, + const char* geoid_model_name, + const char* geoid_model_auth_name, + const char* geoid_model_code, + const PJ* geoid_geog_crs, + const char *const *options); + PJ PROJ_DLL *proj_create_compound_crs(PJ_CONTEXT *ctx, const char *crs_name, PJ* horiz_crs, diff --git a/test/unit/test_c_api.cpp b/test/unit/test_c_api.cpp index 5e6d1924..cb59563e 100644 --- a/test/unit/test_c_api.cpp +++ b/test/unit/test_c_api.cpp @@ -4213,4 +4213,111 @@ TEST_F( EXPECT_NEAR(outcoord.xyzt.z, -32.5823, 1e-3); } +// --------------------------------------------------------------------------- + +TEST_F(CApi, proj_create_vertical_crs_ex) { + + // NAD83(2011) / UTM zone 11N + auto horiz_crs = proj_create_from_database(m_ctxt, "EPSG", "6340", + PJ_CATEGORY_CRS, false, nullptr); + ObjectKeeper keeper_horiz_crs(horiz_crs); + ASSERT_NE(horiz_crs, nullptr); + + auto vert_crs = proj_create_vertical_crs_ex( + m_ctxt, "myVertCRS (ftUS)", "myVertDatum", nullptr, nullptr, + "US survey foot", 0.304800609601219, "PROJ @foo.gtx", nullptr, nullptr, + nullptr, nullptr); + ObjectKeeper keeper_vert_crs(vert_crs); + ASSERT_NE(vert_crs, nullptr); + + auto compound = + proj_create_compound_crs(m_ctxt, "Compound", horiz_crs, vert_crs); + ObjectKeeper keeper_compound(compound); + ASSERT_NE(compound, nullptr); + + // NAD83(2011) 3D + PJ *geog_crs = proj_create(m_ctxt, "EPSG:6319"); + ObjectKeeper keeper_geog_crs(geog_crs); + ASSERT_NE(geog_crs, nullptr); + + auto P = proj_create_crs_to_crs_from_pj(m_ctxt, compound, geog_crs, nullptr, + nullptr); + ObjectKeeper keeper_P(P); + ASSERT_NE(P, nullptr); + + auto name = proj_get_name(P); + ASSERT_TRUE(name != nullptr); + EXPECT_EQ(name, + std::string("Inverse of UTM zone 11N + " + "Transformation from myVertCRS (ftUS) to myVertCRS + " + "Transformation from myVertCRS to NAD83(2011)")); + + auto proj_5 = proj_as_proj_string(m_ctxt, P, PJ_PROJ_5, nullptr); + ASSERT_NE(proj_5, nullptr); + EXPECT_EQ(std::string(proj_5), + "+proj=pipeline " + "+step +inv +proj=utm +zone=11 +ellps=GRS80 " + "+step +proj=unitconvert +z_in=us-ft +z_out=m " + "+step +proj=vgridshift +grids=@foo.gtx +multiplier=1 " + "+step +proj=unitconvert +xy_in=rad +xy_out=deg " + "+step +proj=axisswap +order=2,1"); +} + +// --------------------------------------------------------------------------- + +TEST_F(CApi, proj_create_vertical_crs_ex_with_geog_crs) { + + // NAD83(2011) / UTM zone 11N + auto horiz_crs = proj_create_from_database(m_ctxt, "EPSG", "6340", + PJ_CATEGORY_CRS, false, nullptr); + ObjectKeeper keeper_horiz_crs(horiz_crs); + ASSERT_NE(horiz_crs, nullptr); + + // WGS84 + PJ *wgs84 = proj_create(m_ctxt, "EPSG:4979"); + ObjectKeeper keeper_wgs84(wgs84); + ASSERT_NE(wgs84, nullptr); + + auto vert_crs = proj_create_vertical_crs_ex( + m_ctxt, "myVertCRS", "myVertDatum", nullptr, nullptr, "US survey foot", + 0.304800609601219, "PROJ @foo.gtx", nullptr, nullptr, wgs84, nullptr); + ObjectKeeper keeper_vert_crs(vert_crs); + ASSERT_NE(vert_crs, nullptr); + + auto compound = + proj_create_compound_crs(m_ctxt, "Compound", horiz_crs, vert_crs); + ObjectKeeper keeper_compound(compound); + ASSERT_NE(compound, nullptr); + + // NAD83(2011) 3D + PJ *geog_crs = proj_create(m_ctxt, "EPSG:6319"); + ObjectKeeper keeper_geog_crs(geog_crs); + ASSERT_NE(geog_crs, nullptr); + + auto P = proj_create_crs_to_crs_from_pj(m_ctxt, compound, geog_crs, nullptr, + nullptr); + ObjectKeeper keeper_P(P); + ASSERT_NE(P, nullptr); + + auto name = proj_get_name(P); + ASSERT_TRUE(name != nullptr); + EXPECT_EQ( + name, + std::string("Inverse of UTM zone 11N + " + "Ballpark geographic offset from NAD83(2011) to WGS 84 + " + "Transformation from myVertCRS to myVertCRS (metre) + " + "Transformation from myVertCRS (metre) to WGS 84 + " + "Ballpark geographic offset from WGS 84 to NAD83(2011)")); + + auto proj_5 = proj_as_proj_string(m_ctxt, P, PJ_PROJ_5, nullptr); + ASSERT_NE(proj_5, nullptr); + EXPECT_EQ(std::string(proj_5), + "+proj=pipeline " + "+step +inv +proj=utm +zone=11 +ellps=GRS80 " + "+step +proj=unitconvert +z_in=us-ft +z_out=m " + "+step +proj=vgridshift +grids=@foo.gtx +multiplier=1 " + "+step +proj=unitconvert +xy_in=rad +xy_out=deg " + "+step +proj=axisswap +order=2,1"); +} + } // namespace diff --git a/test/unit/test_operation.cpp b/test/unit/test_operation.cpp index 7d457dab..5e04bf23 100644 --- a/test/unit/test_operation.cpp +++ b/test/unit/test_operation.cpp @@ -7580,6 +7580,88 @@ TEST( // --------------------------------------------------------------------------- +TEST(operation, compoundCRS_of_vertCRS_with_geoid_model_to_geogCRS) { + auto authFactory = + AuthorityFactory::create(DatabaseContext::create(), "EPSG"); + auto ctxt = CoordinateOperationContext::create(authFactory, nullptr, 0.0); + ctxt->setSpatialCriterion( + CoordinateOperationContext::SpatialCriterion::PARTIAL_INTERSECTION); + ctxt->setGridAvailabilityUse( + CoordinateOperationContext::GridAvailabilityUse:: + IGNORE_GRID_AVAILABILITY); + auto wkt = + "COMPOUNDCRS[\"NAD83 / Pennsylvania South + NAVD88 height\",\n" + " PROJCRS[\"NAD83 / Pennsylvania South\",\n" + " BASEGEOGCRS[\"NAD83\",\n" + " DATUM[\"North American Datum 1983\",\n" + " ELLIPSOID[\"GRS 1980\",6378137,298.257222101,\n" + " LENGTHUNIT[\"metre\",1]]],\n" + " PRIMEM[\"Greenwich\",0,\n" + " ANGLEUNIT[\"degree\",0.0174532925199433]]],\n" + " CONVERSION[\"SPCS83 Pennsylvania South zone (meters)\",\n" + " METHOD[\"Lambert Conic Conformal (2SP)\",\n" + " ID[\"EPSG\",9802]],\n" + " PARAMETER[\"Latitude of false origin\",39.3333333333333,\n" + " ANGLEUNIT[\"degree\",0.0174532925199433],\n" + " ID[\"EPSG\",8821]],\n" + " PARAMETER[\"Longitude of false origin\",-77.75,\n" + " ANGLEUNIT[\"degree\",0.0174532925199433],\n" + " ID[\"EPSG\",8822]],\n" + " PARAMETER[\"Latitude of 1st standard " + "parallel\",40.9666666666667,\n" + " ANGLEUNIT[\"degree\",0.0174532925199433],\n" + " ID[\"EPSG\",8823]],\n" + " PARAMETER[\"Latitude of 2nd standard " + "parallel\",39.9333333333333,\n" + " ANGLEUNIT[\"degree\",0.0174532925199433],\n" + " ID[\"EPSG\",8824]],\n" + " PARAMETER[\"Easting at false origin\",600000,\n" + " LENGTHUNIT[\"metre\",1],\n" + " ID[\"EPSG\",8826]],\n" + " PARAMETER[\"Northing at false origin\",0,\n" + " LENGTHUNIT[\"metre\",1],\n" + " ID[\"EPSG\",8827]]],\n" + " CS[Cartesian,2],\n" + " AXIS[\"easting (X)\",east,\n" + " ORDER[1],\n" + " LENGTHUNIT[\"metre\",1]],\n" + " AXIS[\"northing (Y)\",north,\n" + " ORDER[2],\n" + " LENGTHUNIT[\"metre\",1]]],\n" + " VERTCRS[\"NAVD88 height\",\n" + " VDATUM[\"North American Vertical Datum 1988\"],\n" + " CS[vertical,1],\n" + " AXIS[\"gravity-related height (H)\",up,\n" + " LENGTHUNIT[\"metre\",1]],\n" + " GEOIDMODEL[\"GEOID12B\"]]]"; + auto srcObj = + createFromUserInput(wkt, authFactory->databaseContext(), false); + auto src = nn_dynamic_pointer_cast<CRS>(srcObj); + ASSERT_TRUE(src != nullptr); + auto dst = authFactory->createCoordinateReferenceSystem("4269"); // NAD83 + + auto list = CoordinateOperationFactory::create()->createOperations( + NN_NO_CHECK(src), dst, ctxt); + ASSERT_TRUE(!list.empty()); + EXPECT_EQ(list[0]->nameStr(), + "Inverse of SPCS83 Pennsylvania South zone (meters) + " + "Ballpark geographic offset from NAD83 to NAD83(2011) + " + "Inverse of NAD83(2011) to NAVD88 height (1) + " + "Ballpark geographic offset from NAD83(2011) to NAD83"); + auto op_proj = + list[0]->exportToPROJString(PROJStringFormatter::create().get()); + EXPECT_EQ(op_proj, + "+proj=pipeline " + "+step +inv +proj=lcc +lat_0=39.3333333333333 +lon_0=-77.75 " + "+lat_1=40.9666666666667 +lat_2=39.9333333333333 +x_0=600000 " + "+y_0=0 +ellps=GRS80 " + "+step +proj=vgridshift +grids=g2012bu0.gtx +multiplier=1 " + "+step +proj=unitconvert +xy_in=rad +xy_out=deg " + "+step +proj=axisswap +order=2,1"); +} + +// --------------------------------------------------------------------------- + TEST(operation, compoundCRS_from_WKT2_to_geogCRS_3D_context) { auto authFactory = AuthorityFactory::create(DatabaseContext::create(), "EPSG"); |
