xtgeo 4.14.1__cp313-cp313-win_amd64.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- cxtgeo.py +558 -0
- cxtgeoPYTHON_wrap.c +19537 -0
- xtgeo/__init__.py +248 -0
- xtgeo/_cxtgeo.cp313-win_amd64.pyd +0 -0
- xtgeo/_internal.cp313-win_amd64.pyd +0 -0
- xtgeo/common/__init__.py +19 -0
- xtgeo/common/_angles.py +29 -0
- xtgeo/common/_xyz_enum.py +50 -0
- xtgeo/common/calc.py +396 -0
- xtgeo/common/constants.py +30 -0
- xtgeo/common/exceptions.py +42 -0
- xtgeo/common/log.py +93 -0
- xtgeo/common/sys.py +166 -0
- xtgeo/common/types.py +18 -0
- xtgeo/common/version.py +34 -0
- xtgeo/common/xtgeo_dialog.py +604 -0
- xtgeo/cube/__init__.py +9 -0
- xtgeo/cube/_cube_export.py +214 -0
- xtgeo/cube/_cube_import.py +532 -0
- xtgeo/cube/_cube_roxapi.py +180 -0
- xtgeo/cube/_cube_utils.py +287 -0
- xtgeo/cube/_cube_window_attributes.py +273 -0
- xtgeo/cube/cube1.py +1023 -0
- xtgeo/grid3d/__init__.py +15 -0
- xtgeo/grid3d/_ecl_grid.py +778 -0
- xtgeo/grid3d/_ecl_inte_head.py +152 -0
- xtgeo/grid3d/_ecl_logi_head.py +71 -0
- xtgeo/grid3d/_ecl_output_file.py +81 -0
- xtgeo/grid3d/_egrid.py +1004 -0
- xtgeo/grid3d/_find_gridprop_in_eclrun.py +625 -0
- xtgeo/grid3d/_grdecl_format.py +309 -0
- xtgeo/grid3d/_grdecl_grid.py +400 -0
- xtgeo/grid3d/_grid3d.py +29 -0
- xtgeo/grid3d/_grid3d_fence.py +284 -0
- xtgeo/grid3d/_grid3d_utils.py +228 -0
- xtgeo/grid3d/_grid_boundary.py +76 -0
- xtgeo/grid3d/_grid_etc1.py +1683 -0
- xtgeo/grid3d/_grid_export.py +222 -0
- xtgeo/grid3d/_grid_hybrid.py +50 -0
- xtgeo/grid3d/_grid_import.py +79 -0
- xtgeo/grid3d/_grid_import_ecl.py +101 -0
- xtgeo/grid3d/_grid_import_roff.py +135 -0
- xtgeo/grid3d/_grid_import_xtgcpgeom.py +375 -0
- xtgeo/grid3d/_grid_refine.py +258 -0
- xtgeo/grid3d/_grid_roxapi.py +292 -0
- xtgeo/grid3d/_grid_translate_coords.py +154 -0
- xtgeo/grid3d/_grid_wellzone.py +165 -0
- xtgeo/grid3d/_gridprop_export.py +202 -0
- xtgeo/grid3d/_gridprop_import_eclrun.py +164 -0
- xtgeo/grid3d/_gridprop_import_grdecl.py +132 -0
- xtgeo/grid3d/_gridprop_import_roff.py +52 -0
- xtgeo/grid3d/_gridprop_import_xtgcpprop.py +168 -0
- xtgeo/grid3d/_gridprop_lowlevel.py +171 -0
- xtgeo/grid3d/_gridprop_op1.py +272 -0
- xtgeo/grid3d/_gridprop_roxapi.py +301 -0
- xtgeo/grid3d/_gridprop_value_init.py +140 -0
- xtgeo/grid3d/_gridprops_import_eclrun.py +344 -0
- xtgeo/grid3d/_gridprops_import_roff.py +83 -0
- xtgeo/grid3d/_roff_grid.py +470 -0
- xtgeo/grid3d/_roff_parameter.py +303 -0
- xtgeo/grid3d/grid.py +3010 -0
- xtgeo/grid3d/grid_properties.py +699 -0
- xtgeo/grid3d/grid_property.py +1313 -0
- xtgeo/grid3d/types.py +15 -0
- xtgeo/interfaces/rms/__init__.py +18 -0
- xtgeo/interfaces/rms/_regular_surface.py +460 -0
- xtgeo/interfaces/rms/_rms_base.py +100 -0
- xtgeo/interfaces/rms/_rmsapi_package.py +69 -0
- xtgeo/interfaces/rms/rmsapi_utils.py +438 -0
- xtgeo/io/__init__.py +1 -0
- xtgeo/io/_file.py +603 -0
- xtgeo/metadata/__init__.py +17 -0
- xtgeo/metadata/metadata.py +435 -0
- xtgeo/roxutils/__init__.py +7 -0
- xtgeo/roxutils/_roxar_loader.py +54 -0
- xtgeo/roxutils/_roxutils_etc.py +122 -0
- xtgeo/roxutils/roxutils.py +207 -0
- xtgeo/surface/__init__.py +20 -0
- xtgeo/surface/_regsurf_boundary.py +26 -0
- xtgeo/surface/_regsurf_cube.py +210 -0
- xtgeo/surface/_regsurf_cube_window.py +391 -0
- xtgeo/surface/_regsurf_cube_window_v2.py +297 -0
- xtgeo/surface/_regsurf_cube_window_v3.py +360 -0
- xtgeo/surface/_regsurf_export.py +388 -0
- xtgeo/surface/_regsurf_grid3d.py +275 -0
- xtgeo/surface/_regsurf_gridding.py +347 -0
- xtgeo/surface/_regsurf_ijxyz_parser.py +278 -0
- xtgeo/surface/_regsurf_import.py +347 -0
- xtgeo/surface/_regsurf_lowlevel.py +122 -0
- xtgeo/surface/_regsurf_oper.py +538 -0
- xtgeo/surface/_regsurf_utils.py +81 -0
- xtgeo/surface/_surfs_import.py +43 -0
- xtgeo/surface/_zmap_parser.py +138 -0
- xtgeo/surface/regular_surface.py +3043 -0
- xtgeo/surface/surfaces.py +276 -0
- xtgeo/well/__init__.py +24 -0
- xtgeo/well/_blockedwell_roxapi.py +241 -0
- xtgeo/well/_blockedwells_roxapi.py +68 -0
- xtgeo/well/_well_aux.py +30 -0
- xtgeo/well/_well_io.py +327 -0
- xtgeo/well/_well_oper.py +483 -0
- xtgeo/well/_well_roxapi.py +304 -0
- xtgeo/well/_wellmarkers.py +486 -0
- xtgeo/well/_wells_utils.py +158 -0
- xtgeo/well/blocked_well.py +220 -0
- xtgeo/well/blocked_wells.py +134 -0
- xtgeo/well/well1.py +1516 -0
- xtgeo/well/wells.py +211 -0
- xtgeo/xyz/__init__.py +6 -0
- xtgeo/xyz/_polygons_oper.py +272 -0
- xtgeo/xyz/_xyz.py +758 -0
- xtgeo/xyz/_xyz_data.py +646 -0
- xtgeo/xyz/_xyz_io.py +737 -0
- xtgeo/xyz/_xyz_lowlevel.py +42 -0
- xtgeo/xyz/_xyz_oper.py +613 -0
- xtgeo/xyz/_xyz_roxapi.py +766 -0
- xtgeo/xyz/points.py +698 -0
- xtgeo/xyz/polygons.py +827 -0
- xtgeo-4.14.1.dist-info/METADATA +146 -0
- xtgeo-4.14.1.dist-info/RECORD +122 -0
- xtgeo-4.14.1.dist-info/WHEEL +5 -0
- xtgeo-4.14.1.dist-info/licenses/LICENSE.md +165 -0
|
@@ -0,0 +1,388 @@
|
|
|
1
|
+
"""Export RegularSurface data."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import io
|
|
6
|
+
import json
|
|
7
|
+
import struct
|
|
8
|
+
from typing import TYPE_CHECKING
|
|
9
|
+
|
|
10
|
+
import h5py
|
|
11
|
+
import hdf5plugin
|
|
12
|
+
import numpy as np
|
|
13
|
+
|
|
14
|
+
from xtgeo import _cxtgeo
|
|
15
|
+
from xtgeo.common.constants import UNDEF_MAP_IRAPA, UNDEF_MAP_IRAPB
|
|
16
|
+
from xtgeo.common.log import null_logger
|
|
17
|
+
|
|
18
|
+
if TYPE_CHECKING:
|
|
19
|
+
from xtgeo.io._file import FileWrapper
|
|
20
|
+
from xtgeo.surface.regular_surface import RegularSurface
|
|
21
|
+
|
|
22
|
+
logger = null_logger(__name__)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
DEBUG = 0
|
|
26
|
+
|
|
27
|
+
if DEBUG < 0:
|
|
28
|
+
DEBUG = 0
|
|
29
|
+
|
|
30
|
+
PMD_DATAUNITDISTANCE = {
|
|
31
|
+
15: "meter",
|
|
32
|
+
16: "km",
|
|
33
|
+
17: "feet",
|
|
34
|
+
18: "yard",
|
|
35
|
+
19: "mile",
|
|
36
|
+
221: "global",
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
PMD_DATAUNITZ = {
|
|
40
|
+
10: "10",
|
|
41
|
+
31: "31",
|
|
42
|
+
44: "44",
|
|
43
|
+
300: "300",
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def export_irap_ascii(self: RegularSurface, mfile: FileWrapper) -> None:
|
|
48
|
+
"""Export to Irap RMS ascii format."""
|
|
49
|
+
|
|
50
|
+
vals = self.get_values1d(fill_value=UNDEF_MAP_IRAPA, order="F")
|
|
51
|
+
|
|
52
|
+
yinc = self.yinc * self.yflip
|
|
53
|
+
|
|
54
|
+
xmax = self.xori + (self.ncol - 1) * self.xinc
|
|
55
|
+
ymax = self.yori + (self.nrow - 1) * yinc
|
|
56
|
+
|
|
57
|
+
header = (
|
|
58
|
+
f"-996 {self.nrow} {self.xinc} {yinc}\n"
|
|
59
|
+
f"{self.xori} {xmax} {self.yori} {ymax}\n"
|
|
60
|
+
f"{self.ncol} {self.rotation} {self.xori} {self.yori}\n"
|
|
61
|
+
"0 0 0 0 0 0 0\n"
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
def _optimal_shape(vals, start=9):
|
|
65
|
+
"""Optimal shape for the data.
|
|
66
|
+
|
|
67
|
+
It seems by reverse engineering that RMS accepts only 9 or less items per line
|
|
68
|
+
"""
|
|
69
|
+
# Check if divisible by a number
|
|
70
|
+
size = len(vals)
|
|
71
|
+
# Find the nearest factorization
|
|
72
|
+
for i in range(start, 1, -1):
|
|
73
|
+
if size % i == 0:
|
|
74
|
+
return (int(size // i), int(i)) # Ensure integers are returned
|
|
75
|
+
|
|
76
|
+
# If we can't find a perfect divisor, return a valid shape
|
|
77
|
+
return (int(size), 1)
|
|
78
|
+
|
|
79
|
+
buffer = io.StringIO()
|
|
80
|
+
|
|
81
|
+
np.savetxt(buffer, vals.reshape(_optimal_shape(vals)), fmt="%f", delimiter=" ")
|
|
82
|
+
data = buffer.getvalue()
|
|
83
|
+
|
|
84
|
+
# Combine header and data
|
|
85
|
+
buf = (header + data).encode("latin1")
|
|
86
|
+
|
|
87
|
+
if mfile.memstream:
|
|
88
|
+
mfile.file.write(buf)
|
|
89
|
+
else:
|
|
90
|
+
with open(mfile.name, "wb") as fout:
|
|
91
|
+
fout.write(buf)
|
|
92
|
+
|
|
93
|
+
del vals
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
def export_irap_binary(self: RegularSurface, mfile: FileWrapper) -> None:
|
|
97
|
+
"""Export to Irap RMS binary format."""
|
|
98
|
+
|
|
99
|
+
vals = self.get_values1d(fill_value=UNDEF_MAP_IRAPB, order="F")
|
|
100
|
+
|
|
101
|
+
yinc = self.yinc * self.yflip
|
|
102
|
+
|
|
103
|
+
header = struct.pack(
|
|
104
|
+
">3i6f3i3f10i", # > means big endian storage
|
|
105
|
+
32,
|
|
106
|
+
-996,
|
|
107
|
+
self.nrow,
|
|
108
|
+
self.xori,
|
|
109
|
+
self.xori + self.xinc * (self.ncol - 1),
|
|
110
|
+
self.yori,
|
|
111
|
+
self.yori + yinc * (self.nrow - 1),
|
|
112
|
+
self.xinc,
|
|
113
|
+
yinc,
|
|
114
|
+
32,
|
|
115
|
+
16,
|
|
116
|
+
self.ncol,
|
|
117
|
+
self.rotation,
|
|
118
|
+
self.xori,
|
|
119
|
+
self.yori,
|
|
120
|
+
16,
|
|
121
|
+
28,
|
|
122
|
+
0,
|
|
123
|
+
0,
|
|
124
|
+
0,
|
|
125
|
+
0,
|
|
126
|
+
0,
|
|
127
|
+
0,
|
|
128
|
+
0,
|
|
129
|
+
28,
|
|
130
|
+
)
|
|
131
|
+
inum = self.nrow * self.ncol
|
|
132
|
+
|
|
133
|
+
# export to Irap binary in ncol chunks (the only chunk size accepted by RMS)
|
|
134
|
+
nchunk = self.ncol
|
|
135
|
+
chunks = [nchunk] * (inum // nchunk)
|
|
136
|
+
if (inum % nchunk) > 0:
|
|
137
|
+
chunks.append(inum % nchunk)
|
|
138
|
+
start = 0
|
|
139
|
+
data = bytearray(header)
|
|
140
|
+
chunk_size = nchunk * 4
|
|
141
|
+
|
|
142
|
+
# Precompute the struct.pack format for chunk size
|
|
143
|
+
chunk_size_pack = struct.pack(">i", chunk_size)
|
|
144
|
+
|
|
145
|
+
for chunk in chunks:
|
|
146
|
+
chunk_data = np.array(vals[start : start + chunk], dtype=">f4").tobytes()
|
|
147
|
+
data.extend(chunk_size_pack)
|
|
148
|
+
data.extend(chunk_data)
|
|
149
|
+
data.extend(chunk_size_pack)
|
|
150
|
+
start += chunk
|
|
151
|
+
|
|
152
|
+
if mfile.memstream:
|
|
153
|
+
mfile.file.write(data)
|
|
154
|
+
else:
|
|
155
|
+
with open(mfile.name, "wb") as fout:
|
|
156
|
+
fout.write(data)
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
def export_ijxyz_ascii(self: RegularSurface, mfile: FileWrapper) -> None:
|
|
160
|
+
"""Export to DSG IJXYZ ascii format."""
|
|
161
|
+
|
|
162
|
+
dfr = self.get_dataframe(ij=True, order="F") # order F since this was in cxtgeo
|
|
163
|
+
ix = dfr.IX - 1 # since dataframe indexing starts at 1
|
|
164
|
+
jy = dfr.JY - 1
|
|
165
|
+
inlines = self.ilines[ix]
|
|
166
|
+
xlines = self.xlines[jy]
|
|
167
|
+
dfr["IL"] = inlines
|
|
168
|
+
dfr["XL"] = xlines
|
|
169
|
+
dfr = dfr[["IL", "XL", "X_UTME", "Y_UTMN", "VALUES"]]
|
|
170
|
+
|
|
171
|
+
fmt = "%f"
|
|
172
|
+
|
|
173
|
+
if mfile.memstream:
|
|
174
|
+
buf = dfr.to_csv(sep="\t", float_format=fmt, index=False, header=False)
|
|
175
|
+
buf = buf.encode("latin1")
|
|
176
|
+
mfile.file.write(buf)
|
|
177
|
+
else:
|
|
178
|
+
dfr.to_csv(mfile.name, sep="\t", float_format=fmt, index=False, header=False)
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
def export_zmap_ascii(self: RegularSurface, mfile: FileWrapper) -> None:
|
|
182
|
+
"""Export to ZMAP ascii format (non-rotated)."""
|
|
183
|
+
|
|
184
|
+
# zmap can only deal with non-rotated formats; hence make a copy
|
|
185
|
+
# of the instance and derotate that prior to export, so that the
|
|
186
|
+
# original instance is unchanged
|
|
187
|
+
|
|
188
|
+
scopy = self.copy()
|
|
189
|
+
|
|
190
|
+
undef = -99999.0
|
|
191
|
+
if abs(scopy.rotation) > 1.0e-20:
|
|
192
|
+
scopy.unrotate()
|
|
193
|
+
|
|
194
|
+
yinc = scopy._yinc * scopy._yflip
|
|
195
|
+
|
|
196
|
+
vals = scopy.get_values1d(order="C", asmasked=False, fill_value=undef)
|
|
197
|
+
|
|
198
|
+
xmax = scopy.xori + (scopy.ncol - 1) * scopy.xinc
|
|
199
|
+
ymax = scopy.yori + (scopy.nrow - 1) * yinc
|
|
200
|
+
|
|
201
|
+
fcode = 8
|
|
202
|
+
if scopy.values.min() > -10 and scopy.values.max() < 10:
|
|
203
|
+
fcode = 4
|
|
204
|
+
|
|
205
|
+
nfrow = scopy.nrow if scopy.nrow < 5 else 5
|
|
206
|
+
|
|
207
|
+
buf = "! Export from XTGeo (python engine)\n"
|
|
208
|
+
buf += f"@ GRIDFILE, GRID, {nfrow}\n"
|
|
209
|
+
buf += f"20, {undef}, , {fcode}, 1\n"
|
|
210
|
+
buf += f"{scopy.nrow}, {scopy.ncol}, {scopy.xori}, {xmax}, {scopy.yori}, {ymax}\n"
|
|
211
|
+
|
|
212
|
+
buf += "0.0, 0.0, 0.0\n"
|
|
213
|
+
buf += "@\n"
|
|
214
|
+
|
|
215
|
+
vals = vals.tolist()
|
|
216
|
+
ncol = 0
|
|
217
|
+
for icol in range(scopy.ncol):
|
|
218
|
+
for jrow in range(scopy.nrow - 1, -1, -1):
|
|
219
|
+
ic = icol * scopy.nrow + jrow
|
|
220
|
+
buf += f" {vals[ic]:19.{fcode}f}"
|
|
221
|
+
ncol += 1
|
|
222
|
+
if ncol == 5 or jrow == 0:
|
|
223
|
+
buf += "\n"
|
|
224
|
+
ncol = 0
|
|
225
|
+
|
|
226
|
+
# convert buffer to ascii
|
|
227
|
+
buf = buf.encode("latin1")
|
|
228
|
+
|
|
229
|
+
if mfile.memstream:
|
|
230
|
+
mfile.file.write(buf)
|
|
231
|
+
else:
|
|
232
|
+
with open(mfile.name, "wb") as fout:
|
|
233
|
+
fout.write(buf)
|
|
234
|
+
|
|
235
|
+
del vals
|
|
236
|
+
|
|
237
|
+
|
|
238
|
+
def export_storm_binary(self: RegularSurface, mfile: FileWrapper) -> None:
|
|
239
|
+
"""Export to Storm binary format (non-rotated)."""
|
|
240
|
+
|
|
241
|
+
# storm can only deal with non-rotated formats; hence make a copy
|
|
242
|
+
# of the instance and derotate that prior to export, so that the
|
|
243
|
+
# original instance is unchanged
|
|
244
|
+
|
|
245
|
+
scopy = self.copy()
|
|
246
|
+
|
|
247
|
+
if abs(scopy.rotation) > 1.0e-20:
|
|
248
|
+
scopy.unrotate()
|
|
249
|
+
|
|
250
|
+
zmin = scopy.values.min()
|
|
251
|
+
zmax = scopy.values.max()
|
|
252
|
+
|
|
253
|
+
yinc = scopy._yinc * scopy._yflip
|
|
254
|
+
|
|
255
|
+
ier = _cxtgeo.surf_export_storm_bin(
|
|
256
|
+
mfile.get_cfhandle(),
|
|
257
|
+
scopy._ncol,
|
|
258
|
+
scopy._nrow,
|
|
259
|
+
scopy._xori,
|
|
260
|
+
scopy._yori,
|
|
261
|
+
scopy._xinc,
|
|
262
|
+
yinc,
|
|
263
|
+
scopy.get_values1d(order="F", asmasked=False, fill_value=self.undef).astype(
|
|
264
|
+
np.float64
|
|
265
|
+
),
|
|
266
|
+
zmin,
|
|
267
|
+
zmax,
|
|
268
|
+
0,
|
|
269
|
+
)
|
|
270
|
+
if ier != 0:
|
|
271
|
+
raise RuntimeError(f"Export to Storm binary went wrong, code is {ier}")
|
|
272
|
+
del scopy
|
|
273
|
+
|
|
274
|
+
mfile.cfclose()
|
|
275
|
+
|
|
276
|
+
|
|
277
|
+
def export_petromod_binary(
|
|
278
|
+
self: RegularSurface, mfile: FileWrapper, pmd_dataunits: tuple[int, int]
|
|
279
|
+
):
|
|
280
|
+
"""Export to petromod binary format."""
|
|
281
|
+
validunits = False
|
|
282
|
+
unitd = 15
|
|
283
|
+
unitz = 10
|
|
284
|
+
if isinstance(pmd_dataunits, tuple) and len(pmd_dataunits) == 2:
|
|
285
|
+
unitd, unitz = pmd_dataunits
|
|
286
|
+
if isinstance(unitd, int) and isinstance(unitz, int):
|
|
287
|
+
if unitd in PMD_DATAUNITDISTANCE and unitz in PMD_DATAUNITZ:
|
|
288
|
+
validunits = True
|
|
289
|
+
|
|
290
|
+
if unitd <= 0 or unitz <= 0:
|
|
291
|
+
raise ValueError("Values for pmd_dataunits cannot be negative!")
|
|
292
|
+
|
|
293
|
+
if not validunits:
|
|
294
|
+
UserWarning(
|
|
295
|
+
"Format or values for pmd_dataunits out of range: Pair should be in ranges "
|
|
296
|
+
f"{PMD_DATAUNITDISTANCE} and {PMD_DATAUNITZ}"
|
|
297
|
+
)
|
|
298
|
+
|
|
299
|
+
undef = 99999
|
|
300
|
+
|
|
301
|
+
dsc = "Content=Map,"
|
|
302
|
+
dsc += f"DataUnitDistance={unitd},"
|
|
303
|
+
dsc += f"DataUnitZ={unitz},"
|
|
304
|
+
dsc += f"GridNoX={self.ncol},"
|
|
305
|
+
dsc += f"GridNoY={self.nrow},"
|
|
306
|
+
dsc += f"GridStepX={self.xinc},"
|
|
307
|
+
dsc += f"GridStepY={self.yinc},"
|
|
308
|
+
dsc += "MapType=GridMap,"
|
|
309
|
+
dsc += f"OriginX={self.xori},"
|
|
310
|
+
dsc += f"OriginY={self.yori},"
|
|
311
|
+
dsc += f"RotationAngle={self.rotation},"
|
|
312
|
+
dsc += f"RotationOriginX={self.xori},"
|
|
313
|
+
dsc += f"RotationOriginY={self.yori},"
|
|
314
|
+
dsc += f"Undefined={undef},"
|
|
315
|
+
dsc += "Version=1.0"
|
|
316
|
+
|
|
317
|
+
values = np.ma.filled(self.values1d, fill_value=undef)
|
|
318
|
+
|
|
319
|
+
_cxtgeo.surf_export_petromod_bin(
|
|
320
|
+
mfile.get_cfhandle(),
|
|
321
|
+
dsc,
|
|
322
|
+
values.astype(np.float64),
|
|
323
|
+
)
|
|
324
|
+
|
|
325
|
+
mfile.cfclose()
|
|
326
|
+
|
|
327
|
+
|
|
328
|
+
def export_xtgregsurf(self, mfile):
|
|
329
|
+
"""Export to experimental xtgregsurf format, python version."""
|
|
330
|
+
logger.info("Export to xtgregsurf format...")
|
|
331
|
+
|
|
332
|
+
self.metadata.required = self
|
|
333
|
+
|
|
334
|
+
vals = self.get_values1d(fill_value=self._undef, order="C").astype(np.float32)
|
|
335
|
+
|
|
336
|
+
prevalues = (1, 1101, 4, self.ncol, self.nrow)
|
|
337
|
+
mystruct = struct.Struct("= i i i q q")
|
|
338
|
+
hdr = mystruct.pack(*prevalues)
|
|
339
|
+
|
|
340
|
+
meta = self.metadata.get_metadata()
|
|
341
|
+
|
|
342
|
+
jmeta = json.dumps(meta).encode()
|
|
343
|
+
|
|
344
|
+
with open(mfile.name, "wb") as fout:
|
|
345
|
+
fout.write(hdr)
|
|
346
|
+
|
|
347
|
+
with open(mfile.name, "ab") as fout:
|
|
348
|
+
vals.tofile(fout)
|
|
349
|
+
|
|
350
|
+
with open(mfile.name, "ab") as fout:
|
|
351
|
+
fout.write("\nXTGMETA.v01\n".encode())
|
|
352
|
+
|
|
353
|
+
with open(mfile.name, "ab") as fout:
|
|
354
|
+
fout.write(jmeta)
|
|
355
|
+
|
|
356
|
+
logger.info("Export to xtgregsurf format... done!")
|
|
357
|
+
|
|
358
|
+
|
|
359
|
+
def export_hdf5_regsurf(self, mfile, compression="lzf", dtype="float32"):
|
|
360
|
+
"""Export to experimental hdf5 format."""
|
|
361
|
+
logger.info("Export to hdf5 format...")
|
|
362
|
+
|
|
363
|
+
self.metadata.required = self
|
|
364
|
+
|
|
365
|
+
meta = self.metadata.get_metadata()
|
|
366
|
+
jmeta = json.dumps(meta).encode()
|
|
367
|
+
|
|
368
|
+
if compression and compression == "blosc":
|
|
369
|
+
compression = hdf5plugin.Blosc(
|
|
370
|
+
cname="blosclz", clevel=9, shuffle=hdf5plugin.Blosc.SHUFFLE
|
|
371
|
+
)
|
|
372
|
+
|
|
373
|
+
if dtype not in ("float32", "float64", np.float32, np.float64):
|
|
374
|
+
raise ValueError("Wrong dtype input, must be 'float32' or 'float64'")
|
|
375
|
+
|
|
376
|
+
with h5py.File(mfile.name, "w") as fh5:
|
|
377
|
+
grp = fh5.create_group("RegularSurface")
|
|
378
|
+
grp.create_dataset(
|
|
379
|
+
"values",
|
|
380
|
+
data=np.ma.filled(self.values, fill_value=self.undef).astype(dtype),
|
|
381
|
+
compression=compression,
|
|
382
|
+
chunks=True,
|
|
383
|
+
)
|
|
384
|
+
grp.attrs["metadata"] = jmeta
|
|
385
|
+
grp.attrs["provider"] = "xtgeo"
|
|
386
|
+
grp.attrs["format-idcode"] = 1101
|
|
387
|
+
|
|
388
|
+
logger.info("Export to hdf5 format... done!")
|
|
@@ -0,0 +1,275 @@
|
|
|
1
|
+
"""Regular surface vs Grid3D"""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from dataclasses import dataclass
|
|
6
|
+
from typing import TYPE_CHECKING, Literal
|
|
7
|
+
|
|
8
|
+
import numpy as np
|
|
9
|
+
|
|
10
|
+
from xtgeo import _cxtgeo, _internal
|
|
11
|
+
from xtgeo.common.log import null_logger
|
|
12
|
+
from xtgeo.grid3d import _gridprop_lowlevel
|
|
13
|
+
from xtgeo.grid3d.grid_property import GridProperty
|
|
14
|
+
|
|
15
|
+
if TYPE_CHECKING:
|
|
16
|
+
from xtgeo.grid3d.grid import Grid
|
|
17
|
+
from xtgeo.surface.regular_surface import RegularSurface
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
# self = RegularSurface instance!
|
|
21
|
+
|
|
22
|
+
logger = null_logger(__name__)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def slice_grid3d(
|
|
26
|
+
self: RegularSurface,
|
|
27
|
+
grid: Grid,
|
|
28
|
+
prop: GridProperty,
|
|
29
|
+
zsurf: RegularSurface | None = None,
|
|
30
|
+
sbuffer: int = 1,
|
|
31
|
+
):
|
|
32
|
+
"""Private function for the Grid3D slicing."""
|
|
33
|
+
|
|
34
|
+
grid._set_xtgformat1()
|
|
35
|
+
if zsurf is not None:
|
|
36
|
+
other = zsurf
|
|
37
|
+
else:
|
|
38
|
+
logger.info('The current surface is copied as "other"')
|
|
39
|
+
other = self.copy()
|
|
40
|
+
if not self.compare_topology(other, strict=False):
|
|
41
|
+
raise RuntimeError("Topology of maps differ. Stop!")
|
|
42
|
+
|
|
43
|
+
zslice = other.copy()
|
|
44
|
+
|
|
45
|
+
nsurf = self.ncol * self.nrow
|
|
46
|
+
|
|
47
|
+
p_prop = _gridprop_lowlevel.update_carray(prop, discrete=False)
|
|
48
|
+
|
|
49
|
+
istat, updatedval = _cxtgeo.surf_slice_grd3d(
|
|
50
|
+
self.ncol,
|
|
51
|
+
self.nrow,
|
|
52
|
+
self.xori,
|
|
53
|
+
self.xinc,
|
|
54
|
+
self.yori,
|
|
55
|
+
self.yinc,
|
|
56
|
+
self.rotation,
|
|
57
|
+
self.yflip,
|
|
58
|
+
zslice.get_values1d(),
|
|
59
|
+
nsurf,
|
|
60
|
+
grid.ncol,
|
|
61
|
+
grid.nrow,
|
|
62
|
+
grid.nlay,
|
|
63
|
+
grid._coordsv,
|
|
64
|
+
grid._zcornsv,
|
|
65
|
+
grid._actnumsv,
|
|
66
|
+
p_prop,
|
|
67
|
+
sbuffer,
|
|
68
|
+
)
|
|
69
|
+
|
|
70
|
+
if istat != 0:
|
|
71
|
+
logger.warning("Problem, ISTAT = %s", istat)
|
|
72
|
+
|
|
73
|
+
self.set_values1d(updatedval)
|
|
74
|
+
|
|
75
|
+
return istat
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
@dataclass
|
|
79
|
+
class DeriveSurfaceFromGrid3D:
|
|
80
|
+
"""Private class; derive a surface from a 3D grid / gridproperty."""
|
|
81
|
+
|
|
82
|
+
grid: Grid
|
|
83
|
+
template: RegularSurface | str | None = None
|
|
84
|
+
where: str | int = "top"
|
|
85
|
+
property: str | GridProperty = "depth"
|
|
86
|
+
rfactor: float = 1.0
|
|
87
|
+
index_position: Literal["center", "top", "base", "bot"] = "center"
|
|
88
|
+
|
|
89
|
+
# private state variables
|
|
90
|
+
_args: dict | None = None
|
|
91
|
+
_tempsurf: RegularSurface | None = None
|
|
92
|
+
_klayer: int | None = None
|
|
93
|
+
_option: Literal["top", "bot"] = "top"
|
|
94
|
+
_result: np.ma.MaskedArray | list[np.ndarray] | None = None
|
|
95
|
+
|
|
96
|
+
def __post_init__(self) -> None:
|
|
97
|
+
logger.debug("DeriveSurfaceFromGrid3D: __post_init__")
|
|
98
|
+
|
|
99
|
+
self._parametrize_regsurf()
|
|
100
|
+
self._generate_working_surface()
|
|
101
|
+
self._eval_where()
|
|
102
|
+
|
|
103
|
+
xtgformat_convert = self.grid._xtgformat == 1
|
|
104
|
+
if xtgformat_convert:
|
|
105
|
+
self.grid._set_xtgformat2()
|
|
106
|
+
|
|
107
|
+
self._sample_regsurf_from_grid3d()
|
|
108
|
+
|
|
109
|
+
# convert back to original xtgformat due to unforseen consequences
|
|
110
|
+
if xtgformat_convert:
|
|
111
|
+
self.grid._set_xtgformat1()
|
|
112
|
+
|
|
113
|
+
def result(self) -> dict:
|
|
114
|
+
args = {}
|
|
115
|
+
args["xori"] = self._tempsurf.xori
|
|
116
|
+
args["yori"] = self._tempsurf.yori
|
|
117
|
+
args["xinc"] = self._tempsurf.xinc
|
|
118
|
+
args["yinc"] = self._tempsurf.yinc
|
|
119
|
+
args["ncol"] = self._tempsurf.ncol
|
|
120
|
+
args["nrow"] = self._tempsurf.nrow
|
|
121
|
+
args["rotation"] = self._tempsurf.rotation
|
|
122
|
+
args["yflip"] = self._tempsurf.yflip
|
|
123
|
+
args["values"] = self._result
|
|
124
|
+
|
|
125
|
+
return args
|
|
126
|
+
|
|
127
|
+
def _parametrize_regsurf(self) -> None:
|
|
128
|
+
"""Parametrize setting for the regular surface based on template and grid."""
|
|
129
|
+
args = {}
|
|
130
|
+
if self.template is None:
|
|
131
|
+
# need to estimate map settings from the existing 3D grid
|
|
132
|
+
geom = self.grid.get_geometrics(
|
|
133
|
+
allcells=True, cellcenter=True, return_dict=True, _ver=2
|
|
134
|
+
)
|
|
135
|
+
gminx, gminy, _, gmaxx, gmaxy, _ = self.grid.get_bounding_box()
|
|
136
|
+
|
|
137
|
+
xlen = gmaxx - gminx
|
|
138
|
+
ylen = gmaxy - gminy
|
|
139
|
+
xinc = yinc = (1.0 / self.rfactor) * 0.5 * (geom["avg_dx"] + geom["avg_dy"])
|
|
140
|
+
xori = gminx - xinc
|
|
141
|
+
yori = gminy - xinc
|
|
142
|
+
|
|
143
|
+
xlen += 2 * xinc
|
|
144
|
+
ylen += 2 * yinc
|
|
145
|
+
|
|
146
|
+
# take same xinc and yinc
|
|
147
|
+
ncol = int(xlen / xinc) + 1
|
|
148
|
+
nrow = int(ylen / yinc) + 1
|
|
149
|
+
|
|
150
|
+
args["xori"] = xori
|
|
151
|
+
args["yori"] = yori
|
|
152
|
+
args["xinc"] = xinc
|
|
153
|
+
args["yinc"] = yinc
|
|
154
|
+
args["ncol"] = ncol
|
|
155
|
+
args["nrow"] = nrow
|
|
156
|
+
args["values"] = np.ma.zeros((ncol, nrow), dtype=np.float64)
|
|
157
|
+
elif isinstance(self.template, str) and self.template == "native":
|
|
158
|
+
geom = self.grid.get_geometrics(
|
|
159
|
+
allcells=True, cellcenter=True, return_dict=True
|
|
160
|
+
)
|
|
161
|
+
|
|
162
|
+
args["ncol"] = self.grid.ncol
|
|
163
|
+
args["nrow"] = self.grid.nrow
|
|
164
|
+
args["xori"] = geom["xori"]
|
|
165
|
+
args["yori"] = geom["yori"]
|
|
166
|
+
args["xinc"] = geom["avg_dx"]
|
|
167
|
+
args["yinc"] = geom["avg_dy"]
|
|
168
|
+
args["rotation"] = geom["avg_rotation"]
|
|
169
|
+
args["values"] = np.ma.zeros(
|
|
170
|
+
(self.grid.ncol, self.grid.nrow), dtype=np.float64
|
|
171
|
+
)
|
|
172
|
+
args["yflip"] = -1 if self.grid.ijk_handedness == "right" else 1
|
|
173
|
+
|
|
174
|
+
else:
|
|
175
|
+
args["xori"] = self.template.xori
|
|
176
|
+
args["yori"] = self.template.yori
|
|
177
|
+
args["xinc"] = self.template.xinc
|
|
178
|
+
args["yinc"] = self.template.yinc
|
|
179
|
+
args["ncol"] = self.template.ncol
|
|
180
|
+
args["nrow"] = self.template.nrow
|
|
181
|
+
args["rotation"] = self.template.rotation
|
|
182
|
+
args["yflip"] = self.template.yflip
|
|
183
|
+
args["values"] = self.template.values.copy()
|
|
184
|
+
|
|
185
|
+
self._args = args
|
|
186
|
+
|
|
187
|
+
def _generate_working_surface(self) -> None:
|
|
188
|
+
from xtgeo.surface.regular_surface import RegularSurface
|
|
189
|
+
|
|
190
|
+
self._tempsurf = RegularSurface(**self._args)
|
|
191
|
+
# ensure that this is a subsurface left-handed system
|
|
192
|
+
self._tempsurf.make_lefthanded()
|
|
193
|
+
|
|
194
|
+
def _eval_where(self):
|
|
195
|
+
"""Set klayer and option based on ``where`` parameter.
|
|
196
|
+
|
|
197
|
+
Note that klayer is zero-based, while where is one-based.
|
|
198
|
+
"""
|
|
199
|
+
if isinstance(self.where, str):
|
|
200
|
+
where = self.where.lower()
|
|
201
|
+
if where == "top":
|
|
202
|
+
self._klayer = 0
|
|
203
|
+
self._option = "top"
|
|
204
|
+
elif where == "base":
|
|
205
|
+
self._klayer = self.grid.nlay - 1
|
|
206
|
+
self._option = "bot"
|
|
207
|
+
else:
|
|
208
|
+
klayer, what = where.split("_")
|
|
209
|
+
self._klayer = int(klayer) - 1
|
|
210
|
+
if self._klayer < 0 or self._klayer >= self.grid.nlay:
|
|
211
|
+
raise ValueError(f"Klayer out of range in where={where}")
|
|
212
|
+
self._option = "top"
|
|
213
|
+
if what == "base":
|
|
214
|
+
self._option = "bot"
|
|
215
|
+
else:
|
|
216
|
+
self._klayer = self.where - 1
|
|
217
|
+
self._option = "top"
|
|
218
|
+
|
|
219
|
+
def _sample_regsurf_from_grid3d(self) -> None:
|
|
220
|
+
"""Sample the grid3d to get the values for the regular surface."""
|
|
221
|
+
|
|
222
|
+
regsurf_cpp = _internal.regsurf.RegularSurface(self._tempsurf)
|
|
223
|
+
|
|
224
|
+
iindex, jindex, d_top, d_bot, mask = regsurf_cpp.sample_grid3d_layer(
|
|
225
|
+
self.grid._get_grid_cpp(),
|
|
226
|
+
self._klayer,
|
|
227
|
+
{"top": 0, "bot": 1, "base": 1}.get(self.index_position, 2),
|
|
228
|
+
-1, # number of threads for OpenMP; -1 means let the system decide
|
|
229
|
+
)
|
|
230
|
+
|
|
231
|
+
if self.property == "raw":
|
|
232
|
+
self._result = [iindex, jindex, d_top, d_bot, mask]
|
|
233
|
+
return
|
|
234
|
+
|
|
235
|
+
additional_mask = iindex == -1
|
|
236
|
+
iindex = np.where(iindex == -1, 0, iindex)
|
|
237
|
+
jindex = np.where(jindex == -1, 0, jindex)
|
|
238
|
+
|
|
239
|
+
if isinstance(self.property, str):
|
|
240
|
+
if self.property == "depth":
|
|
241
|
+
if self._option == "top":
|
|
242
|
+
d_top = np.ma.masked_invalid(d_top)
|
|
243
|
+
# to avoid problems when nan is present behind the mask
|
|
244
|
+
d_top.data[d_top.mask] = _internal.numerics.UNDEF_DOUBLE
|
|
245
|
+
else:
|
|
246
|
+
d_bot = np.ma.masked_invalid(d_bot)
|
|
247
|
+
d_bot.data[d_bot.mask] = _internal.numerics.UNDEF_DOUBLE
|
|
248
|
+
|
|
249
|
+
self._result = d_top if self._option == "top" else d_bot
|
|
250
|
+
|
|
251
|
+
elif self.property == "i":
|
|
252
|
+
self._result = np.ma.masked_less(iindex, 0)
|
|
253
|
+
self._result.mask += additional_mask
|
|
254
|
+
|
|
255
|
+
elif self.property == "j":
|
|
256
|
+
self._result = np.ma.masked_less(jindex, 0)
|
|
257
|
+
self._result.mask += additional_mask
|
|
258
|
+
else:
|
|
259
|
+
raise ValueError(f"Unknown option: {self.property}")
|
|
260
|
+
|
|
261
|
+
elif isinstance(self.property, GridProperty):
|
|
262
|
+
propmask = iindex < 0
|
|
263
|
+
iindex[iindex < 0] = 0
|
|
264
|
+
jindex[jindex < 0] = 0
|
|
265
|
+
self._result = np.ma.masked_array(np.zeros_like(d_top), mask=propmask)
|
|
266
|
+
self._result = self.property.values[iindex, jindex, self._klayer]
|
|
267
|
+
self._result.mask += additional_mask
|
|
268
|
+
|
|
269
|
+
|
|
270
|
+
def from_grid3d(grid, template, where, property, rfactor, index_position) -> dict:
|
|
271
|
+
"""Private function for deriving a surface from a 3D grid / gridproperty."""
|
|
272
|
+
|
|
273
|
+
return DeriveSurfaceFromGrid3D(
|
|
274
|
+
grid, template, where, property, rfactor, index_position
|
|
275
|
+
).result()
|