Skip to content

Commit

Permalink
Map Viewer Plugin (#971)
Browse files Browse the repository at this point in the history
Co-Authored-By: Therese Natterøy <[email protected]>
Co-Authored-By: Sigurd Pettersen <[email protected]>

Co-authored-by: Therese Natterøy <[email protected]>
Co-authored-by: Sigurd Pettersen <[email protected]>
  • Loading branch information
3 people authored Feb 24, 2022
1 parent 7372653 commit 94a54c6
Show file tree
Hide file tree
Showing 37 changed files with 4,919 additions and 1 deletion.
2 changes: 1 addition & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [UNRELEASED] - YYYY-MM-DD

### Added

- [#971](https://github.com/equinor/webviz-subsurface/pull/971) - `MapViewerFMU` - New plugin for visualizing surface from FMU.
- [#940](https://github.com/equinor/webviz-subsurface/pull/940) - `SimulationTimeSeries` - Added configurable user defined vector definitions.
- [#951](https://github.com/equinor/webviz-subsurface/pull/951) - `SimulationTimeSeries` - Added calculating delta relative to date within ensemble - i.e. subtract realization values on selected date from corresponding realization values for each date in the selected vectors.
- [#944](https://github.com/equinor/webviz-subsurface/pull/940) - `WellCompletions` - Added support for zone to layer mappings that are potentially different across realizations.
Expand Down
3 changes: 3 additions & 0 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@
"InplaceVolumes = webviz_subsurface.plugins:InplaceVolumes",
"InplaceVolumesOneByOne = webviz_subsurface.plugins:InplaceVolumesOneByOne",
"LinePlotterFMU = webviz_subsurface.plugins:LinePlotterFMU",
"MapViewerFMU = webviz_subsurface.plugins:MapViewerFMU",
"MorrisPlot = webviz_subsurface.plugins:MorrisPlot",
"ParameterAnalysis = webviz_subsurface.plugins:ParameterAnalysis",
"ParameterCorrelation = webviz_subsurface.plugins:ParameterCorrelation",
Expand Down Expand Up @@ -86,11 +87,13 @@
"ecl2df>=0.15.0; sys_platform=='linux'",
"fmu-ensemble>=1.2.3",
"fmu-tools>=1.8",
"geojson>=2.5.0",
"jsonschema>=3.2.0",
"opm>=2020.10.1; sys_platform=='linux'",
"pandas>=1.1.5",
"pillow>=6.1",
"pyarrow>=5.0.0",
"pydeck>=0.6.2",
"pyscal>=0.7.5",
"scipy>=1.2",
"statsmodels>=0.12.1", # indirect dependency through https://plotly.com/python/linear-fits/
Expand Down
Empty file.
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
import warnings
from enum import Enum
from typing import Dict, List

from .types.deckgl_props import LayerTypes


class DeckGLMapLayersModel:
"""Handles updates to the DeckGLMap layers prop"""

def __init__(self, layers: List[Dict]) -> None:
self._layers = layers

def _update_layer_by_type(self, layer_type: Enum, layer_data: Dict) -> None:
"""Update a layer specification by the layer type. If multiple layers are found,
no update is performed."""
layers = list(filter(lambda x: x["@@type"] == layer_type, self._layers))
if not layers:
warnings.warn(f"No {layer_type} found in layer specification!")
if len(layers) > 1:
warnings.warn(
f"Multiple layers of type {layer_type} found in layer specification!"
)
if len(layers) == 1:
layer_idx = self._layers.index(layers[0])
self._layers[layer_idx].update(layer_data)

def update_layer_by_id(self, layer_id: str, layer_data: Dict) -> None:
"""Update a layer specification by the layer id."""
layers = list(filter(lambda x: x["id"] == layer_id, self._layers))
if not layers:
warnings.warn(f"No layer with id {layer_id} found in layer specification!")
if len(layers) > 1:
warnings.warn(
f"Multiple layers with id {layer_id} found in layer specification!"
)
if len(layers) == 1:
layer_idx = self._layers.index(layers[0])
self._layers[layer_idx].update(layer_data)

def set_propertymap(
self,
image_url: str,
bounds: List[float],
value_range: List[float],
) -> None:
"""Set the property map image url, bounds and value range in the
Colormap and Hillshading layer"""
self._update_layer_by_type(
layer_type=LayerTypes.HILLSHADING,
layer_data={
"image": image_url,
"bounds": bounds,
"valueRange": value_range,
},
)
self._update_layer_by_type(
layer_type=LayerTypes.COLORMAP,
layer_data={
"image": image_url,
"bounds": bounds,
"valueRange": value_range,
},
)

def set_colormap_image(self, colormap: str) -> None:
"""Set the colormap image url in the ColormapLayer"""
self._update_layer_by_type(
layer_type=LayerTypes.COLORMAP,
layer_data={
"colormap": colormap,
},
)

def set_colormap_range(self, colormap_range: List[float]) -> None:
"""Set the colormap range in the ColormapLayer"""
self._update_layer_by_type(
layer_type=LayerTypes.COLORMAP,
layer_data={
"colorMapRange": colormap_range,
},
)

def set_well_data(self, well_data: List[Dict]) -> None:
"""Set the well data json url in the WellsLayer"""
self._update_layer_by_type(
layer_type=LayerTypes.WELL,
layer_data={
"data": well_data,
},
)

@property
def layers(self) -> List[Dict]:
"""Returns the full layers specification"""
return self._layers
Empty file.
218 changes: 218 additions & 0 deletions webviz_subsurface/_components/deckgl_map/types/deckgl_props.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,218 @@
from dataclasses import dataclass
from enum import Enum
from typing import Any, Dict, List, Optional

import pydeck
from geojson.feature import FeatureCollection
from pydeck.types import String
from typing_extensions import Literal


class LayerTypes(str, Enum):
HILLSHADING = "Hillshading2DLayer"
MAP3D = "Map3DLayer"
COLORMAP = "ColormapLayer"
WELL = "WellsLayer"
DRAWING = "DrawingLayer"
FAULTPOLYGONS = "FaultPolygonsLayer"


class LayerIds(str, Enum):
HILLSHADING = "hillshading-layer"
MAP3D = "map3d-layer"
COLORMAP = "colormap-layer"
WELL = "wells-layer"
DRAWING = "drawing-layer"
FAULTPOLYGONS = "fault-polygons-layer"


class LayerNames(str, Enum):
HILLSHADING = "Hillshading"
MAP3D = "Map"
COLORMAP = "Colormap"
WELL = "Wells"
DRAWING = "Drawings"
FAULTPOLYGONS = "Fault polygons"


@dataclass
class Bounds:
x_min: int = 0
y_min: int = 0
x_max: int = 10
y_max: int = 10


# pylint: disable=too-few-public-methods
class DeckGLMapProps:
"""Default prop settings for DeckGLMap"""

bounds: List[float] = [0, 0, 10000, 10000]
value_range: List[float] = [0, 1]
image: str = "/surface/UNDEF.png"
color_map_name: str = "Physics"
edited_data: Dict[str, Any] = {
"data": {"type": "FeatureCollection", "features": []},
"selectedWell": "",
"selectedFeatureIndexes": [],
}
resources: Dict[str, Any] = {}


class Hillshading2DLayer(pydeck.Layer):
def __init__(
self,
image: str = DeckGLMapProps.image,
name: str = LayerNames.HILLSHADING,
bounds: List[float] = None,
value_range: List[float] = None,
uuid: Optional[str] = None,
**kwargs: Any,
) -> None:
super().__init__(
type=LayerTypes.HILLSHADING,
id=uuid if uuid is not None else LayerIds.HILLSHADING,
image=String(image),
name=String(name),
bounds=bounds if bounds is not None else DeckGLMapProps.bounds,
valueRange=value_range if value_range is not None else [0, 1],
**kwargs,
)


class Map3DLayer(pydeck.Layer):
# pylint: disable=too-many-arguments
def __init__(
self,
mesh: str = DeckGLMapProps.image,
property_texture: str = DeckGLMapProps.image,
color_map_name: str = DeckGLMapProps.color_map_name,
name: str = LayerNames.MAP3D,
bounds: List[float] = None,
mesh_value_range: List[float] = None,
mesh_max_error: int = 5,
property_value_range: List[float] = None,
color_map_range: List[float] = None,
contours: List[float] = None,
rot_deg: float = 0.0,
uuid: Optional[str] = None,
**kwargs: Any,
) -> None:
super().__init__(
type=LayerTypes.MAP3D,
id=uuid if uuid is not None else LayerIds.MAP3D,
mesh=String(mesh),
propertyTexture=String(property_texture),
colorMapName=String(color_map_name),
name=String(name),
bounds=bounds if bounds is not None else DeckGLMapProps.bounds,
meshValueRange=mesh_value_range if mesh_value_range is not None else [0, 1],
propertyValueRange=property_value_range
if property_value_range is not None
else [0, 1],
colorMapRange=color_map_range if color_map_range is not None else [0, 1],
meshMaxError=mesh_max_error,
contours=contours if contours is not None else [0, 100],
rotDeg=rot_deg,
**kwargs,
)


class ColormapLayer(pydeck.Layer):
def __init__(
self,
image: str = DeckGLMapProps.image,
color_map_name: str = DeckGLMapProps.color_map_name,
name: str = LayerNames.COLORMAP,
bounds: List[float] = None,
value_range: List[float] = None,
color_map_range: List[float] = None,
uuid: Optional[str] = None,
**kwargs: Any,
) -> None:
super().__init__(
type=LayerTypes.COLORMAP,
id=uuid if uuid is not None else LayerIds.COLORMAP,
image=String(image),
colorMapName=String(color_map_name),
name=String(name),
bounds=bounds if bounds is not None else DeckGLMapProps.bounds,
valueRange=value_range if value_range is not None else [0, 1],
colorMapRange=color_map_range if color_map_range is not None else [0, 1],
**kwargs,
)


class WellsLayer(pydeck.Layer):
def __init__(
self,
data: FeatureCollection = None,
name: str = LayerNames.WELL,
uuid: Optional[str] = None,
**kwargs: Any,
) -> None:
super().__init__(
type="GeoJsonLayer",
id=uuid if uuid is not None else LayerIds.WELL,
name=String(name),
data={"type": "FeatureCollection", "features": []}
if data is None
else data,
get_text="properties.attribute",
get_text_size=12,
get_text_anchor=String("start"),
# logData=log_data,
# logrunName=log_run,
# logName=log_name,
# selectedWell=String(selected_well),
pointType=String("circle+text"),
lineWidthMinPixels=2,
pointRadiusMinPixels=2,
pickable=True,
**kwargs,
)


class DrawingLayer(pydeck.Layer):
def __init__(
self,
mode: Literal[ # Use Enum?
"view", "modify", "transform", "drawPoint", "drawLineString", "drawPolygon"
] = "view",
uuid: Optional[str] = None,
**kwargs: Any,
):
super().__init__(
type=LayerTypes.DRAWING,
id=uuid if uuid is not None else LayerIds.DRAWING,
name=LayerNames.DRAWING,
mode=String(mode),
**kwargs,
)


class FaultPolygonsLayer(pydeck.Layer):
def __init__(
self,
data: FeatureCollection = None,
name: str = LayerNames.FAULTPOLYGONS,
uuid: Optional[str] = None,
**kwargs: Any,
) -> None:
super().__init__(
type=LayerTypes.FAULTPOLYGONS,
id=uuid if uuid is not None else LayerIds.FAULTPOLYGONS,
name=String(name),
data={
"type": "FeatureCollection",
"features": [],
}
if data is None
else data,
**kwargs,
)


class CustomLayer(pydeck.Layer):
def __init__(self, layer_type: str, uuid: str, name: str, **kwargs: Any) -> None:
super().__init__(type=layer_type, id=String(uuid), name=String(name), **kwargs)
20 changes: 20 additions & 0 deletions webviz_subsurface/_providers/__init__.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,10 @@
from .ensemble_fault_polygons_provider import (
EnsembleFaultPolygonsProvider,
EnsembleFaultPolygonsProviderFactory,
FaultPolygonsAddress,
FaultPolygonsServer,
SimulatedFaultPolygonsAddress,
)
from .ensemble_summary_provider.ensemble_summary_provider import (
EnsembleSummaryProvider,
Frequency,
Expand All @@ -6,5 +13,18 @@
from .ensemble_summary_provider.ensemble_summary_provider_factory import (
EnsembleSummaryProviderFactory,
)
from .ensemble_surface_provider import (
EnsembleSurfaceProvider,
EnsembleSurfaceProviderFactory,
ObservedSurfaceAddress,
QualifiedDiffSurfaceAddress,
QualifiedSurfaceAddress,
SimulatedSurfaceAddress,
StatisticalSurfaceAddress,
SurfaceAddress,
SurfaceMeta,
SurfaceServer,
)
from .ensemble_table_provider import EnsembleTableProvider, EnsembleTableProviderSet
from .ensemble_table_provider_factory import EnsembleTableProviderFactory
from .well_provider import WellProvider, WellProviderFactory, WellServer
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
from .ensemble_fault_polygons_provider import (
EnsembleFaultPolygonsProvider,
SimulatedFaultPolygonsAddress,
)
from .ensemble_fault_polygons_provider_factory import (
EnsembleFaultPolygonsProviderFactory,
)
from .fault_polygons_server import FaultPolygonsAddress, FaultPolygonsServer
Loading

0 comments on commit 94a54c6

Please sign in to comment.