diff --git a/typescript/packages/subsurface-viewer/src/components/Map.tsx b/typescript/packages/subsurface-viewer/src/components/Map.tsx index f1b5992309..0751365b20 100644 --- a/typescript/packages/subsurface-viewer/src/components/Map.tsx +++ b/typescript/packages/subsurface-viewer/src/components/Map.tsx @@ -554,7 +554,7 @@ const Map: React.FC = ({ ): PickingInfo[] => { if (coords?.multiPicking && pickInfo.layer?.context.deck) { const pickInfos = - pickInfo.layer.context.deck.pickMultipleObjects({ // XXX + pickInfo.layer.context.deck.pickMultipleObjects({ x: event.offsetCenter.x, y: event.offsetCenter.y, depth: coords.pickDepth ?? undefined, diff --git a/typescript/packages/subsurface-viewer/src/extensions/collision-modifier-extension.ts b/typescript/packages/subsurface-viewer/src/extensions/collision-modifier-extension.ts new file mode 100644 index 0000000000..f9ee09fc2f --- /dev/null +++ b/typescript/packages/subsurface-viewer/src/extensions/collision-modifier-extension.ts @@ -0,0 +1,20 @@ +import { LayerExtension } from "@deck.gl/core"; +import type { Layer } from "@deck.gl/core"; + +const injectionVs = { + "vs:DECKGL_FILTER_COLOR": ` + color.a = 1.0 / collision_fade; // Note: this will counteract the fading of the labels caused by deck.gl's CollisionFilterExtension + `, +}; + +export class CollisionModifierExtension extends LayerExtension { + static defaultProps = {}; + static extensionName = "CollisionModifierExtension"; + + getShaders(this: Layer) { + return { + modules: [], + inject: injectionVs, + }; + } +} diff --git a/typescript/packages/subsurface-viewer/src/layers/wells/wellsLayer.ts b/typescript/packages/subsurface-viewer/src/layers/wells/wellsLayer.ts index a1b059d0dd..11648b9887 100644 --- a/typescript/packages/subsurface-viewer/src/layers/wells/wellsLayer.ts +++ b/typescript/packages/subsurface-viewer/src/layers/wells/wellsLayer.ts @@ -7,9 +7,9 @@ import type { Position, UpdateParameters, } from "@deck.gl/core"; - +import { CollisionFilterExtension } from "@deck.gl/extensions"; +import { CollisionModifierExtension } from "../../extensions/collision-modifier-extension"; import { CompositeLayer, OrbitViewport } from "@deck.gl/core"; - import type { ExtendedLayerProps, LayerPickInfo, @@ -115,6 +115,10 @@ export interface WellsLayerProps extends ExtendedLayerProps { wellNameAtTop: boolean; wellNameSize: number; wellNameColor: Color; + /** If true will prevent well name cluttering by not displaying overlapping names. + * default false. + */ + wellNameReduceClutter: boolean; isLog: boolean; depthTest: boolean; /** If true means that input z values are interpreted as depths. @@ -150,7 +154,8 @@ const defaultProps = { wellNameVisible: false, wellNameAtTop: false, wellNameSize: 14, - wellNameColor: [0, 0, 0, 255], + wellNameColor: [0, 0, 0], + wellNameReduceClutter: false, selectedWell: "@@#editedData.selectedWells", // used to get data from deckgl layer depthTest: true, ZIncreasingDownwards: true, @@ -618,7 +623,41 @@ export default class WellsLayer extends CompositeLayer { }) ); - // well name + // Reduced cluttering properties + const clutterProps = { + background: true, + collisionEnabled: true, + getCollisionPriority: (d) => { + const labelSize = d.properties.name.length; + //return labelSize; + if (is3d) { + // In 3D prioritize according to label size. + return labelSize; + } else { + // In 2D prioritize according z height. + const labelPosition = getAnnotationPosition( + d, + this.props.wellNameAtTop, + true, + this.props.lineStyle?.color + ); + + const priority = labelPosition + ? (labelPosition?.[2] ?? 1) / 10 + Math.random() // priority must be in [-1000, 1000] + : labelSize; + return priority; + } + }, + collisionTestProps: { + sizeScale: 2, + }, + collisionGroup: "nobodys", + extensions: [ + new CollisionFilterExtension(), + new CollisionModifierExtension(), + ], + }; + const namesLayer = new TextLayer( this.getSubLayerProps({ id: "names", @@ -644,6 +683,8 @@ export default class WellsLayer extends CompositeLayer { }, parameters, visible: this.props.wellNameVisible && !fastDrawing, + + ...(this.props.wellNameReduceClutter ? clutterProps : {}), }) ); @@ -799,8 +840,8 @@ function getAnnotationPosition( color_accessor: ColorAccessor ): Position | null { if (name_at_top) { + // Read top position from Point geometry, if not present, read it from LineString geometry let top; - // Read top position from Point geometry, if not present, read it from LineString geometry const well_head = getWellHeadPosition(well_data); if (well_data) top = well_head; diff --git a/typescript/packages/subsurface-viewer/src/storybook/layers/WellsLayer.stories.tsx b/typescript/packages/subsurface-viewer/src/storybook/layers/WellsLayer.stories.tsx index 26105bbc3a..c1898e71e3 100644 --- a/typescript/packages/subsurface-viewer/src/storybook/layers/WellsLayer.stories.tsx +++ b/typescript/packages/subsurface-viewer/src/storybook/layers/WellsLayer.stories.tsx @@ -614,6 +614,8 @@ const SimplifiedRenderingComponent: React.FC = ( layers: [ new WellsLayer({ data: "./gullfaks.json", + wellNameVisible: true, + wellNameAtTop: true, wellHeadStyle: { size: 4 }, refine: true, outline: true, @@ -652,6 +654,108 @@ export const SimplifiedRendering: StoryObj = { render: (args) => , }; +type ClutterProps = { + wellNameReduceClutter: boolean; + wellNameAtTop: boolean; +}; + +const ReducedWellNameClutterComponent: React.FC = ( + props: ClutterProps +) => { + const propsWithLayers = { + id: "clutter", + layers: [ + new WellsLayer({ + data: "./gullfaks.json", + wellNameVisible: true, + wellNameAtTop: props.wellNameAtTop, + wellHeadStyle: { size: 4 }, + wellNameReduceClutter: props.wellNameReduceClutter, + refine: true, + outline: true, + ZIncreasingDownwards: false, + }), + new AxesLayer({ + id: "axes-layer", + bounds: [450000, 6781000, 0, 464000, 6791000, 3500], + }), + ], + cameraPosition: { + rotationOrbit: 45, + rotationX: 45, + zoom: -4, + target: [(450000 + 464000) / 2, (6781000 + 6791000) / 2, -3500 / 2], + }, + views: { + layout: [1, 1] as [number, number], + viewports: [ + { + id: "view_1", + show3D: true, + }, + ], + }, + }; + + return ; +}; + +export const ReducedWellNameClutter3D: StoryObj< + typeof ReducedWellNameClutterComponent +> = { + args: { + wellNameReduceClutter: false, + wellNameAtTop: true, + }, + render: (args) => , +}; + +const ReducedWellNameClutterComponent2D: React.FC = ( + props: ClutterProps +) => { + const propsWithLayers = { + id: "clutter", + layers: [ + new WellsLayer({ + data: "./gullfaks.json", + wellNameVisible: true, + wellNameAtTop: props.wellNameAtTop, + wellHeadStyle: { size: 4 }, + wellNameReduceClutter: props.wellNameReduceClutter, + refine: true, + outline: true, + ZIncreasingDownwards: false, + }), + new AxesLayer({ + id: "axes-layer", + bounds: [450000, 6781000, 0, 464000, 6791000, 3500], + }), + ], + bounds: [450000, 6781000, 464000, 6791000], + views: { + layout: [1, 1] as [number, number], + viewports: [ + { + id: "view_1", + show3D: false, + }, + ], + }, + }; + + return ; +}; + +export const ReducedWellNameClutter2D: StoryObj< + typeof ReducedWellNameClutterComponent +> = { + args: { + wellNameReduceClutter: false, + wellNameAtTop: true, + }, + render: (args) => , +}; + export const Wells3dDashed: StoryObj = { args: { ...defaultProps, diff --git a/typescript/packages/subsurface-viewer/src/storybook/sharedSettings.tsx b/typescript/packages/subsurface-viewer/src/storybook/sharedSettings.tsx index ac04f5ec1c..490f03733a 100644 --- a/typescript/packages/subsurface-viewer/src/storybook/sharedSettings.tsx +++ b/typescript/packages/subsurface-viewer/src/storybook/sharedSettings.tsx @@ -94,6 +94,7 @@ export const volveWellsFromResourcesLayer = { id: "volve-wells", data: "@@#resources.wellsData", ZIncreasingDownwards: false, + wellNameVisible: true, }; export const volveWellsLayer = {