From 9016ab56282fab4eb65bdbe9df6ba4f3712b215b Mon Sep 17 00:00:00 2001 From: Ruben Thoms Date: Thu, 5 Dec 2024 17:20:16 +0100 Subject: [PATCH] wip --- .../primary/routers/well/converters.py | 2 + .../primary/primary/routers/well/router.py | 19 +- .../primary/primary/routers/well/schemas.py | 2 + .../services/smda_access/smda_access.py | 15 +- .../primary/services/smda_access/types.py | 2 + .../services/wellbore_trajectory_reducer.py | 67 ++++ frontend/.eslintrc.cjs | 2 + frontend/src/api/models/WellboreHeader.ts | 2 + frontend/src/api/services/WellService.ts | 3 + .../DrilledWellTrajectoriesLayer.ts | 2 +- .../customDeckGlLayers/AdvancedWellsLayer.ts | 323 +++++++++++++----- .../2DViewer/view/utils/layerFactory.ts | 18 +- 12 files changed, 365 insertions(+), 92 deletions(-) create mode 100644 backend_py/primary/primary/services/wellbore_trajectory_reducer.py diff --git a/backend_py/primary/primary/routers/well/converters.py b/backend_py/primary/primary/routers/well/converters.py index fb12f9d40..778311597 100644 --- a/backend_py/primary/primary/routers/well/converters.py +++ b/backend_py/primary/primary/routers/well/converters.py @@ -60,6 +60,8 @@ def convert_wellbore_header_to_schema( depthReferenceElevation=drilled_wellbore_header.depth_reference_elevation, wellborePurpose=(drilled_wellbore_header.wellbore_purpose if drilled_wellbore_header.wellbore_purpose else ""), wellboreStatus=drilled_wellbore_header.wellbore_status if drilled_wellbore_header.wellbore_status else "", + parentWellbore=drilled_wellbore_header.parent_wellbore if drilled_wellbore_header.parent_wellbore else "", + kickoffDepthMd=drilled_wellbore_header.kickoff_depth_md, ) diff --git a/backend_py/primary/primary/routers/well/router.py b/backend_py/primary/primary/routers/well/router.py index 2ab5baa46..5edd6e6d4 100644 --- a/backend_py/primary/primary/routers/well/router.py +++ b/backend_py/primary/primary/routers/well/router.py @@ -9,6 +9,7 @@ from primary.auth.auth_helper import AuthHelper from primary.services.ssdl_access.well_access import WellAccess as SsdlWellAccess +from primary.services.wellbore_trajectory_reducer import reduce_child_wellbore_trajectories from . import schemas from . import converters @@ -43,7 +44,8 @@ async def get_well_trajectories( # fmt:off authenticated_user: AuthenticatedUser = Depends(AuthHelper.get_authenticated_user), field_identifier: str = Query(description="Official field identifier"), - wellbore_uuids:List[str] = Query(None, description="Optional subset of wellbore uuids") + wellbore_uuids:List[str] = Query(None, description="Optional subset of wellbore uuids"), + use_kickoff_depth: bool = Query(False, description="Use kickoff depth to reduce child wellbore trajectory length") # fmt:on ) -> List[schemas.WellboreTrajectory]: """Get well trajectories for field""" @@ -56,9 +58,22 @@ async def get_well_trajectories( wellbore_trajectories = await well_access.get_wellbore_trajectories(wellbore_uuids=wellbore_uuids) + if not use_kickoff_depth: + return [ + converters.convert_well_trajectory_to_schema(wellbore_trajectory) + for wellbore_trajectory in wellbore_trajectories + ] + + # Use kickoff depth to reduce child wellbore trajectory length + wellbore_headers = await well_access.get_wellbore_headers(wellbore_uuids=wellbore_uuids) + + adjusted_wellbore_trajectories = reduce_child_wellbore_trajectories( + wellbore_headers=wellbore_headers, wellbore_trajectories=wellbore_trajectories + ) + return [ converters.convert_well_trajectory_to_schema(wellbore_trajectory) - for wellbore_trajectory in wellbore_trajectories + for wellbore_trajectory in adjusted_wellbore_trajectories ] diff --git a/backend_py/primary/primary/routers/well/schemas.py b/backend_py/primary/primary/routers/well/schemas.py index 5e47a71f8..73f68b734 100644 --- a/backend_py/primary/primary/routers/well/schemas.py +++ b/backend_py/primary/primary/routers/well/schemas.py @@ -34,6 +34,8 @@ class WellboreHeader(BaseModel): depthReferenceElevation: float wellborePurpose: str wellboreStatus: str + parentWellbore: str + kickoffDepthMd: float class WellboreTrajectory(BaseModel): diff --git a/backend_py/primary/primary/services/smda_access/smda_access.py b/backend_py/primary/primary/services/smda_access/smda_access.py index 342ca9df1..69d40f03e 100644 --- a/backend_py/primary/primary/services/smda_access/smda_access.py +++ b/backend_py/primary/primary/services/smda_access/smda_access.py @@ -46,7 +46,7 @@ async def get_stratigraphic_units(self, strat_column_identifier: str) -> List[St units = [StratigraphicUnit(**result) for result in results] return units - async def get_wellbore_headers(self) -> List[WellboreHeader]: + async def get_wellbore_headers(self, wellbore_uuids: Optional[List[str]] = None) -> List[WellboreHeader]: """ Get wellbore header information for all wellbores in a field. We need the wellbores with actual survey data, so we must use the wellbore-survey-headers endpoint. @@ -68,6 +68,9 @@ async def get_wellbore_headers(self) -> List[WellboreHeader]: "field_identifier": self._field_identifier, } + if wellbore_uuids: + params["wellbore_uuid"] = ", ".join(wellbore_uuids) + survey_header_results = await self._smda_get_request( endpoint=SmdaEndpoints.WELLBORE_SURVEY_HEADERS, params=params ) @@ -75,7 +78,13 @@ async def get_wellbore_headers(self) -> List[WellboreHeader]: if not survey_header_results: raise NoDataError(f"No wellbore headers found for {self._field_identifier=}.", Service.SMDA) - projection = ["unique_wellbore_identifier", "wellbore_purpose", "wellbore_status"] + projection = [ + "unique_wellbore_identifier", + "wellbore_purpose", + "wellbore_status", + "parent_wellbore", + "kickoff_depth_md", + ] params = { "_projection": ",".join(projection), "_sort": "unique_wellbore_identifier", @@ -89,6 +98,8 @@ async def get_wellbore_headers(self) -> List[WellboreHeader]: if survey_header["unique_wellbore_identifier"] == wellbore_header["unique_wellbore_identifier"]: survey_header["wellbore_purpose"] = wellbore_header.get("wellbore_purpose") survey_header["wellbore_status"] = wellbore_header.get("wellbore_status") + survey_header["parent_wellbore"] = wellbore_header.get("parent_wellbore") + survey_header["kickoff_depth_md"] = wellbore_header.get("kickoff_depth_md") break return [WellboreHeader(**result) for result in survey_header_results] diff --git a/backend_py/primary/primary/services/smda_access/types.py b/backend_py/primary/primary/services/smda_access/types.py index b9bdefa45..00490c960 100644 --- a/backend_py/primary/primary/services/smda_access/types.py +++ b/backend_py/primary/primary/services/smda_access/types.py @@ -44,6 +44,8 @@ class WellboreHeader(BaseModel): depth_reference_elevation: float wellbore_purpose: str | None wellbore_status: str | None + parent_wellbore: str | None + kickoff_depth_md: float | None class StratigraphicUnit(BaseModel): diff --git a/backend_py/primary/primary/services/wellbore_trajectory_reducer.py b/backend_py/primary/primary/services/wellbore_trajectory_reducer.py new file mode 100644 index 000000000..693dd04cf --- /dev/null +++ b/backend_py/primary/primary/services/wellbore_trajectory_reducer.py @@ -0,0 +1,67 @@ +from typing import List + +from primary.services.smda_access.types import WellboreTrajectory, WellboreHeader + + +def reduce_child_wellbore_trajectories( + wellbore_headers: List[WellboreHeader], wellbore_trajectories: List[WellboreTrajectory] +) -> List[WellboreTrajectory]: + """ + Reduce the child wellbore trajectories to the parent wellbore trajectory by removing all common + points from the child wellbore trajectory. + + Args: + wellbore_headers: List of well headers + wellbore_trajectories: List of wellbore trajectories + + Returns: + List of reduced wellbore trajectories + """ + adjusted_wellbore_trajectories: List[WellboreTrajectory] = [] + + for wellbore_header in wellbore_headers: + trajectory = [x for x in wellbore_trajectories if x.wellbore_uuid == wellbore_header.wellbore_uuid][0] + parent_wellbore = wellbore_header.parent_wellbore + kickoff_md = wellbore_header.kickoff_depth_md + + if parent_wellbore is None: + adjusted_wellbore_trajectories.append(trajectory) + continue + + # Reduce child wellbore trajectory length + adjusted_trajectory = WellboreTrajectory( + wellbore_uuid=trajectory.wellbore_uuid, + unique_wellbore_identifier=trajectory.unique_wellbore_identifier, + tvd_msl_arr=[], + md_arr=[], + easting_arr=[], + northing_arr=[], + ) + for i, md in enumerate(trajectory.md_arr): + if md < kickoff_md: + continue + + if md != kickoff_md: + factor = (trajectory.md_arr[i] - kickoff_md) / (trajectory.md_arr[i] - trajectory.md_arr[i - 1]) + adjusted_trajectory.md_arr.append(kickoff_md) + adjusted_trajectory.tvd_msl_arr.append( + trajectory.tvd_msl_arr[i - 1] + factor * (trajectory.tvd_msl_arr[i] - trajectory.tvd_msl_arr[i - 1]) + ) + adjusted_trajectory.easting_arr.append( + trajectory.easting_arr[i - 1] + factor * (trajectory.easting_arr[i] - trajectory.easting_arr[i - 1]) + ) + adjusted_trajectory.northing_arr.append( + trajectory.northing_arr[i - 1] + + factor * (trajectory.northing_arr[i] - trajectory.northing_arr[i - 1]) + ) + + adjusted_trajectory.tvd_msl_arr.extend(trajectory.tvd_msl_arr[i:-1]) + # What is the convention here? Should we keep the original md or adjust it by kickoff depth? + adjusted_trajectory.md_arr.extend(trajectory.md_arr[i:-1]) + adjusted_trajectory.easting_arr.extend(trajectory.easting_arr[i:-1]) + adjusted_trajectory.northing_arr.extend(trajectory.northing_arr[i:-1]) + break + + adjusted_wellbore_trajectories.append(adjusted_trajectory) + + return adjusted_wellbore_trajectories diff --git a/frontend/.eslintrc.cjs b/frontend/.eslintrc.cjs index a199f9775..a4ed4f4eb 100644 --- a/frontend/.eslintrc.cjs +++ b/frontend/.eslintrc.cjs @@ -10,6 +10,7 @@ module.exports = { "react/jsx-uses-react": "off", // Import of React is not required anymore in React 17 "react/react-in-jsx-scope": "off", // Import of React is not required anymore in React 17 "no-console": ["error", { allow: ["debug", "info", "warn", "error"] }], + "camelcase": "always", }, parser: "@typescript-eslint/parser", parserOptions: { @@ -17,6 +18,7 @@ module.exports = { sourceType: "module", }, settings: { + react: { version: "detect", }, diff --git a/frontend/src/api/models/WellboreHeader.ts b/frontend/src/api/models/WellboreHeader.ts index 61c60bdc4..daf78c5f2 100644 --- a/frontend/src/api/models/WellboreHeader.ts +++ b/frontend/src/api/models/WellboreHeader.ts @@ -13,5 +13,7 @@ export type WellboreHeader = { depthReferenceElevation: number; wellborePurpose: string; wellboreStatus: string; + parentWellbore: string; + kickoffDepthMd: number; }; diff --git a/frontend/src/api/services/WellService.ts b/frontend/src/api/services/WellService.ts index e79a6a572..dd55a4c05 100644 --- a/frontend/src/api/services/WellService.ts +++ b/frontend/src/api/services/WellService.ts @@ -40,12 +40,14 @@ export class WellService { * Get well trajectories for field * @param fieldIdentifier Official field identifier * @param wellboreUuids Optional subset of wellbore uuids + * @param useKickoffDepth Use kickoff depth to reduce child wellbore trajectory length * @returns WellboreTrajectory Successful Response * @throws ApiError */ public getWellTrajectories( fieldIdentifier: string, wellboreUuids?: Array, + useKickoffDepth: boolean = false, ): CancelablePromise> { return this.httpRequest.request({ method: 'GET', @@ -53,6 +55,7 @@ export class WellService { query: { 'field_identifier': fieldIdentifier, 'wellbore_uuids': wellboreUuids, + 'use_kickoff_depth': useKickoffDepth, }, errors: { 422: `Validation Error`, diff --git a/frontend/src/modules/2DViewer/layers/implementations/layers/DrilledWellTrajectoriesLayer/DrilledWellTrajectoriesLayer.ts b/frontend/src/modules/2DViewer/layers/implementations/layers/DrilledWellTrajectoriesLayer/DrilledWellTrajectoriesLayer.ts index 961b1f4e1..fcf2f1c5e 100644 --- a/frontend/src/modules/2DViewer/layers/implementations/layers/DrilledWellTrajectoriesLayer/DrilledWellTrajectoriesLayer.ts +++ b/frontend/src/modules/2DViewer/layers/implementations/layers/DrilledWellTrajectoriesLayer/DrilledWellTrajectoriesLayer.ts @@ -102,7 +102,7 @@ export class DrilledWellTrajectoriesLayer implements Layer apiService.well.getWellTrajectories(fieldIdentifier ?? ""), + queryFn: () => apiService.well.getWellTrajectories(fieldIdentifier ?? "", undefined, false), staleTime: 1800000, // TODO gcTime: 1800000, }) diff --git a/frontend/src/modules/2DViewer/view/customDeckGlLayers/AdvancedWellsLayer.ts b/frontend/src/modules/2DViewer/view/customDeckGlLayers/AdvancedWellsLayer.ts index 3dc982da5..8f22d2ef6 100644 --- a/frontend/src/modules/2DViewer/view/customDeckGlLayers/AdvancedWellsLayer.ts +++ b/frontend/src/modules/2DViewer/view/customDeckGlLayers/AdvancedWellsLayer.ts @@ -1,17 +1,50 @@ -import { FilterContext, Layer, LayersList, UpdateParameters } from "@deck.gl/core"; -import { CollisionFilterExtension } from "@deck.gl/extensions"; -import { GeoJsonLayer, TextLayer } from "@deck.gl/layers"; +import { + CompositeLayer, + CompositeLayerProps, + FilterContext, + GetPickingInfoParams, + Layer, + LayersList, + PickingInfo, + UpdateParameters, +} from "@deck.gl/core"; +import { GeoJsonLayer, LineLayer, TextLayer } from "@deck.gl/layers"; +import { Entity } from "@lib/utils/ForceDirectedEntityPositioning"; import { Vec2, rotatePoint2Around } from "@lib/utils/vec2"; -import { WellsLayer } from "@webviz/subsurface-viewer/dist/layers"; import { FeatureCollection, GeometryCollection } from "geojson"; -export class AdvancedWellsLayer extends WellsLayer { +export type WellsLayerProps = { + pointRadiusScale?: number; + lineWidth?: number; + lineWidthScale?: number; + data: FeatureCollection; + visible?: boolean; +}; + +export type WellsLayerPickingInfo = PickingInfo & { + wellboreUuid: string; + name: string; + tvd: number; + md: number; +}; + +const defaultProps: Partial = { + pointRadiusScale: 1, + lineWidth: 1, + lineWidthScale: 1, + visible: true, +}; + +export class AdvancedWellsLayer extends CompositeLayer { static layerName: string = "WellsLayer"; + static defaultProps = defaultProps; // @ts-ignore state!: { labelCoordsMap: Map; + hoveredWellboreUuid: string | null; + activeWellboreUuid: string | null; }; constructor(props: any) { @@ -21,11 +54,11 @@ export class AdvancedWellsLayer extends WellsLayer { filterSubLayer(context: FilterContext): boolean { const { labelCoordsMap } = this.state; - const reg = /names-zoom-([-\d\\.]+)/; + const reg = /(names|points|lines)-zoom-([-\d\\.]+)/; const match = context.layer.id.match(reg); if (match) { - const zoom = parseFloat(match[1]); + const zoom = parseFloat(match[2]); const zoomLevels = Array.from(labelCoordsMap.keys()); const closestZoomLevel = zoomLevels.reduce((prev, curr) => Math.abs(curr - context.viewport.zoom) < Math.abs(prev - context.viewport.zoom) ? curr : prev @@ -39,16 +72,25 @@ export class AdvancedWellsLayer extends WellsLayer { private makeLabelData(): Map { const labelCoordsMap = new Map(); for (let z = 1; z > -5; z--) { - const labelCoords = precalculateLabelPositions(this.props.data as FeatureCollection, 300 / 2 ** z); + const labelCoords = precalculateLabelPositions(this.props.data as FeatureCollection, 300 * 2 ** z); + /* + const forceDirectedEntityPositioning = new ForceDirectedEntityPositioning(labelCoords, { + springRestLength: 10, + springConstant: 0.01 / 2 ** z, + chargeConstant: 100 ** -z, + tolerance: 0.1, + maxIterations: 300, + }); + + const adjustedLabelCoords = forceDirectedEntityPositioning.run(); + */ labelCoordsMap.set(z, labelCoords); } return labelCoordsMap; } - updateState(params: UpdateParameters): void { - super.updateState(params); - + updateState(params: UpdateParameters>>): void { if (!params.changeFlags.dataChanged) { return; } @@ -58,109 +100,209 @@ export class AdvancedWellsLayer extends WellsLayer { } initializeState(): void { - super.initializeState(); - this.setState({ labelCoords: new Map(), + hoveredWellboreUuid: null, + activeWellboreUuid: null, }); } - renderLayers(): LayersList { - const layers = super.renderLayers(); + getPickingInfo(params: GetPickingInfoParams): WellsLayerPickingInfo { + const info = super.getPickingInfo(params) as WellsLayerPickingInfo; + const { index, sourceLayer } = info; - if (!Array.isArray(layers)) { - return layers; + if (index < 0 || !sourceLayer) { + return info; } - const colorsLayer = layers.find((layer) => { - if (!(layer instanceof Layer)) { - return false; - } + if (sourceLayer.id.includes("names") && sourceLayer instanceof TextLayer) { + const wellboreUuid = sourceLayer.props.data[index].wellboreUuid; + const name = sourceLayer.props.data[index].name; + return { + ...info, + wellboreUuid, + name, + md: 0, + }; + } - return layer.id.includes("colors"); - }); + const wellbore = this.props.data.features[index]; + const wellboreUuid = wellbore.properties?.uuid; + const name = wellbore.properties?.name; - const textLayer = layers.find((layer) => { - if (!(layer instanceof TextLayer)) { - return false; - } + if (!wellboreUuid || !name) { + return info; + } - return layer.id.includes("names"); - }); + return { + ...info, + wellboreUuid, + name, + md: 0, + }; + } - if (!(colorsLayer instanceof GeoJsonLayer)) { - return layers; + onHover(info: WellsLayerPickingInfo): boolean { + if (info.object) { + this.setState({ hoveredWellboreUuid: info.wellboreUuid }); + } else { + this.setState({ hoveredWellboreUuid: null }); } - if (!(textLayer instanceof TextLayer)) { - return layers; + return false; + } + + onClick(info: WellsLayerPickingInfo): boolean { + if (info.object) { + this.setState({ activeWellboreUuid: info.wellboreUuid }); + } else { + this.setState({ activeWellboreUuid: null }); } - const newColorsLayer = new GeoJsonLayer({ - data: colorsLayer.props.data, - pickable: true, - stroked: false, - positionFormat: colorsLayer.props.positionFormat, - pointRadiusUnits: "meters", - lineWidthUnits: "meters", - pointRadiusScale: this.props.pointRadiusScale, - lineWidthScale: this.props.lineWidthScale, - getLineWidth: colorsLayer.props.getLineWidth, - getPointRadius: colorsLayer.props.getPointRadius, - lineBillboard: true, - pointBillboard: true, - parameters: colorsLayer.props.parameters, - visible: colorsLayer.props.visible, - id: "colors", - lineWidthMinPixels: 1, - lineWidthMaxPixels: 5, - extensions: colorsLayer.props.extensions, - getDashArray: colorsLayer.props.getDashArray, - getLineColor: colorsLayer.props.getLineColor, - getFillColor: colorsLayer.props.getFillColor, - autoHighlight: true, - onHover: () => {}, - }); + return false; + } - const zoomLabelsLayers: Layer[] = []; + renderLayers(): LayersList { + const { hoveredWellboreUuid, activeWellboreUuid } = this.state; + + const layers: Layer[] = []; + + layers.push( + new GeoJsonLayer({ + data: this.props.data, + pickable: true, + stroked: false, + pointRadiusUnits: "meters", + lineWidthUnits: "meters", + pointRadiusScale: this.props.pointRadiusScale, + lineWidthScale: this.props.lineWidthScale, + getLineWidth: (d: any) => { + if (activeWellboreUuid === d.properties?.uuid) { + return this.props.lineWidth! * 2; + } + return this.props.lineWidth!; + }, + lineBillboard: true, + pointBillboard: true, + visible: this.props.visible, + id: "colors", + lineWidthMinPixels: 1, + lineWidthMaxPixels: 5, + getLineColor: (d: any) => { + if (activeWellboreUuid === d.properties?.uuid) { + return [0, 173, 230, 255]; + } + if (hoveredWellboreUuid === d.properties?.uuid) { + return [255, 100, 0, 255]; + } + return [0, 0, 0, 255]; + }, + getFillColor: (d: any) => { + return hoveredWellboreUuid === d.properties?.uuid ? [255, 0, 0, 255] : [0, 0, 0, 255]; + }, + updateTriggers: { + getFillColor: [hoveredWellboreUuid, activeWellboreUuid], + getLineColor: [hoveredWellboreUuid, activeWellboreUuid], + getLineWidth: [activeWellboreUuid], + }, + }) + ); for (const [zoom, labelCoords] of this.state.labelCoordsMap) { - zoomLabelsLayers.push( + const featureCollection = { + type: "FeatureCollection", + features: labelCoords.map((d) => ({ + type: "Feature", + geometry: { + type: "Point", + coordinates: d.anchorCoordinates, + }, + })), + }; + + layers.push( + new LineLayer( + this.getSubLayerProps({ + id: `lines-zoom-${zoom}`, + data: labelCoords, + getSourcePosition: (d: WellboreLabelCoords) => d.anchorCoordinates, + getTargetPosition: (d: WellboreLabelCoords) => d.coordinates, + getColor: () => { + return [0, 0, 0, 255]; + }, + getWidth: () => { + return 1; + }, + widthMaxPixels: 5, + sizeUnits: "pixels", + }) + ) + ); + + layers.push( + new GeoJsonLayer( + this.getSubLayerProps({ + id: `points-zoom-${zoom}`, + data: featureCollection, + getRadius: (d: any) => { + return 3; + }, + pointRadiusUnits: "pixels", + radiusUnits: "meters", + pointRadiusMaxPixels: 4, + pickable: true, + filled: true, + stroked: false, + autoHighlight: true, + getFillColor: (d: any) => { + return [0, 0, 0]; + }, + }) + ) + ); + + layers.push( new TextLayer({ id: `names-zoom-${zoom}`, data: labelCoords, - getColor: [0, 0, 0], - getBackgroundColor: [255, 255, 255], + getColor: [255, 255, 255], getBorderColor: [0, 173, 230], - getBorderWidth: 1, - getPosition: (d: WellboreLabelCoords) => d.coords, + getBorderWidth: 0, + getPosition: (d: WellboreLabelCoords) => d.coordinates, getText: (d: WellboreLabelCoords) => d.name, getSize: 16, getAngle: (d: WellboreLabelCoords) => d.angle, billboard: false, background: true, - backgroundPadding: [4, 1], + backgroundPadding: [1, 0], fontFamily: "monospace", collisionEnabled: true, sizeUnits: "meters", sizeMaxPixels: 20, - sizeMinPixels: 10, - extensions: [new CollisionFilterExtension()], + sizeMinPixels: 12, + pickable: true, + getBackgroundColor: (d: any) => { + if (activeWellboreUuid === d.wellboreUuid) { + return [0, 173, 230, 255]; + } + if (hoveredWellboreUuid === d.wellboreUuid) { + return [255, 100, 0, 255]; + } + return [0, 0, 0, 255]; + }, + updateTriggers: { + getBackgroundColor: [hoveredWellboreUuid, activeWellboreUuid], + }, }) ); } - return [ - newColorsLayer, - ...layers.filter((layer) => layer !== colorsLayer && layer !== textLayer), - ...zoomLabelsLayers, - ]; + return layers; } } -type WellboreLabelCoords = { +type WellboreLabelCoords = Entity & { wellboreUuid: string; name: string; - coords: [number, number, number]; angle: number; }; @@ -222,17 +364,41 @@ function precalculateLabelPositions(data: FeatureCollection, minDistance: number } const coords = geometry.coordinates as [number, number, number][]; - if (coords.length < 2) { + if (coords.length < 3) { continue; } + const i = coords.length - 2; + 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, + anchorCoordinates: [current[0], current[1]], + coordinates: [current[0], current[1]], + angle: angle, + }); + 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 && i !== 1) { + if (distance < minDistance && i !== coords.length - 3) { continue; } @@ -255,7 +421,7 @@ function precalculateLabelPositions(data: FeatureCollection, minDistance: number if ( labelCoords.some((label) => { const otherBbox = makeBoundingBox( - [label.coords[0], label.coords[1]], + [label.coordinates[0], label.coordinates[1]], label.name.length * 20, 20, label.angle @@ -275,7 +441,8 @@ function precalculateLabelPositions(data: FeatureCollection, minDistance: number labelCoords.push({ name, wellboreUuid: uuid, - coords: [current[0], current[1], 0], + anchorCoordinates: [current[0], current[1]], + coordinates: [current[0], current[1]], angle: angle, }); diff --git a/frontend/src/modules/2DViewer/view/utils/layerFactory.ts b/frontend/src/modules/2DViewer/view/utils/layerFactory.ts index 8170d4683..e0677fd84 100644 --- a/frontend/src/modules/2DViewer/view/utils/layerFactory.ts +++ b/frontend/src/modules/2DViewer/view/utils/layerFactory.ts @@ -6,7 +6,7 @@ import { ColorScaleGradientType, ColorScaleType } from "@lib/utils/ColorScale"; import { Vec2, rotatePoint2Around } from "@lib/utils/vec2"; import { GridMappedProperty_trans, GridSurface_trans } from "@modules/3DViewer/view/queries/queryDataTransforms"; import { ColorScaleWithName } from "@modules/_shared/utils/ColorScaleWithName"; -import { ColormapLayer, Grid3DLayer, WellsLayer } from "@webviz/subsurface-viewer/dist/layers"; +import { ColormapLayer, Grid3DLayer } from "@webviz/subsurface-viewer/dist/layers"; import { Rgb, parse } from "culori"; import { Feature, FeatureCollection, GeoJsonProperties, Geometry } from "geojson"; @@ -203,7 +203,7 @@ function polygonsToGeojson(polygons: PolygonData_api): Feature el.uniqueWellboreIdentifier !== "NO 34/4-K-3 AH" ); @@ -273,11 +273,11 @@ export function wellTrajectoryToGeojson( }; const coordinates: Record = { type: "LineString", - coordinates: zipCoords(wellTrajectory.eastingArr, wellTrajectory.northingArr), + coordinates: zipCoords(wellTrajectory.eastingArr, wellTrajectory.northingArr, wellTrajectory.tvdMslArr), }; - let color = [100, 100, 100]; - let lineWidth = 2; + let color = [0, 0, 0]; + let lineWidth = 5; let wellHeadSize = 1; if (wellTrajectory.wellboreUuid === selectedWellboreUuid) { color = [255, 0, 0]; @@ -305,10 +305,10 @@ export function wellTrajectoryToGeojson( return geometryCollection; } -function zipCoords(x_arr: number[], y_arr: number[]): number[][] { +function zipCoords(xArr: number[], yArr: number[], zArr: number[]): number[][] { const coords: number[][] = []; - for (let i = 0; i < x_arr.length; i++) { - coords.push([x_arr[i], y_arr[i], 0]); + for (let i = 0; i < xArr.length; i++) { + coords.push([xArr[i], yArr[i], -zArr[i]]); } return coords;