|
1 | 1 | from __future__ import annotations |
2 | 2 |
|
| 3 | +import math |
3 | 4 | import os |
4 | 5 | from pathlib import Path |
5 | 6 | from typing import TYPE_CHECKING, Any, NamedTuple, TypeAlias |
6 | 7 |
|
7 | 8 | import numpy as np |
8 | 9 | import resfo |
| 10 | +from pydantic.dataclasses import dataclass |
9 | 11 |
|
10 | 12 | from .field_file_format import ROFF_FORMATS, FieldFileFormat |
11 | 13 | from .grdecl_io import export_grdecl, import_bgrdecl, import_grdecl |
12 | 14 | from .roff_io import export_roff, import_roff |
13 | 15 |
|
14 | 16 | if TYPE_CHECKING: |
15 | 17 | import numpy.typing as npt |
| 18 | + import xtgeo # type: ignore |
16 | 19 |
|
17 | 20 | _PathLike: TypeAlias = str | os.PathLike[str] |
18 | 21 |
|
@@ -96,6 +99,116 @@ def get_shape( |
96 | 99 | return shape |
97 | 100 |
|
98 | 101 |
|
| 102 | +@dataclass(frozen=True) |
| 103 | +class ErtboxParameters: |
| 104 | + nx: int |
| 105 | + ny: int |
| 106 | + nz: int |
| 107 | + xlength: float | None = None |
| 108 | + ylength: float | None = None |
| 109 | + xinc: float | None = None |
| 110 | + yinc: float | None = None |
| 111 | + rotation_angle: float | None = None |
| 112 | + origin: tuple[float, float] | None = None |
| 113 | + |
| 114 | + |
| 115 | +def calculate_ertbox_parameters( |
| 116 | + grid: xtgeo.Grid, left_handed: bool = False |
| 117 | +) -> ErtboxParameters: |
| 118 | + """Calculate ERTBOX grid parameters from an XTGeo grid. |
| 119 | +
|
| 120 | + Extracts geometric parameters including dimensions, cell increments, |
| 121 | + rotation angle, and origin coordinates needed for ERTBOX. |
| 122 | +
|
| 123 | + Args: |
| 124 | + grid: XTGeo Grid3D object |
| 125 | + left_handed: If True, use left-handed coordinate system (default: False) |
| 126 | +
|
| 127 | + Returns: |
| 128 | + ErtboxParameters with grid dimensions, increments, rotation, and origin |
| 129 | + """ |
| 130 | + |
| 131 | + (nx, ny, nz) = grid.dimensions |
| 132 | + |
| 133 | + corner_indices = [] |
| 134 | + |
| 135 | + if left_handed: |
| 136 | + origin_cell = (1, 1, 1) |
| 137 | + x_direction_cell = (nx, 1, 1) |
| 138 | + y_direction_cell = (1, ny, 1) |
| 139 | + else: |
| 140 | + origin_cell = (1, ny, 1) |
| 141 | + x_direction_cell = (nx, ny, 1) |
| 142 | + y_direction_cell = (1, 1, 1) |
| 143 | + |
| 144 | + corner_indices = [origin_cell, x_direction_cell, y_direction_cell] |
| 145 | + |
| 146 | + # List with 3 elements, where each element contains the coordinates |
| 147 | + # for all 8 corners of a single grid cell. |
| 148 | + coord_cell = [] |
| 149 | + |
| 150 | + for corner_index in corner_indices: |
| 151 | + # Get real-world (x,y,z) coordinates for all 8 corners of this grid cell |
| 152 | + # Returns 24 values: [x0,y0,z0, x1,y1,z1, ..., x7,y7,z7] |
| 153 | + coord = grid.get_xyz_cell_corners(ijk=corner_index, activeonly=False) |
| 154 | + coord_cell.append(coord) |
| 155 | + |
| 156 | + if left_handed: |
| 157 | + # Origin: cell (1,1,1), corner 0 |
| 158 | + x0 = coord_cell[0][0] |
| 159 | + y0 = coord_cell[0][1] |
| 160 | + |
| 161 | + # X-direction: cell (nx,1,1), corner 1 |
| 162 | + x1 = coord_cell[1][3] |
| 163 | + y1 = coord_cell[1][4] |
| 164 | + |
| 165 | + # Y-direction: cell (1,ny,1), corner 2 |
| 166 | + x2 = coord_cell[2][6] |
| 167 | + y2 = coord_cell[2][7] |
| 168 | + else: |
| 169 | + # Origin: cell (1,ny,1), corner 2 |
| 170 | + x0 = coord_cell[0][6] |
| 171 | + y0 = coord_cell[0][7] |
| 172 | + |
| 173 | + # X-direction: cell (nx,ny,1), corner 3 |
| 174 | + x1 = coord_cell[1][9] |
| 175 | + y1 = coord_cell[1][10] |
| 176 | + |
| 177 | + # Y-direction: cell (1,1,1), corner 0 |
| 178 | + x2 = coord_cell[2][0] |
| 179 | + y2 = coord_cell[2][1] |
| 180 | + |
| 181 | + deltax1 = x1 - x0 |
| 182 | + deltay1 = y1 - y0 |
| 183 | + |
| 184 | + deltax2 = x2 - x0 |
| 185 | + deltay2 = y2 - y0 |
| 186 | + |
| 187 | + xlength = math.sqrt(deltax1**2 + deltay1**2) |
| 188 | + ylength = math.sqrt(deltax2**2 + deltay2**2) |
| 189 | + xinc = xlength / nx |
| 190 | + yinc = ylength / ny |
| 191 | + |
| 192 | + if math.fabs(deltax1) < 0.00001: |
| 193 | + angle = 90.0 if deltay1 > 0 else -90.0 |
| 194 | + elif deltax1 > 0: |
| 195 | + angle = math.atan(deltay1 / deltax1) * 180.0 / math.pi |
| 196 | + elif deltax1 < 0: |
| 197 | + angle = (math.atan(deltay1 / deltax1) + math.pi) * 180.0 / math.pi |
| 198 | + |
| 199 | + return ErtboxParameters( |
| 200 | + nx=nx, |
| 201 | + ny=ny, |
| 202 | + nz=nz, |
| 203 | + xlength=xlength, |
| 204 | + ylength=ylength, |
| 205 | + xinc=xinc, |
| 206 | + yinc=yinc, |
| 207 | + rotation_angle=angle, |
| 208 | + origin=(x0, y0), |
| 209 | + ) |
| 210 | + |
| 211 | + |
99 | 212 | def read_field( |
100 | 213 | field_path: _PathLike, |
101 | 214 | field_name: str, |
|
0 commit comments