Skip to content

Commit 190bfb2

Browse files
committed
cs2cs / proj_create_crs_to_crs(): fix regression with geocentric CRS
Related to use case of OSGeo/gdal#7753 When a transformation involved a datum shift with a grid and a geocentric CRS as source/target, since PROJ 9.2.0 and the changes for the only_best mode, even in just warning mode, if the grid of the "best" transformation was missing, then the transformation would completely fail, whereas with earlier versions it would fallback to a ballpark transformation. So restore that mode, and actually enhance the operation selection logic, by allowing to select operations based on their area of validity even if the input coordinate are geocentric (in which case a conversion of them to geographic is done).
1 parent 9d3737b commit 190bfb2

File tree

5 files changed

+306
-55
lines changed

5 files changed

+306
-55
lines changed

src/4D_api.cpp

Lines changed: 194 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -245,8 +245,24 @@ int pj_get_suggested_operation(PJ_CONTEXT *,
245245
const auto &alt = opList[i];
246246
bool spatialCriterionOK = false;
247247
if (direction == PJ_FWD) {
248-
if (coord.xyzt.x >= alt.minxSrc && coord.xyzt.y >= alt.minySrc &&
249-
coord.xyzt.x <= alt.maxxSrc && coord.xyzt.y <= alt.maxySrc) {
248+
if (alt.pjSrcGeocentricToLonLat) {
249+
if (alt.minxSrc == -180 && alt.minySrc == -90 &&
250+
alt.maxxSrc == 180 && alt.maxySrc == 90) {
251+
spatialCriterionOK = true;
252+
} else {
253+
PJ_COORD tmp = coord;
254+
pj_fwd4d(tmp, alt.pjSrcGeocentricToLonLat);
255+
if (tmp.xyzt.x >= alt.minxSrc &&
256+
tmp.xyzt.y >= alt.minySrc &&
257+
tmp.xyzt.x <= alt.maxxSrc &&
258+
tmp.xyzt.y <= alt.maxySrc) {
259+
spatialCriterionOK = true;
260+
}
261+
}
262+
} else if (coord.xyzt.x >= alt.minxSrc &&
263+
coord.xyzt.y >= alt.minySrc &&
264+
coord.xyzt.x <= alt.maxxSrc &&
265+
coord.xyzt.y <= alt.maxySrc) {
250266
spatialCriterionOK = true;
251267
} else if (alt.srcIsLonLatDegree && coord.xyzt.y >= alt.minySrc &&
252268
coord.xyzt.y <= alt.maxySrc) {
@@ -264,8 +280,24 @@ int pj_get_suggested_operation(PJ_CONTEXT *,
264280
}
265281
}
266282
} else {
267-
if (coord.xyzt.x >= alt.minxDst && coord.xyzt.y >= alt.minyDst &&
268-
coord.xyzt.x <= alt.maxxDst && coord.xyzt.y <= alt.maxyDst) {
283+
if (alt.pjDstGeocentricToLonLat) {
284+
if (alt.minxDst == -180 && alt.minyDst == -90 &&
285+
alt.maxxDst == 180 && alt.maxyDst == 90) {
286+
spatialCriterionOK = true;
287+
} else {
288+
PJ_COORD tmp = coord;
289+
pj_fwd4d(tmp, alt.pjDstGeocentricToLonLat);
290+
if (tmp.xyzt.x >= alt.minxDst &&
291+
tmp.xyzt.y >= alt.minyDst &&
292+
tmp.xyzt.x <= alt.maxxDst &&
293+
tmp.xyzt.y <= alt.maxyDst) {
294+
spatialCriterionOK = true;
295+
}
296+
}
297+
} else if (coord.xyzt.x >= alt.minxDst &&
298+
coord.xyzt.y >= alt.minyDst &&
299+
coord.xyzt.x <= alt.maxxDst &&
300+
coord.xyzt.y <= alt.maxyDst) {
269301
spatialCriterionOK = true;
270302
} else if (alt.dstIsLonLatDegree && coord.xyzt.y >= alt.minyDst &&
271303
coord.xyzt.y <= alt.maxyDst) {
@@ -320,6 +352,14 @@ int pj_get_suggested_operation(PJ_CONTEXT *,
320352
}
321353

322354
//! @cond Doxygen_Suppress
355+
/**************************************************************************************/
356+
PJCoordOperation::~PJCoordOperation() {
357+
/**************************************************************************************/
358+
proj_destroy(pj);
359+
proj_destroy(pjSrcGeocentricToLonLat);
360+
proj_destroy(pjDstGeocentricToLonLat);
361+
}
362+
323363
/**************************************************************************************/
324364
bool PJCoordOperation::isInstantiable() const {
325365
/**************************************************************************************/
@@ -1659,11 +1699,11 @@ static void reproject_bbox(PJ *pjGeogToCrs, double west_lon, double south_lat,
16591699
}
16601700

16611701
/*****************************************************************************/
1662-
static PJ *add_coord_op_to_list(int idxInOriginalList, PJ *op, double west_lon,
1663-
double south_lat, double east_lon,
1664-
double north_lat, PJ *pjGeogToSrc,
1665-
PJ *pjGeogToDst, bool isOffshore,
1666-
std::vector<PJCoordOperation> &altCoordOps) {
1702+
static PJ *add_coord_op_to_list(
1703+
int idxInOriginalList, PJ *op, double west_lon, double south_lat,
1704+
double east_lon, double north_lat, PJ *pjGeogToSrc, PJ *pjGeogToDst,
1705+
const PJ *pjSrcGeocentricToLonLat, const PJ *pjDstGeocentricToLonLat,
1706+
bool isOffshore, std::vector<PJCoordOperation> &altCoordOps) {
16671707
/*****************************************************************************/
16681708

16691709
double minxSrc;
@@ -1675,19 +1715,35 @@ static PJ *add_coord_op_to_list(int idxInOriginalList, PJ *op, double west_lon,
16751715
double maxxDst;
16761716
double maxyDst;
16771717

1678-
reproject_bbox(pjGeogToSrc, west_lon, south_lat, east_lon, north_lat,
1679-
minxSrc, minySrc, maxxSrc, maxySrc);
1680-
reproject_bbox(pjGeogToDst, west_lon, south_lat, east_lon, north_lat,
1681-
minxDst, minyDst, maxxDst, maxyDst);
1718+
if (pjSrcGeocentricToLonLat) {
1719+
minxSrc = west_lon;
1720+
minySrc = south_lat;
1721+
maxxSrc = east_lon;
1722+
maxySrc = north_lat;
1723+
} else {
1724+
reproject_bbox(pjGeogToSrc, west_lon, south_lat, east_lon, north_lat,
1725+
minxSrc, minySrc, maxxSrc, maxySrc);
1726+
}
1727+
1728+
if (pjDstGeocentricToLonLat) {
1729+
minxDst = west_lon;
1730+
minyDst = south_lat;
1731+
maxxDst = east_lon;
1732+
maxyDst = north_lat;
1733+
} else {
1734+
reproject_bbox(pjGeogToDst, west_lon, south_lat, east_lon, north_lat,
1735+
minxDst, minyDst, maxxDst, maxyDst);
1736+
}
16821737

16831738
if (minxSrc <= maxxSrc && minxDst <= maxxDst) {
16841739
const char *c_name = proj_get_name(op);
16851740
std::string name(c_name ? c_name : "");
16861741

16871742
const double accuracy = proj_coordoperation_get_accuracy(op->ctx, op);
1688-
altCoordOps.emplace_back(idxInOriginalList, minxSrc, minySrc, maxxSrc,
1689-
maxySrc, minxDst, minyDst, maxxDst, maxyDst,
1690-
op, name, accuracy, isOffshore);
1743+
altCoordOps.emplace_back(
1744+
idxInOriginalList, minxSrc, minySrc, maxxSrc, maxySrc, minxDst,
1745+
minyDst, maxxDst, maxyDst, op, name, accuracy, isOffshore,
1746+
pjSrcGeocentricToLonLat, pjDstGeocentricToLonLat);
16911747
op = nullptr;
16921748
}
16931749
return op;
@@ -1798,6 +1854,57 @@ static PJ *create_operation_to_geog_crs(PJ_CONTEXT *ctx, const PJ *crs) {
17981854
return opGeogToCrs;
17991855
}
18001856

1857+
/*****************************************************************************/
1858+
static PJ *create_operation_geocentric_crs_to_geog_crs(PJ_CONTEXT *ctx,
1859+
const PJ *geocentric_crs)
1860+
/*****************************************************************************/
1861+
{
1862+
assert(proj_get_type(geocentric_crs) == PJ_TYPE_GEOCENTRIC_CRS);
1863+
1864+
auto datum = proj_crs_get_datum_forced(ctx, geocentric_crs);
1865+
assert(datum);
1866+
auto cs = proj_create_ellipsoidal_2D_cs(ctx, PJ_ELLPS2D_LONGITUDE_LATITUDE,
1867+
nullptr, 0);
1868+
auto ellps = proj_get_ellipsoid(ctx, datum);
1869+
proj_destroy(datum);
1870+
double semi_major_metre = 0;
1871+
double inv_flattening = 0;
1872+
proj_ellipsoid_get_parameters(ctx, ellps, &semi_major_metre, nullptr,
1873+
nullptr, &inv_flattening);
1874+
// It is critical to set the prime meridian to 0
1875+
auto lon_lat_crs = proj_create_geographic_crs(
1876+
ctx, "unnamed crs", "unnamed datum", proj_get_name(ellps),
1877+
semi_major_metre, inv_flattening, "Reference prime meridian", 0,
1878+
nullptr, 0, cs);
1879+
proj_destroy(ellps);
1880+
proj_destroy(cs);
1881+
1882+
// Create the transformation from this geocentric CRS to the lon-lat one
1883+
auto operation_ctx = proj_create_operation_factory_context(ctx, nullptr);
1884+
proj_operation_factory_context_set_spatial_criterion(
1885+
ctx, operation_ctx, PROJ_SPATIAL_CRITERION_PARTIAL_INTERSECTION);
1886+
proj_operation_factory_context_set_grid_availability_use(
1887+
ctx, operation_ctx,
1888+
PROJ_GRID_AVAILABILITY_DISCARD_OPERATION_IF_MISSING_GRID);
1889+
auto op_list =
1890+
proj_create_operations(ctx, geocentric_crs, lon_lat_crs, operation_ctx);
1891+
proj_operation_factory_context_destroy(operation_ctx);
1892+
proj_destroy(lon_lat_crs);
1893+
1894+
const int nOpCount = op_list == nullptr ? 0 : proj_list_get_count(op_list);
1895+
if (nOpCount != 1) {
1896+
proj_context_log_debug(ctx, "Cannot compute transformation from "
1897+
"geocentric CRS to geographic CRS");
1898+
proj_list_destroy(op_list);
1899+
return nullptr;
1900+
}
1901+
1902+
auto op = proj_list_get(ctx, op_list, 0);
1903+
assert(op);
1904+
proj_list_destroy(op_list);
1905+
return op;
1906+
}
1907+
18011908
/*****************************************************************************/
18021909
PJ *proj_create_crs_to_crs(PJ_CONTEXT *ctx, const char *source_crs,
18031910
const char *target_crs, PJ_AREA *area) {
@@ -1846,21 +1953,46 @@ pj_create_prepared_operations(PJ_CONTEXT *ctx, const PJ *source_crs,
18461953
const PJ *target_crs, PJ_OBJ_LIST *op_list)
18471954
/*****************************************************************************/
18481955
{
1849-
auto pjGeogToSrc = create_operation_to_geog_crs(ctx, source_crs);
1850-
if (!pjGeogToSrc) {
1851-
proj_context_log_debug(ctx,
1852-
"Cannot create transformation from geographic "
1853-
"CRS of source CRS to source CRS");
1854-
return {};
1956+
PJ *pjGeogToSrc = nullptr;
1957+
PJ *pjSrcGeocentricToLonLat = nullptr;
1958+
if (proj_get_type(source_crs) == PJ_TYPE_GEOCENTRIC_CRS) {
1959+
pjSrcGeocentricToLonLat =
1960+
create_operation_geocentric_crs_to_geog_crs(ctx, source_crs);
1961+
if (!pjSrcGeocentricToLonLat) {
1962+
// shouldn't happen
1963+
return {};
1964+
}
1965+
} else {
1966+
pjGeogToSrc = create_operation_to_geog_crs(ctx, source_crs);
1967+
if (!pjGeogToSrc) {
1968+
proj_context_log_debug(
1969+
ctx, "Cannot create transformation from geographic "
1970+
"CRS of source CRS to source CRS");
1971+
return {};
1972+
}
18551973
}
18561974

1857-
auto pjGeogToDst = create_operation_to_geog_crs(ctx, target_crs);
1858-
if (!pjGeogToDst) {
1859-
proj_context_log_debug(ctx,
1860-
"Cannot create transformation from geographic "
1861-
"CRS of target CRS to target CRS");
1862-
proj_destroy(pjGeogToSrc);
1863-
return {};
1975+
PJ *pjGeogToDst = nullptr;
1976+
PJ *pjDstGeocentricToLonLat = nullptr;
1977+
if (proj_get_type(target_crs) == PJ_TYPE_GEOCENTRIC_CRS) {
1978+
pjDstGeocentricToLonLat =
1979+
create_operation_geocentric_crs_to_geog_crs(ctx, target_crs);
1980+
if (!pjDstGeocentricToLonLat) {
1981+
// shouldn't happen
1982+
proj_destroy(pjSrcGeocentricToLonLat);
1983+
proj_destroy(pjGeogToSrc);
1984+
return {};
1985+
}
1986+
} else {
1987+
pjGeogToDst = create_operation_to_geog_crs(ctx, target_crs);
1988+
if (!pjGeogToDst) {
1989+
proj_context_log_debug(
1990+
ctx, "Cannot create transformation from geographic "
1991+
"CRS of target CRS to target CRS");
1992+
proj_destroy(pjSrcGeocentricToLonLat);
1993+
proj_destroy(pjGeogToSrc);
1994+
return {};
1995+
}
18641996
}
18651997

18661998
try {
@@ -1888,18 +2020,21 @@ pj_create_prepared_operations(PJ_CONTEXT *ctx, const PJ *source_crs,
18882020

18892021
const bool isOffshore = areaName && strstr(areaName, "- offshore");
18902022
if (west_lon <= east_lon) {
1891-
op = add_coord_op_to_list(i, op, west_lon, south_lat, east_lon,
1892-
north_lat, pjGeogToSrc, pjGeogToDst,
1893-
isOffshore, preparedOpList);
2023+
op = add_coord_op_to_list(
2024+
i, op, west_lon, south_lat, east_lon, north_lat,
2025+
pjGeogToSrc, pjGeogToDst, pjSrcGeocentricToLonLat,
2026+
pjDstGeocentricToLonLat, isOffshore, preparedOpList);
18942027
} else {
18952028
auto op_clone = proj_clone(ctx, op);
18962029

1897-
op = add_coord_op_to_list(i, op, west_lon, south_lat, 180,
1898-
north_lat, pjGeogToSrc, pjGeogToDst,
1899-
isOffshore, preparedOpList);
2030+
op = add_coord_op_to_list(
2031+
i, op, west_lon, south_lat, 180, north_lat, pjGeogToSrc,
2032+
pjGeogToDst, pjSrcGeocentricToLonLat,
2033+
pjDstGeocentricToLonLat, isOffshore, preparedOpList);
19002034
op_clone = add_coord_op_to_list(
19012035
i, op_clone, -180, south_lat, east_lon, north_lat,
1902-
pjGeogToSrc, pjGeogToDst, isOffshore, preparedOpList);
2036+
pjGeogToSrc, pjGeogToDst, pjSrcGeocentricToLonLat,
2037+
pjDstGeocentricToLonLat, isOffshore, preparedOpList);
19032038
proj_destroy(op_clone);
19042039
}
19052040

@@ -1908,10 +2043,14 @@ pj_create_prepared_operations(PJ_CONTEXT *ctx, const PJ *source_crs,
19082043

19092044
proj_destroy(pjGeogToSrc);
19102045
proj_destroy(pjGeogToDst);
2046+
proj_destroy(pjSrcGeocentricToLonLat);
2047+
proj_destroy(pjDstGeocentricToLonLat);
19112048
return preparedOpList;
19122049
} catch (const std::exception &) {
19132050
proj_destroy(pjGeogToSrc);
19142051
proj_destroy(pjGeogToDst);
2052+
proj_destroy(pjSrcGeocentricToLonLat);
2053+
proj_destroy(pjDstGeocentricToLonLat);
19152054
return {};
19162055
}
19172056
}
@@ -2070,13 +2209,10 @@ PJ *proj_create_crs_to_crs_from_pj(PJ_CONTEXT *ctx, const PJ *source_crs,
20702209
}
20712210

20722211
const auto backup_errno = proj_context_errno(ctx);
2073-
if ((P == nullptr ||
2074-
(op_count == 1 &&
2075-
(!mayNeedToReRunWithDiscardMissing ||
2076-
errorIfBestTransformationNotAvailable ||
2077-
singleOpIsInstanciable == static_cast<int>(true))) ||
2078-
proj_get_type(source_crs) == PJ_TYPE_GEOCENTRIC_CRS ||
2079-
proj_get_type(target_crs) == PJ_TYPE_GEOCENTRIC_CRS)) {
2212+
if (P == nullptr ||
2213+
(op_count == 1 && (!mayNeedToReRunWithDiscardMissing ||
2214+
errorIfBestTransformationNotAvailable ||
2215+
singleOpIsInstanciable == static_cast<int>(true)))) {
20802216
proj_list_destroy(op_list);
20812217
ctx->forceOver = false;
20822218

@@ -2835,21 +2971,28 @@ static bool isSpecialCaseForWGS84_to_GDA2020(const std::string &opName) {
28352971
return opName.find("GDA2020 to WGS 84 (2)") != std::string::npos;
28362972
}
28372973

2838-
PJCoordOperation::PJCoordOperation(int idxInOriginalListIn, double minxSrcIn,
2839-
double minySrcIn, double maxxSrcIn,
2840-
double maxySrcIn, double minxDstIn,
2841-
double minyDstIn, double maxxDstIn,
2842-
double maxyDstIn, PJ *pjIn,
2843-
const std::string &nameIn, double accuracyIn,
2844-
bool isOffshoreIn)
2974+
PJCoordOperation::PJCoordOperation(
2975+
int idxInOriginalListIn, double minxSrcIn, double minySrcIn,
2976+
double maxxSrcIn, double maxySrcIn, double minxDstIn, double minyDstIn,
2977+
double maxxDstIn, double maxyDstIn, PJ *pjIn, const std::string &nameIn,
2978+
double accuracyIn, bool isOffshoreIn, const PJ *pjSrcGeocentricToLonLatIn,
2979+
const PJ *pjDstGeocentricToLonLatIn)
28452980
: idxInOriginalList(idxInOriginalListIn), minxSrc(minxSrcIn),
28462981
minySrc(minySrcIn), maxxSrc(maxxSrcIn), maxySrc(maxySrcIn),
28472982
minxDst(minxDstIn), minyDst(minyDstIn), maxxDst(maxxDstIn),
28482983
maxyDst(maxyDstIn), pj(pjIn), name(nameIn), accuracy(accuracyIn),
28492984
isOffshore(isOffshoreIn),
28502985
isPriorityOp(isSpecialCaseForNAD83_to_NAD83HARN(name) ||
28512986
isSpecialCaseForGDA94_to_WGS84(name) ||
2852-
isSpecialCaseForWGS84_to_GDA2020(name)) {
2987+
isSpecialCaseForWGS84_to_GDA2020(name)),
2988+
pjSrcGeocentricToLonLat(pjSrcGeocentricToLonLatIn
2989+
? proj_clone(pjSrcGeocentricToLonLatIn->ctx,
2990+
pjSrcGeocentricToLonLatIn)
2991+
: nullptr),
2992+
pjDstGeocentricToLonLat(pjDstGeocentricToLonLatIn
2993+
? proj_clone(pjDstGeocentricToLonLatIn->ctx,
2994+
pjDstGeocentricToLonLatIn)
2995+
: nullptr) {
28532996
const auto IsLonLatOrLatLon = [](const PJ *crs, bool &isLonLatDegreeOut,
28542997
bool &isLatLonDegreeOut) {
28552998
const auto eType = proj_get_type(crs);

src/iso19111/c_api.cpp

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9073,7 +9073,8 @@ PJ *proj_normalize_for_visualization(PJ_CONTEXT *ctx, const PJ *obj) {
90739073
alt.idxInOriginalList, minxSrc, minySrc, maxxSrc,
90749074
maxySrc, minxDst, minyDst, maxxDst, maxyDst,
90759075
pjNormalized, co->nameStr(), alt.accuracy,
9076-
alt.isOffshore);
9076+
alt.isOffshore, alt.pjSrcGeocentricToLonLat,
9077+
alt.pjDstGeocentricToLonLat);
90779078
}
90789079
}
90799080
return pjNew.release();

0 commit comments

Comments
 (0)