|
1 | 1 | from dataclasses import dataclass |
| 2 | +from enum import StrEnum |
2 | 3 |
|
3 | 4 | import numpy as np |
4 | 5 | import xtgeo |
| 6 | +from xtgeo import _cxtgeo |
| 7 | +from xtgeo.common.constants import UNDEF_LIMIT |
| 8 | + |
5 | 9 | from numpy.typing import NDArray |
6 | 10 |
|
| 11 | +from webviz_services.service_exceptions import InvalidParameterError, Service |
| 12 | + |
7 | 13 |
|
8 | 14 | def surface_to_float32_numpy_array(surface: xtgeo.RegularSurface) -> NDArray[np.float32]: |
9 | 15 | masked_values = surface.values.astype(np.float32) |
@@ -48,3 +54,120 @@ def get_min_max_surface_values(surface: xtgeo.RegularSurface) -> MinMax | None: |
48 | 54 | return None |
49 | 55 |
|
50 | 56 | 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