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,538 @@
|
|
|
1
|
+
"""Various operations"""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import numbers
|
|
6
|
+
from typing import Any
|
|
7
|
+
|
|
8
|
+
import numpy as np
|
|
9
|
+
import numpy.ma as ma
|
|
10
|
+
|
|
11
|
+
from xtgeo import _cxtgeo
|
|
12
|
+
from xtgeo._cxtgeo import XTGeoCLibError
|
|
13
|
+
from xtgeo.common.constants import UNDEF, UNDEF_LIMIT
|
|
14
|
+
from xtgeo.common.log import null_logger
|
|
15
|
+
from xtgeo.common.xtgeo_dialog import XTGeoDialog
|
|
16
|
+
from xtgeo.xyz.polygons import Polygons
|
|
17
|
+
|
|
18
|
+
xtg = XTGeoDialog()
|
|
19
|
+
|
|
20
|
+
logger = null_logger(__name__)
|
|
21
|
+
|
|
22
|
+
VALID_OPER = (
|
|
23
|
+
"add",
|
|
24
|
+
"iadd",
|
|
25
|
+
"sub",
|
|
26
|
+
"isub",
|
|
27
|
+
"mul",
|
|
28
|
+
"imul",
|
|
29
|
+
"div",
|
|
30
|
+
"idiv",
|
|
31
|
+
"lt",
|
|
32
|
+
"gt",
|
|
33
|
+
"le",
|
|
34
|
+
"eq",
|
|
35
|
+
"ne",
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
VALID_OPER_POLYS = (
|
|
39
|
+
"add",
|
|
40
|
+
"sub",
|
|
41
|
+
"mul",
|
|
42
|
+
"div",
|
|
43
|
+
"set",
|
|
44
|
+
"eli",
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def operations_two(self, other, oper="add"):
|
|
49
|
+
"""General operations between two maps"""
|
|
50
|
+
|
|
51
|
+
other = _check_other(self, other)
|
|
52
|
+
|
|
53
|
+
okstatus = self.compare_topology(other)
|
|
54
|
+
|
|
55
|
+
useother = other
|
|
56
|
+
if not okstatus:
|
|
57
|
+
# to avoid that the "other" instance is changed
|
|
58
|
+
useother = self.copy()
|
|
59
|
+
useother.resample(other)
|
|
60
|
+
|
|
61
|
+
if oper not in VALID_OPER:
|
|
62
|
+
raise ValueError(f"Operation key oper has invalid value: {oper}")
|
|
63
|
+
|
|
64
|
+
retvalue = None
|
|
65
|
+
|
|
66
|
+
if oper == "add":
|
|
67
|
+
self.values = self.values + useother.values
|
|
68
|
+
elif oper == "iadd":
|
|
69
|
+
self.values += useother.values
|
|
70
|
+
elif oper == "sub":
|
|
71
|
+
self.values = self.values - useother.values
|
|
72
|
+
elif oper == "isub":
|
|
73
|
+
self._values -= useother._values
|
|
74
|
+
elif oper == "mul":
|
|
75
|
+
self.values = self.values * useother.values
|
|
76
|
+
elif oper == "imul":
|
|
77
|
+
self._values *= useother._values
|
|
78
|
+
elif oper == "div":
|
|
79
|
+
self.values = self.values / useother.values
|
|
80
|
+
elif oper == "idiv":
|
|
81
|
+
self._values /= useother._values
|
|
82
|
+
|
|
83
|
+
# comparisons:
|
|
84
|
+
elif oper == "lt":
|
|
85
|
+
retvalue = self.values < other.values
|
|
86
|
+
elif oper == "gt":
|
|
87
|
+
retvalue = self.values > other.values
|
|
88
|
+
elif oper == "le":
|
|
89
|
+
retvalue = self.values <= other.values
|
|
90
|
+
elif oper == "ge":
|
|
91
|
+
retvalue = self.values >= other.values
|
|
92
|
+
elif oper == "eq":
|
|
93
|
+
retvalue = self.values == other.values
|
|
94
|
+
elif oper == "ne":
|
|
95
|
+
retvalue = self.values != other.values
|
|
96
|
+
|
|
97
|
+
if useother is not other:
|
|
98
|
+
del useother
|
|
99
|
+
|
|
100
|
+
self._filesrc = "Calculated"
|
|
101
|
+
|
|
102
|
+
# return None or a boolean array
|
|
103
|
+
return retvalue
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
def _check_other(self, other):
|
|
107
|
+
"""Will convert an other scalar to a valid numpy array"""
|
|
108
|
+
|
|
109
|
+
if isinstance(other, numbers.Number):
|
|
110
|
+
vals = other
|
|
111
|
+
other = self.copy()
|
|
112
|
+
other.values *= 0
|
|
113
|
+
other.values += vals
|
|
114
|
+
other._filesrc = None
|
|
115
|
+
|
|
116
|
+
return other
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
def resample(self, other, mask=True, sampling="bilinear"):
|
|
120
|
+
"""Resample from other surface object to this surf."""
|
|
121
|
+
|
|
122
|
+
logger.info("Resampling...")
|
|
123
|
+
|
|
124
|
+
# a special case occur of the maps have same topology, but
|
|
125
|
+
# different masks
|
|
126
|
+
if self.compare_topology(other, strict=False):
|
|
127
|
+
self.values = other.values.copy()
|
|
128
|
+
return
|
|
129
|
+
|
|
130
|
+
svalues = np.ma.filled(self.values, fill_value=UNDEF)
|
|
131
|
+
ovalues = np.ma.filled(other.values, fill_value=UNDEF)
|
|
132
|
+
|
|
133
|
+
_cxtgeo.surf_resample(
|
|
134
|
+
other._ncol,
|
|
135
|
+
other._nrow,
|
|
136
|
+
other._xori,
|
|
137
|
+
other._xinc,
|
|
138
|
+
other._yori,
|
|
139
|
+
other._yinc,
|
|
140
|
+
other._yflip,
|
|
141
|
+
other._rotation,
|
|
142
|
+
ovalues,
|
|
143
|
+
self._ncol,
|
|
144
|
+
self._nrow,
|
|
145
|
+
self._xori,
|
|
146
|
+
self._xinc,
|
|
147
|
+
self._yori,
|
|
148
|
+
self._yinc,
|
|
149
|
+
self._yflip,
|
|
150
|
+
self._rotation,
|
|
151
|
+
svalues,
|
|
152
|
+
0 if not mask else 1,
|
|
153
|
+
2 if sampling == "nearest" else 0,
|
|
154
|
+
)
|
|
155
|
+
|
|
156
|
+
self.values = np.ma.masked_greater(svalues, UNDEF_LIMIT)
|
|
157
|
+
|
|
158
|
+
self.set_values1d(svalues)
|
|
159
|
+
self._filesrc = "Resampled"
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
def distance_from_point(self, point=(0, 0), azimuth=0.0):
|
|
163
|
+
"""Find distance bwteen point and surface."""
|
|
164
|
+
xpv, ypv = point
|
|
165
|
+
|
|
166
|
+
svalues = self.get_values1d()
|
|
167
|
+
|
|
168
|
+
# call C routine
|
|
169
|
+
ier = _cxtgeo.surf_get_dist_values(
|
|
170
|
+
self._xori,
|
|
171
|
+
self._xinc,
|
|
172
|
+
self._yori,
|
|
173
|
+
self._yinc,
|
|
174
|
+
self._ncol,
|
|
175
|
+
self._nrow,
|
|
176
|
+
self._rotation,
|
|
177
|
+
xpv,
|
|
178
|
+
ypv,
|
|
179
|
+
azimuth,
|
|
180
|
+
svalues,
|
|
181
|
+
0,
|
|
182
|
+
)
|
|
183
|
+
|
|
184
|
+
if ier != 0:
|
|
185
|
+
logger.error("Something went wrong...")
|
|
186
|
+
raise RuntimeError(f"Something went wrong in {__name__}")
|
|
187
|
+
|
|
188
|
+
self.set_values1d(svalues)
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
def get_value_from_xy(self, point=(0.0, 0.0), sampling="bilinear"):
|
|
192
|
+
"""Find surface value for point X Y."""
|
|
193
|
+
|
|
194
|
+
xcoord, ycoord = point
|
|
195
|
+
|
|
196
|
+
option = 0 if sampling == "bilinear" else 2
|
|
197
|
+
|
|
198
|
+
zcoord = _cxtgeo.surf_get_z_from_xy(
|
|
199
|
+
float(xcoord),
|
|
200
|
+
float(ycoord),
|
|
201
|
+
self.ncol,
|
|
202
|
+
self.nrow,
|
|
203
|
+
self.xori,
|
|
204
|
+
self.yori,
|
|
205
|
+
self.xinc,
|
|
206
|
+
self.yinc,
|
|
207
|
+
self.yflip,
|
|
208
|
+
self.rotation,
|
|
209
|
+
self.get_values1d(),
|
|
210
|
+
option,
|
|
211
|
+
)
|
|
212
|
+
if zcoord > UNDEF_LIMIT:
|
|
213
|
+
return None
|
|
214
|
+
|
|
215
|
+
return zcoord
|
|
216
|
+
|
|
217
|
+
|
|
218
|
+
def get_xy_value_from_ij(self, iloc, jloc, zvalues=None):
|
|
219
|
+
"""Find X Y value from I J index"""
|
|
220
|
+
|
|
221
|
+
if zvalues is None:
|
|
222
|
+
zvalues = self.get_values1d()
|
|
223
|
+
|
|
224
|
+
try:
|
|
225
|
+
ier, xval, yval, value = _cxtgeo.surf_xyz_from_ij(
|
|
226
|
+
iloc,
|
|
227
|
+
jloc,
|
|
228
|
+
self.xori,
|
|
229
|
+
self.xinc,
|
|
230
|
+
self.yori,
|
|
231
|
+
self.yinc,
|
|
232
|
+
self.ncol,
|
|
233
|
+
self.nrow,
|
|
234
|
+
self._yflip,
|
|
235
|
+
self.rotation,
|
|
236
|
+
zvalues,
|
|
237
|
+
0,
|
|
238
|
+
)
|
|
239
|
+
except XTGeoCLibError:
|
|
240
|
+
raise ValueError(f"Index i {iloc} and/or j {jloc} out of bounds")
|
|
241
|
+
|
|
242
|
+
if value > UNDEF_LIMIT:
|
|
243
|
+
value = None
|
|
244
|
+
|
|
245
|
+
return xval, yval, value
|
|
246
|
+
|
|
247
|
+
|
|
248
|
+
def get_ij_values(self, zero_based=False, order="C", asmasked=False):
|
|
249
|
+
"""Get I J values as numpy 2D arrays.
|
|
250
|
+
|
|
251
|
+
Args:
|
|
252
|
+
zero_based (bool): If True, first index is 0. False (1) is default.
|
|
253
|
+
order (str): 'C' or 'F' order (row vs column major)
|
|
254
|
+
|
|
255
|
+
"""
|
|
256
|
+
|
|
257
|
+
ixn, jyn = np.indices((self._ncol, self._nrow))
|
|
258
|
+
|
|
259
|
+
if order == "F":
|
|
260
|
+
ixn = np.asfortranarray(ixn)
|
|
261
|
+
jyn = np.asfortranarray(jyn)
|
|
262
|
+
|
|
263
|
+
if not zero_based:
|
|
264
|
+
ixn += 1
|
|
265
|
+
jyn += 1
|
|
266
|
+
|
|
267
|
+
if asmasked:
|
|
268
|
+
ixn = ixn[~self.values.mask]
|
|
269
|
+
jyn = jyn[~self.values.mask]
|
|
270
|
+
|
|
271
|
+
return ixn, jyn
|
|
272
|
+
|
|
273
|
+
|
|
274
|
+
def get_ij_values1d(self, zero_based=False, activeonly=True, order="C"):
|
|
275
|
+
"""Get I J values as numpy 1D arrays.
|
|
276
|
+
|
|
277
|
+
Args:
|
|
278
|
+
zero_based (bool): If True, first index is 0. False (1) is default.
|
|
279
|
+
activeonly (bool): If True, only for active nodes
|
|
280
|
+
order (str): 'C' or 'F' order (row vs column major)
|
|
281
|
+
|
|
282
|
+
"""
|
|
283
|
+
|
|
284
|
+
ixn, jyn = self.get_ij_values(zero_based=zero_based, order=order)
|
|
285
|
+
|
|
286
|
+
ixn = ixn.ravel(order=order)
|
|
287
|
+
jyn = jyn.ravel(order=order)
|
|
288
|
+
|
|
289
|
+
if activeonly:
|
|
290
|
+
tmask = ma.getmaskarray(self.get_values1d(order=order, asmasked=True))
|
|
291
|
+
ixn = ma.array(ixn, mask=tmask)
|
|
292
|
+
ixn = ixn[~ixn.mask]
|
|
293
|
+
jyn = ma.array(jyn, mask=tmask)
|
|
294
|
+
jyn = jyn[~jyn.mask]
|
|
295
|
+
|
|
296
|
+
return ixn, jyn
|
|
297
|
+
|
|
298
|
+
|
|
299
|
+
def get_xy_values(self, order="C", asmasked=False):
|
|
300
|
+
"""Get X Y coordinate values as numpy 2D arrays."""
|
|
301
|
+
nno = self.ncol * self.nrow
|
|
302
|
+
|
|
303
|
+
ier, xvals, yvals = _cxtgeo.surf_xy_as_values(
|
|
304
|
+
self.xori,
|
|
305
|
+
self.xinc,
|
|
306
|
+
self.yori,
|
|
307
|
+
self.yinc * self.yflip,
|
|
308
|
+
self.ncol,
|
|
309
|
+
self.nrow,
|
|
310
|
+
self.rotation,
|
|
311
|
+
nno,
|
|
312
|
+
nno,
|
|
313
|
+
0,
|
|
314
|
+
)
|
|
315
|
+
if ier != 0:
|
|
316
|
+
raise XTGeoCLibError(f"Error in surf_xy_as_values, error code: {ier}")
|
|
317
|
+
|
|
318
|
+
# reshape
|
|
319
|
+
xvals = xvals.reshape((self.ncol, self.nrow))
|
|
320
|
+
yvals = yvals.reshape((self.ncol, self.nrow))
|
|
321
|
+
|
|
322
|
+
if order == "F":
|
|
323
|
+
xvals = np.array(xvals, order="F")
|
|
324
|
+
yvals = np.array(yvals, order="F")
|
|
325
|
+
|
|
326
|
+
if asmasked:
|
|
327
|
+
tmpv = ma.filled(self.values, fill_value=np.nan)
|
|
328
|
+
tmpv = np.array(tmpv, order=order)
|
|
329
|
+
tmpv = ma.masked_invalid(tmpv)
|
|
330
|
+
mymask = ma.getmaskarray(tmpv)
|
|
331
|
+
xvals = ma.array(xvals, mask=mymask, order=order)
|
|
332
|
+
yvals = ma.array(yvals, mask=mymask, order=order)
|
|
333
|
+
|
|
334
|
+
return xvals, yvals
|
|
335
|
+
|
|
336
|
+
|
|
337
|
+
def get_xy_values1d(self, order="C", activeonly=True):
|
|
338
|
+
"""Get X Y coordinate values as numpy 1D arrays."""
|
|
339
|
+
|
|
340
|
+
asmasked = False
|
|
341
|
+
if activeonly:
|
|
342
|
+
asmasked = True
|
|
343
|
+
|
|
344
|
+
xvals, yvals = self.get_xy_values(order=order, asmasked=asmasked)
|
|
345
|
+
|
|
346
|
+
xvals = xvals.ravel(order=order)
|
|
347
|
+
yvals = yvals.ravel(order=order)
|
|
348
|
+
|
|
349
|
+
if activeonly:
|
|
350
|
+
xvals = xvals[~xvals.mask]
|
|
351
|
+
yvals = yvals[~yvals.mask]
|
|
352
|
+
|
|
353
|
+
return xvals, yvals
|
|
354
|
+
|
|
355
|
+
|
|
356
|
+
def get_fence(self, xyfence, sampling="bilinear"):
|
|
357
|
+
"""Get surface values along fence."""
|
|
358
|
+
|
|
359
|
+
cxarr = xyfence[:, 0]
|
|
360
|
+
cyarr = xyfence[:, 1]
|
|
361
|
+
czarr = xyfence[:, 2].copy()
|
|
362
|
+
|
|
363
|
+
sampleoptions = {"bilinear": 0, "nearest": 2}
|
|
364
|
+
|
|
365
|
+
# czarr will be updated "inplace":
|
|
366
|
+
istat = _cxtgeo.surf_get_zv_from_xyv(
|
|
367
|
+
cxarr,
|
|
368
|
+
cyarr,
|
|
369
|
+
czarr,
|
|
370
|
+
self.ncol,
|
|
371
|
+
self.nrow,
|
|
372
|
+
self.xori,
|
|
373
|
+
self.yori,
|
|
374
|
+
self.xinc,
|
|
375
|
+
self.yinc,
|
|
376
|
+
self.yflip,
|
|
377
|
+
self.rotation,
|
|
378
|
+
self.get_values1d(),
|
|
379
|
+
sampleoptions.get(sampling, 0),
|
|
380
|
+
)
|
|
381
|
+
|
|
382
|
+
if istat != 0:
|
|
383
|
+
logger.warning("Seem to be rotten")
|
|
384
|
+
|
|
385
|
+
xyfence[:, 2] = czarr
|
|
386
|
+
xyfence = ma.masked_greater(xyfence, UNDEF_LIMIT)
|
|
387
|
+
return ma.mask_rows(xyfence)
|
|
388
|
+
|
|
389
|
+
|
|
390
|
+
def get_randomline(
|
|
391
|
+
self, fencespec, hincrement=None, atleast=5, nextend=2, sampling="bilinear"
|
|
392
|
+
):
|
|
393
|
+
"""Get surface values along fence."""
|
|
394
|
+
|
|
395
|
+
if hincrement is None and isinstance(fencespec, Polygons):
|
|
396
|
+
logger.info("Estimate hincrement from instance...")
|
|
397
|
+
fencespec = _get_randomline_fence(self, fencespec, hincrement, atleast, nextend)
|
|
398
|
+
logger.info("Estimate hincrement from instance... DONE")
|
|
399
|
+
|
|
400
|
+
if fencespec is None or fencespec is False:
|
|
401
|
+
return None
|
|
402
|
+
|
|
403
|
+
sampleoptions = {"bilinear": 0, "nearest": 2}
|
|
404
|
+
|
|
405
|
+
xcoords = fencespec[:, 0]
|
|
406
|
+
ycoords = fencespec[:, 1]
|
|
407
|
+
zcoords = fencespec[:, 2].copy()
|
|
408
|
+
hcoords = fencespec[:, 3]
|
|
409
|
+
|
|
410
|
+
# zcoords will be updated "inplace":
|
|
411
|
+
istat = _cxtgeo.surf_get_zv_from_xyv(
|
|
412
|
+
xcoords,
|
|
413
|
+
ycoords,
|
|
414
|
+
zcoords,
|
|
415
|
+
self.ncol,
|
|
416
|
+
self.nrow,
|
|
417
|
+
self.xori,
|
|
418
|
+
self.yori,
|
|
419
|
+
self.xinc,
|
|
420
|
+
self.yinc,
|
|
421
|
+
self.yflip,
|
|
422
|
+
self.rotation,
|
|
423
|
+
self.get_values1d(),
|
|
424
|
+
sampleoptions.get(sampling, 0),
|
|
425
|
+
)
|
|
426
|
+
|
|
427
|
+
if istat != 0:
|
|
428
|
+
logger.warning("Seem to be rotten")
|
|
429
|
+
|
|
430
|
+
zcoords[zcoords > UNDEF_LIMIT] = np.nan
|
|
431
|
+
return np.vstack([hcoords, zcoords]).T
|
|
432
|
+
|
|
433
|
+
|
|
434
|
+
def _get_randomline_fence(self, fencespec, hincrement, atleast, nextend):
|
|
435
|
+
"""Compute a resampled fence from a Polygons instance"""
|
|
436
|
+
|
|
437
|
+
if hincrement is None:
|
|
438
|
+
avgdxdy = 0.5 * (self.xinc + self.yinc)
|
|
439
|
+
distance = 0.5 * avgdxdy
|
|
440
|
+
else:
|
|
441
|
+
distance = hincrement
|
|
442
|
+
|
|
443
|
+
logger.info("Getting fence from a Polygons instance...")
|
|
444
|
+
fspec = fencespec.get_fence(
|
|
445
|
+
distance=distance, atleast=atleast, nextend=nextend, asnumpy=True
|
|
446
|
+
)
|
|
447
|
+
logger.info("Getting fence from a Polygons instance... DONE")
|
|
448
|
+
return fspec
|
|
449
|
+
|
|
450
|
+
|
|
451
|
+
def _proxy_map_polygons(surf, poly, inside=True):
|
|
452
|
+
"""Return a proxy map where on one to do operations, as 0 and 1."""
|
|
453
|
+
inside_value = 1 if inside else 0
|
|
454
|
+
outside_value = 0 if inside else 1
|
|
455
|
+
|
|
456
|
+
proxy = surf.copy()
|
|
457
|
+
|
|
458
|
+
# allow a single Polygons instance or a list of Polygons instances
|
|
459
|
+
if isinstance(poly, Polygons):
|
|
460
|
+
usepolys = [poly]
|
|
461
|
+
elif isinstance(poly, list) and all(isinstance(pol, Polygons) for pol in poly):
|
|
462
|
+
usepolys = poly
|
|
463
|
+
else:
|
|
464
|
+
raise ValueError("The poly values is not a Polygons or a list of Polygons")
|
|
465
|
+
|
|
466
|
+
proxy.values = outside_value
|
|
467
|
+
xvals, yvals = proxy.get_xy_values(asmasked=False)
|
|
468
|
+
points = np.array([xvals.ravel(), yvals.ravel()]).T
|
|
469
|
+
|
|
470
|
+
import matplotlib.path as mplpath
|
|
471
|
+
|
|
472
|
+
for pol in usepolys:
|
|
473
|
+
idgroups = pol.get_dataframe(copy=False).groupby(pol.pname)
|
|
474
|
+
for _, grp in idgroups:
|
|
475
|
+
singlepoly = np.array([grp[pol.xname].values, grp[pol.yname].values]).T
|
|
476
|
+
poly_path = mplpath.Path(singlepoly)
|
|
477
|
+
is_inside = poly_path.contains_points(points)
|
|
478
|
+
is_inside = is_inside.reshape(proxy.ncol, proxy.nrow)
|
|
479
|
+
proxy.values = np.where(is_inside, inside_value, proxy.values)
|
|
480
|
+
|
|
481
|
+
return proxy
|
|
482
|
+
|
|
483
|
+
|
|
484
|
+
def operation_polygons(self, poly, value: float | Any, opname="add", inside=True):
|
|
485
|
+
"""Operations restricted to polygons, using matplotlib (much faster).
|
|
486
|
+
|
|
487
|
+
The 'value' can be a number or another regular surface (with same design)
|
|
488
|
+
|
|
489
|
+
"""
|
|
490
|
+
|
|
491
|
+
proxy = _proxy_map_polygons(self, poly, inside=inside)
|
|
492
|
+
result = self.copy()
|
|
493
|
+
|
|
494
|
+
if isinstance(value, type(result)):
|
|
495
|
+
if not result.compare_topology(value):
|
|
496
|
+
raise ValueError("Input is RegularSurface, but not same map topology")
|
|
497
|
+
value = value.values.copy()
|
|
498
|
+
else:
|
|
499
|
+
# turn scalar value into numpy array
|
|
500
|
+
value = result.values.copy() * 0 + value
|
|
501
|
+
|
|
502
|
+
if opname == "add":
|
|
503
|
+
result.values = np.ma.where(
|
|
504
|
+
proxy.values == 1, result.values + value, result.values
|
|
505
|
+
)
|
|
506
|
+
elif opname == "sub":
|
|
507
|
+
result.values = np.ma.where(
|
|
508
|
+
proxy.values == 1, result.values - value, result.values
|
|
509
|
+
)
|
|
510
|
+
elif opname == "mul":
|
|
511
|
+
result.values = np.ma.where(
|
|
512
|
+
proxy.values == 1, result.values * value, result.values
|
|
513
|
+
)
|
|
514
|
+
elif opname == "div":
|
|
515
|
+
# Dividing a map of zero is always a hazzle; try to obtain 0.0
|
|
516
|
+
# as result in these cases
|
|
517
|
+
if 0.0 in value:
|
|
518
|
+
xtg.warn(
|
|
519
|
+
"Dividing a surface with value = 0.0 or surface with zero "
|
|
520
|
+
"elements; may get unexpected results, will try to "
|
|
521
|
+
"achieve zero values as result!"
|
|
522
|
+
)
|
|
523
|
+
|
|
524
|
+
result.values = np.ma.where(value == 0.0, 0.0, result.values)
|
|
525
|
+
proxy.values = np.ma.where(value == 0.0, 0, proxy.values)
|
|
526
|
+
result.values = np.ma.where(
|
|
527
|
+
proxy.values == 1, result.values / value, result.values
|
|
528
|
+
)
|
|
529
|
+
|
|
530
|
+
elif opname == "set":
|
|
531
|
+
result.values = np.ma.where(proxy.values == 1, value, result.values)
|
|
532
|
+
elif opname == "eli":
|
|
533
|
+
result.values = np.ma.where(proxy.values == 1, UNDEF, result.values)
|
|
534
|
+
result.values = ma.masked_greater(result.values, UNDEF_LIMIT)
|
|
535
|
+
else:
|
|
536
|
+
raise KeyError(f"The opname={opname} is not one of {VALID_OPER_POLYS}")
|
|
537
|
+
|
|
538
|
+
self.values = result.values
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
"""RegularSurface utilities"""
|
|
2
|
+
|
|
3
|
+
import numpy as np
|
|
4
|
+
|
|
5
|
+
from xtgeo.common.calc import _swap_axes
|
|
6
|
+
from xtgeo.common.constants import UNDEF
|
|
7
|
+
from xtgeo.common.log import null_logger
|
|
8
|
+
|
|
9
|
+
logger = null_logger(__name__)
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def swapaxes(self):
|
|
13
|
+
"""Swap the axes columns vs rows, keep origin. Will change yflip."""
|
|
14
|
+
self._rotation, self._yflip, swapped_values = _swap_axes(
|
|
15
|
+
self._rotation,
|
|
16
|
+
self._yflip,
|
|
17
|
+
values=self.values.filled(UNDEF),
|
|
18
|
+
)
|
|
19
|
+
self._ncol, self._nrow = self._nrow, self._ncol
|
|
20
|
+
self._xinc, self._yinc = self._yinc, self._xinc
|
|
21
|
+
self.values = swapped_values["values"]
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def make_lefthanded(self):
|
|
25
|
+
"""Will rearrange in case of right-handed system, ie. yflip = -1.
|
|
26
|
+
|
|
27
|
+
In contrasts to swapaxes(), this will change the origin.
|
|
28
|
+
"""
|
|
29
|
+
if self.yflip == -1 or self.yinc < 0:
|
|
30
|
+
self._xori, self._yori, _ = self.get_xy_value_from_ij(1, self.nrow)
|
|
31
|
+
self._yflip = 1
|
|
32
|
+
self._yinc = abs(self.yinc)
|
|
33
|
+
|
|
34
|
+
# the values shall be reversed along the last axis
|
|
35
|
+
self._values = self._values[:, ::-1]
|
|
36
|
+
self._xlines = self._xlines[::-1]
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def make_righthanded(self):
|
|
40
|
+
"""Will rearrange in case of left-handed system, ie. yflip = 1.
|
|
41
|
+
|
|
42
|
+
In contrasts to swapaxes(), this will change the origin.
|
|
43
|
+
"""
|
|
44
|
+
if self.yflip == 1 or self.yinc > 0:
|
|
45
|
+
self._xori, self._yori, _ = self.get_xy_value_from_ij(1, self.nrow)
|
|
46
|
+
self._yflip = -1
|
|
47
|
+
self._yinc = -1 * abs(self.yinc)
|
|
48
|
+
|
|
49
|
+
# the values shall be reversed along the last axis
|
|
50
|
+
self._values = self._values[:, ::-1]
|
|
51
|
+
self._xlines = self._xlines[::-1]
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def autocrop(self):
|
|
55
|
+
"""Crop surface by looking at undefined areas, update instance"""
|
|
56
|
+
|
|
57
|
+
minvalue = self.values.min()
|
|
58
|
+
|
|
59
|
+
if np.isnan(minvalue):
|
|
60
|
+
return
|
|
61
|
+
|
|
62
|
+
arrx, arry = np.ma.where(self.values >= minvalue)
|
|
63
|
+
|
|
64
|
+
imin = int(arrx.min())
|
|
65
|
+
imax = int(arrx.max())
|
|
66
|
+
|
|
67
|
+
jmin = int(arry.min())
|
|
68
|
+
jmax = int(arry.max())
|
|
69
|
+
|
|
70
|
+
xori, yori, _dummy = self.get_xy_value_from_ij(imin + 1, jmin + 1)
|
|
71
|
+
|
|
72
|
+
ncol = imax - imin + 1
|
|
73
|
+
nrow = jmax - jmin + 1
|
|
74
|
+
|
|
75
|
+
self._values = self.values[imin : imax + 1, jmin : jmax + 1]
|
|
76
|
+
self._ilines = self.ilines[imin : imax + 1]
|
|
77
|
+
self._xlines = self.xlines[jmin : jmax + 1]
|
|
78
|
+
self._ncol = ncol
|
|
79
|
+
self._nrow = nrow
|
|
80
|
+
self._xori = xori
|
|
81
|
+
self._yori = yori
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
"""Import multiple surfaces"""
|
|
2
|
+
|
|
3
|
+
from xtgeo.common.log import null_logger
|
|
4
|
+
|
|
5
|
+
from .regular_surface import surface_from_grid3d
|
|
6
|
+
|
|
7
|
+
logger = null_logger(__name__)
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def from_grid3d(grid, subgrids, rfactor):
|
|
11
|
+
"""Get surfaces, subtype and order from 3D grid, including subgrids"""
|
|
12
|
+
|
|
13
|
+
logger.info("Extracting surface from 3D grid...")
|
|
14
|
+
|
|
15
|
+
# determine layers
|
|
16
|
+
layers = []
|
|
17
|
+
names = []
|
|
18
|
+
if subgrids and grid.subgrids is not None:
|
|
19
|
+
last = ""
|
|
20
|
+
for sgrd, srange in grid.subgrids.items():
|
|
21
|
+
layers.append(str(srange[0]) + "_top")
|
|
22
|
+
names.append(sgrd + "_top")
|
|
23
|
+
last = str(srange[-1])
|
|
24
|
+
lastname = sgrd
|
|
25
|
+
# base of last layer
|
|
26
|
+
layers.append(last + "_base")
|
|
27
|
+
names.append(lastname + "_base")
|
|
28
|
+
else:
|
|
29
|
+
layers.append("top")
|
|
30
|
+
names.append("top")
|
|
31
|
+
layers.append("base")
|
|
32
|
+
names.append("base")
|
|
33
|
+
|
|
34
|
+
# next extract these layers
|
|
35
|
+
layerstack = []
|
|
36
|
+
for inum, lay in enumerate(layers):
|
|
37
|
+
layer = surface_from_grid3d(grid, template=None, where=lay, rfactor=rfactor)
|
|
38
|
+
layer.name = names[inum]
|
|
39
|
+
layerstack.append(layer)
|
|
40
|
+
|
|
41
|
+
logger.info("Extracting surface from 3D grid... DONE")
|
|
42
|
+
|
|
43
|
+
return layerstack, "tops", "stratigraphic"
|