From e9d3036142173ce79481b13bb575837cba5742fa Mon Sep 17 00:00:00 2001 From: Hans Kallekleiv <16436291+HansKallekleiv@users.noreply.github.com> Date: Fri, 25 Nov 2022 14:25:26 +0100 Subject: [PATCH] Use new maplayer in MapViewerFMU --- webviz_subsurface/_providers/__init__.py | 6 +- .../ensemble_surface_provider/__init__.py | 10 +- .../_surface_to_float32_array.py | 20 ++ .../ensemble_surface_provider/_types.py | 17 + .../surface_array_server.py | 290 ++++++++++++++++++ ...face_server.py => surface_image_server.py} | 42 +-- .../_providers/well_provider/well_server.py | 2 +- .../plugins/_co2_leakage/_plugin.py | 4 +- .../_co2_leakage/_utilities/callbacks.py | 8 +- .../_utilities/surface_publishing.py | 12 +- .../plugins/_map_viewer_fmu/_types.py | 2 +- .../plugins/_map_viewer_fmu/callbacks.py | 90 +++--- .../plugins/_map_viewer_fmu/layout.py | 19 +- .../plugins/_map_viewer_fmu/map_viewer_fmu.py | 21 +- 14 files changed, 419 insertions(+), 124 deletions(-) create mode 100644 webviz_subsurface/_providers/ensemble_surface_provider/_surface_to_float32_array.py create mode 100644 webviz_subsurface/_providers/ensemble_surface_provider/_types.py create mode 100644 webviz_subsurface/_providers/ensemble_surface_provider/surface_array_server.py rename webviz_subsurface/_providers/ensemble_surface_provider/{surface_server.py => surface_image_server.py} (90%) diff --git a/webviz_subsurface/_providers/__init__.py b/webviz_subsurface/_providers/__init__.py index ad2dd4513..6095babb7 100644 --- a/webviz_subsurface/_providers/__init__.py +++ b/webviz_subsurface/_providers/__init__.py @@ -23,8 +23,10 @@ SimulatedSurfaceAddress, StatisticalSurfaceAddress, SurfaceAddress, - SurfaceMeta, - SurfaceServer, + SurfaceImageMeta, + SurfaceImageServer, + SurfaceArrayServer, + SurfaceArrayMeta, ) from .ensemble_table_provider import ( ColumnMetadata, diff --git a/webviz_subsurface/_providers/ensemble_surface_provider/__init__.py b/webviz_subsurface/_providers/ensemble_surface_provider/__init__.py index 7f54193ef..f5a190b92 100644 --- a/webviz_subsurface/_providers/ensemble_surface_provider/__init__.py +++ b/webviz_subsurface/_providers/ensemble_surface_provider/__init__.py @@ -6,9 +6,9 @@ SurfaceAddress, ) from .ensemble_surface_provider_factory import EnsembleSurfaceProviderFactory -from .surface_server import ( - QualifiedDiffSurfaceAddress, - QualifiedSurfaceAddress, - SurfaceMeta, - SurfaceServer, +from .surface_image_server import ( + SurfaceImageMeta, + SurfaceImageServer, ) +from .surface_array_server import SurfaceArrayMeta, SurfaceArrayServer +from ._types import QualifiedSurfaceAddress, QualifiedDiffSurfaceAddress diff --git a/webviz_subsurface/_providers/ensemble_surface_provider/_surface_to_float32_array.py b/webviz_subsurface/_providers/ensemble_surface_provider/_surface_to_float32_array.py new file mode 100644 index 000000000..eb90b8b49 --- /dev/null +++ b/webviz_subsurface/_providers/ensemble_surface_provider/_surface_to_float32_array.py @@ -0,0 +1,20 @@ +import io + +import numpy as np +import xtgeo + + +def surface_to_float32_array(surface: xtgeo.RegularSurface) -> io.BytesIO: + values = surface.values.astype(np.float32) + values.fill_value = np.NaN + values = np.ma.filled(values) + + # Rotate 90 deg left. + # This will cause the width of to run along the X axis + # and height of along Y axis (starting from bottom.) + values = np.rot90(values) + + byte_io = io.BytesIO() + byte_io.write(values.tobytes()) + byte_io.seek(0) + return byte_io diff --git a/webviz_subsurface/_providers/ensemble_surface_provider/_types.py b/webviz_subsurface/_providers/ensemble_surface_provider/_types.py new file mode 100644 index 000000000..2ae8e8ae6 --- /dev/null +++ b/webviz_subsurface/_providers/ensemble_surface_provider/_types.py @@ -0,0 +1,17 @@ +from dataclasses import dataclass + +from .ensemble_surface_provider import SurfaceAddress + + +@dataclass(frozen=True) +class QualifiedSurfaceAddress: + provider_id: str + address: SurfaceAddress + + +@dataclass(frozen=True) +class QualifiedDiffSurfaceAddress: + provider_id_a: str + address_a: SurfaceAddress + provider_id_b: str + address_b: SurfaceAddress diff --git a/webviz_subsurface/_providers/ensemble_surface_provider/surface_array_server.py b/webviz_subsurface/_providers/ensemble_surface_provider/surface_array_server.py new file mode 100644 index 000000000..44da5538c --- /dev/null +++ b/webviz_subsurface/_providers/ensemble_surface_provider/surface_array_server.py @@ -0,0 +1,290 @@ +import io +import hashlib +import json +import logging +import math +from dataclasses import asdict, dataclass +from typing import List, Optional, Tuple, Union +from urllib.parse import quote +from uuid import uuid4 + +import flask +import flask_caching +import xtgeo +from dash import Dash +from webviz_config.webviz_instance_info import WEBVIZ_INSTANCE_INFO + +from webviz_subsurface._utils.perf_timer import PerfTimer + +from ._surface_to_float32_array import surface_to_float32_array +from .ensemble_surface_provider import ( + ObservedSurfaceAddress, + SimulatedSurfaceAddress, + StatisticalSurfaceAddress, + SurfaceAddress, +) +from ._types import QualifiedSurfaceAddress, QualifiedDiffSurfaceAddress + +LOGGER = logging.getLogger(__name__) + +_ROOT_URL_PATH = "/SurfaceArrayServer" + +_SURFACE_SERVER_INSTANCE: Optional["SurfaceArrayServer"] = None + + +@dataclass(frozen=True) +class SurfaceArrayMeta: + x_min: float + x_max: float + y_min: float + y_max: float + x_ori: float + y_ori: float + val_min: float + val_max: float + rot_deg: float + x_count: int + y_count: int + x_inc: float + y_inc: float + + +class SurfaceArrayServer: + def __init__(self, app: Dash) -> None: + cache_dir = ( + WEBVIZ_INSTANCE_INFO.storage_folder + / f"SurfaceArrayServer_filecache_{uuid4()}" + ) + LOGGER.debug(f"Setting up file cache in: {cache_dir}") + self._array_cache = flask_caching.Cache( + config={ + "CACHE_TYPE": "FileSystemCache", + "CACHE_DIR": cache_dir, + "CACHE_DEFAULT_TIMEOUT": 0, + } + ) + self._array_cache.init_app(app.server) + + self._setup_url_rule(app) + + @staticmethod + def instance(app: Dash) -> "SurfaceArrayServer": + # pylint: disable=global-statement + global _SURFACE_SERVER_INSTANCE + if not _SURFACE_SERVER_INSTANCE: + LOGGER.debug("Initializing SurfaceArrayServer instance") + _SURFACE_SERVER_INSTANCE = SurfaceArrayServer(app) + + return _SURFACE_SERVER_INSTANCE + + def publish_surface( + self, + qualified_address: Union[QualifiedSurfaceAddress, QualifiedDiffSurfaceAddress], + surface: xtgeo.RegularSurface, + ) -> None: + timer = PerfTimer() + + if isinstance(qualified_address, QualifiedSurfaceAddress): + base_cache_key = _address_to_str( + qualified_address.provider_id, qualified_address.address + ) + else: + base_cache_key = _diff_address_to_str( + qualified_address.provider_id_a, + qualified_address.address_a, + qualified_address.provider_id_b, + qualified_address.address_b, + ) + + LOGGER.debug( + f"Publishing surface (dim={surface.dimensions}, #cells={surface.ncol*surface.nrow}), " + f"[base_cache_key={base_cache_key}]" + ) + + self._create_and_store_array_in_cache(base_cache_key, surface) + + LOGGER.debug(f"Surface published in: {timer.elapsed_s():.2f}s") + + def get_surface_metadata( + self, + qualified_address: Union[QualifiedSurfaceAddress, QualifiedDiffSurfaceAddress], + ) -> Optional[SurfaceArrayMeta]: + + if isinstance(qualified_address, QualifiedSurfaceAddress): + base_cache_key = _address_to_str( + qualified_address.provider_id, qualified_address.address + ) + else: + base_cache_key = _diff_address_to_str( + qualified_address.provider_id_a, + qualified_address.address_a, + qualified_address.provider_id_b, + qualified_address.address_b, + ) + + meta_cache_key = "META:" + base_cache_key + meta: Optional[SurfaceArrayMeta] = self._array_cache.get(meta_cache_key) + if not meta: + return None + + if not isinstance(meta, SurfaceArrayMeta): + LOGGER.error("Error loading SurfaceArrayMeta from cache") + return None + + return meta + + @staticmethod + def encode_partial_url( + qualified_address: Union[QualifiedSurfaceAddress, QualifiedDiffSurfaceAddress], + ) -> str: + + if isinstance(qualified_address, QualifiedSurfaceAddress): + address_str = _address_to_str( + qualified_address.provider_id, qualified_address.address + ) + else: + address_str = _diff_address_to_str( + qualified_address.provider_id_a, + qualified_address.address_a, + qualified_address.provider_id_b, + qualified_address.address_b, + ) + + url_path: str = f"{_ROOT_URL_PATH}/{quote(address_str)}" + return url_path + + def _setup_url_rule(self, app: Dash) -> None: + @app.server.route(_ROOT_URL_PATH + "/") + def _handle_surface_request(full_surf_address_str: str) -> flask.Response: + LOGGER.debug( + f"Handling surface_request: " + f"full_surf_address_str={full_surf_address_str} " + ) + + timer = PerfTimer() + + array_cache_key = "ARRAY:" + full_surf_address_str + LOGGER.debug(f"Looking for array in cache (key={array_cache_key}") + + cached_array_bytes = self._array_cache.get(array_cache_key) + if not cached_array_bytes: + LOGGER.error( + f"Error getting array for address: {full_surf_address_str}" + ) + flask.abort(404) + + response = flask.send_file( + cached_array_bytes, mimetype="application/octet-stream" + ) + LOGGER.debug( + f"Request handled from array cache in: {timer.elapsed_s():.2f}s" + ) + return response + + def _create_and_store_array_in_cache( + self, + base_cache_key: str, + surface: xtgeo.RegularSurface, + ) -> None: + + timer = PerfTimer() + LOGGER.debug("Converting surface to float32 array...") + array_bytes: io.BytesIO = surface_to_float32_array(surface) + + et_to_array_s = timer.lap_s() + + array_cache_key = "ARRAY:" + base_cache_key + meta_cache_key = "META:" + base_cache_key + + self._array_cache.add(array_cache_key, array_bytes) + + meta = SurfaceArrayMeta( + x_min=surface.xmin, + x_max=surface.xmax, + y_min=surface.ymin, + y_max=surface.ymax, + x_ori=surface.xori, + y_ori=surface.yori, + x_count=surface.ncol, + y_count=surface.nrow, + val_min=surface.values.min(), + val_max=surface.values.max(), + rot_deg=surface.rotation, + x_inc=surface.xinc, + y_inc=surface.yinc, + ) + self._array_cache.add(meta_cache_key, meta) + et_write_cache_s = timer.lap_s() + + LOGGER.debug( + f"Created surface array and wrote to cache in in: {timer.elapsed_s():.2f}s (" + f"to_array={et_to_array_s:.2f}s, write_cache={et_write_cache_s:.2f}s), " + f"[base_cache_key={base_cache_key}]" + ) + + +def _address_to_str( + provider_id: str, + address: SurfaceAddress, +) -> str: + if isinstance(address, StatisticalSurfaceAddress): + addr_type_str = "sta" + elif isinstance(address, SimulatedSurfaceAddress): + addr_type_str = "sim" + elif isinstance(address, ObservedSurfaceAddress): + addr_type_str = "obs" + + addr_hash = hashlib.md5( # nosec + json.dumps(asdict(address), sort_keys=True).encode() + ).hexdigest() + + return f"{provider_id}___{addr_type_str}___{address.name}___{address.attribute}___{addr_hash}" + + +def _diff_address_to_str( + provider_id_a: str, + address_a: SurfaceAddress, + provider_id_b: str, + address_b: SurfaceAddress, +) -> str: + return ( + "diff~~~" + + _address_to_str(provider_id_a, address_a) + + "~~~" + + _address_to_str(provider_id_b, address_b) + ) + + +def _calc_map_component_bounds_and_rot( + surface: xtgeo.RegularSurface, +) -> Tuple[List[float], float]: + surf_corners = surface.get_map_xycorners() + rptx = surf_corners[2][0] + rpty = surf_corners[2][1] + min_x = math.inf + max_x = -math.inf + min_y = math.inf + max_y = -math.inf + angle = -surface.rotation * math.pi / 180 + for coord in surf_corners: + xpos = coord[0] + ypos = coord[1] + x_rotated = ( + rptx + ((xpos - rptx) * math.cos(angle)) - ((ypos - rpty) * math.sin(angle)) + ) + y_rotated = ( + rpty + ((xpos - rptx) * math.sin(angle)) + ((ypos - rpty) * math.cos(angle)) + ) + min_x = min(min_x, x_rotated) + max_x = max(max_x, x_rotated) + min_y = min(min_y, y_rotated) + max_y = max(max_y, y_rotated) + + bounds = [ + min_x, + min_y, + max_x, + max_y, + ] + + return bounds, surface.rotation diff --git a/webviz_subsurface/_providers/ensemble_surface_provider/surface_server.py b/webviz_subsurface/_providers/ensemble_surface_provider/surface_image_server.py similarity index 90% rename from webviz_subsurface/_providers/ensemble_surface_provider/surface_server.py rename to webviz_subsurface/_providers/ensemble_surface_provider/surface_image_server.py index 987cc558d..93611d55c 100644 --- a/webviz_subsurface/_providers/ensemble_surface_provider/surface_server.py +++ b/webviz_subsurface/_providers/ensemble_surface_provider/surface_image_server.py @@ -23,30 +23,17 @@ StatisticalSurfaceAddress, SurfaceAddress, ) +from ._types import QualifiedSurfaceAddress, QualifiedDiffSurfaceAddress LOGGER = logging.getLogger(__name__) -_ROOT_URL_PATH = "/SurfaceServer" +_ROOT_URL_PATH = "/SurfaceImageServer" -_SURFACE_SERVER_INSTANCE: Optional["SurfaceServer"] = None +_SURFACE_SERVER_INSTANCE: Optional["SurfaceImageServer"] = None @dataclass(frozen=True) -class QualifiedSurfaceAddress: - provider_id: str - address: SurfaceAddress - - -@dataclass(frozen=True) -class QualifiedDiffSurfaceAddress: - provider_id_a: str - address_a: SurfaceAddress - provider_id_b: str - address_b: SurfaceAddress - - -@dataclass(frozen=True) -class SurfaceMeta: +class SurfaceImageMeta: x_min: float x_max: float y_min: float @@ -57,10 +44,11 @@ class SurfaceMeta: deckgl_rot_deg: float # Around upper left corner -class SurfaceServer: +class SurfaceImageServer: def __init__(self, app: Dash) -> None: cache_dir = ( - WEBVIZ_INSTANCE_INFO.storage_folder / f"SurfaceServer_filecache_{uuid4()}" + WEBVIZ_INSTANCE_INFO.storage_folder + / f"SurfaceImageServer_filecache_{uuid4()}" ) LOGGER.debug(f"Setting up file cache in: {cache_dir}") self._image_cache = flask_caching.Cache( @@ -76,12 +64,12 @@ def __init__(self, app: Dash) -> None: self._setup_url_rule(app) @staticmethod - def instance(app: Dash) -> "SurfaceServer": + def instance(app: Dash) -> "SurfaceImageServer": # pylint: disable=global-statement global _SURFACE_SERVER_INSTANCE if not _SURFACE_SERVER_INSTANCE: - LOGGER.debug("Initializing SurfaceServer instance") - _SURFACE_SERVER_INSTANCE = SurfaceServer(app) + LOGGER.debug("Initializing SurfaceImageServer instance") + _SURFACE_SERVER_INSTANCE = SurfaceImageServer(app) return _SURFACE_SERVER_INSTANCE @@ -116,7 +104,7 @@ def publish_surface( def get_surface_metadata( self, qualified_address: Union[QualifiedSurfaceAddress, QualifiedDiffSurfaceAddress], - ) -> Optional[SurfaceMeta]: + ) -> Optional[SurfaceImageMeta]: if isinstance(qualified_address, QualifiedSurfaceAddress): base_cache_key = _address_to_str( @@ -131,12 +119,12 @@ def get_surface_metadata( ) meta_cache_key = "META:" + base_cache_key - meta: Optional[SurfaceMeta] = self._image_cache.get(meta_cache_key) + meta: Optional[SurfaceImageMeta] = self._image_cache.get(meta_cache_key) if not meta: return None - if not isinstance(meta, SurfaceMeta): - LOGGER.error("Error loading SurfaceMeta from cache") + if not isinstance(meta, SurfaceImageMeta): + LOGGER.error("Error loading SurfaceImageMeta from cache") return None return meta @@ -213,7 +201,7 @@ def _create_and_store_image_in_cache( deckgl_bounds, deckgl_rot = _calc_map_component_bounds_and_rot(surface) - meta = SurfaceMeta( + meta = SurfaceImageMeta( x_min=surface.xmin, x_max=surface.xmax, y_min=surface.ymin, diff --git a/webviz_subsurface/_providers/well_provider/well_server.py b/webviz_subsurface/_providers/well_provider/well_server.py index 7063855cb..2f00c4273 100644 --- a/webviz_subsurface/_providers/well_provider/well_server.py +++ b/webviz_subsurface/_providers/well_provider/well_server.py @@ -26,7 +26,7 @@ def instance(app: Dash) -> "WellServer": # pylint: disable=global-statement global _WELL_SERVER_INSTANCE if not _WELL_SERVER_INSTANCE: - LOGGER.debug("Initializing SurfaceServer instance") + LOGGER.debug("Initializing WellServer instance") _WELL_SERVER_INSTANCE = WellServer(app) return _WELL_SERVER_INSTANCE diff --git a/webviz_subsurface/plugins/_co2_leakage/_plugin.py b/webviz_subsurface/plugins/_co2_leakage/_plugin.py index 3c54c1793..a5b3e153e 100644 --- a/webviz_subsurface/plugins/_co2_leakage/_plugin.py +++ b/webviz_subsurface/plugins/_co2_leakage/_plugin.py @@ -6,7 +6,7 @@ from webviz_config import WebvizPluginABC, WebvizSettings from webviz_config.utils import StrEnum -from webviz_subsurface._providers import FaultPolygonsServer, SurfaceServer +from webviz_subsurface._providers import FaultPolygonsServer, SurfaceImageServer from webviz_subsurface.plugins._co2_leakage._utilities.callbacks import ( SurfaceData, create_map_layers, @@ -92,7 +92,7 @@ def __init__( ] for ensemble_name in ensembles } - self._surface_server = SurfaceServer.instance(app) + self._surface_server = SurfaceImageServer.instance(app) self._polygons_server = FaultPolygonsServer.instance(app) self._map_attribute_names = init_map_attribute_names(map_attribute_names) diff --git a/webviz_subsurface/plugins/_co2_leakage/_utilities/callbacks.py b/webviz_subsurface/plugins/_co2_leakage/_utilities/callbacks.py index 147b785f3..f2b6e2dcb 100644 --- a/webviz_subsurface/plugins/_co2_leakage/_utilities/callbacks.py +++ b/webviz_subsurface/plugins/_co2_leakage/_utilities/callbacks.py @@ -9,8 +9,8 @@ SimulatedSurfaceAddress, StatisticalSurfaceAddress, SurfaceAddress, - SurfaceMeta, - SurfaceServer, + SurfaceImageMeta, + SurfaceImageServer, ) from webviz_subsurface._providers.ensemble_surface_provider.ensemble_surface_provider import ( SurfaceStatistic, @@ -45,12 +45,12 @@ class SurfaceData: color_map_range: Tuple[Optional[float], Optional[float]] color_map_name: str value_range: Tuple[float, float] - meta_data: SurfaceMeta + meta_data: SurfaceImageMeta img_url: str @staticmethod def from_server( - server: SurfaceServer, + server: SurfaceImageServer, provider: EnsembleSurfaceProvider, address: Union[SurfaceAddress, TruncatedSurfaceAddress], color_map_range: Tuple[Optional[float], Optional[float]], diff --git a/webviz_subsurface/plugins/_co2_leakage/_utilities/surface_publishing.py b/webviz_subsurface/plugins/_co2_leakage/_utilities/surface_publishing.py index f574247fe..b7876f17f 100644 --- a/webviz_subsurface/plugins/_co2_leakage/_utilities/surface_publishing.py +++ b/webviz_subsurface/plugins/_co2_leakage/_utilities/surface_publishing.py @@ -9,8 +9,8 @@ SimulatedSurfaceAddress, StatisticalSurfaceAddress, SurfaceAddress, - SurfaceMeta, - SurfaceServer, + SurfaceImageMeta, + SurfaceImageServer, ) from webviz_subsurface._providers.ensemble_surface_provider.ensemble_surface_provider import ( SurfaceStatistic, @@ -35,10 +35,10 @@ def attribute(self) -> str: def publish_and_get_surface_metadata( - server: SurfaceServer, + server: SurfaceImageServer, provider: EnsembleSurfaceProvider, address: Union[SurfaceAddress, TruncatedSurfaceAddress], -) -> Tuple[Optional[SurfaceMeta], str]: +) -> Tuple[Optional[SurfaceImageMeta], str]: if isinstance(address, TruncatedSurfaceAddress): return _publish_and_get_truncated_surface_metadata(server, provider, address) provider_id: str = provider.provider_id() @@ -55,10 +55,10 @@ def publish_and_get_surface_metadata( def _publish_and_get_truncated_surface_metadata( - server: SurfaceServer, + server: SurfaceImageServer, provider: EnsembleSurfaceProvider, address: TruncatedSurfaceAddress, -) -> Tuple[Optional[SurfaceMeta], str]: +) -> Tuple[Optional[SurfaceImageMeta], str]: qualified_address = QualifiedSurfaceAddress( provider.provider_id(), # TODO: Should probably use a dedicated address type for this. Statistical surface diff --git a/webviz_subsurface/plugins/_map_viewer_fmu/_types.py b/webviz_subsurface/plugins/_map_viewer_fmu/_types.py index 9a897e9f1..025bb306a 100644 --- a/webviz_subsurface/plugins/_map_viewer_fmu/_types.py +++ b/webviz_subsurface/plugins/_map_viewer_fmu/_types.py @@ -3,7 +3,7 @@ class LayerTypes(str, Enum): HILLSHADING = "Hillshading2DLayer" - MAP3D = "Map3DLayer" + MAP3D = "MapLayer" COLORMAP = "ColormapLayer" WELL = "WellsLayer" WELLTOPSLAYER = "GeoJsonLayer" diff --git a/webviz_subsurface/plugins/_map_viewer_fmu/callbacks.py b/webviz_subsurface/plugins/_map_viewer_fmu/callbacks.py index e5146189e..67482c604 100644 --- a/webviz_subsurface/plugins/_map_viewer_fmu/callbacks.py +++ b/webviz_subsurface/plugins/_map_viewer_fmu/callbacks.py @@ -23,12 +23,13 @@ SimulatedSurfaceAddress, StatisticalSurfaceAddress, SurfaceAddress, - SurfaceServer, + SurfaceArrayServer, + SurfaceArrayMeta, ) from ._layer_model import DeckGLMapLayersModel from ._tmp_well_pick_provider import WellPickProvider -from ._types import SurfaceMode +from ._types import SurfaceMode, LayerTypes from .layout import ( DefaultSettings, LayoutElements, @@ -43,7 +44,7 @@ def plugin_callbacks( get_uuid: Callable, ensemble_surface_providers: Dict[str, EnsembleSurfaceProvider], - surface_server: SurfaceServer, + surface_server: SurfaceArrayServer, ensemble_fault_polygons_providers: Dict[str, EnsembleFaultPolygonsProvider], fault_polygons_server: FaultPolygonsServer, map_surface_names_to_fault_polygons: Dict[str, str], @@ -304,7 +305,7 @@ def _update_color_store( # 5th callback @callback( Output({"id": get_uuid(LayoutElements.DECKGLMAP), "tab": MATCH}, "layers"), - Output({"id": get_uuid(LayoutElements.DECKGLMAP), "tab": MATCH}, "bounds"), + # Output({"id": get_uuid(LayoutElements.DECKGLMAP), "tab": MATCH}, "bounds"), Output({"id": get_uuid(LayoutElements.DECKGLMAP), "tab": MATCH}, "views"), Input( {"id": get_uuid(LayoutElements.VERIFIED_VIEW_DATA), "tab": MATCH}, "data" @@ -338,63 +339,48 @@ def _update_map( include_well_layer=well_picks_provider is not None, visible_well_layer=LayoutLabels.SHOW_WELLS in options, visible_fault_polygons_layer=LayoutLabels.SHOW_FAULTPOLYGONS in options, - visible_hillshading_layer=LayoutLabels.SHOW_HILLSHADING in options, ) layer_model = DeckGLMapLayersModel(layers) for idx, data in enumerate(surface_elements): diff_surf = data.get("surf_type") == "diff" - surf_meta, img_url = ( + surf_meta, mesh_url = ( get_surface_metadata_and_image(data) if not diff_surf else get_surface_metadata_and_image_for_diff_surface(surface_elements) ) - viewport_bounds = [ - surf_meta.x_min, - surf_meta.y_min, - surf_meta.x_max, - surf_meta.y_max, - ] - - # Map3DLayer currently not implemented. Will replace - # ColormapLayer and HillshadingLayer - # layer_data = { - # "mesh": img_url, - # "propertyTexture": img_url, - # "bounds": surf_meta.deckgl_bounds, - # "rotDeg": surf_meta.deckgl_rot_deg, - # "meshValueRange": [surf_meta.val_min, surf_meta.val_max], - # "propertyValueRange": [surf_meta.val_min, surf_meta.val_max], - # "colorMapName": data["colormap"], - # "colorMapRange": data["color_range"], - # } - - # layer_model.update_layer_by_id( - # layer_id=f"{LayoutElements.MAP3D_LAYER}-{idx}", - # layer_data=layer_data, - # ) + if ( + data["color_range"][0] != surf_meta.val_min + or data["color_range"][1] != surf_meta.val_max + ): + color_range = data["color_range"] + else: + color_range = None layer_data = { - "image": img_url, - "bounds": surf_meta.deckgl_bounds, - "rotDeg": surf_meta.deckgl_rot_deg, - "valueRange": [surf_meta.val_min, surf_meta.val_max], + "meshUrl": mesh_url, + "frame": { + "origin": [surf_meta.x_ori, surf_meta.y_ori], + "count": [surf_meta.x_count, surf_meta.y_count], + "increment": [surf_meta.x_inc, surf_meta.y_inc], + "rotDeg": surf_meta.rot_deg, + }, + "colorMapName": data["colormap"], + "colorMapRange": color_range, + } - layer_model.update_layer_by_id( - layer_id=f"{LayoutElements.COLORMAP_LAYER}-{idx}", - layer_data=layer_data, - ) + layer_idx = None + for layer in layers: + if layer["id"] == f"{LayoutElements.MAP3D_LAYER}-{idx}": + layer_idx = layers.index(layer) + break + if layer_idx is not None: + layers[layer_idx].update(layer_data) + else: + layer_data["id"] = f"{LayoutElements.MAP3D_LAYER}-{idx}" + layer_data["@@type"] = LayerTypes.MAP3D + layer_data["material"] = False + layers.insert(0, layer_data) - layer_model.update_layer_by_id( - layer_id=f"{LayoutElements.HILLSHADING_LAYER}-{idx}", - layer_data=layer_data, - ) - layer_model.update_layer_by_id( - layer_id=f"{LayoutElements.COLORMAP_LAYER}-{idx}", - layer_data={ - "colorMapName": data["colormap"], - "colorMapRange": data["color_range"], - }, - ) if ( LayoutLabels.SHOW_FAULTPOLYGONS in options and fault_polygon_attribute is not None @@ -435,7 +421,7 @@ def _update_map( ) return ( layer_model.layers, - viewport_bounds if surface_elements else no_update, + # viewport_bounds if surface_elements else no_update, { "layout": view_layout(len(surface_elements), view_columns), "showLabel": True, @@ -445,9 +431,7 @@ def _update_map( "show3D": False, "isSync": True, "layerIds": [ - # f"{LayoutElements.MAP3D_LAYER}-{view}", - f"{LayoutElements.COLORMAP_LAYER}-{view}", - f"{LayoutElements.HILLSHADING_LAYER}-{view}", + f"{LayoutElements.MAP3D_LAYER}-{view}", f"{LayoutElements.FAULTPOLYGONS_LAYER}-{view}", f"{LayoutElements.WELLS_LAYER}-{view}", ], diff --git a/webviz_subsurface/plugins/_map_viewer_fmu/layout.py b/webviz_subsurface/plugins/_map_viewer_fmu/layout.py index 6a0758c8d..62a4b9138 100644 --- a/webviz_subsurface/plugins/_map_viewer_fmu/layout.py +++ b/webviz_subsurface/plugins/_map_viewer_fmu/layout.py @@ -645,25 +645,10 @@ def update_map_layers( include_faultpolygon_layer: bool = True, visible_well_layer: bool = True, visible_fault_polygons_layer: bool = True, - visible_hillshading_layer: bool = True, ) -> List[dict]: layers: List[Dict] = [] for idx in range(views): - layers.extend( - [ - { - "@@type": LayerTypes.COLORMAP, - "name": LayerNames.COLORMAP, - "id": f"{LayoutElements.COLORMAP_LAYER}-{idx}", - }, - { - "@@type": LayerTypes.HILLSHADING, - "name": LayerNames.HILLSHADING, - "id": f"{LayoutElements.HILLSHADING_LAYER}-{idx}", - "visible": visible_hillshading_layer, - }, - ] - ) + if include_faultpolygon_layer: layers.append( { @@ -671,6 +656,7 @@ def update_map_layers( "name": LayerNames.FAULTPOLYGONS, "id": f"{LayoutElements.FAULTPOLYGONS_LAYER}-{idx}", "visible": visible_fault_polygons_layer, + "parameters": {"depthTest": False}, } ) @@ -689,6 +675,7 @@ def update_map_layers( "lineWidthMinPixels": 2, "pointRadiusMinPixels": 2, "pickable": True, + "parameters": {"depthTest": False}, } ) return layers diff --git a/webviz_subsurface/plugins/_map_viewer_fmu/map_viewer_fmu.py b/webviz_subsurface/plugins/_map_viewer_fmu/map_viewer_fmu.py index 46985686c..7b87e809d 100644 --- a/webviz_subsurface/plugins/_map_viewer_fmu/map_viewer_fmu.py +++ b/webviz_subsurface/plugins/_map_viewer_fmu/map_viewer_fmu.py @@ -4,6 +4,7 @@ from dash import Dash, html from webviz_config import WebvizPluginABC, WebvizSettings +from webviz_config.deprecation_decorators import deprecated_plugin_arguments from webviz_subsurface._providers import ( EnsembleFaultPolygonsProviderFactory, @@ -12,8 +13,8 @@ from webviz_subsurface._providers.ensemble_fault_polygons_provider.fault_polygons_server import ( FaultPolygonsServer, ) -from webviz_subsurface._providers.ensemble_surface_provider.surface_server import ( - SurfaceServer, +from webviz_subsurface._providers.ensemble_surface_provider.surface_array_server import ( + SurfaceArrayServer, ) from webviz_subsurface._utils.webvizstore_functions import read_csv @@ -22,6 +23,14 @@ from .color_tables import default_color_tables from .layout import main_layout +def check_deprecation_argument(hillshading_enabled: bool) -> str: + if hillshading_enabled is None: + return None + return ( + "The argument 'hillshading_enabled' no longer has any effect " + "and can be removed from the config file." + ) + class MapViewerFMU(WebvizPluginABC): """Surface visualizer for FMU ensembles. @@ -41,7 +50,6 @@ class MapViewerFMU(WebvizPluginABC): * **`map_surface_names_to_fault_polygons`:** Allows mapping of file surface names to the relevant fault polygon set name * **`color_tables`:** Color tables for the map layers -* **`hillshading_enabled`:** Flag to set initial hillshading on or off --- The available maps are gathered from the `share/results/maps/` folder @@ -80,6 +88,7 @@ class MapViewerFMU(WebvizPluginABC): """ # pylint: disable=too-many-arguments, too-many-locals + @deprecated_plugin_arguments(check_deprecation_argument) def __init__( self, app: Dash, @@ -92,7 +101,7 @@ def __init__( map_surface_names_to_well_pick_names: Dict[str, str] = None, rel_surface_folder: str = "share/results/maps", color_tables: Path = None, - hillshading_enabled: bool = True, + hillshading_enabled: bool = None, ): super().__init__() @@ -110,7 +119,7 @@ def __init__( ) for ens in ensembles } - self._surface_server = SurfaceServer.instance(app) + self._surface_server = SurfaceArrayServer.instance(app) self.well_pick_provider = None self.well_pick_file = well_pick_file @@ -158,7 +167,6 @@ def __init__( if color_tables is None else json.loads(color_tables.read_text()) ) - self.hillshading_enabled = hillshading_enabled self.set_callbacks() @property @@ -173,7 +181,6 @@ def layout(self) -> html.Div: else [], realizations=reals, color_tables=self.color_tables, - hillshading_enabled=self.hillshading_enabled, ) def set_callbacks(self) -> None: