webviz-subsurface 0.2.37__py3-none-any.whl → 0.2.39__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.
- tests/unit_tests/plugin_tests/test_tornado_data.py +10 -1
- webviz_subsurface/_components/tornado/_tornado_bar_chart.py +31 -11
- webviz_subsurface/_components/tornado/_tornado_data.py +20 -2
- webviz_subsurface/_datainput/well_completions.py +2 -1
- webviz_subsurface/_providers/ensemble_table_provider/ensemble_table_provider_factory.py +4 -0
- webviz_subsurface/_utils/design_matrix.py +36 -0
- webviz_subsurface/plugins/_co2_leakage/_plugin.py +623 -493
- webviz_subsurface/plugins/_co2_leakage/_types.py +7 -0
- webviz_subsurface/plugins/_co2_leakage/_utilities/callbacks.py +96 -52
- webviz_subsurface/plugins/_co2_leakage/_utilities/co2volume.py +300 -82
- webviz_subsurface/plugins/_co2_leakage/_utilities/containment_info.py +31 -0
- webviz_subsurface/plugins/_co2_leakage/_utilities/initialization.py +16 -7
- webviz_subsurface/plugins/_co2_leakage/_utilities/surface_publishing.py +102 -9
- webviz_subsurface/plugins/_co2_leakage/views/mainview/mainview.py +14 -1
- webviz_subsurface/plugins/_co2_leakage/views/mainview/settings.py +181 -58
- webviz_subsurface/plugins/_parameter_analysis/_types.py +1 -0
- webviz_subsurface/plugins/_parameter_analysis/_utils/_parameters_model.py +10 -2
- webviz_subsurface/plugins/_parameter_analysis/_views/_parameter_distributions_view/_settings/_visualization_type.py +2 -1
- {webviz_subsurface-0.2.37.dist-info → webviz_subsurface-0.2.39.dist-info}/METADATA +1 -1
- {webviz_subsurface-0.2.37.dist-info → webviz_subsurface-0.2.39.dist-info}/RECORD +25 -22
- {webviz_subsurface-0.2.37.dist-info → webviz_subsurface-0.2.39.dist-info}/LICENSE +0 -0
- {webviz_subsurface-0.2.37.dist-info → webviz_subsurface-0.2.39.dist-info}/LICENSE.chromedriver +0 -0
- {webviz_subsurface-0.2.37.dist-info → webviz_subsurface-0.2.39.dist-info}/WHEEL +0 -0
- {webviz_subsurface-0.2.37.dist-info → webviz_subsurface-0.2.39.dist-info}/entry_points.txt +0 -0
- {webviz_subsurface-0.2.37.dist-info → webviz_subsurface-0.2.39.dist-info}/top_level.txt +0 -0
|
@@ -2,7 +2,7 @@ import logging
|
|
|
2
2
|
from typing import Any, Dict, List, Optional, Tuple, Union
|
|
3
3
|
|
|
4
4
|
import plotly.graph_objects as go
|
|
5
|
-
from dash import Dash, Input, Output, State, callback, html, no_update
|
|
5
|
+
from dash import Dash, Input, Output, Patch, State, callback, ctx, html, no_update
|
|
6
6
|
from dash.exceptions import PreventUpdate
|
|
7
7
|
from webviz_config import WebvizPluginABC, WebvizSettings
|
|
8
8
|
from webviz_config.utils import StrEnum, callback_typecheck
|
|
@@ -15,6 +15,7 @@ from webviz_subsurface.plugins._co2_leakage._utilities.callbacks import (
|
|
|
15
15
|
create_map_layers,
|
|
16
16
|
create_map_viewports,
|
|
17
17
|
derive_surface_address,
|
|
18
|
+
extract_legendonly,
|
|
18
19
|
generate_containment_figures,
|
|
19
20
|
generate_unsmry_figures,
|
|
20
21
|
get_plume_polygon,
|
|
@@ -56,7 +57,9 @@ from webviz_subsurface.plugins._co2_leakage.views.mainview.mainview import (
|
|
|
56
57
|
from webviz_subsurface.plugins._co2_leakage.views.mainview.settings import ViewSettings
|
|
57
58
|
|
|
58
59
|
from . import _error
|
|
60
|
+
from ._types import LegendData
|
|
59
61
|
from ._utilities.color_tables import co2leakage_color_tables
|
|
62
|
+
from ._utilities.containment_info import StatisticsTabOption
|
|
60
63
|
|
|
61
64
|
LOGGER = logging.getLogger(__name__)
|
|
62
65
|
TABLES_PATH = "share/results/tables"
|
|
@@ -64,31 +67,89 @@ TABLES_PATH = "share/results/tables"
|
|
|
64
67
|
|
|
65
68
|
# pylint: disable=too-many-instance-attributes
|
|
66
69
|
class CO2Leakage(WebvizPluginABC):
|
|
67
|
-
"""
|
|
68
|
-
|
|
69
|
-
|
|
70
|
+
"""Plugin for analyzing CO2 leakage potential across multiple realizations in an
|
|
71
|
+
FMU ensemble
|
|
72
|
+
|
|
73
|
+
---
|
|
70
74
|
|
|
71
75
|
* **`ensembles`:** Which ensembles in `shared_settings` to visualize.
|
|
72
76
|
* **`well_pick_file`:** Path to a file containing well picks
|
|
73
|
-
* **`plume_mass_relpath`:** Path to a table of co2
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
* **`
|
|
79
|
-
* **`
|
|
80
|
-
polygons
|
|
81
|
-
* **`map_attribute_names`:** Dictionary for overriding the default mapping between
|
|
77
|
+
* **`plume_mass_relpath`:** Path to a table of co2 _containment data_ for co2 mass
|
|
78
|
+
* **`plume_actual_volume_relpath`:** Path to a table of co2 _containment data_ for co2
|
|
79
|
+
volume of type "actual"
|
|
80
|
+
* **`unsmry_relpath`:** Path to a csv/arrow version of a Cirrus/Eclipse unified summary
|
|
81
|
+
file
|
|
82
|
+
* **`fault_polygon_attribute`:** Polygons with this attribute are used as fault polygons
|
|
83
|
+
* **`map_attribute_names`:** Key-value pairs for overriding the default mapping between
|
|
82
84
|
attributes visualized by the plugin and attributes names used by
|
|
83
85
|
EnsembleSurfaceProvider
|
|
84
86
|
* **`initial_surface`:** Name of the surface/formation to show when the plugin is
|
|
85
|
-
launched. If
|
|
86
|
-
* **`map_surface_names_to_well_pick_names`:**
|
|
87
|
-
|
|
88
|
-
* **`map_surface_names_to_fault_polygons`:**
|
|
89
|
-
|
|
90
|
-
* **`boundary_settings`:** Settings
|
|
91
|
-
|
|
87
|
+
launched. If not provided, the first alphabetical surface is shown.
|
|
88
|
+
* **`map_surface_names_to_well_pick_names`:** Mapping between surface map names and
|
|
89
|
+
surface names used in the well pick file
|
|
90
|
+
* **`map_surface_names_to_fault_polygons`:** Mapping between surface map names and
|
|
91
|
+
surface names used by the fault polygons
|
|
92
|
+
* **`boundary_settings`:** Settings for polygons representing the containment and
|
|
93
|
+
hazardous areas
|
|
94
|
+
---
|
|
95
|
+
|
|
96
|
+
This plugin is tightly linked to the FMU CCS post-process available in the ccs-scripts
|
|
97
|
+
repository. If the ccs-scripts workflow is executed without alterations, there is no
|
|
98
|
+
need for any configuration except the `ensembles` keyword. If any steps of the
|
|
99
|
+
post-process are skipped, the plugin will exclude the respective results and
|
|
100
|
+
functionality.
|
|
101
|
+
|
|
102
|
+
Even though the workflow is standardized, it is sometimes necessary to override
|
|
103
|
+
specific settings. For all path settings, these are interpreted relative to the
|
|
104
|
+
realization root, but can also be absolute paths. Their default values are
|
|
105
|
+
- `well_pick_file`: `share/results/well_picks.csv`
|
|
106
|
+
- `plume_mass_relpath`: `share/results/tables/plume_mass.csv`
|
|
107
|
+
- `plume_actual_volume_relpath`: No value
|
|
108
|
+
- `unsmry_relpath`: No value
|
|
109
|
+
|
|
110
|
+
Fault polygons are assumed to be stored in the `share/results/polygons` folder, and
|
|
111
|
+
with a `dl_extracted_faultlines` attribute. This attribute can be overridden with
|
|
112
|
+
`fault_polygon_attribute`, but the relative path to the polygons cannot.
|
|
113
|
+
|
|
114
|
+
`map_attribute_names` can be used to override how attributes are mapped to specific
|
|
115
|
+
features of the plugin. For instance, the attribute `migration_time_sgas` is mapped to
|
|
116
|
+
the Migration Time (SGAS) visualization, but this can be overridden by specifying
|
|
117
|
+
```
|
|
118
|
+
map_attribute_names:
|
|
119
|
+
MIGRATION_TIME_SGAS: mig_time
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
The following keys are allowed: `MIGRATION_TIME_SGAS, MIGRATION_TIME_AMFG,
|
|
123
|
+
MIGRATION_TIME_XMF2, MAX_SGAS, MAX_AMFG, MAX_XMF2, MAX_SGSTRAND, MAX_SGTRH, MASS,
|
|
124
|
+
DISSOLVED, FREE, FREE_GAS, TRAPPED_GAS`
|
|
125
|
+
|
|
126
|
+
Well pick files and fault polygons might name surfaces differently than the ones
|
|
127
|
+
generated by the ccs-scripts workflow. The options
|
|
128
|
+
`map_surface_names_to_well_pick_names` and `map_surface_names_to_fault_polygons` can
|
|
129
|
+
be used to specify this mapping explicitly. For instance, if the well pick file
|
|
130
|
+
contains a surface called `top_sgas`, but the ccs-scripts workflow generated a
|
|
131
|
+
surface called `top_sgas_2`, this can be specified with
|
|
132
|
+
```
|
|
133
|
+
map_surface_names_to_well_pick_names:
|
|
134
|
+
top_sgas_2: top_sgas
|
|
135
|
+
```
|
|
136
|
+
Similar for `map_surface_names_to_fault_polygons`.
|
|
137
|
+
|
|
138
|
+
`boundary_settings` is the final override option, and it can be used to specify
|
|
139
|
+
polygons representing the containment and hazardous areas. By default, the polygons are
|
|
140
|
+
expected to be named:
|
|
141
|
+
- `share/results/polygons/containment--boundary.csv`
|
|
142
|
+
- `share/results/polygons/hazarduous--boundary.csv`
|
|
143
|
+
|
|
144
|
+
This corresponds to the following input:
|
|
145
|
+
```
|
|
146
|
+
boundary_settings:
|
|
147
|
+
polygon_file_pattern: share/results/polygons/*.csv
|
|
148
|
+
attribute: boundary
|
|
149
|
+
hazardous_name: hazardous
|
|
150
|
+
containment_name: containment
|
|
151
|
+
```
|
|
152
|
+
All four settings are optional, and if not specified, the default values are used.
|
|
92
153
|
"""
|
|
93
154
|
|
|
94
155
|
class Ids(StrEnum):
|
|
@@ -104,7 +165,7 @@ class CO2Leakage(WebvizPluginABC):
|
|
|
104
165
|
ensembles: List[str],
|
|
105
166
|
well_pick_file: Optional[str] = None,
|
|
106
167
|
plume_mass_relpath: str = TABLES_PATH + "/plume_mass.csv",
|
|
107
|
-
plume_actual_volume_relpath: str =
|
|
168
|
+
plume_actual_volume_relpath: Optional[str] = None,
|
|
108
169
|
unsmry_relpath: Optional[str] = None,
|
|
109
170
|
fault_polygon_attribute: str = "dl_extracted_faultlines",
|
|
110
171
|
initial_surface: Optional[str] = None,
|
|
@@ -190,7 +251,6 @@ class CO2Leakage(WebvizPluginABC):
|
|
|
190
251
|
"change": False,
|
|
191
252
|
"unit": "tons",
|
|
192
253
|
}
|
|
193
|
-
self._plot_id = ""
|
|
194
254
|
self._color_tables = co2leakage_color_tables()
|
|
195
255
|
self._well_pick_names: Dict[str, List[str]] = {
|
|
196
256
|
ens: (
|
|
@@ -261,504 +321,574 @@ class CO2Leakage(WebvizPluginABC):
|
|
|
261
321
|
return dates
|
|
262
322
|
|
|
263
323
|
# Might want to do some refactoring if this gets too big
|
|
264
|
-
# pylint: disable=too-many-statements
|
|
265
324
|
def _set_callbacks(self) -> None:
|
|
266
|
-
# Cannot avoid many arguments since all the parameters are needed
|
|
267
|
-
# to determine what to plot
|
|
268
325
|
if self._content["any_table"]:
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
326
|
+
self._add_graph_callback()
|
|
327
|
+
self._add_legend_change_callback()
|
|
328
|
+
self._add_set_unit_list_callback()
|
|
329
|
+
self._add_time_plot_visibility_callback()
|
|
330
|
+
|
|
331
|
+
if self._content["maps"]:
|
|
332
|
+
self._add_set_dates_callback()
|
|
333
|
+
self._add_date_slider_visibility_callback()
|
|
334
|
+
self._add_set_well_options_callback()
|
|
335
|
+
self._add_create_map_callback()
|
|
336
|
+
self._add_options_dialog_callback()
|
|
337
|
+
self._add_thresholds_dialog_callback()
|
|
338
|
+
|
|
339
|
+
self._add_feedback_dialog_callback()
|
|
340
|
+
|
|
341
|
+
if self._content["maps"] and self._content["any_table"]:
|
|
342
|
+
self._add_resize_plot_callback()
|
|
343
|
+
|
|
344
|
+
def _add_resize_plot_callback(self) -> None:
|
|
345
|
+
@callback(
|
|
346
|
+
Output(self._view_component(MapViewElement.Ids.TOP_ELEMENT), "style"),
|
|
347
|
+
Output(self._view_component(MapViewElement.Ids.BOTTOM_ELEMENT), "style"),
|
|
348
|
+
Output(self._view_component(MapViewElement.Ids.BAR_PLOT), "style"),
|
|
349
|
+
Output(self._view_component(MapViewElement.Ids.TIME_PLOT), "style"),
|
|
350
|
+
Output(self._view_component(MapViewElement.Ids.STATISTICS_PLOT), "style"),
|
|
351
|
+
Input(self._settings_component(ViewSettings.Ids.ENSEMBLE), "value"),
|
|
352
|
+
Input(self._view_component(MapViewElement.Ids.SIZE_SLIDER), "value"),
|
|
353
|
+
State(self._view_component(MapViewElement.Ids.TOP_ELEMENT), "style"),
|
|
354
|
+
State(self._view_component(MapViewElement.Ids.BOTTOM_ELEMENT), "style"),
|
|
355
|
+
Input(self._settings_component(ViewSettings.Ids.GRAPH_SOURCE), "value"),
|
|
356
|
+
)
|
|
357
|
+
def resize_plots(
|
|
358
|
+
ensemble: str,
|
|
359
|
+
slider_value: float,
|
|
360
|
+
top_style: Dict,
|
|
361
|
+
bottom_style: Dict,
|
|
362
|
+
source: GraphSource,
|
|
363
|
+
) -> List[Dict]:
|
|
364
|
+
bottom_style["height"] = f"{slider_value}vh"
|
|
365
|
+
top_style["height"] = f"{80 - slider_value}vh"
|
|
366
|
+
|
|
367
|
+
styles = [{"height": f"{slider_value * 0.9 - 4}vh", "width": "90%"}] * 3
|
|
368
|
+
if source == GraphSource.UNSMRY and self._unsmry_providers is None:
|
|
369
|
+
styles = [{"display": "none"}] * 3
|
|
370
|
+
elif (
|
|
371
|
+
source == GraphSource.CONTAINMENT_MASS
|
|
372
|
+
and ensemble not in self._co2_table_providers
|
|
373
|
+
):
|
|
374
|
+
styles = [{"display": "none"}] * 3
|
|
375
|
+
elif (
|
|
376
|
+
source == GraphSource.CONTAINMENT_ACTUAL_VOLUME
|
|
377
|
+
and ensemble not in self._co2_actual_volume_table_providers
|
|
378
|
+
):
|
|
379
|
+
styles = [{"display": "none"}] * 3
|
|
380
|
+
|
|
381
|
+
return [top_style, bottom_style] + styles
|
|
382
|
+
|
|
383
|
+
def _add_feedback_dialog_callback(self) -> None:
|
|
384
|
+
@callback(
|
|
385
|
+
Output(ViewSettings.Ids.FEEDBACK, "open"),
|
|
386
|
+
Input(ViewSettings.Ids.FEEDBACK_BUTTON, "n_clicks"),
|
|
387
|
+
)
|
|
388
|
+
def open_close_feedback(_n_clicks: Optional[int]) -> bool:
|
|
389
|
+
if _n_clicks is not None:
|
|
390
|
+
return _n_clicks > 0
|
|
391
|
+
raise PreventUpdate
|
|
392
|
+
|
|
393
|
+
def _add_thresholds_dialog_callback(self) -> None:
|
|
394
|
+
@callback(
|
|
395
|
+
Output(ViewSettings.Ids.VISUALIZATION_THRESHOLD_DIALOG, "open"),
|
|
396
|
+
Input(ViewSettings.Ids.VISUALIZATION_THRESHOLD_BUTTON, "n_clicks"),
|
|
397
|
+
)
|
|
398
|
+
def open_close_thresholds(_n_clicks: Optional[int]) -> bool:
|
|
399
|
+
if _n_clicks is not None:
|
|
400
|
+
return _n_clicks > 0
|
|
401
|
+
raise PreventUpdate
|
|
402
|
+
|
|
403
|
+
def _add_options_dialog_callback(self) -> None:
|
|
404
|
+
@callback(
|
|
405
|
+
Output(ViewSettings.Ids.OPTIONS_DIALOG, "open"),
|
|
406
|
+
Input(ViewSettings.Ids.OPTIONS_DIALOG_BUTTON, "n_clicks"),
|
|
407
|
+
)
|
|
408
|
+
def open_close_options_dialog(_n_clicks: Optional[int]) -> bool:
|
|
409
|
+
if _n_clicks is not None:
|
|
410
|
+
return _n_clicks > 0
|
|
411
|
+
raise PreventUpdate
|
|
412
|
+
|
|
413
|
+
def _add_create_map_callback(self) -> None:
|
|
414
|
+
# Cannot avoid many arguments and/or locals since all layers of the DeckGL map
|
|
415
|
+
# need to be updated simultaneously
|
|
416
|
+
# pylint: disable=too-many-arguments,too-many-locals
|
|
417
|
+
@callback(
|
|
418
|
+
Output(self._view_component(MapViewElement.Ids.DECKGL_MAP), "layers"),
|
|
419
|
+
Output(self._view_component(MapViewElement.Ids.DECKGL_MAP), "children"),
|
|
420
|
+
Output(self._view_component(MapViewElement.Ids.DECKGL_MAP), "views"),
|
|
421
|
+
inputs={
|
|
422
|
+
"attribute": Input(
|
|
423
|
+
self._settings_component(ViewSettings.Ids.PROPERTY), "value"
|
|
276
424
|
),
|
|
277
|
-
Input(
|
|
278
|
-
|
|
279
|
-
Input(self._settings_component(ViewSettings.Ids.CO2_SCALE), "value"),
|
|
280
|
-
Input(self._settings_component(ViewSettings.Ids.REALIZATION), "value"),
|
|
281
|
-
Input(
|
|
282
|
-
self._settings_component(ViewSettings.Ids.Y_MIN_AUTO_GRAPH), "value"
|
|
425
|
+
"date": Input(
|
|
426
|
+
self._view_component(MapViewElement.Ids.DATE_SLIDER), "value"
|
|
283
427
|
),
|
|
284
|
-
Input(
|
|
285
|
-
|
|
286
|
-
self._settings_component(ViewSettings.Ids.Y_MAX_AUTO_GRAPH), "value"
|
|
428
|
+
"formation": Input(
|
|
429
|
+
self._settings_component(ViewSettings.Ids.FORMATION), "value"
|
|
287
430
|
),
|
|
288
|
-
Input(
|
|
289
|
-
|
|
290
|
-
Input(self._settings_component(ViewSettings.Ids.REGION), "value"),
|
|
291
|
-
Input(self._settings_component(ViewSettings.Ids.PHASE), "value"),
|
|
292
|
-
Input(self._settings_component(ViewSettings.Ids.CONTAINMENT), "value"),
|
|
293
|
-
Input(self._settings_component(ViewSettings.Ids.PLUME_GROUP), "value"),
|
|
294
|
-
Input(self._settings_component(ViewSettings.Ids.COLOR_BY), "value"),
|
|
295
|
-
Input(self._settings_component(ViewSettings.Ids.MARK_BY), "value"),
|
|
296
|
-
Input(self._settings_component(ViewSettings.Ids.SORT_PLOT), "value"),
|
|
297
|
-
Input(self._settings_component(ViewSettings.Ids.REAL_OR_STAT), "value"),
|
|
298
|
-
Input(self._settings_component(ViewSettings.Ids.DATE_OPTION), "value"),
|
|
299
|
-
)
|
|
300
|
-
@callback_typecheck
|
|
301
|
-
def update_graphs(
|
|
302
|
-
ensemble: str,
|
|
303
|
-
source: GraphSource,
|
|
304
|
-
co2_scale: Union[Co2MassScale, Co2VolumeScale],
|
|
305
|
-
realizations: List[int],
|
|
306
|
-
y_min_auto: List[str],
|
|
307
|
-
y_min_val: Optional[float],
|
|
308
|
-
y_max_auto: List[str],
|
|
309
|
-
y_max_val: Optional[float],
|
|
310
|
-
zone: Optional[str],
|
|
311
|
-
region: Optional[str],
|
|
312
|
-
phase: str,
|
|
313
|
-
containment: str,
|
|
314
|
-
plume_group: str,
|
|
315
|
-
color_choice: str,
|
|
316
|
-
mark_choice: Optional[str],
|
|
317
|
-
sorting: str,
|
|
318
|
-
lines_to_show: str,
|
|
319
|
-
date_option: str,
|
|
320
|
-
) -> Tuple[Dict, go.Figure, go.Figure, go.Figure]:
|
|
321
|
-
# pylint: disable=too-many-locals
|
|
322
|
-
figs = [no_update] * 3
|
|
323
|
-
cont_info = process_containment_info(
|
|
324
|
-
zone,
|
|
325
|
-
region,
|
|
326
|
-
phase,
|
|
327
|
-
containment,
|
|
328
|
-
plume_group,
|
|
329
|
-
color_choice,
|
|
330
|
-
mark_choice,
|
|
331
|
-
sorting,
|
|
332
|
-
lines_to_show,
|
|
333
|
-
date_option,
|
|
334
|
-
self._menu_options[ensemble][source],
|
|
335
|
-
)
|
|
336
|
-
if source in [
|
|
337
|
-
GraphSource.CONTAINMENT_MASS,
|
|
338
|
-
GraphSource.CONTAINMENT_ACTUAL_VOLUME,
|
|
339
|
-
]:
|
|
340
|
-
plot_ids = make_plot_ids(
|
|
341
|
-
ensemble,
|
|
342
|
-
source,
|
|
343
|
-
co2_scale,
|
|
344
|
-
cont_info,
|
|
345
|
-
realizations,
|
|
346
|
-
lines_to_show,
|
|
347
|
-
len(figs),
|
|
348
|
-
)
|
|
349
|
-
cont_info["update_first_figure"] = self._plot_id != plot_ids[0]
|
|
350
|
-
self._plot_id = plot_ids[0]
|
|
351
|
-
y_limits = [
|
|
352
|
-
y_min_val if len(y_min_auto) == 0 else None,
|
|
353
|
-
y_max_val if len(y_max_auto) == 0 else None,
|
|
354
|
-
]
|
|
355
|
-
if (
|
|
356
|
-
source == GraphSource.CONTAINMENT_MASS
|
|
357
|
-
and ensemble in self._co2_table_providers
|
|
358
|
-
):
|
|
359
|
-
figs[: len(figs)] = generate_containment_figures(
|
|
360
|
-
self._co2_table_providers[ensemble],
|
|
361
|
-
co2_scale,
|
|
362
|
-
realizations,
|
|
363
|
-
y_limits,
|
|
364
|
-
cont_info,
|
|
365
|
-
)
|
|
366
|
-
elif (
|
|
367
|
-
source == GraphSource.CONTAINMENT_ACTUAL_VOLUME
|
|
368
|
-
and ensemble in self._co2_actual_volume_table_providers
|
|
369
|
-
):
|
|
370
|
-
figs[: len(figs)] = generate_containment_figures(
|
|
371
|
-
self._co2_actual_volume_table_providers[ensemble],
|
|
372
|
-
co2_scale,
|
|
373
|
-
realizations,
|
|
374
|
-
y_limits,
|
|
375
|
-
cont_info,
|
|
376
|
-
)
|
|
377
|
-
set_plot_ids(figs, plot_ids)
|
|
378
|
-
elif source == GraphSource.UNSMRY:
|
|
379
|
-
if self._unsmry_providers is not None:
|
|
380
|
-
if ensemble in self._unsmry_providers:
|
|
381
|
-
figs[0] = go.Figure()
|
|
382
|
-
figs[1] = generate_unsmry_figures(
|
|
383
|
-
self._unsmry_providers[ensemble],
|
|
384
|
-
co2_scale,
|
|
385
|
-
self._co2_table_providers[ensemble],
|
|
386
|
-
)
|
|
387
|
-
figs[2] = go.Figure()
|
|
388
|
-
else:
|
|
389
|
-
LOGGER.warning(
|
|
390
|
-
"""UNSMRY file has not been specified as input.
|
|
391
|
-
Please use unsmry_relpath in the configuration."""
|
|
392
|
-
)
|
|
393
|
-
return figs # type: ignore
|
|
394
|
-
|
|
395
|
-
@callback(
|
|
396
|
-
Output(self._settings_component(ViewSettings.Ids.CO2_SCALE), "options"),
|
|
397
|
-
Output(self._settings_component(ViewSettings.Ids.CO2_SCALE), "value"),
|
|
398
|
-
Input(self._settings_component(ViewSettings.Ids.GRAPH_SOURCE), "value"),
|
|
399
|
-
)
|
|
400
|
-
def make_unit_list(
|
|
401
|
-
attribute: str,
|
|
402
|
-
) -> Union[
|
|
403
|
-
Tuple[List[Any], Co2MassScale],
|
|
404
|
-
Tuple[List[Any], Co2VolumeScale],
|
|
405
|
-
]:
|
|
406
|
-
if attribute == GraphSource.CONTAINMENT_ACTUAL_VOLUME:
|
|
407
|
-
return list(Co2VolumeScale), Co2VolumeScale.BILLION_CUBIC_METERS
|
|
408
|
-
return list(Co2MassScale), Co2MassScale.MTONS
|
|
409
|
-
|
|
410
|
-
@callback(
|
|
411
|
-
Output(
|
|
412
|
-
self._settings_component(ViewSettings.Ids.REAL_OR_STAT), "style"
|
|
431
|
+
"realization": Input(
|
|
432
|
+
self._settings_component(ViewSettings.Ids.REALIZATION), "value"
|
|
413
433
|
),
|
|
414
|
-
|
|
415
|
-
self._settings_component(ViewSettings.Ids.
|
|
434
|
+
"statistic": Input(
|
|
435
|
+
self._settings_component(ViewSettings.Ids.STATISTIC), "value"
|
|
416
436
|
),
|
|
417
|
-
Input(
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
437
|
+
"color_map_name": Input(
|
|
438
|
+
self._settings_component(ViewSettings.Ids.COLOR_SCALE), "value"
|
|
439
|
+
),
|
|
440
|
+
"cm_min_auto": Input(
|
|
441
|
+
self._settings_component(ViewSettings.Ids.CM_MIN_AUTO), "value"
|
|
442
|
+
),
|
|
443
|
+
"cm_min_val": Input(
|
|
444
|
+
self._settings_component(ViewSettings.Ids.CM_MIN), "value"
|
|
445
|
+
),
|
|
446
|
+
"cm_max_auto": Input(
|
|
447
|
+
self._settings_component(ViewSettings.Ids.CM_MAX_AUTO), "value"
|
|
448
|
+
),
|
|
449
|
+
"cm_max_val": Input(
|
|
450
|
+
self._settings_component(ViewSettings.Ids.CM_MAX), "value"
|
|
451
|
+
),
|
|
452
|
+
"plume_threshold": Input(
|
|
453
|
+
self._settings_component(ViewSettings.Ids.PLUME_THRESHOLD),
|
|
454
|
+
"value",
|
|
455
|
+
),
|
|
456
|
+
"plume_smoothing": Input(
|
|
457
|
+
self._settings_component(ViewSettings.Ids.PLUME_SMOOTHING),
|
|
458
|
+
"value",
|
|
459
|
+
),
|
|
460
|
+
"visualization_update": Input(
|
|
461
|
+
self._settings_component(ViewSettings.Ids.VISUALIZATION_UPDATE),
|
|
462
|
+
"n_clicks",
|
|
463
|
+
),
|
|
464
|
+
"mass_unit": Input(
|
|
465
|
+
self._settings_component(ViewSettings.Ids.MASS_UNIT), "value"
|
|
466
|
+
),
|
|
467
|
+
"mass_unit_update": Input(
|
|
468
|
+
self._settings_component(ViewSettings.Ids.MASS_UNIT_UPDATE),
|
|
469
|
+
"n_clicks",
|
|
470
|
+
),
|
|
471
|
+
"options_dialog_options": Input(
|
|
472
|
+
ViewSettings.Ids.OPTIONS_DIALOG_OPTIONS, "value"
|
|
473
|
+
),
|
|
474
|
+
"selected_wells": Input(
|
|
475
|
+
ViewSettings.Ids.OPTIONS_DIALOG_WELL_FILTER, "value"
|
|
476
|
+
),
|
|
477
|
+
"ensemble": Input(
|
|
478
|
+
self._settings_component(ViewSettings.Ids.ENSEMBLE), "value"
|
|
479
|
+
),
|
|
480
|
+
"current_views": State(
|
|
481
|
+
self._view_component(MapViewElement.Ids.DECKGL_MAP), "views"
|
|
482
|
+
),
|
|
483
|
+
"thresholds": [Input(id, "value") for id in self._threshold_ids],
|
|
484
|
+
},
|
|
485
|
+
)
|
|
486
|
+
def update_map_attribute(
|
|
487
|
+
attribute: MapAttribute,
|
|
488
|
+
date: int,
|
|
489
|
+
formation: str,
|
|
490
|
+
realization: List[int],
|
|
491
|
+
statistic: str,
|
|
492
|
+
color_map_name: str,
|
|
493
|
+
cm_min_auto: List[str],
|
|
494
|
+
cm_min_val: Optional[float],
|
|
495
|
+
cm_max_auto: List[str],
|
|
496
|
+
cm_max_val: Optional[float],
|
|
497
|
+
plume_threshold: Optional[float],
|
|
498
|
+
plume_smoothing: Optional[float],
|
|
499
|
+
visualization_update: int,
|
|
500
|
+
mass_unit: str,
|
|
501
|
+
mass_unit_update: int,
|
|
502
|
+
options_dialog_options: List[int],
|
|
503
|
+
selected_wells: List[str],
|
|
504
|
+
ensemble: str,
|
|
505
|
+
current_views: List[Any],
|
|
506
|
+
thresholds: List[float],
|
|
507
|
+
) -> Tuple[List[Dict[Any, Any]], Optional[List[Any]], Dict[Any, Any]]:
|
|
508
|
+
# Unable to clear cache (when needed) without the protected member
|
|
509
|
+
# pylint: disable=protected-access
|
|
510
|
+
current_thresholds = dict(zip(self._threshold_ids, thresholds))
|
|
511
|
+
assert visualization_update >= 0 # Need the input to trigger callback
|
|
512
|
+
assert mass_unit_update >= 0 # These are just to silence pylint
|
|
513
|
+
self._visualization_info = process_visualization_info(
|
|
514
|
+
attribute,
|
|
515
|
+
current_thresholds,
|
|
516
|
+
mass_unit,
|
|
517
|
+
self._visualization_info,
|
|
518
|
+
self._surface_server._image_cache,
|
|
438
519
|
)
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
520
|
+
if self._visualization_info["change"]:
|
|
521
|
+
return [], None, no_update
|
|
522
|
+
attribute = MapAttribute(attribute)
|
|
523
|
+
if len(realization) == 0 or ensemble is None:
|
|
524
|
+
raise PreventUpdate
|
|
525
|
+
if isinstance(date, int):
|
|
526
|
+
datestr = self._ensemble_dates(ensemble)[date]
|
|
527
|
+
elif date is None:
|
|
528
|
+
datestr = None
|
|
529
|
+
# Contour data
|
|
530
|
+
contour_data = None
|
|
531
|
+
if MapType[MapAttribute(attribute).name].value == "PLUME":
|
|
532
|
+
contour_data = {
|
|
533
|
+
"property": property_origin(attribute, self._map_attribute_names),
|
|
534
|
+
"threshold": plume_threshold,
|
|
535
|
+
"smoothing": plume_smoothing,
|
|
452
536
|
}
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
Output(ViewSettings.Ids.OPTIONS_DIALOG_WELL_FILTER, "options"),
|
|
468
|
-
Output(ViewSettings.Ids.OPTIONS_DIALOG_WELL_FILTER, "value"),
|
|
469
|
-
Output(ViewSettings.Ids.OPTIONS_DIALOG_WELL_FILTER, "style"),
|
|
470
|
-
Output(ViewSettings.Ids.WELL_FILTER_HEADER, "style"),
|
|
471
|
-
Input(self._settings_component(ViewSettings.Ids.ENSEMBLE), "value"),
|
|
472
|
-
)
|
|
473
|
-
def set_well_options(
|
|
474
|
-
ensemble: str,
|
|
475
|
-
) -> Tuple[List[Any], List[str], Dict[Any, Any], Dict[Any, Any]]:
|
|
476
|
-
return (
|
|
477
|
-
[{"label": i, "value": i} for i in self._well_pick_names[ensemble]],
|
|
478
|
-
self._well_pick_names[ensemble],
|
|
479
|
-
{
|
|
480
|
-
"display": (
|
|
481
|
-
"block" if self._well_pick_names[ensemble] else "none"
|
|
482
|
-
),
|
|
483
|
-
"height": f"{len(self._well_pick_names[ensemble]) * 22}px",
|
|
484
|
-
},
|
|
485
|
-
{
|
|
486
|
-
"flex": 3,
|
|
487
|
-
"minWidth": "20px",
|
|
488
|
-
"display": (
|
|
489
|
-
"block" if self._well_pick_names[ensemble] else "none"
|
|
490
|
-
),
|
|
491
|
-
},
|
|
492
|
-
)
|
|
493
|
-
|
|
494
|
-
# Cannot avoid many arguments and/or locals since all layers of the DeckGL map
|
|
495
|
-
# need to be updated simultaneously
|
|
496
|
-
# pylint: disable=too-many-arguments,too-many-locals
|
|
497
|
-
@callback(
|
|
498
|
-
Output(self._view_component(MapViewElement.Ids.DECKGL_MAP), "layers"),
|
|
499
|
-
Output(self._view_component(MapViewElement.Ids.DECKGL_MAP), "children"),
|
|
500
|
-
Output(self._view_component(MapViewElement.Ids.DECKGL_MAP), "views"),
|
|
501
|
-
inputs={
|
|
502
|
-
"attribute": Input(
|
|
503
|
-
self._settings_component(ViewSettings.Ids.PROPERTY), "value"
|
|
504
|
-
),
|
|
505
|
-
"date": Input(
|
|
506
|
-
self._view_component(MapViewElement.Ids.DATE_SLIDER), "value"
|
|
507
|
-
),
|
|
508
|
-
"formation": Input(
|
|
509
|
-
self._settings_component(ViewSettings.Ids.FORMATION), "value"
|
|
510
|
-
),
|
|
511
|
-
"realization": Input(
|
|
512
|
-
self._settings_component(ViewSettings.Ids.REALIZATION), "value"
|
|
513
|
-
),
|
|
514
|
-
"statistic": Input(
|
|
515
|
-
self._settings_component(ViewSettings.Ids.STATISTIC), "value"
|
|
516
|
-
),
|
|
517
|
-
"color_map_name": Input(
|
|
518
|
-
self._settings_component(ViewSettings.Ids.COLOR_SCALE), "value"
|
|
519
|
-
),
|
|
520
|
-
"cm_min_auto": Input(
|
|
521
|
-
self._settings_component(ViewSettings.Ids.CM_MIN_AUTO), "value"
|
|
522
|
-
),
|
|
523
|
-
"cm_min_val": Input(
|
|
524
|
-
self._settings_component(ViewSettings.Ids.CM_MIN), "value"
|
|
525
|
-
),
|
|
526
|
-
"cm_max_auto": Input(
|
|
527
|
-
self._settings_component(ViewSettings.Ids.CM_MAX_AUTO), "value"
|
|
528
|
-
),
|
|
529
|
-
"cm_max_val": Input(
|
|
530
|
-
self._settings_component(ViewSettings.Ids.CM_MAX), "value"
|
|
531
|
-
),
|
|
532
|
-
"plume_threshold": Input(
|
|
533
|
-
self._settings_component(ViewSettings.Ids.PLUME_THRESHOLD),
|
|
534
|
-
"value",
|
|
535
|
-
),
|
|
536
|
-
"plume_smoothing": Input(
|
|
537
|
-
self._settings_component(ViewSettings.Ids.PLUME_SMOOTHING),
|
|
538
|
-
"value",
|
|
539
|
-
),
|
|
540
|
-
"visualization_update": Input(
|
|
541
|
-
self._settings_component(ViewSettings.Ids.VISUALIZATION_UPDATE),
|
|
542
|
-
"n_clicks",
|
|
543
|
-
),
|
|
544
|
-
"mass_unit": Input(
|
|
545
|
-
self._settings_component(ViewSettings.Ids.MASS_UNIT), "value"
|
|
546
|
-
),
|
|
547
|
-
"mass_unit_update": Input(
|
|
548
|
-
self._settings_component(ViewSettings.Ids.MASS_UNIT_UPDATE),
|
|
549
|
-
"n_clicks",
|
|
550
|
-
),
|
|
551
|
-
"options_dialog_options": Input(
|
|
552
|
-
ViewSettings.Ids.OPTIONS_DIALOG_OPTIONS, "value"
|
|
553
|
-
),
|
|
554
|
-
"selected_wells": Input(
|
|
555
|
-
ViewSettings.Ids.OPTIONS_DIALOG_WELL_FILTER, "value"
|
|
556
|
-
),
|
|
557
|
-
"ensemble": Input(
|
|
558
|
-
self._settings_component(ViewSettings.Ids.ENSEMBLE), "value"
|
|
537
|
+
# Surface
|
|
538
|
+
surf_data, summed_mass = None, None
|
|
539
|
+
if formation is not None and len(realization) > 0:
|
|
540
|
+
surf_data, summed_mass = SurfaceData.from_server(
|
|
541
|
+
server=self._surface_server,
|
|
542
|
+
provider=self._ensemble_surface_providers[ensemble],
|
|
543
|
+
address=derive_surface_address(
|
|
544
|
+
formation,
|
|
545
|
+
attribute,
|
|
546
|
+
datestr,
|
|
547
|
+
realization,
|
|
548
|
+
self._map_attribute_names,
|
|
549
|
+
statistic,
|
|
550
|
+
contour_data,
|
|
559
551
|
),
|
|
560
|
-
|
|
561
|
-
|
|
552
|
+
color_map_range=(
|
|
553
|
+
cm_min_val if len(cm_min_auto) == 0 else None,
|
|
554
|
+
cm_max_val if len(cm_max_auto) == 0 else None,
|
|
562
555
|
),
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
attribute: MapAttribute,
|
|
568
|
-
date: int,
|
|
569
|
-
formation: str,
|
|
570
|
-
realization: List[int],
|
|
571
|
-
statistic: str,
|
|
572
|
-
color_map_name: str,
|
|
573
|
-
cm_min_auto: List[str],
|
|
574
|
-
cm_min_val: Optional[float],
|
|
575
|
-
cm_max_auto: List[str],
|
|
576
|
-
cm_max_val: Optional[float],
|
|
577
|
-
plume_threshold: Optional[float],
|
|
578
|
-
plume_smoothing: Optional[float],
|
|
579
|
-
visualization_update: int,
|
|
580
|
-
mass_unit: str,
|
|
581
|
-
mass_unit_update: int,
|
|
582
|
-
options_dialog_options: List[int],
|
|
583
|
-
selected_wells: List[str],
|
|
584
|
-
ensemble: str,
|
|
585
|
-
current_views: List[Any],
|
|
586
|
-
thresholds: List[float],
|
|
587
|
-
) -> Tuple[List[Dict[Any, Any]], Optional[List[Any]], Dict[Any, Any]]:
|
|
588
|
-
# Unable to clear cache (when needed) without the protected member
|
|
589
|
-
# pylint: disable=protected-access
|
|
590
|
-
current_thresholds = dict(zip(self._threshold_ids, thresholds))
|
|
591
|
-
assert visualization_update >= 0 # Need the input to trigger callback
|
|
592
|
-
assert mass_unit_update >= 0 # These are just to silence pylint
|
|
593
|
-
self._visualization_info = process_visualization_info(
|
|
594
|
-
attribute,
|
|
595
|
-
current_thresholds,
|
|
596
|
-
mass_unit,
|
|
597
|
-
self._visualization_info,
|
|
598
|
-
self._surface_server._image_cache,
|
|
556
|
+
color_map_name=color_map_name,
|
|
557
|
+
readable_name_=readable_name(attribute),
|
|
558
|
+
visualization_info=self._visualization_info,
|
|
559
|
+
map_attribute_names=self._map_attribute_names,
|
|
599
560
|
)
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
),
|
|
616
|
-
"threshold": plume_threshold,
|
|
617
|
-
"smoothing": plume_smoothing,
|
|
618
|
-
}
|
|
619
|
-
# Surface
|
|
620
|
-
surf_data, summed_mass = None, None
|
|
621
|
-
if formation is not None and len(realization) > 0:
|
|
622
|
-
surf_data, summed_mass = SurfaceData.from_server(
|
|
623
|
-
server=self._surface_server,
|
|
624
|
-
provider=self._ensemble_surface_providers[ensemble],
|
|
625
|
-
address=derive_surface_address(
|
|
626
|
-
formation,
|
|
627
|
-
attribute,
|
|
628
|
-
datestr,
|
|
629
|
-
realization,
|
|
630
|
-
self._map_attribute_names,
|
|
631
|
-
statistic,
|
|
632
|
-
contour_data,
|
|
633
|
-
),
|
|
634
|
-
color_map_range=(
|
|
635
|
-
cm_min_val if len(cm_min_auto) == 0 else None,
|
|
636
|
-
cm_max_val if len(cm_max_auto) == 0 else None,
|
|
637
|
-
),
|
|
638
|
-
color_map_name=color_map_name,
|
|
639
|
-
readable_name_=readable_name(attribute),
|
|
640
|
-
visualization_info=self._visualization_info,
|
|
641
|
-
map_attribute_names=self._map_attribute_names,
|
|
642
|
-
)
|
|
643
|
-
assert isinstance(self._visualization_info["unit"], str)
|
|
644
|
-
surf_data, self._summed_co2 = process_summed_mass(
|
|
645
|
-
formation,
|
|
561
|
+
assert isinstance(self._visualization_info["unit"], str)
|
|
562
|
+
surf_data, self._summed_co2 = process_summed_mass(
|
|
563
|
+
formation,
|
|
564
|
+
realization,
|
|
565
|
+
datestr,
|
|
566
|
+
attribute,
|
|
567
|
+
summed_mass,
|
|
568
|
+
surf_data,
|
|
569
|
+
self._summed_co2,
|
|
570
|
+
self._visualization_info["unit"],
|
|
571
|
+
)
|
|
572
|
+
plume_polygon = None
|
|
573
|
+
if contour_data is not None:
|
|
574
|
+
plume_polygon = get_plume_polygon(
|
|
575
|
+
self._ensemble_surface_providers[ensemble],
|
|
646
576
|
realization,
|
|
577
|
+
formation,
|
|
647
578
|
datestr,
|
|
648
|
-
|
|
649
|
-
summed_mass,
|
|
650
|
-
surf_data,
|
|
651
|
-
self._summed_co2,
|
|
652
|
-
self._visualization_info["unit"],
|
|
653
|
-
)
|
|
654
|
-
plume_polygon = None
|
|
655
|
-
if contour_data is not None:
|
|
656
|
-
plume_polygon = get_plume_polygon(
|
|
657
|
-
self._ensemble_surface_providers[ensemble],
|
|
658
|
-
realization,
|
|
659
|
-
formation,
|
|
660
|
-
datestr,
|
|
661
|
-
contour_data,
|
|
662
|
-
)
|
|
663
|
-
# Create layers and view bounds
|
|
664
|
-
fault_polygon_url = self._fault_polygon_handlers[
|
|
665
|
-
ensemble
|
|
666
|
-
].extract_fault_polygon_url(formation, realization)
|
|
667
|
-
hazardous_polygon_url = self._polygon_handlers[
|
|
668
|
-
ensemble
|
|
669
|
-
].extract_hazardous_poly_url(realization)
|
|
670
|
-
containment_polygon_url = self._polygon_handlers[
|
|
671
|
-
ensemble
|
|
672
|
-
].extract_containment_poly_url(realization)
|
|
673
|
-
layers = create_map_layers(
|
|
674
|
-
realizations=realization,
|
|
675
|
-
formation=formation,
|
|
676
|
-
surface_data=surf_data,
|
|
677
|
-
fault_polygon_url=fault_polygon_url,
|
|
678
|
-
containment_bounds_url=containment_polygon_url,
|
|
679
|
-
haz_bounds_url=hazardous_polygon_url,
|
|
680
|
-
well_pick_provider=self._well_pick_provider.get(ensemble, None),
|
|
681
|
-
plume_extent_data=plume_polygon,
|
|
682
|
-
options_dialog_options=options_dialog_options,
|
|
683
|
-
selected_wells=selected_wells,
|
|
579
|
+
contour_data,
|
|
684
580
|
)
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
581
|
+
# Create layers and view bounds
|
|
582
|
+
fault_polygon_url = self._fault_polygon_handlers[
|
|
583
|
+
ensemble
|
|
584
|
+
].extract_fault_polygon_url(formation, realization)
|
|
585
|
+
hazardous_polygon_url = self._polygon_handlers[
|
|
586
|
+
ensemble
|
|
587
|
+
].extract_hazardous_poly_url(realization)
|
|
588
|
+
containment_polygon_url = self._polygon_handlers[
|
|
589
|
+
ensemble
|
|
590
|
+
].extract_containment_poly_url(realization)
|
|
591
|
+
layers = create_map_layers(
|
|
592
|
+
realizations=realization,
|
|
593
|
+
formation=formation,
|
|
594
|
+
surface_data=surf_data,
|
|
595
|
+
fault_polygon_url=fault_polygon_url,
|
|
596
|
+
containment_bounds_url=containment_polygon_url,
|
|
597
|
+
haz_bounds_url=hazardous_polygon_url,
|
|
598
|
+
well_pick_provider=self._well_pick_provider.get(ensemble, None),
|
|
599
|
+
plume_extent_data=plume_polygon,
|
|
600
|
+
options_dialog_options=options_dialog_options,
|
|
601
|
+
selected_wells=selected_wells,
|
|
698
602
|
)
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
603
|
+
annotations = create_map_annotations(
|
|
604
|
+
formation=formation,
|
|
605
|
+
surface_data=surf_data,
|
|
606
|
+
colortables=self._color_tables,
|
|
607
|
+
attribute=attribute,
|
|
608
|
+
unit=self._visualization_info["unit"],
|
|
609
|
+
)
|
|
610
|
+
viewports = no_update if current_views else create_map_viewports()
|
|
611
|
+
return layers, annotations, viewports
|
|
703
612
|
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
613
|
+
def _add_set_well_options_callback(self) -> None:
|
|
614
|
+
@callback(
|
|
615
|
+
Output(ViewSettings.Ids.OPTIONS_DIALOG_WELL_FILTER, "options"),
|
|
616
|
+
Output(ViewSettings.Ids.OPTIONS_DIALOG_WELL_FILTER, "value"),
|
|
617
|
+
Output(ViewSettings.Ids.OPTIONS_DIALOG_WELL_FILTER, "style"),
|
|
618
|
+
Output(ViewSettings.Ids.WELL_FILTER_HEADER, "style"),
|
|
619
|
+
Input(self._settings_component(ViewSettings.Ids.ENSEMBLE), "value"),
|
|
620
|
+
)
|
|
621
|
+
def set_well_options(
|
|
622
|
+
ensemble: str,
|
|
623
|
+
) -> Tuple[List[Any], List[str], Dict[Any, Any], Dict[Any, Any]]:
|
|
624
|
+
return (
|
|
625
|
+
[{"label": i, "value": i} for i in self._well_pick_names[ensemble]],
|
|
626
|
+
self._well_pick_names[ensemble],
|
|
627
|
+
{
|
|
628
|
+
"display": ("block" if self._well_pick_names[ensemble] else "none"),
|
|
629
|
+
"height": f"{len(self._well_pick_names[ensemble]) * 22}px",
|
|
630
|
+
},
|
|
631
|
+
{
|
|
632
|
+
"flex": 3,
|
|
633
|
+
"minWidth": "20px",
|
|
634
|
+
"display": ("block" if self._well_pick_names[ensemble] else "none"),
|
|
635
|
+
},
|
|
707
636
|
)
|
|
708
|
-
def open_close_thresholds(_n_clicks: Optional[int]) -> bool:
|
|
709
|
-
if _n_clicks is not None:
|
|
710
|
-
return _n_clicks > 0
|
|
711
|
-
raise PreventUpdate
|
|
712
637
|
|
|
638
|
+
def _add_date_slider_visibility_callback(self) -> None:
|
|
713
639
|
@callback(
|
|
714
|
-
Output(
|
|
715
|
-
Input(ViewSettings.Ids.
|
|
640
|
+
Output(self._view_component(MapViewElement.Ids.DATE_WRAPPER), "style"),
|
|
641
|
+
Input(self._settings_component(ViewSettings.Ids.PROPERTY), "value"),
|
|
716
642
|
)
|
|
717
|
-
def
|
|
718
|
-
if
|
|
719
|
-
return
|
|
720
|
-
|
|
643
|
+
def toggle_date_slider(attribute: str) -> Dict[str, str]:
|
|
644
|
+
if MapType[MapAttribute(attribute).name].value == "MIGRATION_TIME":
|
|
645
|
+
return {"display": "none"}
|
|
646
|
+
return {}
|
|
721
647
|
|
|
722
|
-
|
|
648
|
+
def _add_set_dates_callback(self) -> None:
|
|
649
|
+
@callback(
|
|
650
|
+
Output(self._view_component(MapViewElement.Ids.DATE_SLIDER), "marks"),
|
|
651
|
+
Output(self._view_component(MapViewElement.Ids.DATE_SLIDER), "value"),
|
|
652
|
+
Input(self._settings_component(ViewSettings.Ids.ENSEMBLE), "value"),
|
|
653
|
+
)
|
|
654
|
+
def set_dates(
|
|
655
|
+
ensemble: str,
|
|
656
|
+
) -> Tuple[Dict[int, Dict[str, Any]], Optional[int]]:
|
|
657
|
+
if ensemble is None:
|
|
658
|
+
return {}, None
|
|
659
|
+
# Dates
|
|
660
|
+
date_list = self._ensemble_dates(ensemble)
|
|
661
|
+
dates = {
|
|
662
|
+
i: {
|
|
663
|
+
"label": f"{d[:4]}",
|
|
664
|
+
"style": {"writingMode": "vertical-rl"},
|
|
665
|
+
}
|
|
666
|
+
for i, d in enumerate(date_list)
|
|
667
|
+
}
|
|
668
|
+
if len(dates.keys()) > 0:
|
|
669
|
+
return dates, max(dates.keys())
|
|
670
|
+
return dates, None
|
|
723
671
|
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
672
|
+
def _add_time_plot_visibility_callback(self) -> None:
|
|
673
|
+
@callback(
|
|
674
|
+
Output(self._settings_component(ViewSettings.Ids.REAL_OR_STAT), "style"),
|
|
675
|
+
Output(self._settings_component(ViewSettings.Ids.Y_LIM_OPTIONS), "style"),
|
|
676
|
+
Input(self._settings_component(ViewSettings.Ids.REALIZATION), "value"),
|
|
677
|
+
)
|
|
678
|
+
def toggle_time_plot_options_visibility(
|
|
679
|
+
realizations: List[int],
|
|
680
|
+
) -> Tuple[Dict[str, str], Dict[str, str]]:
|
|
681
|
+
if len(realizations) == 1:
|
|
682
|
+
return (
|
|
683
|
+
{"display": "none"},
|
|
684
|
+
{"display": "flex", "flex-direction": "column"},
|
|
685
|
+
)
|
|
686
|
+
return (
|
|
687
|
+
{"display": "flex", "flex-direction": "row"},
|
|
688
|
+
{"display": "none"},
|
|
739
689
|
)
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
690
|
+
|
|
691
|
+
def _add_set_unit_list_callback(self) -> None:
|
|
692
|
+
@callback(
|
|
693
|
+
Output(self._settings_component(ViewSettings.Ids.CO2_SCALE), "options"),
|
|
694
|
+
Output(self._settings_component(ViewSettings.Ids.CO2_SCALE), "value"),
|
|
695
|
+
Input(self._settings_component(ViewSettings.Ids.GRAPH_SOURCE), "value"),
|
|
696
|
+
)
|
|
697
|
+
def make_unit_list(
|
|
698
|
+
attribute: str,
|
|
699
|
+
) -> Union[Tuple[List[Any], Co2MassScale], Tuple[List[Any], Co2VolumeScale],]:
|
|
700
|
+
if attribute == GraphSource.CONTAINMENT_ACTUAL_VOLUME:
|
|
701
|
+
return list(Co2VolumeScale), Co2VolumeScale.BILLION_CUBIC_METERS
|
|
702
|
+
return list(Co2MassScale), Co2MassScale.MTONS
|
|
703
|
+
|
|
704
|
+
def _add_graph_callback(self) -> None:
|
|
705
|
+
# Cannot avoid many arguments since all the parameters are needed
|
|
706
|
+
# to determine what to plot
|
|
707
|
+
# pylint: disable=too-many-arguments
|
|
708
|
+
@callback(
|
|
709
|
+
Output(self._view_component(MapViewElement.Ids.BAR_PLOT), "figure"),
|
|
710
|
+
Output(self._view_component(MapViewElement.Ids.TIME_PLOT), "figure"),
|
|
711
|
+
Output(
|
|
712
|
+
self._view_component(MapViewElement.Ids.STATISTICS_PLOT),
|
|
713
|
+
"figure",
|
|
714
|
+
),
|
|
715
|
+
# LEGEND_DATA_STORE is updated whenever the legend is clicked. However,
|
|
716
|
+
# there is not need to update the plots based on this change, since that
|
|
717
|
+
# is done by plotly internally. We therefore use State instead of Input
|
|
718
|
+
State(self._view_component(MapViewElement.Ids.LEGEND_DATA_STORE), "data"),
|
|
719
|
+
Input(self._settings_component(ViewSettings.Ids.ENSEMBLE), "value"),
|
|
720
|
+
Input(self._settings_component(ViewSettings.Ids.GRAPH_SOURCE), "value"),
|
|
721
|
+
Input(self._settings_component(ViewSettings.Ids.CO2_SCALE), "value"),
|
|
722
|
+
Input(self._settings_component(ViewSettings.Ids.REALIZATION), "value"),
|
|
723
|
+
Input(self._settings_component(ViewSettings.Ids.Y_MIN_AUTO_GRAPH), "value"),
|
|
724
|
+
Input(self._settings_component(ViewSettings.Ids.Y_MIN_GRAPH), "value"),
|
|
725
|
+
Input(self._settings_component(ViewSettings.Ids.Y_MAX_AUTO_GRAPH), "value"),
|
|
726
|
+
Input(self._settings_component(ViewSettings.Ids.Y_MAX_GRAPH), "value"),
|
|
727
|
+
Input(self._settings_component(ViewSettings.Ids.ZONE), "value"),
|
|
728
|
+
Input(self._settings_component(ViewSettings.Ids.REGION), "value"),
|
|
729
|
+
Input(self._settings_component(ViewSettings.Ids.PHASE), "value"),
|
|
730
|
+
Input(self._settings_component(ViewSettings.Ids.CONTAINMENT), "value"),
|
|
731
|
+
Input(self._settings_component(ViewSettings.Ids.PLUME_GROUP), "value"),
|
|
732
|
+
Input(self._settings_component(ViewSettings.Ids.COLOR_BY), "value"),
|
|
733
|
+
Input(self._settings_component(ViewSettings.Ids.MARK_BY), "value"),
|
|
734
|
+
Input(self._settings_component(ViewSettings.Ids.SORT_PLOT), "value"),
|
|
735
|
+
Input(self._settings_component(ViewSettings.Ids.REAL_OR_STAT), "value"),
|
|
736
|
+
Input(self._settings_component(ViewSettings.Ids.DATE_OPTION), "value"),
|
|
737
|
+
Input(
|
|
738
|
+
self._settings_component(ViewSettings.Ids.STATISTICS_TAB_OPTION),
|
|
739
|
+
"value",
|
|
740
|
+
),
|
|
741
|
+
Input(self._settings_component(ViewSettings.Ids.BOX_SHOW_POINTS), "value"),
|
|
742
|
+
)
|
|
743
|
+
@callback_typecheck
|
|
744
|
+
def update_graphs(
|
|
745
|
+
legend_data: LegendData,
|
|
746
|
+
ensemble: str,
|
|
747
|
+
source: GraphSource,
|
|
748
|
+
co2_scale: Union[Co2MassScale, Co2VolumeScale],
|
|
749
|
+
realizations: List[int],
|
|
750
|
+
y_min_auto: List[str],
|
|
751
|
+
y_min_val: Optional[float],
|
|
752
|
+
y_max_auto: List[str],
|
|
753
|
+
y_max_val: Optional[float],
|
|
754
|
+
zone: Optional[str],
|
|
755
|
+
region: Optional[str],
|
|
756
|
+
phase: str,
|
|
757
|
+
containment: str,
|
|
758
|
+
plume_group: str,
|
|
759
|
+
color_choice: str,
|
|
760
|
+
mark_choice: Optional[str],
|
|
761
|
+
sorting: str,
|
|
762
|
+
lines_to_show: str,
|
|
763
|
+
date_option: str,
|
|
764
|
+
statistics_tab_option: StatisticsTabOption,
|
|
765
|
+
box_show_points: str,
|
|
766
|
+
) -> Tuple[Dict, go.Figure, go.Figure, go.Figure]:
|
|
767
|
+
# pylint: disable=too-many-locals
|
|
768
|
+
figs = [no_update] * 3
|
|
769
|
+
cont_info = process_containment_info(
|
|
770
|
+
zone,
|
|
771
|
+
region,
|
|
772
|
+
phase,
|
|
773
|
+
containment,
|
|
774
|
+
plume_group,
|
|
775
|
+
color_choice,
|
|
776
|
+
mark_choice,
|
|
777
|
+
sorting,
|
|
778
|
+
lines_to_show,
|
|
779
|
+
date_option,
|
|
780
|
+
statistics_tab_option,
|
|
781
|
+
box_show_points,
|
|
782
|
+
self._menu_options[ensemble][source],
|
|
783
|
+
)
|
|
784
|
+
if source in [
|
|
785
|
+
GraphSource.CONTAINMENT_MASS,
|
|
786
|
+
GraphSource.CONTAINMENT_ACTUAL_VOLUME,
|
|
787
|
+
]:
|
|
788
|
+
plot_ids = make_plot_ids(
|
|
789
|
+
ensemble,
|
|
790
|
+
source,
|
|
791
|
+
co2_scale,
|
|
792
|
+
cont_info,
|
|
793
|
+
realizations,
|
|
794
|
+
len(figs),
|
|
795
|
+
)
|
|
796
|
+
y_limits = [
|
|
797
|
+
y_min_val if len(y_min_auto) == 0 else None,
|
|
798
|
+
y_max_val if len(y_max_auto) == 0 else None,
|
|
799
|
+
]
|
|
800
|
+
if (
|
|
754
801
|
source == GraphSource.CONTAINMENT_MASS
|
|
755
|
-
and ensemble
|
|
802
|
+
and ensemble in self._co2_table_providers
|
|
756
803
|
):
|
|
757
|
-
|
|
804
|
+
figs[: len(figs)] = generate_containment_figures(
|
|
805
|
+
self._co2_table_providers[ensemble],
|
|
806
|
+
co2_scale,
|
|
807
|
+
realizations,
|
|
808
|
+
y_limits,
|
|
809
|
+
cont_info,
|
|
810
|
+
legend_data,
|
|
811
|
+
)
|
|
758
812
|
elif (
|
|
759
813
|
source == GraphSource.CONTAINMENT_ACTUAL_VOLUME
|
|
760
|
-
and ensemble
|
|
814
|
+
and ensemble in self._co2_actual_volume_table_providers
|
|
761
815
|
):
|
|
762
|
-
|
|
816
|
+
figs[: len(figs)] = generate_containment_figures(
|
|
817
|
+
self._co2_actual_volume_table_providers[ensemble],
|
|
818
|
+
co2_scale,
|
|
819
|
+
realizations,
|
|
820
|
+
y_limits,
|
|
821
|
+
cont_info,
|
|
822
|
+
legend_data,
|
|
823
|
+
)
|
|
824
|
+
set_plot_ids(figs, plot_ids)
|
|
825
|
+
elif source == GraphSource.UNSMRY:
|
|
826
|
+
if self._unsmry_providers is not None:
|
|
827
|
+
if ensemble in self._unsmry_providers:
|
|
828
|
+
figs[0] = go.Figure()
|
|
829
|
+
figs[1] = generate_unsmry_figures(
|
|
830
|
+
self._unsmry_providers[ensemble],
|
|
831
|
+
co2_scale,
|
|
832
|
+
self._co2_table_providers[ensemble],
|
|
833
|
+
)
|
|
834
|
+
figs[2] = go.Figure()
|
|
835
|
+
else:
|
|
836
|
+
LOGGER.warning(
|
|
837
|
+
"""UNSMRY file has not been specified as input.
|
|
838
|
+
Please use unsmry_relpath in the configuration."""
|
|
839
|
+
)
|
|
840
|
+
return figs # type: ignore
|
|
841
|
+
|
|
842
|
+
def _add_legend_change_callback(self) -> None:
|
|
843
|
+
@callback(
|
|
844
|
+
Output(self._view_component(MapViewElement.Ids.LEGEND_DATA_STORE), "data"),
|
|
845
|
+
Input(self._view_component(MapViewElement.Ids.BAR_PLOT), "restyleData"),
|
|
846
|
+
State(self._view_component(MapViewElement.Ids.BAR_PLOT), "figure"),
|
|
847
|
+
Input(self._view_component(MapViewElement.Ids.TIME_PLOT), "restyleData"),
|
|
848
|
+
State(self._view_component(MapViewElement.Ids.TIME_PLOT), "figure"),
|
|
849
|
+
Input(
|
|
850
|
+
self._view_component(MapViewElement.Ids.STATISTICS_PLOT), "restyleData"
|
|
851
|
+
),
|
|
852
|
+
State(self._view_component(MapViewElement.Ids.STATISTICS_PLOT), "figure"),
|
|
853
|
+
Input(
|
|
854
|
+
self._settings_component(ViewSettings.Ids.STATISTICS_TAB_OPTION),
|
|
855
|
+
"value",
|
|
856
|
+
),
|
|
857
|
+
)
|
|
858
|
+
def on_bar_legend_update(
|
|
859
|
+
bar_event: List[Any],
|
|
860
|
+
bar_figure: go.Figure,
|
|
861
|
+
time_event: List[Any],
|
|
862
|
+
time_figure: go.Figure,
|
|
863
|
+
stats_event: List[Any],
|
|
864
|
+
stats_figure: go.Figure,
|
|
865
|
+
_: StatisticsTabOption,
|
|
866
|
+
) -> Patch:
|
|
867
|
+
# We cannot subscribe to a legend click event directly, but we can subscribe
|
|
868
|
+
# to the more general "restyleData" event, and then try to identify if this
|
|
869
|
+
# was a click event or not. If yes, we update the appropriate store component
|
|
870
|
+
p = Patch()
|
|
871
|
+
_id = ctx.triggered_id
|
|
872
|
+
if _id is None:
|
|
873
|
+
return p
|
|
874
|
+
|
|
875
|
+
if _id == self._view_component(MapViewElement.Ids.BAR_PLOT):
|
|
876
|
+
if self._is_legend_click_event(bar_event):
|
|
877
|
+
p["bar_legendonly"] = extract_legendonly(bar_figure)
|
|
878
|
+
elif _id == self._view_component(MapViewElement.Ids.TIME_PLOT):
|
|
879
|
+
if self._is_legend_click_event(time_event):
|
|
880
|
+
p["time_legendonly"] = extract_legendonly(time_figure)
|
|
881
|
+
elif _id in (
|
|
882
|
+
self._view_component(MapViewElement.Ids.STATISTICS_PLOT),
|
|
883
|
+
self._settings_component(ViewSettings.Ids.STATISTICS_TAB_OPTION),
|
|
884
|
+
):
|
|
885
|
+
if self._is_legend_click_event(stats_event):
|
|
886
|
+
p["stats_legendonly"] = extract_legendonly(stats_figure)
|
|
887
|
+
return p
|
|
763
888
|
|
|
764
|
-
|
|
889
|
+
@staticmethod
|
|
890
|
+
def _is_legend_click_event(event: List[Any]) -> bool:
|
|
891
|
+
# A typical legend click event would be: [{'visible': ['legendonly']}, [1]]
|
|
892
|
+
if event is None or not isinstance(event, list):
|
|
893
|
+
return False
|
|
894
|
+
return any("visible" in e for e in event if isinstance(e, dict))
|