Skip to content

Commit 74505e2

Browse files
authored
r.out.gdal: Skip nodata and range checks if export is forced (#6170)
* skip nodata and range checks if export is forced and nodata and data type are given by the user * update documentation * add tests using pytest
1 parent 8ca3c7a commit 74505e2

File tree

4 files changed

+163
-43
lines changed

4 files changed

+163
-43
lines changed

raster/r.out.gdal/main.c

Lines changed: 31 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -528,8 +528,11 @@ int main(int argc, char *argv[])
528528
? "DCELL"
529529
: (maptype == FCELL_TYPE ? "FCELL" : "??"))));
530530

531-
/* if GDAL datatype set by user, do checks */
532-
if (type->answer) {
531+
if (flag_f->answer)
532+
G_verbose_message(_("Forcing raster export"));
533+
534+
/* if GDAL datatype set by user and export not forced, do checks */
535+
if (type->answer && !flag_f->answer) {
533536

534537
/* Check if raster data range is outside of the range of
535538
* given GDAL datatype, not even overlapping */
@@ -575,13 +578,9 @@ int main(int argc, char *argv[])
575578
}
576579
}
577580
if (retval == -1) {
578-
if (flag_f->answer)
579-
G_warning(_("Forcing raster export"));
580-
else
581-
G_fatal_error(
582-
_("Raster export aborted. "
583-
"To override data loss check, use the -%c flag"),
584-
flag_f->key);
581+
G_fatal_error(_("Raster export aborted. "
582+
"To override data loss check, use the -%c flag"),
583+
flag_f->key);
585584
}
586585
}
587586

@@ -619,33 +618,34 @@ int main(int argc, char *argv[])
619618
}
620619

621620
/* exact range and nodata checks for each band */
622-
G_message(_("Checking GDAL data type and nodata value..."));
623-
for (band = 0; band < ref.nfiles; band++) {
624-
if (ref.nfiles > 1) {
625-
G_verbose_message(
626-
_("Checking options for raster map <%s> (band %d)..."),
627-
G_fully_qualified_name(ref.file[band].name,
628-
ref.file[band].mapset),
629-
band + 1);
630-
}
621+
if (!flag_f->answer || !nodataopt->answer) {
622+
G_message(_("Checking GDAL data type and nodata value..."));
623+
for (band = 0; band < ref.nfiles; band++) {
624+
if (ref.nfiles > 1) {
625+
G_verbose_message(
626+
_("Checking options for raster map <%s> (band %d)..."),
627+
G_fully_qualified_name(ref.file[band].name,
628+
ref.file[band].mapset),
629+
band + 1);
630+
}
631631

632-
retval = exact_checks(datatype, ref.file[band].name,
633-
ref.file[band].mapset, &cellhead, maptype,
634-
nodataval, nodataopt->key, default_nodataval);
632+
retval = exact_checks(datatype, ref.file[band].name,
633+
ref.file[band].mapset, &cellhead, maptype,
634+
nodataval, nodataopt->key, default_nodataval);
635635

636-
/* nodata value is present in the data to be exported */
637-
if (retval == -1) {
638-
if (flag_f->answer)
639-
G_warning(_("Forcing raster export."));
640-
else
636+
/* nodata value is present in the data to be exported */
637+
if (retval == -1) {
638+
if (flag_f->answer)
639+
G_verbose_message(_("Forcing raster export."));
640+
else
641+
G_fatal_error(_("Raster export aborted."));
642+
}
643+
/* data don't fit into range of GDAL datatype */
644+
else if (retval == -2) {
641645
G_fatal_error(_("Raster export aborted."));
642-
}
643-
/* data don't fit into range of GDAL datatype */
644-
else if (retval == -2) {
645-
G_fatal_error(_("Raster export aborted."));
646+
}
646647
}
647648
}
648-
649649
/* Create dataset for output with target driver or, if needed, with
650650
* in-memory driver */
651651
char **papszOptions = NULL;

raster/r.out.gdal/r.out.gdal.html

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -94,12 +94,19 @@ <h3>Ranges of GDAL data types</h3>
9494
available for a given file format, and certain compression methods may
9595
only be supported for certain data types (depends on vendor and version).
9696
<!-- e.g. data destined for ESRI software should use COMPRESS=LZW/PACKBITS/DEFLATE ??? -->
97-
<p>If the export settings are set such that data loss would occur in the output
98-
file (i.e, due to the particular choice of data type and/or file type), the
99-
normal behaviour of <em>r.out.gdal</em> in this case would be to issue an error
100-
message describing the problem and exit without exporting. The <b>-f</b> flag
101-
allows raster export even if some of the data loss tests are not passed, and
102-
warnings are issued instead of errors.
97+
<p>By default, <em>r.out.gdal</em> identifies suitable settings for
98+
<b>nodata</b> and data <b>type</b>, if not given by the user.
99+
If however the export settings are set such that data loss would occur
100+
in the output file (i.e, due to the particular choice of data type and/or
101+
file type), the normal behaviour of <em>r.out.gdal</em> in this case
102+
would be to issue an error message describing the problem and exit without
103+
exporting. However, depending on the size of the data, tests to check for
104+
data loss may take some time. For performance reasons, these tests can be
105+
skipped if the <b>-f</b>b> flag, the <b>nodata</b>b> option and the
106+
<b>type</b>b> option are all specified. The assumption here is that the
107+
user has made a deliberate choice about these settings and considered the
108+
potential risk of data loss when forcing the export with the <b>-f</b>b>
109+
flag.
103110
<p><em>r.out.gdal</em> exports may appear all black or gray on initial
104111
display in other GIS software. This is not a bug of <em>r.out.gdal</em>,
105112
but often caused by the default color table assigned by that software.

raster/r.out.gdal/r.out.gdal.md

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -99,12 +99,18 @@ Some software may not recognize all of the compression methods available
9999
for a given file format, and certain compression methods may only be
100100
supported for certain data types (depends on vendor and version).
101101

102-
If the export settings are set such that data loss would occur in the
103-
output file (i.e, due to the particular choice of data type and/or file
104-
type), the normal behaviour of *r.out.gdal* in this case would be to
105-
issue an error message describing the problem and exit without
106-
exporting. The **-f** flag allows raster export even if some of the data
107-
loss tests are not passed, and warnings are issued instead of errors.
102+
By default, *r.out.gdal* identifies suitable settings for
103+
**nodata** and data **type**, if not given by the user.
104+
If however the export settings are set such that data loss would occur
105+
in the output file (i.e, due to the particular choice of data type and/or
106+
file type), the normal behaviour of *r.out.gdal* in this case would be
107+
to issue an error message describing the problem and exit without
108+
exporting. However, depending on the data size, data loss tests may take
109+
some time. For performance reasons, those tests can therefore be skipped
110+
if both the **-f** flag, the **nodata** option and the **type**
111+
option are given. Here the assumtion is that the user has made a
112+
deliberate choice about those settings and considered the potential risk
113+
for data loss when forcing the export with the **-f** flag.
108114

109115
*r.out.gdal* exports may appear all black or gray on initial display in
110116
other GIS software. This is not a bug of *r.out.gdal*, but often caused
Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
import grass.script as gs
2+
import pytest
3+
from grass.tools import Tools
4+
5+
FORMAT_DICT = {
6+
"COG": "tif",
7+
"GPKG": "gpkg",
8+
"GTiff": "tif",
9+
"VRT": "vrt",
10+
"netCDF": "nc",
11+
"ENVI": "img",
12+
}
13+
14+
15+
@pytest.fixture(scope="module")
16+
def simple_raster_map(tmp_path_factory):
17+
"""Fixture to create a basic GRASS environment with a simple raster map containing attributes.
18+
19+
Set up a temporary GRASS project and region and create a raster map
20+
using r.mapcalc.
21+
22+
Yields:
23+
tuple: (map name, GRASS session handle, tmp_path)
24+
25+
"""
26+
tmp_path = tmp_path_factory.mktemp("r_out_gdal_project")
27+
project = tmp_path / "grassdata"
28+
gs.create_project(project, epsg=4326)
29+
30+
with gs.setup.init(project) as session:
31+
tools = Tools(session=session, consistent_return_value=True)
32+
tools.g_region(n=10, s=0, e=10, w=0, res=1)
33+
34+
tools.r_mapcalc(
35+
expression="test_raster=row() * col()",
36+
)
37+
38+
yield "test_raster", session, tmp_path
39+
40+
41+
@pytest.mark.parametrize("file_format", list(FORMAT_DICT.keys()))
42+
def test_basic_cog_export(simple_raster_map, file_format):
43+
"""Test basic export of various formats using 'r.out.gdal'.
44+
45+
Verifies:
46+
- Export of common format works with defaults.
47+
"""
48+
mapname, session, tmp_path = simple_raster_map
49+
tools = Tools(session=session, consistent_return_value=True)
50+
suffix = FORMAT_DICT[file_format]
51+
output_file = tmp_path / f"{mapname}_{file_format}.{suffix}"
52+
export = tools.r_out_gdal(
53+
input=mapname,
54+
output=output_file,
55+
format=file_format,
56+
)
57+
# Check successful export
58+
assert output_file.exists(), f"{mapname} not exported to {output_file}"
59+
# Check that nodata and data range checks are performed
60+
assert "Checking GDAL data type and nodata value" in export.stderr
61+
62+
63+
def test_compressed_gtiff_export(simple_raster_map):
64+
"""Test export of compressed GeoTiff using 'r.out.gdal'.
65+
66+
Verifies:
67+
- Export of common format works.
68+
"""
69+
mapname, session, tmp_path = simple_raster_map
70+
tools = Tools(session=session, consistent_return_value=True)
71+
72+
output_file = tmp_path / f"{mapname}_lzw.tif"
73+
export = tools.r_out_gdal(
74+
input=mapname,
75+
output=output_file,
76+
format="GTiff",
77+
createopt="COMPRESS=LZW",
78+
)
79+
# Check successful export
80+
assert output_file.exists(), f"{mapname} not exported to {output_file}"
81+
# Check that nodata and data range checks are performed
82+
assert "Checking GDAL data type and nodata value" in export.stderr
83+
84+
85+
def test_fast_gtiff_export(simple_raster_map):
86+
"""Test fast export of compressed GeoTiff using 'r.out.gdal'.
87+
88+
Verifies:
89+
- Fast export of works if type, nodata and -f are given.
90+
"""
91+
mapname, session, tmp_path = simple_raster_map
92+
tools = Tools(session=session, consistent_return_value=True)
93+
94+
output_file = tmp_path / f"{mapname}_fast.tif"
95+
export = tools.r_out_gdal(
96+
flags="f",
97+
input=mapname,
98+
output=output_file,
99+
format="GTiff",
100+
type="Byte",
101+
nodata=255,
102+
createopt="COMPRESS=LZW",
103+
)
104+
# Check successful export
105+
assert output_file.exists(), f"{mapname} not exported to {output_file}"
106+
# Check that nodata and data range checks are NOT performed
107+
assert "Checking GDAL data type and nodata value" not in export.stderr

0 commit comments

Comments
 (0)