Skip to content

Commit a335541

Browse files
committed
Move utility functions to webviz_services
- Add xtgeo helper to `sruface_helpers.py` - Add new util file for creating the well trajectory formation segments - Use ServiceLayerException instead of HttpException - Have dataclass in webviz_service, and BaseClass in router. Add converters.
1 parent cb1b6b7 commit a335541

File tree

8 files changed

+570
-105
lines changed

8 files changed

+570
-105
lines changed

backend_py/libs/services/src/webviz_services/smda_access/drogon/_drogon_well_data.py

Lines changed: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
from typing import List
2+
import numpy as np
23

34
from ..types import WellboreTrajectory, WellboreHeader, WellborePick, StratigraphicColumn
45

@@ -44,6 +45,22 @@ def get_drogon_well_headers() -> List[WellboreHeader]:
4445

4546

4647
def get_drogon_well_trajectories() -> List[WellboreTrajectory]:
48+
# Original second wellbore data
49+
original_tvd_msl = np.array([-49.0, 1293.4185, 1536.9384, 1616.4998, 1630.5153, 1656.9874])
50+
original_easting = np.array([463256.911, 463564.402, 463637.925, 463690.658, 463910.452, 464465.876])
51+
original_northing = np.array([5930542.294, 5931057.803, 5931184.235, 5931278.837, 5931688.122, 5932767.761])
52+
original_md = np.array([0.0, 1477.0, 1761.5, 1899.2601, 2363.9988, 3578.5])
53+
54+
# Create 100x more sample points using linear interpolation
55+
num_original_points = len(original_md)
56+
num_interpolated_points = (num_original_points - 1) * 100 + 1
57+
58+
# Interpolate based on MD (measured depth) as the independent variable
59+
md_interp = np.linspace(original_md[0], original_md[-1], num_interpolated_points)
60+
tvd_msl_interp = np.interp(md_interp, original_md, original_tvd_msl)
61+
easting_interp = np.interp(md_interp, original_md, original_easting)
62+
northing_interp = np.interp(md_interp, original_md, original_northing)
63+
4764
return [
4865
WellboreTrajectory(
4966
wellbore_uuid="drogon_vertical",
@@ -56,10 +73,10 @@ def get_drogon_well_trajectories() -> List[WellboreTrajectory]:
5673
WellboreTrajectory(
5774
wellbore_uuid="drogon_horizontal",
5875
unique_wellbore_identifier="55/33-A-4",
59-
tvd_msl_arr=[-49.0, 1293.4185, 1536.9384, 1616.4998, 1630.5153, 1656.9874],
60-
md_arr=[0.0, 1477.0, 1761.5, 1899.2601, 2363.9988, 3578.5],
61-
easting_arr=[463256.911, 463564.402, 463637.925, 463690.658, 463910.452, 464465.876],
62-
northing_arr=[5930542.294, 5931057.803, 5931184.235, 5931278.837, 5931688.122, 5932767.761],
76+
tvd_msl_arr=tvd_msl_interp.tolist(),
77+
md_arr=md_interp.tolist(),
78+
easting_arr=easting_interp.tolist(),
79+
northing_arr=northing_interp.tolist(),
6380
),
6481
]
6582

backend_py/libs/services/src/webviz_services/utils/surface_helpers.py

Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,15 @@
11
from dataclasses import dataclass
2+
from enum import StrEnum
23

34
import numpy as np
45
import xtgeo
6+
from xtgeo import _cxtgeo
7+
from xtgeo.common.constants import UNDEF_LIMIT
8+
59
from numpy.typing import NDArray
610

11+
from webviz_services.service_exceptions import InvalidParameterError, Service
12+
713

814
def surface_to_float32_numpy_array(surface: xtgeo.RegularSurface) -> NDArray[np.float32]:
915
masked_values = surface.values.astype(np.float32)
@@ -48,3 +54,120 @@ def get_min_max_surface_values(surface: xtgeo.RegularSurface) -> MinMax | None:
4854
return None
4955

5056
return MinMax(min=masked_min_val, max=masked_max_val)
57+
58+
59+
class PickDirection(StrEnum):
60+
"""Direction of the pick relative to the surface"""
61+
62+
UPWARD = "UPWARD"
63+
DOWNWARD = "DOWNWARD"
64+
65+
66+
@dataclass
67+
class SurfaceWellPick:
68+
"""Surface pick data along a well trajectory"""
69+
70+
unique_wellbore_identifier: str
71+
x: float
72+
y: float
73+
z: float
74+
md: float
75+
direction: PickDirection
76+
77+
78+
@dataclass
79+
class WellTrajectory:
80+
"""
81+
Well trajectory defined by a set of (x, y, z) coordinates and measured depths (md).
82+
83+
unique_wellbore_identifier: str
84+
x_points: X-coordinates of well trajectory points.
85+
y_points: Y-coordinates of well trajectory points.
86+
z_points: Z-coordinates (depth values) of well trajectory points.
87+
md_points: Measured depth values at each well trajectory point.
88+
"""
89+
90+
unique_wellbore_identifier: str
91+
x_points: list[float]
92+
y_points: list[float]
93+
z_points: list[float]
94+
md_points: list[float]
95+
96+
97+
def get_surface_picks_for_well_trajectory_from_xtgeo(
98+
surf: xtgeo.RegularSurface,
99+
well_trajectory: WellTrajectory,
100+
) -> list[SurfaceWellPick] | None:
101+
"""
102+
Calculate intersections (wellpicks) between a surface and a well trajectory.
103+
104+
Uses the underlying xtgeo C-extension directly for performance.
105+
106+
Note that this function performs interpolation internally to find the intersections. Thereby
107+
it can be inaccurate calculations of picks if the length between a well trajectory point and
108+
the next is large and the surface intersects between these points.
109+
110+
"""
111+
112+
# Ensure equal length arrays
113+
if not (
114+
well_trajectory.x_points
115+
and well_trajectory.y_points
116+
and well_trajectory.z_points
117+
and well_trajectory.md_points
118+
and len(well_trajectory.x_points)
119+
== len(well_trajectory.y_points)
120+
== len(well_trajectory.z_points)
121+
== len(well_trajectory.md_points)
122+
):
123+
raise InvalidParameterError(
124+
"Well trajectory point arrays must be non-empty and of equal length", Service.GENERAL
125+
)
126+
127+
xarray = np.array(well_trajectory.x_points, dtype=np.float32)
128+
yarray = np.array(well_trajectory.y_points, dtype=np.float32)
129+
zarray = np.array(well_trajectory.z_points, dtype=np.float32)
130+
mdarray = np.array(well_trajectory.md_points, dtype=np.float32)
131+
132+
# nval = number of valid picks
133+
# xres, yres, zres = arrays of x,y,z coordinates of picks
134+
# mres = array of measured depth values of picks
135+
# dres = array of direction indicators of picks (1=downward, 0=upward)
136+
nval, xres, yres, zres, mres, dres = _cxtgeo.well_surf_picks(
137+
xarray,
138+
yarray,
139+
zarray,
140+
mdarray,
141+
surf.ncol,
142+
surf.nrow,
143+
surf.xori,
144+
surf.yori,
145+
surf.xinc,
146+
surf.yinc,
147+
surf.yflip,
148+
surf.rotation,
149+
surf.npvalues1d,
150+
xarray.size,
151+
yarray.size,
152+
zarray.size,
153+
mdarray.size,
154+
mdarray.size,
155+
)
156+
if nval < 1:
157+
return None
158+
159+
mres[mres > UNDEF_LIMIT] = np.nan
160+
161+
res: list[SurfaceWellPick] = []
162+
for i in range(nval):
163+
res.append(
164+
SurfaceWellPick(
165+
unique_wellbore_identifier=well_trajectory.unique_wellbore_identifier,
166+
x=xres[i],
167+
y=yres[i],
168+
z=zres[i],
169+
md=mres[i],
170+
direction=PickDirection.DOWNWARD if dres[i] == 1 else PickDirection.UPWARD,
171+
)
172+
)
173+
return res

0 commit comments

Comments
 (0)