diff options
| author | Even Rouault <even.rouault@spatialys.com> | 2020-10-16 18:26:09 +0200 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2020-10-16 18:26:09 +0200 |
| commit | 82b496fb32df0b6705159cd5c626aab20c8e9d39 (patch) | |
| tree | b6652073c9d66960e5c16d61055c53ffc3f8656f /src | |
| parent | 93508fbec18e192646f2890e1ceb86de4cc9fd35 (diff) | |
| parent | 686713479eb0b39feb5369b82647f96edf809b6c (diff) | |
| download | PROJ-82b496fb32df0b6705159cd5c626aab20c8e9d39.tar.gz PROJ-82b496fb32df0b6705159cd5c626aab20c8e9d39.zip | |
Merge pull request #2370 from rouault/epsg10
Update to EPSG 10.003 and make code base robust to dealing with WKT CRS with DatumEnsemble
Diffstat (limited to 'src')
| -rw-r--r-- | src/4D_api.cpp | 36 | ||||
| -rw-r--r-- | src/iso19111/c_api.cpp | 256 | ||||
| -rw-r--r-- | src/iso19111/coordinateoperation.cpp | 236 | ||||
| -rw-r--r-- | src/iso19111/crs.cpp | 249 | ||||
| -rw-r--r-- | src/iso19111/datum.cpp | 78 | ||||
| -rw-r--r-- | src/iso19111/factory.cpp | 598 | ||||
| -rw-r--r-- | src/proj.h | 17 | ||||
| -rw-r--r-- | src/proj_experimental.h | 4 |
8 files changed, 1026 insertions, 448 deletions
diff --git a/src/4D_api.cpp b/src/4D_api.cpp index 75fa6d04..3c6ab802 100644 --- a/src/4D_api.cpp +++ b/src/4D_api.cpp @@ -1069,28 +1069,18 @@ static PJ* create_operation_to_geog_crs(PJ_CONTEXT* ctx, const PJ* crs) { geodetic_crs_type == PJ_TYPE_GEOGRAPHIC_3D_CRS ) { auto datum = proj_crs_get_datum(ctx, geodetic_crs); - if( datum ) - { - auto cs = proj_create_ellipsoidal_2D_cs( - ctx, PJ_ELLPS2D_LONGITUDE_LATITUDE, nullptr, 0); - auto ellps = proj_get_ellipsoid(ctx, datum); - proj_destroy(datum); - double semi_major_metre = 0; - double inv_flattening = 0; - proj_ellipsoid_get_parameters(ctx, ellps, &semi_major_metre, - nullptr, nullptr, &inv_flattening); - auto temp = proj_create_geographic_crs( - ctx, "unnamed crs", "unnamed datum", - proj_get_name(ellps), - semi_major_metre, inv_flattening, - "Reference prime meridian", 0, nullptr, 0, - cs); - proj_destroy(ellps); - proj_destroy(cs); - proj_destroy(geodetic_crs); - geodetic_crs = temp; - geodetic_crs_type = proj_get_type(geodetic_crs); - } + auto datum_ensemble = proj_crs_get_datum_ensemble(ctx, geodetic_crs); + auto cs = proj_create_ellipsoidal_2D_cs( + ctx, PJ_ELLPS2D_LONGITUDE_LATITUDE, nullptr, 0); + auto temp = proj_create_geographic_crs_from_datum( + ctx, "unnamed crs", datum ? datum : datum_ensemble, + cs); + proj_destroy(datum); + proj_destroy(datum_ensemble); + proj_destroy(cs); + proj_destroy(geodetic_crs); + geodetic_crs = temp; + geodetic_crs_type = proj_get_type(geodetic_crs); } if( geodetic_crs_type != PJ_TYPE_GEOGRAPHIC_2D_CRS ) { @@ -1233,7 +1223,7 @@ std::vector<CoordOperation> pj_create_prepared_operations(PJ_CONTEXT *ctx, &west_lon, &south_lat, &east_lon, &north_lat, &areaName) ) { const bool isOffshore = - areaName && strstr(areaName, "offshore"); + areaName && strstr(areaName, "- offshore"); if( west_lon <= east_lon ) { op = add_coord_op_to_list(i, op, diff --git a/src/iso19111/c_api.cpp b/src/iso19111/c_api.cpp index 21a07729..cad76431 100644 --- a/src/iso19111/c_api.cpp +++ b/src/iso19111/c_api.cpp @@ -898,15 +898,23 @@ convertPJObjectTypeToObjectType(PJ_TYPE type, bool &valid) { break; case PJ_TYPE_GEODETIC_REFERENCE_FRAME: - case PJ_TYPE_DYNAMIC_GEODETIC_REFERENCE_FRAME: cppType = AuthorityFactory::ObjectType::GEODETIC_REFERENCE_FRAME; break; + case PJ_TYPE_DYNAMIC_GEODETIC_REFERENCE_FRAME: + cppType = + AuthorityFactory::ObjectType::DYNAMIC_GEODETIC_REFERENCE_FRAME; + break; + case PJ_TYPE_VERTICAL_REFERENCE_FRAME: - case PJ_TYPE_DYNAMIC_VERTICAL_REFERENCE_FRAME: cppType = AuthorityFactory::ObjectType::VERTICAL_REFERENCE_FRAME; break; + case PJ_TYPE_DYNAMIC_VERTICAL_REFERENCE_FRAME: + cppType = + AuthorityFactory::ObjectType::DYNAMIC_VERTICAL_REFERENCE_FRAME; + break; + case PJ_TYPE_DATUM_ENSEMBLE: cppType = AuthorityFactory::ObjectType::DATUM; break; @@ -3044,31 +3052,35 @@ PJ *proj_create_geographic_crs(PJ_CONTEXT *ctx, const char *crs_name, * * @param ctx PROJ context, or NULL for default context * @param crs_name Name of the GeographicCRS. Or NULL - * @param datum Datum. Must not be NULL. + * @param datum_or_datum_ensemble Datum or DatumEnsemble (DatumEnsemble possible + * since 7.2). Must not be NULL. * @param ellipsoidal_cs Coordinate system. Must not be NULL. * * @return Object of type GeographicCRS that must be unreferenced with * proj_destroy(), or NULL in case of error. */ PJ *proj_create_geographic_crs_from_datum(PJ_CONTEXT *ctx, const char *crs_name, - PJ *datum, PJ *ellipsoidal_cs) { + PJ *datum_or_datum_ensemble, + PJ *ellipsoidal_cs) { SANITIZE_CTX(ctx); - auto l_datum = - std::dynamic_pointer_cast<GeodeticReferenceFrame>(datum->iso_obj); - if (!l_datum) { + if (datum_or_datum_ensemble == nullptr) { proj_log_error(ctx, __FUNCTION__, - "datum is not a GeodeticReferenceFrame"); + "Missing input datum_or_datum_ensemble"); return nullptr; } + auto l_datum = std::dynamic_pointer_cast<GeodeticReferenceFrame>( + datum_or_datum_ensemble->iso_obj); + auto l_datum_ensemble = std::dynamic_pointer_cast<DatumEnsemble>( + datum_or_datum_ensemble->iso_obj); auto cs = std::dynamic_pointer_cast<EllipsoidalCS>(ellipsoidal_cs->iso_obj); if (!cs) { return nullptr; } try { auto geogCRS = - GeographicCRS::create(createPropertyMapName(crs_name), - NN_NO_CHECK(l_datum), NN_NO_CHECK(cs)); + GeographicCRS::create(createPropertyMapName(crs_name), l_datum, + l_datum_ensemble, NN_NO_CHECK(cs)); return pj_obj_create(ctx, geogCRS); } catch (const std::exception &e) { proj_log_error(ctx, __FUNCTION__, e.what()); @@ -3141,7 +3153,8 @@ PJ *proj_create_geocentric_crs( * * @param ctx PROJ context, or NULL for default context * @param crs_name Name of the GeographicCRS. Or NULL - * @param datum Datum. Must not be NULL. + * @param datum_or_datum_ensemble Datum or DatumEnsemble (DatumEnsemble possible + * since 7.2). Must not be 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 @@ -3150,22 +3163,24 @@ PJ *proj_create_geocentric_crs( * proj_destroy(), or NULL in case of error. */ PJ *proj_create_geocentric_crs_from_datum(PJ_CONTEXT *ctx, const char *crs_name, - const PJ *datum, + const PJ *datum_or_datum_ensemble, const char *linear_units, double linear_units_conv) { SANITIZE_CTX(ctx); + if (datum_or_datum_ensemble == nullptr) { + proj_log_error(ctx, __FUNCTION__, + "Missing input datum_or_datum_ensemble"); + return nullptr; + } + auto l_datum = std::dynamic_pointer_cast<GeodeticReferenceFrame>( + datum_or_datum_ensemble->iso_obj); + auto l_datum_ensemble = std::dynamic_pointer_cast<DatumEnsemble>( + datum_or_datum_ensemble->iso_obj); try { const UnitOfMeasure linearUnit( createLinearUnit(linear_units, linear_units_conv)); - auto l_datum = - std::dynamic_pointer_cast<GeodeticReferenceFrame>(datum->iso_obj); - if (!l_datum) { - proj_log_error(ctx, __FUNCTION__, - "datum is not a GeodeticReferenceFrame"); - return nullptr; - } auto geodCRS = GeodeticCRS::create( - createPropertyMapName(crs_name), NN_NO_CHECK(l_datum), + createPropertyMapName(crs_name), l_datum, l_datum_ensemble, cs::CartesianCS::createGeocentric(linearUnit)); return pj_obj_create(ctx, geodCRS); } catch (const std::exception &e) { @@ -7981,6 +7996,207 @@ PJ *proj_crs_get_datum(PJ_CONTEXT *ctx, const PJ *crs) { // --------------------------------------------------------------------------- +/** \brief Returns the datum ensemble of a SingleCRS. + * + * 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 Object of type SingleCRS (must not be NULL) + * @return Object that must be unreferenced with proj_destroy(), or NULL + * in case of error (or if there is no datum ensemble) + * + * @since 7.2 + */ +PJ *proj_crs_get_datum_ensemble(PJ_CONTEXT *ctx, const PJ *crs) { + SANITIZE_CTX(ctx); + if (!crs) { + proj_log_error(ctx, __FUNCTION__, "missing required input"); + return nullptr; + } + auto l_crs = dynamic_cast<const SingleCRS *>(crs->iso_obj.get()); + if (!l_crs) { + proj_log_error(ctx, __FUNCTION__, "Object is not a SingleCRS"); + return nullptr; + } + const auto &datumEnsemble = l_crs->datumEnsemble(); + if (!datumEnsemble) { + return nullptr; + } + return pj_obj_create(ctx, NN_NO_CHECK(datumEnsemble)); +} + +// --------------------------------------------------------------------------- + +/** \brief Returns the number of members of a datum ensemble. + * + * @param ctx PROJ context, or NULL for default context + * @param datum_ensemble Object of type DatumEnsemble (must not be NULL) + * + * @since 7.2 + */ +int proj_datum_ensemble_get_member_count(PJ_CONTEXT *ctx, + const PJ *datum_ensemble) { + SANITIZE_CTX(ctx); + if (!datum_ensemble) { + proj_log_error(ctx, __FUNCTION__, "missing required input"); + return 0; + } + auto l_datum_ensemble = + dynamic_cast<const DatumEnsemble *>(datum_ensemble->iso_obj.get()); + if (!l_datum_ensemble) { + proj_log_error(ctx, __FUNCTION__, "Object is not a DatumEnsemble"); + return 0; + } + return static_cast<int>(l_datum_ensemble->datums().size()); +} + +// --------------------------------------------------------------------------- + +/** \brief Returns the positional accuracy of the datum ensemble. + * + * @param ctx PROJ context, or NULL for default context + * @param datum_ensemble Object of type DatumEnsemble (must not be NULL) + * @return the accuracy, or -1 in case of error. + * + * @since 7.2 + */ +double proj_datum_ensemble_get_accuracy(PJ_CONTEXT *ctx, + const PJ *datum_ensemble) { + SANITIZE_CTX(ctx); + if (!datum_ensemble) { + proj_log_error(ctx, __FUNCTION__, "missing required input"); + return -1; + } + auto l_datum_ensemble = + dynamic_cast<const DatumEnsemble *>(datum_ensemble->iso_obj.get()); + if (!l_datum_ensemble) { + proj_log_error(ctx, __FUNCTION__, "Object is not a DatumEnsemble"); + return -1; + } + const auto &accuracy = l_datum_ensemble->positionalAccuracy(); + try { + return c_locale_stod(accuracy->value()); + } catch (const std::exception &) { + } + return -1; +} + +// --------------------------------------------------------------------------- + +/** \brief Returns a member from a datum ensemble. + * + * 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 datum_ensemble Object of type DatumEnsemble (must not be NULL) + * @param member_index Index of the datum member to extract (between 0 and + * proj_datum_ensemble_get_member_count()-1) + * @return Object that must be unreferenced with proj_destroy(), or NULL + * in case of error (or if there is no datum ensemble) + * + * @since 7.2 + */ +PJ *proj_datum_ensemble_get_member(PJ_CONTEXT *ctx, const PJ *datum_ensemble, + int member_index) { + SANITIZE_CTX(ctx); + if (!datum_ensemble) { + proj_log_error(ctx, __FUNCTION__, "missing required input"); + return nullptr; + } + auto l_datum_ensemble = + dynamic_cast<const DatumEnsemble *>(datum_ensemble->iso_obj.get()); + if (!l_datum_ensemble) { + proj_log_error(ctx, __FUNCTION__, "Object is not a DatumEnsemble"); + return nullptr; + } + if (member_index < 0 || + member_index >= static_cast<int>(l_datum_ensemble->datums().size())) { + proj_log_error(ctx, __FUNCTION__, "Invalid member_index"); + return nullptr; + } + return pj_obj_create(ctx, l_datum_ensemble->datums()[member_index]); +} + +// --------------------------------------------------------------------------- + +/** \brief Returns a datum for a SingleCRS. + * + * If the SingleCRS has a datum, then this datum is returned. + * Otherwise, the SingleCRS has a datum ensemble, and this datum ensemble is + * returned as a regular datum instead of a datum ensemble. + * + * 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 Object of type SingleCRS (must not be NULL) + * @return Object that must be unreferenced with proj_destroy(), or NULL + * in case of error (or if there is no datum) + * + * @since 7.2 + */ +PJ *proj_crs_get_datum_forced(PJ_CONTEXT *ctx, const PJ *crs) { + SANITIZE_CTX(ctx); + if (!crs) { + proj_log_error(ctx, __FUNCTION__, "missing required input"); + return nullptr; + } + auto l_crs = dynamic_cast<const SingleCRS *>(crs->iso_obj.get()); + if (!l_crs) { + proj_log_error(ctx, __FUNCTION__, "Object is not a SingleCRS"); + return nullptr; + } + const auto &datum = l_crs->datum(); + if (datum) { + return pj_obj_create(ctx, NN_NO_CHECK(datum)); + } + const auto &datumEnsemble = l_crs->datumEnsemble(); + assert(datumEnsemble); + auto dbContext = getDBcontextNoException(ctx, __FUNCTION__); + return pj_obj_create(ctx, datumEnsemble->asDatum(dbContext)); +} + +// --------------------------------------------------------------------------- + +/** \brief Returns the frame reference epoch of a dynamic geodetic or vertical + * reference frame. + * + * @param ctx PROJ context, or NULL for default context + * @param datum Object of type DynamicGeodeticReferenceFrame or + * DynamicVerticalReferenceFrame (must not be NULL) + * @return the frame reference epoch as decimal year, or -1 in case of error. + * + * @since 7.2 + */ +double proj_dynamic_datum_get_frame_reference_epoch(PJ_CONTEXT *ctx, + const PJ *datum) { + SANITIZE_CTX(ctx); + if (!datum) { + proj_log_error(ctx, __FUNCTION__, "missing required input"); + return -1; + } + auto dgrf = dynamic_cast<const DynamicGeodeticReferenceFrame *>( + datum->iso_obj.get()); + auto dvrf = dynamic_cast<const DynamicVerticalReferenceFrame *>( + datum->iso_obj.get()); + if (!dgrf && !dvrf) { + proj_log_error(ctx, __FUNCTION__, "Object is not a " + "DynamicGeodeticReferenceFrame or " + "DynamicVerticalReferenceFrame"); + return -1; + } + const auto &frameReferenceEpoch = + dgrf ? dgrf->frameReferenceEpoch() : dvrf->frameReferenceEpoch(); + return frameReferenceEpoch.value(); +} + +// --------------------------------------------------------------------------- + /** \brief Returns the coordinate system of a SingleCRS. * * The returned object must be unreferenced with proj_destroy() after diff --git a/src/iso19111/coordinateoperation.cpp b/src/iso19111/coordinateoperation.cpp index 6fcf4d30..184ff435 100644 --- a/src/iso19111/coordinateoperation.cpp +++ b/src/iso19111/coordinateoperation.cpp @@ -11174,7 +11174,8 @@ struct CoordinateOperationFactory::Private { static std::vector<CoordinateOperationNNPtr> createOperationsGeogToGeog( std::vector<CoordinateOperationNNPtr> &res, const crs::CRSNNPtr &sourceCRS, const crs::CRSNNPtr &targetCRS, - const crs::GeographicCRS *geogSrc, const crs::GeographicCRS *geogDst); + Private::Context &context, const crs::GeographicCRS *geogSrc, + const crs::GeographicCRS *geogDst); static void createOperationsWithDatumPivot( std::vector<CoordinateOperationNNPtr> &res, @@ -12338,16 +12339,17 @@ CoordinateOperationFactory::Private::findsOpsInRegistryWithIntermediate( //! @cond Doxygen_Suppress static TransformationNNPtr createBallparkGeographicOffset(const crs::CRSNNPtr &sourceCRS, - const crs::CRSNNPtr &targetCRS) { + const crs::CRSNNPtr &targetCRS, + const io::DatabaseContextPtr &dbContext) { const crs::GeographicCRS *geogSrc = dynamic_cast<const crs::GeographicCRS *>(sourceCRS.get()); const crs::GeographicCRS *geogDst = dynamic_cast<const crs::GeographicCRS *>(targetCRS.get()); - const bool isSameDatum = - geogSrc && geogDst && geogSrc->datum() && geogDst->datum() && - geogSrc->datum()->_isEquivalentTo( - geogDst->datum().get(), util::IComparable::Criterion::EQUIVALENT); + const bool isSameDatum = geogSrc && geogDst && + geogSrc->datumNonNull(dbContext)->_isEquivalentTo( + geogDst->datumNonNull(dbContext).get(), + util::IComparable::Criterion::EQUIVALENT); auto name = buildOpName(isSameDatum ? NULL_GEOGRAPHIC_OFFSET : BALLPARK_GEOGRAPHIC_OFFSET, @@ -12692,8 +12694,8 @@ static CoordinateOperationNNPtr createHorizVerticalHorizPROJBased( std::vector<CoordinateOperationNNPtr> CoordinateOperationFactory::Private::createOperationsGeogToGeog( std::vector<CoordinateOperationNNPtr> &res, const crs::CRSNNPtr &sourceCRS, - const crs::CRSNNPtr &targetCRS, const crs::GeographicCRS *geogSrc, - const crs::GeographicCRS *geogDst) { + const crs::CRSNNPtr &targetCRS, Private::Context &context, + const crs::GeographicCRS *geogSrc, const crs::GeographicCRS *geogDst) { assert(sourceCRS.get() == geogSrc); assert(targetCRS.get() == geogDst); @@ -12723,10 +12725,13 @@ CoordinateOperationFactory::Private::createOperationsGeogToGeog( std::string name(buildTransfName(geogSrc->nameStr(), geogDst->nameStr())); - const bool sameDatum = - geogSrc->datum() != nullptr && geogDst->datum() != nullptr && - geogSrc->datum()->_isEquivalentTo( - geogDst->datum().get(), util::IComparable::Criterion::EQUIVALENT); + const auto &authFactory = context.context->getAuthorityFactory(); + const auto dbContext = + authFactory ? authFactory->databaseContext().as_nullable() : nullptr; + + const bool sameDatum = geogSrc->datumNonNull(dbContext)->_isEquivalentTo( + geogDst->datumNonNull(dbContext).get(), + util::IComparable::Criterion::EQUIVALENT); // Do the CRS differ by their axis order ? bool axisReversal2D = false; @@ -12821,7 +12826,7 @@ CoordinateOperationFactory::Private::createOperationsGeogToGeog( datum, dstCS)); steps.emplace_back( - createBallparkGeographicOffset(sourceCRS, interm_crs)); + createBallparkGeographicOffset(sourceCRS, interm_crs, dbContext)); steps.emplace_back(Transformation::createLongitudeRotation( util::PropertyMap() @@ -12855,11 +12860,11 @@ CoordinateOperationFactory::Private::createOperationsGeogToGeog( .set(common::ObjectUsage::DOMAIN_OF_VALIDITY_KEY, metadata::Extent::WORLD), sourceCRS, interm_crs, offset_pm)); - steps.emplace_back( - createBallparkGeographicOffset(interm_crs, targetCRS)); + steps.emplace_back(createBallparkGeographicOffset( + interm_crs, targetCRS, dbContext)); } else { - steps.emplace_back( - createBallparkGeographicOffset(sourceCRS, targetCRS)); + steps.emplace_back(createBallparkGeographicOffset( + sourceCRS, targetCRS, dbContext)); } } @@ -13030,10 +13035,14 @@ void CoordinateOperationFactory::Private::createOperationsWithDatumPivot( CreateOperationsWithDatumPivotAntiRecursion guard(context); const auto &authFactory = context.context->getAuthorityFactory(); + const auto &dbContext = authFactory->databaseContext(); + const auto candidatesSrcGeod(findCandidateGeodCRSForDatum( - authFactory, geodSrc, geodSrc->datum().get())); + authFactory, geodSrc, + geodSrc->datumNonNull(dbContext.as_nullable()).get())); const auto candidatesDstGeod(findCandidateGeodCRSForDatum( - authFactory, geodDst, geodDst->datum().get())); + authFactory, geodDst, + geodDst->datumNonNull(dbContext.as_nullable()).get())); const bool sourceAndTargetAre3D = geodSrc->coordinateSystem()->axisList().size() == 3 && @@ -13545,16 +13554,16 @@ bool CoordinateOperationFactory::Private::createOperationsFromDatabase( } } else if (geodSrc && geodDst) { - const auto &srcDatum = geodSrc->datum(); - const auto &dstDatum = geodDst->datum(); - sameGeodeticDatum = - srcDatum != nullptr && dstDatum != nullptr && - srcDatum->_isEquivalentTo(dstDatum.get(), - util::IComparable::Criterion::EQUIVALENT); + const auto &authFactory = context.context->getAuthorityFactory(); + const auto dbContext = authFactory->databaseContext().as_nullable(); + + const auto srcDatum = geodSrc->datumNonNull(dbContext); + const auto dstDatum = geodDst->datumNonNull(dbContext); + sameGeodeticDatum = srcDatum->_isEquivalentTo( + dstDatum.get(), util::IComparable::Criterion::EQUIVALENT); if (res.empty() && !sameGeodeticDatum && - !context.inCreateOperationsWithDatumPivotAntiRecursion && - srcDatum != nullptr && dstDatum != nullptr) { + !context.inCreateOperationsWithDatumPivotAntiRecursion) { // If we still didn't find a transformation, and that the source // and target are GeodeticCRS, then go through their underlying // datum to find potential transformations between other @@ -13860,8 +13869,10 @@ std::vector<CoordinateOperationNNPtr> CoordinateOperationFactory::Private:: }; AntiRecursionGuard guard(context); const auto &authFactory = context.context->getAuthorityFactory(); - auto candidatesVert = - findCandidateVertCRSForDatum(authFactory, vertDst->datum().get()); + const auto dbContext = authFactory->databaseContext().as_nullable(); + + auto candidatesVert = findCandidateVertCRSForDatum( + authFactory, vertDst->datumNonNull(dbContext).get()); for (const auto &candidateVert : candidatesVert) { auto resTmp = createOperations(sourceCRS, candidateVert, context); if (!resTmp.empty()) { @@ -13964,11 +13975,14 @@ void CoordinateOperationFactory::Private:: const crs::GeographicCRS *geogSrcIn, const crs::VerticalCRS *vertDstIn, const crs::CRSNNPtr &targetCRSIn) { if (res.empty() && geogSrcIn && vertDstIn && - geogSrcIn->coordinateSystem()->axisList().size() == 3 && - geogSrcIn->datum()) { + geogSrcIn->coordinateSystem()->axisList().size() == 3) { const auto &authFactory = context.context->getAuthorityFactory(); + const auto dbContext = + authFactory ? authFactory->databaseContext().as_nullable() + : nullptr; const auto candidatesSrcGeod(findCandidateGeodCRSForDatum( - authFactory, geogSrcIn, geogSrcIn->datum().get())); + authFactory, geogSrcIn, + geogSrcIn->datumNonNull(dbContext).get())); for (const auto &candidate : candidatesSrcGeod) { auto geogCandidate = util::nn_dynamic_pointer_cast<crs::GeographicCRS>( @@ -14030,7 +14044,8 @@ void CoordinateOperationFactory::Private::createOperationsGeodToGeod( auto geogDst = dynamic_cast<const crs::GeographicCRS *>(geodDst); if (geogSrc && geogDst) { - createOperationsGeogToGeog(res, sourceCRS, targetCRS, geogSrc, geogDst); + createOperationsGeogToGeog(res, sourceCRS, targetCRS, context, geogSrc, + geogDst); return; } @@ -14038,14 +14053,23 @@ void CoordinateOperationFactory::Private::createOperationsGeodToGeod( const bool isSrcGeographic = geogSrc != nullptr; const bool isTargetGeocentric = geodDst->isGeocentric(); const bool isTargetGeographic = geogDst != nullptr; + + const auto IsSameDatum = [&context, &geodSrc, &geodDst]() { + const auto &authFactory = context.context->getAuthorityFactory(); + const auto dbContext = + authFactory ? authFactory->databaseContext().as_nullable() + : nullptr; + + return geodSrc->datumNonNull(dbContext)->_isEquivalentTo( + geodDst->datumNonNull(dbContext).get(), + util::IComparable::Criterion::EQUIVALENT); + }; + if (((isSrcGeocentric && isTargetGeographic) || - (isSrcGeographic && isTargetGeocentric)) && - geodSrc->datum() != nullptr && geodDst->datum() != nullptr) { + (isSrcGeographic && isTargetGeocentric))) { // Same datum ? - if (geodSrc->datum()->_isEquivalentTo( - geodDst->datum().get(), - util::IComparable::Criterion::EQUIVALENT)) { + if (IsSameDatum()) { res.emplace_back( Conversion::createGeographicGeocentric(sourceCRS, targetCRS)); } else if (isSrcGeocentric && geogDst) { @@ -14057,7 +14081,7 @@ void CoordinateOperationFactory::Private::createOperationsGeodToGeod( common::IdentifiedObject::NAME_KEY, interm_crs_name), geogDst), - NN_NO_CHECK(geogDst->datum()), + geogDst->datum(), geogDst->datumEnsemble(), NN_CHECK_ASSERT( util::nn_dynamic_pointer_cast<cs::CartesianCS>( geodSrc->coordinateSystem())))); @@ -14082,10 +14106,7 @@ void CoordinateOperationFactory::Private::createOperationsGeodToGeod( if (isSrcGeocentric && isTargetGeocentric) { if (sourceCRS->_isEquivalentTo( targetCRS.get(), util::IComparable::Criterion::EQUIVALENT) || - (geodSrc->datum() != nullptr && geodDst->datum() != nullptr && - geodSrc->datum()->_isEquivalentTo( - geodDst->datum().get(), - util::IComparable::Criterion::EQUIVALENT))) { + IsSameDatum()) { std::string name(NULL_GEOCENTRIC_TRANSLATION); name += " from "; name += sourceCRS->nameStr(); @@ -14151,14 +14172,19 @@ void CoordinateOperationFactory::Private::createOperationsBoundToGeog( const auto &hubSrc = boundSrc->hubCRS(); auto hubSrcGeog = dynamic_cast<const crs::GeographicCRS *>(hubSrc.get()); auto geogCRSOfBaseOfBoundSrc = boundSrc->baseCRS()->extractGeographicCRS(); - auto geogDstDatum = geogDst->datum(); + + const auto &authFactory = context.context->getAuthorityFactory(); + const auto dbContext = + authFactory ? authFactory->databaseContext().as_nullable() : nullptr; + + const auto geogDstDatum = geogDst->datumNonNull(dbContext); // If the underlying datum of the source is the same as the target, do // not consider the boundCRS at all, but just its base - if (geogCRSOfBaseOfBoundSrc && geogDstDatum) { - auto geogCRSOfBaseOfBoundSrcDatum = geogCRSOfBaseOfBoundSrc->datum(); - if (geogCRSOfBaseOfBoundSrcDatum && - geogCRSOfBaseOfBoundSrcDatum->_isEquivalentTo( + if (geogCRSOfBaseOfBoundSrc) { + auto geogCRSOfBaseOfBoundSrcDatum = + geogCRSOfBaseOfBoundSrc->datumNonNull(dbContext); + if (geogCRSOfBaseOfBoundSrcDatum->_isEquivalentTo( geogDstDatum.get(), util::IComparable::Criterion::EQUIVALENT)) { res = createOperations(boundSrc->baseCRS(), targetCRS, context); return; @@ -14170,7 +14196,7 @@ void CoordinateOperationFactory::Private::createOperationsBoundToGeog( if (hubSrcGeog && geogCRSOfBaseOfBoundSrc && (hubSrcGeog->_isEquivalentTo( geogDst, util::IComparable::Criterion::EQUIVALENT) || - hubSrcGeog->is2DPartOf3D(NN_NO_CHECK(geogDst)))) { + hubSrcGeog->is2DPartOf3D(NN_NO_CHECK(geogDst), dbContext))) { triedBoundCrsToGeogCRSSameAsHubCRS = true; CoordinateOperationPtr opIntermediate; @@ -14223,9 +14249,8 @@ void CoordinateOperationFactory::Private::createOperationsBoundToGeog( } } // If the datum are equivalent, this is also fine - } else if (geogCRSOfBaseOfBoundSrc && hubSrcGeog && hubSrcGeog->datum() && - geogDstDatum && - hubSrcGeog->datum()->_isEquivalentTo( + } else if (geogCRSOfBaseOfBoundSrc && hubSrcGeog && + hubSrcGeog->datumNonNull(dbContext)->_isEquivalentTo( geogDstDatum.get(), util::IComparable::Criterion::EQUIVALENT)) { auto opsFirst = createOperations( @@ -14268,12 +14293,11 @@ void CoordinateOperationFactory::Private::createOperationsBoundToGeog( // Case of "+proj=latlong +ellps=clrk66 // +nadgrids=ntv1_can.dat,conus" // to "+proj=latlong +datum=NAD83" - } else if (geogCRSOfBaseOfBoundSrc && hubSrcGeog && hubSrcGeog->datum() && - geogDstDatum && + } else if (geogCRSOfBaseOfBoundSrc && hubSrcGeog && geogCRSOfBaseOfBoundSrc->ellipsoid()->_isEquivalentTo( datum::Ellipsoid::CLARKE_1866.get(), util::IComparable::Criterion::EQUIVALENT) && - hubSrcGeog->datum()->_isEquivalentTo( + hubSrcGeog->datumNonNull(dbContext)->_isEquivalentTo( datum::GeodeticReferenceFrame::EPSG_6326.get(), util::IComparable::Criterion::EQUIVALENT) && geogDstDatum->_isEquivalentTo( @@ -14410,8 +14434,7 @@ void CoordinateOperationFactory::Private::createOperationsBoundToGeog( common::IdentifiedObject::NAME_KEY, "Change of vertical unit"), common::Scale(heightDepthReversal ? -factor : factor)); - auto dbContext = context.context->getAuthorityFactory() - ->databaseContext(); + conv->setCRSs( hubSrc, hubSrc->demoteTo2D(std::string(), dbContext) @@ -14492,18 +14515,20 @@ void CoordinateOperationFactory::Private::createOperationsBoundToVert( void CoordinateOperationFactory::Private::createOperationsVertToVert( const crs::CRSNNPtr &sourceCRS, const crs::CRSNNPtr &targetCRS, - Private::Context & /*context*/, const crs::VerticalCRS *vertSrc, + Private::Context &context, const crs::VerticalCRS *vertSrc, const crs::VerticalCRS *vertDst, std::vector<CoordinateOperationNNPtr> &res) { ENTER_FUNCTION(); - const auto &srcDatum = vertSrc->datum(); - const auto &dstDatum = vertDst->datum(); - const bool equivalentVDatum = - (srcDatum && dstDatum && - srcDatum->_isEquivalentTo(dstDatum.get(), - util::IComparable::Criterion::EQUIVALENT)); + const auto &authFactory = context.context->getAuthorityFactory(); + const auto dbContext = + authFactory ? authFactory->databaseContext().as_nullable() : nullptr; + + const auto srcDatum = vertSrc->datumNonNull(dbContext); + const auto dstDatum = vertDst->datumNonNull(dbContext); + const bool equivalentVDatum = srcDatum->_isEquivalentTo( + dstDatum.get(), util::IComparable::Criterion::EQUIVALENT); const auto &srcAxis = vertSrc->coordinateSystem()->axisList()[0]; const double convSrc = srcAxis->unit().conversionToSI(); @@ -14673,10 +14698,15 @@ void CoordinateOperationFactory::Private::createOperationsBoundToBound( auto baseOfBoundDstAsVertCRS = dynamic_cast<crs::VerticalCRS *>(boundDst->baseCRS().get()); if (baseOfBoundSrcAsVertCRS && baseOfBoundDstAsVertCRS) { - const auto datumSrc = baseOfBoundSrcAsVertCRS->datum(); - const auto datumDst = baseOfBoundDstAsVertCRS->datum(); - if (datumSrc && datumDst && - datumSrc->nameStr() == datumDst->nameStr() && + + const auto &authFactory = context.context->getAuthorityFactory(); + const auto dbContext = + authFactory ? authFactory->databaseContext().as_nullable() + : nullptr; + + const auto datumSrc = baseOfBoundSrcAsVertCRS->datumNonNull(dbContext); + const auto datumDst = baseOfBoundDstAsVertCRS->datumNonNull(dbContext); + if (datumSrc->nameStr() == datumDst->nameStr() && (datumSrc->nameStr() != "unknown" || boundSrc->transformation()->_isEquivalentTo( boundDst->transformation().get(), @@ -14946,7 +14976,7 @@ void CoordinateOperationFactory::Private::createOperationsCompoundToGeog( if (!foundRegisteredTransformWithAllGridsAvailable && srcGeogCRS && !srcGeogCRS->_isEquivalentTo( geogDst, util::IComparable::Criterion::EQUIVALENT) && - !srcGeogCRS->is2DPartOf3D(NN_NO_CHECK(geogDst))) { + !srcGeogCRS->is2DPartOf3D(NN_NO_CHECK(geogDst), dbContext)) { auto verticalTransformsTmp = createOperations( componentsSrc[1], NN_NO_CHECK(srcGeogCRS) @@ -15128,32 +15158,29 @@ void CoordinateOperationFactory::Private::createOperationsToGeod( const crs::CRSNNPtr &sourceCRS, const crs::CRSNNPtr &targetCRS, Private::Context &context, const crs::GeodeticCRS *geodDst, std::vector<CoordinateOperationNNPtr> &res) { - auto datum = geodDst->datum(); - if (datum) { - auto cs = cs::EllipsoidalCS::createLatitudeLongitudeEllipsoidalHeight( - common::UnitOfMeasure::DEGREE, common::UnitOfMeasure::METRE); - auto intermGeog3DCRS = - util::nn_static_pointer_cast<crs::CRS>(crs::GeographicCRS::create( - util::PropertyMap() - .set(common::IdentifiedObject::NAME_KEY, geodDst->nameStr()) - .set(common::ObjectUsage::DOMAIN_OF_VALIDITY_KEY, - metadata::Extent::WORLD), - NN_NO_CHECK(datum), cs)); - auto sourceToGeog3DOps = - createOperations(sourceCRS, intermGeog3DCRS, context); - auto geog3DToTargetOps = - createOperations(intermGeog3DCRS, targetCRS, context); - if (!geog3DToTargetOps.empty()) { - for (const auto &op : sourceToGeog3DOps) { - auto newOp = op->shallowClone(); - setCRSs(newOp.get(), sourceCRS, intermGeog3DCRS); - try { - res.emplace_back( - ConcatenatedOperation::createComputeMetadata( - {newOp, geog3DToTargetOps.front()}, - disallowEmptyIntersection)); - } catch (const InvalidOperationEmptyIntersection &) { - } + + auto cs = cs::EllipsoidalCS::createLatitudeLongitudeEllipsoidalHeight( + common::UnitOfMeasure::DEGREE, common::UnitOfMeasure::METRE); + auto intermGeog3DCRS = + util::nn_static_pointer_cast<crs::CRS>(crs::GeographicCRS::create( + util::PropertyMap() + .set(common::IdentifiedObject::NAME_KEY, geodDst->nameStr()) + .set(common::ObjectUsage::DOMAIN_OF_VALIDITY_KEY, + metadata::Extent::WORLD), + geodDst->datum(), geodDst->datumEnsemble(), cs)); + auto sourceToGeog3DOps = + createOperations(sourceCRS, intermGeog3DCRS, context); + auto geog3DToTargetOps = + createOperations(intermGeog3DCRS, targetCRS, context); + if (!geog3DToTargetOps.empty()) { + for (const auto &op : sourceToGeog3DOps) { + auto newOp = op->shallowClone(); + setCRSs(newOp.get(), sourceCRS, intermGeog3DCRS); + try { + res.emplace_back(ConcatenatedOperation::createComputeMetadata( + {newOp, geog3DToTargetOps.front()}, + disallowEmptyIntersection)); + } catch (const InvalidOperationEmptyIntersection &) { } } } @@ -15322,6 +15349,10 @@ void CoordinateOperationFactory::Private::createOperationsBoundToCompound( const crs::CompoundCRS *compoundDst, std::vector<CoordinateOperationNNPtr> &res) { + const auto &authFactory = context.context->getAuthorityFactory(); + const auto dbContext = + authFactory ? authFactory->databaseContext().as_nullable() : nullptr; + const auto &componentsDst = compoundDst->componentReferenceSystems(); if (!componentsDst.empty()) { auto compDst0BoundCrs = @@ -15333,13 +15364,11 @@ void CoordinateOperationFactory::Private::createOperationsBoundToCompound( dynamic_cast<crs::GeographicCRS *>( compDst0BoundCrs->hubCRS().get()); if (boundSrcHubAsGeogCRS && compDst0BoundCrsHubAsGeogCRS) { - const auto &boundSrcHubAsGeogCRSDatum = - boundSrcHubAsGeogCRS->datum(); - const auto &compDst0BoundCrsHubAsGeogCRSDatum = - compDst0BoundCrsHubAsGeogCRS->datum(); - if (boundSrcHubAsGeogCRSDatum && - compDst0BoundCrsHubAsGeogCRSDatum && - boundSrcHubAsGeogCRSDatum->_isEquivalentTo( + const auto boundSrcHubAsGeogCRSDatum = + boundSrcHubAsGeogCRS->datumNonNull(dbContext); + const auto compDst0BoundCrsHubAsGeogCRSDatum = + compDst0BoundCrsHubAsGeogCRS->datumNonNull(dbContext); + if (boundSrcHubAsGeogCRSDatum->_isEquivalentTo( compDst0BoundCrsHubAsGeogCRSDatum.get())) { auto cs = cs::EllipsoidalCS:: createLatitudeLongitudeEllipsoidalHeight( @@ -15352,7 +15381,8 @@ void CoordinateOperationFactory::Private::createOperationsBoundToCompound( boundSrcHubAsGeogCRS->nameStr()) .set(common::ObjectUsage::DOMAIN_OF_VALIDITY_KEY, metadata::Extent::WORLD), - NN_NO_CHECK(boundSrcHubAsGeogCRSDatum), cs)); + boundSrcHubAsGeogCRS->datum(), + boundSrcHubAsGeogCRS->datumEnsemble(), cs)); auto sourceToGeog3DOps = createOperations(sourceCRS, intermGeog3DCRS, context); auto geog3DToTargetOps = diff --git a/src/iso19111/crs.cpp b/src/iso19111/crs.cpp index e9082473..cea6476f 100644 --- a/src/iso19111/crs.cpp +++ b/src/iso19111/crs.cpp @@ -1022,7 +1022,8 @@ CRSNNPtr CRS::promoteTo3D(const std::string &newName, if (firstResAxisList[2]->_isEquivalentTo( verticalAxisIfNotAlreadyPresent.get(), util::IComparable::Criterion::EQUIVALENT) && - geogCRS->is2DPartOf3D(NN_NO_CHECK(firstResGeog))) { + geogCRS->is2DPartOf3D(NN_NO_CHECK(firstResGeog), + dbContext)) { return NN_NO_CHECK( util::nn_dynamic_pointer_cast<CRS>(firstRes)); } @@ -1202,6 +1203,18 @@ const datum::DatumEnsemblePtr &SingleCRS::datumEnsemble() PROJ_PURE_DEFN { // --------------------------------------------------------------------------- +//! @cond Doxygen_Suppress +/** \brief Return the real datum or a synthetized one if a datumEnsemble. + */ +const datum::DatumNNPtr +SingleCRS::datumNonNull(const io::DatabaseContextPtr &dbContext) const { + return d->datum ? NN_NO_CHECK(d->datum) + : d->datumEnsemble->asDatum(dbContext); +} +//! @endcond + +// --------------------------------------------------------------------------- + /** \brief Return the cs::CoordinateSystem associated with the CRS. * * @return a CoordinateSystem. @@ -1221,20 +1234,41 @@ bool SingleCRS::baseIsEquivalentTo( !ObjectUsage::_isEquivalentTo(other, criterion, dbContext))) { return false; } - const auto &thisDatum = d->datum; - const auto &otherDatum = otherSingleCRS->d->datum; - if (thisDatum) { - if (!thisDatum->_isEquivalentTo(otherDatum.get(), criterion, - dbContext)) { - return false; + + if (criterion == util::IComparable::Criterion::STRICT) { + const auto &thisDatum = d->datum; + const auto &otherDatum = otherSingleCRS->d->datum; + if (thisDatum) { + if (!thisDatum->_isEquivalentTo(otherDatum.get(), criterion, + dbContext)) { + return false; + } + } else { + if (otherDatum) { + return false; + } + } + + const auto &thisDatumEnsemble = d->datumEnsemble; + const auto &otherDatumEnsemble = otherSingleCRS->d->datumEnsemble; + if (thisDatumEnsemble) { + if (!thisDatumEnsemble->_isEquivalentTo(otherDatumEnsemble.get(), + criterion, dbContext)) { + return false; + } + } else { + if (otherDatumEnsemble) { + return false; + } } } else { - if (otherDatum) { + if (!datumNonNull(dbContext)->_isEquivalentTo( + otherSingleCRS->datumNonNull(dbContext).get(), criterion, + dbContext)) { return false; } } - // TODO test DatumEnsemble return d->coordinateSystem->_isEquivalentTo( otherSingleCRS->d->coordinateSystem.get(), criterion, dbContext) && @@ -1355,6 +1389,21 @@ const datum::GeodeticReferenceFramePtr &GeodeticCRS::datum() PROJ_PURE_DEFN { // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress +/** \brief Return the real datum or a synthetized one if a datumEnsemble. + */ +const datum::GeodeticReferenceFrameNNPtr +GeodeticCRS::datumNonNull(const io::DatabaseContextPtr &dbContext) const { + return NN_NO_CHECK( + d->datum_ + ? d->datum_ + : util::nn_dynamic_pointer_cast<datum::GeodeticReferenceFrame>( + SingleCRS::getPrivate()->datumEnsemble->asDatum(dbContext))); +} +//! @endcond + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress static datum::GeodeticReferenceFrame *oneDatum(const GeodeticCRS *crs) { const auto &l_datumEnsemble = crs->datumEnsemble(); assert(l_datumEnsemble); @@ -1718,8 +1767,8 @@ void GeodeticCRS::addDatumInfoToPROJString( const auto &TOWGS84Params = formatter->getTOWGS84Parameters(); bool datumWritten = false; const auto &nadgrids = formatter->getHDatumExtension(); - const auto &l_datum = datum(); - if (formatter->getCRSExport() && l_datum && TOWGS84Params.empty() && + const auto l_datum = datumNonNull(formatter->databaseContext()); + if (formatter->getCRSExport() && TOWGS84Params.empty() && nadgrids.empty()) { if (l_datum->_isEquivalentTo( datum::GeodeticReferenceFrame::EPSG_6326.get(), @@ -1967,11 +2016,12 @@ GeodeticCRS::identify(const io::AuthorityFactoryPtr &authorityFactory) const { if (authorityFactory) { - const auto &thisDatum(datum()); + const auto thisDatum(datumNonNull(dbContext)); - auto searchByDatum = [this, &authorityFactory, &res, &thisDatum, - &geodetic_crs_type, crsCriterion, &dbContext]() { - for (const auto &id : thisDatum->identifiers()) { + auto searchByDatumCode = [this, &authorityFactory, &res, + &geodetic_crs_type, crsCriterion, &dbContext]( + const common::IdentifiedObjectNNPtr &l_datum) { + for (const auto &id : l_datum->identifiers()) { try { auto tempRes = authorityFactory->createGeodeticCRSFromDatum( *id->codeSpace(), id->code(), geodetic_crs_type); @@ -1986,10 +2036,10 @@ GeodeticCRS::identify(const io::AuthorityFactoryPtr &authorityFactory) const { } }; - const auto &thisEllipsoid(ellipsoid()); auto searchByEllipsoid = [this, &authorityFactory, &res, &thisDatum, - &thisEllipsoid, &geodetic_crs_type, - l_implicitCS, &dbContext]() { + &geodetic_crs_type, l_implicitCS, + &dbContext]() { + const auto &thisEllipsoid = thisDatum->ellipsoid(); const auto ellipsoids = thisEllipsoid->identifiers().empty() ? authorityFactory->createEllipsoidFromExisting( @@ -2003,9 +2053,8 @@ GeodeticCRS::identify(const io::AuthorityFactoryPtr &authorityFactory) const { *id->codeSpace(), id->code(), geodetic_crs_type); for (const auto &crs : tempRes) { - const auto &crsDatum(crs->datum()); - if (crsDatum && - crsDatum->ellipsoid()->_isEquivalentTo( + const auto crsDatum(crs->datumNonNull(dbContext)); + if (crsDatum->ellipsoid()->_isEquivalentTo( ellps.get(), util::IComparable::Criterion::EQUIVALENT, dbContext) && @@ -2027,18 +2076,32 @@ GeodeticCRS::identify(const io::AuthorityFactoryPtr &authorityFactory) const { } }; + const auto searchByDatumOrEllipsoid = [&authorityFactory, &res, + &thisDatum, searchByDatumCode, + searchByEllipsoid]() { + if (!thisDatum->identifiers().empty()) { + searchByDatumCode(thisDatum); + } else { + auto candidateDatums = authorityFactory->createObjectsFromName( + thisDatum->nameStr(), {io::AuthorityFactory::ObjectType:: + GEODETIC_REFERENCE_FRAME}, + false); + const size_t sizeBefore = res.size(); + for (const auto &candidateDatum : candidateDatums) { + searchByDatumCode(candidateDatum); + } + if (sizeBefore == res.size()) { + searchByEllipsoid(); + } + } + }; + const bool unsignificantName = thisName.empty() || ci_equal(thisName, "unknown") || ci_equal(thisName, "unnamed"); if (unsignificantName) { - if (thisDatum) { - if (!thisDatum->identifiers().empty()) { - searchByDatum(); - } else { - searchByEllipsoid(); - } - } + searchByDatumOrEllipsoid(); } else if (hasCodeCompatibleOfAuthorityFactory(this, authorityFactory)) { // If the CRS has already an id, check in the database for the @@ -2088,12 +2151,8 @@ GeodeticCRS::identify(const io::AuthorityFactoryPtr &authorityFactory) const { break; } } - if (!gotAbove25Pct && thisDatum) { - if (!thisDatum->identifiers().empty()) { - searchByDatum(); - } else { - searchByEllipsoid(); - } + if (!gotAbove25Pct) { + searchByDatumOrEllipsoid(); } } @@ -2120,22 +2179,20 @@ GeodeticCRS::identify(const io::AuthorityFactoryPtr &authorityFactory) const { } // Then datum matching - const auto &aDatum(a.first->datum()); - const auto &bDatum(b.first->datum()); - if (thisDatum && aDatum && bDatum) { - const auto thisEquivADatum(thisDatum->_isEquivalentTo( - aDatum.get(), util::IComparable::Criterion::EQUIVALENT, - dbContext)); - const auto thisEquivBDatum(thisDatum->_isEquivalentTo( - bDatum.get(), util::IComparable::Criterion::EQUIVALENT, - dbContext)); - - if (thisEquivADatum && !thisEquivBDatum) { - return true; - } - if (!thisEquivADatum && thisEquivBDatum) { - return false; - } + const auto aDatum(a.first->datumNonNull(dbContext)); + const auto bDatum(b.first->datumNonNull(dbContext)); + const auto thisEquivADatum(thisDatum->_isEquivalentTo( + aDatum.get(), util::IComparable::Criterion::EQUIVALENT, + dbContext)); + const auto thisEquivBDatum(thisDatum->_isEquivalentTo( + bDatum.get(), util::IComparable::Criterion::EQUIVALENT, + dbContext)); + + if (thisEquivADatum && !thisEquivBDatum) { + return true; + } + if (!thisEquivADatum && thisEquivBDatum) { + return false; } // Then coordinate system matching @@ -2167,23 +2224,21 @@ GeodeticCRS::identify(const io::AuthorityFactoryPtr &authorityFactory) const { return false; } - if (aDatum && bDatum) { - // Favor the CRS whole ellipsoid names matches the ellipsoid - // name (WGS84...) - const bool aEllpsNameEqCRSName = - metadata::Identifier::isEquivalentName( - aDatum->ellipsoid()->nameStr().c_str(), - a.first->nameStr().c_str()); - const bool bEllpsNameEqCRSName = - metadata::Identifier::isEquivalentName( - bDatum->ellipsoid()->nameStr().c_str(), - b.first->nameStr().c_str()); - if (aEllpsNameEqCRSName && !bEllpsNameEqCRSName) { - return true; - } - if (bEllpsNameEqCRSName && !aEllpsNameEqCRSName) { - return false; - } + // Favor the CRS whole ellipsoid names matches the ellipsoid + // name (WGS84...) + const bool aEllpsNameEqCRSName = + metadata::Identifier::isEquivalentName( + aDatum->ellipsoid()->nameStr().c_str(), + a.first->nameStr().c_str()); + const bool bEllpsNameEqCRSName = + metadata::Identifier::isEquivalentName( + bDatum->ellipsoid()->nameStr().c_str(), + b.first->nameStr().c_str()); + if (aEllpsNameEqCRSName && !bEllpsNameEqCRSName) { + return true; + } + if (bEllpsNameEqCRSName && !aEllpsNameEqCRSName) { + return false; } // Arbitrary final sorting criterion @@ -2331,7 +2386,8 @@ GeographicCRS::create(const util::PropertyMap &properties, /** \brief Return whether the current GeographicCRS is the 2D part of the * other 3D GeographicCRS. */ -bool GeographicCRS::is2DPartOf3D(util::nn<const GeographicCRS *> other) +bool GeographicCRS::is2DPartOf3D(util::nn<const GeographicCRS *> other, + const io::DatabaseContextPtr &dbContext) PROJ_PURE_DEFN { const auto &axis = d->coordinateSystem_->axisList(); const auto &otherAxis = other->d->coordinateSystem_->axisList(); @@ -2349,13 +2405,10 @@ bool GeographicCRS::is2DPartOf3D(util::nn<const GeographicCRS *> other) 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(), util::IComparable::Criterion::EQUIVALENT); - } - return false; + const auto thisDatum = datumNonNull(dbContext); + const auto otherDatum = other->datumNonNull(dbContext); + return thisDatum->_isEquivalentTo(otherDatum.get(), + util::IComparable::Criterion::EQUIVALENT); } //! @endcond @@ -2497,7 +2550,8 @@ GeographicCRS::demoteTo2D(const std::string &newName, auto firstResAsGeogCRS = util::nn_dynamic_pointer_cast<GeographicCRS>(firstRes); if (firstResAsGeogCRS && - firstResAsGeogCRS->is2DPartOf3D(NN_NO_CHECK(this))) { + firstResAsGeogCRS->is2DPartOf3D(NN_NO_CHECK(this), + dbContext)) { return NN_NO_CHECK(firstResAsGeogCRS); } } @@ -2594,15 +2648,13 @@ void GeographicCRS::_exportToPROJString( if (formatter->getLegacyCRSToCRSContext() && formatter->getHDatumExtension().empty() && formatter->getTOWGS84Parameters().empty()) { - const auto &l_datum = datum(); - if (l_datum && - l_datum->_isEquivalentTo( + const auto l_datum = datumNonNull(formatter->databaseContext()); + if (l_datum->_isEquivalentTo( datum::GeodeticReferenceFrame::EPSG_6326.get(), util::IComparable::Criterion::EQUIVALENT)) { done = true; formatter->addParam("ellps", "WGS84"); - } else if (l_datum && - l_datum->_isEquivalentTo( + } else if (l_datum->_isEquivalentTo( datum::GeodeticReferenceFrame::EPSG_6269.get(), util::IComparable::Criterion::EQUIVALENT)) { done = true; @@ -2769,6 +2821,19 @@ const cs::VerticalCSNNPtr VerticalCRS::coordinateSystem() const { // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress +/** \brief Return the real datum or a synthetized one if a datumEnsemble. + */ +const datum::VerticalReferenceFrameNNPtr +VerticalCRS::datumNonNull(const io::DatabaseContextPtr &dbContext) const { + return NN_NO_CHECK( + util::nn_dynamic_pointer_cast<datum::VerticalReferenceFrame>( + SingleCRS::datumNonNull(dbContext))); +} +//! @endcond + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress void VerticalCRS::_exportToWKT(io::WKTFormatter *formatter) const { const bool isWKT2 = formatter->version() == io::WKTFormatter::Version::WKT2; formatter->startNode(isWKT2 ? io::WKTConstants::VERTCRS @@ -3860,12 +3925,15 @@ ProjectedCRS::identify(const io::AuthorityFactoryPtr &authorityFactory) const { std::list<Pair> res; const auto &thisName(nameStr()); + io::DatabaseContextPtr dbContext = + authorityFactory ? authorityFactory->databaseContext().as_nullable() + : nullptr; std::list<std::pair<GeodeticCRSNNPtr, int>> baseRes; const auto &l_baseCRS(baseCRS()); - const auto l_datum = l_baseCRS->datum(); + const auto l_datum = l_baseCRS->datumNonNull(dbContext); const bool significantNameForDatum = - l_datum != nullptr && !ci_starts_with(l_datum->nameStr(), "unknown") && + !ci_starts_with(l_datum->nameStr(), "unknown") && l_datum->nameStr() != "unnamed"; const auto &ellipsoid = l_baseCRS->ellipsoid(); auto geogCRS = dynamic_cast<const GeographicCRS *>(l_baseCRS.get()); @@ -3900,9 +3968,6 @@ ProjectedCRS::identify(const io::AuthorityFactoryPtr &authorityFactory) const { const auto &conv = derivingConversionRef(); const auto &cs = coordinateSystem(); - io::DatabaseContextPtr dbContext = - authorityFactory ? authorityFactory->databaseContext().as_nullable() - : nullptr; if (baseRes.size() == 1 && baseRes.front().second >= 70 && conv->isUTM(zone, north) && @@ -3992,10 +4057,9 @@ ProjectedCRS::identify(const io::AuthorityFactoryPtr &authorityFactory) const { util::IComparable::Criterion::EQUIVALENT, dbContext)) { if (!significantNameForDatum || - (crs->baseCRS()->datum() && - l_datum->_isEquivalentTo( - crs->baseCRS()->datum().get(), - util::IComparable::Criterion::EQUIVALENT))) { + l_datum->_isEquivalentTo( + crs->baseCRS()->datumNonNull(dbContext).get(), + util::IComparable::Criterion::EQUIVALENT)) { res.emplace_back(crs, 70); } else { res.emplace_back(crs, 60); @@ -4877,7 +4941,6 @@ BoundCRSNNPtr BoundCRS::createFromNadgrids(const CRSNNPtr &baseCRSIn, ? NN_NO_CHECK(std::static_pointer_cast<CRS>(sourceGeographicCRS)) : baseCRSIn; if (sourceGeographicCRS != nullptr && - sourceGeographicCRS->datum() != nullptr && sourceGeographicCRS->primeMeridian()->longitude().getSIValue() != 0.0) { transformationSourceCRS = GeographicCRS::create( util::PropertyMap().set(common::IdentifiedObject::NAME_KEY, @@ -4886,9 +4949,9 @@ BoundCRSNNPtr BoundCRS::createFromNadgrids(const CRSNNPtr &baseCRSIn, datum::GeodeticReferenceFrame::create( util::PropertyMap().set( common::IdentifiedObject::NAME_KEY, - sourceGeographicCRS->datum()->nameStr() + + sourceGeographicCRS->datumNonNull(nullptr)->nameStr() + " (with Greenwich prime meridian)"), - sourceGeographicCRS->datum()->ellipsoid(), + sourceGeographicCRS->datumNonNull(nullptr)->ellipsoid(), util::optional<std::string>(), datum::PrimeMeridian::GREENWICH), sourceGeographicCRS->coordinateSystem()); } diff --git a/src/iso19111/datum.cpp b/src/iso19111/datum.cpp index fbd4fd2d..7d42fd13 100644 --- a/src/iso19111/datum.cpp +++ b/src/iso19111/datum.cpp @@ -1581,8 +1581,7 @@ DatumEnsemble::DatumEnsemble(const std::vector<DatumNNPtr> &datumsIn, #ifdef notdef DatumEnsemble::DatumEnsemble(const DatumEnsemble &other) - : common::IdentifiedObject(other), - d(internal::make_unique<Private>(*other.d)) {} + : common::ObjectUsage(other), d(internal::make_unique<Private>(*other.d)) {} #endif // --------------------------------------------------------------------------- @@ -1621,16 +1620,83 @@ DatumEnsemble::positionalAccuracy() const { // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress +DatumNNPtr +DatumEnsemble::asDatum(const io::DatabaseContextPtr &dbContext) const { + + const auto &l_datums = datums(); + auto *grf = dynamic_cast<const GeodeticReferenceFrame *>(l_datums[0].get()); + + const auto &l_identifiers = identifiers(); + if (dbContext) { + if (!l_identifiers.empty()) { + const auto &id = l_identifiers[0]; + try { + auto factory = io::AuthorityFactory::create( + NN_NO_CHECK(dbContext), *(id->codeSpace())); + if (grf) { + return factory->createGeodeticDatum(id->code()); + } else { + return factory->createVerticalDatum(id->code()); + } + } catch (const std::exception &) { + } + } + } + + std::string l_name(nameStr()); + if (grf) { + // Remap to traditional datum names + if (l_name == "World Geodetic System 1984 ensemble") { + l_name = "World Geodetic System 1984"; + } else if (l_name == + "European Terrestrial Reference System 1989 ensemble") { + l_name = "European Terrestrial Reference System 1989"; + } + } + auto props = + util::PropertyMap().set(common::IdentifiedObject::NAME_KEY, l_name); + if (isDeprecated()) { + props.set(common::IdentifiedObject::DEPRECATED_KEY, true); + } + if (!l_identifiers.empty()) { + const auto &id = l_identifiers[0]; + props.set(metadata::Identifier::CODESPACE_KEY, *(id->codeSpace())) + .set(metadata::Identifier::CODE_KEY, id->code()); + } + const auto &l_usages = domains(); + if (!l_usages.empty()) { + + auto array(util::ArrayOfBaseObject::create()); + for (const auto &usage : l_usages) { + array->add(usage); + } + props.set(common::ObjectUsage::OBJECT_DOMAIN_KEY, + util::nn_static_pointer_cast<util::BaseObject>(array)); + } + const auto anchor = util::optional<std::string>(); + + if (grf) { + return GeodeticReferenceFrame::create(props, grf->ellipsoid(), anchor, + grf->primeMeridian()); + } else { + assert(dynamic_cast<VerticalReferenceFrame *>(l_datums[0].get())); + return datum::VerticalReferenceFrame::create(props, anchor); + } +} +//! @endcond + +// --------------------------------------------------------------------------- + +//! @cond Doxygen_Suppress void DatumEnsemble::_exportToWKT( io::WKTFormatter *formatter) const // throw(FormattingException) { const bool isWKT2 = formatter->version() == io::WKTFormatter::Version::WKT2; if (!isWKT2 || !formatter->use2019Keywords()) { - throw io::FormattingException( - "DatumEnsemble can only be exported to WKT2:2019"); + return asDatum(formatter->databaseContext())->_exportToWKT(formatter); } - auto l_datums = datums(); + const auto &l_datums = datums(); assert(!l_datums.empty()); formatter->startNode(io::WKTConstants::ENSEMBLE, false); @@ -1687,7 +1753,7 @@ void DatumEnsemble::_exportToJSON( writer->Add(l_name); } - auto l_datums = datums(); + const auto &l_datums = datums(); writer->AddObjKey("members"); { auto membersContext(writer->MakeArrayContext(false)); diff --git a/src/iso19111/factory.cpp b/src/iso19111/factory.cpp index a011f397..ef5c6e02 100644 --- a/src/iso19111/factory.cpp +++ b/src/iso19111/factory.cpp @@ -1064,6 +1064,18 @@ std::string DatabaseContext::getOldProjGridName(const std::string &gridName) { // --------------------------------------------------------------------------- +// FIXME: as we don't support datum ensemble yet, add it from name +static std::string removeEnsembleSuffix(const std::string &name) { + if (name == "World Geodetic System 1984 ensemble") { + return "World Geodetic System 1984"; + } else if (name == "European Terrestrial Reference System 1989 ensemble") { + return "European Terrestrial Reference System 1989"; + } + return name; +} + +// --------------------------------------------------------------------------- + /** \brief Gets the alias name from an official name. * * @param officialName Official name. Mandatory @@ -1084,7 +1096,13 @@ DatabaseContext::getAliasFromOfficialName(const std::string &officialName, } auto res = d->run(sql, {officialName}); if (res.empty()) { - return std::string(); + res = d->run( + "SELECT auth_name, code FROM alias_name WHERE table_name = ? AND " + "alt_name = ? AND source IN ('EPSG', 'PROJ')", + {tableName, officialName}); + if (res.size() != 1) { + return std::string(); + } } const auto &row = res.front(); res = d->run("SELECT alt_name FROM alias_name WHERE table_name = ? AND " @@ -1131,8 +1149,14 @@ std::list<std::string> DatabaseContext::getAliases( } auto resSql = d->run(sql, {officialName}); if (resSql.empty()) { - d->cacheAliasNames_.insert(key, res); - return res; + resSql = d->run("SELECT auth_name, code FROM alias_name WHERE " + "table_name = ? AND " + "alt_name = ? AND source IN ('EPSG', 'PROJ')", + {tableName, officialName}); + if (resSql.size() != 1) { + d->cacheAliasNames_.insert(key, res); + return res; + } } const auto &row = resSql.front(); resolvedAuthName = row[0]; @@ -1312,21 +1336,19 @@ struct AuthorityFactory::Private { UnitOfMeasure createUnitOfMeasure(const std::string &auth_name, const std::string &code); - util::PropertyMap createProperties(const std::string &code, - const std::string &name, bool deprecated, - const metadata::ExtentPtr &extent); + util::PropertyMap + createProperties(const std::string &code, const std::string &name, + bool deprecated, + const std::vector<ObjectDomainNNPtr> &usages); - util::PropertyMap createProperties(const std::string &code, - const std::string &name, bool deprecated, - const std::string &area_of_use_auth_name, - const std::string &area_of_use_code); + util::PropertyMap + createPropertiesSearchUsages(const std::string &table_name, + const std::string &code, + const std::string &name, bool deprecated); - util::PropertyMap createProperties(const std::string &code, - const std::string &name, bool deprecated, - const std::string &remarks, - const std::string &scope, - const std::string &area_of_use_auth_name, - const std::string &area_of_use_code); + util::PropertyMap createPropertiesSearchUsages( + const std::string &table_name, const std::string &code, + const std::string &name, bool deprecated, const std::string &remarks); SQLResultSet run(const std::string &sql, const ListOfParams ¶meters = ListOfParams()); @@ -1385,7 +1407,7 @@ AuthorityFactory::Private::createUnitOfMeasure(const std::string &auth_name, util::PropertyMap AuthorityFactory::Private::createProperties( const std::string &code, const std::string &name, bool deprecated, - const metadata::ExtentPtr &extent) { + const std::vector<ObjectDomainNNPtr> &usages) { auto props = util::PropertyMap() .set(metadata::Identifier::CODESPACE_KEY, authority()) .set(metadata::Identifier::CODE_KEY, code) @@ -1393,45 +1415,92 @@ util::PropertyMap AuthorityFactory::Private::createProperties( if (deprecated) { props.set(common::IdentifiedObject::DEPRECATED_KEY, true); } - if (extent) { - props.set( - common::ObjectUsage::DOMAIN_OF_VALIDITY_KEY, - NN_NO_CHECK(std::static_pointer_cast<util::BaseObject>(extent))); + if (!usages.empty()) { + + auto array(util::ArrayOfBaseObject::create()); + for (const auto &usage : usages) { + array->add(usage); + } + props.set(common::ObjectUsage::OBJECT_DOMAIN_KEY, + util::nn_static_pointer_cast<util::BaseObject>(array)); } return props; } // --------------------------------------------------------------------------- -util::PropertyMap AuthorityFactory::Private::createProperties( - const std::string &code, const std::string &name, bool deprecated, - const std::string &area_of_use_auth_name, - const std::string &area_of_use_code) { - return createProperties(code, name, deprecated, - area_of_use_auth_name.empty() - ? nullptr - : createFactory(area_of_use_auth_name) - ->createExtent(area_of_use_code) - .as_nullable()); +util::PropertyMap AuthorityFactory::Private::createPropertiesSearchUsages( + const std::string &table_name, const std::string &code, + const std::string &name, bool deprecated) { + + const std::string sql( + "SELECT extent.description, extent.south_lat, " + "extent.north_lat, extent.west_lon, extent.east_lon, " + "scope.scope, " + "(CASE WHEN scope.scope LIKE '%large scale%' THEN 0 ELSE 1 END) " + "AS score " + "FROM usage " + "JOIN extent ON usage.extent_auth_name = extent.auth_name AND " + "usage.extent_code = extent.code " + "JOIN scope ON usage.scope_auth_name = scope.auth_name AND " + "usage.scope_code = scope.code " + "WHERE object_table_name = ? AND object_auth_name = ? AND " + "object_code = ? " + "ORDER BY score, usage.auth_name, usage.code"); + auto res = run(sql, {table_name, authority(), code}); + std::vector<ObjectDomainNNPtr> usages; + for (const auto &row : res) { + try { + size_t idx = 0; + const auto &extent_description = row[idx++]; + const auto &south_lat_str = row[idx++]; + const auto &north_lat_str = row[idx++]; + const auto &west_lon_str = row[idx++]; + const auto &east_lon_str = row[idx++]; + const auto &scope = row[idx]; + + util::optional<std::string> scopeOpt; + if (!scope.empty()) { + scopeOpt = scope; + } + + metadata::ExtentPtr extent; + if (south_lat_str.empty()) { + extent = metadata::Extent::create( + util::optional<std::string>(extent_description), + {}, {}, {}) + .as_nullable(); + } else { + double south_lat = c_locale_stod(south_lat_str); + double north_lat = c_locale_stod(north_lat_str); + double west_lon = c_locale_stod(west_lon_str); + double east_lon = c_locale_stod(east_lon_str); + auto bbox = metadata::GeographicBoundingBox::create( + west_lon, south_lat, east_lon, north_lat); + extent = metadata::Extent::create( + util::optional<std::string>(extent_description), + std::vector<metadata::GeographicExtentNNPtr>{bbox}, + std::vector<metadata::VerticalExtentNNPtr>(), + std::vector<metadata::TemporalExtentNNPtr>()) + .as_nullable(); + } + + usages.emplace_back(ObjectDomain::create(scopeOpt, extent)); + } catch (const std::exception &) { + } + } + return createProperties(code, name, deprecated, std::move(usages)); } // --------------------------------------------------------------------------- -util::PropertyMap AuthorityFactory::Private::createProperties( - const std::string &code, const std::string &name, bool deprecated, - const std::string &remarks, const std::string &scope, - const std::string &area_of_use_auth_name, - const std::string &area_of_use_code) { - auto props = createProperties(code, name, deprecated, - area_of_use_auth_name.empty() - ? nullptr - : createFactory(area_of_use_auth_name) - ->createExtent(area_of_use_code) - .as_nullable()); +util::PropertyMap AuthorityFactory::Private::createPropertiesSearchUsages( + const std::string &table_name, const std::string &code, + const std::string &name, bool deprecated, const std::string &remarks) { + auto props = + createPropertiesSearchUsages(table_name, code, name, deprecated); if (!remarks.empty()) props.set(common::IdentifiedObject::REMARKS_KEY, remarks); - if (!scope.empty()) - props.set(common::ObjectUsage::SCOPE_KEY, scope); return props; } @@ -1535,9 +1604,9 @@ AuthorityFactory::CRSInfo::CRSInfo() util::BaseObjectNNPtr AuthorityFactory::createObject(const std::string &code) const { - auto res = d->runWithCodeParam( - "SELECT table_name FROM object_view WHERE auth_name = ? AND code = ?", - code); + auto res = d->runWithCodeParam("SELECT table_name, type FROM object_view " + "WHERE auth_name = ? AND code = ?", + code); if (res.empty()) { throw NoSuchAuthorityCodeException("not found", d->authority(), code); } @@ -1553,8 +1622,10 @@ AuthorityFactory::createObject(const std::string &code) const { } throw FactoryException(msg); } - const auto &table_name = res.front()[0]; - if (table_name == "area") { + const auto &first_row = res.front(); + const auto &table_name = first_row[0]; + const auto &type = first_row[1]; + if (table_name == "extent") { return util::nn_static_pointer_cast<util::BaseObject>( createExtent(code)); } @@ -1571,10 +1642,18 @@ AuthorityFactory::createObject(const std::string &code) const { createEllipsoid(code)); } if (table_name == "geodetic_datum") { + if (type == "ensemble") { + return util::nn_static_pointer_cast<util::BaseObject>( + createDatumEnsemble(code, table_name)); + } return util::nn_static_pointer_cast<util::BaseObject>( createGeodeticDatum(code)); } if (table_name == "vertical_datum") { + if (type == "ensemble") { + return util::nn_static_pointer_cast<util::BaseObject>( + createDatumEnsemble(code, table_name)); + } return util::nn_static_pointer_cast<util::BaseObject>( createVerticalDatum(code)); } @@ -1638,19 +1717,19 @@ AuthorityFactory::createExtent(const std::string &code) const { return NN_NO_CHECK(extent); } } - auto sql = "SELECT name, south_lat, north_lat, west_lon, east_lon, " - "deprecated FROM area WHERE auth_name = ? AND code = ?"; + auto sql = "SELECT description, south_lat, north_lat, west_lon, east_lon, " + "deprecated FROM extent WHERE auth_name = ? AND code = ?"; auto res = d->runWithCodeParam(sql, code); if (res.empty()) { - throw NoSuchAuthorityCodeException("area not found", d->authority(), + throw NoSuchAuthorityCodeException("extent not found", d->authority(), code); } try { const auto &row = res.front(); - const auto &name = row[0]; + const auto &description = row[0]; if (row[1].empty()) { auto extent = metadata::Extent::create( - util::optional<std::string>(name), {}, {}, {}); + util::optional<std::string>(description), {}, {}, {}); d->context()->d->cache(cacheKey, extent); return extent; } @@ -1662,7 +1741,7 @@ AuthorityFactory::createExtent(const std::string &code) const { west_lon, south_lat, east_lon, north_lat); auto extent = metadata::Extent::create( - util::optional<std::string>(name), + util::optional<std::string>(description), std::vector<metadata::GeographicExtentNNPtr>{bbox}, std::vector<metadata::VerticalExtentNNPtr>(), std::vector<metadata::TemporalExtentNNPtr>()); @@ -1670,7 +1749,7 @@ AuthorityFactory::createExtent(const std::string &code) const { return extent; } catch (const std::exception &ex) { - throw buildFactoryException("area", code, ex); + throw buildFactoryException("extent", code, ex); } } @@ -1816,7 +1895,7 @@ AuthorityFactory::createPrimeMeridian(const std::string &code) const { normalizeMeasure(uom_code, longitude, normalized_uom_code); auto uom = d->createUnitOfMeasure(uom_auth_name, normalized_uom_code); - auto props = d->createProperties(code, name, deprecated, nullptr); + auto props = d->createProperties(code, name, deprecated, {}); auto pm = datum::PrimeMeridian::create( props, common::Angle(normalized_value, uom)); d->context()->d->cache(cacheKey, pm); @@ -1897,7 +1976,7 @@ AuthorityFactory::createEllipsoid(const std::string &code) const { const auto &body = row[6]; const bool deprecated = row[7] == "1"; auto uom = d->createUnitOfMeasure(uom_auth_name, uom_code); - auto props = d->createProperties(code, name, deprecated, nullptr); + auto props = d->createProperties(code, name, deprecated, {}); if (!inv_flattening_str.empty()) { auto ellps = datum::Ellipsoid::createFlattenedSphere( props, common::Length(semi_major_axis, uom), @@ -1940,13 +2019,14 @@ AuthorityFactory::createGeodeticDatum(const std::string &code) const { return NN_NO_CHECK(datum); } } - auto res = d->runWithCodeParam( - "SELECT name, ellipsoid_auth_name, ellipsoid_code, " - "prime_meridian_auth_name, prime_meridian_code, area_of_use_auth_name, " - "area_of_use_code, publication_date, deprecated FROM geodetic_datum " - "WHERE " - "auth_name = ? AND code = ?", - code); + auto res = + d->runWithCodeParam("SELECT name, ellipsoid_auth_name, ellipsoid_code, " + "prime_meridian_auth_name, prime_meridian_code, " + "publication_date, frame_reference_epoch, " + "deprecated FROM geodetic_datum " + "WHERE " + "auth_name = ? AND code = ?", + code); if (res.empty()) { throw NoSuchAuthorityCodeException("geodetic datum not found", d->authority(), code); @@ -1958,22 +2038,29 @@ AuthorityFactory::createGeodeticDatum(const std::string &code) const { const auto &ellipsoid_code = row[2]; const auto &prime_meridian_auth_name = row[3]; const auto &prime_meridian_code = row[4]; - const auto &area_of_use_auth_name = row[5]; - const auto &area_of_use_code = row[6]; - const auto &publication_date = row[7]; - const bool deprecated = row[8] == "1"; + const auto &publication_date = row[5]; + const auto &frame_reference_epoch = row[6]; + const bool deprecated = row[7] == "1"; auto ellipsoid = d->createFactory(ellipsoid_auth_name) ->createEllipsoid(ellipsoid_code); auto pm = d->createFactory(prime_meridian_auth_name) ->createPrimeMeridian(prime_meridian_code); - auto props = d->createProperties( - code, name, deprecated, area_of_use_auth_name, area_of_use_code); + auto props = d->createPropertiesSearchUsages( + "geodetic_datum", code, removeEnsembleSuffix(name), deprecated); auto anchor = util::optional<std::string>(); if (!publication_date.empty()) { props.set("PUBLICATION_DATE", publication_date); } auto datum = - datum::GeodeticReferenceFrame::create(props, ellipsoid, anchor, pm); + frame_reference_epoch.empty() + ? datum::GeodeticReferenceFrame::create(props, ellipsoid, + anchor, pm) + : util::nn_static_pointer_cast<datum::GeodeticReferenceFrame>( + datum::DynamicGeodeticReferenceFrame::create( + props, ellipsoid, anchor, pm, + common::Measure(c_locale_stod(frame_reference_epoch), + common::UnitOfMeasure::YEAR), + util::optional<std::string>())); d->context()->d->cache(cacheKey, datum); return datum; } catch (const std::exception &ex) { @@ -1993,10 +2080,11 @@ AuthorityFactory::createGeodeticDatum(const std::string &code) const { datum::VerticalReferenceFrameNNPtr AuthorityFactory::createVerticalDatum(const std::string &code) const { - auto res = d->runWithCodeParam( - "SELECT name, area_of_use_auth_name, area_of_use_code, deprecated FROM " - "vertical_datum WHERE auth_name = ? AND code = ?", - code); + auto res = + d->runWithCodeParam("SELECT name, publication_date, " + "frame_reference_epoch, deprecated FROM " + "vertical_datum WHERE auth_name = ? AND code = ?", + code); if (res.empty()) { throw NoSuchAuthorityCodeException("vertical datum not found", d->authority(), code); @@ -2004,13 +2092,24 @@ AuthorityFactory::createVerticalDatum(const std::string &code) const { try { const auto &row = res.front(); const auto &name = row[0]; - const auto &area_of_use_auth_name = row[1]; - const auto &area_of_use_code = row[2]; + const auto &publication_date = row[1]; + const auto &frame_reference_epoch = row[2]; const bool deprecated = row[3] == "1"; - auto props = d->createProperties( - code, name, deprecated, area_of_use_auth_name, area_of_use_code); + auto props = d->createPropertiesSearchUsages("vertical_datum", code, + name, deprecated); + if (!publication_date.empty()) { + props.set("PUBLICATION_DATE", publication_date); + } auto anchor = util::optional<std::string>(); - return datum::VerticalReferenceFrame::create(props, anchor); + if (frame_reference_epoch.empty()) { + return datum::VerticalReferenceFrame::create(props, anchor); + } else { + return datum::DynamicVerticalReferenceFrame::create( + props, anchor, util::optional<datum::RealizationMethod>(), + common::Measure(c_locale_stod(frame_reference_epoch), + common::UnitOfMeasure::YEAR), + util::optional<std::string>()); + } } catch (const std::exception &ex) { throw buildFactoryException("vertical reference frame", code, ex); } @@ -2018,6 +2117,62 @@ AuthorityFactory::createVerticalDatum(const std::string &code) const { // --------------------------------------------------------------------------- +/** \brief Returns a datum::DatumEnsemble from the specified code. + * + * @param code Object code allocated by authority. + * @param type "geodetic_datum", "vertical_datum" or empty string if unknown + * @return object. + * @throw NoSuchAuthorityCodeException + * @throw FactoryException + */ + +datum::DatumEnsembleNNPtr +AuthorityFactory::createDatumEnsemble(const std::string &code, + const std::string &type) const { + auto res = d->run( + "SELECT 'geodetic_datum', name, ensemble_accuracy, deprecated FROM " + "geodetic_datum WHERE " + "auth_name = ? AND code = ? AND ensemble_accuracy IS NOT NULL " + "UNION ALL " + "SELECT 'vertical_datum', name, ensemble_accuracy, deprecated FROM " + "vertical_datum WHERE " + "auth_name = ? AND code = ? AND ensemble_accuracy IS NOT NULL", + {d->authority(), code, d->authority(), code}); + if (res.empty()) { + throw NoSuchAuthorityCodeException("datum ensemble not found", + d->authority(), code); + } + for (const auto &row : res) { + const std::string &gotType = row[0]; + const std::string &name = row[1]; + const std::string &ensembleAccuracy = row[2]; + const bool deprecated = row[3] == "1"; + if (type.empty() || type == gotType) { + auto resMembers = + d->run("SELECT member_auth_name, member_code FROM " + gotType + + "_ensemble_member WHERE " + "ensemble_auth_name = ? AND ensemble_code = ? " + "ORDER BY sequence", + {d->authority(), code}); + + std::vector<datum::DatumNNPtr> members; + for (const auto &memberRow : resMembers) { + members.push_back( + d->createFactory(memberRow[0])->createDatum(memberRow[1])); + } + auto props = d->createPropertiesSearchUsages(gotType, code, name, + deprecated); + return datum::DatumEnsemble::create( + props, std::move(members), + metadata::PositionalAccuracy::create(ensembleAccuracy)); + } + } + throw NoSuchAuthorityCodeException("datum ensemble not found", + d->authority(), code); +} + +// --------------------------------------------------------------------------- + /** \brief Returns a datum::Datum from the specified code. * * @param code Object code allocated by authority. @@ -2105,7 +2260,13 @@ AuthorityFactory::createCoordinateSystem(const std::string &code) const { const auto &orientation = row[2]; const auto &uom_auth_name = row[3]; const auto &uom_code = row[4]; - auto uom = d->createUnitOfMeasure(uom_auth_name, uom_code); + if (uom_auth_name.empty() && csType != "ordinal") { + throw FactoryException("no unit of measure for an axis is only " + "supported for ordinatal CS"); + } + auto uom = uom_auth_name.empty() + ? common::UnitOfMeasure::NONE + : d->createUnitOfMeasure(uom_auth_name, uom_code); auto props = util::PropertyMap().set(common::IdentifiedObject::NAME_KEY, name); const cs::AxisDirection *direction = @@ -2176,6 +2337,9 @@ AuthorityFactory::createCoordinateSystem(const std::string &code) const { } throw FactoryException("invalid number of axis for VerticalCS"); } + if (csType == "ordinal") { + return cacheAndRet(cs::OrdinalCS::create(props, axisList)); + } throw FactoryException("unhandled coordinate system type: " + csType); } @@ -2216,18 +2380,16 @@ static crs::GeodeticCRSNNPtr cloneWithProps(const crs::GeodeticCRSNNPtr &geodCRS, const util::PropertyMap &props) { auto cs = geodCRS->coordinateSystem(); - auto datum = geodCRS->datum(); - if (!datum) { - return geodCRS; - } auto ellipsoidalCS = util::nn_dynamic_pointer_cast<cs::EllipsoidalCS>(cs); if (ellipsoidalCS) { - return crs::GeographicCRS::create(props, NN_NO_CHECK(datum), + return crs::GeographicCRS::create(props, geodCRS->datum(), + geodCRS->datumEnsemble(), NN_NO_CHECK(ellipsoidalCS)); } auto geocentricCS = util::nn_dynamic_pointer_cast<cs::CartesianCS>(cs); if (geocentricCS) { - return crs::GeodeticCRS::create(props, NN_NO_CHECK(datum), + return crs::GeodeticCRS::create(props, geodCRS->datum(), + geodCRS->datumEnsemble(), NN_NO_CHECK(geocentricCS)); } return geodCRS; @@ -2250,7 +2412,7 @@ AuthorityFactory::createGeodeticCRS(const std::string &code, } std::string sql("SELECT name, type, coordinate_system_auth_name, " "coordinate_system_code, datum_auth_name, datum_code, " - "area_of_use_auth_name, area_of_use_code, text_definition, " + "text_definition, " "deprecated FROM " "geodetic_crs WHERE auth_name = ? AND code = ?"); if (geographicOnly) { @@ -2270,13 +2432,11 @@ AuthorityFactory::createGeodeticCRS(const std::string &code, const auto &cs_code = row[3]; const auto &datum_auth_name = row[4]; const auto &datum_code = row[5]; - const auto &area_of_use_auth_name = row[6]; - const auto &area_of_use_code = row[7]; - const auto &text_definition = row[8]; - const bool deprecated = row[9] == "1"; + const auto &text_definition = row[6]; + const bool deprecated = row[7] == "1"; - auto props = d->createProperties( - code, name, deprecated, area_of_use_auth_name, area_of_use_code); + auto props = d->createPropertiesSearchUsages("geodetic_crs", code, name, + deprecated); if (!text_definition.empty()) { DatabaseContext::Private::RecursionDetector detector(d->context()); @@ -2359,7 +2519,7 @@ AuthorityFactory::createVerticalCRS(const std::string &code) const { auto res = d->runWithCodeParam( "SELECT name, coordinate_system_auth_name, " "coordinate_system_code, datum_auth_name, datum_code, " - "area_of_use_auth_name, area_of_use_code, deprecated FROM " + "deprecated FROM " "vertical_crs WHERE auth_name = ? AND code = ?", code); if (res.empty()) { @@ -2373,16 +2533,14 @@ AuthorityFactory::createVerticalCRS(const std::string &code) const { const auto &cs_code = row[2]; const auto &datum_auth_name = row[3]; const auto &datum_code = row[4]; - const auto &area_of_use_auth_name = row[5]; - const auto &area_of_use_code = row[6]; - const bool deprecated = row[7] == "1"; + const bool deprecated = row[5] == "1"; auto cs = d->createFactory(cs_auth_name)->createCoordinateSystem(cs_code); auto datum = d->createFactory(datum_auth_name)->createVerticalDatum(datum_code); - auto props = d->createProperties( - code, name, deprecated, area_of_use_auth_name, area_of_use_code); + auto props = d->createPropertiesSearchUsages("vertical_crs", code, name, + deprecated); auto verticalCS = util::nn_dynamic_pointer_cast<cs::VerticalCS>(cs); if (verticalCS) { @@ -2412,8 +2570,7 @@ operation::ConversionNNPtr AuthorityFactory::createConversion(const std::string &code) const { static const char *sql = - "SELECT name, description, scope, " - "area_of_use_auth_name, area_of_use_code, " + "SELECT name, description, " "method_auth_name, method_code, method_name, " "param1_auth_name, param1_code, param1_name, param1_value, " @@ -2463,9 +2620,6 @@ AuthorityFactory::createConversion(const std::string &code) const { size_t idx = 0; const auto &name = row[idx++]; const auto &description = row[idx++]; - const auto &scope = row[idx++]; - const auto &area_of_use_auth_name = row[idx++]; - const auto &area_of_use_code = row[idx++]; const auto &method_auth_name = row[idx++]; const auto &method_code = row[idx++]; const auto &method_name = row[idx++]; @@ -2498,9 +2652,11 @@ AuthorityFactory::createConversion(const std::string &code) const { } const bool deprecated = row[base_param_idx + N_MAX_PARAMS * 6] == "1"; - auto propConversion = - d->createProperties(code, name, deprecated, description, scope, - area_of_use_auth_name, area_of_use_code); + auto propConversion = d->createPropertiesSearchUsages( + "conversion", code, name, deprecated); + if (!description.empty()) + propConversion.set(common::IdentifiedObject::REMARKS_KEY, + description); auto propMethod = util::PropertyMap().set( common::IdentifiedObject::NAME_KEY, method_name); @@ -2556,7 +2712,7 @@ AuthorityFactory::Private::createProjectedCRSBegin(const std::string &code) { "SELECT name, coordinate_system_auth_name, " "coordinate_system_code, geodetic_crs_auth_name, geodetic_crs_code, " "conversion_auth_name, conversion_code, " - "area_of_use_auth_name, area_of_use_code, text_definition, " + "text_definition, " "deprecated FROM projected_crs WHERE auth_name = ? AND code = ?", code); } @@ -2581,13 +2737,11 @@ AuthorityFactory::Private::createProjectedCRSEnd(const std::string &code, const auto &geodetic_crs_code = row[4]; const auto &conversion_auth_name = row[5]; const auto &conversion_code = row[6]; - const auto &area_of_use_auth_name = row[7]; - const auto &area_of_use_code = row[8]; - const auto &text_definition = row[9]; - const bool deprecated = row[10] == "1"; + const auto &text_definition = row[7]; + const bool deprecated = row[8] == "1"; - auto props = createProperties(code, name, deprecated, - area_of_use_auth_name, area_of_use_code); + auto props = createPropertiesSearchUsages("projected_crs", code, name, + deprecated); if (!text_definition.empty()) { DatabaseContext::Private::RecursionDetector detector(context()); @@ -2670,12 +2824,12 @@ AuthorityFactory::Private::createProjectedCRSEnd(const std::string &code, crs::CompoundCRSNNPtr AuthorityFactory::createCompoundCRS(const std::string &code) const { - auto res = d->runWithCodeParam( - "SELECT name, horiz_crs_auth_name, horiz_crs_code, " - "vertical_crs_auth_name, vertical_crs_code, " - "area_of_use_auth_name, area_of_use_code, deprecated FROM " - "compound_crs WHERE auth_name = ? AND code = ?", - code); + auto res = + d->runWithCodeParam("SELECT name, horiz_crs_auth_name, horiz_crs_code, " + "vertical_crs_auth_name, vertical_crs_code, " + "deprecated FROM " + "compound_crs WHERE auth_name = ? AND code = ?", + code); if (res.empty()) { throw NoSuchAuthorityCodeException("compoundCRS not found", d->authority(), code); @@ -2687,9 +2841,7 @@ AuthorityFactory::createCompoundCRS(const std::string &code) const { const auto &horiz_crs_code = row[2]; const auto &vertical_crs_auth_name = row[3]; const auto &vertical_crs_code = row[4]; - const auto &area_of_use_auth_name = row[5]; - const auto &area_of_use_code = row[6]; - const bool deprecated = row[7] == "1"; + const bool deprecated = row[5] == "1"; auto horizCRS = d->createFactory(horiz_crs_auth_name) @@ -2697,8 +2849,8 @@ AuthorityFactory::createCompoundCRS(const std::string &code) const { auto vertCRS = d->createFactory(vertical_crs_auth_name) ->createVerticalCRS(vertical_crs_code); - auto props = d->createProperties( - code, name, deprecated, area_of_use_auth_name, area_of_use_code); + auto props = d->createPropertiesSearchUsages("compound_crs", code, name, + deprecated); return crs::CompoundCRS::create( props, std::vector<crs::CRSNNPtr>{horizCRS, vertCRS}); } catch (const std::exception &ex) { @@ -2831,10 +2983,10 @@ operation::CoordinateOperationNNPtr AuthorityFactory::createCoordinateOperation( if (type == "helmert_transformation") { auto res = d->runWithCodeParam( - "SELECT name, description, scope, " + "SELECT name, description, " "method_auth_name, method_code, method_name, " "source_crs_auth_name, source_crs_code, target_crs_auth_name, " - "target_crs_code, area_of_use_auth_name, area_of_use_code, " + "target_crs_code, " "accuracy, tx, ty, tz, translation_uom_auth_name, " "translation_uom_code, rx, ry, rz, rotation_uom_auth_name, " "rotation_uom_code, scale_difference, " @@ -2858,7 +3010,6 @@ operation::CoordinateOperationNNPtr AuthorityFactory::createCoordinateOperation( size_t idx = 0; const auto &name = row[idx++]; const auto &description = row[idx++]; - const auto &scope = row[idx++]; const auto &method_auth_name = row[idx++]; const auto &method_code = row[idx++]; const auto &method_name = row[idx++]; @@ -2866,8 +3017,6 @@ operation::CoordinateOperationNNPtr AuthorityFactory::createCoordinateOperation( const auto &source_crs_code = row[idx++]; const auto &target_crs_auth_name = row[idx++]; const auto &target_crs_code = row[idx++]; - const auto &area_of_use_auth_name = row[idx++]; - const auto &area_of_use_code = row[idx++]; const auto &accuracy = row[idx++]; const auto &tx = row[idx++]; @@ -3046,9 +3195,8 @@ operation::CoordinateOperationNNPtr AuthorityFactory::createCoordinateOperation( values.emplace_back(createLength(pz, uom_pivot)); } - auto props = - d->createProperties(code, name, deprecated, description, scope, - area_of_use_auth_name, area_of_use_code); + auto props = d->createPropertiesSearchUsages( + type, code, name, deprecated, description); if (!operation_version.empty()) { props.set(operation::CoordinateOperation::OPERATION_VERSION_KEY, operation_version); @@ -3076,10 +3224,10 @@ operation::CoordinateOperationNNPtr AuthorityFactory::createCoordinateOperation( if (type == "grid_transformation") { auto res = d->runWithCodeParam( - "SELECT name, description, scope, " + "SELECT name, description, " "method_auth_name, method_code, method_name, " "source_crs_auth_name, source_crs_code, target_crs_auth_name, " - "target_crs_code, area_of_use_auth_name, area_of_use_code, " + "target_crs_code, " "accuracy, grid_param_auth_name, grid_param_code, grid_param_name, " "grid_name, " "grid2_param_auth_name, grid2_param_code, grid2_param_name, " @@ -3098,7 +3246,6 @@ operation::CoordinateOperationNNPtr AuthorityFactory::createCoordinateOperation( size_t idx = 0; const auto &name = row[idx++]; const auto &description = row[idx++]; - const auto &scope = row[idx++]; const auto &method_auth_name = row[idx++]; const auto &method_code = row[idx++]; const auto &method_name = row[idx++]; @@ -3106,8 +3253,6 @@ operation::CoordinateOperationNNPtr AuthorityFactory::createCoordinateOperation( const auto &source_crs_code = row[idx++]; const auto &target_crs_auth_name = row[idx++]; const auto &target_crs_code = row[idx++]; - const auto &area_of_use_auth_name = row[idx++]; - const auto &area_of_use_code = row[idx++]; const auto &accuracy = row[idx++]; const auto &grid_param_auth_name = row[idx++]; const auto &grid_param_code = row[idx++]; @@ -3162,9 +3307,8 @@ operation::CoordinateOperationNNPtr AuthorityFactory::createCoordinateOperation( operation::ParameterValue::createFilename(grid2_name)); } - auto props = - d->createProperties(code, name, deprecated, description, scope, - area_of_use_auth_name, area_of_use_code); + auto props = d->createPropertiesSearchUsages( + type, code, name, deprecated, description); if (!operation_version.empty()) { props.set(operation::CoordinateOperation::OPERATION_VERSION_KEY, operation_version); @@ -3197,10 +3341,10 @@ operation::CoordinateOperationNNPtr AuthorityFactory::createCoordinateOperation( std::ostringstream buffer; buffer.imbue(std::locale::classic()); buffer - << "SELECT name, description, scope, " + << "SELECT name, description, " "method_auth_name, method_code, method_name, " "source_crs_auth_name, source_crs_code, target_crs_auth_name, " - "target_crs_code, area_of_use_auth_name, area_of_use_code, " + "target_crs_code, " "accuracy"; constexpr int N_MAX_PARAMS = 7; for (int i = 1; i <= N_MAX_PARAMS; ++i) { @@ -3225,7 +3369,6 @@ operation::CoordinateOperationNNPtr AuthorityFactory::createCoordinateOperation( size_t idx = 0; const auto &name = row[idx++]; const auto &description = row[idx++]; - const auto &scope = row[idx++]; const auto &method_auth_name = row[idx++]; const auto &method_code = row[idx++]; const auto &method_name = row[idx++]; @@ -3233,8 +3376,6 @@ operation::CoordinateOperationNNPtr AuthorityFactory::createCoordinateOperation( const auto &source_crs_code = row[idx++]; const auto &target_crs_auth_name = row[idx++]; const auto &target_crs_code = row[idx++]; - const auto &area_of_use_auth_name = row[idx++]; - const auto &area_of_use_code = row[idx++]; const auto &accuracy = row[idx++]; const size_t base_param_idx = idx; @@ -3279,9 +3420,8 @@ operation::CoordinateOperationNNPtr AuthorityFactory::createCoordinateOperation( d->createFactory(target_crs_auth_name) ->createCoordinateReferenceSystem(target_crs_code); - auto props = - d->createProperties(code, name, deprecated, description, scope, - area_of_use_auth_name, area_of_use_code); + auto props = d->createPropertiesSearchUsages( + type, code, name, deprecated, description); if (!operation_version.empty()) { props.set(operation::CoordinateOperation::OPERATION_VERSION_KEY, operation_version); @@ -3338,10 +3478,10 @@ operation::CoordinateOperationNNPtr AuthorityFactory::createCoordinateOperation( if (allowConcatenated && type == "concatenated_operation") { auto res = d->runWithCodeParam( - "SELECT name, description, scope, " + "SELECT name, description, " "source_crs_auth_name, source_crs_code, " "target_crs_auth_name, target_crs_code, " - "area_of_use_auth_name, area_of_use_code, accuracy, " + "accuracy, " "operation_version, deprecated FROM " "concatenated_operation WHERE auth_name = ? AND code = ?", code); @@ -3362,13 +3502,10 @@ operation::CoordinateOperationNNPtr AuthorityFactory::createCoordinateOperation( size_t idx = 0; const auto &name = row[idx++]; const auto &description = row[idx++]; - const auto &scope = row[idx++]; const auto &source_crs_auth_name = row[idx++]; const auto &source_crs_code = row[idx++]; const auto &target_crs_auth_name = row[idx++]; const auto &target_crs_code = row[idx++]; - const auto &area_of_use_auth_name = row[idx++]; - const auto &area_of_use_code = row[idx++]; const auto &accuracy = row[idx++]; const auto &operation_version = row[idx++]; const auto &deprecated_str = row[idx++]; @@ -3392,9 +3529,8 @@ operation::CoordinateOperationNNPtr AuthorityFactory::createCoordinateOperation( ->createCoordinateReferenceSystem(target_crs_code), operations); - auto props = - d->createProperties(code, name, deprecated, description, scope, - area_of_use_auth_name, area_of_use_code); + auto props = d->createPropertiesSearchUsages( + type, code, name, deprecated, description); if (!operation_version.empty()) { props.set(operation::CoordinateOperation::OPERATION_VERSION_KEY, operation_version); @@ -3602,11 +3738,17 @@ AuthorityFactory::createFromCoordinateReferenceSystemCodes( sql = "SELECT source_crs_auth_name, source_crs_code, " "target_crs_auth_name, target_crs_code, " "cov.auth_name, cov.code, cov.table_name, " - "area.south_lat, area.west_lon, area.north_lat, area.east_lon, " + "extent.south_lat, extent.west_lon, extent.north_lat, " + "extent.east_lon, " "ss.replacement_auth_name, ss.replacement_code FROM " - "coordinate_operation_view cov JOIN area " - "ON cov.area_of_use_auth_name = area.auth_name AND " - "cov.area_of_use_code = area.code " + "coordinate_operation_view cov " + "JOIN usage ON " + "usage.object_table_name = cov.table_name AND " + "usage.object_auth_name = cov.auth_name AND " + "usage.object_code = cov.code " + "JOIN extent " + "ON extent.auth_name = usage.extent_auth_name AND " + "extent.code = usage.extent_code " "LEFT JOIN supersession ss ON " "ss.superseded_table_name = cov.table_name AND " "ss.superseded_auth_name = cov.auth_name AND " @@ -3618,11 +3760,17 @@ AuthorityFactory::createFromCoordinateReferenceSystemCodes( sql = "SELECT source_crs_auth_name, source_crs_code, " "target_crs_auth_name, target_crs_code, " "cov.auth_name, cov.code, cov.table_name, " - "area.south_lat, area.west_lon, area.north_lat, area.east_lon " + "extent.south_lat, extent.west_lon, extent.north_lat, " + "extent.east_lon " "FROM " - "coordinate_operation_view cov JOIN area " - "ON cov.area_of_use_auth_name = area.auth_name AND " - "cov.area_of_use_code = area.code " + "coordinate_operation_view cov " + "JOIN usage ON " + "usage.object_table_name = cov.table_name AND " + "usage.object_auth_name = cov.auth_name AND " + "usage.object_code = cov.code " + "JOIN extent " + "ON extent.auth_name = usage.extent_auth_name AND " + "extent.code = usage.extent_code " "WHERE "; } ListOfParams params; @@ -3955,10 +4103,20 @@ AuthorityFactory::createFromCRSCodesWithIntermediates( "ss2.same_source_target_crs = 1 "); const std::string joinArea( (discardSuperseded ? joinSupersession : std::string()) + - "JOIN area a1 ON v1.area_of_use_auth_name = a1.auth_name " - "AND v1.area_of_use_code = a1.code " - "JOIN area a2 ON v2.area_of_use_auth_name = a2.auth_name " - "AND v2.area_of_use_code = a2.code "); + "JOIN usage u1 ON " + "u1.object_table_name = v1.table_name AND " + "u1.object_auth_name = v1.auth_name AND " + "u1.object_code = v1.code " + "JOIN extent a1 " + "ON a1.auth_name = u1.extent_auth_name AND " + "a1.code = u1.extent_code " + "JOIN usage u2 ON " + "u2.object_table_name = v2.table_name AND " + "u2.object_auth_name = v2.auth_name AND " + "u2.object_code = v2.code " + "JOIN extent a2 " + "ON a2.auth_name = u2.extent_auth_name AND " + "a2.code = u2.extent_code "); const std::string orderBy( "ORDER BY (CASE WHEN accuracy1 is NULL THEN 1 ELSE 0 END) + " "(CASE WHEN accuracy2 is NULL THEN 1 ELSE 0 END), " @@ -4399,11 +4557,20 @@ AuthorityFactory::createBetweenGeodeticCRSWithDatumBasedIntermediates( "AND g_v2s.code = v2.source_crs_code " "AND g_v2t.auth_name = v2.target_crs_auth_name " "AND g_v2t.code = v2.target_crs_code "); - const std::string joinArea( - "JOIN area a1 ON v1.area_of_use_auth_name = a1.auth_name " - "AND v1.area_of_use_code = a1.code " - "JOIN area a2 ON v2.area_of_use_auth_name = a2.auth_name " - "AND v2.area_of_use_code = a2.code "); + const std::string joinArea("JOIN usage u1 ON " + "u1.object_table_name = v1.table_name AND " + "u1.object_auth_name = v1.auth_name AND " + "u1.object_code = v1.code " + "JOIN extent a1 " + "ON a1.auth_name = u1.extent_auth_name AND " + "a1.code = u1.extent_code " + "JOIN usage u2 ON " + "u2.object_table_name = v2.table_name AND " + "u2.object_auth_name = v2.auth_name AND " + "u2.object_code = v2.code " + "JOIN extent a2 " + "ON a2.auth_name = u2.extent_auth_name AND " + "a2.code = u2.extent_code "); auto params = ListOfParams{sourceCRSAuthName, sourceCRSCode, targetCRSAuthName, targetCRSCode}; @@ -5094,9 +5261,17 @@ AuthorityFactory::getAuthorityCodes(const ObjectType &type, case ObjectType::GEODETIC_REFERENCE_FRAME: sql = "SELECT code FROM geodetic_datum WHERE "; break; + case ObjectType::DYNAMIC_GEODETIC_REFERENCE_FRAME: + sql = "SELECT code FROM geodetic_datum WHERE " + "frame_reference_epoch IS NOT NULL AND "; + break; case ObjectType::VERTICAL_REFERENCE_FRAME: sql = "SELECT code FROM vertical_datum WHERE "; break; + case ObjectType::DYNAMIC_VERTICAL_REFERENCE_FRAME: + sql = "SELECT code FROM vertical_datum WHERE " + "frame_reference_epoch IS NOT NULL AND "; + break; case ObjectType::CRS: sql = "SELECT code FROM crs_view WHERE "; break; @@ -5206,13 +5381,23 @@ AuthorityFactory::getDescriptionText(const std::string &code) const { * @throw FactoryException */ std::list<AuthorityFactory::CRSInfo> AuthorityFactory::getCRSInfoList() const { + + const auto getSqlArea = [](const std::string &table_name) { + return "JOIN usage u ON " + "u.object_table_name = '" + + table_name + "' AND " + "u.object_auth_name = c.auth_name AND " + "u.object_code = c.code " + "JOIN extent a " + "ON a.auth_name = u.extent_auth_name AND " + "a.code = u.extent_code "; + }; + std::string sql = "SELECT c.auth_name, c.code, c.name, c.type, " "c.deprecated, " "a.west_lon, a.south_lat, a.east_lon, a.north_lat, " - "a.name, NULL FROM geodetic_crs c " - "JOIN area a ON " - "c.area_of_use_auth_name = a.auth_name AND " - "c.area_of_use_code = a.code"; + "a.description, NULL FROM geodetic_crs c " + + getSqlArea("geodetic_crs"); ListOfParams params; if (d->hasAuthorityRestriction()) { sql += " WHERE c.auth_name = ?"; @@ -5222,10 +5407,9 @@ std::list<AuthorityFactory::CRSInfo> AuthorityFactory::getCRSInfoList() const { sql += "SELECT c.auth_name, c.code, c.name, 'projected', " "c.deprecated, " "a.west_lon, a.south_lat, a.east_lon, a.north_lat, " - "a.name, cm.name AS conversion_method_name FROM projected_crs c " - "JOIN area a ON " - "c.area_of_use_auth_name = a.auth_name AND " - "c.area_of_use_code = a.code " + "a.description, cm.name AS conversion_method_name FROM " + "projected_crs c " + + getSqlArea("projected_crs") + "LEFT JOIN conversion_table conv ON " "c.conversion_auth_name = conv.auth_name AND " "c.conversion_code = conv.code " @@ -5240,10 +5424,8 @@ std::list<AuthorityFactory::CRSInfo> AuthorityFactory::getCRSInfoList() const { sql += "SELECT c.auth_name, c.code, c.name, 'vertical', " "c.deprecated, " "a.west_lon, a.south_lat, a.east_lon, a.north_lat, " - "a.name, NULL FROM vertical_crs c " - "JOIN area a ON " - "c.area_of_use_auth_name = a.auth_name AND " - "c.area_of_use_code = a.code"; + "a.description, NULL FROM vertical_crs c " + + getSqlArea("vertical_crs"); if (d->hasAuthorityRestriction()) { sql += " WHERE c.auth_name = ?"; params.emplace_back(d->authority()); @@ -5252,10 +5434,8 @@ std::list<AuthorityFactory::CRSInfo> AuthorityFactory::getCRSInfoList() const { sql += "SELECT c.auth_name, c.code, c.name, 'compound', " "c.deprecated, " "a.west_lon, a.south_lat, a.east_lon, a.north_lat, " - "a.name, NULL FROM compound_crs c " - "JOIN area a ON " - "c.area_of_use_auth_name = a.auth_name AND " - "c.area_of_use_code = a.code"; + "a.description, NULL FROM compound_crs c " + + getSqlArea("compound_crs"); if (d->hasAuthorityRestriction()) { sql += " WHERE c.auth_name = ?"; params.emplace_back(d->authority()); @@ -5416,7 +5596,7 @@ std::string AuthorityFactory::getOfficialNameFromAlias( if (res.empty()) { // shouldn't happen normally return std::string(); } - return res.front()[0]; + return removeEnsembleSuffix(res.front()[0]); } } return std::string(); @@ -5466,7 +5646,7 @@ std::string AuthorityFactory::getOfficialNameFromAlias( outTableName = row[1]; outAuthName = row[2]; outCode = row[3]; - return row[0]; + return removeEnsembleSuffix(row[0]); } } @@ -5571,10 +5751,18 @@ AuthorityFactory::createObjectsFromNameEx( res.emplace_back( TableType("geodetic_datum", std::string())); break; + case ObjectType::DYNAMIC_GEODETIC_REFERENCE_FRAME: + res.emplace_back( + TableType("geodetic_datum", "frame_reference_epoch")); + break; case ObjectType::VERTICAL_REFERENCE_FRAME: res.emplace_back( TableType("vertical_datum", std::string())); break; + case ObjectType::DYNAMIC_VERTICAL_REFERENCE_FRAME: + res.emplace_back( + TableType("vertical_datum", "frame_reference_epoch")); + break; case ObjectType::CRS: res.emplace_back(TableType("geodetic_crs", std::string())); res.emplace_back(TableType("projected_crs", std::string())); @@ -5653,9 +5841,13 @@ AuthorityFactory::createObjectsFromNameEx( sql += tableNameTypePair.first; sql += " WHERE 1 = 1 "; if (!tableNameTypePair.second.empty()) { - sql += "AND type = '"; - sql += tableNameTypePair.second; - sql += "' "; + if (tableNameTypePair.second == "frame_reference_epoch") { + sql += "AND frame_reference_epoch IS NOT NULL "; + } else { + sql += "AND type = '"; + sql += tableNameTypePair.second; + sql += "' "; + } } if (deprecated) { sql += "AND deprecated = 1 "; @@ -5683,9 +5875,13 @@ AuthorityFactory::createObjectsFromNameEx( sql += tableNameTypePair.first; sql += "' "; if (!tableNameTypePair.second.empty()) { - sql += "AND ov.type = '"; - sql += tableNameTypePair.second; - sql += "' "; + if (tableNameTypePair.second == "frame_reference_epoch") { + sql += "AND ov.frame_reference_epoch IS NOT NULL "; + } else { + sql += "AND ov.type = '"; + sql += tableNameTypePair.second; + sql += "' "; + } } if (deprecated) { sql += "AND ov.deprecated = 1 "; @@ -5925,7 +6121,7 @@ std::list<std::pair<std::string, std::string>> AuthorityFactory::listAreaOfUseFromName(const std::string &name, bool approximateMatch) const { std::string sql( - "SELECT auth_name, code FROM area WHERE deprecated = 0 AND "); + "SELECT auth_name, code FROM extent WHERE deprecated = 0 AND "); ListOfParams params; if (d->hasAuthorityRestriction()) { sql += " auth_name = ? AND "; @@ -1249,6 +1249,23 @@ PJ PROJ_DLL *proj_crs_get_sub_crs(PJ_CONTEXT *ctx, const PJ *crs, int index); PJ PROJ_DLL *proj_crs_get_datum(PJ_CONTEXT *ctx, const PJ *crs); +PJ PROJ_DLL *proj_crs_get_datum_ensemble(PJ_CONTEXT *ctx, const PJ *crs); + +PJ PROJ_DLL *proj_crs_get_datum_forced(PJ_CONTEXT *ctx, const PJ *crs); + +int PROJ_DLL proj_datum_ensemble_get_member_count(PJ_CONTEXT *ctx, + const PJ *datum_ensemble); + +double PROJ_DLL proj_datum_ensemble_get_accuracy(PJ_CONTEXT *ctx, + const PJ *datum_ensemble); + +PJ PROJ_DLL *proj_datum_ensemble_get_member(PJ_CONTEXT *ctx, + const PJ *datum_ensemble, + int member_index); + +double PROJ_DLL proj_dynamic_datum_get_frame_reference_epoch(PJ_CONTEXT *ctx, + const PJ *datum); + PJ PROJ_DLL *proj_crs_get_coordinate_system(PJ_CONTEXT *ctx, const PJ *crs); PJ_COORDINATE_SYSTEM_TYPE PROJ_DLL proj_cs_get_type(PJ_CONTEXT *ctx, diff --git a/src/proj_experimental.h b/src/proj_experimental.h index 50f1fb9f..05e49451 100644 --- a/src/proj_experimental.h +++ b/src/proj_experimental.h @@ -168,7 +168,7 @@ PJ PROJ_DLL *proj_create_geographic_crs( PJ PROJ_DLL *proj_create_geographic_crs_from_datum( PJ_CONTEXT *ctx, const char *crs_name, - PJ* datum, + PJ* datum_or_datum_ensemble, PJ* ellipsoidal_cs); PJ PROJ_DLL *proj_create_geocentric_crs( @@ -187,7 +187,7 @@ PJ PROJ_DLL *proj_create_geocentric_crs( PJ PROJ_DLL *proj_create_geocentric_crs_from_datum( PJ_CONTEXT *ctx, const char *crs_name, - const PJ* datum, + const PJ* datum_or_datum_ensemble, const char *linear_units, double linear_units_conv); |
