Newer
Older
#
# 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")

POLCHER Jan
committed
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) :

POLCHER Jan
committed
# 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][:])))]

POLCHER Jan
committed
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) :
#
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) :
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
#

POLCHER Jan
committed
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
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
#
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])]

POLCHER Jan
committed
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

POLCHER Jan
committed
def __init__(self, nf, box, index, part, hydrogrid) :
istr, iend, jstr, jend = box[:]

POLCHER Jan
committed
#
# Flow direction
#
self.trip=gather(nf.variables["trip"][jstr:jend,istr:iend].astype(np.float32), index, 97)
self.tripdesc=nf.variables["trip"].long_name
#

POLCHER Jan
committed
# 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 :

POLCHER Jan
committed
INFO("We need to scan full file to find the largest basin ID")
# This variable seems not to be used further

POLCHER Jan
committed
##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

POLCHER Jan
committed
# 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
#

POLCHER Jan
committed
# Flow accumulation
#
self.fac=gather(nf.variables["fac"][jstr:jend,istr:iend].astype(np.float32), index, 0)
self.facdesc=nf.variables["fac"].long_name

POLCHER Jan
committed
# 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.
#
self.orog = gather(nf.variables["orog"][jstr:jend,istr:iend].astype(np.float32), index, 0)
self.orogdesc=nf.variables["orog"].long_name

POLCHER Jan
committed
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)

POLCHER Jan
committed
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)