Skip to content

Commit dc9d6f6

Browse files
committed
Warper: improve XSCALE/YSALE computation logic when the target shape is quite different from the source shape
The current logic to compute them is quite naive, and only reasonably works when the shape of the output chunk is similar (in proportion and orientation) to the one of the input chunk. Failing that, inappropriate values are computed leading typically to blurring. Rework the logic to be closer to a specific case that had already been implemented to deal with discontinuities at the antimeridian, but with modifications. So we compute the transformation of a maximum of 10x10 unit squares in the target pixel space to the source pixel space. If a source polygon retains an approximative rectangular shape with modest rotation, use the width/height of that rectangle to compute XSCALE/YSCALE. Otherwise compute the area of the rectangle and make XSCALE/YSCALE be the inverse of the square root of the area. Average the values for each output polygon, except values that are less than 10% of the maximum scale, to avoid issues with polar discontinuities Also revise the logic to decide whether we use a 4 input sample per output pixel vs generalized resampling kernel for bilinear and bicubic. Previously we only used the 4-sample formula if not downsampling (with a margin error of 5%). Increase that tolerance to allow up to a 50% downsampling. And emit a debug trace when the generalized formula is used instead of the 4 sample one. Due to those changes, a few existing expected checksums had to be adjusted. And we also had to adjust a heuristics in the average/mode/Q1/Q3/max/etc. resampling code path that dealt with polar discontinuities to make a related test still pass.
1 parent 3c5c592 commit dc9d6f6

File tree

9 files changed

+310
-223
lines changed

9 files changed

+310
-223
lines changed

alg/gdalwarpkernel.cpp

Lines changed: 232 additions & 135 deletions
Large diffs are not rendered by default.
Binary file not shown.
11.3 KB
Binary file not shown.
316 Bytes
Binary file not shown.

autotest/alg/warp.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2302,3 +2302,19 @@ def test_warp_zero_sized_target_extent():
23022302
)
23032303
assert out_ds.RasterXSize == 1
23042304
assert out_ds.RasterYSize == 1
2305+
2306+
2307+
@gdaltest.enable_exceptions()
2308+
def test_warp_geolocation_array_with_rotation(tmp_path):
2309+
"""Test that computed XSCALE/YSCALE are reasonable when a geolocation array
2310+
causes a ~ 90 degree rotation. Our input image is a checker of black and
2311+
white, and the expected output is also a non-blurred checker.
2312+
"""
2313+
gdal.Warp(
2314+
tmp_path / "out.tif",
2315+
"data/input_for_geoloc_array_with_rotation.tif",
2316+
options="-r cubic -t_srs EPSG:32640 -to METHOD=GEOLOC_ARRAY -to GEOLOC_ARRAY=data/geoloc_array_with_rotation.tif -tr 30 30",
2317+
)
2318+
ds = gdal.Open(tmp_path / "out.tif")
2319+
ref_ds = gdal.Open("data/expected_output_for_geoloc_array_with_rotation.tif")
2320+
assert ds.GetRasterBand(1).Checksum() == ref_ds.GetRasterBand(1).Checksum()

autotest/gcore/cog.py

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -485,11 +485,7 @@ def my_cbk(pct, _, arg):
485485
if gt[i] != pytest.approx(expected_gt[i], abs=1e-10 * abs(expected_gt[i])):
486486
assert False, gt
487487
got_cs = [ds.GetRasterBand(i + 1).Checksum() for i in range(3)]
488-
assert got_cs in (
489-
[26293, 23439, 14955],
490-
[26228, 22085, 12992],
491-
[25088, 23140, 13265], # libjpeg 9e
492-
)
488+
assert got_cs == pytest.approx([20968, 23493, 14665], abs=100)
493489
assert ds.GetRasterBand(1).GetMaskBand().Checksum() == 17849
494490
assert ds.GetRasterBand(1).GetOverviewCount() == 0
495491
ds = None

autotest/gdrivers/gpkg.py

Lines changed: 52 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -2270,16 +2270,14 @@ def test_gpkg_22(tile_drv_name):
22702270

22712271

22722272
@pytest.mark.require_driver("PNG")
2273-
def test_gpkg_26():
2274-
2275-
gdal.Unlink("/vsimem/tmp.gpkg")
2276-
2277-
tests = [
2273+
@pytest.mark.parametrize(
2274+
"scheme,expected_cs,other_options",
2275+
[
22782276
("CUSTOM", [4672, 4672, 4672, 4873], None),
2279-
("GoogleCRS84Quad", [3562, 3562, 3562, 3691], None),
2280-
("GoogleCRS84Quad", [3562, 3562, 3562, 3691], ["RESAMPLING=BILINEAR"]),
2281-
("GoogleCRS84Quad", [3417, 3417, 3417, 3691], ["RESAMPLING=CUBIC"]),
2282-
("GoogleCRS84Quad", [3562, 3562, 3562, 3691], ["ZOOM_LEVEL_STRATEGY=AUTO"]),
2277+
("GoogleCRS84Quad", [3439, 3439, 3439, 3691], None),
2278+
("GoogleCRS84Quad", [3439, 3439, 3439, 3691], ["RESAMPLING=BILINEAR"]),
2279+
("GoogleCRS84Quad", [3549, 3549, 3549, 3691], ["RESAMPLING=CUBIC"]),
2280+
("GoogleCRS84Quad", [3439, 3439, 3439, 3691], ["ZOOM_LEVEL_STRATEGY=AUTO"]),
22832281
(
22842282
"GoogleCRS84Quad",
22852283
[14445, 14445, 14445, 14448],
@@ -2295,77 +2293,65 @@ def test_gpkg_26():
22952293
None,
22962294
["ZOOM_LEVEL=31"],
22972295
),
2298-
("GoogleCRS84Quad", [3562, 3562, 3562, 3691], ["ZOOM_LEVEL_STRATEGY=LOWER"]),
2296+
("GoogleCRS84Quad", [3439, 3439, 3439, 3691], ["ZOOM_LEVEL_STRATEGY=LOWER"]),
22992297
("GoogleMapsCompatible", [4118, 4118, 4118, 4406], None),
2300-
("PseudoTMS_GlobalGeodetic", [3562, 3562, 3562, 3691], None),
2298+
("PseudoTMS_GlobalGeodetic", [3439, 3439, 3439, 3691], None),
23012299
("PseudoTMS_GlobalMercator", [4118, 4118, 4118, 4406], None),
2302-
]
2303-
2304-
for (scheme, expected_cs, other_options) in tests:
2305-
2306-
src_ds = gdal.Open("data/byte.tif")
2307-
options = ["TILE_FORMAT=PNG", "TILING_SCHEME=" + scheme]
2308-
if other_options:
2309-
options = options + other_options
2310-
if expected_cs is None:
2311-
with gdal.quiet_errors():
2312-
ds = gdaltest.gpkg_dr.CreateCopy(
2313-
"/vsimem/tmp.gpkg", src_ds, options=options
2314-
)
2315-
assert ds is None
2316-
continue
2317-
2318-
ds = gdaltest.gpkg_dr.CreateCopy("/vsimem/tmp.gpkg", src_ds, options=options)
2319-
ds = None
2300+
],
2301+
)
2302+
def test_gpkg_26(tmp_vsimem, scheme, expected_cs, other_options):
23202303

2321-
ds = gdal.OpenEx("/vsimem/tmp.gpkg", open_options=["BAND_COUNT=4"])
2322-
assert ds.GetMetadataItem("AREA_OR_POINT") == "Area"
2323-
got_cs = [ds.GetRasterBand(i + 1).Checksum() for i in range(4)]
2324-
# VC12 returns [3561, 3561, 3561, 3691] for GoogleCRS84Quad
2325-
# and For GoogleCRS84Quad RESAMPLING=CUBIC, got [3415, 3415, 3415, 3691]
2326-
if max([abs(got_cs[i] - expected_cs[i]) for i in range(4)]) > 2:
2327-
print(
2328-
"For %s, got %s, expected %s" % (scheme, str(got_cs), str(expected_cs))
2304+
src_ds = gdal.Open("data/byte.tif")
2305+
options = ["TILE_FORMAT=PNG", "TILING_SCHEME=" + scheme]
2306+
if other_options:
2307+
options = options + other_options
2308+
if expected_cs is None:
2309+
with gdal.quiet_errors():
2310+
ds = gdaltest.gpkg_dr.CreateCopy(
2311+
tmp_vsimem / "tmp.gpkg", src_ds, options=options
23292312
)
2330-
assert gdal.GetConfigOption("APPVEYOR") is not None
2331-
ds = None
2313+
assert ds is None
2314+
return
23322315

2333-
gdal.Unlink("/vsimem/tmp.gpkg")
2316+
ds = gdaltest.gpkg_dr.CreateCopy(tmp_vsimem / "tmp.gpkg", src_ds, options=options)
2317+
ds = None
23342318

2335-
tests = [
2319+
ds = gdal.OpenEx(tmp_vsimem / "tmp.gpkg", open_options=["BAND_COUNT=4"])
2320+
assert ds.GetMetadataItem("AREA_OR_POINT") == "Area"
2321+
assert [ds.GetRasterBand(i + 1).Checksum() for i in range(4)] == pytest.approx(
2322+
expected_cs, abs=10
2323+
)
2324+
ds = None
2325+
2326+
2327+
@pytest.mark.require_driver("PNG")
2328+
@pytest.mark.parametrize(
2329+
"scheme,expected_cs,other_options",
2330+
[
23362331
(
23372332
"GoogleCRS84Quad",
2338-
[
2339-
[42255, 47336, 24963, 35707],
2340-
[42255, 47336, 24965, 35707],
2341-
[42253, 47333, 24961, 35707],
2342-
[42253, 47334, 24963, 35707], # s390x
2343-
],
2333+
[42255, 47336, 24963, 35707],
23442334
None,
23452335
),
2346-
("GoogleMapsCompatible", [[35429, 36787, 20035, 17849]], None),
2347-
]
2348-
2349-
for (scheme, expected_cs, other_options) in tests:
2336+
("GoogleMapsCompatible", [31249, 35596, 19001, 17849], None),
2337+
],
2338+
)
2339+
def test_gpkg_26_bis(tmp_vsimem, scheme, expected_cs, other_options):
23502340

2351-
src_ds = gdal.Open("data/small_world.tif")
2352-
options = ["TILE_FORMAT=PNG", "TILING_SCHEME=" + scheme]
2353-
if other_options:
2354-
options = options + other_options
2355-
ds = gdaltest.gpkg_dr.CreateCopy("/vsimem/tmp.gpkg", src_ds, options=options)
2356-
ds = None
2341+
src_ds = gdal.Open("data/small_world.tif")
2342+
options = ["TILE_FORMAT=PNG", "TILING_SCHEME=" + scheme]
2343+
if other_options:
2344+
options = options + other_options
2345+
ds = gdaltest.gpkg_dr.CreateCopy(tmp_vsimem / "tmp.gpkg", src_ds, options=options)
2346+
ds = None
23572347

2358-
ds = gdal.Open("/vsimem/tmp.gpkg")
2359-
got_cs = [ds.GetRasterBand(i + 1).Checksum() for i in range(4)]
2360-
if got_cs not in expected_cs:
2361-
print(
2362-
"For %s, got %s, expected %s" % (scheme, str(got_cs), str(expected_cs))
2363-
)
2364-
assert gdal.GetConfigOption("APPVEYOR") is not None
2365-
ds = None
2348+
ds = gdal.Open(tmp_vsimem / "tmp.gpkg")
2349+
assert [ds.GetRasterBand(i + 1).Checksum() for i in range(4)] == pytest.approx(
2350+
expected_cs, abs=10
2351+
)
23662352

2367-
gdal.Unlink("/vsimem/tmp.gpkg")
23682353

2354+
def test_gpkg_26_errors():
23692355
# Test a few error cases
23702356
with gdal.quiet_errors():
23712357
ds = gdaltest.gpkg_dr.Create(

autotest/utilities/test_gdalalg_raster_tile.py

Lines changed: 7 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1323,11 +1323,9 @@ def test_gdalalg_raster_tile_rgb(tmp_vsimem):
13231323
assert ds.RasterCount == 3
13241324
assert ds.RasterXSize == 256
13251325
assert ds.RasterYSize == 256
1326-
assert [ds.GetRasterBand(i + 1).Checksum() for i in range(3)] == [
1327-
24650,
1328-
23280,
1329-
16559,
1330-
]
1326+
assert [ds.GetRasterBand(i + 1).Checksum() for i in range(3)] == pytest.approx(
1327+
[22597, 22783, 16561], abs=30
1328+
)
13311329

13321330

13331331
def test_gdalalg_raster_tile_rgba_all_opaque(tmp_vsimem):
@@ -1363,11 +1361,7 @@ def test_gdalalg_raster_tile_rgba_all_opaque(tmp_vsimem):
13631361
assert ds.RasterXSize == 256
13641362
assert ds.RasterYSize == 256
13651363
assert [ds.GetRasterBand(i + 1).Checksum() for i in range(3)] == pytest.approx(
1366-
[
1367-
25111,
1368-
24737,
1369-
16108,
1370-
],
1364+
[25111, 24737, 16107],
13711365
abs=10,
13721366
)
13731367

@@ -1504,11 +1498,9 @@ def test_gdalalg_raster_tile_rgba_no_alpha(tmp_vsimem):
15041498
assert ds.RasterCount == 3
15051499
assert ds.RasterXSize == 256
15061500
assert ds.RasterYSize == 256
1507-
assert [ds.GetRasterBand(i + 1).Checksum() for i in range(3)] == [
1508-
24650,
1509-
23280,
1510-
16559,
1511-
]
1501+
assert [ds.GetRasterBand(i + 1).Checksum() for i in range(3)] == pytest.approx(
1502+
[22597, 22783, 16561], abs=30
1503+
)
15121504

15131505

15141506
def test_gdalalg_raster_tile_max_zoom(tmp_vsimem):

autotest/utilities/test_gdalwarp_lib.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2497,7 +2497,7 @@ def test_gdalwarp_lib_ct():
24972497
options='-r cubic -f MEM -t_srs EPSG:4326 -ct "proj=pipeline step inv proj=utm zone=11 ellps=clrk66 step proj=unitconvert xy_in=rad xy_out=deg step proj=axisswap order=2,1"',
24982498
)
24992499

2500-
assert dstDS.GetRasterBand(1).Checksum() == 4705, "Bad checksum"
2500+
assert dstDS.GetRasterBand(1).Checksum() == 4772
25012501

25022502

25032503
def test_gdalwarp_lib_ct_wkt():
@@ -2646,7 +2646,7 @@ def test_gdalwarp_lib_ct_wkt():
26462646
coordinateOperation=wkt,
26472647
)
26482648

2649-
assert dstDS.GetRasterBand(1).Checksum() == 4705, "Bad checksum"
2649+
assert dstDS.GetRasterBand(1).Checksum() == 4772
26502650

26512651

26522652
###############################################################################

0 commit comments

Comments
 (0)