xtgeo 4.8.0__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.

Potentially problematic release.


This version of xtgeo might be problematic. Click here for more details.

Files changed (117) hide show
  1. cxtgeo.py +582 -0
  2. cxtgeoPYTHON_wrap.c +20938 -0
  3. xtgeo/__init__.py +246 -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 +21 -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 +340 -0
  23. xtgeo/cube/cube1.py +1023 -0
  24. xtgeo/grid3d/__init__.py +15 -0
  25. xtgeo/grid3d/_ecl_grid.py +774 -0
  26. xtgeo/grid3d/_ecl_inte_head.py +148 -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 +266 -0
  32. xtgeo/grid3d/_grdecl_grid.py +388 -0
  33. xtgeo/grid3d/_grid3d.py +29 -0
  34. xtgeo/grid3d/_grid3d_fence.py +181 -0
  35. xtgeo/grid3d/_grid3d_utils.py +228 -0
  36. xtgeo/grid3d/_grid_boundary.py +76 -0
  37. xtgeo/grid3d/_grid_etc1.py +1566 -0
  38. xtgeo/grid3d/_grid_export.py +221 -0
  39. xtgeo/grid3d/_grid_hybrid.py +66 -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 +125 -0
  45. xtgeo/grid3d/_grid_roxapi.py +292 -0
  46. xtgeo/grid3d/_grid_wellzone.py +165 -0
  47. xtgeo/grid3d/_gridprop_export.py +178 -0
  48. xtgeo/grid3d/_gridprop_import_eclrun.py +164 -0
  49. xtgeo/grid3d/_gridprop_import_grdecl.py +130 -0
  50. xtgeo/grid3d/_gridprop_import_roff.py +52 -0
  51. xtgeo/grid3d/_gridprop_import_xtgcpprop.py +168 -0
  52. xtgeo/grid3d/_gridprop_lowlevel.py +171 -0
  53. xtgeo/grid3d/_gridprop_op1.py +174 -0
  54. xtgeo/grid3d/_gridprop_roxapi.py +239 -0
  55. xtgeo/grid3d/_gridprop_value_init.py +140 -0
  56. xtgeo/grid3d/_gridprops_import_eclrun.py +344 -0
  57. xtgeo/grid3d/_gridprops_import_roff.py +83 -0
  58. xtgeo/grid3d/_roff_grid.py +469 -0
  59. xtgeo/grid3d/_roff_parameter.py +303 -0
  60. xtgeo/grid3d/grid.py +2537 -0
  61. xtgeo/grid3d/grid_properties.py +699 -0
  62. xtgeo/grid3d/grid_property.py +1341 -0
  63. xtgeo/grid3d/types.py +15 -0
  64. xtgeo/io/__init__.py +1 -0
  65. xtgeo/io/_file.py +592 -0
  66. xtgeo/metadata/__init__.py +17 -0
  67. xtgeo/metadata/metadata.py +431 -0
  68. xtgeo/roxutils/__init__.py +7 -0
  69. xtgeo/roxutils/_roxar_loader.py +54 -0
  70. xtgeo/roxutils/_roxutils_etc.py +122 -0
  71. xtgeo/roxutils/roxutils.py +207 -0
  72. xtgeo/surface/__init__.py +18 -0
  73. xtgeo/surface/_regsurf_boundary.py +26 -0
  74. xtgeo/surface/_regsurf_cube.py +210 -0
  75. xtgeo/surface/_regsurf_cube_window.py +391 -0
  76. xtgeo/surface/_regsurf_cube_window_v2.py +297 -0
  77. xtgeo/surface/_regsurf_cube_window_v3.py +360 -0
  78. xtgeo/surface/_regsurf_export.py +388 -0
  79. xtgeo/surface/_regsurf_grid3d.py +271 -0
  80. xtgeo/surface/_regsurf_gridding.py +347 -0
  81. xtgeo/surface/_regsurf_ijxyz_parser.py +278 -0
  82. xtgeo/surface/_regsurf_import.py +347 -0
  83. xtgeo/surface/_regsurf_lowlevel.py +122 -0
  84. xtgeo/surface/_regsurf_oper.py +631 -0
  85. xtgeo/surface/_regsurf_roxapi.py +241 -0
  86. xtgeo/surface/_regsurf_utils.py +81 -0
  87. xtgeo/surface/_surfs_import.py +43 -0
  88. xtgeo/surface/_zmap_parser.py +138 -0
  89. xtgeo/surface/regular_surface.py +2967 -0
  90. xtgeo/surface/surfaces.py +276 -0
  91. xtgeo/well/__init__.py +24 -0
  92. xtgeo/well/_blockedwell_roxapi.py +221 -0
  93. xtgeo/well/_blockedwells_roxapi.py +68 -0
  94. xtgeo/well/_well_aux.py +30 -0
  95. xtgeo/well/_well_io.py +327 -0
  96. xtgeo/well/_well_oper.py +574 -0
  97. xtgeo/well/_well_roxapi.py +304 -0
  98. xtgeo/well/_wellmarkers.py +486 -0
  99. xtgeo/well/_wells_utils.py +158 -0
  100. xtgeo/well/blocked_well.py +216 -0
  101. xtgeo/well/blocked_wells.py +122 -0
  102. xtgeo/well/well1.py +1514 -0
  103. xtgeo/well/wells.py +211 -0
  104. xtgeo/xyz/__init__.py +6 -0
  105. xtgeo/xyz/_polygons_oper.py +272 -0
  106. xtgeo/xyz/_xyz.py +741 -0
  107. xtgeo/xyz/_xyz_data.py +646 -0
  108. xtgeo/xyz/_xyz_io.py +490 -0
  109. xtgeo/xyz/_xyz_lowlevel.py +42 -0
  110. xtgeo/xyz/_xyz_oper.py +613 -0
  111. xtgeo/xyz/_xyz_roxapi.py +766 -0
  112. xtgeo/xyz/points.py +681 -0
  113. xtgeo/xyz/polygons.py +811 -0
  114. xtgeo-4.8.0.dist-info/METADATA +145 -0
  115. xtgeo-4.8.0.dist-info/RECORD +117 -0
  116. xtgeo-4.8.0.dist-info/WHEEL +5 -0
  117. xtgeo-4.8.0.dist-info/licenses/LICENSE.md +165 -0
@@ -0,0 +1,631 @@
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 operation_polygons(self, poly, value, opname="add", inside=True):
452
+ """Operations restricted to polygons"""
453
+
454
+ # keep this for a while (e.g. mid 2024), and then replace it with _v2 below.
455
+ if not isinstance(poly, Polygons):
456
+ raise ValueError("The poly input is not a Polygons instance")
457
+ if opname not in VALID_OPER_POLYS:
458
+ raise ValueError(f"Operation key opname has invalid value: {opname}")
459
+
460
+ # make a copy of the RegularSurface which is used a "filter" or "proxy"
461
+ # value will be 1 inside polygons, 0 outside. Undef cells are kept as is
462
+
463
+ proxy = self.copy()
464
+ proxy.values *= 0.0
465
+ vals = proxy.get_values1d(fill_value=UNDEF)
466
+
467
+ # value could be a scalar or another surface; if another surface,
468
+ # must ensure same topology
469
+
470
+ if isinstance(value, type(self)):
471
+ if not self.compare_topology(value):
472
+ raise ValueError("Input is RegularSurface, but not same map topology")
473
+ value = value.values.copy()
474
+ else:
475
+ # turn scalar value into numpy array
476
+ value = self.values.copy() * 0 + value
477
+
478
+ idgroups = poly.get_dataframe(copy=False).groupby(poly.pname)
479
+
480
+ for _, grp in idgroups:
481
+ xcor = grp[poly.xname].values
482
+ ycor = grp[poly.yname].values
483
+
484
+ ier = _cxtgeo.surf_setval_poly(
485
+ proxy.xori,
486
+ proxy.xinc,
487
+ proxy.yori,
488
+ proxy.yinc,
489
+ proxy.ncol,
490
+ proxy.nrow,
491
+ proxy.yflip,
492
+ proxy.rotation,
493
+ vals,
494
+ xcor,
495
+ ycor,
496
+ 1.0,
497
+ 0,
498
+ )
499
+ if ier == -9:
500
+ xtg.warn("Polygon is not closed")
501
+
502
+ proxy.set_values1d(vals)
503
+ proxyv = proxy.values.astype(np.int8)
504
+
505
+ proxytarget = 1
506
+ if not inside:
507
+ proxytarget = 0
508
+
509
+ tmp = None
510
+ if opname == "add":
511
+ tmp = self.values.copy() + value
512
+ elif opname == "sub":
513
+ tmp = self.values.copy() - value
514
+ elif opname == "mul":
515
+ tmp = self.values.copy() * value
516
+ elif opname == "div":
517
+ # Dividing a map of zero is always a hazzle; try to obtain 0.0
518
+ # as result in these cases
519
+ if 0.0 in value:
520
+ xtg.warn(
521
+ "Dividing a surface with value=0.0 or surface with zero "
522
+ "elements; may get unexpected results, try to "
523
+ "achieve zero values as result!"
524
+ )
525
+ with np.errstate(divide="ignore", invalid="ignore"):
526
+ this = ma.filled(self.values, fill_value=1.0)
527
+ that = ma.filled(value, fill_value=1.0)
528
+ mask = ma.getmaskarray(self.values)
529
+ tmp = np.true_divide(this, that)
530
+ tmp = np.where(np.isinf(tmp), 0, tmp)
531
+ tmp = np.nan_to_num(tmp)
532
+ tmp = ma.array(tmp, mask=mask)
533
+
534
+ elif opname == "set":
535
+ tmp = value
536
+ elif opname == "eli":
537
+ tmp = value * 0 + UNDEF
538
+ tmp = ma.masked_greater(tmp, UNDEF_LIMIT)
539
+
540
+ self.values[proxyv == proxytarget] = tmp[proxyv == proxytarget]
541
+ del tmp
542
+
543
+
544
+ def _proxy_map_polygons(surf, poly, inside=True):
545
+ """Return a proxy map where on one to do operations, as 0 and 1."""
546
+ inside_value = 1 if inside else 0
547
+ outside_value = 0 if inside else 1
548
+
549
+ proxy = surf.copy()
550
+
551
+ # allow a single Polygons instance or a list of Polygons instances
552
+ if isinstance(poly, Polygons):
553
+ usepolys = [poly]
554
+ elif isinstance(poly, list) and all(isinstance(pol, Polygons) for pol in poly):
555
+ usepolys = poly
556
+ else:
557
+ raise ValueError("The poly values is not a Polygons or a list of Polygons")
558
+
559
+ proxy.values = outside_value
560
+ xvals, yvals = proxy.get_xy_values(asmasked=False)
561
+ points = np.array([xvals.ravel(), yvals.ravel()]).T
562
+
563
+ import matplotlib.path as mplpath
564
+
565
+ for pol in usepolys:
566
+ idgroups = pol.get_dataframe(copy=False).groupby(pol.pname)
567
+ for _, grp in idgroups:
568
+ singlepoly = np.array([grp[pol.xname].values, grp[pol.yname].values]).T
569
+ poly_path = mplpath.Path(singlepoly)
570
+ is_inside = poly_path.contains_points(points)
571
+ is_inside = is_inside.reshape(proxy.ncol, proxy.nrow)
572
+ proxy.values = np.where(is_inside, inside_value, proxy.values)
573
+
574
+ return proxy
575
+
576
+
577
+ def operation_polygons_v2(self, poly, value: float | Any, opname="add", inside=True):
578
+ """Operations restricted to polygons, using matplotlib (much faster).
579
+
580
+ The 'value' can be a number or another regular surface (with same design)
581
+
582
+ """
583
+
584
+ proxy = _proxy_map_polygons(self, poly, inside=inside)
585
+ result = self.copy()
586
+
587
+ if isinstance(value, type(result)):
588
+ if not result.compare_topology(value):
589
+ raise ValueError("Input is RegularSurface, but not same map topology")
590
+ value = value.values.copy()
591
+ else:
592
+ # turn scalar value into numpy array
593
+ value = result.values.copy() * 0 + value
594
+
595
+ if opname == "add":
596
+ result.values = np.ma.where(
597
+ proxy.values == 1, result.values + value, result.values
598
+ )
599
+ elif opname == "sub":
600
+ result.values = np.ma.where(
601
+ proxy.values == 1, result.values - value, result.values
602
+ )
603
+ elif opname == "mul":
604
+ result.values = np.ma.where(
605
+ proxy.values == 1, result.values * value, result.values
606
+ )
607
+ elif opname == "div":
608
+ # Dividing a map of zero is always a hazzle; try to obtain 0.0
609
+ # as result in these cases
610
+ if 0.0 in value:
611
+ xtg.warn(
612
+ "Dividing a surface with value = 0.0 or surface with zero "
613
+ "elements; may get unexpected results, will try to "
614
+ "achieve zero values as result!"
615
+ )
616
+
617
+ result.values = np.ma.where(value == 0.0, 0.0, result.values)
618
+ proxy.values = np.ma.where(value == 0.0, 0, proxy.values)
619
+ result.values = np.ma.where(
620
+ proxy.values == 1, result.values / value, result.values
621
+ )
622
+
623
+ elif opname == "set":
624
+ result.values = np.ma.where(proxy.values == 1, value, result.values)
625
+ elif opname == "eli":
626
+ result.values = np.ma.where(proxy.values == 1, UNDEF, result.values)
627
+ result.values = ma.masked_greater(result.values, UNDEF_LIMIT)
628
+ else:
629
+ raise KeyError(f"The opname={opname} is not one of {VALID_OPER_POLYS}")
630
+
631
+ self.values = result.values