diff options
| -rw-r--r-- | include/proj/io.hpp | 32 | ||||
| -rw-r--r-- | src/iso19111/c_api.cpp | 212 | ||||
| -rw-r--r-- | src/iso19111/factory.cpp | 161 | ||||
| -rw-r--r-- | src/proj.h | 81 | ||||
| -rw-r--r-- | test/unit/test_c_api.cpp | 206 | ||||
| -rw-r--r-- | test/unit/test_factory.cpp | 99 |
6 files changed, 768 insertions, 23 deletions
diff --git a/include/proj/io.hpp b/include/proj/io.hpp index 4120d707..aedf0457 100644 --- a/include/proj/io.hpp +++ b/include/proj/io.hpp @@ -888,6 +888,38 @@ class PROJ_GCC_DLL AuthorityFactory { PROJ_DLL std::string getDescriptionText(const std::string &code) const; + /** CRS information */ + struct CRSInfo { + /** Authority name */ + std::string authName{}; + /** Code */ + std::string code{}; + /** Name */ + std::string name{}; + /** Type */ + ObjectType type{ObjectType::CRS}; + /** Whether the object is deprecated */ + bool deprecated{}; + /** Whereas the west_lon_degree, south_lat_degree, east_lon_degree and + * north_lat_degree fields are valid. */ + bool bbox_valid{}; + /** Western-most longitude of the area of use, in degrees. */ + double west_lon_degree{}; + /** Southern-most latitude of the area of use, in degrees. */ + double south_lat_degree{}; + /** Eastern-most longitude of the area of use, in degrees. */ + double east_lon_degree{}; + /** Northern-most latitude of the area of use, in degrees. */ + double north_lat_degree{}; + /** Name of the area of use. */ + std::string areaName{}; + /** Name of the projection method for a projected CRS. Might be empty + * even for projected CRS in some cases. */ + std::string projectionMethodName{}; + }; + + PROJ_DLL std::list<CRSInfo> getCRSInfoList() const; + // non-standard PROJ_DLL static AuthorityFactoryNNPtr create(const DatabaseContextNNPtr &context, diff --git a/src/iso19111/c_api.cpp b/src/iso19111/c_api.cpp index de11f181..5982da47 100644 --- a/src/iso19111/c_api.cpp +++ b/src/iso19111/c_api.cpp @@ -34,6 +34,7 @@ #include <cstdarg> #include <cstring> #include <map> +#include <new> #include <utility> #include <vector> @@ -1895,6 +1896,8 @@ PROJ_STRING_LIST proj_get_authorities_from_database(PJ_CONTEXT *ctx) { * * @return a NULL terminated list of NUL-terminated strings that must be * freed with proj_string_list_destroy(), or NULL in case of error. + * + * @see proj_get_crs_info_list_from_database() */ PROJ_STRING_LIST proj_get_codes_from_database(PJ_CONTEXT *ctx, const char *auth_name, @@ -1932,6 +1935,215 @@ void proj_string_list_destroy(PROJ_STRING_LIST list) { // --------------------------------------------------------------------------- +/** \brief Instanciate a default set of parameters to be used by + * proj_get_crs_list(). + * + * @return a new object to free with proj_get_crs_list_parameters_destroy() */ +PROJ_CRS_LIST_PARAMETERS *proj_get_crs_list_parameters_create() { + auto ret = new (std::nothrow) PROJ_CRS_LIST_PARAMETERS(); + if (ret) { + ret->types = nullptr; + ret->typesCount = 0; + ret->crs_area_of_use_contains_bbox = TRUE; + ret->bbox_valid = FALSE; + ret->west_lon_degree = 0.0; + ret->south_lat_degree = 0.0; + ret->east_lon_degree = 0.0; + ret->north_lat_degree = 0.0; + ret->allow_deprecated = FALSE; + } + return ret; +} + +// --------------------------------------------------------------------------- + +/** \brief Destroy an object returned by proj_get_crs_list_parameters_create() + */ +void proj_get_crs_list_parameters_destroy(PROJ_CRS_LIST_PARAMETERS *params) { + delete params; +} + +// --------------------------------------------------------------------------- + +/** \brief Enumerate CRS objects from the database, taking into account various + * criteria. + * + * The returned object is an array of PROJ_CRS_INFO* pointers, whose last + * entry is NULL. This array should be freed with proj_crs_list_destroy() + * + * When no filter parameters are set, this is functionnaly equivalent to + * proj_get_crs_info_list_from_database(), instanciating a PJ* object for each + * of the proj_create_from_database() and retrieving information with the + * various getters. However this function will be much faster. + * + * @param ctx PROJ context, or NULL for default context + * @param auth_name Authority name, used to restrict the search. + * Or NULL for all authorities. + * @param params Additional criteria, or NULL. If not-NULL, params SHOULD + * have been allocated by proj_get_crs_list_parameters_create(), as the + * PROJ_CRS_LIST_PARAMETERS structure might grow over time. + * @param out_result_count Output parameter pointing to an integer to receive + * the size of the result list. Might be NULL + * @return an array of PROJ_CRS_INFO* pointers to be freed with + * proj_crs_list_destroy(), or NULL in case of error. + */ +PROJ_CRS_INFO ** +proj_get_crs_info_list_from_database(PJ_CONTEXT *ctx, const char *auth_name, + const PROJ_CRS_LIST_PARAMETERS *params, + int *out_result_count) { + SANITIZE_CTX(ctx); + PROJ_CRS_INFO **ret = nullptr; + int i = 0; + try { + auto factory = AuthorityFactory::create(getDBcontext(ctx), + auth_name ? auth_name : ""); + auto list = factory->getCRSInfoList(); + ret = new PROJ_CRS_INFO *[list.size() + 1]; + GeographicBoundingBoxPtr bbox; + if (params && params->bbox_valid) { + bbox = GeographicBoundingBox::create( + params->west_lon_degree, params->south_lat_degree, + params->east_lon_degree, params->north_lat_degree) + .as_nullable(); + } + for (const auto &info : list) { + auto type = PJ_TYPE_CRS; + if (info.type == AuthorityFactory::ObjectType::GEOGRAPHIC_2D_CRS) { + type = PJ_TYPE_GEOGRAPHIC_2D_CRS; + } else if (info.type == + AuthorityFactory::ObjectType::GEOGRAPHIC_3D_CRS) { + type = PJ_TYPE_GEOGRAPHIC_3D_CRS; + } else if (info.type == + AuthorityFactory::ObjectType::GEOCENTRIC_CRS) { + type = PJ_TYPE_GEOCENTRIC_CRS; + } else if (info.type == + AuthorityFactory::ObjectType::PROJECTED_CRS) { + type = PJ_TYPE_PROJECTED_CRS; + } else if (info.type == + AuthorityFactory::ObjectType::VERTICAL_CRS) { + type = PJ_TYPE_VERTICAL_CRS; + } else if (info.type == + AuthorityFactory::ObjectType::COMPOUND_CRS) { + type = PJ_TYPE_COMPOUND_CRS; + } + if (params && params->typesCount) { + bool typeValid = false; + for (size_t j = 0; j < params->typesCount; j++) { + if (params->types[j] == type) { + typeValid = true; + break; + } else if (params->types[j] == PJ_TYPE_GEOGRAPHIC_CRS && + (type == PJ_TYPE_GEOGRAPHIC_2D_CRS || + type == PJ_TYPE_GEOGRAPHIC_3D_CRS)) { + typeValid = true; + break; + } else if (params->types[j] == PJ_TYPE_GEODETIC_CRS && + (type == PJ_TYPE_GEOCENTRIC_CRS || + type == PJ_TYPE_GEOGRAPHIC_2D_CRS || + type == PJ_TYPE_GEOGRAPHIC_3D_CRS)) { + typeValid = true; + break; + } + } + if (!typeValid) { + continue; + } + } + if (params && !params->allow_deprecated && info.deprecated) { + continue; + } + if (params && params->bbox_valid) { + if (!info.bbox_valid) { + continue; + } + if (info.west_lon_degree <= info.east_lon_degree && + params->west_lon_degree <= params->east_lon_degree) { + if (params->crs_area_of_use_contains_bbox) { + if (params->west_lon_degree < info.west_lon_degree || + params->east_lon_degree > info.east_lon_degree || + params->south_lat_degree < info.south_lat_degree || + params->north_lat_degree > info.north_lat_degree) { + continue; + } + } else { + if (info.east_lon_degree < params->west_lon_degree || + info.west_lon_degree > params->east_lon_degree || + info.north_lat_degree < params->south_lat_degree || + info.south_lat_degree > params->north_lat_degree) { + continue; + } + } + } else { + auto crsExtent = GeographicBoundingBox::create( + info.west_lon_degree, info.south_lat_degree, + info.east_lon_degree, info.north_lat_degree); + if (params->crs_area_of_use_contains_bbox) { + if (!crsExtent->contains(NN_NO_CHECK(bbox))) { + continue; + } + } else { + if (!bbox->intersects(crsExtent)) { + continue; + } + } + } + } + + ret[i] = new PROJ_CRS_INFO; + ret[i]->auth_name = pj_strdup(info.authName.c_str()); + ret[i]->code = pj_strdup(info.code.c_str()); + ret[i]->name = pj_strdup(info.name.c_str()); + ret[i]->type = type; + ret[i]->deprecated = info.deprecated; + ret[i]->bbox_valid = info.bbox_valid; + ret[i]->west_lon_degree = info.west_lon_degree; + ret[i]->south_lat_degree = info.south_lat_degree; + ret[i]->east_lon_degree = info.east_lon_degree; + ret[i]->north_lat_degree = info.north_lat_degree; + ret[i]->area_name = pj_strdup(info.areaName.c_str()); + ret[i]->projection_method_name = + info.projectionMethodName.empty() + ? nullptr + : pj_strdup(info.projectionMethodName.c_str()); + i++; + } + ret[i] = nullptr; + if (out_result_count) + *out_result_count = i; + return ret; + } catch (const std::exception &e) { + proj_log_error(ctx, __FUNCTION__, e.what()); + if (ret) { + ret[i + 1] = nullptr; + proj_crs_list_destroy(ret); + } + if (out_result_count) + *out_result_count = 0; + } + return nullptr; +} + +// --------------------------------------------------------------------------- + +/** \brief Destroy the result returned by + * proj_get_crs_info_list_from_database(). + */ +void proj_crs_list_destroy(PROJ_CRS_INFO **list) { + if (list) { + for (int i = 0; list[i] != nullptr; i++) { + pj_dalloc(list[i]->auth_name); + pj_dalloc(list[i]->code); + pj_dalloc(list[i]->name); + pj_dalloc(list[i]->area_name); + pj_dalloc(list[i]->projection_method_name); + delete list[i]; + } + delete[] list; + } +} + +// --------------------------------------------------------------------------- + /** \brief Return the Conversion of a DerivedCRS (such as a ProjectedCRS), * or the Transformation from the baseCRS to the hubCRS of a BoundCRS * diff --git a/src/iso19111/factory.cpp b/src/iso19111/factory.cpp index a3047f8b..81abcdf1 100644 --- a/src/iso19111/factory.cpp +++ b/src/iso19111/factory.cpp @@ -77,9 +77,17 @@ namespace io { //! @cond Doxygen_Suppress -#define GEOG_2D "'geographic 2D'" -#define GEOG_3D "'geographic 3D'" -#define GEOCENTRIC "'geocentric'" +// CRS subtypes +#define GEOG_2D "geographic 2D" +#define GEOG_3D "geographic 3D" +#define GEOCENTRIC "geocentric" +#define PROJECTED "projected" +#define VERTICAL "vertical" +#define COMPOUND "compound" + +#define GEOG_2D_SINGLE_QUOTED "'geographic 2D'" +#define GEOG_3D_SINGLE_QUOTED "'geographic 3D'" +#define GEOCENTRIC_SINGLE_QUOTED "'geocentric'" // --------------------------------------------------------------------------- @@ -1051,7 +1059,7 @@ DatabaseContext::getAliasFromOfficialName(const std::string &officialName, sql += replaceAll(tableName, "\"", "\"\""); sql += "\" WHERE name = ?"; if (tableName == "geodetic_crs") { - sql += " AND type = " GEOG_2D; + sql += " AND type = " GEOG_2D_SINGLE_QUOTED; } auto res = d->run(sql, {officialName}); if (res.empty()) { @@ -2067,7 +2075,8 @@ AuthorityFactory::createGeodeticCRS(const std::string &code, "deprecated FROM " "geodetic_crs WHERE auth_name = ? AND code = ?"); if (geographicOnly) { - sql += " AND type in (" GEOG_2D "," GEOG_3D ")"; + sql += " AND type in (" GEOG_2D_SINGLE_QUOTED "," GEOG_3D_SINGLE_QUOTED + ")"; } auto res = d->runWithCodeParam(sql, code); if (res.empty()) { @@ -2124,15 +2133,14 @@ AuthorityFactory::createGeodeticCRS(const std::string &code, auto ellipsoidalCS = util::nn_dynamic_pointer_cast<cs::EllipsoidalCS>(cs); - if ((type == "geographic 2D" || type == "geographic 3D") && - ellipsoidalCS) { + if ((type == GEOG_2D || type == GEOG_3D) && ellipsoidalCS) { auto crsRet = crs::GeographicCRS::create( props, datum, NN_NO_CHECK(ellipsoidalCS)); d->context()->d->cache(cacheKey, crsRet); return crsRet; } auto geocentricCS = util::nn_dynamic_pointer_cast<cs::CartesianCS>(cs); - if (type == "geocentric" && geocentricCS) { + if (type == GEOCENTRIC && geocentricCS) { auto crsRet = crs::GeodeticCRS::create(props, datum, NN_NO_CHECK(geocentricCS)); d->context()->d->cache(cacheKey, crsRet); @@ -2477,17 +2485,16 @@ AuthorityFactory::createCoordinateReferenceSystem(const std::string &code, code); } const auto &type = res.front()[0]; - if (type == "geographic 2D" || type == "geographic 3D" || - type == "geocentric") { + if (type == GEOG_2D || type == GEOG_3D || type == GEOCENTRIC) { return createGeodeticCRS(code); } - if (type == "vertical") { + if (type == VERTICAL) { return createVerticalCRS(code); } - if (type == "projected") { + if (type == PROJECTED) { return createProjectedCRS(code); } - if (allowCompound && type == "compound") { + if (allowCompound && type == COMPOUND) { return createCompoundCRS(code); } throw FactoryException("unhandled CRS type: " + type); @@ -3782,17 +3789,22 @@ AuthorityFactory::getAuthorityCodes(const ObjectType &type, sql = "SELECT code FROM geodetic_crs WHERE "; break; case ObjectType::GEOCENTRIC_CRS: - sql = "SELECT code FROM geodetic_crs WHERE type = " GEOCENTRIC " AND "; + sql = "SELECT code FROM geodetic_crs WHERE type " + "= " GEOCENTRIC_SINGLE_QUOTED " AND "; break; case ObjectType::GEOGRAPHIC_CRS: - sql = "SELECT code FROM geodetic_crs WHERE type IN (" GEOG_2D - "," GEOG_3D ") AND "; + sql = "SELECT code FROM geodetic_crs WHERE type IN " + "(" GEOG_2D_SINGLE_QUOTED "," GEOG_3D_SINGLE_QUOTED ") AND "; break; case ObjectType::GEOGRAPHIC_2D_CRS: - sql = "SELECT code FROM geodetic_crs WHERE type = " GEOG_2D " AND "; + sql = + "SELECT code FROM geodetic_crs WHERE type = " GEOG_2D_SINGLE_QUOTED + " AND "; break; case ObjectType::GEOGRAPHIC_3D_CRS: - sql = "SELECT code FROM geodetic_crs WHERE type = " GEOG_3D " AND "; + sql = + "SELECT code FROM geodetic_crs WHERE type = " GEOG_3D_SINGLE_QUOTED + " AND "; break; case ObjectType::VERTICAL_CRS: sql = "SELECT code FROM vertical_crs WHERE "; @@ -3858,6 +3870,107 @@ AuthorityFactory::getDescriptionText(const std::string &code) const { // --------------------------------------------------------------------------- +/** \brief Return a list of information on CRS objects + * + * This is functionnaly equivalent of listing the codes from an authority, + * instanciating + * a CRS object for each of them and getting the information from this CRS + * object, but this implementation has much less overhead. + * + * @throw FactoryException + */ +std::list<AuthorityFactory::CRSInfo> AuthorityFactory::getCRSInfoList() const { + 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"; + ListOfParams params; + if (d->hasAuthorityRestriction()) { + sql += " WHERE c.auth_name = ?"; + params.emplace_back(d->authority()); + } + sql += " UNION ALL "; + 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, conv.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 " + "LEFT JOIN conversion conv ON " + "c.conversion_auth_name = conv.auth_name AND " + "c.conversion_code = conv.code"; + if (d->hasAuthorityRestriction()) { + sql += " WHERE c.auth_name = ?"; + params.emplace_back(d->authority()); + } + sql += " UNION ALL "; + 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"; + if (d->hasAuthorityRestriction()) { + sql += " WHERE c.auth_name = ?"; + params.emplace_back(d->authority()); + } + sql += " UNION ALL "; + 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"; + if (d->hasAuthorityRestriction()) { + sql += " WHERE c.auth_name = ?"; + params.emplace_back(d->authority()); + } + auto sqlRes = d->run(sql, params); + std::list<AuthorityFactory::CRSInfo> res; + for (const auto &row : sqlRes) { + AuthorityFactory::CRSInfo info; + info.authName = row[0]; + info.code = row[1]; + info.name = row[2]; + const auto &type = row[3]; + if (type == GEOG_2D) { + info.type = AuthorityFactory::ObjectType::GEOGRAPHIC_2D_CRS; + } else if (type == GEOG_3D) { + info.type = AuthorityFactory::ObjectType::GEOGRAPHIC_3D_CRS; + } else if (type == GEOCENTRIC) { + info.type = AuthorityFactory::ObjectType::GEOCENTRIC_CRS; + } else if (type == PROJECTED) { + info.type = AuthorityFactory::ObjectType::PROJECTED_CRS; + } else if (type == VERTICAL) { + info.type = AuthorityFactory::ObjectType::VERTICAL_CRS; + } else if (type == COMPOUND) { + info.type = AuthorityFactory::ObjectType::COMPOUND_CRS; + } + info.deprecated = row[4] == "1"; + if (row[5].empty()) { + info.bbox_valid = false; + } else { + info.bbox_valid = true; + info.west_lon_degree = c_locale_stod(row[5]); + info.south_lat_degree = c_locale_stod(row[6]); + info.east_lon_degree = c_locale_stod(row[7]); + info.north_lat_degree = c_locale_stod(row[8]); + } + info.areaName = row[9]; + info.projectionMethodName = row[10]; + res.emplace_back(info); + } + return res; +} + +// --------------------------------------------------------------------------- + /** \brief Gets the official name from a possibly alias name. * * @param aliasedName Alias name. @@ -4056,23 +4169,25 @@ AuthorityFactory::createObjectsFromName( break; case ObjectType::GEOCENTRIC_CRS: addToListStringWithOR(otherConditions, - "(table_name = " GEOCENTRIC " AND " - "type = " GEOCENTRIC ")"); + "(table_name = " GEOCENTRIC_SINGLE_QUOTED + " AND " + "type = " GEOCENTRIC_SINGLE_QUOTED ")"); break; case ObjectType::GEOGRAPHIC_CRS: addToListStringWithOR(otherConditions, "(table_name = 'geodetic_crs' AND " - "type IN (" GEOG_2D "," GEOG_3D "))"); + "type IN (" GEOG_2D_SINGLE_QUOTED + "," GEOG_3D_SINGLE_QUOTED "))"); break; case ObjectType::GEOGRAPHIC_2D_CRS: addToListStringWithOR(otherConditions, "(table_name = 'geodetic_crs' AND " - "type = " GEOG_2D ")"); + "type = " GEOG_2D_SINGLE_QUOTED ")"); break; case ObjectType::GEOGRAPHIC_3D_CRS: addToListStringWithOR(otherConditions, "(table_name = 'geodetic_crs' AND " - "type = " GEOG_3D ")"); + "type = " GEOG_3D_SINGLE_QUOTED ")"); break; case ObjectType::PROJECTED_CRS: addToListString(tableNameList, "'projected_crs'"); @@ -645,6 +645,74 @@ typedef enum PJ_CS_TYPE_TEMPORALMEASURE } PJ_COORDINATE_SYSTEM_TYPE; +/** \brief Structure given overall description of a CRS. + * + * This structure may grow over time, and should not be directly allocated by + * client code. + */ +typedef struct +{ + /** Authority name. */ + char* auth_name; + /** Object code. */ + char* code; + /** Object name. */ + char* name; + /** Object type. */ + PJ_TYPE type; + /** Whether the object is deprecated */ + int deprecated; + /** Whereas the west_lon_degree, south_lat_degree, east_lon_degree and + * north_lat_degree fields are valid. */ + int bbox_valid; + /** Western-most longitude of the area of use, in degrees. */ + double west_lon_degree; + /** Southern-most latitude of the area of use, in degrees. */ + double south_lat_degree; + /** Eastern-most longitude of the area of use, in degrees. */ + double east_lon_degree; + /** Northern-most latitude of the area of use, in degrees. */ + double north_lat_degree; + /** Name of the area of use. */ + char* area_name; + /** Name of the projection method for a projected CRS. Might be NULL even + *for projected CRS in some cases. */ + char* projection_method_name; +} PROJ_CRS_INFO; + +/** \brief Structure describing optional parameters for proj_get_crs_list(); + * + * This structure may grow over time, and should not be directly allocated by + * client code. + */ +typedef struct +{ + /** Array of allowed object types. Should be NULL if all types are allowed*/ + const PJ_TYPE* types; + /** Size of types. Should be 0 if all types are allowed*/ + size_t typesCount; + + /** If TRUE and bbox_valid == TRUE, then only CRS whose area of use + * entirely contains the specified bounding box will be returned. + * If FALSE and bbox_valid == TRUE, then only CRS whose area of use + * intersects the specified bounding box will be returned. + */ + int crs_area_of_use_contains_bbox; + /** To set to TRUE so that west_lon_degree, south_lat_degree, + * east_lon_degree and north_lat_degree fields are taken into account. */ + int bbox_valid; + /** Western-most longitude of the area of use, in degrees. */ + double west_lon_degree; + /** Southern-most latitude of the area of use, in degrees. */ + double south_lat_degree; + /** Eastern-most longitude of the area of use, in degrees. */ + double east_lon_degree; + /** Northern-most latitude of the area of use, in degrees. */ + double north_lat_degree; + + /** Whether deprecated objects are allowed. Default to FALSE. */ + int allow_deprecated; +} PROJ_CRS_LIST_PARAMETERS; /**@}*/ @@ -774,6 +842,19 @@ PROJ_STRING_LIST PROJ_DLL proj_get_codes_from_database(PJ_CONTEXT *ctx, PJ_TYPE type, int allow_deprecated); +PROJ_CRS_LIST_PARAMETERS PROJ_DLL *proj_get_crs_list_parameters_create(void); + +void PROJ_DLL proj_get_crs_list_parameters_destroy( + PROJ_CRS_LIST_PARAMETERS* params); + +PROJ_CRS_INFO PROJ_DLL **proj_get_crs_info_list_from_database( + PJ_CONTEXT *ctx, + const char *auth_name, + const PROJ_CRS_LIST_PARAMETERS* params, + int *out_result_count); + +void PROJ_DLL proj_crs_list_destroy(PROJ_CRS_INFO** list); + /* ------------------------------------------------------------------------- */ diff --git a/test/unit/test_c_api.cpp b/test/unit/test_c_api.cpp index ff29185f..be43f715 100644 --- a/test/unit/test_c_api.cpp +++ b/test/unit/test_c_api.cpp @@ -3022,4 +3022,210 @@ TEST_F(CApi, proj_create_cartesian_2D_cs) { } } +// --------------------------------------------------------------------------- + +TEST_F(CApi, proj_get_crs_info_list_from_database) { + { proj_crs_list_destroy(nullptr); } + + { proj_get_crs_list_parameters_destroy(nullptr); } + + // All null parameters + { + auto list = proj_get_crs_info_list_from_database(nullptr, nullptr, + nullptr, nullptr); + ASSERT_NE(list, nullptr); + ASSERT_NE(list[0], nullptr); + EXPECT_NE(list[0]->auth_name, nullptr); + EXPECT_NE(list[0]->code, nullptr); + EXPECT_NE(list[0]->name, nullptr); + proj_crs_list_destroy(list); + } + + // Default parameters + { + int result_count = 0; + auto params = proj_get_crs_list_parameters_create(); + auto list = proj_get_crs_info_list_from_database(m_ctxt, "EPSG", params, + &result_count); + proj_get_crs_list_parameters_destroy(params); + ASSERT_NE(list, nullptr); + EXPECT_GT(result_count, 1); + EXPECT_EQ(list[result_count], nullptr); + bool found4326 = false; + bool found4978 = false; + bool found4979 = false; + bool found32631 = false; + bool found3855 = false; + bool found3901 = false; + for (int i = 0; i < result_count; i++) { + auto code = std::string(list[i]->code); + if (code == "4326") { + found4326 = true; + EXPECT_EQ(std::string(list[i]->auth_name), "EPSG"); + EXPECT_EQ(std::string(list[i]->name), "WGS 84"); + EXPECT_EQ(list[i]->type, PJ_TYPE_GEOGRAPHIC_2D_CRS); + EXPECT_EQ(list[i]->deprecated, 0); + EXPECT_EQ(list[i]->bbox_valid, 1); + EXPECT_EQ(list[i]->west_lon_degree, -180.0); + EXPECT_EQ(list[i]->south_lat_degree, -90.0); + EXPECT_EQ(list[i]->east_lon_degree, 180.0); + EXPECT_EQ(list[i]->north_lat_degree, 90.0); + EXPECT_EQ(std::string(list[i]->area_name), "World"); + EXPECT_EQ(list[i]->projection_method_name, nullptr); + } else if (code == "4978") { + found4978 = true; + EXPECT_EQ(list[i]->type, PJ_TYPE_GEOCENTRIC_CRS); + } else if (code == "4979") { + found4979 = true; + EXPECT_EQ(list[i]->type, PJ_TYPE_GEOGRAPHIC_3D_CRS); + } else if (code == "32631") { + found32631 = true; + EXPECT_EQ(list[i]->type, PJ_TYPE_PROJECTED_CRS); + EXPECT_EQ(std::string(list[i]->projection_method_name), + "Transverse Mercator"); + } else if (code == "3855") { + found3855 = true; + EXPECT_EQ(list[i]->type, PJ_TYPE_VERTICAL_CRS); + } else if (code == "3901") { + found3901 = true; + EXPECT_EQ(list[i]->type, PJ_TYPE_COMPOUND_CRS); + } + EXPECT_EQ(list[i]->deprecated, 0); + } + EXPECT_TRUE(found4326); + EXPECT_TRUE(found4978); + EXPECT_TRUE(found4979); + EXPECT_TRUE(found32631); + EXPECT_TRUE(found3855); + EXPECT_TRUE(found3901); + proj_crs_list_destroy(list); + } + + // Filter on only geodetic crs + { + int result_count = 0; + auto params = proj_get_crs_list_parameters_create(); + params->typesCount = 1; + auto type = PJ_TYPE_GEODETIC_CRS; + params->types = &type; + auto list = proj_get_crs_info_list_from_database(m_ctxt, "EPSG", params, + &result_count); + bool foundGeog2D = false; + bool foundGeog3D = false; + bool foundGeocentric = false; + for (int i = 0; i < result_count; i++) { + foundGeog2D |= list[i]->type == PJ_TYPE_GEOGRAPHIC_2D_CRS; + foundGeog3D |= list[i]->type == PJ_TYPE_GEOGRAPHIC_3D_CRS; + foundGeocentric |= list[i]->type == PJ_TYPE_GEOCENTRIC_CRS; + EXPECT_TRUE(list[i]->type == PJ_TYPE_GEOGRAPHIC_2D_CRS || + list[i]->type == PJ_TYPE_GEOGRAPHIC_3D_CRS || + list[i]->type == PJ_TYPE_GEOCENTRIC_CRS); + } + EXPECT_TRUE(foundGeog2D); + EXPECT_TRUE(foundGeog3D); + EXPECT_TRUE(foundGeocentric); + proj_get_crs_list_parameters_destroy(params); + proj_crs_list_destroy(list); + } + + // Filter on only geographic crs + { + int result_count = 0; + auto params = proj_get_crs_list_parameters_create(); + params->typesCount = 1; + auto type = PJ_TYPE_GEOGRAPHIC_CRS; + params->types = &type; + auto list = proj_get_crs_info_list_from_database(m_ctxt, "EPSG", params, + &result_count); + bool foundGeog2D = false; + bool foundGeog3D = false; + for (int i = 0; i < result_count; i++) { + foundGeog2D |= list[i]->type == PJ_TYPE_GEOGRAPHIC_2D_CRS; + foundGeog3D |= list[i]->type == PJ_TYPE_GEOGRAPHIC_3D_CRS; + EXPECT_TRUE(list[i]->type == PJ_TYPE_GEOGRAPHIC_2D_CRS || + list[i]->type == PJ_TYPE_GEOGRAPHIC_3D_CRS); + } + EXPECT_TRUE(foundGeog2D); + EXPECT_TRUE(foundGeog3D); + proj_get_crs_list_parameters_destroy(params); + proj_crs_list_destroy(list); + } + + // Filter on only geographic 2D crs and projected CRS + { + int result_count = 0; + auto params = proj_get_crs_list_parameters_create(); + params->typesCount = 2; + const PJ_TYPE types[] = {PJ_TYPE_GEOGRAPHIC_2D_CRS, + PJ_TYPE_PROJECTED_CRS}; + params->types = types; + auto list = proj_get_crs_info_list_from_database(m_ctxt, "EPSG", params, + &result_count); + bool foundGeog2D = false; + bool foundProjected = false; + for (int i = 0; i < result_count; i++) { + foundGeog2D |= list[i]->type == PJ_TYPE_GEOGRAPHIC_2D_CRS; + foundProjected |= list[i]->type == PJ_TYPE_PROJECTED_CRS; + EXPECT_TRUE(list[i]->type == PJ_TYPE_GEOGRAPHIC_2D_CRS || + list[i]->type == PJ_TYPE_PROJECTED_CRS); + } + EXPECT_TRUE(foundGeog2D); + EXPECT_TRUE(foundProjected); + proj_get_crs_list_parameters_destroy(params); + proj_crs_list_destroy(list); + } + + // Filter on bbox (inclusion) + { + int result_count = 0; + auto params = proj_get_crs_list_parameters_create(); + params->bbox_valid = 1; + params->west_lon_degree = 2; + params->south_lat_degree = 49; + params->east_lon_degree = 2.1; + params->north_lat_degree = 49.1; + params->typesCount = 1; + auto type = PJ_TYPE_PROJECTED_CRS; + params->types = &type; + auto list = proj_get_crs_info_list_from_database(m_ctxt, "EPSG", params, + &result_count); + ASSERT_NE(list, nullptr); + EXPECT_GT(result_count, 1); + for (int i = 0; i < result_count; i++) { + EXPECT_LE(list[i]->west_lon_degree, params->west_lon_degree); + EXPECT_LE(list[i]->south_lat_degree, params->south_lat_degree); + EXPECT_GE(list[i]->east_lon_degree, params->east_lon_degree); + EXPECT_GE(list[i]->north_lat_degree, params->north_lat_degree); + } + proj_get_crs_list_parameters_destroy(params); + proj_crs_list_destroy(list); + } + + // Filter on bbox (intersection) + { + int result_count = 0; + auto params = proj_get_crs_list_parameters_create(); + params->bbox_valid = 1; + params->west_lon_degree = 2; + params->south_lat_degree = 49; + params->east_lon_degree = 2.1; + params->north_lat_degree = 49.1; + params->crs_area_of_use_contains_bbox = 0; + params->typesCount = 1; + auto type = PJ_TYPE_PROJECTED_CRS; + params->types = &type; + auto list = proj_get_crs_info_list_from_database(m_ctxt, "EPSG", params, + &result_count); + ASSERT_NE(list, nullptr); + EXPECT_GT(result_count, 1); + for (int i = 0; i < result_count; i++) { + EXPECT_LE(list[i]->west_lon_degree, params->east_lon_degree); + EXPECT_LE(list[i]->south_lat_degree, params->north_lat_degree); + EXPECT_GE(list[i]->east_lon_degree, params->west_lon_degree); + EXPECT_GE(list[i]->north_lat_degree, params->south_lat_degree); + } + proj_get_crs_list_parameters_destroy(params); + proj_crs_list_destroy(list); + } +} } // namespace diff --git a/test/unit/test_factory.cpp b/test/unit/test_factory.cpp index 95436c75..ce019079 100644 --- a/test/unit/test_factory.cpp +++ b/test/unit/test_factory.cpp @@ -2810,4 +2810,103 @@ TEST(factory, listAreaOfUseFromName) { } } +// --------------------------------------------------------------------------- + +TEST(factory, getCRSInfoList) { + auto ctxt = DatabaseContext::create(); + { + auto factory = AuthorityFactory::create(ctxt, std::string()); + auto list = factory->getCRSInfoList(); + EXPECT_GT(list.size(), 1U); + bool foundEPSG = false; + bool foundIGNF = true; + bool found4326 = false; + for (const auto &info : list) { + foundEPSG |= info.authName == "EPSG"; + foundIGNF |= info.authName == "IGNF"; + if (info.authName == "EPSG" && info.code == "4326") { + found4326 = true; + } + } + EXPECT_TRUE(foundEPSG); + EXPECT_TRUE(foundIGNF); + EXPECT_TRUE(found4326); + } + { + auto factory = AuthorityFactory::create(ctxt, "EPSG"); + auto list = factory->getCRSInfoList(); + EXPECT_GT(list.size(), 1U); + bool found4326 = false; + bool found4978 = false; + bool found4979 = false; + bool found32631 = false; + bool found3855 = false; + bool found6871 = false; + for (const auto &info : list) { + EXPECT_EQ(info.authName, "EPSG"); + if (info.code == "4326") { + EXPECT_EQ(info.name, "WGS 84"); + EXPECT_EQ(info.type, + AuthorityFactory::ObjectType::GEOGRAPHIC_2D_CRS); + EXPECT_EQ(info.deprecated, false); + EXPECT_EQ(info.bbox_valid, true); + EXPECT_EQ(info.west_lon_degree, -180.0); + EXPECT_EQ(info.south_lat_degree, -90.0); + EXPECT_EQ(info.east_lon_degree, 180.0); + EXPECT_EQ(info.north_lat_degree, 90.0); + EXPECT_EQ(info.areaName, "World"); + EXPECT_TRUE(info.projectionMethodName.empty()); + found4326 = true; + } else if (info.code == "4296") { // Soudan - deprecated + EXPECT_EQ(info.bbox_valid, false); + EXPECT_EQ(info.west_lon_degree, 0.0); + EXPECT_EQ(info.south_lat_degree, 0.0); + EXPECT_EQ(info.east_lon_degree, 0.0); + EXPECT_EQ(info.north_lat_degree, 0.0); + } else if (info.code == "4978") { + EXPECT_EQ(info.name, "WGS 84"); + EXPECT_EQ(info.type, + AuthorityFactory::ObjectType::GEOCENTRIC_CRS); + found4978 = true; + } else if (info.code == "4979") { + EXPECT_EQ(info.name, "WGS 84"); + EXPECT_EQ(info.type, + AuthorityFactory::ObjectType::GEOGRAPHIC_3D_CRS); + found4979 = true; + } else if (info.code == "32631") { + EXPECT_EQ(info.name, "WGS 84 / UTM zone 31N"); + EXPECT_EQ(info.type, + AuthorityFactory::ObjectType::PROJECTED_CRS); + EXPECT_EQ(info.deprecated, false); + EXPECT_EQ(info.bbox_valid, true); + EXPECT_EQ(info.west_lon_degree, 0.0); + EXPECT_EQ(info.south_lat_degree, 0.0); + EXPECT_EQ(info.east_lon_degree, 6.0); + EXPECT_EQ(info.north_lat_degree, 84.0); + EXPECT_EQ(info.areaName, "World - N hemisphere - 0\xC2\xB0" + "E to 6\xC2\xB0" + "E - by country"); + EXPECT_EQ(info.projectionMethodName, "Transverse Mercator"); + found32631 = true; + } else if (info.code == "3855") { + EXPECT_EQ(info.name, "EGM2008 height"); + EXPECT_EQ(info.type, + AuthorityFactory::ObjectType::VERTICAL_CRS); + found3855 = true; + } else if (info.code == "6871") { + EXPECT_EQ(info.name, + "WGS 84 / Pseudo-Mercator + EGM2008 geoid height"); + EXPECT_EQ(info.type, + AuthorityFactory::ObjectType::COMPOUND_CRS); + found6871 = true; + } + } + EXPECT_TRUE(found4326); + EXPECT_TRUE(found4978); + EXPECT_TRUE(found4979); + EXPECT_TRUE(found32631); + EXPECT_TRUE(found3855); + EXPECT_TRUE(found6871); + } +} } // namespace |
