diff --git a/pyproject.toml b/pyproject.toml index 8647c1f..f166f18 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "hec-dss-python" -version = "0.1.31" +version = "0.1.32" description = "Python wrapper for the HEC-DSS file database C library." authors = ["Hydrologic Engineering Center"] license = "MIT" diff --git a/setup.cfg b/setup.cfg index b2c248e..9eac351 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,6 +1,6 @@ [metadata] name = hecdss -version = 0.1.31 +version = 0.1.32 author = Hydrologic Engineering Center author_email =hec.dss@usace.army.mil description = Python wrapper for the HEC-DSS file database C library. diff --git a/src/hecdss/gridded_data.py b/src/hecdss/gridded_data.py index c7b0590..2797c2c 100644 --- a/src/hecdss/gridded_data.py +++ b/src/hecdss/gridded_data.py @@ -85,7 +85,7 @@ def __init__(self): self.data = np.zeros(0) self.location_info = None - def range_limit_table(self, minval, maxval, range_, bins, datasize, data): + def range_limit_table(self, minval, maxval, range_, bins, data): """ Calculate the range limit table and the number of values equal or exceeding each range limit. @@ -94,7 +94,6 @@ def range_limit_table(self, minval, maxval, range_, bins, datasize, data): maxval (float): Maximum value. range_ (float): Range of values. bins (int): Number of bins. - datasize (int): Size of the data. data (numpy.ndarray): Data array. """ max_bins = 15 @@ -103,7 +102,7 @@ def range_limit_table(self, minval, maxval, range_, bins, datasize, data): bins = max_bins self.rangeLimitTable = np.empty(bins, dtype=float) - self.rangeLimitTable[0] = NULL_INT + self.rangeLimitTable[0] = self.nullValue self.rangeLimitTable[1] = minval step = range_ / bins @@ -118,6 +117,7 @@ def update_grid_info(self): """ Update grid information based on the data array. """ + self.data[self.data == self.nullValue] = np.nan self.numberOfCellsX = len(self.data[0]) self.numberOfCellsY = len(self.data) n = np.size(self.data) @@ -126,10 +126,14 @@ def update_grid_info(self): bin_range = int(math.ceil(self.maxDataValue) - math.floor(self.minDataValue)) self.meanDataValue = np.nanmean(self.data) - self.data = np.nan_to_num(self.data, nan=NULL_INT) - self.numberOfRanges = math.floor(2 + 3.322 * math.log10(n)) + self.data = np.nan_to_num(self.data, nan=self.nullValue) + if bin_range == 0: + self.numberOfRanges = 2 + else: + self.numberOfRanges = math.floor(2 + 3.322 * math.log10(n)) flat_data = self.data.flatten() - self.range_limit_table(self.minDataValue, self.maxDataValue, bin_range, self.numberOfRanges, n, flat_data) + self.range_limit_table(self.minDataValue, self.maxDataValue, + bin_range, self.numberOfRanges, flat_data) @staticmethod def create(path=None, @@ -223,7 +227,7 @@ def create(path=None, gd.meanDataValue = meanDataValue gd.rangeLimitTable = rangeLimitTable gd.numberEqualOrExceedingRangeLimit = numberEqualOrExceedingRangeLimit - gd.data = np.array(data) + gd.data = np.array(data).astype(float) gd.location_info = location_info gd.update_grid_info() diff --git a/tests/test_gridded_data.py b/tests/test_gridded_data.py index f88ae8f..3a3376c 100644 --- a/tests/test_gridded_data.py +++ b/tests/test_gridded_data.py @@ -9,6 +9,8 @@ from hecdss.gridded_data import GriddedData +NULL_INT = -3.4028234663852886e+38 + class TestGriddedData(unittest.TestCase): def setUp(self) -> None: @@ -125,6 +127,40 @@ def test_gridded_data_write_precompressed(self): assert gd_original.numberOfCellsY == gd_readback.numberOfCellsY, "numberOfCellsY mismatch" assert gd_original.dataUnits == gd_readback.dataUnits, "dataUnits mismatch" + def test_null_consistency(self): + """ + This test serves to ensure that the range limit table remains consistent with different cases of missing values. + """ + gd_nan = self._create_half_nul_gd(np.nan) + gd_negative = self._create_half_nul_gd(-9999) + gd_null_int = self._create_half_nul_gd(NULL_INT) + gd_zero = self._create_half_nul_gd(0) + + assert (gd_nan.numberOfRanges == 2) + assert (gd_nan.numberOfRanges == gd_negative.numberOfRanges) + assert (gd_nan.numberOfRanges == gd_null_int.numberOfRanges) + assert (gd_nan.numberOfRanges == gd_zero.numberOfRanges) + + assert (gd_nan.maxDataValue == gd_negative.maxDataValue) + assert (gd_nan.maxDataValue == gd_null_int.maxDataValue) + assert (gd_nan.maxDataValue == gd_zero.maxDataValue) + + assert (gd_nan.minDataValue == gd_negative.minDataValue) + assert (gd_nan.minDataValue == gd_null_int.minDataValue) + assert (gd_nan.minDataValue == gd_zero.minDataValue) + + + def _create_half_nul_gd(self, default_value): + gd_data = [[1 for _ in range(100)] for _ in range(50)] + gd_data.extend([[default_value for _ in range(100)] for _ in range(50)]) + + gd_test = GriddedData.create( + data=gd_data, + nullValue=default_value + ) + + return gd_test + if __name__ == "__main__": unittest.main()