From 18f4a7cc2b0da30e1fb879be6007b0b7e21b88da Mon Sep 17 00:00:00 2001 From: Ruben Thoms Date: Tue, 18 Jun 2024 17:14:50 +0200 Subject: [PATCH] wip --- .../utils/surfaceStatisticalFancharts.ts | 4 +- .../settings/atoms/layersAtoms.ts | 3 + .../surfacesUncertaintyLayer.tsx | 305 ++++++++++++++++++ .../settings/components/layers.tsx | 12 + .../src/modules/Intersection/typesAndEnums.ts | 2 + .../utils/layers/SurfacesUncertaintyLayer.ts | 242 ++++++++++++++ .../view/atoms/atomDefinitions.ts | 4 + .../view/components/layersWrapper.tsx | 60 ++++ 8 files changed, 629 insertions(+), 3 deletions(-) create mode 100644 frontend/src/modules/Intersection/settings/components/layerSettings/surfacesUncertaintyLayer.tsx create mode 100644 frontend/src/modules/Intersection/utils/layers/SurfacesUncertaintyLayer.ts diff --git a/frontend/src/framework/components/EsvIntersection/utils/surfaceStatisticalFancharts.ts b/frontend/src/framework/components/EsvIntersection/utils/surfaceStatisticalFancharts.ts index 5e8b5ea0d..74f1c3c9b 100644 --- a/frontend/src/framework/components/EsvIntersection/utils/surfaceStatisticalFancharts.ts +++ b/frontend/src/framework/components/EsvIntersection/utils/surfaceStatisticalFancharts.ts @@ -26,7 +26,7 @@ export function makeSurfaceStatisticalFanchartFromRealizationSurface( realizationSamplePoints: number[][], cumulatedLength: number[], surfaceName: string, - stratColorMap: StratigraphyColorMap, + color: string, visibility?: { mean: boolean; minMax: boolean; @@ -56,8 +56,6 @@ export function makeSurfaceStatisticalFanchartFromRealizationSurface( p90[i] = calcPercentile(values, 90); } - const color = stratColorMap[surfaceName] || "black"; - return { color, label: surfaceName, diff --git a/frontend/src/modules/Intersection/settings/atoms/layersAtoms.ts b/frontend/src/modules/Intersection/settings/atoms/layersAtoms.ts index fe2064162..9528f1564 100644 --- a/frontend/src/modules/Intersection/settings/atoms/layersAtoms.ts +++ b/frontend/src/modules/Intersection/settings/atoms/layersAtoms.ts @@ -8,6 +8,7 @@ import { BaseLayer } from "@modules/Intersection/utils/layers/BaseLayer"; import { GridLayer } from "@modules/Intersection/utils/layers/GridLayer"; import { SeismicLayer } from "@modules/Intersection/utils/layers/SeismicLayer"; import { SurfaceLayer } from "@modules/Intersection/utils/layers/SurfaceLayer"; +import { SurfacesUncertaintyLayer } from "@modules/Intersection/utils/layers/SurfacesUncertaintyLayer"; import { WellpicksLayer } from "@modules/Intersection/utils/layers/WellpicksLayer"; import { QueryClient } from "@tanstack/query-core"; @@ -34,6 +35,8 @@ function makeLayer(type: LayerType, name: string, queryClient: QueryClient): Bas return new SurfaceLayer(name, queryClient); case LayerType.WELLPICKS: return new WellpicksLayer(name, queryClient); + case LayerType.SURFACES_UNCERTAINTY: + return new SurfacesUncertaintyLayer(name, queryClient); default: throw new Error(`Layer type ${type} not supported`); } diff --git a/frontend/src/modules/Intersection/settings/components/layerSettings/surfacesUncertaintyLayer.tsx b/frontend/src/modules/Intersection/settings/components/layerSettings/surfacesUncertaintyLayer.tsx new file mode 100644 index 000000000..06ff89968 --- /dev/null +++ b/frontend/src/modules/Intersection/settings/components/layerSettings/surfacesUncertaintyLayer.tsx @@ -0,0 +1,305 @@ +import React from "react"; + +import { SurfaceAttributeType_api, SurfaceMeta_api } from "@api"; +import { apiService } from "@framework/ApiService"; +import { EnsembleIdent } from "@framework/EnsembleIdent"; +import { EnsembleSet } from "@framework/EnsembleSet"; +import { WorkbenchSession, useEnsembleRealizationFilterFunc } from "@framework/WorkbenchSession"; +import { WorkbenchSettings } from "@framework/WorkbenchSettings"; +import { EnsembleDropdown } from "@framework/components/EnsembleDropdown"; +import { defaultColorPalettes } from "@framework/utils/colorPalettes"; +import { ColorPaletteSelector, ColorPaletteSelectorType } from "@lib/components/ColorPaletteSelector"; +import { Dropdown, DropdownOption } from "@lib/components/Dropdown"; +import { Input } from "@lib/components/Input"; +import { PendingWrapper } from "@lib/components/PendingWrapper"; +import { Select } from "@lib/components/Select"; +import { ColorPalette } from "@lib/utils/ColorPalette"; +import { ColorSet } from "@lib/utils/ColorSet"; +import { useLayerSettings } from "@modules/Intersection/utils/layers/BaseLayer"; +import { + SurfacesUncertaintyLayer, + SurfacesUncertaintyLayerSettings, +} from "@modules/Intersection/utils/layers/SurfacesUncertaintyLayer"; +import { UseQueryResult, useQuery } from "@tanstack/react-query"; + +import { cloneDeep, isEqual } from "lodash"; + +import { fixupSetting } from "./utils"; + +export type SurfacesUncertaintyLayerSettingsComponentProps = { + layer: SurfacesUncertaintyLayer; + ensembleSet: EnsembleSet; + workbenchSession: WorkbenchSession; + workbenchSettings: WorkbenchSettings; +}; + +export function SurfacesUncertaintyLayerSettingsComponent( + props: SurfacesUncertaintyLayerSettingsComponentProps +): React.ReactNode { + const settings = useLayerSettings(props.layer); + const [newSettings, setNewSettings] = React.useState(cloneDeep(settings)); + const [prevSettings, setPrevSettings] = React.useState(cloneDeep(settings)); + + if (!isEqual(settings, prevSettings)) { + setPrevSettings(settings); + setNewSettings(settings); + } + + const ensembleFilterFunc = useEnsembleRealizationFilterFunc(props.workbenchSession); + + const surfaceDirectoryQuery = useSurfaceDirectoryQuery( + newSettings.ensembleIdent?.getCaseUuid(), + newSettings.ensembleIdent?.getEnsembleName() + ); + + const fixupEnsembleIdent = fixupSetting( + "ensembleIdent", + props.ensembleSet.getEnsembleArr().map((el) => el.getIdent()), + newSettings + ); + if (!isEqual(fixupEnsembleIdent, newSettings.ensembleIdent)) { + setNewSettings((prev) => ({ ...prev, ensembleIdent: fixupEnsembleIdent })); + } + + if (fixupEnsembleIdent) { + const fixupRealizationNums = fixupRealizationNumsSetting( + newSettings.realizationNums, + ensembleFilterFunc(fixupEnsembleIdent) + ); + if (!isEqual(fixupRealizationNums, newSettings.realizationNums)) { + setNewSettings((prev) => ({ ...prev, realizationNums: fixupRealizationNums })); + } + } + + const availableAttributes: string[] = []; + const availableSurfaceNames: string[] = []; + + if (surfaceDirectoryQuery.data) { + availableAttributes.push( + ...Array.from( + new Set( + surfaceDirectoryQuery.data + .filter((el) => el.attribute_type === SurfaceAttributeType_api.DEPTH) + .map((el) => el.attribute_name) + ) + ) + ); + + const fixupAttribute = fixupSetting("attribute", availableAttributes, newSettings); + if (!isEqual(fixupAttribute, newSettings.attribute)) { + setNewSettings((prev) => ({ ...prev, attribute: fixupAttribute })); + } + } + + if (surfaceDirectoryQuery.data && newSettings.attribute) { + availableSurfaceNames.push( + ...Array.from( + new Set( + surfaceDirectoryQuery.data + .filter((el) => el.attribute_name === newSettings.attribute) + .map((el) => el.name) + ) + ) + ); + + const fixupSurfaceNames = fixupSurfaceNamesSetting(newSettings.surfaceNames, availableSurfaceNames); + if (!isEqual(fixupSurfaceNames, newSettings.surfaceNames)) { + setNewSettings((prev) => ({ ...prev, surfaceNames: fixupSurfaceNames })); + } + + props.layer.maybeRefetchData(); + } + + React.useEffect( + function propagateSettingsChange() { + props.layer.maybeUpdateSettings(cloneDeep(newSettings)); + }, + [newSettings, props.layer] + ); + + React.useEffect( + function maybeRefetchData() { + props.layer.setIsSuspended(surfaceDirectoryQuery.isFetching); + if (!surfaceDirectoryQuery.isFetching) { + props.layer.maybeRefetchData(); + } + }, + [surfaceDirectoryQuery.isFetching, props.layer, newSettings] + ); + + function handleEnsembleChange(ensembleIdent: EnsembleIdent | null) { + setNewSettings((prev) => ({ ...prev, ensembleIdent })); + } + + function handleRealizationsChange(realizationNums: string[]) { + setNewSettings((prev) => ({ ...prev, realizationNums: realizationNums.map((el) => parseInt(el)) })); + } + + function handleAttributeChange(attribute: string) { + setNewSettings((prev) => ({ ...prev, attribute })); + } + + function handleSurfaceNamesChange(surfaceNames: string[]) { + setNewSettings((prev) => ({ ...prev, surfaceNames })); + } + + function handleResolutionChange(e: React.ChangeEvent) { + setNewSettings((prev) => ({ ...prev, resolution: parseFloat(e.target.value) })); + } + + function handleColorPaletteChange(colorPalette: ColorPalette) { + props.layer.setColorSet(new ColorSet(colorPalette)); + } + + const availableRealizations: number[] = []; + if (fixupEnsembleIdent) { + availableRealizations.push(...ensembleFilterFunc(fixupEnsembleIdent)); + } + + return ( +
+
+
Ensemble
+
+ +
+
+
+
Realizations
+
+ + +
+
+
+
Sample resolution
+
+ +
+
+
+
Color set
+
+ +
+
+
+ ); +} + +function makeRealizationOptions(realizations: readonly number[]): DropdownOption[] { + return realizations.map((realization) => ({ label: realization.toString(), value: realization.toString() })); +} + +function makeAttributeOptions(attributes: string[]): DropdownOption[] { + return attributes.map((attr) => ({ label: attr, value: attr })); +} + +function makeSurfaceNameOptions(surfaceNames: string[]): DropdownOption[] { + return surfaceNames.map((surfaceName) => ({ label: surfaceName, value: surfaceName })); +} + +const STALE_TIME = 60 * 1000; +const CACHE_TIME = 60 * 1000; + +export function useSurfaceDirectoryQuery( + caseUuid: string | undefined, + ensembleName: string | undefined +): UseQueryResult { + return useQuery({ + queryKey: ["getSurfaceDirectory", caseUuid, ensembleName], + queryFn: () => apiService.surface.getSurfaceDirectory(caseUuid ?? "", ensembleName ?? ""), + staleTime: STALE_TIME, + gcTime: CACHE_TIME, + enabled: Boolean(caseUuid && ensembleName), + }); +} + +function fixupRealizationNumsSetting( + currentRealizationNums: readonly number[], + validRealizationNums: readonly number[] +): number[] { + if (validRealizationNums.length === 0) { + return [...currentRealizationNums]; + } + + let adjustedRealizationNums = currentRealizationNums.filter((el) => validRealizationNums.includes(el)); + + if (adjustedRealizationNums.length === 0) { + adjustedRealizationNums = [...validRealizationNums]; + } + + return adjustedRealizationNums; +} + +function fixupSurfaceNamesSetting(currentSurfaceNames: string[], validSurfaceNames: string[]): string[] { + if (validSurfaceNames.length === 0) { + return currentSurfaceNames; + } + + let adjustedSurfaceNames = currentSurfaceNames.filter((el) => validSurfaceNames.includes(el)); + + if (adjustedSurfaceNames.length === 0) { + adjustedSurfaceNames = [validSurfaceNames[0]]; + } + + return adjustedSurfaceNames; +} diff --git a/frontend/src/modules/Intersection/settings/components/layers.tsx b/frontend/src/modules/Intersection/settings/components/layers.tsx index e864064fc..f2d2ceee6 100644 --- a/frontend/src/modules/Intersection/settings/components/layers.tsx +++ b/frontend/src/modules/Intersection/settings/components/layers.tsx @@ -26,6 +26,7 @@ import { import { isGridLayer } from "@modules/Intersection/utils/layers/GridLayer"; import { isSeismicLayer } from "@modules/Intersection/utils/layers/SeismicLayer"; import { isSurfaceLayer } from "@modules/Intersection/utils/layers/SurfaceLayer"; +import { isSurfacesUncertaintyLayer } from "@modules/Intersection/utils/layers/SurfacesUncertaintyLayer"; import { isWellpicksLayer } from "@modules/Intersection/utils/layers/WellpicksLayer"; import { Dropdown, MenuButton } from "@mui/base"; import { @@ -48,6 +49,7 @@ import { isEqual } from "lodash"; import { GridLayerSettingsComponent } from "./layerSettings/gridLayer"; import { SeismicLayerSettingsComponent } from "./layerSettings/seismicLayer"; import { SurfaceLayerSettingsComponent } from "./layerSettings/surfaceLayer"; +import { SurfacesUncertaintyLayerSettingsComponent } from "./layerSettings/surfacesUncertaintyLayer"; import { WellpicksLayerSettingsComponent } from "./layerSettings/wellpicksLayer"; import { layersAtom } from "../atoms/layersAtoms"; @@ -447,6 +449,16 @@ function LayerItem(props: LayerItemProps): React.ReactNode { /> ); } + if (isSurfacesUncertaintyLayer(layer)) { + return ( + + ); + } return null; } diff --git a/frontend/src/modules/Intersection/typesAndEnums.ts b/frontend/src/modules/Intersection/typesAndEnums.ts index 6dcc15c1e..a8291d4fa 100644 --- a/frontend/src/modules/Intersection/typesAndEnums.ts +++ b/frontend/src/modules/Intersection/typesAndEnums.ts @@ -5,6 +5,7 @@ export enum LayerType { SEISMIC = "seismic", SURFACES = "surfaces", WELLPICKS = "wellpicks", + SURFACES_UNCERTAINTY = "surfaces-uncertainty", } export const LAYER_TYPE_TO_STRING_MAPPING = { @@ -12,6 +13,7 @@ export const LAYER_TYPE_TO_STRING_MAPPING = { [LayerType.SEISMIC]: "Seismic", [LayerType.SURFACES]: "Surfaces", [LayerType.WELLPICKS]: "Wellpicks", + [LayerType.SURFACES_UNCERTAINTY]: "Surfaces Uncertainty", }; export enum LayerActionType { diff --git a/frontend/src/modules/Intersection/utils/layers/SurfacesUncertaintyLayer.ts b/frontend/src/modules/Intersection/utils/layers/SurfacesUncertaintyLayer.ts new file mode 100644 index 000000000..b6d7a2e5e --- /dev/null +++ b/frontend/src/modules/Intersection/utils/layers/SurfacesUncertaintyLayer.ts @@ -0,0 +1,242 @@ +import { SurfaceRealizationSampleValues_api } from "@api"; +import { apiService } from "@framework/ApiService"; +import { EnsembleIdent } from "@framework/EnsembleIdent"; +import { defaultColorPalettes } from "@framework/utils/colorPalettes"; +import { ColorSet } from "@lib/utils/ColorSet"; +import { Vector2D, pointDistance, vectorNormalize } from "@lib/utils/geometry"; +import { QueryClient } from "@tanstack/query-core"; + +import { isEqual } from "lodash"; + +import { BaseLayer, BoundingBox, LayerTopic } from "./BaseLayer"; + +const STALE_TIME = 60 * 1000; +const CACHE_TIME = 60 * 1000; + +export type SurfacesUncertaintyLayerSettings = { + ensembleIdent: EnsembleIdent | null; + realizationNums: number[]; + polylineUtmXy: number[]; + surfaceNames: string[]; + attribute: string | null; + extensionLength: number; + resolution: number; +}; + +export type SurfaceUncertaintyData = { + surfaceName: string; + cumulatedLengths: number[]; + sampledValues: number[][]; +}; + +function transformData( + cumulatedLengths: number[], + surfaceName: string, + data: SurfaceRealizationSampleValues_api[] +): SurfaceUncertaintyData { + const sampledValues: number[][] = data.map((realization) => realization.sampled_values); + return { + surfaceName: surfaceName, + cumulatedLengths, + sampledValues, + }; +} + +export class SurfacesUncertaintyLayer extends BaseLayer { + private _colorSet: ColorSet; + + constructor(name: string, queryClient: QueryClient) { + const defaultSettings = { + ensembleIdent: null, + realizationNums: [], + polylineUtmXy: [], + surfaceNames: [], + attribute: null, + extensionLength: 0, + resolution: 1, + }; + super(name, defaultSettings, queryClient); + + this._colorSet = new ColorSet(defaultColorPalettes[0]); + } + + getColorSet(): ColorSet { + return this._colorSet; + } + + setColorSet(colorSet: ColorSet): void { + this._colorSet = colorSet; + this.notifySubscribers(LayerTopic.DATA); + } + + private makeBoundingBox(): void { + if (!this._data) { + return; + } + + let minX = Number.MAX_VALUE; + let maxX = Number.MIN_VALUE; + let minY = Number.MAX_VALUE; + let maxY = Number.MIN_VALUE; + + for (const surface of this._data) { + let totalLength = 0; + for (let i = 2; i < this._settings.polylineUtmXy.length; i += 2) { + totalLength += pointDistance( + { + x: this._settings.polylineUtmXy[i], + y: this._settings.polylineUtmXy[i + 1], + }, + { + x: this._settings.polylineUtmXy[i - 2], + y: this._settings.polylineUtmXy[i - 1], + } + ); + } + minX = -this._settings.extensionLength; + maxX = Math.max(maxX, totalLength + this._settings.extensionLength); + for (const real of surface.sampledValues) { + for (const z of real) { + minY = Math.min(minY, z); + maxY = Math.max(maxY, z); + } + } + } + + super.setBoundingBox({ + x: [minX, maxX], + y: [minY, maxY], + z: [0, 0], + }); + } + + getBoundingBox(): BoundingBox | null { + const bbox = super.getBoundingBox(); + if (bbox) { + return bbox; + } + + this.makeBoundingBox(); + return super.getBoundingBox(); + } + + protected areSettingsValid(): boolean { + return ( + this._settings.ensembleIdent !== null && + this._settings.attribute !== null && + this._settings.surfaceNames.length > 0 && + this._settings.realizationNums.length > 0 && + this._settings.polylineUtmXy.length > 0 && + this._settings.resolution > 0 + ); + } + + protected doSettingsChangesRequireDataRefetch( + prevSettings: SurfacesUncertaintyLayerSettings, + newSettings: SurfacesUncertaintyLayerSettings + ): boolean { + return ( + !isEqual(prevSettings.surfaceNames, newSettings.surfaceNames) || + prevSettings.attribute !== newSettings.attribute || + !isEqual(prevSettings.realizationNums, newSettings.realizationNums) || + !isEqual(prevSettings.ensembleIdent, newSettings.ensembleIdent) || + prevSettings.extensionLength !== newSettings.extensionLength || + !isEqual(prevSettings.polylineUtmXy, newSettings.polylineUtmXy) || + prevSettings.resolution !== newSettings.resolution + ); + } + + protected async fetchData(): Promise { + const promises: Promise[] = []; + + super.setBoundingBox(null); + + const polyline = this._settings.polylineUtmXy; + + const xPoints: number[] = []; + const yPoints: number[] = []; + let cumulatedHorizontalPolylineLength = -this._settings.extensionLength; + const cumulatedHorizontalPolylineLengthArr: number[] = []; + for (let i = 0; i < polyline.length; i += 2) { + if (i > 0) { + const distance = pointDistance( + { x: polyline[i], y: polyline[i + 1] }, + { x: polyline[i - 2], y: polyline[i - 1] } + ); + const numPoints = Math.floor(distance / this._settings.resolution) - 1; + + for (let p = 1; p <= numPoints; p++) { + const vector: Vector2D = { + x: polyline[i] - polyline[i - 2], + y: polyline[i + 1] - polyline[i - 1], + }; + const normalizedVector = vectorNormalize(vector); + xPoints.push(polyline[i - 2] + normalizedVector.x * this._settings.resolution * p); + yPoints.push(polyline[i - 1] + normalizedVector.y * this._settings.resolution * p); + cumulatedHorizontalPolylineLength += this._settings.resolution; + cumulatedHorizontalPolylineLengthArr.push(cumulatedHorizontalPolylineLength); + } + } + + xPoints.push(polyline[i]); + yPoints.push(polyline[i + 1]); + + if (i > 0) { + const distance = pointDistance( + { x: polyline[i], y: polyline[i + 1] }, + { x: xPoints[xPoints.length - 1], y: yPoints[yPoints.length - 1] } + ); + + cumulatedHorizontalPolylineLength += distance; + } + + cumulatedHorizontalPolylineLengthArr.push(cumulatedHorizontalPolylineLength); + } + + const queryBody = { + sample_points: { + x_points: xPoints, + y_points: yPoints, + }, + }; + + for (const surfaceName of this._settings.surfaceNames) { + const queryKey = [ + "getSurfaceIntersection", + this._settings.ensembleIdent?.getCaseUuid() ?? "", + this._settings.ensembleIdent?.getEnsembleName() ?? "", + this._settings.realizationNums, + surfaceName, + this._settings.attribute ?? "", + this._settings.polylineUtmXy, + this._settings.extensionLength, + this._settings.resolution, + ]; + this.registerQueryKey(queryKey); + + const promise = this._queryClient + .fetchQuery({ + queryKey, + queryFn: () => + apiService.surface.postSampleSurfaceInPoints( + this._settings.ensembleIdent?.getCaseUuid() ?? "", + this._settings.ensembleIdent?.getEnsembleName() ?? "", + surfaceName, + this._settings.attribute ?? "", + this._settings.realizationNums, + queryBody + ), + staleTime: STALE_TIME, + gcTime: CACHE_TIME, + }) + .then((data) => transformData(cumulatedHorizontalPolylineLengthArr, surfaceName, data)); + promises.push(promise); + } + + return Promise.all(promises); + } +} + +export function isSurfacesUncertaintyLayer(layer: BaseLayer): layer is SurfacesUncertaintyLayer { + return layer instanceof SurfacesUncertaintyLayer; +} diff --git a/frontend/src/modules/Intersection/view/atoms/atomDefinitions.ts b/frontend/src/modules/Intersection/view/atoms/atomDefinitions.ts index 4732e0afa..18d0deffc 100644 --- a/frontend/src/modules/Intersection/view/atoms/atomDefinitions.ts +++ b/frontend/src/modules/Intersection/view/atoms/atomDefinitions.ts @@ -10,6 +10,7 @@ import { BaseLayer } from "@modules/Intersection/utils/layers/BaseLayer"; import { isGridLayer } from "@modules/Intersection/utils/layers/GridLayer"; import { isSeismicLayer } from "@modules/Intersection/utils/layers/SeismicLayer"; import { isSurfaceLayer } from "@modules/Intersection/utils/layers/SurfaceLayer"; +import { isSurfacesUncertaintyLayer } from "@modules/Intersection/utils/layers/SurfacesUncertaintyLayer"; import { isWellpicksLayer } from "@modules/Intersection/utils/layers/WellpicksLayer"; import { calcExtendedSimplifiedWellboreTrajectoryInXYPlane } from "@modules/_shared/utils/wellbore"; @@ -136,6 +137,9 @@ export function viewAtomsInitialization( if (isSurfaceLayer(layer)) { layer.maybeUpdateSettings({ polylineUtmXy: polyline.polylineUtmXy, extensionLength }); } + if (isSurfacesUncertaintyLayer(layer)) { + layer.maybeUpdateSettings({ polylineUtmXy: polyline.polylineUtmXy, extensionLength }); + } if (isWellpicksLayer(layer)) { layer.maybeUpdateSettings({ ensembleIdent, diff --git a/frontend/src/modules/Intersection/view/components/layersWrapper.tsx b/frontend/src/modules/Intersection/view/components/layersWrapper.tsx index 03d05202e..16ac27c3e 100644 --- a/frontend/src/modules/Intersection/view/components/layersWrapper.tsx +++ b/frontend/src/modules/Intersection/view/components/layersWrapper.tsx @@ -6,6 +6,7 @@ import { IntersectionReferenceSystem, ReferenceLine, SurfaceData, + SurfaceLine, getPicksData, getSeismicOptions, } from "@equinor/esv-intersection"; @@ -13,6 +14,8 @@ import { ViewContext } from "@framework/ModuleContext"; import { WorkbenchServices } from "@framework/WorkbenchServices"; import { LayerItem, LayerType } from "@framework/components/EsvIntersection"; import { Viewport } from "@framework/components/EsvIntersection/esvIntersection"; +import { SurfaceStatisticalFanchart } from "@framework/components/EsvIntersection/layers/SurfaceStatisticalFanchartCanvasLayer"; +import { makeSurfaceStatisticalFanchartFromRealizationSurface } from "@framework/components/EsvIntersection/utils/surfaceStatisticalFancharts"; import { IntersectionType } from "@framework/types/intersection"; import { useElementBoundingRect } from "@lib/hooks/useElementBoundingRect"; import { SettingsToViewInterface } from "@modules/Intersection/settingsToViewInterface"; @@ -21,6 +24,7 @@ import { BaseLayer, LayerStatus, useLayers } from "@modules/Intersection/utils/l import { GridLayer, isGridLayer } from "@modules/Intersection/utils/layers/GridLayer"; import { SeismicLayer, isSeismicLayer } from "@modules/Intersection/utils/layers/SeismicLayer"; import { isSurfaceLayer } from "@modules/Intersection/utils/layers/SurfaceLayer"; +import { isSurfacesUncertaintyLayer } from "@modules/Intersection/utils/layers/SurfacesUncertaintyLayer"; import { isWellpicksLayer } from "@modules/Intersection/utils/layers/WellpicksLayer"; import { ColorLegendsContainer } from "@modules_shared/components/ColorLegendsContainer"; @@ -310,6 +314,62 @@ export function LayersWrapper(props: LayersWrapperProps): React.ReactNode { }); } + if (isSurfacesUncertaintyLayer(layer)) { + const surfaceLayer = layer; + const data = surfaceLayer.getData(); + + if (!data) { + continue; + } + + const colorSet = surfaceLayer.getColorSet(); + + let currentColor = colorSet.getFirstColor(); + const labelData: SurfaceLine[] = []; + const fancharts: SurfaceStatisticalFanchart[] = []; + + for (const surface of data) { + const fanchart = makeSurfaceStatisticalFanchartFromRealizationSurface( + surface.sampledValues, + surface.cumulatedLengths, + surface.surfaceName, + currentColor + ); + labelData.push({ + data: fanchart.data.mean, + color: currentColor, + label: surface.surfaceName, + }); + currentColor = colorSet.getNextColor(); + fancharts.push(fanchart); + } + + esvLayers.push({ + id: `${layer.getId()}-surfaces-uncertainty`, + type: LayerType.SURFACE_STATISTICAL_FANCHARTS_CANVAS, + hoverable: true, + options: { + data: { + fancharts, + }, + order: index, + referenceSystem: props.referenceSystem ?? undefined, + }, + }); + + esvLayers.push({ + id: `${layer.getId()}-surfaces-uncertainty-labels`, + type: LayerType.GEOMODEL_LABELS, + options: { + data: { + areas: [], + lines: labelData, + }, + referenceSystem: props.referenceSystem ?? undefined, + }, + }); + } + if (isWellpicksLayer(layer)) { const wellpicksLayer = layer; const data = wellpicksLayer.getFilteredData();