webviz-subsurface 0.2.36__py3-none-any.whl → 0.2.38__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.
- webviz_subsurface/__init__.py +1 -1
- webviz_subsurface/_components/color_picker.py +1 -1
- webviz_subsurface/_datainput/well_completions.py +2 -1
- webviz_subsurface/_providers/ensemble_polygon_provider/__init__.py +3 -0
- webviz_subsurface/_providers/ensemble_polygon_provider/_polygon_discovery.py +97 -0
- webviz_subsurface/_providers/ensemble_polygon_provider/_provider_impl_file.py +226 -0
- webviz_subsurface/_providers/ensemble_polygon_provider/ensemble_polygon_provider.py +53 -0
- webviz_subsurface/_providers/ensemble_polygon_provider/ensemble_polygon_provider_factory.py +99 -0
- webviz_subsurface/_providers/ensemble_polygon_provider/polygon_server.py +125 -0
- webviz_subsurface/plugins/_co2_leakage/_plugin.py +577 -293
- webviz_subsurface/plugins/_co2_leakage/_types.py +7 -0
- webviz_subsurface/plugins/_co2_leakage/_utilities/_misc.py +9 -0
- webviz_subsurface/plugins/_co2_leakage/_utilities/callbacks.py +226 -186
- webviz_subsurface/plugins/_co2_leakage/_utilities/co2volume.py +591 -128
- webviz_subsurface/plugins/_co2_leakage/_utilities/containment_data_provider.py +147 -0
- webviz_subsurface/plugins/_co2_leakage/_utilities/containment_info.py +31 -0
- webviz_subsurface/plugins/_co2_leakage/_utilities/ensemble_well_picks.py +105 -0
- webviz_subsurface/plugins/_co2_leakage/_utilities/generic.py +170 -2
- webviz_subsurface/plugins/_co2_leakage/_utilities/initialization.py +199 -97
- webviz_subsurface/plugins/_co2_leakage/_utilities/polygon_handler.py +60 -0
- webviz_subsurface/plugins/_co2_leakage/_utilities/summary_graphs.py +77 -173
- webviz_subsurface/plugins/_co2_leakage/_utilities/surface_publishing.py +122 -21
- webviz_subsurface/plugins/_co2_leakage/_utilities/unsmry_data_provider.py +108 -0
- webviz_subsurface/plugins/_co2_leakage/views/mainview/mainview.py +44 -19
- webviz_subsurface/plugins/_co2_leakage/views/mainview/settings.py +944 -359
- {webviz_subsurface-0.2.36.dist-info → webviz_subsurface-0.2.38.dist-info}/METADATA +2 -2
- {webviz_subsurface-0.2.36.dist-info → webviz_subsurface-0.2.38.dist-info}/RECORD +33 -20
- {webviz_subsurface-0.2.36.dist-info → webviz_subsurface-0.2.38.dist-info}/WHEEL +1 -1
- /webviz_subsurface/plugins/_co2_leakage/_utilities/{fault_polygons.py → fault_polygons_handler.py} +0 -0
- {webviz_subsurface-0.2.36.dist-info → webviz_subsurface-0.2.38.dist-info}/LICENSE +0 -0
- {webviz_subsurface-0.2.36.dist-info → webviz_subsurface-0.2.38.dist-info}/LICENSE.chromedriver +0 -0
- {webviz_subsurface-0.2.36.dist-info → webviz_subsurface-0.2.38.dist-info}/entry_points.txt +0 -0
- {webviz_subsurface-0.2.36.dist-info → webviz_subsurface-0.2.38.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
from pathlib import Path
|
|
2
|
+
from typing import Dict
|
|
3
|
+
|
|
4
|
+
from fmu.ensemble import ScratchEnsemble
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def realization_paths(ens_path: str) -> Dict[int, Path]:
|
|
8
|
+
scratch_ensemble = ScratchEnsemble("_", paths=ens_path).filter("OK")
|
|
9
|
+
return {i: Path(r.runpath()) for i, r in scratch_ensemble.realizations.items()}
|
|
@@ -11,7 +11,6 @@ from flask_caching import Cache
|
|
|
11
11
|
|
|
12
12
|
from webviz_subsurface._providers import (
|
|
13
13
|
EnsembleSurfaceProvider,
|
|
14
|
-
EnsembleTableProvider,
|
|
15
14
|
SimulatedSurfaceAddress,
|
|
16
15
|
StatisticalSurfaceAddress,
|
|
17
16
|
SurfaceAddress,
|
|
@@ -21,19 +20,35 @@ from webviz_subsurface._providers import (
|
|
|
21
20
|
from webviz_subsurface._providers.ensemble_surface_provider.ensemble_surface_provider import (
|
|
22
21
|
SurfaceStatistic,
|
|
23
22
|
)
|
|
24
|
-
from webviz_subsurface.
|
|
23
|
+
from webviz_subsurface.plugins._co2_leakage._types import LegendData
|
|
25
24
|
from webviz_subsurface.plugins._co2_leakage._utilities import plume_extent
|
|
26
25
|
from webviz_subsurface.plugins._co2_leakage._utilities.co2volume import (
|
|
26
|
+
generate_co2_box_plot_figure,
|
|
27
|
+
generate_co2_statistics_figure,
|
|
27
28
|
generate_co2_time_containment_figure,
|
|
28
29
|
generate_co2_time_containment_one_realization_figure,
|
|
29
30
|
generate_co2_volume_figure,
|
|
30
31
|
)
|
|
32
|
+
from webviz_subsurface.plugins._co2_leakage._utilities.containment_data_provider import (
|
|
33
|
+
ContainmentDataProvider,
|
|
34
|
+
)
|
|
35
|
+
from webviz_subsurface.plugins._co2_leakage._utilities.containment_info import (
|
|
36
|
+
ContainmentInfo,
|
|
37
|
+
StatisticsTabOption,
|
|
38
|
+
)
|
|
39
|
+
from webviz_subsurface.plugins._co2_leakage._utilities.ensemble_well_picks import (
|
|
40
|
+
EnsembleWellPicks,
|
|
41
|
+
)
|
|
31
42
|
from webviz_subsurface.plugins._co2_leakage._utilities.generic import (
|
|
32
43
|
Co2MassScale,
|
|
33
44
|
Co2VolumeScale,
|
|
45
|
+
FilteredMapAttribute,
|
|
34
46
|
GraphSource,
|
|
35
47
|
LayoutLabels,
|
|
36
48
|
MapAttribute,
|
|
49
|
+
MapGroup,
|
|
50
|
+
MapType,
|
|
51
|
+
MenuOptions,
|
|
37
52
|
)
|
|
38
53
|
from webviz_subsurface.plugins._co2_leakage._utilities.summary_graphs import (
|
|
39
54
|
generate_summary_figure,
|
|
@@ -42,21 +57,22 @@ from webviz_subsurface.plugins._co2_leakage._utilities.surface_publishing import
|
|
|
42
57
|
TruncatedSurfaceAddress,
|
|
43
58
|
publish_and_get_surface_metadata,
|
|
44
59
|
)
|
|
45
|
-
from webviz_subsurface.plugins.
|
|
46
|
-
|
|
60
|
+
from webviz_subsurface.plugins._co2_leakage._utilities.unsmry_data_provider import (
|
|
61
|
+
UnsmryDataProvider,
|
|
47
62
|
)
|
|
48
63
|
|
|
49
64
|
|
|
50
65
|
def property_origin(
|
|
51
|
-
attribute: MapAttribute, map_attribute_names:
|
|
66
|
+
attribute: MapAttribute, map_attribute_names: FilteredMapAttribute
|
|
52
67
|
) -> str:
|
|
53
|
-
if attribute
|
|
54
|
-
return
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
68
|
+
if MapType[MapAttribute(attribute).name].value == "PLUME":
|
|
69
|
+
return [
|
|
70
|
+
map_attribute_names[attr]
|
|
71
|
+
for attr in MapAttribute
|
|
72
|
+
if MapGroup[attr.name].value == MapGroup[MapAttribute(attribute).name].value
|
|
73
|
+
and MapType[attr.name] == "MAX"
|
|
74
|
+
][0]
|
|
75
|
+
return map_attribute_names[attribute]
|
|
60
76
|
|
|
61
77
|
|
|
62
78
|
@dataclass
|
|
@@ -77,7 +93,7 @@ class SurfaceData:
|
|
|
77
93
|
color_map_name: str,
|
|
78
94
|
readable_name_: str,
|
|
79
95
|
visualization_info: Dict[str, Any],
|
|
80
|
-
map_attribute_names:
|
|
96
|
+
map_attribute_names: FilteredMapAttribute,
|
|
81
97
|
) -> Tuple[Any, Optional[Any]]:
|
|
82
98
|
surf_meta, img_url, summed_mass = publish_and_get_surface_metadata(
|
|
83
99
|
server,
|
|
@@ -110,22 +126,30 @@ class SurfaceData:
|
|
|
110
126
|
)
|
|
111
127
|
|
|
112
128
|
|
|
129
|
+
def extract_legendonly(figure: go.Figure) -> List[str]:
|
|
130
|
+
# Finds the names OR legendgroup of the traces in the figure which have their
|
|
131
|
+
# visibility set to "legendonly". In the figure, these traces are toggled OFF in the
|
|
132
|
+
# legend.
|
|
133
|
+
return [
|
|
134
|
+
d.get("legendgroup", d.get("name"))
|
|
135
|
+
for d in figure["data"]
|
|
136
|
+
if d.get("visible", "") == "legendonly"
|
|
137
|
+
]
|
|
138
|
+
|
|
139
|
+
|
|
113
140
|
def derive_surface_address(
|
|
114
141
|
surface_name: str,
|
|
115
142
|
attribute: MapAttribute,
|
|
116
143
|
date: Optional[str],
|
|
117
144
|
realization: List[int],
|
|
118
|
-
map_attribute_names:
|
|
145
|
+
map_attribute_names: FilteredMapAttribute,
|
|
119
146
|
statistic: str,
|
|
120
147
|
contour_data: Optional[Dict[str, Any]],
|
|
121
148
|
) -> Union[SurfaceAddress, TruncatedSurfaceAddress]:
|
|
122
|
-
if attribute
|
|
149
|
+
if MapType[MapAttribute(attribute).name].value == "PLUME":
|
|
150
|
+
max_attr_name = f"MAX_{MapGroup[MapAttribute(attribute).name]}"
|
|
123
151
|
assert date is not None
|
|
124
|
-
basis = (
|
|
125
|
-
MapAttribute.MAX_SGAS
|
|
126
|
-
if attribute == MapAttribute.SGAS_PLUME
|
|
127
|
-
else MapAttribute.MAX_AMFG
|
|
128
|
-
)
|
|
152
|
+
basis = getattr(MapAttribute, max_attr_name)
|
|
129
153
|
return TruncatedSurfaceAddress(
|
|
130
154
|
name=surface_name,
|
|
131
155
|
datestr=date,
|
|
@@ -136,11 +160,7 @@ def derive_surface_address(
|
|
|
136
160
|
)
|
|
137
161
|
date = (
|
|
138
162
|
None
|
|
139
|
-
if attribute
|
|
140
|
-
in [
|
|
141
|
-
MapAttribute.MIGRATION_TIME_SGAS,
|
|
142
|
-
MapAttribute.MIGRATION_TIME_AMFG,
|
|
143
|
-
]
|
|
163
|
+
if MapType[MapAttribute(attribute).name].value == "MIGRATION_TIME"
|
|
144
164
|
else date
|
|
145
165
|
)
|
|
146
166
|
if len(realization) == 1:
|
|
@@ -161,12 +181,9 @@ def derive_surface_address(
|
|
|
161
181
|
|
|
162
182
|
def readable_name(attribute: MapAttribute) -> str:
|
|
163
183
|
unit = ""
|
|
164
|
-
if attribute
|
|
165
|
-
MapAttribute.MIGRATION_TIME_SGAS,
|
|
166
|
-
MapAttribute.MIGRATION_TIME_AMFG,
|
|
167
|
-
]:
|
|
184
|
+
if MapType[MapAttribute(attribute).name].value == "MIGRATION_TIME":
|
|
168
185
|
unit = " [year]"
|
|
169
|
-
elif attribute
|
|
186
|
+
elif MapType[MapAttribute(attribute).name].value == "PLUME":
|
|
170
187
|
unit = " [# real.]"
|
|
171
188
|
return f"{attribute.value}{unit}"
|
|
172
189
|
|
|
@@ -211,12 +228,9 @@ def get_plume_polygon(
|
|
|
211
228
|
|
|
212
229
|
|
|
213
230
|
def _find_legend_title(attribute: MapAttribute, unit: str) -> str:
|
|
214
|
-
if attribute
|
|
215
|
-
MapAttribute.MIGRATION_TIME_SGAS,
|
|
216
|
-
MapAttribute.MIGRATION_TIME_AMFG,
|
|
217
|
-
]:
|
|
231
|
+
if MapType[MapAttribute(attribute).name].value == "MIGRATION_TIME":
|
|
218
232
|
return "years"
|
|
219
|
-
if
|
|
233
|
+
if MapType[MapAttribute(attribute).name].value == "MASS":
|
|
220
234
|
return unit
|
|
221
235
|
return ""
|
|
222
236
|
|
|
@@ -234,7 +248,8 @@ def create_map_annotations(
|
|
|
234
248
|
and surface_data.color_map_range[0] is not None
|
|
235
249
|
and surface_data.color_map_range[1] is not None
|
|
236
250
|
):
|
|
237
|
-
|
|
251
|
+
max_value = surface_data.color_map_range[1]
|
|
252
|
+
num_digits = 4 if max_value < 1 else np.ceil(np.log(max_value) / np.log(10))
|
|
238
253
|
numbersize = max((6, min((17 - num_digits, 11))))
|
|
239
254
|
annotations.append(
|
|
240
255
|
wsc.ViewAnnotation(
|
|
@@ -283,12 +298,13 @@ def create_map_viewports() -> Dict:
|
|
|
283
298
|
|
|
284
299
|
# pylint: disable=too-many-arguments
|
|
285
300
|
def create_map_layers(
|
|
301
|
+
realizations: List[int],
|
|
286
302
|
formation: str,
|
|
287
303
|
surface_data: Optional[SurfaceData],
|
|
288
304
|
fault_polygon_url: Optional[str],
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
well_pick_provider: Optional[
|
|
305
|
+
containment_bounds_url: Optional[str],
|
|
306
|
+
haz_bounds_url: Optional[str],
|
|
307
|
+
well_pick_provider: Optional[EnsembleWellPicks],
|
|
292
308
|
plume_extent_data: Optional[geojson.FeatureCollection],
|
|
293
309
|
options_dialog_options: List[int],
|
|
294
310
|
selected_wells: List[str],
|
|
@@ -322,8 +338,9 @@ def create_map_layers(
|
|
|
322
338
|
"data": fault_polygon_url,
|
|
323
339
|
}
|
|
324
340
|
)
|
|
341
|
+
|
|
325
342
|
if (
|
|
326
|
-
|
|
343
|
+
containment_bounds_url is not None
|
|
327
344
|
and LayoutLabels.SHOW_CONTAINMENT_POLYGON in options_dialog_options
|
|
328
345
|
):
|
|
329
346
|
layers.append(
|
|
@@ -331,14 +348,15 @@ def create_map_layers(
|
|
|
331
348
|
"@@type": "GeoJsonLayer",
|
|
332
349
|
"name": "Containment Polygon",
|
|
333
350
|
"id": "license-boundary-layer",
|
|
334
|
-
"data":
|
|
351
|
+
"data": containment_bounds_url,
|
|
335
352
|
"stroked": False,
|
|
336
353
|
"getFillColor": [0, 172, 0, 120],
|
|
337
354
|
"visible": True,
|
|
338
355
|
}
|
|
339
356
|
)
|
|
357
|
+
|
|
340
358
|
if (
|
|
341
|
-
|
|
359
|
+
haz_bounds_url is not None
|
|
342
360
|
and LayoutLabels.SHOW_HAZARDOUS_POLYGON in options_dialog_options
|
|
343
361
|
):
|
|
344
362
|
layers.append(
|
|
@@ -346,48 +364,25 @@ def create_map_layers(
|
|
|
346
364
|
"@@type": "GeoJsonLayer",
|
|
347
365
|
"name": "Hazardous Polygon",
|
|
348
366
|
"id": "hazardous-boundary-layer",
|
|
349
|
-
"data":
|
|
367
|
+
"data": haz_bounds_url,
|
|
350
368
|
"stroked": False,
|
|
351
369
|
"getFillColor": [200, 0, 0, 120],
|
|
352
370
|
"visible": True,
|
|
353
371
|
}
|
|
354
372
|
)
|
|
373
|
+
|
|
355
374
|
if (
|
|
356
375
|
well_pick_provider is not None
|
|
357
376
|
and formation is not None
|
|
377
|
+
and len(realizations) > 0
|
|
358
378
|
and LayoutLabels.SHOW_WELLS in options_dialog_options
|
|
359
379
|
):
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
if len(well_data["features"]) == 0:
|
|
363
|
-
wellstring = "well: " if len(selected_wells) == 1 else "wells: "
|
|
364
|
-
wellstring += ", ".join(selected_wells)
|
|
365
|
-
warnings.warn(
|
|
366
|
-
f"Combination of formation: {formation} and "
|
|
367
|
-
f"{wellstring} not found in well picks file."
|
|
368
|
-
)
|
|
369
|
-
for i in range(len(well_data["features"])):
|
|
370
|
-
current_attribute = well_data["features"][i]["properties"]["attribute"]
|
|
371
|
-
well_data["features"][i]["properties"]["attribute"] = (
|
|
372
|
-
" " + current_attribute
|
|
373
|
-
)
|
|
374
|
-
layers.append(
|
|
375
|
-
{
|
|
376
|
-
"@@type": "GeoJsonLayer",
|
|
377
|
-
"name": "Well Picks",
|
|
378
|
-
"id": "well-picks-layer",
|
|
379
|
-
"data": well_data,
|
|
380
|
-
"visible": True,
|
|
381
|
-
"getText": "@@=properties.attribute",
|
|
382
|
-
"getTextSize": 12,
|
|
383
|
-
"getTextAnchor": "start",
|
|
384
|
-
"pointType": "circle+text",
|
|
385
|
-
"lineWidthMinPixels": 2,
|
|
386
|
-
"pointRadiusMinPixels": 2,
|
|
387
|
-
"pickable": True,
|
|
388
|
-
"parameters": {"depthTest": False},
|
|
389
|
-
}
|
|
380
|
+
layer = well_pick_provider.geojson_layer(
|
|
381
|
+
realizations[0], selected_wells, formation
|
|
390
382
|
)
|
|
383
|
+
if layer is not None:
|
|
384
|
+
layers.append(layer)
|
|
385
|
+
|
|
391
386
|
if plume_extent_data is not None:
|
|
392
387
|
layers.append(
|
|
393
388
|
{
|
|
@@ -403,28 +398,58 @@ def create_map_layers(
|
|
|
403
398
|
|
|
404
399
|
|
|
405
400
|
def generate_containment_figures(
|
|
406
|
-
table_provider:
|
|
401
|
+
table_provider: ContainmentDataProvider,
|
|
407
402
|
co2_scale: Union[Co2MassScale, Co2VolumeScale],
|
|
408
|
-
|
|
403
|
+
realizations: List[int],
|
|
409
404
|
y_limits: List[Optional[float]],
|
|
410
|
-
containment_info:
|
|
405
|
+
containment_info: ContainmentInfo,
|
|
406
|
+
legenddata: LegendData,
|
|
411
407
|
) -> Tuple[go.Figure, go.Figure, go.Figure]:
|
|
412
408
|
try:
|
|
413
409
|
fig0 = generate_co2_volume_figure(
|
|
414
410
|
table_provider,
|
|
415
|
-
table_provider.realizations
|
|
411
|
+
table_provider.realizations,
|
|
416
412
|
co2_scale,
|
|
417
413
|
containment_info,
|
|
414
|
+
legenddata["bar_legendonly"],
|
|
418
415
|
)
|
|
419
|
-
fig1 =
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
416
|
+
fig1 = (
|
|
417
|
+
generate_co2_time_containment_figure(
|
|
418
|
+
table_provider,
|
|
419
|
+
realizations,
|
|
420
|
+
co2_scale,
|
|
421
|
+
containment_info,
|
|
422
|
+
legenddata["time_legendonly"],
|
|
423
|
+
)
|
|
424
|
+
if len(realizations) > 1
|
|
425
|
+
else generate_co2_time_containment_one_realization_figure(
|
|
426
|
+
table_provider,
|
|
427
|
+
co2_scale,
|
|
428
|
+
realizations[0],
|
|
429
|
+
y_limits,
|
|
430
|
+
containment_info,
|
|
431
|
+
)
|
|
427
432
|
)
|
|
433
|
+
if (
|
|
434
|
+
containment_info.statistics_tab_option
|
|
435
|
+
== StatisticsTabOption.PROBABILITY_PLOT
|
|
436
|
+
):
|
|
437
|
+
fig2 = generate_co2_statistics_figure(
|
|
438
|
+
table_provider,
|
|
439
|
+
realizations,
|
|
440
|
+
co2_scale,
|
|
441
|
+
containment_info,
|
|
442
|
+
legenddata["stats_legendonly"],
|
|
443
|
+
)
|
|
444
|
+
else: # "box_plot"
|
|
445
|
+
# Deliberately uses same legend as statistics
|
|
446
|
+
fig2 = generate_co2_box_plot_figure(
|
|
447
|
+
table_provider,
|
|
448
|
+
realizations,
|
|
449
|
+
co2_scale,
|
|
450
|
+
containment_info,
|
|
451
|
+
legenddata["stats_legendonly"],
|
|
452
|
+
)
|
|
428
453
|
except KeyError as exc:
|
|
429
454
|
warnings.warn(f"Could not generate CO2 figures: {exc}")
|
|
430
455
|
raise exc
|
|
@@ -432,58 +457,20 @@ def generate_containment_figures(
|
|
|
432
457
|
|
|
433
458
|
|
|
434
459
|
def generate_unsmry_figures(
|
|
435
|
-
table_provider_unsmry:
|
|
460
|
+
table_provider_unsmry: UnsmryDataProvider,
|
|
436
461
|
co2_mass_scale: Union[Co2MassScale, Co2VolumeScale],
|
|
437
|
-
table_provider_containment:
|
|
438
|
-
) ->
|
|
439
|
-
return (
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
co2_mass_scale,
|
|
444
|
-
table_provider_containment,
|
|
445
|
-
table_provider_containment.realizations(),
|
|
446
|
-
),
|
|
462
|
+
table_provider_containment: ContainmentDataProvider,
|
|
463
|
+
) -> go.Figure:
|
|
464
|
+
return generate_summary_figure(
|
|
465
|
+
table_provider_unsmry,
|
|
466
|
+
co2_mass_scale,
|
|
467
|
+
table_provider_containment,
|
|
447
468
|
)
|
|
448
469
|
|
|
449
470
|
|
|
450
|
-
def _parse_polygon_file(filename: str) -> Dict[str, Any]:
|
|
451
|
-
df = read_csv(filename)
|
|
452
|
-
if "x" in df.columns:
|
|
453
|
-
xyz = df[["x", "y"]].values
|
|
454
|
-
elif "X_UTME" in df.columns:
|
|
455
|
-
if "POLY_ID" in df.columns:
|
|
456
|
-
xyz = [gf[["X_UTME", "Y_UTMN"]].values for _, gf in df.groupby("POLY_ID")]
|
|
457
|
-
else:
|
|
458
|
-
xyz = df[["X_UTME", "Y_UTMN"]].values
|
|
459
|
-
else:
|
|
460
|
-
# Attempt to use the first two columns as the x and y coordinates
|
|
461
|
-
xyz = df.values[:, :2]
|
|
462
|
-
if isinstance(xyz, list):
|
|
463
|
-
poly_type = "MultiPolygon"
|
|
464
|
-
coords = [[arr.tolist()] for arr in xyz]
|
|
465
|
-
else:
|
|
466
|
-
poly_type = "Polygon"
|
|
467
|
-
coords = [xyz.tolist()]
|
|
468
|
-
as_geojson = {
|
|
469
|
-
"type": "FeatureCollection",
|
|
470
|
-
"features": [
|
|
471
|
-
{
|
|
472
|
-
"type": "Feature",
|
|
473
|
-
"properties": {},
|
|
474
|
-
"geometry": {
|
|
475
|
-
"type": poly_type,
|
|
476
|
-
"coordinates": coords,
|
|
477
|
-
},
|
|
478
|
-
}
|
|
479
|
-
],
|
|
480
|
-
}
|
|
481
|
-
return as_geojson
|
|
482
|
-
|
|
483
|
-
|
|
484
471
|
def process_visualization_info(
|
|
485
|
-
|
|
486
|
-
|
|
472
|
+
attribute: str,
|
|
473
|
+
thresholds: dict,
|
|
487
474
|
unit: str,
|
|
488
475
|
stored_info: Dict[str, Any],
|
|
489
476
|
cache: Cache,
|
|
@@ -491,90 +478,147 @@ def process_visualization_info(
|
|
|
491
478
|
"""
|
|
492
479
|
Clear surface cache if the threshold for visualization or mass unit is changed
|
|
493
480
|
"""
|
|
481
|
+
stored_info["attribute"] = attribute
|
|
494
482
|
stored_info["change"] = False
|
|
495
|
-
|
|
496
|
-
|
|
483
|
+
if (
|
|
484
|
+
MapType[MapAttribute(attribute).name].value not in ["PLUME", "MIGRATION_TIME"]
|
|
485
|
+
and unit != stored_info["unit"]
|
|
486
|
+
):
|
|
497
487
|
stored_info["unit"] = unit
|
|
498
488
|
stored_info["change"] = True
|
|
499
|
-
if
|
|
500
|
-
stored_info["
|
|
501
|
-
|
|
489
|
+
if thresholds is not None:
|
|
490
|
+
for att in stored_info["thresholds"].keys():
|
|
491
|
+
if stored_info["thresholds"][att] != thresholds[att]:
|
|
492
|
+
stored_info["change"] = True
|
|
493
|
+
stored_info["thresholds"][att] = thresholds[att]
|
|
502
494
|
if stored_info["change"]:
|
|
503
495
|
cache.clear()
|
|
504
|
-
# stored_info["n_clicks"] = n_clicks
|
|
505
496
|
return stored_info
|
|
506
497
|
|
|
507
498
|
|
|
499
|
+
# pylint: disable=too-many-locals
|
|
508
500
|
def process_containment_info(
|
|
509
501
|
zone: Optional[str],
|
|
510
502
|
region: Optional[str],
|
|
511
503
|
phase: str,
|
|
512
504
|
containment: str,
|
|
505
|
+
plume_group: str,
|
|
513
506
|
color_choice: str,
|
|
514
507
|
mark_choice: Optional[str],
|
|
515
508
|
sorting: str,
|
|
516
|
-
|
|
517
|
-
|
|
509
|
+
lines_to_show: str,
|
|
510
|
+
date_option: str,
|
|
511
|
+
statistics_tab_option: StatisticsTabOption,
|
|
512
|
+
box_show_points: str,
|
|
513
|
+
menu_options: MenuOptions,
|
|
514
|
+
) -> ContainmentInfo:
|
|
518
515
|
if mark_choice is None:
|
|
519
516
|
mark_choice = "phase"
|
|
520
517
|
zones = menu_options["zones"]
|
|
521
518
|
regions = menu_options["regions"]
|
|
519
|
+
plume_groups = menu_options["plume_groups"]
|
|
522
520
|
if len(zones) > 0:
|
|
523
521
|
zones = [zone_name for zone_name in zones if zone_name != "all"]
|
|
524
522
|
if len(regions) > 0:
|
|
525
523
|
regions = [reg_name for reg_name in regions if reg_name != "all"]
|
|
526
|
-
|
|
527
|
-
|
|
524
|
+
if len(plume_groups) > 0:
|
|
525
|
+
plume_groups = [pg_name for pg_name in plume_groups if pg_name != "all"]
|
|
526
|
+
|
|
527
|
+
def plume_sort_key(name: str) -> int:
|
|
528
|
+
if name == "undetermined":
|
|
529
|
+
return 999
|
|
530
|
+
return name.count("+")
|
|
531
|
+
|
|
532
|
+
plume_groups = sorted(plume_groups, key=plume_sort_key)
|
|
533
|
+
|
|
528
534
|
if "zone" in [mark_choice, color_choice]:
|
|
529
535
|
region = "all"
|
|
530
536
|
if "region" in [mark_choice, color_choice]:
|
|
531
537
|
zone = "all"
|
|
532
|
-
return
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
"
|
|
544
|
-
|
|
538
|
+
return ContainmentInfo(
|
|
539
|
+
zone=zone,
|
|
540
|
+
region=region,
|
|
541
|
+
zones=zones,
|
|
542
|
+
regions=regions,
|
|
543
|
+
phase=phase,
|
|
544
|
+
containment=containment,
|
|
545
|
+
plume_group=plume_group,
|
|
546
|
+
color_choice=color_choice,
|
|
547
|
+
mark_choice=mark_choice,
|
|
548
|
+
sorting=sorting,
|
|
549
|
+
phases=[phase for phase in menu_options["phases"] if phase != "total"],
|
|
550
|
+
containments=["hazardous", "outside", "contained"],
|
|
551
|
+
plume_groups=plume_groups,
|
|
552
|
+
use_stats=lines_to_show == "stat",
|
|
553
|
+
date_option=date_option,
|
|
554
|
+
statistics_tab_option=statistics_tab_option,
|
|
555
|
+
box_show_points=box_show_points,
|
|
556
|
+
)
|
|
545
557
|
|
|
546
558
|
|
|
547
|
-
def
|
|
548
|
-
|
|
559
|
+
def make_plot_ids(
|
|
560
|
+
ensemble: str,
|
|
549
561
|
source: GraphSource,
|
|
550
562
|
scale: Union[Co2MassScale, Co2VolumeScale],
|
|
551
|
-
containment_info:
|
|
563
|
+
containment_info: ContainmentInfo,
|
|
552
564
|
realizations: List[int],
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
565
|
+
# lines_to_show: str,
|
|
566
|
+
num_figs: int,
|
|
567
|
+
) -> List[str]:
|
|
568
|
+
"""
|
|
569
|
+
Removed some keywords from plot id that we don't want to trigger updates for
|
|
570
|
+
with respect to visible legends and potentially zoom level.
|
|
571
|
+
|
|
572
|
+
Note: Currently the legends are reset if you swap to a plot with different plot id
|
|
573
|
+
and back, so it works temporarily, in a sense. This might be good enough for now.
|
|
574
|
+
If we want to store it more extensively, we need to do something like what's been
|
|
575
|
+
outlined in _plugin.py.
|
|
576
|
+
"""
|
|
577
|
+
zone_str = containment_info.zone if containment_info.zone is not None else "None"
|
|
578
|
+
region_str = (
|
|
579
|
+
containment_info.region if containment_info.region is not None else "None"
|
|
580
|
+
)
|
|
581
|
+
plume_group_str = (
|
|
582
|
+
containment_info.plume_group
|
|
583
|
+
if containment_info.plume_group is not None
|
|
584
|
+
else "None"
|
|
585
|
+
)
|
|
586
|
+
mark_choice_str = (
|
|
587
|
+
containment_info.mark_choice
|
|
588
|
+
if containment_info.mark_choice is not None
|
|
589
|
+
else "None"
|
|
590
|
+
)
|
|
591
|
+
plot_id = "-".join(
|
|
592
|
+
(
|
|
593
|
+
ensemble,
|
|
594
|
+
source,
|
|
595
|
+
scale,
|
|
596
|
+
zone_str,
|
|
597
|
+
region_str,
|
|
598
|
+
plume_group_str,
|
|
599
|
+
str(containment_info.phase),
|
|
600
|
+
str(containment_info.containment),
|
|
601
|
+
containment_info.color_choice,
|
|
602
|
+
mark_choice_str,
|
|
603
|
+
containment_info.sorting,
|
|
604
|
+
containment_info.date_option,
|
|
574
605
|
)
|
|
575
|
-
|
|
606
|
+
)
|
|
607
|
+
ids = [plot_id] * num_figs
|
|
608
|
+
# ids += [plot_id + f"-{realizations}"] * (num_figs - 1)
|
|
609
|
+
# ids[1] += f"-{lines_to_show}"
|
|
610
|
+
ids[1] += "-single" if len(realizations) == 1 else "-multiple"
|
|
611
|
+
ids[2] += f"-{containment_info.statistics_tab_option}"
|
|
612
|
+
return ids
|
|
613
|
+
|
|
614
|
+
|
|
615
|
+
def set_plot_ids(
|
|
616
|
+
figs: List[go.Figure],
|
|
617
|
+
plot_ids: List[str],
|
|
618
|
+
) -> None:
|
|
619
|
+
for fig, plot_id in zip(figs, plot_ids):
|
|
620
|
+
if fig != no_update:
|
|
576
621
|
fig["layout"]["uirevision"] = plot_id
|
|
577
|
-
figs[-1]["layout"]["uirevision"] += f"-{realizations}"
|
|
578
622
|
|
|
579
623
|
|
|
580
624
|
def process_summed_mass(
|
|
@@ -589,11 +633,7 @@ def process_summed_mass(
|
|
|
589
633
|
) -> Tuple[Optional[SurfaceData], Dict[str, float]]:
|
|
590
634
|
summed_co2_key = f"{formation}-{realization[0]}-{datestr}-{attribute}-{unit}"
|
|
591
635
|
if len(realization) == 1:
|
|
592
|
-
if attribute
|
|
593
|
-
MapAttribute.MASS,
|
|
594
|
-
MapAttribute.DISSOLVED,
|
|
595
|
-
MapAttribute.FREE,
|
|
596
|
-
]:
|
|
636
|
+
if MapType[MapAttribute(attribute).name].value == "MASS":
|
|
597
637
|
if summed_mass is not None and summed_co2_key not in summed_co2:
|
|
598
638
|
summed_co2[summed_co2_key] = summed_mass
|
|
599
639
|
if summed_co2_key in summed_co2 and surf_data is not None:
|