Skip to content

Commit 2f860e0

Browse files
authored
Merge pull request #111 from HydrologicEngineeringCenter/preCompression
Enable User to write a precompressed grid to dss
2 parents f478383 + aec8c89 commit 2f860e0

File tree

6 files changed

+70
-6
lines changed

6 files changed

+70
-6
lines changed

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[tool.poetry]
22
name = "hec-dss-python"
3-
version = "0.1.28"
3+
version = "0.1.29"
44
description = "Python wrapper for the HEC-DSS file database C library."
55
authors = ["Hydrologic Engineering Center"]
66
license = "MIT"

setup.cfg

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[metadata]
22
name = hecdss
3-
version = 0.1.28
3+
version = 0.1.29
44
author = Hydrologic Engineering Center
55
author_email =hec.dss@usace.army.mil
66
description = Python wrapper for the HEC-DSS file database C library.

src/hecdss/download_hecdss.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ def download_and_unzip(url, zip_file, destination_dir):
3636
print(f"Failed to download zip file. Status code: {response.status_code}")
3737

3838
base_url = "https://www.hec.usace.army.mil/nexus/repository/maven-public/mil/army/usace/hec/hecdss/"
39-
version = "7-IW-4"
39+
version = "7-JA-4"
4040

4141
destination_dir = Path(__file__).parent.joinpath("lib")
4242
zip_url = f"{base_url}{version}-win-x86_64/hecdss-{version}-win-x86_64.zip"

src/hecdss/hecdss.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -679,6 +679,24 @@ def put(self, container) -> int:
679679

680680
return status
681681

682+
683+
def writePrecompressedGrid(self, gd, compressedData, CompressionSize):
684+
"""
685+
puts pre-compressed gridded data into the DSS file
686+
687+
Args
688+
compressedData (bytes): Compressed data.
689+
CompressionSize (int): Size of the compressed data.
690+
Returns:
691+
int: 0 if successful, -1 otherwise.
692+
"""
693+
694+
if compressedData and CompressionSize > 0:
695+
status = self._native.hec_dss_gridStore(gd, compressedData, CompressionSize)
696+
self._catalog = None
697+
return status
698+
return -1
699+
682700
def delete(self, pathname: str, allrecords: bool = False, startdatetime=None, enddatetime=None) -> int:
683701
"""deletes a record from the DSS file
684702
Args:

src/hecdss/native.py

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -332,6 +332,8 @@ def hec_dss_gridRetrieve(self, pathname: str,
332332
def hec_dss_gridStore(
333333
self,
334334
gd,
335+
compressedData=None,
336+
compressionSize=0,
335337
):
336338
self.dll.hec_dss_pdStore.restype = c_int
337339
self.dll.hec_dss_pdStore.argtypes = [
@@ -348,6 +350,7 @@ def hec_dss_gridStore(
348350
ctypes.c_int, # timeZoneRawOffset
349351
ctypes.c_int, # isInterval
350352
ctypes.c_int, # isTimeStamped
353+
ctypes.c_int, # compressionSize
351354
ctypes.c_char_p, # dataUnits
352355
ctypes.c_char_p, # dataSource
353356
ctypes.c_char_p, # srsName
@@ -378,6 +381,7 @@ def hec_dss_gridStore(
378381
c_timeZoneRawOffset = c_int(gd.timeZoneRawOffset)
379382
c_isInterval = c_int(gd.isInterval)
380383
c_isTimeStamped = c_int(gd.isTimeStamped)
384+
c_compressionSize = c_int(compressionSize) # default compression
381385

382386
c_dataUnits = c_char_p(gd.dataUnits.encode("utf-8"))
383387
c_dataSource = c_char_p(gd.dataSource.encode("utf-8"))
@@ -397,15 +401,19 @@ def hec_dss_gridStore(
397401
c_numberEqualOrExceedingRangeLimit = (c_int * len(gd.numberEqualOrExceedingRangeLimit))(
398402
*gd.numberEqualOrExceedingRangeLimit)
399403

400-
arr = gd.data.astype('float32', copy=False)
401-
c_data = arr.ctypes.data_as(ctypes.POINTER(ctypes.c_float))
404+
if compressedData is not None and compressionSize:
405+
# Treat compressed data as raw bytes, not float32
406+
c_data = ctypes.cast(compressedData, ctypes.POINTER(ctypes.c_float))
407+
else:
408+
arr = gd.data.astype('float32', copy=False)
409+
c_data = arr.ctypes.data_as(ctypes.POINTER(ctypes.c_float))
402410

403411
return self.dll.hec_dss_gridStore(self.handle, c_pathname, c_gridType, c_dataType,
404412
c_lowerLeftCellX, c_lowerLeftCellY,
405413
c_numberOfCellsX, c_numberOfCellsY,
406414
c_numberOfRanges, c_srsDefinitionType,
407415
c_timeZoneRawOffset, c_isInterval,
408-
c_isTimeStamped,
416+
c_isTimeStamped, c_compressionSize,
409417
c_dataUnits, c_dataSource,
410418
c_srsName, c_srsDefinition, c_timeZoneID,
411419
c_cellSize, c_xCoordOfGridCellZero,

tests/test_gridded_data.py

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,44 @@ def test_gridded_data_read_store_read(self):
8686
assert (
8787
gd.dataUnits == gd2.dataUnits), f"gd2.dataUnits is not equal to {gd.dataUnits}. gd2.dataUnits is {gd2.dataUnits}"
8888

89+
def test_gridded_data_write_precompressed(self):
90+
"""
91+
Test writing precompressed gridded data using zlib deflate.
92+
Reads existing grid data, compresses it, writes using writePrecompressedGrid, and compares.
93+
"""
94+
import zlib
95+
96+
# Read existing gridded data
97+
original_path = "/grid/EAU GALLA RIVER/SNOW MELT/02FEB2020:0600/03FEB2020:0600/SHG-SNODAS/"
98+
file = self.test_files.get_copy("grid-example.dss")
99+
100+
with HecDss(file) as dss:
101+
# Read the original grid
102+
gd_original = dss.get(original_path)
103+
104+
# Compress the data using zlib deflate
105+
raw_bytes = gd_original.data.astype(np.float32).tobytes()
106+
compressed_data = zlib.compress(raw_bytes)
107+
compression_size = len(compressed_data)
108+
109+
# Create a new GriddedData object with metadata from original
110+
# but pointing to a new path
111+
new_path = "/grid/EAU GALLA RIVER/SNOW MELT/02FEB2020:0600/03FEB2020:0600/SHG-SNODAS-COMPRESSED/"
112+
gd_original.id = new_path
113+
114+
115+
# Write the precompressed grid
116+
status = dss.writePrecompressedGrid(gd_original, compressed_data, compression_size)
117+
118+
# Read back the compressed grid
119+
gd_readback = dss.get(new_path)
120+
121+
# Compare the two grids
122+
assert status == 0, f"writePrecompressedGrid status should be 0, is {status}"
123+
assert np.array_equal(gd_original.data, gd_readback.data), "Data from original and compressed grid do not match"
124+
assert gd_original.numberOfCellsX == gd_readback.numberOfCellsX, "numberOfCellsX mismatch"
125+
assert gd_original.numberOfCellsY == gd_readback.numberOfCellsY, "numberOfCellsY mismatch"
126+
assert gd_original.dataUnits == gd_readback.dataUnits, "dataUnits mismatch"
89127

90128

91129
if __name__ == "__main__":

0 commit comments

Comments
 (0)