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.
Files changed (122) hide show
  1. cxtgeo.py +558 -0
  2. cxtgeoPYTHON_wrap.c +19537 -0
  3. xtgeo/__init__.py +248 -0
  4. xtgeo/_cxtgeo.cp313-win_amd64.pyd +0 -0
  5. xtgeo/_internal.cp313-win_amd64.pyd +0 -0
  6. xtgeo/common/__init__.py +19 -0
  7. xtgeo/common/_angles.py +29 -0
  8. xtgeo/common/_xyz_enum.py +50 -0
  9. xtgeo/common/calc.py +396 -0
  10. xtgeo/common/constants.py +30 -0
  11. xtgeo/common/exceptions.py +42 -0
  12. xtgeo/common/log.py +93 -0
  13. xtgeo/common/sys.py +166 -0
  14. xtgeo/common/types.py +18 -0
  15. xtgeo/common/version.py +34 -0
  16. xtgeo/common/xtgeo_dialog.py +604 -0
  17. xtgeo/cube/__init__.py +9 -0
  18. xtgeo/cube/_cube_export.py +214 -0
  19. xtgeo/cube/_cube_import.py +532 -0
  20. xtgeo/cube/_cube_roxapi.py +180 -0
  21. xtgeo/cube/_cube_utils.py +287 -0
  22. xtgeo/cube/_cube_window_attributes.py +273 -0
  23. xtgeo/cube/cube1.py +1023 -0
  24. xtgeo/grid3d/__init__.py +15 -0
  25. xtgeo/grid3d/_ecl_grid.py +778 -0
  26. xtgeo/grid3d/_ecl_inte_head.py +152 -0
  27. xtgeo/grid3d/_ecl_logi_head.py +71 -0
  28. xtgeo/grid3d/_ecl_output_file.py +81 -0
  29. xtgeo/grid3d/_egrid.py +1004 -0
  30. xtgeo/grid3d/_find_gridprop_in_eclrun.py +625 -0
  31. xtgeo/grid3d/_grdecl_format.py +309 -0
  32. xtgeo/grid3d/_grdecl_grid.py +400 -0
  33. xtgeo/grid3d/_grid3d.py +29 -0
  34. xtgeo/grid3d/_grid3d_fence.py +284 -0
  35. xtgeo/grid3d/_grid3d_utils.py +228 -0
  36. xtgeo/grid3d/_grid_boundary.py +76 -0
  37. xtgeo/grid3d/_grid_etc1.py +1683 -0
  38. xtgeo/grid3d/_grid_export.py +222 -0
  39. xtgeo/grid3d/_grid_hybrid.py +50 -0
  40. xtgeo/grid3d/_grid_import.py +79 -0
  41. xtgeo/grid3d/_grid_import_ecl.py +101 -0
  42. xtgeo/grid3d/_grid_import_roff.py +135 -0
  43. xtgeo/grid3d/_grid_import_xtgcpgeom.py +375 -0
  44. xtgeo/grid3d/_grid_refine.py +258 -0
  45. xtgeo/grid3d/_grid_roxapi.py +292 -0
  46. xtgeo/grid3d/_grid_translate_coords.py +154 -0
  47. xtgeo/grid3d/_grid_wellzone.py +165 -0
  48. xtgeo/grid3d/_gridprop_export.py +202 -0
  49. xtgeo/grid3d/_gridprop_import_eclrun.py +164 -0
  50. xtgeo/grid3d/_gridprop_import_grdecl.py +132 -0
  51. xtgeo/grid3d/_gridprop_import_roff.py +52 -0
  52. xtgeo/grid3d/_gridprop_import_xtgcpprop.py +168 -0
  53. xtgeo/grid3d/_gridprop_lowlevel.py +171 -0
  54. xtgeo/grid3d/_gridprop_op1.py +272 -0
  55. xtgeo/grid3d/_gridprop_roxapi.py +301 -0
  56. xtgeo/grid3d/_gridprop_value_init.py +140 -0
  57. xtgeo/grid3d/_gridprops_import_eclrun.py +344 -0
  58. xtgeo/grid3d/_gridprops_import_roff.py +83 -0
  59. xtgeo/grid3d/_roff_grid.py +470 -0
  60. xtgeo/grid3d/_roff_parameter.py +303 -0
  61. xtgeo/grid3d/grid.py +3010 -0
  62. xtgeo/grid3d/grid_properties.py +699 -0
  63. xtgeo/grid3d/grid_property.py +1313 -0
  64. xtgeo/grid3d/types.py +15 -0
  65. xtgeo/interfaces/rms/__init__.py +18 -0
  66. xtgeo/interfaces/rms/_regular_surface.py +460 -0
  67. xtgeo/interfaces/rms/_rms_base.py +100 -0
  68. xtgeo/interfaces/rms/_rmsapi_package.py +69 -0
  69. xtgeo/interfaces/rms/rmsapi_utils.py +438 -0
  70. xtgeo/io/__init__.py +1 -0
  71. xtgeo/io/_file.py +603 -0
  72. xtgeo/metadata/__init__.py +17 -0
  73. xtgeo/metadata/metadata.py +435 -0
  74. xtgeo/roxutils/__init__.py +7 -0
  75. xtgeo/roxutils/_roxar_loader.py +54 -0
  76. xtgeo/roxutils/_roxutils_etc.py +122 -0
  77. xtgeo/roxutils/roxutils.py +207 -0
  78. xtgeo/surface/__init__.py +20 -0
  79. xtgeo/surface/_regsurf_boundary.py +26 -0
  80. xtgeo/surface/_regsurf_cube.py +210 -0
  81. xtgeo/surface/_regsurf_cube_window.py +391 -0
  82. xtgeo/surface/_regsurf_cube_window_v2.py +297 -0
  83. xtgeo/surface/_regsurf_cube_window_v3.py +360 -0
  84. xtgeo/surface/_regsurf_export.py +388 -0
  85. xtgeo/surface/_regsurf_grid3d.py +275 -0
  86. xtgeo/surface/_regsurf_gridding.py +347 -0
  87. xtgeo/surface/_regsurf_ijxyz_parser.py +278 -0
  88. xtgeo/surface/_regsurf_import.py +347 -0
  89. xtgeo/surface/_regsurf_lowlevel.py +122 -0
  90. xtgeo/surface/_regsurf_oper.py +538 -0
  91. xtgeo/surface/_regsurf_utils.py +81 -0
  92. xtgeo/surface/_surfs_import.py +43 -0
  93. xtgeo/surface/_zmap_parser.py +138 -0
  94. xtgeo/surface/regular_surface.py +3043 -0
  95. xtgeo/surface/surfaces.py +276 -0
  96. xtgeo/well/__init__.py +24 -0
  97. xtgeo/well/_blockedwell_roxapi.py +241 -0
  98. xtgeo/well/_blockedwells_roxapi.py +68 -0
  99. xtgeo/well/_well_aux.py +30 -0
  100. xtgeo/well/_well_io.py +327 -0
  101. xtgeo/well/_well_oper.py +483 -0
  102. xtgeo/well/_well_roxapi.py +304 -0
  103. xtgeo/well/_wellmarkers.py +486 -0
  104. xtgeo/well/_wells_utils.py +158 -0
  105. xtgeo/well/blocked_well.py +220 -0
  106. xtgeo/well/blocked_wells.py +134 -0
  107. xtgeo/well/well1.py +1516 -0
  108. xtgeo/well/wells.py +211 -0
  109. xtgeo/xyz/__init__.py +6 -0
  110. xtgeo/xyz/_polygons_oper.py +272 -0
  111. xtgeo/xyz/_xyz.py +758 -0
  112. xtgeo/xyz/_xyz_data.py +646 -0
  113. xtgeo/xyz/_xyz_io.py +737 -0
  114. xtgeo/xyz/_xyz_lowlevel.py +42 -0
  115. xtgeo/xyz/_xyz_oper.py +613 -0
  116. xtgeo/xyz/_xyz_roxapi.py +766 -0
  117. xtgeo/xyz/points.py +698 -0
  118. xtgeo/xyz/polygons.py +827 -0
  119. xtgeo-4.14.1.dist-info/METADATA +146 -0
  120. xtgeo-4.14.1.dist-info/RECORD +122 -0
  121. xtgeo-4.14.1.dist-info/WHEEL +5 -0
  122. xtgeo-4.14.1.dist-info/licenses/LICENSE.md +165 -0
@@ -0,0 +1,486 @@
1
+ """Well marker data; private module"""
2
+
3
+ from __future__ import annotations
4
+
5
+ import numpy as np
6
+ import pandas as pd
7
+
8
+ from xtgeo import _cxtgeo
9
+ from xtgeo.common.constants import (
10
+ UNDEF,
11
+ UNDEF_DISC,
12
+ UNDEF_INT,
13
+ UNDEF_INT_LIMIT,
14
+ UNDEF_LIMIT,
15
+ )
16
+ from xtgeo.common.log import null_logger
17
+ from xtgeo.xyz.points import Points
18
+
19
+ logger = null_logger(__name__)
20
+
21
+
22
+ def get_zonation_points(
23
+ self: Points, tops, incl_limit, top_prefix, zonelist, use_undef
24
+ ):
25
+ """
26
+ Getting zonation tops (private routine)
27
+
28
+ Args, see calling routine
29
+ """
30
+ scopy = self.copy() # avoid altering original instance
31
+
32
+ scopy.geometrics()
33
+ dataframe = scopy.get_dataframe()
34
+
35
+ # as zlog is float64; need to convert to int array with high number as undef
36
+ if scopy.zonelogname is not None:
37
+ if use_undef:
38
+ dataframe.dropna(subset=[scopy.zonelogname], inplace=True)
39
+ zlog = dataframe[scopy.zonelogname].values
40
+ zlog[np.isnan(zlog)] = UNDEF_DISC
41
+ zlog = np.rint(zlog).astype(int)
42
+ else:
43
+ return None
44
+
45
+ xvv = dataframe[scopy.xname].values
46
+ yvv = dataframe[scopy.yname].values
47
+ zvv = dataframe[scopy.zname].values
48
+ incl = dataframe["Q_INCL"].values
49
+ mdv = dataframe["Q_MDEPTH"].values
50
+
51
+ if scopy.mdlogname is not None:
52
+ mdv = dataframe[scopy.mdlogname].values
53
+
54
+ if zonelist is None:
55
+ # need to declare as list; otherwise Py3 will get dict.keys
56
+ zonelist = list(scopy.get_logrecord(scopy.zonelogname).keys())
57
+
58
+ logger.debug("Find values for %s", zonelist)
59
+
60
+ ztops, ztopnames, zisos, zisonames = _extract_ztops(
61
+ scopy,
62
+ zonelist,
63
+ xvv,
64
+ yvv,
65
+ zvv,
66
+ zlog,
67
+ mdv,
68
+ incl,
69
+ tops=tops,
70
+ incl_limit=incl_limit,
71
+ prefix=top_prefix,
72
+ use_undef=use_undef,
73
+ )
74
+
75
+ zlist = ztops if tops else zisos
76
+
77
+ # collect temporary columns, made by geometrics() and _extract_ztops() methods
78
+ tmp_columns = [
79
+ col
80
+ for col in (ztopnames or []) + (zisonames or [])
81
+ if col.startswith("Q_") and col not in self.get_lognames()
82
+ ]
83
+
84
+ dataframe = (
85
+ pd.DataFrame(zlist, columns=ztopnames)
86
+ if tops
87
+ else pd.DataFrame(zlist, columns=zisonames)
88
+ )
89
+
90
+ return dataframe.drop(columns=tmp_columns, errors="ignore")
91
+
92
+
93
+ def _extract_ztops(
94
+ self,
95
+ zonelist,
96
+ xcv,
97
+ ycv,
98
+ zcv,
99
+ zlog,
100
+ mdv,
101
+ incl,
102
+ tops=True,
103
+ incl_limit=80,
104
+ prefix="Top",
105
+ use_undef=False,
106
+ ):
107
+ """Extract a list of tops for a zone.
108
+
109
+ Args:
110
+ zonelist (list-like): The zonelog list numbers to apply; either
111
+ as a list, or a tuple; 2 entries forms a range [start, stop]
112
+ xcv (np): X Position numpy array
113
+ ycv (np): Y Position numpy array
114
+ zcv (np): Z Position numpy array
115
+ zlog (np): Zonelog array
116
+ mdv (np): MDepth log numpy array
117
+ incl (np): Inclination log numpy array
118
+ tops (bool): Compute tops or thickness (zone) points (default True)
119
+ incl_limit (float): Limitation of zone computation (angle, degrees)
120
+ use_undef (bool): If True, then transition from UNDEF is also
121
+ used.
122
+ """
123
+
124
+ # The wellpoints will be a list of tuples (one tuple per hit)
125
+ wpts = []
126
+ zlogname = self.zonelogname
127
+
128
+ if not tops and incl_limit is None:
129
+ incl_limit = 80
130
+
131
+ azi = -999.0 # tmp so far
132
+
133
+ if isinstance(zonelist, tuple):
134
+ if len(zonelist) != 2:
135
+ raise ValueError(
136
+ f"zonelist given as tuple must be of length 2, was {len(zonelist)}"
137
+ )
138
+ usezonerange = range(zonelist[0], zonelist[1] + 1)
139
+ elif isinstance(zonelist, list):
140
+ if len(zonelist) == 1:
141
+ raise ValueError(
142
+ f"zonelist given as list must contain two or more"
143
+ f" elements, had 1: {zonelist}"
144
+ )
145
+ usezonerange = zonelist
146
+ else:
147
+ raise TypeError(
148
+ f"zonelist must be either list (of two or more elements) or "
149
+ f"a tuple (with two elements representing start and stop), was"
150
+ f"{type(zonelist)}"
151
+ )
152
+
153
+ # check if increasing monotonic and with no jumps:
154
+ if not all(i + 1 == j for i, j in zip(usezonerange, usezonerange[1:])):
155
+ raise ValueError(
156
+ f"zonelist must be strictly increasing with increment of one,"
157
+ f" was {usezonerange}"
158
+ )
159
+
160
+ iundef = UNDEF_INT
161
+ iundeflimit = UNDEF_INT_LIMIT
162
+ pzone = iundef
163
+
164
+ if use_undef:
165
+ pzone = usezonerange[0] - 1
166
+
167
+ for ind, zone in np.ndenumerate(zlog):
168
+ ino = ind[0] # since ind is a tuple...
169
+
170
+ if pzone != zone and pzone < iundeflimit and zone < iundeflimit:
171
+ logger.debug("Found break in zonation")
172
+ if pzone < zone:
173
+ logger.debug(
174
+ "Found match, increasing zonation at %s < %s (MD %s)",
175
+ pzone,
176
+ zone,
177
+ mdv[ino],
178
+ )
179
+ for kzv in range(pzone + 1, zone + 1):
180
+ if kzv in usezonerange:
181
+ zname = self.get_logrecord_codename(zlogname, kzv)
182
+ zname = prefix + zname
183
+ ztop = (
184
+ xcv[ino],
185
+ ycv[ino],
186
+ zcv[ino],
187
+ mdv[ino],
188
+ incl[ino],
189
+ azi,
190
+ kzv,
191
+ zname,
192
+ self.xwellname,
193
+ )
194
+ wpts.append(ztop)
195
+ if pzone > zone and ino > 0:
196
+ logger.debug(
197
+ "Found match, decreasing zonation at %s > %s (MD %s)",
198
+ pzone,
199
+ zone,
200
+ mdv[ino - 1],
201
+ )
202
+ for kzv in range(pzone, zone, -1):
203
+ if kzv in usezonerange:
204
+ zname = self.get_logrecord_codename(zlogname, kzv)
205
+ zname = prefix + zname
206
+ ztop = (
207
+ xcv[ino - 1],
208
+ ycv[ino - 1],
209
+ zcv[ino - 1],
210
+ mdv[ino - 1],
211
+ incl[ino - 1],
212
+ azi,
213
+ kzv,
214
+ zname,
215
+ self.xwellname,
216
+ )
217
+ wpts.append(ztop)
218
+ pzone = zone
219
+
220
+ wpts_names = [
221
+ self.xname,
222
+ self.yname,
223
+ self.zname,
224
+ self.mdlogname,
225
+ "Q_INCL",
226
+ "Q_AZI",
227
+ "Zone",
228
+ "TopName",
229
+ "WellName",
230
+ ]
231
+
232
+ if tops:
233
+ return wpts, wpts_names, None, None
234
+
235
+ # next get a MIDPOINT zthickness (DZ)
236
+ llen = len(wpts) - 1
237
+
238
+ zwpts_names = [
239
+ self.xname,
240
+ self.yname,
241
+ self.zname,
242
+ self.mdlogname + "_AVG",
243
+ "Q_MD1",
244
+ "Q_MD2",
245
+ "Q_INCL",
246
+ "Q_AZI",
247
+ "Zone",
248
+ "ZoneName",
249
+ "WellName",
250
+ ]
251
+
252
+ zwpts = []
253
+ for ino in range(llen):
254
+ i1v = ino
255
+ i2v = ino + 1
256
+ xx1, yy1, zz1, md1, incl1, _azi1, zk1, zn1, wn1 = wpts[i1v]
257
+ xx2, yy2, zz2, md2, incl2, _azi2, zk2, zn2, _wn2 = wpts[i2v]
258
+
259
+ # mid point
260
+ xx_avg = (xx1 + xx2) / 2
261
+ yy_avg = (yy1 + yy2) / 2
262
+ md_avg = (md1 + md2) / 2
263
+ incl_avg = (incl1 + incl2) / 2
264
+
265
+ azi_avg = -999.0 # to be fixed later
266
+
267
+ zzp = round(abs(zz2 - zz1), 4)
268
+
269
+ useok = False
270
+
271
+ if incl_avg < incl_limit:
272
+ useok = True
273
+
274
+ if useok and zk2 != zk1:
275
+ usezk = zk1
276
+ usezn = zn1
277
+ if zk1 > zk2:
278
+ usezk = zk2
279
+ usezn = zn2
280
+ usezn = usezn[len(prefix) :]
281
+
282
+ zzone = (
283
+ xx_avg,
284
+ yy_avg,
285
+ zzp,
286
+ md_avg,
287
+ md1,
288
+ md2,
289
+ incl_avg,
290
+ azi_avg,
291
+ usezk,
292
+ usezn,
293
+ wn1,
294
+ )
295
+ zwpts.append(zzone)
296
+
297
+ return wpts, wpts_names, zwpts, zwpts_names
298
+
299
+
300
+ def get_fraction_per_zone(
301
+ self,
302
+ dlogname,
303
+ dvalues,
304
+ zonelist=None,
305
+ incl_limit=80,
306
+ count_limit=3,
307
+ zonelogname=None,
308
+ ):
309
+ """Fraction of e.g. a facies in a zone segment.
310
+
311
+ X_UTME Y_UTMN Z_TVDSS Zonelog Facies M_INCL
312
+ 464011.719 5931757.257 1663.1079 3.0 1.0 10.2
313
+ 464011.751 5931757.271 1663.6084 3.0 1.0 10.3
314
+ 464011.783 5931757.285 1664.1090 3.0 2.0 11.2
315
+ 464011.815 5931757.299 1664.6097 3.0 2.0 11.4
316
+ 464011.847 5931757.313 1665.1105 3.0 2.0 11.5
317
+ 464011.879 5931757.326 1665.6114 3.0 2.0 12.0
318
+ 464011.911 5931757.340 1666.1123 3.0 1.0 12.2
319
+ 464011.943 5931757.354 1666.6134 3.0 1.0 13.4
320
+
321
+ Count fraction of one or more facies (dvalues list)
322
+ filtered on a zone, given that Inclination is below limit all over.
323
+ Since a zone can be repeated, it is important to split
324
+ into segments by POLY_ID. When fraction is determined, the
325
+ AVG X Y coord is applied.
326
+
327
+ If there are one or more occurences of undef for the dlogname
328
+ in that interval, no value shall be computed.
329
+
330
+ Args:
331
+ dlogname (str): Name of discrete log e.g. Facies
332
+ dvalues (list): List of codes to sum fraction upon
333
+ zonelist (list): List of zones to compute over
334
+ incl_limit (float): Skip if max inclination found > incl_limit
335
+ count_limit (int): Minimum number of samples required per segment
336
+
337
+ Returns:
338
+ A dataframe with relevant data...
339
+
340
+ """
341
+ logger.debug("The zonelist is %s", zonelist)
342
+ logger.debug("The dlogname is %s", dlogname)
343
+ logger.debug("The dvalues are %s", dvalues)
344
+
345
+ if zonelogname is not None:
346
+ usezonelogname = zonelogname
347
+ self.zonelogname = zonelogname
348
+ else:
349
+ usezonelogname = self.zonelogname
350
+
351
+ if usezonelogname is None:
352
+ raise RuntimeError("Stop, zonelogname is None")
353
+
354
+ self.make_zone_qual_log("_QFLAG")
355
+
356
+ if zonelist is None:
357
+ # need to declare as list; otherwise Py3 will get dict.keys
358
+ zonelist = list(self.get_logrecord(self.zonelogname).keys())
359
+
360
+ useinclname = "Q_INCL"
361
+ if "M_INCL" in self.get_dataframe(copy=False):
362
+ useinclname = "M_INCL"
363
+ else:
364
+ self.geometrics()
365
+
366
+ result = {}
367
+ result[self.xname] = []
368
+ result[self.yname] = []
369
+ result["DFRAC"] = []
370
+ result["Q_INCL"] = []
371
+ result["ZONE"] = []
372
+ result["WELLNAME"] = []
373
+ result[dlogname] = []
374
+
375
+ svalues = str(dvalues).rstrip("]").lstrip("[").replace(", ", "+")
376
+
377
+ xtralogs = [dlogname, useinclname, "_QFLAG"]
378
+ for izon in zonelist:
379
+ logger.debug("The zone number is %s", izon)
380
+ logger.debug("The extralogs are %s", xtralogs)
381
+
382
+ dfr = self.get_zone_interval(izon, extralogs=xtralogs)
383
+
384
+ if dfr is None:
385
+ continue
386
+
387
+ dfrx = dfr.groupby("POLY_ID")
388
+
389
+ for _polyid, dframe in dfrx:
390
+ qinclmax = dframe["Q_INCL"].max()
391
+ qinclavg = dframe["Q_INCL"].mean()
392
+ qflag = dframe["_QFLAG"].mean()
393
+ dseries = dframe[dlogname]
394
+ if qflag < 0.5 or qflag > 2.5: # 1 or 2 is OK
395
+ logger.debug("Skipped due to zone %s", qflag)
396
+ continue
397
+ if qinclmax > incl_limit:
398
+ logger.debug("Skipped due to max inclination %s", qinclmax)
399
+ continue
400
+ if dseries.size < count_limit: # interval too short for fraction
401
+ logger.debug("Skipped due to too few values %s", dseries.size)
402
+ continue
403
+ if dseries.max() > UNDEF_INT_LIMIT:
404
+ logger.debug("Skipped due to too missing/undef value(s)")
405
+ continue
406
+
407
+ xavg = dframe[self.xname].mean()
408
+ yavg = dframe[self.yname].mean()
409
+
410
+ dfrac = 0.0
411
+ for dval in dvalues:
412
+ if any(dseries.isin([dval])):
413
+ dfrac += dseries.value_counts(normalize=True)[dval]
414
+
415
+ result[self.xname].append(xavg)
416
+ result[self.yname].append(yavg)
417
+ result["DFRAC"].append(dfrac)
418
+ result["Q_INCL"].append(qinclavg)
419
+ result["ZONE"].append(izon)
420
+ result["WELLNAME"].append(self.xwellname)
421
+ result[dlogname].append(svalues)
422
+
423
+ # make the dataframe and return it
424
+ if result[self.xname]:
425
+ return pd.DataFrame.from_dict(result)
426
+
427
+ self.delete_log("_QFLAG")
428
+
429
+ return None
430
+
431
+
432
+ def get_surface_picks(self, surf):
433
+ """get Surface picks"""
434
+
435
+ dataframe = self.get_dataframe(copy=False)
436
+ xcor = dataframe[self.xname].values
437
+ ycor = dataframe[self.yname].values
438
+ zcor = dataframe[self.zname].values
439
+
440
+ if self.mdlogname:
441
+ mcor = dataframe[self.mdlogname].values
442
+ else:
443
+ mcor = np.zeros(xcor.size, dtype=np.float64) + UNDEF
444
+
445
+ nval, xres, yres, zres, mres, dres = _cxtgeo.well_surf_picks(
446
+ xcor,
447
+ ycor,
448
+ zcor,
449
+ mcor,
450
+ surf.ncol,
451
+ surf.nrow,
452
+ surf.xori,
453
+ surf.yori,
454
+ surf.xinc,
455
+ surf.yinc,
456
+ surf.yflip,
457
+ surf.rotation,
458
+ surf.npvalues1d,
459
+ xcor.size,
460
+ xcor.size,
461
+ xcor.size,
462
+ xcor.size,
463
+ xcor.size,
464
+ )
465
+
466
+ if nval > 0:
467
+ poi = Points()
468
+
469
+ mres[mres > UNDEF_LIMIT] = np.nan
470
+
471
+ res = {}
472
+ res[poi.xname] = xres[:nval]
473
+ res[poi.yname] = yres[:nval]
474
+ res[poi.zname] = zres[:nval]
475
+ if self.mdlogname:
476
+ res[self.mdlogname] = mres[:nval]
477
+ res["DIRECTION"] = dres[:nval]
478
+ res["WELLNAME"] = self.wellname
479
+
480
+ poi.set_dataframe(pd.DataFrame.from_dict(res))
481
+
482
+ return poi
483
+
484
+ return None
485
+
486
+ # return a xtgeo Poinst() object with points as dataframe, given that nval > 0
@@ -0,0 +1,158 @@
1
+ """Utilities for Wells class"""
2
+
3
+ import logging
4
+
5
+ import numpy as np
6
+ import pandas as pd
7
+ import shapely.geometry as sg
8
+
9
+ from xtgeo.common.xtgeo_dialog import XTGeoDialog, XTGShowProgress
10
+
11
+ logger = logging.getLogger(__name__)
12
+ logger.addHandler(logging.NullHandler())
13
+
14
+ xtg = XTGeoDialog()
15
+
16
+ # self == Wells instance (plural wells)
17
+
18
+
19
+ def wellintersections(
20
+ self,
21
+ wfilter=None,
22
+ showprogress=False,
23
+ ):
24
+ """Get intersections between wells, return as dataframe table.
25
+
26
+ This routine is using "shapely" functions!
27
+
28
+ Some actions are done in order to filter away the part of the trajectories
29
+ that are paralell.
30
+
31
+ """
32
+
33
+ xpoints = []
34
+
35
+ # make a dict if nocrossings
36
+ nox = {}
37
+
38
+ wlen = len(self.wells)
39
+
40
+ progress = XTGShowProgress(wlen, show=showprogress, leadtext="progress: ", skip=5)
41
+
42
+ for iwell, well in enumerate(self.wells):
43
+ progress.flush(iwell)
44
+
45
+ logger.debug("Work with %s", well.name)
46
+ try:
47
+ well.geometrics()
48
+ except ValueError:
49
+ logger.debug("Skip %s (cannot compute geometrics)", well.name)
50
+ continue
51
+
52
+ welldfr = well.get_dataframe()
53
+
54
+ xcor = welldfr[well.xname].values
55
+ ycor = welldfr[well.yname].values
56
+ mcor = welldfr[well.mdlogname].values
57
+ logger.debug("The mdlogname property is: %s", well.mdlogname)
58
+
59
+ if xcor.size < 2:
60
+ continue
61
+
62
+ thisline1 = sg.LineString(np.stack([xcor, ycor], axis=1))
63
+ thisline2 = sg.LineString(np.stack([xcor, ycor, mcor], axis=1))
64
+
65
+ nox[well.name] = []
66
+ # loop over other wells
67
+ for other in self.wells:
68
+ if other.name == well.name:
69
+ continue # same well
70
+
71
+ if not well.may_overlap(other):
72
+ nox[well.name].append(other.name)
73
+ continue # a quick check; no chance for overlap
74
+
75
+ logger.debug("Consider crossing with %s ...", other.name)
76
+
77
+ # try to be smart to skip entries that earlier have beenn tested
78
+ # for crossing. If other does not cross well, then well does not
79
+ # cross other...
80
+ if other.name in nox and well.name in nox[other.name]:
81
+ continue
82
+
83
+ # truncate away the paralell part on a copy
84
+ owell = other.copy()
85
+
86
+ # wfilter = None
87
+ if wfilter is not None and "parallel" in wfilter:
88
+ xtol = wfilter["parallel"].get("xtol")
89
+ ytol = wfilter["parallel"].get("ytol")
90
+ ztol = wfilter["parallel"].get("ztol")
91
+ itol = wfilter["parallel"].get("itol")
92
+ atol = wfilter["parallel"].get("atol")
93
+ owell.truncate_parallel_path(
94
+ well, xtol=xtol, ytol=ytol, ztol=ztol, itol=itol, atol=atol
95
+ )
96
+
97
+ other_dframe = owell.get_dataframe()
98
+ xcorc = other_dframe[well.xname].values
99
+ ycorc = other_dframe[well.yname].values
100
+ zcorc = other_dframe[well.zname].values
101
+
102
+ if xcorc.size < 2:
103
+ continue
104
+
105
+ otherline = sg.LineString(np.stack([xcorc, ycorc, zcorc], axis=1))
106
+
107
+ if not thisline1.crosses(otherline):
108
+ nox[well.name].append(other.name)
109
+ continue
110
+
111
+ ixx = thisline1.intersection(otherline)
112
+
113
+ if ixx.is_empty:
114
+ nox[well.name].append(other.name)
115
+ continue
116
+
117
+ # need this trick to get mdepth
118
+ other2 = sg.LineString(np.stack([xcorc, ycorc], axis=1))
119
+ ixx2 = thisline2.intersection(other2)
120
+
121
+ logger.debug("==> Intersects with %s", other.name)
122
+
123
+ if isinstance(ixx, sg.Point):
124
+ xcor, ycor, zcor = ixx.coords[0]
125
+ _x, _y, mcor = ixx2.coords[0]
126
+ xpoints.append([well.name, mcor, other.name, xcor, ycor, zcor])
127
+
128
+ elif isinstance(ixx, sg.MultiPoint):
129
+ pxx2 = list(ixx2)
130
+ for ino, pxx in enumerate(list(ixx)):
131
+ xcor, ycor, zcor = pxx.coords[0]
132
+ _x, _y, mcor = pxx2[ino].coords[0]
133
+ xpoints.append([well.name, mcor, other.name, xcor, ycor, zcor])
134
+
135
+ elif isinstance(ixx, sg.GeometryCollection):
136
+ gxx2 = list(ixx2)
137
+ for ino, gxx in enumerate(list(ixx)):
138
+ if isinstance(gxx, sg.Point):
139
+ xcor, ycor, zcor = gxx.coords[0]
140
+ _x, _y, mcor = gxx2[ino].coords[0]
141
+ xpoints.append([well.name, mcor, other.name, xcor, ycor, zcor])
142
+
143
+ dfr = pd.DataFrame(
144
+ xpoints,
145
+ columns=[
146
+ "WELL",
147
+ "MDEPTH",
148
+ "CWELL",
149
+ self._wells[0].xname,
150
+ self._wells[0].yname,
151
+ self._wells[0].zname,
152
+ ],
153
+ )
154
+
155
+ progress.finished()
156
+
157
+ logger.debug("All intersections found!")
158
+ return dfr