NREL-reV 0.8.7__py3-none-any.whl → 0.9.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {NREL_reV-0.8.7.dist-info → NREL_reV-0.9.0.dist-info}/METADATA +13 -10
- {NREL_reV-0.8.7.dist-info → NREL_reV-0.9.0.dist-info}/RECORD +43 -43
- {NREL_reV-0.8.7.dist-info → NREL_reV-0.9.0.dist-info}/WHEEL +1 -1
- reV/SAM/SAM.py +217 -133
- reV/SAM/econ.py +18 -14
- reV/SAM/generation.py +611 -422
- reV/SAM/windbos.py +93 -79
- reV/bespoke/bespoke.py +681 -377
- reV/bespoke/cli_bespoke.py +2 -0
- reV/bespoke/place_turbines.py +187 -43
- reV/config/output_request.py +2 -1
- reV/config/project_points.py +218 -140
- reV/econ/econ.py +166 -114
- reV/econ/economies_of_scale.py +91 -45
- reV/generation/base.py +331 -184
- reV/generation/generation.py +326 -200
- reV/generation/output_attributes/lcoe_fcr_inputs.json +38 -3
- reV/handlers/__init__.py +0 -1
- reV/handlers/exclusions.py +16 -15
- reV/handlers/multi_year.py +57 -26
- reV/handlers/outputs.py +6 -5
- reV/handlers/transmission.py +44 -27
- reV/hybrids/hybrid_methods.py +30 -30
- reV/hybrids/hybrids.py +305 -189
- reV/nrwal/nrwal.py +262 -168
- reV/qa_qc/cli_qa_qc.py +14 -10
- reV/qa_qc/qa_qc.py +217 -119
- reV/qa_qc/summary.py +228 -146
- reV/rep_profiles/rep_profiles.py +349 -230
- reV/supply_curve/aggregation.py +349 -188
- reV/supply_curve/competitive_wind_farms.py +90 -48
- reV/supply_curve/exclusions.py +138 -85
- reV/supply_curve/extent.py +75 -50
- reV/supply_curve/points.py +735 -390
- reV/supply_curve/sc_aggregation.py +357 -248
- reV/supply_curve/supply_curve.py +604 -347
- reV/supply_curve/tech_mapping.py +144 -82
- reV/utilities/__init__.py +274 -16
- reV/utilities/pytest_utils.py +8 -4
- reV/version.py +1 -1
- {NREL_reV-0.8.7.dist-info → NREL_reV-0.9.0.dist-info}/LICENSE +0 -0
- {NREL_reV-0.8.7.dist-info → NREL_reV-0.9.0.dist-info}/entry_points.txt +0 -0
- {NREL_reV-0.8.7.dist-info → NREL_reV-0.9.0.dist-info}/top_level.txt +0 -0
@@ -3,10 +3,12 @@
|
|
3
3
|
Competitive Wind Farms exclusion handler
|
4
4
|
"""
|
5
5
|
import logging
|
6
|
-
import numpy as np
|
7
6
|
|
7
|
+
import numpy as np
|
8
8
|
from rex.utilities.utilities import parse_table
|
9
9
|
|
10
|
+
from reV.utilities import SupplyCurveField
|
11
|
+
|
10
12
|
logger = logging.getLogger(__name__)
|
11
13
|
|
12
14
|
|
@@ -33,30 +35,36 @@ class CompetitiveWindFarms:
|
|
33
35
|
"""
|
34
36
|
self._wind_dirs = self._parse_wind_dirs(wind_dirs)
|
35
37
|
|
36
|
-
self._sc_gids, self._sc_point_gids, self._mask =
|
37
|
-
|
38
|
+
self._sc_gids, self._sc_point_gids, self._mask = self._parse_sc_points(
|
39
|
+
sc_points, offshore=offshore
|
40
|
+
)
|
38
41
|
|
39
42
|
self._offshore = offshore
|
40
43
|
|
41
44
|
valid = np.isin(self.sc_point_gids, self._wind_dirs.index)
|
42
45
|
if not np.all(valid):
|
43
|
-
msg = (
|
44
|
-
|
45
|
-
|
46
|
+
msg = (
|
47
|
+
"'sc_points contains sc_point_gid values that do not "
|
48
|
+
"correspond to valid 'wind_dirs' sc_point_gids:\n{}".format(
|
49
|
+
self.sc_point_gids[~valid]
|
50
|
+
)
|
51
|
+
)
|
46
52
|
logger.error(msg)
|
47
53
|
raise RuntimeError(msg)
|
48
54
|
|
49
55
|
mask = self._wind_dirs.index.isin(self._sc_point_gids.keys())
|
50
56
|
self._wind_dirs = self._wind_dirs.loc[mask]
|
51
|
-
self._upwind, self._downwind = self._get_neighbors(
|
52
|
-
|
57
|
+
self._upwind, self._downwind = self._get_neighbors(
|
58
|
+
self._wind_dirs, n_dirs=n_dirs
|
59
|
+
)
|
53
60
|
|
54
61
|
def __repr__(self):
|
55
62
|
gids = len(self._upwind)
|
56
63
|
# pylint: disable=unsubscriptable-object
|
57
64
|
neighbors = len(self._upwind.values[0])
|
58
|
-
msg =
|
59
|
-
|
65
|
+
msg = "{} with {} sc_point_gids and {} prominent directions".format(
|
66
|
+
self.__class__.__name__, gids, neighbors
|
67
|
+
)
|
60
68
|
|
61
69
|
return msg
|
62
70
|
|
@@ -76,23 +84,25 @@ class CompetitiveWindFarms:
|
|
76
84
|
"""
|
77
85
|
if not isinstance(keys, tuple):
|
78
86
|
msg = ("{} must be a tuple of form (source, gid) where source is: "
|
79
|
-
"
|
80
|
-
.format(keys
|
87
|
+
"{}, '{}', or 'upwind', 'downwind'"
|
88
|
+
.format(keys, SupplyCurveField.SC_GID,
|
89
|
+
SupplyCurveField.SC_POINT_GID))
|
81
90
|
logger.error(msg)
|
82
91
|
raise ValueError(msg)
|
83
92
|
|
84
93
|
source, gid = keys
|
85
|
-
if source ==
|
94
|
+
if source == SupplyCurveField.SC_POINT_GID:
|
86
95
|
out = self.map_sc_gid_to_sc_point_gid(gid)
|
87
|
-
elif source ==
|
96
|
+
elif source == SupplyCurveField.SC_GID:
|
88
97
|
out = self.map_sc_point_gid_to_sc_gid(gid)
|
89
|
-
elif source ==
|
98
|
+
elif source == "upwind":
|
90
99
|
out = self.map_upwind(gid)
|
91
|
-
elif source ==
|
100
|
+
elif source == "downwind":
|
92
101
|
out = self.map_downwind(gid)
|
93
102
|
else:
|
94
|
-
msg = ("{} must be:
|
95
|
-
"'downwind'".format(source
|
103
|
+
msg = ("{} must be: {}, {}, or 'upwind', "
|
104
|
+
"'downwind'".format(source, SupplyCurveField.SC_GID,
|
105
|
+
SupplyCurveField.SC_POINT_GID))
|
96
106
|
logger.error(msg)
|
97
107
|
raise ValueError(msg)
|
98
108
|
|
@@ -133,9 +143,9 @@ class CompetitiveWindFarms:
|
|
133
143
|
-------
|
134
144
|
ndarray
|
135
145
|
"""
|
136
|
-
sc_gids =
|
137
|
-
|
138
|
-
|
146
|
+
sc_gids = np.concatenate(
|
147
|
+
[self._sc_point_gids[gid] for gid in self.sc_point_gids]
|
148
|
+
)
|
139
149
|
|
140
150
|
return sc_gids
|
141
151
|
|
@@ -181,8 +191,10 @@ class CompetitiveWindFarms:
|
|
181
191
|
cardinal direction for each sc point gid
|
182
192
|
"""
|
183
193
|
wind_dirs = cls._parse_table(wind_dirs)
|
194
|
+
wind_dirs = wind_dirs.rename(
|
195
|
+
columns=SupplyCurveField.map_from_legacy())
|
184
196
|
|
185
|
-
wind_dirs = wind_dirs.set_index(
|
197
|
+
wind_dirs = wind_dirs.set_index(SupplyCurveField.SC_POINT_GID)
|
186
198
|
columns = [c for c in wind_dirs if c.endswith(('_gid', '_pr'))]
|
187
199
|
wind_dirs = wind_dirs[columns]
|
188
200
|
|
@@ -212,21 +224,25 @@ class CompetitiveWindFarms:
|
|
212
224
|
Mask array to mask excluded sc_point_gids
|
213
225
|
"""
|
214
226
|
sc_points = cls._parse_table(sc_points)
|
215
|
-
|
227
|
+
sc_points = sc_points.rename(
|
228
|
+
columns=SupplyCurveField.map_from_legacy())
|
229
|
+
if SupplyCurveField.OFFSHORE in sc_points and not offshore:
|
216
230
|
logger.debug('Not including offshore supply curve points in '
|
217
231
|
'CompetitiveWindFarm')
|
218
|
-
mask = sc_points[
|
232
|
+
mask = sc_points[SupplyCurveField.OFFSHORE] == 0
|
219
233
|
sc_points = sc_points.loc[mask]
|
220
234
|
|
221
|
-
mask = np.ones(int(1 + sc_points[
|
235
|
+
mask = np.ones(int(1 + sc_points[SupplyCurveField.SC_POINT_GID].max()),
|
236
|
+
dtype=bool)
|
222
237
|
|
223
|
-
sc_points = sc_points[[
|
224
|
-
|
238
|
+
sc_points = sc_points[[SupplyCurveField.SC_GID,
|
239
|
+
SupplyCurveField.SC_POINT_GID]]
|
240
|
+
sc_gids = sc_points.set_index(SupplyCurveField.SC_GID)
|
225
241
|
sc_gids = {k: int(v[0]) for k, v in sc_gids.iterrows()}
|
226
242
|
|
227
|
-
|
228
|
-
|
229
|
-
sc_point_gids = {int(k): v[
|
243
|
+
groups = sc_points.groupby(SupplyCurveField.SC_POINT_GID)
|
244
|
+
sc_point_gids = groups[SupplyCurveField.SC_GID].unique().to_frame()
|
245
|
+
sc_point_gids = {int(k): v[SupplyCurveField.SC_GID]
|
230
246
|
for k, v in sc_point_gids.iterrows()}
|
231
247
|
|
232
248
|
return sc_gids, sc_point_gids, mask
|
@@ -251,19 +267,30 @@ class CompetitiveWindFarms:
|
|
251
267
|
downwind : pandas.DataFrame
|
252
268
|
Downwind neighbor gids for n prominent wind directions
|
253
269
|
"""
|
254
|
-
cols = [
|
255
|
-
|
256
|
-
|
270
|
+
cols = [
|
271
|
+
c
|
272
|
+
for c in wind_dirs
|
273
|
+
if (c.endswith("_gid") and not c.startswith("sc"))
|
274
|
+
]
|
275
|
+
directions = [c.split("_")[0] for c in cols]
|
257
276
|
upwind_gids = wind_dirs[cols].values
|
258
277
|
|
259
|
-
cols = [
|
278
|
+
cols = ["{}_pr".format(d) for d in directions]
|
260
279
|
neighbor_pr = wind_dirs[cols].values
|
261
280
|
|
262
281
|
neighbors = np.argsort(neighbor_pr)[:, :n_dirs]
|
263
282
|
upwind_gids = np.take_along_axis(upwind_gids, neighbors, axis=1)
|
264
283
|
|
265
|
-
downwind_map = {
|
266
|
-
|
284
|
+
downwind_map = {
|
285
|
+
"N": "S",
|
286
|
+
"NE": "SW",
|
287
|
+
"E": "W",
|
288
|
+
"SE": "NW",
|
289
|
+
"S": "N",
|
290
|
+
"SW": "NE",
|
291
|
+
"W": "E",
|
292
|
+
"NW": "SE",
|
293
|
+
}
|
267
294
|
cols = ["{}_gid".format(downwind_map[d]) for d in directions]
|
268
295
|
downwind_gids = wind_dirs[cols].values
|
269
296
|
downwind_gids = np.take_along_axis(downwind_gids, neighbors, axis=1)
|
@@ -338,6 +365,7 @@ class CompetitiveWindFarms:
|
|
338
365
|
----------
|
339
366
|
sc_point_gid : int
|
340
367
|
Supply point curve gid to get upwind neighbors
|
368
|
+
|
341
369
|
Returns
|
342
370
|
-------
|
343
371
|
int | list
|
@@ -353,6 +381,7 @@ class CompetitiveWindFarms:
|
|
353
381
|
----------
|
354
382
|
sc_point_gid : int
|
355
383
|
Supply point curve gid to get downwind neighbors
|
384
|
+
|
356
385
|
Returns
|
357
386
|
-------
|
358
387
|
int | list
|
@@ -383,8 +412,9 @@ class CompetitiveWindFarms:
|
|
383
412
|
|
384
413
|
return out
|
385
414
|
|
386
|
-
def remove_noncompetitive_farm(
|
387
|
-
|
415
|
+
def remove_noncompetitive_farm(
|
416
|
+
self, sc_points, sort_on="total_lcoe", downwind=False
|
417
|
+
):
|
388
418
|
"""
|
389
419
|
Remove neighboring sc points for given number of prominent wind
|
390
420
|
directions
|
@@ -407,34 +437,45 @@ class CompetitiveWindFarms:
|
|
407
437
|
wind farms
|
408
438
|
"""
|
409
439
|
sc_points = self._parse_table(sc_points)
|
410
|
-
|
411
|
-
|
440
|
+
sc_points = sc_points.rename(
|
441
|
+
columns=SupplyCurveField.map_from_legacy())
|
442
|
+
if SupplyCurveField.OFFSHORE in sc_points and not self._offshore:
|
443
|
+
mask = sc_points[SupplyCurveField.OFFSHORE] == 0
|
412
444
|
sc_points = sc_points.loc[mask]
|
413
445
|
|
414
446
|
sc_points = sc_points.sort_values(sort_on)
|
415
447
|
|
416
|
-
sc_point_gids = sc_points[
|
448
|
+
sc_point_gids = sc_points[SupplyCurveField.SC_POINT_GID].values
|
449
|
+
sc_point_gids = sc_point_gids.astype(int)
|
417
450
|
|
418
451
|
for i in range(len(sc_points)):
|
419
452
|
gid = sc_point_gids[i]
|
420
453
|
if self.mask[gid]:
|
421
|
-
upwind_gids = self[
|
454
|
+
upwind_gids = self["upwind", gid]
|
422
455
|
for n in upwind_gids:
|
423
456
|
self.exclude_sc_point_gid(n)
|
424
457
|
|
425
458
|
if downwind:
|
426
|
-
downwind_gids = self[
|
459
|
+
downwind_gids = self["downwind", gid]
|
427
460
|
for n in downwind_gids:
|
428
461
|
self.exclude_sc_point_gid(n)
|
429
462
|
|
430
463
|
sc_gids = self.sc_gids
|
431
|
-
mask = sc_points[
|
464
|
+
mask = sc_points[SupplyCurveField.SC_GID].isin(sc_gids)
|
432
465
|
|
433
466
|
return sc_points.loc[mask].reset_index(drop=True)
|
434
467
|
|
435
468
|
@classmethod
|
436
|
-
def run(
|
437
|
-
|
469
|
+
def run(
|
470
|
+
cls,
|
471
|
+
wind_dirs,
|
472
|
+
sc_points,
|
473
|
+
n_dirs=2,
|
474
|
+
offshore=False,
|
475
|
+
sort_on="total_lcoe",
|
476
|
+
downwind=False,
|
477
|
+
out_fpath=None,
|
478
|
+
):
|
438
479
|
"""
|
439
480
|
Exclude given number of neighboring Supply Point gids based on most
|
440
481
|
prominent wind directions
|
@@ -469,8 +510,9 @@ class CompetitiveWindFarms:
|
|
469
510
|
wind farms
|
470
511
|
"""
|
471
512
|
cwf = cls(wind_dirs, sc_points, n_dirs=n_dirs, offshore=offshore)
|
472
|
-
sc_points = cwf.remove_noncompetitive_farm(
|
473
|
-
|
513
|
+
sc_points = cwf.remove_noncompetitive_farm(
|
514
|
+
sc_points, sort_on=sort_on, downwind=downwind
|
515
|
+
)
|
474
516
|
|
475
517
|
if out_fpath is not None:
|
476
518
|
sc_points.to_csv(out_fpath, index=False)
|
reV/supply_curve/exclusions.py
CHANGED
@@ -3,14 +3,14 @@
|
|
3
3
|
Generate reV inclusion mask from exclusion layers
|
4
4
|
"""
|
5
5
|
import logging
|
6
|
-
import numpy as np
|
7
|
-
from scipy import ndimage
|
8
6
|
from warnings import warn
|
9
7
|
|
8
|
+
import numpy as np
|
10
9
|
from rex.utilities.loggers import log_mem
|
11
|
-
from
|
12
|
-
|
13
|
-
from reV.
|
10
|
+
from scipy import ndimage
|
11
|
+
|
12
|
+
from reV.handlers.exclusions import ExclusionLayers, LATITUDE, LONGITUDE
|
13
|
+
from reV.utilities.exceptions import ExclusionLayerError, SupplyCurveInputError
|
14
14
|
|
15
15
|
logger = logging.getLogger(__name__)
|
16
16
|
|
@@ -32,6 +32,7 @@ class LayerMask:
|
|
32
32
|
weight=1.0,
|
33
33
|
exclude_nodata=False,
|
34
34
|
nodata_value=None,
|
35
|
+
extent=None,
|
35
36
|
**kwargs):
|
36
37
|
"""
|
37
38
|
Parameters
|
@@ -49,39 +50,44 @@ class LayerMask:
|
|
49
50
|
|
50
51
|
By default, ``None``.
|
51
52
|
exclude_range : list | tuple, optional
|
52
|
-
Two-item list of
|
53
|
-
to exclude. Mutually exclusive
|
54
|
-
in the description of
|
55
|
-
By default, ``None``.
|
53
|
+
Two-item list of [min threshold, max threshold] (ends are
|
54
|
+
inclusive) for values to exclude. Mutually exclusive
|
55
|
+
with other inputs (see info in the description of
|
56
|
+
`exclude_values`). By default, ``None``.
|
56
57
|
include_values : int | float | list, optional
|
57
58
|
Single value or list of values to include. Mutually
|
58
|
-
exclusive with other inputs
|
59
|
-
`exclude_values
|
59
|
+
exclusive with other inputs (see info in the description of
|
60
|
+
`exclude_values`). By default, ``None``.
|
60
61
|
include_range : list | tuple, optional
|
61
|
-
Two-item list of
|
62
|
-
to include. Mutually exclusive with
|
63
|
-
in the description of
|
64
|
-
By default, ``None``.
|
62
|
+
Two-item list of [min threshold, max threshold] (ends are
|
63
|
+
inclusive) for values to include. Mutually exclusive with
|
64
|
+
other inputs (see info in the description of
|
65
|
+
`exclude_values`). By default, ``None``.
|
65
66
|
include_weights : dict, optional
|
66
67
|
A dictionary of ``{value: weight}`` pairs, where the
|
67
68
|
``value`` in the layer that should be included with the
|
68
|
-
given ``weight``. Mutually exclusive with other inputs
|
69
|
-
info in the description of `exclude_values
|
69
|
+
given ``weight``. Mutually exclusive with other inputs (see
|
70
|
+
info in the description of `exclude_values`).
|
70
71
|
By default, ``None``.
|
71
72
|
force_include_values : int | float | list, optional
|
72
|
-
Force the inclusion of the given value(s).
|
73
|
-
|
74
|
-
|
73
|
+
Force the inclusion of the given value(s). This input
|
74
|
+
completely replaces anything provided as `include_values`
|
75
|
+
and is mutually exclusive with other inputs (eee info in
|
76
|
+
the description of `exclude_values`). By default, ``None``.
|
75
77
|
force_include_range : list | tuple, optional
|
76
78
|
Force the inclusion of given values in the range
|
77
|
-
|
78
|
-
|
79
|
-
`
|
79
|
+
[min threshold, max threshold] (ends are inclusive). This
|
80
|
+
input completely replaces anything provided as
|
81
|
+
`include_range` and is mutually exclusive with other inputs
|
82
|
+
(see info in the description of `exclude_values`).
|
83
|
+
By default, ``None``.
|
80
84
|
use_as_weights : bool, optional
|
81
|
-
Option to use layer as final inclusion weights.
|
82
|
-
|
83
|
-
|
84
|
-
are
|
85
|
+
Option to use layer as final inclusion weights (i.e.
|
86
|
+
1 = fully included, 0.75 = 75% included, 0.5 = 50% included,
|
87
|
+
etc.). If ``True``, all inclusion/exclusions specifications
|
88
|
+
for the layer are ignored and the raw values (scaled by the
|
89
|
+
`weight` input) are used as inclusion weights.
|
90
|
+
By default, ``False``.
|
85
91
|
weight : float, optional
|
86
92
|
Weight applied to exclusion layer after it is calculated.
|
87
93
|
Can be used, for example, to turn a binary exclusion layer
|
@@ -98,6 +104,38 @@ class LayerMask:
|
|
98
104
|
inferred when LayerMask is added to
|
99
105
|
:class:`reV.supply_curve.exclusions.ExclusionMask`.
|
100
106
|
By default, ``None``.
|
107
|
+
extent : dict, optional
|
108
|
+
Optional dictionary with values that can be used to
|
109
|
+
initialize this class (i.e. `layer`, `exclude_values`,
|
110
|
+
`include_range`, etc.). This dictionary should contain the
|
111
|
+
specifications to create a boolean mask that defines the
|
112
|
+
extent to which the original mask should be applied.
|
113
|
+
For example, suppose you specify the input the following
|
114
|
+
way::
|
115
|
+
|
116
|
+
input_dict = {
|
117
|
+
"viewsheds": {
|
118
|
+
"exclude_values": 1,
|
119
|
+
"extent": {
|
120
|
+
"layer": "federal_parks",
|
121
|
+
"include_range": [1, 5]
|
122
|
+
}
|
123
|
+
}
|
124
|
+
}
|
125
|
+
|
126
|
+
for layer_name, kwargs in input_dict.items():
|
127
|
+
layer = LayerMask(layer_name, **kwargs)
|
128
|
+
...
|
129
|
+
|
130
|
+
This would mean that you are masking out all viewshed layer
|
131
|
+
values equal to 1, **but only where the "federal_parks"
|
132
|
+
layer is equal to 1, 2, 3, 4, or 5**. Outside of these
|
133
|
+
regions (i.e. outside of federal park regions), the viewshed
|
134
|
+
exclusion is **NOT** applied. If the extent mask created by
|
135
|
+
these options is not boolean, an error is thrown (i.e. do
|
136
|
+
not specify `weight` or `use_as_weights`).
|
137
|
+
By default ``None``, which applies the original layer mask
|
138
|
+
to the full extent.
|
101
139
|
**kwargs
|
102
140
|
Optional inputs to maintain legacy kwargs of ``inclusion_*``
|
103
141
|
instead of ``include_*``.
|
@@ -125,13 +163,14 @@ class LayerMask:
|
|
125
163
|
self.nodata_value = nodata_value
|
126
164
|
|
127
165
|
if weight > 1 or weight < 0:
|
128
|
-
msg = ('
|
166
|
+
msg = ('Invalid weight ({}) provided for layer {}:'
|
129
167
|
'\nWeight must fall between 0 and 1!'.format(weight, layer))
|
130
168
|
logger.error(msg)
|
131
169
|
raise ValueError(msg)
|
132
170
|
|
133
171
|
self._weight = weight
|
134
172
|
self._mask_type = self._check_mask_type()
|
173
|
+
self.extent = LayerMask(**extent) if extent is not None else None
|
135
174
|
|
136
175
|
def __repr__(self):
|
137
176
|
msg = ('{} for "{}" exclusion, of type "{}"'
|
@@ -207,8 +246,7 @@ class LayerMask:
|
|
207
246
|
|
208
247
|
if all(isinstance(x, (int, float)) for x in range_var):
|
209
248
|
return min(range_var)
|
210
|
-
|
211
|
-
return range_var[0]
|
249
|
+
return range_var[0]
|
212
250
|
|
213
251
|
@property
|
214
252
|
def max_value(self):
|
@@ -226,8 +264,7 @@ class LayerMask:
|
|
226
264
|
|
227
265
|
if all(isinstance(x, (int, float)) for x in range_var):
|
228
266
|
return max(range_var)
|
229
|
-
|
230
|
-
return range_var[1]
|
267
|
+
return range_var[1]
|
231
268
|
|
232
269
|
@property
|
233
270
|
def exclude_values(self):
|
@@ -331,7 +368,7 @@ class LayerMask:
|
|
331
368
|
contradictory
|
332
369
|
|
333
370
|
Returns
|
334
|
-
|
371
|
+
-------
|
335
372
|
mask : str
|
336
373
|
Mask type
|
337
374
|
"""
|
@@ -355,6 +392,16 @@ class LayerMask:
|
|
355
392
|
logger.error(msg)
|
356
393
|
raise ExclusionLayerError(msg)
|
357
394
|
|
395
|
+
if mask is None:
|
396
|
+
msg = ('Exactly one approach must be specified to create the '
|
397
|
+
'inclusion mask for layer {!r}! Please specify one of: '
|
398
|
+
'`exclude_values`, `exclude_range`, `include_values`, '
|
399
|
+
'`include_range`, `include_weights`, '
|
400
|
+
'`force_include_values`, or `force_include_range`.'
|
401
|
+
.format(self.name))
|
402
|
+
logger.error(msg)
|
403
|
+
raise ExclusionLayerError(msg)
|
404
|
+
|
358
405
|
if mask == 'include_weights' and self._weight < 1:
|
359
406
|
msg = ("Values are individually weighted when using "
|
360
407
|
"'include_weights', the supplied weight of {} will be "
|
@@ -640,7 +687,7 @@ class ExclusionMask:
|
|
640
687
|
Returns
|
641
688
|
-------
|
642
689
|
_excl_h5 : ExclusionLayers
|
643
|
-
|
690
|
+
"""
|
644
691
|
return self._excl_h5
|
645
692
|
|
646
693
|
@property
|
@@ -665,7 +712,7 @@ class ExclusionMask:
|
|
665
712
|
Returns
|
666
713
|
-------
|
667
714
|
list
|
668
|
-
|
715
|
+
"""
|
669
716
|
return self._layers.keys()
|
670
717
|
|
671
718
|
@property
|
@@ -676,7 +723,7 @@ class ExclusionMask:
|
|
676
723
|
Returns
|
677
724
|
-------
|
678
725
|
list
|
679
|
-
|
726
|
+
"""
|
680
727
|
return self._layers.values()
|
681
728
|
|
682
729
|
@property
|
@@ -700,7 +747,7 @@ class ExclusionMask:
|
|
700
747
|
-------
|
701
748
|
ndarray
|
702
749
|
"""
|
703
|
-
return self.excl_h5[
|
750
|
+
return self.excl_h5[LATITUDE]
|
704
751
|
|
705
752
|
@property
|
706
753
|
def longitude(self):
|
@@ -711,7 +758,7 @@ class ExclusionMask:
|
|
711
758
|
-------
|
712
759
|
ndarray
|
713
760
|
"""
|
714
|
-
return self.excl_h5[
|
761
|
+
return self.excl_h5[LONGITUDE]
|
715
762
|
|
716
763
|
def add_layer(self, layer, replace=False):
|
717
764
|
"""
|
@@ -888,34 +935,54 @@ class ExclusionMask:
|
|
888
935
|
|
889
936
|
return mask
|
890
937
|
|
891
|
-
def
|
892
|
-
|
893
|
-
|
938
|
+
def _add_layer_to_mask(self, mask, layer, ds_slice, check_layers,
|
939
|
+
combine_func):
|
940
|
+
"""Add layer mask to full mask."""
|
941
|
+
layer_mask = self._compute_layer_mask(layer, ds_slice, check_layers)
|
942
|
+
if mask is None:
|
943
|
+
return layer_mask
|
894
944
|
|
895
|
-
|
896
|
-
----------
|
897
|
-
mask : ndarray | None
|
898
|
-
Mask to apply force inclusion layers to
|
899
|
-
layers : list
|
900
|
-
List of force inclusion layers
|
901
|
-
ds_slice : int | slice | list | ndarray
|
902
|
-
What to extract from ds, each arg is for a sequential axis.
|
903
|
-
For example, (slice(0, 64), slice(0, 64)) will extract a 64x64
|
904
|
-
exclusions mask.
|
905
|
-
"""
|
906
|
-
for layer in layers:
|
907
|
-
layer_slice = (layer.name, ) + ds_slice
|
908
|
-
layer_mask = layer[self.excl_h5[layer_slice]]
|
909
|
-
logger.debug('Computing forced inclusions for {}. Layer has '
|
910
|
-
'average value of {:.2f}'
|
911
|
-
.format(layer, layer_mask.mean()))
|
912
|
-
log_mem(logger, log_level='DEBUG')
|
913
|
-
if mask is None:
|
914
|
-
mask = layer_mask
|
915
|
-
else:
|
916
|
-
mask = np.maximum(mask, layer_mask, dtype='float32')
|
945
|
+
return combine_func(mask, layer_mask, dtype='float32')
|
917
946
|
|
918
|
-
|
947
|
+
def _compute_layer_mask(self, layer, ds_slice, check_layers=False):
|
948
|
+
"""Compute mask for single layer, including extent."""
|
949
|
+
layer_mask = self._masked_layer_data(layer, ds_slice)
|
950
|
+
layer_mask = self._apply_layer_mask_extent(layer, layer_mask, ds_slice)
|
951
|
+
|
952
|
+
logger.debug('Computed exclusions {} for {}. Layer has average value '
|
953
|
+
'of {:.2f}.'
|
954
|
+
.format(layer, ds_slice, layer_mask.mean()))
|
955
|
+
log_mem(logger, log_level='DEBUG')
|
956
|
+
|
957
|
+
if check_layers and not layer_mask.any():
|
958
|
+
msg = "Layer {} is fully excluded!".format(layer.name)
|
959
|
+
logger.error(msg)
|
960
|
+
raise ExclusionLayerError(msg)
|
961
|
+
|
962
|
+
return layer_mask
|
963
|
+
|
964
|
+
def _apply_layer_mask_extent(self, layer, layer_mask, ds_slice):
|
965
|
+
"""Apply extent to layer mask, if any."""
|
966
|
+
if layer.extent is None:
|
967
|
+
return layer_mask
|
968
|
+
|
969
|
+
layer_extent = self._masked_layer_data(layer.extent, ds_slice)
|
970
|
+
if not np.array_equal(layer_extent, layer_extent.astype(bool)):
|
971
|
+
msg = ("Extent layer must be boolean (i.e. 0 and 1 values "
|
972
|
+
"only)! Please check your extent definition for layer "
|
973
|
+
"{} to ensure you are producing a boolean layer!"
|
974
|
+
.format(layer.name))
|
975
|
+
logger.error(msg)
|
976
|
+
raise ExclusionLayerError(msg)
|
977
|
+
|
978
|
+
logger.debug("Filtering mask for layer %s down to specified extent",
|
979
|
+
layer.name)
|
980
|
+
layer_mask = np.where(layer_extent, layer_mask, 1)
|
981
|
+
return layer_mask
|
982
|
+
|
983
|
+
def _masked_layer_data(self, layer, ds_slice):
|
984
|
+
"""Extract masked data for layer."""
|
985
|
+
return layer[self.excl_h5[(layer.name, ) + ds_slice]]
|
919
986
|
|
920
987
|
def _generate_mask(self, *ds_slice, check_layers=False):
|
921
988
|
"""
|
@@ -950,27 +1017,13 @@ class ExclusionMask:
|
|
950
1017
|
if layer.force_include:
|
951
1018
|
force_include.append(layer)
|
952
1019
|
else:
|
953
|
-
|
954
|
-
|
955
|
-
|
956
|
-
|
957
|
-
|
958
|
-
|
959
|
-
|
960
|
-
|
961
|
-
if check_layers and not layer_mask.any():
|
962
|
-
msg = ("Layer {} is fully excluded!"
|
963
|
-
.format(layer.name))
|
964
|
-
logger.error(msg)
|
965
|
-
raise ExclusionLayerError(msg)
|
966
|
-
|
967
|
-
if mask is None:
|
968
|
-
mask = layer_mask
|
969
|
-
else:
|
970
|
-
mask = np.minimum(mask, layer_mask, dtype='float32')
|
971
|
-
|
972
|
-
if force_include:
|
973
|
-
mask = self._force_include(mask, force_include, ds_slice)
|
1020
|
+
mask = self._add_layer_to_mask(mask, layer, ds_slice,
|
1021
|
+
check_layers,
|
1022
|
+
combine_func=np.minimum)
|
1023
|
+
for layer in force_include:
|
1024
|
+
mask = self._add_layer_to_mask(mask, layer, ds_slice,
|
1025
|
+
check_layers,
|
1026
|
+
combine_func=np.maximum)
|
974
1027
|
|
975
1028
|
if self._min_area is not None:
|
976
1029
|
mask = self._area_filter(mask, self._min_area,
|