Merge branch 'release/3.16.0' into enhancement/1044-ndr-n-load-update
This commit is contained in:
commit
80763c7c6d
|
@ -14,7 +14,7 @@ conda:
|
|||
environment: .readthedocs_environment.yml
|
||||
|
||||
build:
|
||||
os: ubuntu-22.04
|
||||
os: ubuntu-24.04
|
||||
tools:
|
||||
python: "mambaforge-4.10"
|
||||
jobs:
|
||||
|
|
|
@ -99,6 +99,8 @@ NDR
|
|||
``measured-runoff``. See the Data Needs section of the NDR User
|
||||
Guide for more details.
|
||||
(`#1044 <https://github.com/natcap/invest/issues/1044>`_).
|
||||
* Fixed a bug where input rasters (e.g. LULC) without a defined nodata value could
|
||||
cause an OverflowError. (`#1904 <https://github.com/natcap/invest/issues/1904>`_).
|
||||
|
||||
Seasonal Water Yield
|
||||
====================
|
||||
|
|
|
@ -9,6 +9,7 @@ import pygeoprocessing
|
|||
import pygeoprocessing.routing
|
||||
import taskgraph
|
||||
from osgeo import gdal
|
||||
from osgeo import gdal_array
|
||||
from osgeo import ogr
|
||||
|
||||
from .. import gettext
|
||||
|
@ -696,7 +697,7 @@ def execute(args):
|
|||
'mask_raster_path': f_reg['mask_path'],
|
||||
'target_masked_raster_path': f_reg['masked_runoff_proxy_path'],
|
||||
'target_dtype': gdal.GDT_Float32,
|
||||
'default_nodata': _TARGET_NODATA,
|
||||
'target_nodata': _TARGET_NODATA,
|
||||
},
|
||||
dependent_task_list=[mask_task, align_raster_task],
|
||||
target_path_list=[f_reg['masked_runoff_proxy_path']],
|
||||
|
@ -709,7 +710,7 @@ def execute(args):
|
|||
'mask_raster_path': f_reg['mask_path'],
|
||||
'target_masked_raster_path': f_reg['masked_dem_path'],
|
||||
'target_dtype': gdal.GDT_Float32,
|
||||
'default_nodata': float(numpy.finfo(numpy.float32).min),
|
||||
'target_nodata': float(numpy.finfo(numpy.float32).min),
|
||||
},
|
||||
dependent_task_list=[mask_task, align_raster_task],
|
||||
target_path_list=[f_reg['masked_dem_path']],
|
||||
|
@ -722,7 +723,7 @@ def execute(args):
|
|||
'mask_raster_path': f_reg['mask_path'],
|
||||
'target_masked_raster_path': f_reg['masked_lulc_path'],
|
||||
'target_dtype': gdal.GDT_Int32,
|
||||
'default_nodata': numpy.iinfo(numpy.int32).min,
|
||||
'target_nodata': numpy.iinfo(numpy.int32).min,
|
||||
},
|
||||
dependent_task_list=[mask_task, align_raster_task],
|
||||
target_path_list=[f_reg['masked_lulc_path']],
|
||||
|
@ -1207,7 +1208,7 @@ def _create_mask_raster(source_raster_path, source_vector_path,
|
|||
|
||||
|
||||
def _mask_raster(source_raster_path, mask_raster_path,
|
||||
target_masked_raster_path, default_nodata, target_dtype):
|
||||
target_masked_raster_path, target_nodata, target_dtype):
|
||||
"""Using a raster of 1s and 0s, determine which pixels remain in output.
|
||||
|
||||
Args:
|
||||
|
@ -1219,8 +1220,8 @@ def _mask_raster(source_raster_path, mask_raster_path,
|
|||
target raster.
|
||||
target_masked_raster_path (str): The path to where the target raster
|
||||
should be written.
|
||||
default_nodata (int, float, None): The nodata value that should be used
|
||||
if ``source_raster_path`` does not have a defined nodata value.
|
||||
target_nodata (int, float): The target nodata value that should match
|
||||
``target_dtype``.
|
||||
target_dtype (int): The ``gdal.GDT_*`` datatype of the target raster.
|
||||
|
||||
Returns:
|
||||
|
@ -1228,22 +1229,20 @@ def _mask_raster(source_raster_path, mask_raster_path,
|
|||
"""
|
||||
source_raster_info = pygeoprocessing.get_raster_info(source_raster_path)
|
||||
source_nodata = source_raster_info['nodata'][0]
|
||||
nodata = source_nodata
|
||||
if nodata is None:
|
||||
nodata = default_nodata
|
||||
target_numpy_dtype = gdal_array.GDALTypeCodeToNumericTypeCode(target_dtype)
|
||||
|
||||
def _mask_op(mask, raster):
|
||||
result = numpy.full(mask.shape, nodata,
|
||||
dtype=source_raster_info['numpy_type'])
|
||||
result = numpy.full(mask.shape, target_nodata,
|
||||
dtype=target_numpy_dtype)
|
||||
valid_pixels = (
|
||||
~pygeoprocessing.array_equals_nodata(raster, nodata) &
|
||||
~pygeoprocessing.array_equals_nodata(raster, source_nodata) &
|
||||
(mask == 1))
|
||||
result[valid_pixels] = raster[valid_pixels]
|
||||
return result
|
||||
|
||||
pygeoprocessing.raster_calculator(
|
||||
[(mask_raster_path, 1), (source_raster_path, 1)], _mask_op,
|
||||
target_masked_raster_path, target_dtype, nodata)
|
||||
target_masked_raster_path, target_dtype, target_nodata)
|
||||
|
||||
|
||||
def _add_fields_to_shapefile(field_pickle_map, target_vector_path):
|
||||
|
|
|
@ -370,6 +370,39 @@ class NDRTests(unittest.TestCase):
|
|||
if mismatch_list:
|
||||
raise RuntimeError("results not expected: %s" % mismatch_list)
|
||||
|
||||
def test_mask_raster_nodata_overflow(self):
|
||||
"""NDR test when target nodata value overflows source dtype."""
|
||||
from natcap.invest.ndr import ndr
|
||||
|
||||
source_raster_path = os.path.join(self.workspace_dir, 'source.tif')
|
||||
target_raster_path = os.path.join(
|
||||
self.workspace_dir, 'target.tif')
|
||||
source_dtype = numpy.int8
|
||||
target_dtype = gdal.GDT_Int32
|
||||
target_nodata = numpy.iinfo(numpy.int32).min
|
||||
|
||||
pygeoprocessing.numpy_array_to_raster(
|
||||
base_array=numpy.full((4, 4), 1, dtype=source_dtype),
|
||||
target_nodata=None,
|
||||
pixel_size=(1, -1),
|
||||
origin=(0, 0),
|
||||
projection_wkt=None,
|
||||
target_path=source_raster_path)
|
||||
|
||||
ndr._mask_raster(
|
||||
source_raster_path=source_raster_path,
|
||||
mask_raster_path=source_raster_path, # mask=source for convenience
|
||||
target_masked_raster_path=target_raster_path,
|
||||
target_nodata=target_nodata,
|
||||
target_dtype=target_dtype)
|
||||
|
||||
# Mostly we're testing that _mask_raster did not raise an OverflowError,
|
||||
# but we can assert the results anyway.
|
||||
array = pygeoprocessing.raster_to_numpy_array(target_raster_path)
|
||||
numpy.testing.assert_array_equal(
|
||||
array,
|
||||
numpy.full((4, 4), 1, dtype=numpy.int32)) # matches target_dtype
|
||||
|
||||
def test_validation(self):
|
||||
"""NDR test argument validation."""
|
||||
from natcap.invest import validation
|
||||
|
|
Loading…
Reference in New Issue