17
17
import numpy as np
18
18
import pandas as pd
19
19
from scipy .interpolate import griddata
20
- from scipy .ndimage import gaussian_filter , maximum_filter
20
+ from scipy .ndimage import gaussian_filter , maximum_filter , \
21
+ binary_fill_holes
21
22
22
23
from hydrodiy .gis import gutils
23
24
from hydrodiy .io import csv
@@ -1019,6 +1020,7 @@ def __init__(self, name, flowdir):
1019
1020
self ._idxcell_outlet = None
1020
1021
self ._idxinlets = None
1021
1022
self ._idxcells_area = None
1023
+ self ._idxcells_area_filled = None
1022
1024
self ._idxcells_boundary = None
1023
1025
self ._flowpathlengths = None
1024
1026
self ._xycells_boundary = None
@@ -1075,8 +1077,10 @@ def __sub__(self, other):
1075
1077
catchment ._idxcells_boundary = None
1076
1078
catchment ._xycells_boundary = None
1077
1079
1078
- catchment ._idxcells_area = np .setdiff1d (self ._idxcells_area ,
1079
- other ._idxcells_area )
1080
+ # Difference between filled cell boundary
1081
+ catchment ._idxcells_area = np .setdiff1d (\
1082
+ self ._idxcells_area_filled ,
1083
+ other ._idxcells_area_filled )
1080
1084
1081
1085
return catchment
1082
1086
@@ -1103,6 +1107,9 @@ def from_dict(cls, dic):
1103
1107
catchment ._idxcells_area = \
1104
1108
np .array (dic ["idxcells_area" ]).astype (np .int64 )
1105
1109
1110
+ catchment ._idxcells_area_filled = \
1111
+ np .array (dic ["idxcells_area_filled" ]).astype (np .int64 )
1112
+
1106
1113
return catchment
1107
1114
1108
1115
@@ -1122,6 +1129,12 @@ def idxcells_area(self):
1122
1129
""" Get cells of catchment area """
1123
1130
return self ._idxcells_area
1124
1131
1132
+ @property
1133
+ def idxcells_area_filled (self ):
1134
+ """ Get cells of filled catchment area (no holes)"""
1135
+ return self ._idxcells_area_filled
1136
+
1137
+
1125
1138
@property
1126
1139
def flowpathlengths (self ):
1127
1140
""" Get flowpaths cells expressed in number of cells """
@@ -1153,11 +1166,11 @@ def clone(self):
1153
1166
def extent (self ):
1154
1167
""" Get catchment area extent """
1155
1168
1156
- if self ._idxcells_area is None :
1157
- raise ValueError ("idxcells_area is None, please" + \
1169
+ if self ._idxcells_area_filled is None :
1170
+ raise ValueError ("idxcells_area_filled is None, please" + \
1158
1171
" delineate the area" )
1159
1172
1160
- xy = self ._flowdir .cell2coord (self ._idxcells_area )
1173
+ xy = self ._flowdir .cell2coord (self ._idxcells_area_filled )
1161
1174
cz = self .flowdir .cellsize
1162
1175
return np .min (xy [:, 0 ])- cz / 2 , np .max (xy [:, 0 ])+ cz / 2 , \
1163
1176
np .min (xy [:, 1 ])- cz / 2 , np .max (xy [:, 1 ])+ cz / 2 ,
@@ -1223,11 +1236,14 @@ def delineate_area(self, idxcell_outlet, idxinlets=None, nval=1000000):
1223
1236
buffer2 = - 1 * np .ones (nval , dtype = np .int64 )
1224
1237
1225
1238
# Compute area
1239
+ flowdir = self .flowdir
1226
1240
ierr = c_hydrodiy_gis .delineate_area (FLOWDIRCODE ,
1227
- self . flowdir .data , self .idxcell_outlet , idxinlets ,
1241
+ flowdir .data , self .idxcell_outlet , idxinlets ,
1228
1242
idxcells , buffer1 , buffer2 )
1229
1243
1230
1244
if ierr > 0 :
1245
+ self ._idxcells_area = None
1246
+ self ._idxcells_area_filled = None
1231
1247
raise ValueError (("c_hydrodiy_gis.delineate_area" + \
1232
1248
" returns {0}. Consider increasing " + \
1233
1249
"buffer size ({1})" ).format (ierr , nval ))
@@ -1236,6 +1252,29 @@ def delineate_area(self, idxcell_outlet, idxinlets=None, nval=1000000):
1236
1252
self ._idxcells_area = idxcells [idx ]
1237
1253
1238
1254
1255
+ if idx .sum ()> 0 :
1256
+ # Fill area cells
1257
+ # .. create a minimal rectangle containing catchment area
1258
+ rowcol = flowdir .cell2rowcol (self ._idxcells_area )
1259
+ i0 , j0 = np .maximum (0 , rowcol .min (axis = 0 )- 1 )
1260
+ i1 , j1 = rowcol .max (axis = 0 )+ 1
1261
+ i1 = min (flowdir .nrows - 1 , i1 )
1262
+ j1 = min (flowdir .ncols - 1 , j1 )
1263
+ nrows , ncols = i1 - i0 + 1 , j1 - j0 + 1
1264
+ grid = np .zeros ((nrows , ncols ), dtype = int )
1265
+ grid [rowcol [:, 0 ]- i0 , rowcol [:, 1 ]- j0 ] = 1
1266
+ # .. fill holes
1267
+ grid = binary_fill_holes (grid )
1268
+ # .. get cell coordinates
1269
+ irows , icols = np .where (grid == 1 )
1270
+ irows += i0
1271
+ icols += j0
1272
+ filled = irows * flowdir .ncols + icols
1273
+ self ._idxcells_area_filled = filled
1274
+ else :
1275
+ self ._idxcells_area_filled = self ._idxcells_area
1276
+
1277
+
1239
1278
def delineate_boundary (self , catchment_area_mask = None ):
1240
1279
""" Delineate catchment boundary from area """
1241
1280
has_c_module ("gis" )
@@ -1248,20 +1287,22 @@ def delineate_boundary(self, catchment_area_mask=None):
1248
1287
ncols = self ._flowdir .ncols
1249
1288
1250
1289
# Initialise boundary cells with vector of same length
1251
- # than area
1252
- nval = np .int64 (len (self ._idxcells_area ))
1290
+ # than area. Use FILLED area cells!
1291
+ cells_area = self ._idxcells_area_filled
1292
+ nval = np .int64 (len (cells_area ))
1253
1293
idxcells_boundary = - 1 * np .ones (nval , dtype = np .int64 )
1254
1294
buf = - 1 * np .ones (nval , dtype = np .int64 )
1255
1295
1256
1296
# Initialise catchment area mask
1257
1297
if catchment_area_mask is None :
1258
1298
catchment_area_mask = np .zeros (nrows * ncols , dtype = np .int64 )
1259
- catchment_area_mask [self . _idxcells_area ] = 1
1299
+ catchment_area_mask [cells_area ] = 1
1260
1300
1301
+ # Use filled area (algorithm failed when there are holes)
1261
1302
# Compute boundary with varying dmax
1262
1303
# This point could be improved!!
1263
1304
ierr = c_hydrodiy_gis .delineate_boundary (nrows , ncols , \
1264
- self . _idxcells_area , \
1305
+ cells_area , \
1265
1306
buf , catchment_area_mask , \
1266
1307
idxcells_boundary )
1267
1308
@@ -1332,7 +1373,7 @@ def compute_flowpathlengths(self):
1332
1373
"idxcell_end" , "length[cell]" ])
1333
1374
1334
1375
1335
- def intersect (self , grid ):
1376
+ def intersect (self , grid , filled = False ):
1336
1377
""" Intersect catchment area with other grid and compute
1337
1378
the weight of each cell from the new grid falling into the
1338
1379
catchment.
@@ -1341,6 +1382,8 @@ def intersect(self, grid):
1341
1382
-----------
1342
1383
grid : hydrodiy.grid.Grid
1343
1384
Input grid (a.g. rainfall data grid).
1385
+ filled : bool
1386
+ Used filled catchment area.
1344
1387
1345
1388
Returns
1346
1389
-----------
@@ -1375,7 +1418,8 @@ def intersect(self, grid):
1375
1418
xll , yll , csz , nrows , ncols = grid ._getsize ()
1376
1419
_ , _ , csz_area , _ , _ = self .flowdir ._getsize ()
1377
1420
1378
- xy_area = self .flowdir .cell2coord (self ._idxcells_area )
1421
+ cells = self ._idxcells_area_filled if filled else self ._idxcells_area
1422
+ xy_area = self .flowdir .cell2coord (cells )
1379
1423
npoints = np .zeros ((1 ,), dtype = np .int64 )
1380
1424
idxcells = np .zeros (nrows * ncols , dtype = np .int64 )
1381
1425
weights = np .zeros (nrows * ncols , dtype = np .float64 )
@@ -1430,14 +1474,15 @@ def intersect(self, grid):
1430
1474
return area_grid , idxcells , weights
1431
1475
1432
1476
1433
- def isin (self , idxcell ):
1477
+ def isin (self , idxcell , filled = False ):
1434
1478
""" Check if a cell is within the catchment area """
1435
1479
1436
1480
if self ._idxcells_area is None :
1437
1481
raise ValueError ("idxcells_area is None, " + \
1438
1482
"please delineate the area" )
1439
1483
1440
- return idxcell in self ._idxcells_area
1484
+ cells = self ._idxcells_area_filled if filled else self ._idxcells_area
1485
+ return idxcell in cells
1441
1486
1442
1487
1443
1488
def compute_area (self , to_proj , from_proj = None ):
@@ -1495,7 +1540,10 @@ def plot_area(self, ax, *args, **kwargs):
1495
1540
raise ValueError ("idxcells_boundary is None, " + \
1496
1541
"please delineate the area" )
1497
1542
1498
- xy = self .flowdir .cell2coord (self ._idxcells_area )
1543
+ filled = kwargs .get ("filled" , False )
1544
+ cells = self ._idxcells_area_filled if filled else self ._idxcells_area
1545
+
1546
+ xy = self .flowdir .cell2coord (cells )
1499
1547
ax .plot (xy [:, 0 ], xy [:, 1 ], * args , ** kwargs )
1500
1548
1501
1549
@@ -1526,19 +1574,12 @@ def to_dict(self):
1526
1574
"idxcell_outlet" :self ._idxcell_outlet ,
1527
1575
"idxinlets" :inlets ,
1528
1576
"idxcells_area" :list (self ._idxcells_area ),
1577
+ "idxcells_area_filled" :list (self ._idxcells_area_filled ),
1529
1578
"flowdir" :self .flowdir .to_dict (),
1530
1579
}
1531
1580
return dic
1532
1581
1533
1582
1534
- def load (self , filename ):
1535
- """ Load data from a JSON file """
1536
-
1537
- if not filename .endswith ("json" ):
1538
- raise ValueError (("Filename ({0}) should end with a" + \
1539
- " bil extension" ).format (filename ))
1540
-
1541
-
1542
1583
1543
1584
def delineate_river (flowdir , idxupstream , nval = 1000000 ):
1544
1585
""" Delineate river upstream point and flow direction grid
0 commit comments