# # Reads ans manages the model grid # import numpy as np from netCDF4 import Dataset # Doc : https://spacetelescope.github.io/spherical_geometry/api/spherical_geometry.polygon.SphericalPolygon.html from spherical_geometry import polygon import RPPtools as RPP import os # import configparser config=configparser.ConfigParser() config.read("run.def") EarthRadius=config.getfloat("OverAll", "EarthRadius", fallback=6370000.0) # import getargs log_master, log_world = getargs.getLogger(__name__) INFO, DEBUG, ERROR = log_master.info, log_master.debug, log_world.error INFO_ALL, DEBUG_ALL = log_world.info, log_world.debug # def getbox(ncdf, corners) : # Add a few points to tbe box to make sure to cover everything halo_pts=2 ii=[np.argmin(abs(ncdf.variables["nav_lon"][0,:]-np.min(corners[0][:]))), np.argmin(abs(ncdf.variables["nav_lon"][0,:]-np.max(corners[0][:])))] jj=[np.argmin(abs(ncdf.variables["nav_lat"][:,0]-np.min(corners[1][:]))), np.argmin(abs(ncdf.variables["nav_lat"][:,0]-np.max(corners[1][:])))] return min(ii)-halo_pts, max(ii)+halo_pts, min(jj)-halo_pts, max(jj)+halo_pts # # Get the corners for a regular lat/lon grid # def corners(lon, lat, hdlon, hdlat) : jjm,iim = lon.shape # index = [[j,i] for i in range(iim) for j in range(jjm)] # cornersll = [RPP.boxit(np.array([lon[j,i], lat[j,i]]), hdlon, hdlat, 2) for j,i in index] Llats = [[0.0000001 if p[1] == 0 else np.sign(p[1])*89.99 if np.abs(p[1]) == 90 else p[1] for p in boxll] for boxll in cornersll] Llons = [[0.0000001 if p[0] == 0 else np.sign(p[0])*89.99 if np.abs(p[0]) == 90 else p[0] for p in boxll] for boxll in cornersll] centersll = [[lon[j,i], lat[j,i]] for j,i in index] cornerspoly = [ polygon.SphericalPolygon.from_lonlat(lons, lats, center=cent) for lons, lats, cent in zip(Llons, Llats, centersll)] radiusll = [RPP.maxradius(np.array(cent), np.array(lons), np.array(lats)) for cent, lons, lats in zip(centersll,Llons,Llats)] # return cornersll, cornerspoly, centersll, radiusll, index # def gather(x, index, default = 0) : y=[] for ia in index : if (ia[:,0] == [-1, -1]).all(): y.append([default]) else: y.append(list(x[ia[0,i],ia[1,i]] for i in range(ia.shape[1]) )) return y # def getattrcontaining(nc, varname, substr) : att=[] for s in nc.variables[varname].ncattrs() : if s.lower().find(substr) >= 0 : att.append(nc.variables[varname].getncattr(s)) return att # def calctopoindex(nf, istr, iend, jstr, jend, index, hydrogrid) : # The routing code in Fortran (i=1, j=2) # 8 --- 1 --- 2 # | | | # 7 --- X --- 3 # | | | # 6 --- 5 --- 4 # inc = np.array([[0,1,1,1,0,-1,-1,-1],[-1,-1,0,1,1,1,0,-1]]) elevation_precision=0.1 # jlen,ilen = nf.variables["orog"].shape missing = nf.variables["orog"].missing_value land = np.where(nf.variables["orog"][jstr:jend,istr:iend] < missing) topoind=np.zeros((jend-jstr,iend-istr)) topoind[:,:] = np.nan # for j,i in zip(land[:][0],land[:][1]) : trip = int(nf.variables["trip"][jstr+j,istr+i]-1) if trip < 8 : inf=(istr+i+inc[0,trip])%ilen jnf=(jstr+j+inc[1,trip])%jlen dz=max(elevation_precision,nf.variables["orog"][jstr+j,istr+i]-nf.variables["orog"][jnf,inf]) dl=nf.variables["disto"][jstr+j,istr+i]-nf.variables["disto"][jnf,inf] else : dz = max(elevation_precision,nf.variables["orog"][jstr+j,istr+i]) dl = nf.variables["disto"][jstr+j,istr+i] rx,ry = hydrogrid.resolution(i,j) dl = rx*0.5 topoind[j,i]=np.sqrt(dl**3./(dz*1.0e6)) # minitopo = np.nanmin(topoind) if minitopo <= np.finfo(float).eps : ERROR("Topoind close to zero encoutered.") sys.exit() # return gather(topoind, index), "Topographic index which serves for the residence time", minitopo # class HydroGrid : def __init__(self, lolacorners, wfile) : # self.source=config.get("OverAll", "HydroFile") INFO("Opening in HydroGrid : "+self.source) self.ncfile=Dataset(self.source,'r') istr, iend, jstr, jend = getbox(self.ncfile, lolacorners) hdlon = np.mean(np.abs(np.diff(self.ncfile.variables["nav_lon"][0,:]))) hdlat = np.mean(np.abs(np.diff(self.ncfile.variables["nav_lat"][:,0]))) # In case the box is on the edge of the routing.nc file if istr<0: istr = 0 if iend >=self.ncfile.variables["nav_lon"][0,:].shape[0]: iend = self.ncfile.variables["nav_lon"][0,:].shape[0] self.box=[istr, iend, jstr, jend] self.lon=np.copy(self.ncfile.variables["nav_lon"][jstr:jend,istr:iend]) self.lat=np.copy(self.ncfile.variables["nav_lat"][jstr:jend,istr:iend]) self.jjm,self.iim = self.lon.shape DEBUG("# Range Lon :"+str(np.min(self.lon))+" -- "+str(np.max(self.lon))) DEBUG("# Range Lat :"+str(np.min(self.lat))+" -- "+str(np.max(self.lat))) # if wfile is None or not os.path.exists(wfile): self.polyll, self.polylist, self.centers, self.radius, self.index = corners(self.lon, self.lat, hdlon, hdlat) def select(self, c, r) : indices = [i for i in range(len(self.centers)) if (RPP.loladist(np.array(c),np.array(self.centers[i])) <= r+self.radius[i])] return indices def resolution(self,i,j) : if i+1 >= self.iim or j+1 >= self.jjm : inx=i-1; jnx=j-1 else : inx=i+1; jnx=j+1 dlon = np.radians(np.abs(self.lon[j,i]-self.lon[j,inx])) alon = np.cos(np.radians(self.lat[j,i]))**2*(np.sin(dlon/2))**2 clon = 2*np.arctan2(np.sqrt(alon), np.sqrt(1-alon)) dlat = np.radians(np.abs(self.lat[j,i]-self.lat[jnx,i])) alat = (np.sin(dlat/2))**2 clat = 2*np.arctan2(np.sqrt(alat), np.sqrt(1-alat)) return EarthRadius*clon, EarthRadius*clat class HydroData : def __init__(self, nf, box, index, part, hydrogrid) : istr, iend, jstr, jend = box[:] # # Flow direction # self.trip=gather(nf.variables["trip"][jstr:jend,istr:iend].astype(np.float32), index, 97) self.tripdesc=nf.variables["trip"].long_name # # ID of basin # self.basins=gather(nf.variables["basins"][jstr:jend,istr:iend], index, 999) self.basinsdesc=nf.variables["basins"].long_name att = getattrcontaining(nf, "basins", "max") if len(att) > 0 : self.basinsmax=att[0] else : INFO("We need to scan full file to find the largest basin ID") # This variable seems not to be used further ##self.basinsmax = part.domainmax(np.ma.max(np.ma.masked_where(self.basins < 1.e10, self.basins))) ##self.basinsmax = part.domainmax(np.max(np.array(self.basins)[np.array(self.basins) < missing])) self.basinsmax = 999 # # Distance to ocean # self.disto=gather(nf.variables["disto"][jstr:jend,istr:iend].astype(np.float32), index, 0) self.distodesc=nf.variables["disto"].long_name # # Flow accumulation # self.fac=gather(nf.variables["fac"][jstr:jend,istr:iend].astype(np.float32), index, 0) self.facdesc=nf.variables["fac"].long_name # # If Orography is present in the file then we can compute the topographic index. # Else the topographic index needs to be present in the file. # if "orog" in nf.variables.keys(): self.orog = gather(nf.variables["orog"][jstr:jend,istr:iend].astype(np.float32), index, 0) self.orogdesc=nf.variables["orog"].long_name self.topoind, self.topoinddesc, mintopo = calctopoindex(nf, istr, iend, jstr, jend, index, hydrogrid) self.topoindmin = part.domainmin(mintopo) else: self.orog = gather(np.zeros((jend-jstr,iend-istr)).astype(np.float32), index) self.topoind=gather(nf.variables["topoind"][jstr:jend,istr:iend].astype(np.float32), index, 10) self.topoinddesc=nf.variables["topoind"].long_name att = getattrcontaining(nf, "topoind", "min") if len(att) > 0 : self.topoindmin=att[0] else : INFO("We need to scan full file to find minimum topoind over domain") self.topoindmin=np.min(np.where(nf.variables["topoind"][:,:] < 1.e15)) # # if "floodplains" in nf.variables.keys(): self.floodplains = gather(nf.variables["floodplains"][jstr:jend,istr:iend].astype(np.float32), index, 0) self.floodplainsdesc=nf.variables["floodplains"].long_name else: self.floodplains = gather(np.zeros((jend-jstr,iend-istr)).astype(np.float32), index)