Skip to content

Commit

Permalink
wip
Browse files Browse the repository at this point in the history
  • Loading branch information
rubenthoms committed Dec 3, 2024
1 parent d1af4a9 commit 2482635
Show file tree
Hide file tree
Showing 2 changed files with 220 additions and 17 deletions.
Original file line number Diff line number Diff line change
@@ -1,22 +1,49 @@
import { FilterContext, Layer, LayersList } from "@deck.gl/core";
import { GeoJsonLayer } from "@deck.gl/layers";
import { FilterContext, Layer, LayersList, UpdateParameters } from "@deck.gl/core";
import { CollisionFilterExtension } from "@deck.gl/extensions";
import { GeoJsonLayer, TextLayer } from "@deck.gl/layers";
import { WellsLayer } from "@webviz/subsurface-viewer/dist/layers";

import { FeatureCollection, GeometryCollection } from "geojson";

export class AdvancedWellsLayer extends WellsLayer {
static layerName: string = "WellsLayer";

// @ts-ignore

Check failure on line 11 in frontend/src/modules/2DViewer/view/customDeckGlLayers/AdvancedWellsLayer.ts

View workflow job for this annotation

GitHub Actions / frontend

Use "@ts-expect-error" instead of "@ts-ignore", as "@ts-ignore" will do nothing if the following line is error-free
state!: {
labelCoords: WellboreLabelCoords[];
};

constructor(props: any) {
super(props);
}

filterSubLayer(context: FilterContext): boolean {
if (context.layer.id.includes("names")) {
return context.viewport.zoom > -2;
return context.viewport.zoom > -3;
}

return true;
}

updateState(params: UpdateParameters<WellsLayer>): void {
super.updateState(params);

if (!params.changeFlags.dataChanged) {
return;
}

const labelCoords = precalculateLabelPositions(params.props.data as FeatureCollection);
this.setState({ labelCoords });
}

initializeState(): void {
super.initializeState();

this.setState({
labelCoords: precalculateLabelPositions(this.props.data as FeatureCollection),
});
}

renderLayers(): LayersList {
const layers = super.renderLayers();

Expand All @@ -32,10 +59,22 @@ export class AdvancedWellsLayer extends WellsLayer {
return layer.id.includes("colors");
});

const textLayer = layers.find((layer) => {
if (!(layer instanceof TextLayer)) {
return false;
}

return layer.id.includes("names");
});

if (!(colorsLayer instanceof GeoJsonLayer)) {
return layers;
}

if (!(textLayer instanceof TextLayer)) {
return layers;
}

const newColorsLayer = new GeoJsonLayer({
data: colorsLayer.props.data,
pickable: true,
Expand All @@ -62,6 +101,115 @@ export class AdvancedWellsLayer extends WellsLayer {
onHover: () => {},
});

return [newColorsLayer, ...layers.filter((layer) => layer !== colorsLayer)];
const newTextLayer = new TextLayer({
id: "names",
data: this.state.labelCoords,
getColor: [0, 0, 0],
getBackgroundColor: [255, 255, 255],
getBorderColor: [0, 173, 230],
getBorderWidth: 1,
getPosition: (d: WellboreLabelCoords) => d.coords,
getText: (d: WellboreLabelCoords) => d.name,
getSize: 16,
getAngle: (d: WellboreLabelCoords) => d.angle,
billboard: false,
background: true,
backgroundPadding: [4, 1],
fontFamily: "monospace",
collisionEnabled: true,
sizeUnits: "meters",
sizeMaxPixels: 20,
sizeMinPixels: 8,
extensions: [new CollisionFilterExtension()],
});

return [
newColorsLayer,
...layers.filter((layer) => layer !== colorsLayer && layer !== textLayer),
newTextLayer,
];
}
}

type WellboreLabelCoords = {
wellboreUuid: string;
name: string;
coords: [number, number, number];
angle: number;
};

function precalculateLabelPositions(data: FeatureCollection, minDistance: number = 1000): WellboreLabelCoords[] {
const labelCoords: WellboreLabelCoords[] = [];

for (const feature of data.features) {
const name = feature.properties?.name;
const uuid = feature.properties?.uuid;
if (!uuid) {
continue;
}

const collection = feature.geometry as GeometryCollection;
if (!collection.geometries) {
continue;
}

for (const geometry of collection.geometries) {
if (geometry.type !== "LineString") {
continue;
}

const coords = geometry.coordinates as [number, number, number][];
if (coords.length < 2) {
continue;
}

let lastCoordinates = coords[0];
for (let i = 1; i < coords.length - 2; i++) {
const distance = Math.sqrt(
(coords[i][0] - lastCoordinates[0]) ** 2 + (coords[i][1] - lastCoordinates[1]) ** 2
);

if (distance < minDistance) {
continue;
}

if (
labelCoords.some(
(label) =>
label.coords[0] - minDistance / 5 <= coords[i][0] &&
label.coords[0] + minDistance / 5 >= coords[i][0] &&
label.coords[1] - minDistance / 5 <= coords[i][1] &&
label.coords[1] + minDistance / 5 >= coords[i][1]
)
) {
continue;
}

const current = coords[i];
const prev = coords[i - 1];
const next = coords[i + 1];

let angle = Math.atan2(prev[1] - next[1], prev[0] - next[0]) * (180 / Math.PI);

if (angle < -90) {
angle += 180;
}

if (angle > 90) {
angle -= 180;
}

labelCoords.push({
name,
wellboreUuid: uuid,
coords: [current[0], current[1], 0],
angle: angle,
});

lastCoordinates = coords[i];
}
}
}

return labelCoords;
}
81 changes: 68 additions & 13 deletions frontend/src/modules/2DViewer/view/customDeckGlLayers/LabelLayer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ import { GeoJsonLayer, LineLayer, TextLayer } from "@deck.gl/layers";
import { Entity, ForceDirectedEntityPositioning } from "@lib/utils/ForceDirectedEntityPositioning";
import { EntityGroup, ProximityGrouping } from "@lib/utils/ProximityGrouping";

import { Feature } from "geojson";

type LabelData = {
coordinates: [number, number, number];
name: string;
Expand All @@ -36,11 +38,6 @@ export type LabelLayerProps = {
sizeMaxPixels: number;
};

type BoundingBox2D = {
topLeft: number[];
bottomRight: number[];
};

export type LabelPickingInfo = PickingInfo & {
additionalText?: string;
};
Expand Down Expand Up @@ -194,23 +191,66 @@ export class LabelLayer extends CompositeLayer<LabelLayerProps> {
const { adjustedData } = this.state;
const info = super.getPickingInfo(params) as LabelPickingInfo;
const { index, sourceLayer } = info;
if (index >= 0 && sourceLayer) {

console.debug(info);

if (index < 0) {
return info;
}

const reg = /(text|points)-zoom-([-\d\\.]+)/;
const match = info.sourceLayer?.id.match(reg);

if (match) {
const zoomLevel = parseFloat(match[2]);
const labelGroup = this.state.labelGroups.get(zoomLevel);
if (!labelGroup) {
return info;
}
info.object.name = this.reduceNames([
labelGroup[info.index].name,
...labelGroup[info.index].entities.map((e) => e.name),
]);
return info;
}

if (sourceLayer) {
const otherNames = adjustedData[index].otherNames;
info.object.name = this.reduceNames([adjustedData[index].name, ...otherNames]);
console.debug(info.object.name);
}
return info;
}

onHover(info: LabelPickingInfo): boolean {
const { adjustedData, hoveredId } = this.state;
let newHoveredId: string | null;
if (info.index >= 0) {
newHoveredId = this.makeId(adjustedData[info.index]);
} else {
if (info.index < 0) {
if (hoveredId === null) {
return false;
}

newHoveredId = null;
this.setState({ ...this.state, hoveredId: newHoveredId });
return false;
}

const reg = /(text|points)-zoom-([-\d\\.]+)/;
const match = info.sourceLayer?.id.match(reg);

if (match) {
const zoomLevel = parseFloat(match[2]);
const labelGroup = this.state.labelGroups.get(zoomLevel);
if (!labelGroup) {
return false;
}
newHoveredId = this.makeZoomLevelGroupId(zoomLevel, labelGroup[info.index]);
if (newHoveredId !== hoveredId) {
this.setState({ ...this.state, hoveredId: newHoveredId });
}
return false;
}

newHoveredId = this.makeId(adjustedData[info.index]);
if (newHoveredId !== hoveredId) {
this.setState({ ...this.state, hoveredId: newHoveredId });
}
Expand All @@ -222,6 +262,10 @@ export class LabelLayer extends CompositeLayer<LabelLayerProps> {
return `${labelData.name}-${labelData.coordinates.join(",")}`;
}

private makeZoomLevelGroupId(zoomLevel: number, group: EntityGroup<IntermediateLabelData>): string {
return `zoom-${zoomLevel}-${group.coordinates.join(",")}`;
}

private reduceNames(names: string[], maxNum: number = 5): string {
const ellipsis = names.length > maxNum ? `\n... + ${names.length - maxNum} more` : "";
const newNames = names.slice(0, Math.min(maxNum, names.length));
Expand All @@ -239,7 +283,7 @@ export class LabelLayer extends CompositeLayer<LabelLayerProps> {
const featureCollection = {
type: "FeatureCollection",
features: labelGroup.map((d) => ({
id: d.coordinates.join(","),
id: this.makeZoomLevelGroupId(zoomLevel, d),
type: "Feature",
geometry: {
type: "Point",
Expand All @@ -256,18 +300,29 @@ export class LabelLayer extends CompositeLayer<LabelLayerProps> {
id: `points-zoom-${zoomLevel}`,
data: featureCollection,
getRadius: 100 / 2 ** zoomLevel,
getFillColor: [155, 155, 155, 30],
getFillColor: (d: Feature) => {
if (hoveredId && d.id === hoveredId) {
return [20, 20, 255, 30];
}
return [255, 255, 255, 30];
},
stroked: false,
pickable: true,
pointRadiusUnits: "meters",
parameters: {
depthMask: false,
},
updateTriggers: {
getFillColor: [hoveredId],
},
})
)
);
zoomLayers.push(
new TextLayer(
this.getSubLayerProps({
id: `text-zoom-${zoomLevel}`,
data: labelGroups,
data: labelGroup,
getPosition: (d: ExtendedLabelData) => d.coordinates,
getText: (d: ExtendedLabelData) => `${d.name}`,
getSize: 16,
Expand Down

0 comments on commit 2482635

Please sign in to comment.