Skip to content

Commit 98b36d1

Browse files
nyalldawsonlbartoletti
authored andcommitted
Always fallback to default coordinate operations when bounding box transfrom fails
Bounding box transforms are inherently approximate, so we can safely just fallback to the proj default operation if the user-specified operation fails when transforming a bounding box. This fixes cases where the user-specified operation involves a grid shift and the bounds to transform falls outside of the grid. In this case proj_trans_bounds fails. By falling back to a proj default operation and re-trying then we get a valid approximate transformation of the bounding box. Fixes map rendering issues when users are using a grid shift operation to transform layers to map CRS Fixes #60737 Fixes #60753
1 parent fcc833f commit 98b36d1

File tree

2 files changed

+66
-3
lines changed

2 files changed

+66
-3
lines changed

src/core/proj/qgscoordinatetransform.cpp

+21-3
Original file line numberDiff line numberDiff line change
@@ -676,9 +676,27 @@ QgsRectangle QgsCoordinateTransform::transformBoundingBox( const QgsRectangle &r
676676
proj_errno_reset( projData );
677677
// proj documentation recommends 21 points for densification
678678
constexpr int DENSIFY_POINTS = 21;
679-
const int projResult = proj_trans_bounds( projContext, projData, ( direction == Qgis::TransformDirection::Forward && !d->mIsReversed ) || ( direction == Qgis::TransformDirection::Reverse && d->mIsReversed ) ? PJ_FWD : PJ_INV,
680-
xMin, yMin, xMax, yMax,
681-
&transXMin, &transYMin, &transXMax, &transYMax, DENSIFY_POINTS );
679+
int projResult = proj_trans_bounds( projContext, projData, ( direction == Qgis::TransformDirection::Forward && !d->mIsReversed ) || ( direction == Qgis::TransformDirection::Reverse && d->mIsReversed ) ? PJ_FWD : PJ_INV,
680+
xMin, yMin, xMax, yMax,
681+
&transXMin, &transYMin, &transXMax, &transYMax, DENSIFY_POINTS );
682+
683+
if ( ( projResult != 1
684+
|| !std::isfinite( transXMin )
685+
|| !std::isfinite( transXMax )
686+
|| !std::isfinite( transYMin )
687+
|| !std::isfinite( transYMax ) )
688+
&& ( d->mAvailableOpCount > 1 || d->mAvailableOpCount == -1 ) // only use fallbacks if more than one operation is possible -- otherwise we've already tried it and it failed
689+
)
690+
{
691+
// fail #1 -- try with getting proj to auto-pick an appropriate coordinate operation for the points
692+
if ( PJ *transform = d->threadLocalFallbackProjData() )
693+
{
694+
projResult = proj_trans_bounds( projContext, transform, ( direction == Qgis::TransformDirection::Forward && !d->mIsReversed ) || ( direction == Qgis::TransformDirection::Reverse && d->mIsReversed ) ? PJ_FWD : PJ_INV,
695+
xMin, yMin, xMax, yMax,
696+
&transXMin, &transYMin, &transXMax, &transYMax, DENSIFY_POINTS );
697+
}
698+
}
699+
682700
if ( projResult != 1
683701
|| !std::isfinite( transXMin )
684702
|| !std::isfinite( transXMax )

tests/src/python/test_qgscoordinatetransform.py

+45
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,51 @@ def testTransformQgsRectangle_Regression17600(self):
127127
myTransformedExtentReverse.yMinimum(), myExtent.yMinimum()
128128
)
129129

130+
def test_transform_bounding_box_grid(self):
131+
"""
132+
This test assumes the ca_nrc_NA83SCRS.tif grid is available on the system!
133+
"""
134+
transform = QgsCoordinateTransform(
135+
QgsCoordinateReferenceSystem("EPSG:4269"),
136+
QgsCoordinateReferenceSystem("EPSG:3857"),
137+
QgsCoordinateTransformContext(),
138+
)
139+
res = transform.transformBoundingBox(
140+
QgsRectangle(
141+
-123.65020876249999,
142+
45.987175336410544,
143+
-101.22289073749998,
144+
62.961980263589439,
145+
)
146+
)
147+
self.assertAlmostEqual(res.xMinimum(), -13764678, -2)
148+
self.assertAlmostEqual(res.yMinimum(), 5778294, -2)
149+
self.assertAlmostEqual(res.xMaximum(), -11268080, -2)
150+
self.assertAlmostEqual(res.yMaximum(), 9090934, -2)
151+
152+
transform = QgsCoordinateTransform(
153+
QgsCoordinateReferenceSystem("EPSG:4269"),
154+
QgsCoordinateReferenceSystem("EPSG:3857"),
155+
QgsCoordinateTransformContext(),
156+
)
157+
# force use of grid shift operation. This will fail as the source rect is outside of the grid bounds, but we should silently
158+
# fall back to the non-grid operation
159+
transform.setCoordinateOperation(
160+
"+proj=pipeline +step +proj=unitconvert +xy_in=deg +xy_out=rad +step +proj=hgridshift +grids=ca_nrc_NA83SCRS.tif +step +proj=webmerc +lat_0=0 +lon_0=0 +x_0=0 +y_0=0 +ellps=WGS84"
161+
)
162+
res = transform.transformBoundingBox(
163+
QgsRectangle(
164+
-123.65020876249999,
165+
45.987175336410544,
166+
-101.22289073749998,
167+
62.961980263589439,
168+
)
169+
)
170+
self.assertAlmostEqual(res.xMinimum(), -13764678, -2)
171+
self.assertAlmostEqual(res.yMinimum(), 5778294, -2)
172+
self.assertAlmostEqual(res.xMaximum(), -11268080, -2)
173+
self.assertAlmostEqual(res.yMaximum(), 9090934, -2)
174+
130175
def testContextProj6(self):
131176
"""
132177
Various tests to ensure that datum transforms are correctly set respecting context

0 commit comments

Comments
 (0)