Skip to content

Commit 4de4812

Browse files
author
Rusty Holleman
committed
raster compat w/RAS2025, ditch multiprocessing, more RAS reading
1 parent 7d9f26d commit 4de4812

File tree

3 files changed

+110
-21
lines changed

3 files changed

+110
-21
lines changed

stompy/model/openfoam/depth_average.py

Lines changed: 21 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
@author: chrhol4587
88
"""
99
import numpy as np
10-
import os
10+
import os, time
1111
import hashlib, pickle
1212
from multiprocessing import Pool
1313
import subprocess
@@ -364,26 +364,30 @@ def precalc_raster_info_bbox(self,fld):
364364
raster_weights[pix,cell] = A[cell]/Apixel
365365
self.raster_precalc = raster_weights
366366

367-
368367
def precalc_raster_info_clipping(self, fld, force=False):
369368
# weights is a matrix with columns corresponding to openfoam cells
370369
# and rows corresponding to output pixels in row-major order
371370

372371
raster_weights = None
373372
tasks = [ (self.proc_dir(proc),fld)
374373
for proc in range(self.n_procs) ]
375-
376-
with Pool(self.n_tasks) as pool:
377-
378-
results = pool.starmap(precalc_raster_weights_proc,tasks)
374+
375+
# fallback to serial...
376+
if 0:
377+
with Pool(self.n_tasks) as pool:
378+
379+
results = pool.starmap(precalc_raster_weights_proc,tasks)
380+
else:
381+
results = [precalc_raster_weights_proc(*t)
382+
for t in tasks]
379383

380-
for proc,proc_raster_weights in enumerate(results):
381-
print(f"Assembling from processor {proc}")
382-
# Want to stack these left-to-right
383-
if raster_weights is None:
384-
raster_weights = proc_raster_weights
385-
else:
386-
raster_weights = sparse.hstack( (raster_weights,proc_raster_weights) )
384+
for proc,proc_raster_weights in enumerate(results):
385+
print(f"Assembling from processor {proc}")
386+
# Want to stack these left-to-right
387+
if raster_weights is None:
388+
raster_weights = proc_raster_weights
389+
else:
390+
raster_weights = sparse.hstack( (raster_weights,proc_raster_weights) )
387391
self.raster_precalc = raster_weights
388392

389393
def to_raster(self, variable, timename):
@@ -476,7 +480,10 @@ def face_centers(self,proc=None):
476480

477481
def compute_cell_centers(self,center_fn):
478482
case = os.path.dirname(center_fn)
479-
completed = subprocess.run([self.writeMeshObj,"-constant","-time","none"], cwd=case)
483+
try:
484+
completed = subprocess.run([self.writeMeshObj,"-constant","-time","none"], cwd=case)
485+
except FileNotFoundError:
486+
return False
480487
if completed.returncode!=0:
481488
return False
482489
if not os.path.exists(center_fn):

stompy/model/ras/result_reader.py

Lines changed: 71 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,11 @@
66
"""
77

88
from ...grid import unstructured_grid
9-
from ... import utils
9+
from ... import utils, memoize
1010
import h5py
1111
import numpy as np
12+
import datetime
13+
import glob, os
1214

1315
# Would like to replicate what RAS does
1416
# technically that requires the terrain
@@ -61,6 +63,54 @@ def face_interp_ben(wse_j1, wse_j2, z_j1, z_j2, face_min_z):
6163
face_wse = min(face_wse, max_wse)
6264
return face_wse
6365

66+
class RasProject:
67+
def __init__(self,prj_fn):
68+
self.prj_fn = prj_fn
69+
self.prj_dir = os.path.dirname(prj_fn)
70+
self.prj_name = os.path.basename(prj_fn).replace(".prj","")
71+
72+
def getPlanFile(self, plan_name):
73+
'''
74+
reads the plan file from the plan HDF5 file and finds correct corresponding plan file to the short ID
75+
'''
76+
plan_files = self.getPlanFiles()
77+
h=None
78+
for plf in plan_files:
79+
if plf.endswith('.tmp.hdf'):
80+
continue
81+
try:
82+
h = h5py.File(os.path.join(self.prj_dir, plf), 'r')
83+
try:
84+
short_title = h['Results/Unsteady'].attrs['Short ID'].decode('utf-8')
85+
finally:
86+
h.close()
87+
if short_title == plan_name:
88+
print(f'Found plan file {plf} for plan: {plan_name}')
89+
return plf
90+
except Exception as e:
91+
print('Error reading plan file:', plf)
92+
# print(e)
93+
continue
94+
95+
print('Unable to find plan file for plan:', plan_name)
96+
#print('Please specficy plan file using variable plan_file=... in inputs.')
97+
return None
98+
99+
def getPlanFiles(self):
100+
'''
101+
gets all the plan files in the project directory
102+
'''
103+
plan_file_form = f'{self.prj_name}.p*.hdf' # "*.p[!*.]*"
104+
plan_files = glob.glob(os.path.join(self.prj_dir, plan_file_form))
105+
return plan_files
106+
107+
@memoize.imemoize()
108+
def get_results(self,plan_name,area_name=None):
109+
plan_file = self.getPlanFile(plan_name)
110+
if plan_file is None:
111+
return None
112+
return RasReader(plan_file, area_name=area_name)
113+
64114
class RasReader:
65115
# Where to find results within h5 file
66116
unsteady_base_ras6=('/Results/Unsteady/Output/Output Blocks/'
@@ -73,6 +123,10 @@ def __init__(self,results_fn, area_name=None):
73123

74124
self.load_h5()
75125
self.load_grid()
126+
127+
def short_title(self):
128+
return self.h5['Results/Unsteady'].attrs['Short ID'].decode('utf-8')
129+
76130
def load_h5(self):
77131
self.h5 = h5py.File(self.results_fn,'r')
78132
# Is this RAS6 or RAS2025?
@@ -81,6 +135,10 @@ def load_h5(self):
81135
self.version="RAS6"
82136
except KeyError:
83137
self.version="RAS2025"
138+
139+
def close(self):
140+
self.h5.close()
141+
self.h5 = None
84142

85143
def terrain_file(self):
86144
key = f"/Geometry/2D Flow Areas/{self.area_name}"
@@ -113,8 +171,19 @@ def area_base(self):
113171
else:
114172
raise Exception("Bad version: "+self.version)
115173

116-
def time_days(self):
174+
def time_relative_days(self):
117175
return self.h5[self.unsteady_base+'/Time']
176+
177+
def time_start(self):
178+
t = self.h5['Plan Data/Plan Information'].attrs['Simulation Start Time'].decode('ascii')
179+
# '22Aug2022 02:00:00'
180+
t_datetime = datetime.datetime.strptime(t,"%d%b%Y %H:%M:%S")
181+
return np.datetime64(t_datetime)
182+
183+
def times(self):
184+
t0 = self.time_start()
185+
offset_days = self.time_relative_days()[:]
186+
return t0 + np.array(offset_days*86400,np.int64)*np.timedelta64(1,'s')
118187

119188
def cell_wse(self,time_step,trim_virtual=True):
120189
key=self.area_base+'Water Surface'

stompy/spatial/rasterize_stl.py

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -63,10 +63,17 @@ def stl_to_field(fp_stl,R,translate=[0,0,0],dx=None,dy=None,max_dim=None,pad=[0,
6363
else:
6464
# with rounding there's some one-off in here, but I'm not worrying
6565
# about that right now.
66-
dx=max( xxyy[1]-xxyy[0], xxyy[3]-xxyy[2] ) / float(max_dim)
66+
# try to be consistent with SimpleGrid.delta()
67+
# dx = (xmax-xmin)/(self.F.shape[1]-1.0)
68+
# i.e. xmin and xmax reference pixel centers
69+
#
70+
dx=max( xxyy[1]-xxyy[0], xxyy[3]-xxyy[2] ) / (float(max_dim)-1.0)
6771
dy=dx
68-
69-
Z = np.full( (int( (xxyy[3]-xxyy[2])/dx),int( (xxyy[1]-xxyy[0])/dx)), np.float64(nodata))
72+
73+
# 1.0 to account for pixel centers, and 0.5 so int() will round to nearest
74+
Z = np.full( (int(1.5 + (xxyy[3]-xxyy[2])/dx),
75+
int(1.5 + (xxyy[1]-xxyy[0])/dx)),
76+
np.float64(nodata))
7077
print("Output shape will be ",Z.shape)
7178

7279
fld = field.SimpleGrid(extents=xxyy,F=Z)
@@ -161,10 +168,13 @@ def main():
161168
kwargs['dy']=args.res
162169
else:
163170
kwargs['max_dim']=args.size
164-
fld = stl_to_field(fp_stl, R, translate=translate, nodata=args.nodata,
171+
fld = stl_to_field(fp_stl, R, translate=translate,
165172
pad=args.pad, **kwargs)
166173
fld.assign_projection(args.proj)
167-
fld.write_gdal(args.tif_file,overwrite=args.force)
174+
fld.F = fld.F.astype(np.float32) # RAS2025 doesn't like doubles
175+
# RAS 6.6 defaults to DEFLATE. Stick with that for better compatibility
176+
fld.write_gdal(args.tif_file,overwrite=args.force, nodata=args.nodata,
177+
options=["COMPRESS=DEFLATE"])
168178

169179
if args.plot:
170180
import matplotlib.pyplot as plt
@@ -181,3 +191,6 @@ def main():
181191
if __name__ == '__main__':
182192
main()
183193

194+
195+
196+

0 commit comments

Comments
 (0)