wolfhece 2.0.4__py3-none-any.whl → 2.0.6__py3-none-any.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 (42) hide show
  1. wolfhece/GraphNotebook.py +0 -1
  2. wolfhece/GraphProfile.py +5 -14
  3. wolfhece/Lidar2002.py +0 -1
  4. wolfhece/PyCrosssections.py +21 -26
  5. wolfhece/PyDraw.py +219 -58
  6. wolfhece/PyGui.py +6 -3
  7. wolfhece/PyPalette.py +2 -2
  8. wolfhece/PyParams.py +48 -48
  9. wolfhece/PyVertex.py +1 -1
  10. wolfhece/PyVertexvectors.py +40 -4
  11. wolfhece/Results2DGPU.py +7 -6
  12. wolfhece/apps/WolfPython.png +0 -0
  13. wolfhece/bernoulli/NetworkOpenGL.py +1 -1
  14. wolfhece/cli.py +7 -0
  15. wolfhece/flow_SPWMI.py +1 -1
  16. wolfhece/friction_law.py +6 -6
  17. wolfhece/gpuview.py +1 -1
  18. wolfhece/hydrology/PyWatershed.py +9 -10
  19. wolfhece/lagrangian/emitter.py +1 -1
  20. wolfhece/lagrangian/example_domain.py +1 -1
  21. wolfhece/lagrangian/velocity_field.py +4 -4
  22. wolfhece/libs/WolfDll.dll +0 -0
  23. wolfhece/libs/WolfDll_CD.dll +0 -0
  24. wolfhece/libs/WolfOGL.c +28187 -28187
  25. wolfhece/mar/Interface_MAR_WOLF_objet.py +1004 -0
  26. wolfhece/mar/commontools.py +1289 -59
  27. wolfhece/mesh2d/bc_manager.py +89 -13
  28. wolfhece/mesh2d/cst_2D_boundary_conditions.py +12 -0
  29. wolfhece/mesh2d/wolf2dprev.py +1 -2
  30. wolfhece/pydike.py +1 -1
  31. wolfhece/pyshields.py +43 -43
  32. wolfhece/pywalous.py +2 -2
  33. wolfhece/scenario/config_manager.py +3 -1
  34. wolfhece/ui/wolf_multiselection_collapsiblepane.py +10 -10
  35. wolfhece/wolf_array.py +1298 -418
  36. wolfhece/wolf_texture.py +1 -1
  37. wolfhece/wolfresults_2D.py +124 -19
  38. {wolfhece-2.0.4.dist-info → wolfhece-2.0.6.dist-info}/METADATA +5 -1
  39. {wolfhece-2.0.4.dist-info → wolfhece-2.0.6.dist-info}/RECORD +42 -39
  40. {wolfhece-2.0.4.dist-info → wolfhece-2.0.6.dist-info}/WHEEL +0 -0
  41. {wolfhece-2.0.4.dist-info → wolfhece-2.0.6.dist-info}/entry_points.txt +0 -0
  42. {wolfhece-2.0.4.dist-info → wolfhece-2.0.6.dist-info}/top_level.txt +0 -0
wolfhece/wolf_array.py CHANGED
@@ -17,7 +17,7 @@ try:
17
17
  except:
18
18
  msg=_('Error importing OpenGL library')
19
19
  msg+=_(' Python version : ' + sys.version)
20
- msg+=_(' Please check your version of opengl32.dll -- conflict may exist between different fils present on your desktop')
20
+ msg+=_(' Please check your version of opengl32.dll -- conflict may exist between different files present on your desktop')
21
21
  raise Exception(msg)
22
22
 
23
23
  import math
@@ -34,6 +34,7 @@ from shapely.ops import linemerge, substring
34
34
  from os.path import dirname,basename,join
35
35
  import logging
36
36
  from typing import Literal
37
+ from copy import deepcopy
37
38
 
38
39
  from .PyTranslate import _
39
40
  from .GraphNotebook import PlotPanel
@@ -75,7 +76,7 @@ WOLF_ARRAY_MNAP_INTEGER = 20
75
76
  WOLF_ARRAY_MB = [WOLF_ARRAY_MB_SINGLE, WOLF_ARRAY_MB_INTEGER, WOLF_ARRAY_MNAP_INTEGER]
76
77
 
77
78
 
78
- def getkeyblock(i, addone=True):
79
+ def getkeyblock(i, addone=True) -> str:
79
80
  """
80
81
  Name/Key of a block in the dictionnary of a WolfArrayMB instance
81
82
 
@@ -86,12 +87,22 @@ def getkeyblock(i, addone=True):
86
87
  else:
87
88
  return 'block' + str(i)
88
89
 
90
+ def decodekeyblock(key, addone=True) -> int:
91
+ """
92
+ Decode key of a block in the dictionnary of a WolfArrayMB instance
93
+
94
+ For Fortran compatibility, addone is True by default so first block is "block1" and not "block0"
95
+ """
96
+ if addone:
97
+ return int(key[5:])
98
+ else:
99
+ return int(key[5:]) - 1
89
100
  class header_wolf():
90
101
  """
91
102
  Header of WolfArray
92
103
 
93
104
  In case of a mutliblock, the header have informations about all the blocks in head_blocks dictionnary.
94
- Keys are generated by "getkeyblock" function
105
+ Block keys are generated by "getkeyblock" function
95
106
  """
96
107
 
97
108
  # FIXME It'd be wise to put the multiblock case into another class.
@@ -101,13 +112,19 @@ class header_wolf():
101
112
  head_blocks: dict[str,"header_wolf"]
102
113
 
103
114
  def __init__(self) -> None:
104
- # Origin is the point in world space from which every other
105
- # Origin is the point in world sapce from which every other
106
- # coordinates are measured
115
+ """
116
+ Origin (origx, origy, [origz]) is the point in local space from which every other coordinates are measured.
117
+
118
+ Translation (translx, transly, [translz]) is the translation of the origin in global space. If translation is null, the origin is the same in local and global space. :-)
107
119
 
108
- # In the multibloc case, there's the origin for the whole
109
- # simulation and, for each block, the origin is ignored.
110
- # It is replaced
120
+ Resolution (dx, dy, [dz]) is the spatial resolution of the array.
121
+
122
+ Nullvalue is the value of the null value in the array.
123
+
124
+ (nbx, nby, [nbz]) are the number of cells in the array along X and Y [and Z]. It is the shape of the array.
125
+
126
+ @property nbdims is the number of dimensions of the array (2 or 3)
127
+ """
111
128
 
112
129
  self.origx = 0.0
113
130
  self.origy = 0.0
@@ -127,9 +144,10 @@ class header_wolf():
127
144
 
128
145
  self.head_blocks = {}
129
146
 
130
- self.nbdims = 0
147
+ self.nullvalue = 0.
131
148
 
132
149
  def __str__(self) -> str:
150
+ """ Return a string representation of the header """
133
151
  ret = ''
134
152
  ret += _('Shape : {} x {} \n').format(self.nbx, self.nby)
135
153
  ret += _('Resolution : {} x {} \n').format(self.dx, self.dy)
@@ -137,18 +155,59 @@ class header_wolf():
137
155
  ret += _(' - Origin : ({} ; {}) \n').format(self.origx, self.origy)
138
156
  ret += _(' - End : ({} ; {}) \n').format(self.origx + self.nbx * self.dx, self.origy +self.nby * self.dy)
139
157
  ret += _(' - Widht x Height : {} x {} \n').format(self.nbx * self.dx, self.nby * self.dy)
158
+ ret += _('Null value :{}\n'.format(self.nullvalue))
140
159
 
141
160
  return ret
142
161
 
162
+ @property
163
+ def nbdims(self):
164
+ if self.nbz == 0:
165
+ if self.nbx > 0 and self.nby > 0:
166
+ return 2
167
+ else:
168
+ return 0
169
+ elif self.nbz > 0:
170
+ return 3
171
+ else:
172
+ raise Exception(_('The number of dimensions is not correct'))
173
+
174
+ @nbdims.setter
175
+ def nbdims(self, value):
176
+ logging.warning(_('nbdims was an attribute of header_wolf.\nIt is now a read-only property.\nPlease use nbx, nby and nbz instead to define the shape of the array'))
177
+ raise Exception(_('This property is read-only'))
178
+
179
+ @property
180
+ def shape(self):
181
+ if self.nbdims == 2:
182
+ return (self.nbx, self.nby)
183
+ elif self.nbdims == 3:
184
+ return (self.nbx, self.nby, self.nbz)
185
+ else:
186
+ return (0, 0)
187
+
188
+ @shape.setter
189
+ def shape(self, value:tuple[int]):
190
+ if len(value) == 3:
191
+ self.nbx = value[0]
192
+ self.nby = value[1]
193
+ self.nbz = value[2]
194
+ elif len(value) == 2:
195
+ self.nbx = value[0]
196
+ self.nby = value[1]
197
+ self.nbz = 0
198
+ else:
199
+ raise Exception(_('The number of dimensions is not correct'))
200
+
143
201
  @property
144
202
  def nb_blocks(self):
145
203
  return len(self.head_blocks)
146
204
 
147
205
  def __getitem__(self, key:Union[int,str]=None):
148
206
  """
149
- return block header
207
+ Return block header
150
208
 
151
- :param key:int = block's index (0-based)
209
+ :param key:int = block's index (0-based) or key (str)
210
+ :return : header_wolf instance if key is found, None otherwise
152
211
  """
153
212
  if key is None:
154
213
  return self
@@ -163,17 +222,54 @@ class header_wolf():
163
222
  else:
164
223
  return None
165
224
 
166
- def set_orig(self, x:float, y:float, z:float):
225
+ def __setitem__(self, key:Union[int,str], value:"header_wolf"):
226
+ """
227
+ Set block header
228
+
229
+ :param value = tuple (key, header_wolf)
230
+
231
+ 'key' can be an int (0-based) or a str
232
+ If str, please use getkeyblock function to generate the key
233
+ """
234
+
235
+ if isinstance(key,int):
236
+ _key = getkeyblock(key)
237
+ else:
238
+ _key = key
239
+
240
+ self.head_blocks[_key] = deepcopy(value)
241
+
242
+ def set_origin(self, x:float, y:float, z:float):
243
+ """
244
+ Set origin
245
+
246
+ :param x = origin along X
247
+ :param y = origin along Y
248
+ :param z = origin along Z
249
+ """
167
250
  self.origx = x
168
251
  self.origy = y
169
252
  self.origz = z
170
253
 
171
- def set_transl(self, tr_x:float, tr_y:float, tr_z:float):
254
+ def set_translation(self, tr_x:float, tr_y:float, tr_z:float):
255
+ """
256
+ Set translation
257
+
258
+ :param tr_x = translation along X
259
+ :param tr_y = translation along Y
260
+ :param tr_z = translation along Z
261
+ """
172
262
  self.translx = tr_x
173
263
  self.transly = tr_y
174
264
  self.translz = tr_z
175
265
 
176
266
  def get_bounds(self, abs=True):
267
+ """
268
+ Return bounds in coordinates
269
+
270
+ :param abs = if True, add translation to (x, y) (coordinate to global space)
271
+ :return : tuple of two lists of two floats - ([xmin, xmax],[ymin, ymax])
272
+ """
177
273
  if abs:
178
274
  return ([self.origx + self.translx, self.origx + self.translx + float(self.nbx) * self.dx],
179
275
  [self.origy + self.transly, self.origy + self.transly + float(self.nby) * self.dy])
@@ -181,13 +277,19 @@ class header_wolf():
181
277
  return ([self.origx, self.origx + float(self.nbx) * self.dx],
182
278
  [self.origy, self.origy + float(self.nby) * self.dy])
183
279
 
184
- def get_bounds_ij(self, abs=True):
280
+ def get_bounds_ij(self, abs=False):
281
+ """
282
+ Return bounds in indices
283
+
284
+ Firstly, get_bounds is called to get bounds in coordinates and then get_ij_from_xy is called to get bounds in indices.
185
285
 
286
+ :param abs = if True, add translation to (x, y) (coordinate to global space)
287
+ """
186
288
  mybounds = self.get_bounds(abs)
187
289
 
188
290
  return (
189
- [self.get_ij_from_xy(mybounds[0][0], mybounds[1][0]), self.get_ij_from_xy(mybounds[0][1], mybounds[0][0])],
190
- [self.get_ij_from_xy(mybounds[0][0], mybounds[1][1]), self.get_ij_from_xy(mybounds[0][1], mybounds[1][1])])
291
+ [self.get_ij_from_xy(mybounds[0][0], mybounds[1][0], abs=abs), self.get_ij_from_xy(mybounds[0][1], mybounds[0][0], abs=abs)],
292
+ [self.get_ij_from_xy(mybounds[0][0], mybounds[1][1], abs=abs), self.get_ij_from_xy(mybounds[0][1], mybounds[1][1], abs=abs)])
191
293
 
192
294
  def get_ij_from_xy(self, x:float, y:float, z:float=0., scale:float=1., aswolf:bool=False, abs:bool=True, forcedims2:bool=False) -> Union[tuple[np.int32,np.int32], tuple[np.int32,np.int32,np.int32]]:
193
295
  """
@@ -251,7 +353,7 @@ class header_wolf():
251
353
  locxy[:,1] -= self.transly
252
354
 
253
355
  i = np.int32(locxy[:,0] / (self.dx * scale))
254
- j = np.int32(locxy[:,2] / (self.dy * scale))
356
+ j = np.int32(locxy[:,1] / (self.dy * scale))
255
357
 
256
358
  if aswolf:
257
359
  i += 1
@@ -261,7 +363,7 @@ class header_wolf():
261
363
  locxy[:,2] -= self.origz
262
364
  if abs:
263
365
  locxy[:,2] -= self.translz
264
- k = np.int32(locxy[:,3] / (self.dz * scale))
366
+ k = np.int32(locxy[:,2] / (self.dz * scale))
265
367
 
266
368
  if aswolf:
267
369
  k += 1
@@ -353,13 +455,47 @@ class header_wolf():
353
455
  xy[:,0] = (np.float64( (ij[:,0])+decali) + .5) * (self.dx*scale) + self.origx + tr_x
354
456
  xy[:,1] = (np.float64( (ij[:,1])+decalj) + .5) * (self.dy*scale) + self.origy + tr_y
355
457
 
356
- if self.nbdims == 3 and ij.shape[2]==3:
458
+ if self.nbdims == 3 and ij.shape[1]==3:
357
459
  xy[:,2] = (np.float64( (ij[:,2])+decalk) + .5) * (self.dz*scale) + self.origz + tr_z
358
460
 
359
461
  return xy
360
462
 
361
- def find_intersection(self, other, ij=False):
463
+ def ij2xy(self, i:int, j:int, k:int=0, scale:float=1., aswolf:bool=False, abs:bool=True) -> Union[tuple[np.float64,np.float64], tuple[np.float64,np.float64,np.float64]]:
464
+ """ alias for get_xy_from_ij """
465
+ return self.get_xy_from_ij(i, j, k, scale, aswolf, abs)
466
+
467
+ def ij2xy(self, ij:np.ndarray, scale:float=1., aswolf:bool=False, abs:bool=True) -> np.ndarray:
468
+ """ alias for get_xy_from_ij_array """
469
+ return self.get_xy_from_ij_array(ij, scale, aswolf, abs)
470
+
471
+ def xy2ij(self, x:float, y:float, z:float=0., scale:float=1., aswolf:bool=False, abs:bool=True, forcedims2:bool=False) -> Union[tuple[np.int32,np.int32], tuple[np.int32,np.int32,np.int32]]:
472
+ """ alias for get_ij_from_xy """
473
+ return self.get_ij_from_xy(x, y, z, scale, aswolf, abs, forcedims2)
362
474
 
475
+ def xy2ij(self, xy:np.ndarray, scale:float=1., aswolf:bool=False, abs:bool=True) -> np.ndarray:
476
+ """ alias for get_xy_from_ij_array """
477
+ return self.get_xy_from_ij_array(xy, scale, aswolf, abs)
478
+
479
+ def xyz2ijk(self, xyz:np.ndarray, scale:float=1., aswolf:bool=False, abs:bool=True) -> np.ndarray:
480
+ """ alias for get_xy_from_ij_array """
481
+ assert xyz.shape[1] == 3, _('xyz must be a 2D array with 3 columns')
482
+ return self.get_xy_from_ij_array(xyz, scale, aswolf, abs)
483
+
484
+ def ijk2xyz(self, ijk:np.ndarray, scale:float=1., aswolf:bool=False, abs:bool=True) -> np.ndarray:
485
+ """ alias for get_xy_from_ij_array """
486
+ assert ijk.shape[1] == 3, _('ijk must be a 2D array with 3 columns')
487
+ return self.get_xy_from_ij_array(ijk, scale, aswolf, abs)
488
+
489
+
490
+ def find_intersection(self, other:"header_wolf", ij:bool = False) -> Union[tuple[list[float],list[float]], tuple[list[list[float]],list[list[float]]]]:
491
+ """
492
+ Find the intersection of two header
493
+
494
+ @arg other: other header
495
+ @arg ij: if True, return indices instead of coordinates
496
+
497
+ :return: None or tuple of two lists of two floats - ([xmin, xmax],[ymin, ymax]) or indices in each header (if ij=True) [[imin1, imax1], [jmin1, jmax1]], [[imin2, imax2], [jmin2, jmax2]]
498
+ """
363
499
  mybounds = self.get_bounds()
364
500
  otherbounds = other.get_bounds()
365
501
 
@@ -387,7 +523,12 @@ class header_wolf():
387
523
  else:
388
524
  return ([ox, ex], [oy, ey])
389
525
 
390
- def find_union(self, other):
526
+ def find_union(self, other:"header_wolf") -> tuple[list[float],list[float]]:
527
+ """
528
+ Find the union of two header
529
+
530
+ :return: tuple of two lists of two floats - ([xmin, xmax],[ymin, ymax])
531
+ """
391
532
 
392
533
  mybounds = self.get_bounds()
393
534
  otherbounds = other.get_bounds()
@@ -400,7 +541,16 @@ class header_wolf():
400
541
  return ([ox, ex], [oy, ey])
401
542
 
402
543
  def read_txt_header(self, filename:str):
403
- """ Lecture du header .txt """
544
+ """
545
+ Read informations from header .txt
546
+
547
+ :param filename : path and filename of the basefile
548
+
549
+ If filename is a Path object, it is converted to a string
550
+ If filename ends with '.tif', nothing is done because infos are in the .tif file
551
+ If filename ends with '.flt', a .hdr file must be present and it will be read
552
+ Otherwise, a filename.txt file must be present
553
+ """
404
554
  if isinstance(filename, Path):
405
555
  filename = str(filename)
406
556
 
@@ -483,7 +633,6 @@ class header_wolf():
483
633
 
484
634
  decal = 9
485
635
  if self.wolftype == WOLF_ARRAY_FULL_SINGLE_3D:
486
- self.nbdims = 3
487
636
  tmp = lines[9].split(':')
488
637
  self.nbz = int(tmp[1])
489
638
  tmp = lines[10].split(':')
@@ -525,11 +674,13 @@ class header_wolf():
525
674
  wolftype:int,
526
675
  forceupdate:bool=False):
527
676
  """
528
- Ecriture du header dans un fichier texte
677
+ Writing the header to a text file
529
678
 
530
- filename : chemin d'accès et nom de fichier avec '.txt' qui ne sera pas ajouté automatiquement
531
- wolftype : type de la matrice WOLF_ARRAY_*
679
+ Nullvalue is not written
532
680
 
681
+ :param filename : path and filename with '.txt' extension, which will NOT be automatically added
682
+ :param wolftype : type of the WOLF_ARRAY_* array
683
+ :param forceupdate : if True, the file is rewritten even if it already exists
533
684
  """
534
685
 
535
686
  assert wolftype in [WOLF_ARRAY_CSR_DOUBLE, WOLF_ARRAY_FULL_SINGLE, WOLF_ARRAY_FULL_DOUBLE, WOLF_ARRAY_SYM_DOUBLE, WOLF_ARRAY_FULL_LOGICAL, WOLF_ARRAY_CSR_DOUBLE, WOLF_ARRAY_FULL_INTEGER, WOLF_ARRAY_FULL_SINGLE_3D, WOLF_ARRAY_FULL_INTEGER8, WOLF_ARRAY_MB_SINGLE, WOLF_ARRAY_MB_INTEGER, WOLF_ARRAY_FULL_INTEGER16, WOLF_ARRAY_MNAP_INTEGER]
@@ -564,8 +715,15 @@ class header_wolf():
564
715
  f.write('DX :\t{0}\n'.format(str(curhead.dx)))
565
716
  f.write('DY :\t{0}\n'.format(str(curhead.dy)))
566
717
 
567
- def is_like(self,other):
568
- """Comparison of two headers"""
718
+ def is_like(self, other:"header_wolf", check_mb:bool=False) -> bool:
719
+ """
720
+ Comparison of two headers
721
+
722
+ :param other : other header to compare
723
+ :param check_mb : if True, the comparison is done on the blocks too
724
+
725
+ The nullvalue is not taken into account
726
+ """
569
727
  test = True
570
728
  test &= self.origx == other.origx
571
729
  test &= self.origy == other.origy
@@ -585,8 +743,151 @@ class header_wolf():
585
743
 
586
744
  test &= self.nbdims == other.nbdims
587
745
 
746
+ if check_mb:
747
+ test &= self.nb_blocks == other.nb_blocks
748
+ for block1, block2 in zip(self.head_blocks.values(), other.head_blocks.values()):
749
+ test &= block1.is_like(block2)
750
+
588
751
  return test
589
752
 
753
+ def align2grid(self, x1:float, y1:float, eps:float=0.0001) -> tuple[float,float]:
754
+ """ Align coordinates to nearest grid point """
755
+
756
+ if x1-self.origx < 0:
757
+ x2 = np.round((x1 - self.origx + eps) / self.dx) * self.dx + self.origx
758
+ else:
759
+ x2 = np.round((x1 - self.origx - eps) / self.dx) * self.dx + self.origx
760
+
761
+ if y1-self.origy < 0:
762
+ y2 = np.round((y1 - self.origy + eps) / self.dy) * self.dy + self.origy
763
+ else:
764
+ y2 = np.round((y1 - self.origy - eps) / self.dy) * self.dy + self.origy
765
+
766
+ return x2, y2
767
+
768
+ def _rasterize_segment(self,
769
+ x1:float, y1:float,
770
+ x2:float, y2:float,
771
+ xstart:float=None, ystart:float=None) -> list[list[float]]:
772
+ """
773
+ Rasterize a segment according to the grid
774
+
775
+ :param x1: x coordinate of the first point
776
+ :param y1: y coordinate of the first point
777
+ :param x2: x coordinate of the second point
778
+ :param y2: y coordinate of the second point
779
+ :param xstart: x coordinate of the starting point
780
+ :param ystart: y coordinate of the starting point
781
+
782
+ :return: numpy array of the rasterized segment
783
+ """
784
+
785
+ if xstart is None and ystart is None:
786
+ xstart, ystart = self.align2grid(x1, y1)
787
+
788
+ x2, y2 = self.align2grid(x2, y2)
789
+
790
+ points=[]
791
+ points.append([xstart, ystart])
792
+
793
+ length = 99999.
794
+ prec = min(self.dx, self.dy)
795
+ direction = np.array([x2-xstart, y2-ystart])
796
+ length = np.linalg.norm(direction)
797
+ direction /= length
798
+
799
+ while length >= prec:
800
+
801
+ if np.abs(direction[0])>= np.abs(direction[1]):
802
+ xstart += self.dx * np.sign(direction[0])
803
+ else:
804
+ ystart += self.dy * np.sign(direction[1])
805
+
806
+ points.append([xstart, ystart])
807
+
808
+ direction = np.array([x2-xstart, y2-ystart])
809
+ length = np.linalg.norm(direction)
810
+ direction /= length
811
+
812
+ return points
813
+
814
+ def rasterize_vector(self, vector2raster:vector, outformat:Union[np.ndarray, vector]=vector) -> Union[np.ndarray,vector]:
815
+ """
816
+ Rasterize a vector according to the grid
817
+
818
+ :param vector2raster: vector to rasterize
819
+ :param outformat: output format (np.ndarray or vector)
820
+ """
821
+
822
+ assert outformat in [np.ndarray, vector], _('outformat must be np.ndarray or vector')
823
+
824
+ # get the vertices of the vector
825
+ xy = vector2raster.asnparray().tolist()
826
+
827
+ # rasterize the vector
828
+ rasterized = []
829
+ rasterized += self._rasterize_segment(xy[0][0], xy[0][1], xy[1][0], xy[1][1])
830
+
831
+ for i in range(1, len(xy)-1):
832
+ out = self._rasterize_segment(xy[i][0], xy[i][1],
833
+ xy[i+1][0], xy[i+1][1],
834
+ rasterized[-1][0], rasterized[-1][1])
835
+ rasterized += out[1:]
836
+
837
+ # get the indices of the rasterized vector
838
+ xy = np.array(rasterized)
839
+
840
+ if outformat is np.ndarray:
841
+ return xy
842
+ elif outformat is vector:
843
+ #create new vector
844
+ newvector = vector()
845
+ newvector.add_vertices_from_array(xy)
846
+
847
+ return newvector
848
+
849
+ def get_xy_infootprint_vect(self, myvect: vector) -> np.ndarray:
850
+ """
851
+ Return the coordinates of the cells in the footprint of a vector
852
+
853
+ :param myvect = target vector
854
+ """
855
+
856
+ myptsij = self.get_ij_infootprint_vect(myvect)
857
+ mypts=np.asarray(myptsij.copy(),dtype=np.float64)
858
+ mypts[:,0] = (mypts[:,0]+.5)*self.dx +self.origx +self.translx
859
+ mypts[:,1] = (mypts[:,1]+.5)*self.dy +self.origy +self.transly
860
+
861
+ return mypts,myptsij
862
+
863
+ def get_ij_infootprint_vect(self, myvect: vector) -> np.ndarray:
864
+ """
865
+ Return the indices of the cells in the footprint of a vector
866
+
867
+ :param myvect = target vector
868
+ """
869
+
870
+ i1, j1 = self.get_ij_from_xy(myvect.xmin, myvect.ymin)
871
+ i2, j2 = self.get_ij_from_xy(myvect.xmax, myvect.ymax)
872
+ i1 = max(i1,0) # FIXME Why ??? How could i,j be negative ? --> because this fucntion can be called with a vector that is not in the array (e.g. a vector defined by clicks in the UI)
873
+ j1 = max(j1,0)
874
+ i2 = min(i2,self.nbx-1)
875
+ j2 = min(j2,self.nby-1)
876
+ xv,yv = np.meshgrid(np.arange(i1,i2+1),np.arange(j1,j2+1))
877
+ mypts = np.hstack((xv.flatten()[:,np.newaxis],yv.flatten()[:,np.newaxis]))
878
+
879
+ return mypts
880
+
881
+ def convert_xy2ij_np(self,xy):
882
+ """ Convert XY coordinates to IJ indices with Numpy without any check/options """
883
+ return np.asarray((xy[:,0]-self.origx -self.translx)/self.dx-.5,dtype=np.int32), \
884
+ np.asarray((xy[:,1]-self.origy -self.transly)/self.dy-.5,dtype=np.int32)
885
+
886
+ def convert_ij2xy_np(self,xy):
887
+ """ Convert IJ indices to XY coordinates with Numpy without any check/options """
888
+ return np.asarray((xy[:,0]+.5)*self.dx+self.origx +self.translx ,dtype=np.float64), \
889
+ np.asarray((xy[:,1]+.5)*self.dy+self.origy +self.transly ,dtype=np.float64)
890
+
590
891
  class NewArray(wx.Dialog):
591
892
  """
592
893
  wx GUI interaction to create a new WolfArray
@@ -665,9 +966,10 @@ class NewArray(wx.Dialog):
665
966
  self.Centre(wx.BOTH)
666
967
 
667
968
 
969
+ #FIXME : Generalize to 3D
668
970
  class CropDialog(wx.Dialog):
669
971
  """
670
- wx GUI interaction to crop array's data
972
+ wx GUI interaction to crop 2D array's data
671
973
 
672
974
  Used in "read_data" of a WolfArray
673
975
  """
@@ -747,6 +1049,8 @@ class CropDialog(wx.Dialog):
747
1049
  self.Centre(wx.BOTH)
748
1050
 
749
1051
  def get_header(self):
1052
+ """ Return a header_wolf object with the values of the dialog """
1053
+
750
1054
  myhead = header_wolf()
751
1055
  myhead.origx = float(self.ox.Value)
752
1056
  myhead.origy = float(self.oy.Value)
@@ -754,19 +1058,23 @@ class CropDialog(wx.Dialog):
754
1058
  myhead.dy = float(self.dy.Value)
755
1059
  myhead.nbx = int((float(self.ex.Value) - myhead.origx) / myhead.dx)
756
1060
  myhead.nby = int((float(self.ey.Value) - myhead.origy) / myhead.dy)
757
- myhead.nbdims = 2
758
1061
 
759
1062
  return myhead
760
1063
 
761
1064
 
762
1065
  class Ops_Array(wx.Frame):
763
1066
  """
764
- Fenêtre d'opérations sur un WolfArray
1067
+ Operations wx.Frame on WolfArray class
765
1068
 
766
- Cet objet permet de manipuler les données d'un WolfArray
1069
+ This class is used to perform operations on a WolfArray
767
1070
  """
768
1071
 
769
1072
  def __init__(self, parentarray:"WolfArray", mapviewer=None):
1073
+ """ Init the Ops_Array class
1074
+
1075
+ :param parentarray: WolfArray to operate on
1076
+ :param mapviewer: WolfMapViewer to update if necessary
1077
+ """
770
1078
 
771
1079
  self.parentarray:WolfArray
772
1080
  self.parentarray = parentarray
@@ -777,6 +1085,7 @@ class Ops_Array(wx.Frame):
777
1085
 
778
1086
  self.wx_exists = wx.App.Get() is not None
779
1087
 
1088
+ # active objects
780
1089
  self.active_vector:vector = None
781
1090
  self.active_zone:zone = None
782
1091
  self.active_array:WolfArray = self.parentarray
@@ -793,7 +1102,7 @@ class Ops_Array(wx.Frame):
793
1102
  self.set_GUI()
794
1103
 
795
1104
  def get_mapviewer(self):
796
- """ Retourne une instance WolfMapViewer """
1105
+ """ Retourne l'instance WolfMapViewer """
797
1106
  return self.mapviewer
798
1107
 
799
1108
  def get_linked_arrays(self):
@@ -980,20 +1289,28 @@ class Ops_Array(wx.Frame):
980
1289
 
981
1290
  # Tools
982
1291
  Toolssizer = wx.BoxSizer(wx.VERTICAL)
1292
+
983
1293
  hbox = wx.BoxSizer(wx.HORIZONTAL)
1294
+
984
1295
  self.lbl_nullval = wx.StaticText(self.tools,label=_('Null value'))
985
1296
  self.txt_nullval = wx.TextCtrl(self.tools,value=str(self.parentarray.nullvalue), style=wx.TE_CENTER)
986
1297
  self.txt_nullval.SetToolTip(_('Array null value'))
987
1298
  hbox.Add(self.lbl_nullval, 0, wx.EXPAND|wx.ALL)
988
1299
  hbox.Add(self.txt_nullval, 1, wx.EXPAND|wx.ALL)
989
1300
 
990
- self.ApplyTools = wx.Button(self.tools, wx.ID_ANY, _("Apply modifications"), wx.DefaultPosition,
1301
+ self.ApplyTools = wx.Button(self.tools, wx.ID_ANY, _("Apply null value"), wx.DefaultPosition,
991
1302
  wx.DefaultSize, 0)
992
1303
 
1304
+
1305
+ self.nullborder = wx.Button(self.tools, wx.ID_ANY, _("Null border"), wx.DefaultPosition,
1306
+ wx.DefaultSize, 0)
1307
+
993
1308
  Toolssizer.Add(hbox, 0, wx.EXPAND)
994
1309
  Toolssizer.Add(self.ApplyTools, 1, wx.EXPAND)
1310
+ Toolssizer.Add(self.nullborder, 1, wx.EXPAND)
995
1311
 
996
1312
  self.ApplyTools.SetToolTip(_("Save modifications into memmory/object"))
1313
+ self.nullborder.SetToolTip(_("Set null value on the border of the array"))
997
1314
 
998
1315
  self.tools.SetSizer(Toolssizer)
999
1316
  self.tools.Layout()
@@ -1008,12 +1325,12 @@ class Ops_Array(wx.Frame):
1008
1325
  bSizer16 = wx.BoxSizer(wx.VERTICAL)
1009
1326
 
1010
1327
  selectmethodChoices = [_("by clicks"), _("inside active vector"), _("inside active zone"),
1011
- _("inside temporary vector"), _("under active vector"), _("under active zone"),
1012
- _("under temporary vector")]
1328
+ _("inside temporary vector"), _("along active vector"), _("along active zone"),
1329
+ _("along temporary vector")]
1013
1330
  self.selectmethod = wx.RadioBox(self.selection, wx.ID_ANY, _("How to select nodes?"), wx.DefaultPosition,
1014
1331
  wx.DefaultSize, selectmethodChoices, 1, wx.RA_SPECIFY_COLS)
1015
1332
  self.selectmethod.SetSelection(0)
1016
- self.selectmethod.SetToolTip(_("Selection mode : \n - one by one (keyboard shortcut N) \n- inside the currently activated polygon (keyboard shortcut V) \n- inside the currently activated zone (multipolygons) \n- inside a temporary polygon (keyboard shortcut B) \n- under the currently activated polyline \n- under the currently activated zone (multipolylines) \n- under a temporary polyline"))
1333
+ self.selectmethod.SetToolTip(_("Selection mode : \n - one by one (keyboard shortcut N) \n- inside the currently activated polygon (keyboard shortcut V) \n- inside the currently activated zone (multipolygons) \n- inside a temporary polygon (keyboard shortcut B) \n- along the currently activated polyline \n- along the currently activated zone (multipolylines) \n- along a temporary polyline"))
1017
1334
 
1018
1335
  bSizer16.Add(self.selectmethod, 0, wx.ALL, 5)
1019
1336
 
@@ -1227,7 +1544,8 @@ class Ops_Array(wx.Frame):
1227
1544
  self.saveas.Bind(wx.EVT_BUTTON, self.OnSaveasvec)
1228
1545
  self.save.Bind(wx.EVT_BUTTON, self.OnSavevec)
1229
1546
  self.ApplyOp.Bind(wx.EVT_BUTTON, self.OnApplyOpMath)
1230
- self.ApplyTools.Bind(wx.EVT_BUTTON, self.OnApplyTools)
1547
+ self.ApplyTools.Bind(wx.EVT_BUTTON, self.OnApplyNullvalue)
1548
+ self.nullborder.Bind(wx.EVT_BUTTON, self.OnNullBorder)
1231
1549
  self.SelectOp.Bind(wx.EVT_BUTTON, self.OnApplyOpSelect)
1232
1550
  self.palapply.Bind(wx.EVT_BUTTON, self.Onupdatepal)
1233
1551
  self.palsave.Bind(wx.EVT_BUTTON, self.Onsavepal)
@@ -1240,21 +1558,18 @@ class Ops_Array(wx.Frame):
1240
1558
  self.histoupdateerase.Bind(wx.EVT_BUTTON, self.OnClickHistoUpdate)
1241
1559
 
1242
1560
  def interpolation2D(self, event: wx.MouseEvent):
1561
+ """ calling Interpolation 2D """
1243
1562
  self.parentarray.interpolation2D()
1244
1563
 
1245
1564
  def Unmaskall(self, event: wx.MouseEvent):
1246
1565
  """
1247
- Enlève le masque des tous les éléments
1566
+ Unmask all values in the current array
1248
1567
  @author Pierre Archambeau
1249
1568
  """
1250
- curarray: WolfArray
1251
- curarray = self.parentarray
1252
- curarray.mask_reset()
1253
- curarray = self.parentarray
1254
- curarray.updatepalette()
1255
- curarray.delete_lists()
1569
+ self.parentarray.mask_reset()
1570
+ self.refresh_array()
1256
1571
 
1257
- def Unmasksel(self,event:wx.MouseEvent):
1572
+ def Unmasksel(self, event:wx.MouseEvent):
1258
1573
  """
1259
1574
  Enlève le masque des éléments sélectionnés
1260
1575
  @author Pierre Archambeau
@@ -1271,17 +1586,12 @@ class Ops_Array(wx.Frame):
1271
1586
 
1272
1587
  curarray.array.mask[destij[:, 0], destij[:, 1]] = False
1273
1588
 
1274
- curarray.updatepalette()
1275
- curarray.delete_lists()
1589
+ self.refresh_array()
1276
1590
 
1277
1591
  def InvertMask(self, event: wx.MouseEvent):
1278
-
1279
- curarray: WolfArray
1280
- curarray = self.parentarray
1281
- curarray.mask_invert()
1282
- curarray = self.parentarray
1283
- curarray.updatepalette()
1284
- curarray.delete_lists()
1592
+ """ Invert mask """
1593
+ self.parentarray.mask_invert()
1594
+ self.refresh_array()
1285
1595
 
1286
1596
  def interp2Dpolygons(self, event: wx.MouseEvent):
1287
1597
  """
@@ -1299,15 +1609,12 @@ class Ops_Array(wx.Frame):
1299
1609
  dlg.Destroy()
1300
1610
 
1301
1611
  actzone = self.active_zone
1302
- curarray: WolfArray
1303
- curarray = self.parentarray
1304
1612
 
1305
1613
  for curvec in actzone.myvectors:
1306
1614
  curvec: vector
1307
1615
  self._interp2Dpolygon(curvec, method)
1308
1616
 
1309
- curarray.updatepalette()
1310
- curarray.delete_lists()
1617
+ self.refresh_array()
1311
1618
 
1312
1619
  def interp2Dpolygon(self, event: wx.MouseEvent):
1313
1620
  """
@@ -1324,14 +1631,9 @@ class Ops_Array(wx.Frame):
1324
1631
  method = dlg.GetStringSelection()
1325
1632
  dlg.Destroy()
1326
1633
 
1327
- actzone = self.active_zone
1328
- curarray: WolfArray
1329
- curarray = self.parentarray
1330
-
1331
1634
  self._interp2Dpolygon(self.active_vector, method)
1332
1635
 
1333
- curarray.updatepalette()
1334
- curarray.delete_lists()
1636
+ self.refresh_array()
1335
1637
 
1336
1638
  def _interp2Dpolygon(self, vect: vector, method):
1337
1639
  """
@@ -1370,28 +1672,21 @@ class Ops_Array(wx.Frame):
1370
1672
  cf _interp2Dpolyline
1371
1673
  """
1372
1674
  actzone = self.active_zone
1373
- curarray: WolfArray
1374
- curarray = self.parentarray
1375
1675
 
1376
1676
  for curvec in actzone.myvectors:
1377
1677
  curvec: vector
1378
1678
  self._interp2Dpolyline(curvec)
1379
1679
 
1380
- curarray.updatepalette()
1381
- curarray.delete_lists()
1680
+ self.refresh_array()
1382
1681
 
1383
1682
  def interp2Dpolyline(self, event: wx.MouseEvent):
1384
1683
  """
1385
1684
  Bouton d'interpolation sous la polyligne active
1386
1685
  cf _interp2Dpolyline
1387
1686
  """
1388
- curarray: WolfArray
1389
- curarray = self.parentarray
1390
-
1391
1687
  self._interp2Dpolyline(self.active_vector)
1392
1688
 
1393
- curarray.updatepalette()
1394
- curarray.delete_lists()
1689
+ self.refresh_array()
1395
1690
 
1396
1691
  def _interp2Dpolyline(self, vect: vector, usemask=True):
1397
1692
  """
@@ -1515,14 +1810,17 @@ class Ops_Array(wx.Frame):
1515
1810
  self.reset_all_selection()
1516
1811
 
1517
1812
  def OnApplyOpSelect(self, event):
1518
- curcond = self.condition.GetSelection()
1813
+ """ Select nodes based on condition """
1519
1814
 
1815
+ # condition operator
1816
+ curcond = self.condition.GetSelection()
1817
+ # condition value
1520
1818
  curcondvalue = float(self.condvalue.GetValue())
1521
1819
 
1522
1820
  self.parentarray.mngselection.condition_select(curcond, curcondvalue)
1523
1821
 
1524
- def OnApplyTools(self, event:wx.MouseEvent):
1525
-
1822
+ def OnApplyNullvalue(self, event:wx.MouseEvent):
1823
+ """ Apply null value to the array """
1526
1824
  newnull = self.txt_nullval.Value
1527
1825
  if newnull.lower() == 'nan':
1528
1826
  newnull = np.nan
@@ -1532,25 +1830,52 @@ class Ops_Array(wx.Frame):
1532
1830
  if self.parentarray.nullvalue!= newnull:
1533
1831
  self.parentarray.nullvalue = newnull
1534
1832
  self.parentarray.mask_data(newnull)
1535
- self.parentarray.reset_plot()
1536
- if self.mapviewer is not None:
1537
- self.mapviewer.Refresh()
1833
+ self.refresh_array()
1538
1834
 
1539
- def OnApplyOpMath(self, event):
1835
+ def refresh_array(self):
1836
+ """ Force refresh of the parent array """
1837
+ self.parentarray.reset_plot()
1838
+
1839
+ def OnNullBorder(self, event:wx.MouseEvent):
1840
+ """ Nullify the border of the array """
1841
+
1842
+ dlg = wx.SingleChoiceDialog(None, "Choose the border width [number of nodes]", "Border width", [str(i) for i in range(1, 20)])
1843
+
1844
+ ret = dlg.ShowModal()
1845
+ if ret == wx.ID_CANCEL:
1846
+ dlg.Destroy()
1847
+ return
1540
1848
 
1849
+ borderwidth = int(dlg.GetStringSelection())
1850
+
1851
+ self.parentarray.nullify_border(borderwidth)
1852
+
1853
+ def OnApplyOpMath(self, event:wx.MouseEvent):
1854
+ """ Apply math operator to the array """
1855
+
1856
+ # operator type
1541
1857
  curop = self.choiceop.GetSelection()
1858
+ # condition type
1542
1859
  curcond = self.condition.GetSelection()
1543
1860
 
1861
+ # operator value
1544
1862
  opval = self.opvalue.GetValue()
1545
1863
  if opval.lower() == 'null' or opval.lower() == 'nan':
1546
1864
  curopvalue = self.parentarray.nullvalue
1547
1865
  else:
1548
1866
  curopvalue = float(opval)
1549
- curcondvalue = float(self.condvalue.GetValue())
1867
+
1868
+ # condition value
1869
+ curcondvalue = self.condvalue.GetValue()
1870
+ if curcondvalue.lower() == 'null' or curcondvalue.lower() == 'nan':
1871
+ curcondvalue = self.parentarray.nullvalue
1872
+ else:
1873
+ curcondvalue = float(curcondvalue)
1550
1874
 
1551
1875
  self.parentarray.mngselection.treat_select(curop, curcond, curopvalue, curcondvalue)
1552
1876
 
1553
- def Onmask(self, event):
1877
+ def Onmask(self, event:wx.MouseEvent):
1878
+ """ Mask nodes based on condition """
1554
1879
 
1555
1880
  curop = self.choiceop.GetSelection()
1556
1881
  curcond = self.condition.GetSelection()
@@ -1559,10 +1884,11 @@ class Ops_Array(wx.Frame):
1559
1884
  curcondvalue = float(self.condvalue.GetValue())
1560
1885
 
1561
1886
  self.parentarray.mngselection.mask_condition(curop, curcond, curopvalue, curcondvalue)
1562
- self.parentarray.reset_plot()
1563
- pass
1887
+ self.refresh_array()
1888
+
1889
+ def OnManageVectors(self, event:wx.MouseEvent):
1890
+ """ Open vector manager """
1564
1891
 
1565
- def OnManageVectors(self, event):
1566
1892
  if self.mapviewer is not None:
1567
1893
  if self.mapviewer.linked:
1568
1894
  if self.mapviewer.link_shareopsvect:
@@ -1572,7 +1898,9 @@ class Ops_Array(wx.Frame):
1572
1898
 
1573
1899
  self.myzones.showstructure()
1574
1900
 
1575
- def OnLoadvec(self, event):
1901
+ def OnLoadvec(self, event:wx.MouseEvent):
1902
+ """ Load vector file """
1903
+
1576
1904
  dlg = wx.FileDialog(None, 'Select file',
1577
1905
  wildcard='Vec file (*.vec)|*.vec|Vecz file (*.vecz)|*.vecz|Dxf file (*.dxf)|*.dxf|All (*.*)|*.*', style=wx.FD_OPEN)
1578
1906
 
@@ -1585,15 +1913,18 @@ class Ops_Array(wx.Frame):
1585
1913
  dlg.Destroy()
1586
1914
  self.myzones = Zones(self.fnsave, parent=self)
1587
1915
 
1916
+ # Link the same vector manager to all the linked arrays
1917
+ #FIXME : only works if the active_array is the good one
1588
1918
  if self.mapviewer is not None:
1589
1919
  if self.mapviewer.linked:
1590
1920
  if not self.mapviewer.linkedList is None:
1591
- for curFrame in self.mapviewer.linkedList:
1592
- if curFrame.link_shareopsvect:
1593
- curFrame.active_array.myops.myzones = self.myzones
1594
- curFrame.active_array.myops.fnsave = self.fnsave
1921
+ for curViewer in self.mapviewer.linkedList:
1922
+ if curViewer.link_shareopsvect:
1923
+ curViewer.active_array.myops.myzones = self.myzones
1924
+ curViewer.active_array.myops.fnsave = self.fnsave
1595
1925
 
1596
- def OnSaveasvec(self, event):
1926
+ def OnSaveasvec(self, event:wx.MouseEvent):
1927
+ """ Save vector file """
1597
1928
 
1598
1929
  dlg = wx.FileDialog(None, 'Select file', wildcard='Vec file (*.vec)|*.vec|Vecz file (*.vecz)|*.vecz|All (*.*)|*.*', style=wx.FD_SAVE)
1599
1930
 
@@ -1607,49 +1938,64 @@ class Ops_Array(wx.Frame):
1607
1938
 
1608
1939
  self.myzones.saveas(self.fnsave)
1609
1940
 
1941
+ # Link the same vector manager to all the linked arrays
1942
+ #FIXME : only works if the active_array is the good one
1610
1943
  if self.mapviewer is not None:
1611
1944
  if self.mapviewer.linked:
1612
1945
  if not self.mapviewer.linkedList is None:
1613
- for curFrame in self.mapviewer.linkedList:
1614
- if curFrame.link_shareopsvect:
1615
- curFrame.active_array.myops.fnsave = self.fnsave
1616
-
1617
- def OnSavevec(self, event):
1946
+ for curViewer in self.mapviewer.linkedList:
1947
+ if curViewer.link_shareopsvect:
1948
+ curViewer.active_array.myops.fnsave = self.fnsave
1618
1949
 
1950
+ def OnSavevec(self, event:wx.MouseEvent):
1951
+ """ Save vector file """
1619
1952
  if self.fnsave == '':
1620
1953
  return
1621
1954
 
1622
1955
  self.myzones.saveas(self.fnsave)
1623
1956
 
1624
1957
  def select_node_by_node(self):
1958
+ """
1959
+ Select nodes by individual clicks
1960
+
1961
+ Set the right action in the mapviewer who will attend the clicks
1962
+ """
1963
+
1625
1964
  if self.mapviewer is not None:
1626
1965
  self.mapviewer.action = 'select node by node'
1627
1966
  self.mapviewer.active_array = self.parentarray
1628
1967
 
1629
1968
  def select_zone_inside_manager(self):
1969
+ """
1970
+ Select nodes inside the active zone (manager)
1971
+ """
1630
1972
 
1631
1973
  if self.active_zone is None:
1632
- wx.MessageBox('Please select an active zone !')
1974
+ logging.warning(_('Please select an active zone !'))
1633
1975
  return
1634
1976
 
1635
1977
  for curvec in self.active_zone.myvectors:
1636
1978
  self._select_vector_inside_manager(curvec)
1637
1979
 
1638
1980
  def select_vector_inside_manager(self):
1981
+ """
1982
+ Select nodes inside the active vector (manager)
1983
+ """
1639
1984
  if self.active_vector is None:
1640
- wx.MessageBox('Please select an active vector !')
1985
+ logging.warning(_('Please select an active vector !'))
1641
1986
  return
1642
1987
 
1643
1988
  self._select_vector_inside_manager(self.active_vector)
1644
1989
 
1645
1990
  def _select_vector_inside_manager(self, vect: vector):
1991
+ """ Select nodes inside a vector or set action to add vertices to a vector by clicks"""
1646
1992
 
1647
- if len(vect.myvertices) > 2:
1993
+ if vect.nbvertices > 2:
1648
1994
  self.parentarray.mngselection.select_insidepoly(vect)
1649
1995
 
1650
1996
  elif self.mapviewer is not None:
1651
- if len(vect.myvertices) < 3:
1652
- wx.MessageBox('Please add points to vector by clicks !')
1997
+ if vect.nbvertices < 3:
1998
+ logging.info(_('Please add points to vector !'))
1653
1999
 
1654
2000
  self.mapviewer.action = 'select by vector inside'
1655
2001
  self.mapviewer.active_array = self.parentarray
@@ -1659,6 +2005,7 @@ class Ops_Array(wx.Frame):
1659
2005
  self.vectmp.add_vertex(firstvert)
1660
2006
 
1661
2007
  def select_zone_under_manager(self):
2008
+ """ Select nodes along the active zone (manager) """
1662
2009
 
1663
2010
  if self.active_zone is None:
1664
2011
  wx.MessageBox('Please select an active zone !')
@@ -1668,6 +2015,7 @@ class Ops_Array(wx.Frame):
1668
2015
  self._select_vector_under_manager(curvec)
1669
2016
 
1670
2017
  def select_vector_under_manager(self):
2018
+ """ Select nodes along the active vector (manager) """
1671
2019
  if self.active_vector is None:
1672
2020
  wx.MessageBox('Please select an active vector !')
1673
2021
  return
@@ -1675,15 +2023,16 @@ class Ops_Array(wx.Frame):
1675
2023
  self._select_vector_under_manager(self.active_vector)
1676
2024
 
1677
2025
  def _select_vector_under_manager(self, vect: vector):
2026
+ """ Select nodes along a vector or set action to add vertices to a vector by clicks """
1678
2027
 
1679
- if len(vect.myvertices) > 1:
2028
+ if vect.nbvertices > 1:
1680
2029
  self.parentarray.mngselection.select_underpoly(vect)
1681
2030
 
1682
2031
  elif self.mapviewer is not None:
1683
- if len(vect.myvertices) < 2:
1684
- wx.MessageBox('Please add points to vector by clicks !')
2032
+ if vect.nbvertices < 2:
2033
+ logging.info(_('Please add points to vector by clicks !'))
1685
2034
 
1686
- self.mapviewer.action = 'select by vector under'
2035
+ self.mapviewer.action = 'select by vector along'
1687
2036
  self.mapviewer.active_array = self.parentarray
1688
2037
  self.Active_vector(vect)
1689
2038
 
@@ -1691,7 +2040,10 @@ class Ops_Array(wx.Frame):
1691
2040
  self.vectmp.add_vertex(firstvert)
1692
2041
 
1693
2042
  def select_vector_inside_tmp(self):
2043
+ """ Select nodes inside the temporary vector """
2044
+
1694
2045
  if self.mapviewer is not None:
2046
+ logging.info(_('Please add points to vector by clicks !'))
1695
2047
  self.mapviewer.action = 'select by tmp vector inside'
1696
2048
  self.vectmp.reset()
1697
2049
  self.Active_vector(self.vectmp)
@@ -1701,8 +2053,10 @@ class Ops_Array(wx.Frame):
1701
2053
  self.vectmp.add_vertex(firstvert)
1702
2054
 
1703
2055
  def select_vector_under_tmp(self):
2056
+ """ Select nodes along the temporary vector """
1704
2057
  if self.mapviewer is not None:
1705
- self.mapviewer.action = 'select by tmp vector under'
2058
+ logging.info(_('Please add points to vector by clicks !'))
2059
+ self.mapviewer.action = 'select by tmp vector along'
1706
2060
  self.vectmp.reset()
1707
2061
  self.Active_vector(self.vectmp)
1708
2062
  self.mapviewer.active_array = self.parentarray
@@ -1710,7 +2064,9 @@ class Ops_Array(wx.Frame):
1710
2064
  firstvert = wolfvertex(0., 0.)
1711
2065
  self.vectmp.add_vertex(firstvert)
1712
2066
 
1713
- def OnLaunchSelect(self, event):
2067
+ def OnLaunchSelect(self, event:wx.MouseEvent):
2068
+ """ Action button """
2069
+
1714
2070
  id = self.selectmethod.GetSelection()
1715
2071
 
1716
2072
  if id == 0:
@@ -1732,23 +2088,24 @@ class Ops_Array(wx.Frame):
1732
2088
  logging.info(_(''))
1733
2089
  self.select_vector_inside_tmp()
1734
2090
  elif id == 4:
1735
- logging.info(_('Node selection under active vector (manager)'))
2091
+ logging.info(_('Node selection along active vector (manager)'))
1736
2092
  self.select_vector_under_manager()
1737
2093
  elif id == 5:
1738
- logging.info(_('Node selection under active zone (manager)'))
2094
+ logging.info(_('Node selection along active zone (manager)'))
1739
2095
  self.select_zone_under_manager()
1740
2096
  elif id == 6:
1741
- logging.info(_('Node selection under temporary vector'))
2097
+ logging.info(_('Node selection along temporary vector'))
1742
2098
  logging.info(_(''))
1743
2099
  logging.info(_(' Choose vector by clicks...'))
1744
2100
  logging.info(_(''))
1745
2101
  self.select_vector_under_tmp()
1746
2102
 
1747
- def onclose(self, event):
2103
+ def onclose(self, event:wx.MouseEvent):
2104
+ """ Hide the window """
1748
2105
  self.Hide()
1749
2106
 
1750
- def onshow(self, event):
1751
-
2107
+ def onshow(self, event:wx.MouseEvent):
2108
+ """ Show the window - set string with null value and update palette """
1752
2109
  if self.parentarray.nullvalue == np.nan:
1753
2110
  self.txt_nullval.Value = 'nan'
1754
2111
  else :
@@ -1756,7 +2113,9 @@ class Ops_Array(wx.Frame):
1756
2113
 
1757
2114
  self.update_palette()
1758
2115
 
1759
- def Active_vector(self, vect: vector, copyall=True):
2116
+ def Active_vector(self, vect: vector, copyall:bool=True):
2117
+ """ Set the active vector to vect and forward to mapviewer """
2118
+
1760
2119
  if vect is None:
1761
2120
  return
1762
2121
  self.active_vector = vect
@@ -1768,24 +2127,33 @@ class Ops_Array(wx.Frame):
1768
2127
  if self.mapviewer is not None and copyall:
1769
2128
  self.mapviewer.Active_vector(vect)
1770
2129
 
1771
- def Active_zone(self, zone):
1772
- self.active_zone = zone
2130
+ def Active_zone(self, target_zone:zone):
2131
+ """ Set the active zone to target_zone and forward to mapviewer """
2132
+ self.active_zone = target_zone
1773
2133
  if self.mapviewer is not None:
1774
- self.mapviewer.Active_zone(zone)
2134
+ self.mapviewer.Active_zone(target_zone)
1775
2135
 
1776
2136
  def update_palette(self):
2137
+ """
2138
+ Update palette
2139
+
2140
+ Redraw the palette with Matplotlib and fill the grid with the values and RGB components
2141
+ """
1777
2142
  self.Palette.add_ax()
1778
2143
  fig, ax = self.Palette.get_fig_ax()
1779
2144
  self.parentarray.mypal.plot(fig, ax)
1780
2145
  fig.canvas.draw()
1781
2146
  self.parentarray.mypal.fillgrid(self.palgrid)
1782
2147
 
1783
- def Onsavepal(self, event):
2148
+ def Onsavepal(self, event:wx.MouseEvent):
2149
+ """ Save palette to file """
2150
+
1784
2151
  myarray: WolfArray
1785
2152
  myarray = self.parentarray
1786
2153
  myarray.mypal.savefile()
1787
2154
 
1788
- def Onloadpal(self, event):
2155
+ def Onloadpal(self, event:wx.MouseEvent):
2156
+ """ Load palette from file """
1789
2157
  myarray: WolfArray
1790
2158
  myarray = self.parentarray
1791
2159
  myarray.mypal.readfile()
@@ -1793,17 +2161,16 @@ class Ops_Array(wx.Frame):
1793
2161
  myarray.mypal.automatic = False
1794
2162
  self.palauto.SetValue(0)
1795
2163
 
1796
- myarray.reset_plot()
1797
- # myarray.updatepalette(0)
1798
- # myarray.delete_lists()
1799
- self.update_palette()
2164
+ self.refresh_array()
1800
2165
 
1801
- def Onpalimage(self, event):
2166
+ def Onpalimage(self, event:wx.MouseEvent):
2167
+ """ Create image from palette """
1802
2168
  myarray: WolfArray
1803
2169
  myarray = self.parentarray
1804
2170
  myarray.mypal.export_image()
1805
2171
 
1806
- def Onpaldistribute(self, event):
2172
+ def Onpaldistribute(self, event:wx.MouseEvent):
2173
+ """ Distribute values in palette """
1807
2174
  myarray: WolfArray
1808
2175
  myarray = self.parentarray
1809
2176
  myarray.mypal.distribute_values()
@@ -1811,12 +2178,10 @@ class Ops_Array(wx.Frame):
1811
2178
  myarray.mypal.automatic = False
1812
2179
  self.palauto.SetValue(0)
1813
2180
 
1814
- myarray.reset_plot()
1815
- # myarray.updatepalette(0)
1816
- # myarray.delete_lists()
1817
- self.update_palette()
2181
+ self.refresh_array()
1818
2182
 
1819
- def Onupdatepal(self, event):
2183
+ def Onupdatepal(self, event:wx.MouseEvent):
2184
+ """ Apply options to palette """
1820
2185
  curarray: WolfArray
1821
2186
  curarray = self.parentarray
1822
2187
 
@@ -1860,10 +2225,10 @@ class Ops_Array(wx.Frame):
1860
2225
  curarray.shading = True
1861
2226
 
1862
2227
  if dellists:
1863
- curarray.reset_plot()
1864
- # curarray.delete_lists()
2228
+ self.refresh_array()
1865
2229
 
1866
2230
  def OnClickHistoUpdate(self, event: wx.Event):
2231
+ """ Create a histogram of the current array """
1867
2232
 
1868
2233
  itemlabel = event.GetEventObject().GetLabel()
1869
2234
  fig, ax = self.histo.get_fig_ax()
@@ -1888,7 +2253,8 @@ class Ops_Array(wx.Frame):
1888
2253
 
1889
2254
  fig.canvas.draw()
1890
2255
 
1891
- def OnClickColorPal(self, event):
2256
+ def OnClickColorPal(self, event: wx.Event):
2257
+ """ Edit color of a palette item """
1892
2258
 
1893
2259
  gridto = self.palgrid
1894
2260
  k = gridto.GetGridCursorRow()
@@ -1920,51 +2286,71 @@ class Ops_Array(wx.Frame):
1920
2286
 
1921
2287
  class SelectionData():
1922
2288
  """
1923
- Données sélectionnées par l'utilisateur dans un WolfArray
2289
+ User-selected data in a WolfArray
1924
2290
 
1925
- Contient deux éléments de stockage :
1926
- - myselection (list) : sélection courante qui sera perdue en cas reset
1927
- - selections( dict) : sélection(s) mémorisée(s) pour être exploitée par ex. dans une opération d'interpolation spatiale
1928
- Ces sélection ne sont perdues qu'en cas de reset général
2291
+ Contains two storage elements :
2292
+ - myselection (list): Current selection which will be lost in the event of a reset
2293
+ - selections( dict): Stored selection(s) to be used, for example, in a spatial interpolation operation.
2294
+ These selections are only lost in the event of a general reset.
1929
2295
 
1930
- Les mailles sélectionnées sont stockées via leurs coordonnées sptiales "monde" afin de pouvoir les transférer facilement à d'autres objets
2296
+ The selected nodes are stored using their "world" spatial coordinates so that they can be easily transferred to other objects.
1931
2297
  """
1932
- myselection: list
1933
- selections: dict
2298
+ myselection:list[tuple[float, float]]
2299
+ selections: dict[str:dict['select':list[tuple[float, float]], 'idgllist':int, 'color':list[float]]]
1934
2300
 
1935
- def __init__(self, parent) -> None:
2301
+ def __init__(self, parent:"WolfArray") -> None:
1936
2302
  self.parent: WolfArray
1937
2303
  self.parent = parent
1938
2304
 
2305
+ #copy of the resolution
1939
2306
  self.dx = parent.dx
1940
2307
  self.dy = parent.dy
1941
2308
 
1942
2309
  self.myselection = []
1943
2310
  self.selections = {}
1944
- self.update_plot_selection = False
2311
+
2312
+ self.update_plot_selection = False # force to update OpenGL list if True
1945
2313
  self.hideselection = False
1946
- self.numlist_select = 0
2314
+ self.numlist_select = 0 # OpenGL list index
1947
2315
 
1948
- def move_selectionto(self, idx, color):
1949
- """Transfert de la sélection courante dans un dictionnaire"""
2316
+ def move_selectionto(self, idx:str, color:list[float]):
2317
+ """
2318
+ Transfer current selection to dictionary
2319
+
2320
+ :param idx: id/key of the selection
2321
+ :param color: color of the selection - list of 3 floats between 0 and 255
2322
+ """
2323
+
2324
+ assert len(color) == 3, "color must be a list of 3 floats between 0 and 255"
2325
+
2326
+ # force idx to be a string
1950
2327
  idtxt = str(idx)
1951
2328
  self.selections[idtxt] = {}
1952
2329
  curdict = self.selections[idtxt]
1953
2330
 
1954
2331
  curdict['select'] = self.myselection
1955
- curdict['idgllist'] = 0
1956
- self.myselection = []
1957
- self.update_nb_nodes_sections()
2332
+ curdict['idgllist'] = 0 # will be created later - index of OpenGL list
1958
2333
  curdict['color'] = color
1959
2334
 
2335
+ self.myselection = [] # reset current selection
2336
+ self.update_nb_nodes_sections()
2337
+
1960
2338
  def plot_selection(self):
2339
+ """ Plot current selection and stored selections """
1961
2340
 
2341
+ # Make a copy of the current value of the flag because it will be modified in the function _plot_selection
2342
+ # So, if we want to update the plot, we need to apply the flag on each selection (current ans stored)
1962
2343
  update_select = self.update_plot_selection
2344
+
1963
2345
  if self.myselection != 'all':
2346
+ # plot current selection in RED if not 'all'
1964
2347
  if len(self.myselection) > 0:
1965
- self.numlist_select = self._plot_selection(self.myselection, (1., 0., 0.), self.numlist_select)
2348
+ self.numlist_select = self._plot_selection(self.myselection,
2349
+ (1., 0., 0.),
2350
+ self.numlist_select)
1966
2351
 
1967
2352
  if len(self.selections) > 0:
2353
+ # plot stored selections
1968
2354
  for cur in self.selections.values():
1969
2355
  if cur['select'] != 'all':
1970
2356
  self.update_plot_selection = update_select
@@ -1974,8 +2360,16 @@ class SelectionData():
1974
2360
  float(col[2]) / 255.),
1975
2361
  cur['idgllist'])
1976
2362
 
1977
- def _plot_selection(self, curlist, color, loclist=0):
2363
+ def _plot_selection(self, curlist:list[float], color:list[float], loclist:int=0):
2364
+ """
2365
+ Plot a selection
2366
+
2367
+ :param curlist: list of selected nodes -- list of tuples (x,y)
2368
+ :param color: color of the selection - list of 3 floats between 0 and 1
2369
+ :param loclist: index of OpenGL list
2370
+ """
1978
2371
 
2372
+ #FIXME : Is it a good idea to use SHADER rather than list ?
1979
2373
  if self.update_plot_selection:
1980
2374
  if loclist != 0:
1981
2375
  glDeleteLists(loclist, 1)
@@ -2020,22 +2414,41 @@ class SelectionData():
2020
2414
 
2021
2415
  return loclist
2022
2416
 
2023
- def add_node_to_selection(self, x, y, verif=True):
2024
- """Ajout d'une coordonnée à la sélection"""
2417
+ def add_node_to_selection(self, x:float, y:float, verif:bool=True):
2418
+ """
2419
+ Add one coordinate to the selection
2420
+
2421
+ :param x: x coordinate
2422
+ :param y: y coordinate
2423
+ :param verif: if True, the coordinates are checked to avoid duplicates
2424
+ """
2425
+
2025
2426
  # on repasse par les i,j car les coordonnées transférées peuvent venir d'un click souris
2026
2427
  # le but est de ne conserver que les coordonnées des CG de mailles
2027
2428
  i, j = self.parent.get_ij_from_xy(x, y)
2028
2429
  self.add_node_to_selectionij(i, j, verif)
2029
2430
 
2030
- def add_nodes_to_selection(self, xy, verif=True):
2031
- """Ajout d'une liste de coordonnées à la sélection"""
2431
+ def add_nodes_to_selection(self, xy:list[float], verif:bool=True):
2432
+ """
2433
+ Add multiple coordinates to the selection
2434
+
2435
+ :param xy: list of coordinates
2436
+ :param verif: if True, the coordinates are checked to avoid duplicates
2437
+ """
2438
+
2032
2439
  # on repasse par les i,j car les coordonnées transférées peuvent venir d'un click souris
2033
2440
  # le but est de ne conserver que les coordonnées des CG de mailles
2034
2441
  ij = [self.parent.get_ij_from_xy(x, y) for x, y in xy]
2035
2442
  self.add_nodes_to_selectionij(ij, verif)
2036
2443
 
2037
- def add_node_to_selectionij(self, i, j, verif=True):
2038
- """Ajout d'un couple d'indices à la sélection"""
2444
+ def add_node_to_selectionij(self, i:int, j:int, verif=True):
2445
+ """
2446
+ Add one ij coordinate to the selection
2447
+
2448
+ :param i: i coordinate
2449
+ :param j: j coordinate
2450
+ :param verif: if True, the coordinates are checked to avoid duplicates
2451
+ """
2039
2452
  x1, y1 = self.parent.get_xy_from_ij(i, j)
2040
2453
 
2041
2454
  if verif:
@@ -2050,8 +2463,14 @@ class SelectionData():
2050
2463
  else:
2051
2464
  self.myselection.append((x1, y1))
2052
2465
 
2053
- def add_nodes_to_selectionij(self, ij, verif=True):
2054
- """Ajout d'une liste de couples d'indices à la sélection"""
2466
+ def add_nodes_to_selectionij(self, ij:list[tuple[float, float]], verif:bool=True):
2467
+ """
2468
+ Add multiple ij coordinates to the selection
2469
+
2470
+ :param ij: list of ij coordinates
2471
+ :param verif: if True, the coordinates are checked to avoid duplicates
2472
+ """
2473
+
2055
2474
  if len(ij)==0:
2056
2475
  logging.info(_('Nothing to do in add_nodes_to_selectionij !'))
2057
2476
  return
@@ -2081,6 +2500,7 @@ class SelectionData():
2081
2500
  self.myselection = np.unique(self.myselection, axis=0)
2082
2501
 
2083
2502
  def select_insidepoly(self, myvect: vector):
2503
+ """ Select nodes inside a polygon """
2084
2504
 
2085
2505
  nbini = len(self.myselection)
2086
2506
 
@@ -2100,6 +2520,7 @@ class SelectionData():
2100
2520
  self.update_nb_nodes_sections()
2101
2521
 
2102
2522
  def select_underpoly(self, myvect: vector):
2523
+ """ Select nodes along a polyline """
2103
2524
 
2104
2525
  nbini = len(self.myselection)
2105
2526
 
@@ -2147,6 +2568,29 @@ class SelectionData():
2147
2568
  array = self.parent.array
2148
2569
  nbini = len(self.myselection)
2149
2570
 
2571
+ if array.dtype == np.float32:
2572
+ condval = np.float32(condval)
2573
+ condval2 = np.float32(condval2)
2574
+ elif array.dtype == np.float64:
2575
+ condval = np.float64(condval)
2576
+ condval2 = np.float64(condval2)
2577
+ elif array.dtype == np.int32:
2578
+ condval = np.int32(condval)
2579
+ condval2 = np.int32(condval2)
2580
+ elif array.dtype == np.int64:
2581
+ condval = np.int64(condval)
2582
+ condval2 = np.int64(condval2)
2583
+ elif array.dtype == np.int16:
2584
+ condval = np.int16(condval)
2585
+ condval2 = np.int16(condval2)
2586
+ elif array.dtype == np.int8:
2587
+ condval = np.int8(condval)
2588
+ condval2 = np.int8(condval2)
2589
+ else:
2590
+ logging.error(_('Unknown dtype in treat_select !'))
2591
+ return
2592
+
2593
+
2150
2594
  if usemask :
2151
2595
  mask=np.logical_not(array.mask)
2152
2596
  if nbini == 0:
@@ -2310,6 +2754,29 @@ class SelectionData():
2310
2754
  return np.isnan(val)
2311
2755
 
2312
2756
  array = self.parent.array
2757
+
2758
+ if array.dtype == np.float32:
2759
+ opval = np.float32(opval)
2760
+ condval = np.float32(condval)
2761
+ elif array.dtype == np.float64:
2762
+ opval = np.float64(opval)
2763
+ condval = np.float64(condval)
2764
+ elif array.dtype == np.int32:
2765
+ opval = np.int32(opval)
2766
+ condval = np.int32(condval)
2767
+ elif array.dtype == np.int64:
2768
+ opval = np.int64(opval)
2769
+ condval = np.int64(condval)
2770
+ elif array.dtype == np.int16:
2771
+ opval = np.int16(opval)
2772
+ condval = np.int16(condval)
2773
+ elif array.dtype == np.int8:
2774
+ opval = np.int8(opval)
2775
+ condval = np.int8(condval)
2776
+ else:
2777
+ logging.error(_('Unknown dtype in treat_select !'))
2778
+ return
2779
+
2313
2780
  if self.myselection == 'all':
2314
2781
  if op == 0:
2315
2782
  if cond == 0:
@@ -2437,6 +2904,10 @@ class SelectionData():
2437
2904
  ind = np.argwhere(np.logical_and(np.isnan(array), np.logical_not(array.mask)))
2438
2905
  array[ind[:, 0], ind[:, 1]] = opval
2439
2906
  else:
2907
+ if len(self.myselection) == 0:
2908
+ logging.info(_('Nothing to do in treat_select ! -- PLease select some nodes'))
2909
+ return
2910
+
2440
2911
  ij = [self.parent.get_ij_from_xy(cur[0], cur[1]) for cur in self.myselection]
2441
2912
 
2442
2913
  if op == 0:
@@ -2461,9 +2932,13 @@ class SelectionData():
2461
2932
  array.data[i, j] = opval
2462
2933
 
2463
2934
  self.parent.mask_data(self.parent.nullvalue)
2935
+
2936
+ self.refresh_parantarray()
2937
+
2938
+ def refresh_parantarray(self):
2939
+ """ Refresh the parent array after a selection """
2940
+
2464
2941
  self.parent.reset_plot()
2465
- if self.parent.mapviewer is not None:
2466
- self.parent.mapviewer.Refresh()
2467
2942
 
2468
2943
  def mask_condition(self, op, cond, opval, condval):
2469
2944
  # operationChoices = [ u"+", u"-", u"*", u"/", u"replace'" ]
@@ -2483,6 +2958,29 @@ class SelectionData():
2483
2958
  return np.isnan(val)
2484
2959
 
2485
2960
  array = self.parent.array
2961
+
2962
+ if array.dtype == np.float32:
2963
+ opval = np.float32(opval)
2964
+ condval = np.float32(condval)
2965
+ elif array.dtype == np.float64:
2966
+ opval = np.float64(opval)
2967
+ condval = np.float64(condval)
2968
+ elif array.dtype == np.int32:
2969
+ opval = np.int32(opval)
2970
+ condval = np.int32(condval)
2971
+ elif array.dtype == np.int64:
2972
+ opval = np.int64(opval)
2973
+ condval = np.int64(condval)
2974
+ elif array.dtype == np.int16:
2975
+ opval = np.int16(opval)
2976
+ condval = np.int16(condval)
2977
+ elif array.dtype == np.int8:
2978
+ opval = np.int8(opval)
2979
+ condval = np.int8(condval)
2980
+ else:
2981
+ logging.error(_('Unknown dtype in treat_select !'))
2982
+ return
2983
+
2486
2984
  if self.myselection == 'all':
2487
2985
  if cond == 0:
2488
2986
  # <
@@ -2553,8 +3051,6 @@ class SelectionData():
2553
3051
  myhead.nbx = int((ex - myhead.origx) / array.dx)
2554
3052
  myhead.nby = int((ey - myhead.origy) / array.dy)
2555
3053
 
2556
- myhead.nbdims=2
2557
-
2558
3054
  return myhead
2559
3055
 
2560
3056
  def get_newarray(self):
@@ -2613,7 +3109,8 @@ class WolfArray(Element_To_Draw, header_wolf):
2613
3109
  srcheader=None, # initialize dimension from header_wolf instance
2614
3110
  idx:str = '', # indentity --> required by the mapviewer
2615
3111
  plotted:bool = False, # True = will be plotted if required by the mapviewer
2616
- need_for_wx:bool = False # True = a wxApp is required (if no application is underway --> Error)
3112
+ need_for_wx:bool = False, # True = a wxApp is required (if no application is underway --> Error)
3113
+ mask_source:np.ndarray = None, # mask to link to the data
2617
3114
  ) -> None:
2618
3115
 
2619
3116
  Element_To_Draw.__init__(self, idx, plotted, mapviewer, need_for_wx)
@@ -2629,7 +3126,6 @@ class WolfArray(Element_To_Draw, header_wolf):
2629
3126
  self.linkedarrays = []
2630
3127
 
2631
3128
  self.filename = ''
2632
- self.nbdims = 2
2633
3129
  self.isblock = False
2634
3130
  self.blockindex = 0
2635
3131
  self.wolftype = whichtype
@@ -2686,17 +3182,22 @@ class WolfArray(Element_To_Draw, header_wolf):
2686
3182
 
2687
3183
  self.head_blocks = header.head_blocks.copy()
2688
3184
 
2689
- # FIXME Why not initialize with nullvalue ?
2690
- self.array = ma.MaskedArray(np.ones((self.nbx, self.nby), order='F', dtype=self.dtype))
3185
+ self.allocate_ressources()
3186
+
3187
+ # # FIXME Why not initialize with nullvalue ?
3188
+ # self.array = ma.MaskedArray(np.ones((self.nbx, self.nby), order='F', dtype=self.dtype))
2691
3189
 
2692
3190
  if fname is not None:
2693
- self.filename = fname
3191
+
3192
+ self.filename = str(fname)
2694
3193
  self.read_all()
2695
- if masknull and self.preload:
3194
+
3195
+ if mask_source is not None:
3196
+ self.copy_mask_log(mask_source)
3197
+ elif masknull and self.preload:
2696
3198
  self.mask_data(self.nullvalue)
2697
3199
 
2698
3200
  elif mold is not None:
2699
- self.nbdims = mold.nbdims
2700
3201
  self.nbx = mold.nbx
2701
3202
  self.nby = mold.nby
2702
3203
  self.nbz = mold.nbz
@@ -2726,12 +3227,11 @@ class WolfArray(Element_To_Draw, header_wolf):
2726
3227
  self.add_ops_sel() # Ajout d'un gestionnaire de sélection et d'opérations
2727
3228
 
2728
3229
  def show_properties(self):
2729
- """ Affichage des propriétés de la matrice """
3230
+ """ Affichage des propriétés de la matrice dans une fenêtre wxPython """
2730
3231
  if self.wx_exists and self.myops is not None:
2731
3232
  self.myops.SetTitle(_('Operations on array: ') + self.idx)
2732
3233
  self.myops.Show()
2733
3234
 
2734
-
2735
3235
  @property
2736
3236
  def dtype(self):
2737
3237
  """
@@ -2763,6 +3263,14 @@ class WolfArray(Element_To_Draw, header_wolf):
2763
3263
  return dtype
2764
3264
 
2765
3265
  def loadnap_and_apply(self):
3266
+ """
3267
+ Load a mask file (aka nap) and apply it to the array;
3268
+
3269
+ The mask values are set to the nullvalue.
3270
+
3271
+ The mask file must have the same name as the array file, with the extension .napbin.
3272
+ It is useful for 2D WOLF simulations.
3273
+ """
2766
3274
 
2767
3275
  file_name, file_extension = os.path.splitext(self.filename)
2768
3276
  fnnap = file_name + '.napbin'
@@ -2772,8 +3280,7 @@ class WolfArray(Element_To_Draw, header_wolf):
2772
3280
  self.array.data[np.where(locnap.array.mask)] = self.nullvalue
2773
3281
  self.mask_data(self.nullvalue)
2774
3282
 
2775
- if self.mapviewer is not None:
2776
- self.reset_plot()
3283
+ self.reset_plot()
2777
3284
 
2778
3285
  def add_crosslinked_array(self, newlink:"WolfArray"):
2779
3286
  """Ajout d'une matrice liée croisée"""
@@ -2796,17 +3303,19 @@ class WolfArray(Element_To_Draw, header_wolf):
2796
3303
  if self.linkedvec is not None:
2797
3304
  self.mask_outsidepoly(self.linkedvec)
2798
3305
 
2799
- if self.mapviewer is not None:
2800
- self.reset_plot()
3306
+ self.reset_plot()
2801
3307
 
2802
- def export_geotif(self,outdir='',extent = ''):
3308
+ def export_geotif(self, outdir='', extent = ''):
2803
3309
  """
2804
- Export de la matrice au format Geotiff
3310
+ Export de la matrice au format Geotiff (Lambert 72 - EPSG:31370)
2805
3311
 
2806
3312
  Formats supportés :
2807
3313
  - Int32
2808
3314
  - Float32
2809
3315
  - Float64
3316
+
3317
+ :param outdir: directory
3318
+ :param extent: suffix to add to the filename before the extension '.tif'
2810
3319
  """
2811
3320
  from osgeo import gdal, osr, gdalconst
2812
3321
 
@@ -2849,7 +3358,7 @@ class WolfArray(Element_To_Draw, header_wolf):
2849
3358
  band.FlushCache()
2850
3359
  band.ComputeStatistics(True)
2851
3360
 
2852
- def import_geotif(self, fn:str='', which = None, crop=None):
3361
+ def import_geotif(self, fn:str='', which:int = None, crop:list[float]=None):
2853
3362
  """
2854
3363
  Import de la matrice au format Geotiff
2855
3364
 
@@ -2857,6 +3366,10 @@ class WolfArray(Element_To_Draw, header_wolf):
2857
3366
  - Int32
2858
3367
  - Float32
2859
3368
  - Float64
3369
+
3370
+ :param fn: filename
3371
+ :param which: band to import
3372
+ :param crop: crop the data - [xmin, xmax, ymin, ymax]
2860
3373
  """
2861
3374
  from osgeo import gdal, osr, gdalconst
2862
3375
 
@@ -2968,7 +3481,13 @@ class WolfArray(Element_To_Draw, header_wolf):
2968
3481
  def change_gui(self, newparentgui):
2969
3482
  """
2970
3483
  Move GUI to another instance
3484
+
3485
+ :param newparentgui: WolfMapViewer instance
2971
3486
  """
3487
+
3488
+ from .PyDraw import WolfMapViewer
3489
+ assert isinstance(newparentgui, WolfMapViewer), _('newparentgui must be a WolfMapViewer instance')
3490
+
2972
3491
  self.wx_exists = wx.App.Get() is not None
2973
3492
 
2974
3493
  if self.mapviewer is None:
@@ -3020,7 +3539,7 @@ class WolfArray(Element_To_Draw, header_wolf):
3020
3539
  plt.show()
3021
3540
 
3022
3541
  def compare_tri(self,mytri:Triangulation):
3023
-
3542
+ """ Graphique de comparaison des valeurs d'un nuage de points et des valeurs de la matrice sous les mêmes positions """
3024
3543
  xyz_cloud = mytri.pts
3025
3544
  zarray = np.array([self.get_value(curxy[0],curxy[1]) for curxy in xyz_cloud])
3026
3545
 
@@ -3055,7 +3574,7 @@ class WolfArray(Element_To_Draw, header_wolf):
3055
3574
 
3056
3575
  plt.show()
3057
3576
 
3058
- def interpolate_on_cloud(self, xy, z, method='linear'):
3577
+ def interpolate_on_cloud(self, xy:np.ndarray, z:np.ndarray, method='linear'):
3059
3578
  """
3060
3579
  See : https://docs.scipy.org/doc/scipy/reference/generated/scipy.interpolate.griddata.html
3061
3580
 
@@ -3216,8 +3735,7 @@ class WolfArray(Element_To_Draw, header_wolf):
3216
3735
  self.reset_plot()
3217
3736
  return
3218
3737
 
3219
- def import_from_gltf(self, fn='', fnpos='', interp_method = 'matplotlib'):
3220
-
3738
+ def import_from_gltf(self, fn:str='', fnpos:str='', interp_method:Literal['matplotlib','numpy'] = 'matplotlib'):
3221
3739
  """
3222
3740
  interp_method == 'matplotlib' or 'griddata'
3223
3741
  """
@@ -3274,7 +3792,13 @@ class WolfArray(Element_To_Draw, header_wolf):
3274
3792
 
3275
3793
  self.reset_plot()
3276
3794
 
3277
- def export_to_gltf(self, bounds=None, fn=''):
3795
+ def export_to_gltf(self, bounds:list[float]=None, fn:str=''):
3796
+ """
3797
+ Export to GLTF/GLB format
3798
+
3799
+ :param bounds: [[xmin,xmax],[ymin,ymax]]
3800
+ :param fn: filename
3801
+ """
3278
3802
 
3279
3803
  mytri, znull = self.get_triangulation(bounds)
3280
3804
  mytri.export_to_gltf(fn)
@@ -3321,7 +3845,12 @@ class WolfArray(Element_To_Draw, header_wolf):
3321
3845
  f.write(str(bounds[1][1]) + '\n')
3322
3846
  f.write(str(znull))
3323
3847
 
3324
- def get_triangulation(self, bounds=None):
3848
+ def get_triangulation(self, bounds:list[float]=None):
3849
+ """
3850
+ Traingulation of the array
3851
+
3852
+ :param bounds: [[xmin,xmax],[ymin,ymax]]
3853
+ """
3325
3854
 
3326
3855
  if bounds is None:
3327
3856
  ox = self.origx + self.translx
@@ -3376,7 +3905,9 @@ class WolfArray(Element_To_Draw, header_wolf):
3376
3905
  mytri = Triangulation(pts = points, tri = triangles)
3377
3906
  return mytri, znull
3378
3907
 
3379
- def hillshade(self, azimuth, angle_altitude):
3908
+ def hillshade(self, azimuth:float, angle_altitude:float):
3909
+ """ Create a hillshade array """
3910
+
3380
3911
  azimuth = 360.0 - azimuth
3381
3912
 
3382
3913
  x, y = np.gradient(self.array)
@@ -3395,6 +3926,7 @@ class WolfArray(Element_To_Draw, header_wolf):
3395
3926
  self.shaded.delete_lists()
3396
3927
 
3397
3928
  def get_gradient_norm(self):
3929
+ """ Compute and return the norm of the gradient """
3398
3930
 
3399
3931
  mygradient = WolfArray(mold=self)
3400
3932
 
@@ -3405,6 +3937,7 @@ class WolfArray(Element_To_Draw, header_wolf):
3405
3937
  return mygradient
3406
3938
 
3407
3939
  def get_laplace(self):
3940
+ """ Compute and return the laplacian """
3408
3941
  mylap = WolfArray(mold=self)
3409
3942
  mylap.array = ma.asarray(laplace(self.array) / self.dx ** 2.)
3410
3943
  mylap.array.mask = self.array.mask
@@ -3412,6 +3945,7 @@ class WolfArray(Element_To_Draw, header_wolf):
3412
3945
  return mylap
3413
3946
 
3414
3947
  def volume_estimation(self, axs=None):
3948
+ """ Estimation of the volume of the selected zone """
3415
3949
 
3416
3950
  vect = self.array[np.logical_not(self.array.mask)].flatten()
3417
3951
  zmin = np.amin(vect)
@@ -3534,7 +4068,9 @@ class WolfArray(Element_To_Draw, header_wolf):
3534
4068
 
3535
4069
  return axs
3536
4070
 
3537
- def paste_all(self, fromarray):
4071
+ def paste_all(self, fromarray:"WolfArray"):
4072
+ """ Paste all the values from another WolfArray """
4073
+
3538
4074
  fromarray: WolfArray
3539
4075
 
3540
4076
  i1, j1 = self.get_ij_from_xy(fromarray.origx, fromarray.origy)
@@ -3560,8 +4096,14 @@ class WolfArray(Element_To_Draw, header_wolf):
3560
4096
  self.mask_data(self.nullvalue)
3561
4097
  self.reset_plot()
3562
4098
 
3563
- def set_values_sel(self, xy, z,update=True):
4099
+ def set_values_sel(self, xy:list[float], z:list[float], update:bool=True):
4100
+ """
4101
+ Set values at the selected positions
3564
4102
 
4103
+ :param xy: [[x1,y1],[x2,y2],...]
4104
+ :param z: [z1,z2,...]
4105
+ :param update: update the plot
4106
+ """
3565
4107
  sel = np.asarray(xy)
3566
4108
 
3567
4109
  if len(sel) == 1:
@@ -3574,8 +4116,7 @@ class WolfArray(Element_To_Draw, header_wolf):
3574
4116
  else:
3575
4117
  ijall = np.asarray(self.get_ij_from_xy(sel[:, 0], sel[:, 1])).transpose()
3576
4118
 
3577
- useful = np.where(
3578
- (ijall[:, 0] >= 0) & (ijall[:, 0] < self.nbx) & (ijall[:, 1] >= 0) & (ijall[:, 1] < self.nby))
4119
+ useful = np.where((ijall[:, 0] >= 0) & (ijall[:, 0] < self.nbx) & (ijall[:, 1] >= 0) & (ijall[:, 1] < self.nby))
3579
4120
 
3580
4121
  self.array[ijall[useful, 0], ijall[useful, 1]] = z[useful]
3581
4122
 
@@ -3585,6 +4126,10 @@ class WolfArray(Element_To_Draw, header_wolf):
3585
4126
  self.reset_plot()
3586
4127
 
3587
4128
  def init_from_new(self, dlg: NewArray):
4129
+ """
4130
+ Initialize the array properties from the NewArray dialog
4131
+ """
4132
+
3588
4133
  self.dx = float(dlg.dx.Value)
3589
4134
  self.dy = float(dlg.dy.Value)
3590
4135
  self.nbx = int(dlg.nbx.Value)
@@ -3595,7 +4140,16 @@ class WolfArray(Element_To_Draw, header_wolf):
3595
4140
  self.array = ma.MaskedArray(np.ones((self.nbx, self.nby), order='F', dtype=np.float32))
3596
4141
  self.mask_reset()
3597
4142
 
3598
- def init_from_header(self, myhead: header_wolf, dtype:np.dtype = None):
4143
+ def init_from_header(self, myhead: header_wolf, dtype:np.dtype = None, force_type_from_header:bool=False):
4144
+ """
4145
+ Initialize the array properties from a header_wolf object
4146
+
4147
+ :param myhead: header_wolf object
4148
+ :param dtype: numpy dtype
4149
+ :param force_type_from_header: force the type from the header passed as argument
4150
+ """
4151
+ if force_type_from_header:
4152
+ self.wolftype = myhead.wolftype
3599
4153
 
3600
4154
  if dtype is None:
3601
4155
  if self.wolftype == WOLF_ARRAY_FULL_DOUBLE:
@@ -3619,11 +4173,15 @@ class WolfArray(Element_To_Draw, header_wolf):
3619
4173
  self.array = ma.MaskedArray(np.ones((self.nbx, self.nby), order='F', dtype=dtype))
3620
4174
  self.mask_reset()
3621
4175
 
3622
- def interpolation2D(self):
4176
+ def interpolation2D(self, key:str='1'):
4177
+ """ Interpolation 2D basde on selected points in key 1 """
4178
+
4179
+ #FIXME : auhtorize interpolation on other keys
3623
4180
 
3624
- if '1' in self.mngselection.selections.keys():
4181
+ key = str(key)
4182
+ if key in self.mngselection.selections.keys():
3625
4183
  if len(self.mngselection.myselection)>0:
3626
- curlist = self.mngselection.selections['1']['select']
4184
+ curlist = self.mngselection.selections[key]['select']
3627
4185
  cursel = self.mngselection.myselection
3628
4186
  if len(curlist) > 0:
3629
4187
  ij = [self.get_ij_from_xy(cur[0], cur[1]) for cur in curlist]
@@ -3645,60 +4203,98 @@ class WolfArray(Element_To_Draw, header_wolf):
3645
4203
 
3646
4204
  self.reset_plot()
3647
4205
 
3648
- def copy_mask(self, source:"WolfArray", forcenullvalue= False):
4206
+ def copy_mask(self, source:"WolfArray", forcenullvalue:bool= False, link:bool=True):
4207
+ """
4208
+ Copy/Link the mask from another WolfArray
4209
+
4210
+ :param source: WolfArray source
4211
+ :param forcenullvalue: force nullvalue in the masked zone
4212
+ :param link: link the mask if True (default), copy it otherwise
4213
+ """
4214
+
4215
+ assert self.shape == source.shape, _('Bad shape')
3649
4216
 
3650
4217
  if forcenullvalue:
3651
4218
  self.array[np.where(source.array.mask)] = self.nullvalue
3652
4219
 
3653
- self.array.mask = source.array.mask
4220
+ if link:
4221
+ self.array.mask = source.array.mask
4222
+ else:
4223
+ self.array.mask = source.array.mask.copy()
4224
+
3654
4225
  self.nbnotnull = source.nbnotnull
3655
4226
 
3656
- if self.plotted:
3657
- self.reset_plot()
4227
+ self.reset_plot()
3658
4228
 
3659
- def count(self):
3660
- self.nbnotnull = self.array.count()
4229
+ def mask_union(self, source:"WolfArray", link:bool=True):
4230
+ """
4231
+ Union of the mask with another WolfArray
3661
4232
 
3662
- def mask_union(self, source:"WolfArray"):
4233
+ :param source: WolfArray source
4234
+ :param link: link the mask if True (default), copy it otherwise
4235
+ """
3663
4236
 
3664
4237
  union = self.array.mask & source.array.mask
3665
4238
 
3666
- self.array[(~union) & (self.array.mask)] = 0.
3667
- source.array[(~union) & (source.array.mask)] = 0.
4239
+ self.array[(~union) & (self.array.mask)] = self.nullvalue
4240
+ source.array[(~union) & (source.array.mask)] = self.nullvalue
3668
4241
 
3669
- self.array.mask = union
3670
- source.array.mask = union
3671
4242
 
3672
- self.count()
4243
+ if link:
4244
+ self.array.mask = union
4245
+ source.array.mask = union
4246
+ else:
4247
+ self.array.mask = union.copy()
4248
+ source.array.mask = union.copy()
3673
4249
 
3674
- if self.plotted:
3675
- self.reset_plot()
4250
+ self.reset_plot()
4251
+ source.reset_plot()
3676
4252
 
3677
- def copy_mask_log(self,mask):
3678
- self.array.mask = mask
3679
- self.count()
4253
+ def copy_mask_log(self, mask:np.ndarray, link:bool=True):
4254
+ """
4255
+ Copy the mask from a numpy array
3680
4256
 
3681
- if self.plotted:
3682
- self.reset_plot()
4257
+ :param mask: numpy array
4258
+ :param link: link the mask if True (default), copy it otherwise
4259
+ """
4260
+ assert self.shape == mask.shape, _('Bad shape')
4261
+
4262
+ if link:
4263
+ self.array.mask = mask
4264
+ else:
4265
+ self.array.mask = mask.copy()
4266
+
4267
+ self.reset_plot()
3683
4268
 
3684
4269
  def check_plot(self):
4270
+ """ Make sure the array is plotted """
4271
+
3685
4272
  self.plotted = True
3686
4273
 
3687
4274
  if not self.loaded and self.filename != '':
4275
+ # if not loaded, load it
3688
4276
  self.read_data()
3689
4277
  if self.masknull:
3690
- self.mask_data(0.)
4278
+ self.mask_data(self.nullvalue)
3691
4279
 
3692
4280
  self.loaded = True
3693
4281
 
3694
4282
  if self.rgb is None:
3695
4283
  self.updatepalette(0)
3696
4284
 
3697
- def uncheck_plot(self, unload=True, forceresetOGL=False, askquestion=True):
4285
+ def uncheck_plot(self, unload:bool=True, forceresetOGL:bool=False, askquestion:bool=True):
4286
+ """
4287
+ Make sure the array is not plotted
4288
+
4289
+ :param unload: unload the data if True (default), keep it otherwise
4290
+ :param forceresetOGL: force the reset of the OpenGL lists
4291
+ :param askquestion: ask the question if True and a wx App is running (default), don't ask it otherwise
4292
+ """
4293
+
3698
4294
  self.plotted = False
3699
4295
 
3700
4296
  if unload and self.filename != '':
3701
- if askquestion:
4297
+ if askquestion and self.wx_exists:
3702
4298
  dlg = wx.MessageDialog(None,
3703
4299
  _('Do you want to unload data? \n If YES, the data will be reloaded from file once checekd \n If not saved, modifications will be lost !!'),
3704
4300
  style=wx.YES_NO)
@@ -3714,7 +4310,7 @@ class WolfArray(Element_To_Draw, header_wolf):
3714
4310
  return
3715
4311
 
3716
4312
  if not forceresetOGL:
3717
- if askquestion:
4313
+ if askquestion and self.wx_exists:
3718
4314
  dlg = wx.MessageDialog(None, _('Do you want to reset OpenGL lists?'), style=wx.YES_NO)
3719
4315
  ret = dlg.ShowModal()
3720
4316
  if ret == wx.ID_YES:
@@ -3724,7 +4320,13 @@ class WolfArray(Element_To_Draw, header_wolf):
3724
4320
  self.delete_lists()
3725
4321
  self.rgb = None
3726
4322
 
3727
- def get_header(self, abs=True) -> header_wolf:
4323
+ def get_header(self, abs:bool=True) -> header_wolf:
4324
+ """
4325
+ Return a header_wolf object - different from the self object header
4326
+
4327
+ :param abs: if True (default), return an absolute header (shifted origin) and translation set to 0.
4328
+ """
4329
+
3728
4330
  curhead = header_wolf()
3729
4331
 
3730
4332
  curhead.origx = self.origx
@@ -3745,7 +4347,7 @@ class WolfArray(Element_To_Draw, header_wolf):
3745
4347
 
3746
4348
  curhead.head_blocks = self.head_blocks.copy()
3747
4349
 
3748
- curhead.nbdims = self.nbdims
4350
+ curhead.wolftype = self.wolftype
3749
4351
 
3750
4352
  if abs:
3751
4353
  curhead.origx += curhead.translx
@@ -3758,6 +4360,7 @@ class WolfArray(Element_To_Draw, header_wolf):
3758
4360
  return curhead
3759
4361
 
3760
4362
  def set_header(self, header: header_wolf):
4363
+ """ Set the header from a header_wolf object """
3761
4364
  self.origx = header.origx
3762
4365
  self.origy = header.origy
3763
4366
  self.origz = header.origz
@@ -3774,8 +4377,6 @@ class WolfArray(Element_To_Draw, header_wolf):
3774
4377
  self.nby = header.nby
3775
4378
  self.nbz = header.nbz
3776
4379
 
3777
- self.nbdims = header.nbdims
3778
-
3779
4380
  self.head_blocks = header.head_blocks.copy()
3780
4381
 
3781
4382
  self.add_ops_sel()
@@ -3783,7 +4384,6 @@ class WolfArray(Element_To_Draw, header_wolf):
3783
4384
  def __add__(self, other):
3784
4385
  """Surcharge de l'opérateur d'addition"""
3785
4386
  newArray = WolfArray()
3786
- newArray.nbdims = self.nbdims
3787
4387
  newArray.nbx = self.nbx
3788
4388
  newArray.nby = self.nby
3789
4389
  newArray.dx = self.dx
@@ -3809,7 +4409,6 @@ class WolfArray(Element_To_Draw, header_wolf):
3809
4409
  def __mul__(self, other):
3810
4410
  """Surcharge de l'opérateur d'addition"""
3811
4411
  newArray = WolfArray()
3812
- newArray.nbdims = self.nbdims
3813
4412
  newArray.nbx = self.nbx
3814
4413
  newArray.nby = self.nby
3815
4414
  newArray.dx = self.dx
@@ -3835,7 +4434,6 @@ class WolfArray(Element_To_Draw, header_wolf):
3835
4434
  def __sub__(self, other):
3836
4435
  """Surcharge de l'opérateur de soustraction"""
3837
4436
  newArray = WolfArray()
3838
- newArray.nbdims = self.nbdims
3839
4437
  newArray.nbx = self.nbx
3840
4438
  newArray.nby = self.nby
3841
4439
  newArray.dx = self.dx
@@ -3861,7 +4459,6 @@ class WolfArray(Element_To_Draw, header_wolf):
3861
4459
  def __pow__(self, other):
3862
4460
  """Surcharge de l'opérateur puissance"""
3863
4461
  newArray = WolfArray()
3864
- newArray.nbdims = self.nbdims
3865
4462
  newArray.nbx = self.nbx
3866
4463
  newArray.nby = self.nby
3867
4464
  newArray.dx = self.dx
@@ -3883,7 +4480,6 @@ class WolfArray(Element_To_Draw, header_wolf):
3883
4480
  def __truediv__(self, other):
3884
4481
  """Surcharge de l'opérateur division"""
3885
4482
  newArray = WolfArray()
3886
- newArray.nbdims = self.nbdims
3887
4483
  newArray.nbx = self.nbx
3888
4484
  newArray.nby = self.nby
3889
4485
  newArray.dx = self.dx
@@ -3908,6 +4504,11 @@ class WolfArray(Element_To_Draw, header_wolf):
3908
4504
  return newArray
3909
4505
 
3910
4506
  def mask_outsidepoly(self, myvect: vector):
4507
+ """
4508
+ Mask nodes outside a polygon and set values to nullvalue
4509
+
4510
+ :param myvect: target vector in global coordinates
4511
+ """
3911
4512
  # The polygon here is in world coordinates
3912
4513
  # (coord will be converted back with translation, origin and dx/dy)
3913
4514
  # (mesh coord, 0-based)
@@ -3916,7 +4517,7 @@ class WolfArray(Element_To_Draw, header_wolf):
3916
4517
  mask[:,:] = True # Mask everything
3917
4518
 
3918
4519
  # trouve les indices dans le polygone
3919
- myij = self.get_ij_inside_polygon(myvect,False)
4520
+ myij = self.get_ij_inside_polygon(myvect, False)
3920
4521
  # démasquage des mailles contenues
3921
4522
  mask[myij[:,0],myij[:,1]] = False
3922
4523
  # annulation des valeurs en dehors du polygone
@@ -3932,31 +4533,18 @@ class WolfArray(Element_To_Draw, header_wolf):
3932
4533
  # FIXME This operation seems to contradict mask[:,:] = True
3933
4534
  self.mask_data(self.nullvalue)
3934
4535
 
3935
- def get_xy_infootprint_vect(self, myvect: vector) -> np.ndarray:
3936
-
3937
- myptsij = self.get_ij_infootprint_vect(myvect)
3938
- mypts=np.asarray(myptsij.copy(),dtype=np.float64)
3939
- mypts[:,0] = (mypts[:,0]+.5)*self.dx +self.origx +self.translx
3940
- mypts[:,1] = (mypts[:,1]+.5)*self.dy +self.origy +self.transly
3941
-
3942
- return mypts,myptsij
3943
4536
 
3944
- def get_ij_infootprint_vect(self, myvect: vector) -> np.ndarray:
3945
- i1, j1 = self.get_ij_from_xy(myvect.xmin, myvect.ymin)
3946
- i2, j2 = self.get_ij_from_xy(myvect.xmax, myvect.ymax)
3947
- i1 = max(i1,0) # FIXME Why ??? How could i,j be negative ?
3948
- j1 = max(j1,0)
3949
- i2 = min(i2,self.nbx-1)
3950
- j2 = min(j2,self.nby-1)
3951
- xv,yv = np.meshgrid(np.arange(i1,i2+1),np.arange(j1,j2+1))
3952
- mypts = np.hstack((xv.flatten()[:,np.newaxis],yv.flatten()[:,np.newaxis]))
3953
-
3954
- return mypts
3955
-
3956
- def get_xy_inside_polygon(self, myvect: vector, usemask=True):
4537
+ # *************************************************************************************************************************
4538
+ # POSITION and VALUES associated to a vector/polygon/polyline
4539
+ # These functions can not be stored in header_wolf, because wa can use the mask of the array to limit the search
4540
+ # These functions are also present in WolfResults_2D, but they are not exactly the same dur to the structure of the results
4541
+ # *************************************************************************************************************************
4542
+ def get_xy_inside_polygon(self, myvect: vector, usemask:bool=True):
3957
4543
  """
3958
- Obtention des coordonnées contenues dans un polygone
3959
- usemask = restreint les éléments aux éléments non masqués de la matrice
4544
+ Return the coordinates inside a polygon
4545
+
4546
+ :param myvect = target vector
4547
+ :param usemask = limit potential nodes to unmaksed nodes
3960
4548
  """
3961
4549
 
3962
4550
  myvect.find_minmax()
@@ -3974,10 +4562,12 @@ class WolfArray(Element_To_Draw, header_wolf):
3974
4562
 
3975
4563
  return mypointsxy
3976
4564
 
3977
- def get_xy_under_polyline(self, myvect: vector, usemask=True):
4565
+ def get_xy_under_polyline(self, myvect: vector, usemask:bool=True):
3978
4566
  """
3979
- Obtention des coordonnées contenues sous une polyligne
3980
- usemask = restreint les éléments aux éléments non masqués de la matrice
4567
+ Return the coordinates along a polyline
4568
+
4569
+ :param myvect = target vector
4570
+ :param usemask = limit potential nodes to unmaksed nodes
3981
4571
  """
3982
4572
 
3983
4573
  allij = self.get_ij_under_polyline(myvect, usemask)
@@ -3985,18 +4575,12 @@ class WolfArray(Element_To_Draw, header_wolf):
3985
4575
 
3986
4576
  return mypoints
3987
4577
 
3988
- def convert_xy2ij(self,xy):
3989
- return np.asarray((xy[:,0]-self.origx -self.translx)/self.dx-.5,dtype=np.int32), \
3990
- np.asarray((xy[:,1]-self.origy -self.transly)/self.dy-.5,dtype=np.int32)
3991
-
3992
- def convert_ij2xy(self,xy):
3993
- return np.asarray((xy[:,0]+.5)*self.dx+self.origx +self.translx ,dtype=np.float64), \
3994
- np.asarray((xy[:,1]+.5)*self.dy+self.origy +self.transly ,dtype=np.float64)
3995
-
3996
- def get_ij_inside_polygon(self, myvect: vector, usemask=True):
4578
+ def get_ij_inside_polygon(self, myvect: vector, usemask:bool=True):
3997
4579
  """
3998
- Obtention des indices contenues dans un polygone
3999
- usemask = restreint les éléments aux éléments non masqués de la matrice
4580
+ Return the indices inside a polygon
4581
+
4582
+ :param myvect = target vector
4583
+ :param usemask = limit potential nodes to unmaksed nodes
4000
4584
  """
4001
4585
 
4002
4586
  myvect.find_minmax()
@@ -4014,10 +4598,12 @@ class WolfArray(Element_To_Draw, header_wolf):
4014
4598
 
4015
4599
  return mypointsij
4016
4600
 
4017
- def get_ij_under_polyline(self, myvect: vector, usemask=True):
4601
+ def get_ij_under_polyline(self, myvect: vector, usemask:bool=True):
4018
4602
  """
4019
- Obtention des coordonnées sous une polyligne
4020
- usemask = restreint les éléments aux éléments non masqués de la matrice
4603
+ Return the indices along a polyline
4604
+
4605
+ :param myvect = target vector
4606
+ :param usedmask = limit potential nodes to unmaksed nodes
4021
4607
  """
4022
4608
 
4023
4609
  ds = min(self.dx, self.dy)
@@ -4033,7 +4619,7 @@ class WolfArray(Element_To_Draw, header_wolf):
4033
4619
 
4034
4620
  return allij
4035
4621
 
4036
- def get_values_insidepoly(self, myvect: vector, usemask=True, getxy=False):
4622
+ def get_values_insidepoly(self, myvect: vector, usemask:bool=True, getxy:bool=False):
4037
4623
  """
4038
4624
  Récupération des valeurs contenues dans un polygone
4039
4625
 
@@ -4048,7 +4634,7 @@ class WolfArray(Element_To_Draw, header_wolf):
4048
4634
  else:
4049
4635
  return myvalues, None
4050
4636
 
4051
- def get_values_underpoly(self, myvect: vector, usemask=True, getxy=False):
4637
+ def get_values_underpoly(self, myvect: vector, usemask:bool=True, getxy:bool=False):
4052
4638
  """
4053
4639
  Récupération des valeurs contenues sous une polyligne
4054
4640
 
@@ -4064,7 +4650,7 @@ class WolfArray(Element_To_Draw, header_wolf):
4064
4650
  else:
4065
4651
  return myvalues, None
4066
4652
 
4067
- def get_all_values_insidepoly(self, myvect: vector, usemask=True, getxy=False):
4653
+ def get_all_values_insidepoly(self, myvect: vector, usemask:bool=True, getxy:bool=False):
4068
4654
  """
4069
4655
  Récupération de toutes les valeurs contenues dans un polygone
4070
4656
 
@@ -4077,7 +4663,7 @@ class WolfArray(Element_To_Draw, header_wolf):
4077
4663
 
4078
4664
  return self.get_values_insidepoly(myvect, usemask,getxy)
4079
4665
 
4080
- def get_all_values_underpoly(self, myvect: vector, usemask=True, getxy=False):
4666
+ def get_all_values_underpoly(self, myvect: vector, usemask:bool=True, getxy:bool=False):
4081
4667
  """
4082
4668
  Récupération de toutes les valeurs sous la polyligne
4083
4669
 
@@ -4090,22 +4676,29 @@ class WolfArray(Element_To_Draw, header_wolf):
4090
4676
 
4091
4677
  return self.get_values_underpoly(myvect, usemask, getxy)
4092
4678
 
4679
+ # *************************************************************************************************************************
4680
+ # END POSITION and VALUES associated to a vector/polygon/polyline
4681
+ # *************************************************************************************************************************
4682
+
4093
4683
  def reset(self):
4094
- # FIXME Shouldn't it be self.nullvalue instead of zero ?
4684
+ """ Reset the array to nullvalue """
4685
+
4095
4686
  if self.nbdims == 2:
4096
- self.array[:, :] = 0.0
4687
+ self.array[:, :] = self.nullvalue
4097
4688
  elif self.nbdims == 3:
4098
- self.array[:, :, :] = 0.0
4689
+ self.array[:, :, :] = self.nullvalue
4099
4690
 
4100
4691
  def allocate_ressources(self):
4101
4692
  """ Memory Allocation according to dtype/wolftype"""
4693
+
4102
4694
  if self.nbdims == 2:
4103
- self.array = ma.ones([self.nbx, self.nby], dtype=self.dtype)
4695
+ self.array = ma.ones([self.nbx, self.nby], order='F', dtype=self.dtype)
4104
4696
  elif self.nbdims == 3:
4105
- self.array = ma.ones([self.nbx, self.nby, self.nbz], dtype=self.dtype)
4697
+ self.array = ma.ones([self.nbx, self.nby, self.nbz], order='F', dtype=self.dtype)
4106
4698
 
4107
4699
  def read_all(self, which_band = None):
4108
4700
  """ Lecture d'un Wolf aray depuis le nom de fichier """
4701
+
4109
4702
  if not os.path.exists(self.filename):
4110
4703
  if self.wx_exists:
4111
4704
  logging.warning(_('No data file : ')+self.filename)
@@ -4162,8 +4755,15 @@ class WolfArray(Element_To_Draw, header_wolf):
4162
4755
  self.loaded = True
4163
4756
  return
4164
4757
 
4165
- def write_all(self, newpath = None):
4166
- """ Ecriture de tous les fichiers d'un Wolf array """
4758
+ def write_all(self, newpath:str = None):
4759
+ """
4760
+ Ecriture de tous les fichiers d'un Wolf array
4761
+
4762
+ :param newpath: path and filename with extension
4763
+ """
4764
+
4765
+ if isinstance(newpath, Path):
4766
+ newpath = str(newpath)
4167
4767
 
4168
4768
  if newpath is not None:
4169
4769
  self.filename = newpath
@@ -4227,7 +4827,6 @@ class WolfArray(Element_To_Draw, header_wolf):
4227
4827
 
4228
4828
  super().read_txt_header(self.filename)
4229
4829
 
4230
-
4231
4830
  def write_txt_header(self):
4232
4831
  """
4233
4832
  Write header to txt file
@@ -4321,6 +4920,7 @@ class WolfArray(Element_To_Draw, header_wolf):
4321
4920
  self.loaded = True
4322
4921
 
4323
4922
  def _read_binary_data(self, f, seek=0):
4923
+ """ Read binary data from file """
4324
4924
 
4325
4925
  if seek > 0:
4326
4926
  f.seek(0)
@@ -4353,13 +4953,15 @@ class WolfArray(Element_To_Draw, header_wolf):
4353
4953
  """ Ecriture du tableau en binaire """
4354
4954
  self.array.data.transpose().tofile(self.filename, "")
4355
4955
 
4356
- def write_xyz(self, fname):
4956
+ def write_xyz(self, fname:str):
4357
4957
  """ Ecriture d un fichier xyz avec toutes les données du Wolf Array """
4358
4958
  my_file = XYZFile(fname)
4359
4959
  my_file.fill_from_wolf_array(self)
4360
4960
  my_file.write_to_file()
4361
4961
 
4362
- def get_xyz(self, which='all'):
4962
+ def get_xyz(self, which='all') -> np.ndarray:
4963
+ """ Return an array of xyz coordinates and values """
4964
+
4363
4965
  x1, y1 = self.get_xy_from_ij(0, 0)
4364
4966
  x2, y2 = self.get_xy_from_ij(self.nbx, self.nby, aswolf=True)
4365
4967
  xloc = np.linspace(x1, x2, self.nbx)
@@ -4372,25 +4974,45 @@ class WolfArray(Element_To_Draw, header_wolf):
4372
4974
 
4373
4975
  return xyz[filter]
4374
4976
 
4375
- def set_general_frame_from_xyz(self, fname, dx, dy):
4376
- """ Lecture d'un fichier xyz et initialisation des données de base """
4977
+ def set_general_frame_from_xyz(self, fname:str, dx:float, dy:float, border_size:int=5):
4978
+ """
4979
+ Lecture d'un fichier texte xyz et initialisation des données de base
4980
+
4981
+ :param fname: nom du fichier xyz
4982
+ :param dx: pas en x
4983
+ :param dy: pas en y
4984
+ :param border_size: nombre de mailles de bordure en plus de l'extension spatiale du fichier
4985
+ """
4986
+
4377
4987
  my_file = XYZFile(fname)
4378
4988
  my_file.read_from_file()
4379
4989
  (xlim, ylim) = my_file.get_extent()
4380
4990
 
4381
4991
  self.dx = dx
4382
4992
  self.dy = dy
4383
- self.origx = m.floor(xlim[0]) - 5.0 * self.dx
4384
- self.origy = m.floor(ylim[0]) - 5.0 * self.dy
4385
- self.nbx = int((m.floor(xlim[1]) - m.ceil(xlim[0])) / self.dx) + 10
4386
- self.nby = int((m.floor(ylim[1]) - m.ceil(ylim[0])) / self.dy) + 10
4993
+ self.origx = m.floor(xlim[0]) - float(border_size) * self.dx
4994
+ self.origy = m.floor(ylim[0]) - float(border_size) * self.dy
4995
+ self.nbx = int((m.floor(xlim[1]) - m.ceil(xlim[0])) / self.dx) + 2*border_size
4996
+ self.nby = int((m.floor(ylim[1]) - m.ceil(ylim[0])) / self.dy) + 2*border_size
4387
4997
 
4388
4998
  self.array = np.ma.zeros((self.nbx, self.nby))
4389
4999
  return my_file
4390
5000
 
4391
- def fillin_from_xyz(self, xyz):
4392
-
4393
- self.array.data[self.get_ij_from_xy(xyz[:, 0], xyz[:, 1])] = np.float32(xyz[:, 2])
5001
+ def fillin_from_xyz(self, xyz:np.ndarray):
5002
+ """ Remplissage du tableau à partir d'un tableau xyz """
5003
+
5004
+ if self.dtype == np.float32:
5005
+ self.array.data[self.get_ij_from_xy(xyz[:, 0], xyz[:, 1])] = np.float32(xyz[:, 2])
5006
+ elif self.dtype == np.float64:
5007
+ self.array.data[self.get_ij_from_xy(xyz[:, 0], xyz[:, 1])] = np.float64(xyz[:, 2])
5008
+ elif self.dtype == np.int32:
5009
+ self.array.data[self.get_ij_from_xy(xyz[:, 0], xyz[:, 1])] = np.int32(xyz[:, 2])
5010
+ elif self.dtype == np.int16:
5011
+ self.array.data[self.get_ij_from_xy(xyz[:, 0], xyz[:, 1])] = np.int16(xyz[:, 2])
5012
+ elif self.dtype == np.int8:
5013
+ self.array.data[self.get_ij_from_xy(xyz[:, 0], xyz[:, 1])] = np.int8(xyz[:, 2])
5014
+ else:
5015
+ logging.warning(_('Type not supported : ')+str(self.dtype))
4394
5016
 
4395
5017
  def mask_force_null(self):
4396
5018
  """
@@ -4398,8 +5020,7 @@ class WolfArray(Element_To_Draw, header_wolf):
4398
5020
  """
4399
5021
  self.mask_reset()
4400
5022
  self.mask_data(self.nullvalue)
4401
- if self.plotted:
4402
- self.reset_plot()
5023
+ self.reset_plot()
4403
5024
 
4404
5025
  def unmask(self):
4405
5026
  """ alias to mask_reset """
@@ -4413,7 +5034,6 @@ class WolfArray(Element_To_Draw, header_wolf):
4413
5034
  """
4414
5035
  Unmask everything
4415
5036
  """
4416
- # FIXME For WolfArray_Sim2D, this is strange since that class is there to share a mask...
4417
5037
 
4418
5038
  if self.nbdims == 2:
4419
5039
  # FIXME if WolfArray_Sim2D mask linking should work
@@ -4421,19 +5041,29 @@ class WolfArray(Element_To_Draw, header_wolf):
4421
5041
  # to avoid replacing the linked mask by a (non linked) one.
4422
5042
 
4423
5043
  if isinstance(self.array.mask, np.bool_):
5044
+ # mask is not an array, but a single boolean value
4424
5045
  self.array.mask = np.zeros(self.array.shape)
4425
5046
  else:
4426
5047
  self.array.mask.fill(False) # False == not masked
5048
+
4427
5049
  self.nbnotnull = self.nbx * self.nby
5050
+
4428
5051
  elif self.nbdims == 3:
4429
- self.array.mask = np.zeros((self.nbx, self.nby, self.nbz))
5052
+ if isinstance(self.array.mask, np.bool_):
5053
+ self.array.mask = np.zeros((self.nbx, self.nby, self.nbz))
5054
+ else:
5055
+ self.array.mask.fill(False) # False == not masked
5056
+
4430
5057
  self.nbnotnull = self.nbx * self.nby * self.nbz
4431
5058
 
4432
5059
  def count(self):
5060
+ """ Count the number of not masked values """
5061
+
4433
5062
  self.nbnotnull = self.array.count()
4434
5063
  return self.nbnotnull
4435
5064
 
4436
5065
  def mask_data(self, value):
5066
+ """ Mask cell where values are equal to `value`"""
4437
5067
  if self.array is None:
4438
5068
  return
4439
5069
 
@@ -4441,10 +5071,22 @@ class WolfArray(Element_To_Draw, header_wolf):
4441
5071
  value=int(value)
4442
5072
 
4443
5073
  if value is not None:
4444
- if np.isnan(value) or math.isnan(value):
4445
- self.array.mask = np.isnan(self.array.data)
5074
+
5075
+ if isinstance(self.array.mask, np.bool_):
5076
+ # mask is not an array, but a single boolean value
5077
+ # we must create a new mask array
5078
+ if np.isnan(value) or math.isnan(value):
5079
+ self.array.mask = np.isnan(self.array.data)
5080
+ else:
5081
+ self.array.mask = self.array.data == value
4446
5082
  else:
4447
- self.array.mask = self.array.data == value
5083
+ # Copy to prevent unlinking the mask (see `mask_reset`)
5084
+ if np.isnan(value) or math.isnan(value):
5085
+ np.copyto(self.array.mask, np.isnan(self.array.data))
5086
+ # self.array.mask[:,:] = np.isnan(self.array.data)
5087
+ else:
5088
+ np.copyto(self.array.mask, self.array.data == value)
5089
+ # self.array.mask[:,:] = self.array.data == value
4448
5090
  self.count()
4449
5091
 
4450
5092
  def mask_lower(self, value):
@@ -4457,6 +5099,7 @@ class WolfArray(Element_To_Draw, header_wolf):
4457
5099
  self.count()
4458
5100
 
4459
5101
  def mask_lowerequal(self, value):
5102
+ """ Mask cell where values are lower or equal than `value`"""
4460
5103
  if self.array is None:
4461
5104
  return
4462
5105
 
@@ -4465,22 +5108,30 @@ class WolfArray(Element_To_Draw, header_wolf):
4465
5108
  self.count()
4466
5109
 
4467
5110
  def set_nullvalue_in_mask(self):
5111
+ """ Set nullvalue in masked cells """
4468
5112
  if self.array is None:
4469
5113
  return
4470
5114
  self.array.data[self.array.mask] = self.nullvalue
4471
5115
 
4472
5116
  def reset_plot(self, whichpal=0, mimic=True):
4473
- self.delete_lists()
4474
-
4475
- if mimic:
4476
- for cur in self.linkedarrays:
4477
- if id(cur.mypal) == id(self.mypal) and id(self) !=id(cur):
4478
- cur.reset_plot(whichpal=whichpal, mimic=False)
5117
+ """ Reset plot of the array """
4479
5118
 
4480
5119
  self.count()
4481
- self.updatepalette(whichpal)
5120
+ if self.plotted:
5121
+ self.delete_lists()
5122
+
5123
+ if mimic:
5124
+ for cur in self.linkedarrays:
5125
+ if id(cur.mypal) == id(self.mypal) and id(self) !=id(cur):
5126
+ cur.reset_plot(whichpal=whichpal, mimic=False)
5127
+
5128
+ self.updatepalette(whichpal)
5129
+
5130
+ if self.mapviewer is not None:
5131
+ self.mapviewer.Refresh()
4482
5132
 
4483
5133
  def mask_allexceptdata(self, value):
5134
+ """ Mask cell where values are different from `value`"""
4484
5135
  if self.array is None:
4485
5136
  return
4486
5137
 
@@ -4489,13 +5140,20 @@ class WolfArray(Element_To_Draw, header_wolf):
4489
5140
  self.count()
4490
5141
 
4491
5142
  def mask_invert(self):
5143
+ """ Invert the mask """
4492
5144
  if self.array is None:
4493
5145
  return
4494
5146
  # Copy to prevent unlinking the mask (see `mask_reset`)
4495
5147
  np.copyto(self.array.mask, np.logical_not(self.array.mask))
4496
5148
  self.count()
4497
5149
 
4498
- def meshgrid(self, mode='gc'):
5150
+ def meshgrid(self, mode:Literal['gc', 'borders']='gc'):
5151
+ """
5152
+ Création d'un maillage 2D
5153
+
5154
+ :param mode: 'gc' pour les centres de mailles, 'borders' pour les bords de mailles
5155
+ """
5156
+
4499
5157
  x_start = self.translx + self.origx
4500
5158
  y_start = self.transly + self.origy
4501
5159
  if mode == 'gc':
@@ -4508,22 +5166,41 @@ class WolfArray(Element_To_Draw, header_wolf):
4508
5166
  y, x = np.meshgrid(y_discr, x_discr)
4509
5167
  return x, y
4510
5168
 
4511
- def crop(self, i_start, j_start, nbx, nby, k_start=1, nbz=1):
4512
- # FIXME Sanitize parameters
5169
+ def crop(self, i_start:int, j_start:int, nbx:int, nby:int, k_start:int=1, nbz:int=1):
5170
+ """
5171
+ Crop the array
5172
+
5173
+ :param i_start: start index in x
5174
+ :param j_start: start index in y
5175
+ :param nbx: number of cells in x
5176
+ :param nby: number of cells in y
5177
+ :param k_start: start index in z
5178
+ :param nbz: number of cells in z
5179
+
5180
+ :return: cropped array, WolfArray instance
5181
+ """
5182
+
5183
+ assert type(i_start) == int, "i_start must be an integer"
5184
+ assert type(j_start) == int, "j_start must be an integer"
5185
+ assert type(nbx) == int, "nbx must be an integer"
5186
+ assert type(nby) == int, "nby must be an integer"
5187
+ assert type(k_start) == int, "k_start must be an integer"
5188
+ assert type(nbz) == int, "nbz must be an integer"
5189
+
4513
5190
  newWolfArray = WolfArray()
4514
5191
  newWolfArray.nbx = nbx
4515
5192
  newWolfArray.nby = nby
4516
5193
  newWolfArray.dx = self.dx
4517
5194
  newWolfArray.dy = self.dy
4518
- newWolfArray.origx = self.origx + i_start * self.dx
4519
- newWolfArray.origy = self.origy + j_start * self.dy
5195
+ newWolfArray.origx = self.origx + float(i_start) * self.dx
5196
+ newWolfArray.origy = self.origy + float(j_start) * self.dy
4520
5197
  newWolfArray.translx = self.translx
4521
5198
  newWolfArray.transly = self.transly
4522
5199
 
4523
5200
  if self.nbdims == 3:
4524
5201
  newWolfArray.nbz = nbz
4525
5202
  newWolfArray.dz = self.dz
4526
- newWolfArray.origz = self.origz + k_start * self.dz
5203
+ newWolfArray.origz = self.origz + float(k_start) * self.dz
4527
5204
  newWolfArray.translz = self.translz
4528
5205
 
4529
5206
  newWolfArray.array = self.array[i_start:i_start + nbx, j_start:j_start + nby, k_start:k_start + nbz]
@@ -4532,64 +5209,32 @@ class WolfArray(Element_To_Draw, header_wolf):
4532
5209
 
4533
5210
  return newWolfArray
4534
5211
 
4535
- def extremum(self, which='min'):
5212
+ def extremum(self, which:Literal['min','max']='min'):
5213
+ """ Return the extremum value """
5214
+
4536
5215
  if which == 'min':
4537
5216
  my_extr = np.amin(self.array)
4538
- else:
5217
+ elif which == 'max':
4539
5218
  my_extr = np.amax(self.array)
4540
-
4541
- return my_extr
4542
-
4543
- def get_bounds(self, abs=True):
4544
- if abs:
4545
- return ([self.origx + self.translx, self.origx + self.translx + float(self.nbx) * self.dx],
4546
- [self.origy + self.transly, self.origy + self.transly + float(self.nby) * self.dy])
4547
5219
  else:
4548
- return ([self.origx, self.origx + float(self.nbx) * self.dx],
4549
- [self.origy, self.origy + float(self.nby) * self.dy])
4550
-
4551
- def find_intersection(self, other, ij=False):
4552
-
4553
- mybounds = self.get_bounds()
4554
- otherbounds = other.get_bounds()
4555
-
4556
- if otherbounds[0][0] > mybounds[0][1]:
4557
- return None
4558
- elif otherbounds[1][0] > mybounds[1][1]:
4559
- return None
4560
- elif otherbounds[0][1] < mybounds[0][0]:
4561
- return None
4562
- elif otherbounds[1][1] < mybounds[0][1]:
4563
- return None
4564
- else:
4565
- ox = max(mybounds[0][0], otherbounds[0][0])
4566
- oy = max(mybounds[1][0], otherbounds[1][0])
4567
- ex = min(mybounds[0][1], otherbounds[0][1])
4568
- ey = min(mybounds[1][1], otherbounds[1][1])
4569
- if ij:
4570
- i1, j1 = self.get_ij_from_xy(ox, oy)
4571
- i2, j2 = self.get_ij_from_xy(ex, ey)
5220
+ logging.warning(_('Extremum not supported : ')+which)
5221
+ my_extr = -99999.
4572
5222
 
4573
- i3, j3 = other.get_ij_from_xy(ox, oy)
4574
- i4, j4 = other.get_ij_from_xy(ex, ey)
4575
- return ([[i1, i2], [j1, j2]],
4576
- [[i3, i4], [j3, j4]])
4577
- else:
4578
- return ([ox, ex], [oy, ey])
4579
-
4580
- def find_union(self, other):
4581
-
4582
- mybounds = self.get_bounds()
4583
- otherbounds = other.get_bounds()
5223
+ return my_extr
4584
5224
 
4585
- ox = min(mybounds[0][0], otherbounds[0][0])
4586
- oy = min(mybounds[1][0], otherbounds[1][0])
4587
- ex = max(mybounds[0][1], otherbounds[0][1])
4588
- ey = max(mybounds[1][1], otherbounds[1][1])
5225
+ def get_value(self, x:float, y:float, z:float=0., nullvalue:float=-99999):
5226
+ """
5227
+ Return the value at given coordinates
4589
5228
 
4590
- return ([ox, ex], [oy, ey])
5229
+ :param x: x coordinate
5230
+ :param y: y coordinate
5231
+ :param z: z coordinate
5232
+ :param nullvalue: value to return if the point is outside the array
5233
+ """
4591
5234
 
4592
- def get_value(self, x, y, z=0., nullvalue=-99999):
5235
+ if isinstance(self.array.mask, np.bool_):
5236
+ logging.error(_('Mask is not an array - Please check your data'))
5237
+ return nullvalue
4593
5238
 
4594
5239
  if self.nbdims == 2:
4595
5240
  i, j = self.get_ij_from_xy(x, y)
@@ -4600,6 +5245,7 @@ class WolfArray(Element_To_Draw, header_wolf):
4600
5245
  value = self.array[i, j]
4601
5246
  else:
4602
5247
  value = nullvalue
5248
+
4603
5249
  elif self.nbdims == 3:
4604
5250
  i, j, k = self.get_ij_from_xy(x, y, z)
4605
5251
  if i >= 0 and i < self.nbx and j >= 0 and j < self.nby and k >= 0 and k < self.nbz:
@@ -4611,11 +5257,20 @@ class WolfArray(Element_To_Draw, header_wolf):
4611
5257
  else:
4612
5258
  value = nullvalue
4613
5259
 
5260
+ #FIXME : forcing to convert to float is not a good idea
4614
5261
  return float(value)
4615
5262
 
4616
- def get_xlim(self, window_x, window_y):
4617
- a_x = window_x / (self.nbx * self.dx)
4618
- a_y = window_y / (self.nby * self.dy)
5263
+ def get_xlim(self, window_x:float, window_y:float):
5264
+ """
5265
+ Return the limits in x for a given window size
5266
+
5267
+ :param window_x: window size in x
5268
+ :param window_y: window size in y
5269
+ """
5270
+
5271
+ a_x = window_x / (float(self.nbx) * self.dx)
5272
+ a_y = window_y / (float(self.nby) * self.dy)
5273
+
4619
5274
  if a_x < a_y:
4620
5275
  # C'est la mise à l'échelle selon x qui compte
4621
5276
  return (self.origx + self.translx, self.origx + self.translx + self.nbx * self.dx)
@@ -4625,9 +5280,15 @@ class WolfArray(Element_To_Draw, header_wolf):
4625
5280
  return (self.origx + self.translx + self.nbx * self.dx * 0.5 - l * 0.5,
4626
5281
  self.origx + self.translx + self.nbx * self.dx * 0.5 + l * 0.5)
4627
5282
 
4628
- def get_ylim(self, window_x, window_y):
4629
- a_x = window_x / (self.nbx * self.dx)
4630
- a_y = window_y / (self.nby * self.dy)
5283
+ def get_ylim(self, window_x:float, window_y:float):
5284
+ """
5285
+ Retrun the limits in y for a given window size
5286
+
5287
+ :param window_x: window size in x
5288
+ :param window_y: window size in y
5289
+ """
5290
+ a_x = window_x / (float(self.nbx) * self.dx)
5291
+ a_y = window_y / (float(self.nby) * self.dy)
4631
5292
  if a_x < a_y:
4632
5293
  # C'est la mise à l'échelle selon x qui compte
4633
5294
  l = (self.nbx * self.dx) / window_x * window_y
@@ -4637,7 +5298,13 @@ class WolfArray(Element_To_Draw, header_wolf):
4637
5298
  # C'est la mise à l'échelle selon y qui compte
4638
5299
  return (self.origy + self.transly, self.origy + self.transly + self.nby * self.dy)
4639
5300
 
4640
- def get_working_array(self, onzoom=[]):
5301
+ def get_working_array(self, onzoom:list[float]=[]):
5302
+ """
5303
+ Return the part of the array in the zoom window
5304
+
5305
+ :param onzoom: zoom window -- [xmin, xmax, ymin, ymax]
5306
+ """
5307
+
4641
5308
  if onzoom != []:
4642
5309
  istart, jstart = self.get_ij_from_xy(onzoom[0], onzoom[2])
4643
5310
  iend, jend = self.get_ij_from_xy(onzoom[1], onzoom[3])
@@ -4653,7 +5320,13 @@ class WolfArray(Element_To_Draw, header_wolf):
4653
5320
  else:
4654
5321
  return self.array[self.array.mask == False]
4655
5322
 
4656
- def updatepalette(self, which=0, onzoom=[]):
5323
+ def updatepalette(self, which:int=0, onzoom=[]):
5324
+ """
5325
+ Update the palette/colormap
5326
+
5327
+ :param which: which palette to update
5328
+ :param onzoom: zoom window -- [xmin, xmax, ymin, ymax]
5329
+ """
4657
5330
 
4658
5331
  if self.array is None:
4659
5332
  return
@@ -4677,10 +5350,21 @@ class WolfArray(Element_To_Draw, header_wolf):
4677
5350
  self.rgb[self.array.mask] = [1., 1., 1., 1.]
4678
5351
 
4679
5352
  if self.myops is not None:
5353
+ # update the wx
4680
5354
  self.myops.update_palette()
4681
5355
 
4682
- def plot(self, sx=None, sy=None, xmin=None, ymin=None, xmax=None, ymax=None, size=None):
4683
-
5356
+ def plot(self, sx:float=None, sy:float=None, xmin:float=None, ymin:float=None, xmax:float=None, ymax:float=None, size:float=None):
5357
+ """
5358
+ Plot the array - OpenGL
5359
+
5360
+ :param sx: scale along X
5361
+ :param sy: scale along Y
5362
+ :param xmin: Lower-Left coordinates in X
5363
+ :param ymin: Lower-Left coordinates in Y
5364
+ :param xmax: Upper-Right coordinates in X
5365
+ :param ymax: Upper-Right coordinates in Y
5366
+ :param size: size of the window (not used here but necessary for compatibility with Element_To_Draw)
5367
+ """
4684
5368
  if not self.plotted:
4685
5369
  return
4686
5370
 
@@ -4781,6 +5465,8 @@ class WolfArray(Element_To_Draw, header_wolf):
4781
5465
  self.myops.myzones.plot()
4782
5466
 
4783
5467
  def delete_lists(self):
5468
+ """ Delete OpenGL lists """
5469
+
4784
5470
  for idx, cursize in enumerate(self.mygrid):
4785
5471
  curlist = self.mygrid[cursize]
4786
5472
  nbx = curlist['nbx']
@@ -4793,8 +5479,13 @@ class WolfArray(Element_To_Draw, header_wolf):
4793
5479
  self.gridmaxscales = -1
4794
5480
 
4795
5481
  def plot_matplotlib(self):
5482
+ """
5483
+ Plot the array - Matplotlib version
5484
+
5485
+ Using imshow and RGB array
5486
+ """
4796
5487
 
4797
- self.mask_data(0.)
5488
+ self.mask_data(self.nullvalue)
4798
5489
  self.updatepalette(0)
4799
5490
 
4800
5491
  fig = plt.figure()
@@ -4807,6 +5498,7 @@ class WolfArray(Element_To_Draw, header_wolf):
4807
5498
  plt.show()
4808
5499
 
4809
5500
  def fillonecellgrid(self, curscale, loci, locj, force=False):
5501
+ """ Fill one cell of the plotted grid """
4810
5502
 
4811
5503
  cursize = curscale # 2**curscale
4812
5504
 
@@ -4863,14 +5555,14 @@ class WolfArray(Element_To_Draw, header_wolf):
4863
5555
 
4864
5556
  curlist['done'][loci, locj] = 1
4865
5557
 
4866
- def suxsuy_contour(self, filename='', abs=False) -> tuple[list[int,int], list[int,int], vector, bool]:
5558
+ def suxsuy_contour(self, filename:str='', abs:bool=False) -> tuple[list[int,int], list[int,int], vector, bool]:
4867
5559
  """
4868
5560
  The borders are computed on basis of the current *mask*
4869
5561
 
4870
- @param filename : if provided, write 'sux', 'sux' and 'xy' files
4871
- @param abs : add translation coordinates (Global World Coordinates)
5562
+ :param filename : if provided, write 'sux', 'sux' and 'xy' files
5563
+ :param abs : add translation coordinates (Global World Coordinates)
4872
5564
 
4873
- @return indicesX, indicesY, contourgen, interior
5565
+ :return indicesX, indicesY, contourgen, interior
4874
5566
 
4875
5567
  indicesX : list of coupled indices along X - vertical border - 1-based like Fortran
4876
5568
  indicesY : list of coupled indices along Y - horizontal border - 1-based like Fortran
@@ -5058,6 +5750,8 @@ class WolfArray(Element_To_Draw, header_wolf):
5058
5750
  return WOLF_ARRAY_FULL_SINGLE
5059
5751
  elif curarray.dtype == np.int32:
5060
5752
  return WOLF_ARRAY_FULL_INTEGER
5753
+ elif curarray.dtype == np.int8:
5754
+ return WOLF_ARRAY_FULL_INTEGER8
5061
5755
 
5062
5756
  self.array = np.ma.array(array.copy())
5063
5757
  self.wolftype = wolftype_from_npz(array)
@@ -5066,6 +5760,20 @@ class WolfArray(Element_To_Draw, header_wolf):
5066
5760
  self.nullvalue = nullvalue
5067
5761
  self.mask_data(self.nullvalue)
5068
5762
  self.reset_plot()
5763
+
5764
+ def nullify_border(self, width:int = 1):
5765
+ """
5766
+ Set border to nullvalue
5767
+ """
5768
+ self.array.data[:width,:] = self.nullvalue
5769
+ self.array.data[-width:,:] = self.nullvalue
5770
+ self.array.data[:,:width] = self.nullvalue
5771
+ self.array.data[:,-width:] = self.nullvalue
5772
+
5773
+ self.array.mask[:width,:] = True
5774
+ self.array.mask[-width:,:] = True
5775
+ self.array.mask[:,:width] = True
5776
+ self.array.mask[:,-width:] = True
5069
5777
  class WolfArrayMB(WolfArray):
5070
5778
  """
5071
5779
  Matrice multiblocks
@@ -5080,7 +5788,7 @@ class WolfArrayMB(WolfArray):
5080
5788
  create=False, mapviewer=None, nullvalue=0, srcheader=None):
5081
5789
  self.myblocks = {}
5082
5790
  super().__init__(fname, mold, masknull, crop, whichtype, preload, create, mapviewer, nullvalue, srcheader)
5083
- self.wolftype = WOLF_ARRAY_MB_SINGLE
5791
+ # self.wolftype = whichtype
5084
5792
 
5085
5793
  def __getitem__(self, block_key:Union[int,str]) -> WolfArray:
5086
5794
  """Access a block of this multi-blocks array."""
@@ -5094,13 +5802,31 @@ class WolfArrayMB(WolfArray):
5094
5802
  else:
5095
5803
  return None
5096
5804
 
5097
- def add_block(self, arr: WolfArray):
5098
- """ Adds a properly configured block this multiblock.
5099
- The index/key must already be set on `arr`.
5805
+ def add_block(self, arr: WolfArray, force_idx:bool=False, copy_array=False):
5806
+ """
5807
+ Adds a properly configured block this multiblock.
5808
+
5809
+ :param arr: The block to add.
5810
+ :param force_idx: If True, the index/key will be set on `arr`. If False, the index/key must already be set on `arr`.
5100
5811
  """
5101
- assert arr.idx is not None and type(arr.idx) == str and arr.idx.strip() != '', f"The block index/key is wrong {arr.idx}"
5102
- assert arr.idx not in self.myblocks, "You can't have the same block twice"
5812
+
5813
+ if copy_array:
5814
+ arr = WolfArray(mold=arr)
5815
+ force_idx = True
5816
+
5817
+ if force_idx:
5818
+ arr.idx = getkeyblock(len(self.myblocks))
5819
+ else:
5820
+ assert arr.idx is not None and type(arr.idx) == str and arr.idx.strip() != '', f"The block index/key is wrong {arr.idx}"
5821
+ assert arr.idx not in self.myblocks, "You can't have the same block twice"
5822
+ pos = len(self.myblocks)
5823
+ posidx = decodekeyblock(arr.idx, False)
5824
+ assert pos == posidx, f"The block index/key is wrong {arr.idx}"
5825
+
5103
5826
  self.myblocks[arr.idx] = arr
5827
+
5828
+ arr.isblock = True
5829
+ arr.blockindex = len(self.myblocks) - 1
5104
5830
 
5105
5831
  def share_palette(self):
5106
5832
  """Partage de la palette de couleurs entre matrices liées"""
@@ -5109,7 +5835,8 @@ class WolfArrayMB(WolfArray):
5109
5835
  cur.mypal = self.mypal
5110
5836
  cur.link_palette()
5111
5837
 
5112
- def copy_mask(self, source:"WolfArrayMB", forcenullvalue= False):
5838
+ def copy_mask(self, source:"WolfArrayMB", forcenullvalue:bool= False):
5839
+ """ Copy the mask of two arrays """
5113
5840
 
5114
5841
  if isinstance(self, type(source)):
5115
5842
  if self.check_consistency(source):
@@ -5118,8 +5845,11 @@ class WolfArrayMB(WolfArray):
5118
5845
  curblock.copy_mask(curblockother, forcenullvalue)
5119
5846
  i+=1
5120
5847
  self.reset_plot()
5848
+ else:
5849
+ logging.warning(_('Copy mask not supported between different types of arrays'))
5121
5850
 
5122
5851
  def count(self):
5852
+ """ Count the number of not null cells """
5123
5853
 
5124
5854
  self.nbnotnull = 0
5125
5855
  for i in range(self.nb_blocks):
@@ -5130,6 +5860,8 @@ class WolfArrayMB(WolfArray):
5130
5860
  self.nbnotnull += nbnotnull
5131
5861
 
5132
5862
  def check_plot(self):
5863
+ """ Check plot and apply to each block """
5864
+
5133
5865
  self.plotted = True
5134
5866
  self.mimic_plotdata()
5135
5867
 
@@ -5137,7 +5869,7 @@ class WolfArrayMB(WolfArray):
5137
5869
  if os.path.exists(self.filename):
5138
5870
  self.read_data()
5139
5871
  if self.masknull:
5140
- self.mask_data(0.)
5872
+ self.mask_data(self.nullvalue)
5141
5873
  if self.rgb is None:
5142
5874
  self.rgb = np.ones((self.nbx, self.nby, 4), order='F', dtype=np.integer)
5143
5875
  self.updatepalette(0)
@@ -5145,33 +5877,69 @@ class WolfArrayMB(WolfArray):
5145
5877
  else:
5146
5878
  raise Exception(_(f"Trying to load an array that doesn't exist ({self.filename})"))
5147
5879
 
5148
- def uncheck_plot(self, unload=True, forceresetOGL=False, askquestion=True):
5880
+ def uncheck_plot(self, unload:bool=True, forceresetOGL:bool=False, askquestion:bool=True):
5881
+ """ Uncheck plot and apply to each block """
5882
+
5149
5883
  self.plotted = False
5150
5884
  self.mimic_plotdata()
5151
5885
 
5152
5886
  if unload and self.filename != '':
5153
5887
  if askquestion and not forceresetOGL:
5154
- dlg = wx.MessageDialog(None, _('Do you want to reset OpenGL lists?'), style=wx.YES_NO)
5155
- ret = dlg.ShowModal()
5156
- if ret == wx.ID_YES:
5888
+ if self.wx_exists:
5889
+ dlg = wx.MessageDialog(None, _('Do you want to reset OpenGL lists?'), style=wx.YES_NO)
5890
+ ret = dlg.ShowModal()
5891
+ if ret == wx.ID_YES:
5157
5892
  forceresetOGL = True
5893
+ else:
5894
+ forceresetOGL = True
5158
5895
 
5159
5896
  for curblock in self.myblocks.values():
5160
5897
  curblock.uncheck_plot(unload, forceresetOGL, askquestion=False)
5161
5898
  self.rgb = None
5899
+
5162
5900
  self.myblocks = {}
5163
5901
  self.loaded = False
5164
5902
 
5165
5903
  def mask_data(self, value):
5166
- for i in range(self.nb_blocks):
5167
- curblock = self.myblocks[getkeyblock(i)]
5168
- curarray = curblock.array
5169
- curarray.mask = curarray.data == value
5904
+ """ Mask cells where values are equal to `value`"""
5170
5905
 
5171
- self.count()
5906
+ if self.wolftype in [WOLF_ARRAY_FULL_INTEGER, WOLF_ARRAY_FULL_INTEGER16]:
5907
+ value=int(value)
5908
+
5909
+ if value is not None:
5910
+
5911
+ for curblock in self.myblocks.values():
5912
+ curarray = curblock.array
5913
+
5914
+ if isinstance(curarray.mask, np.bool_):
5915
+ # mask is not an array, but a single boolean value
5916
+ # we must create a new mask array
5917
+ if np.isnan(value) or math.isnan(value):
5918
+ curarray.mask = np.isnan(curarray.data)
5919
+ else:
5920
+ curarray.mask = curarray.data == value
5921
+ else:
5922
+ # Copy to prevent unlinking the mask (see `mask_reset`)
5923
+ if np.isnan(value) or math.isnan(value):
5924
+ np.copyto(curarray.mask, np.isnan(curarray.data))
5925
+ else:
5926
+ np.copyto(curarray.mask, curarray.data == value)
5927
+
5928
+ self.count()
5929
+
5930
+ # for i in range(self.nb_blocks):
5931
+ # curblock = self.myblocks[getkeyblock(i)]
5932
+ # curarray = curblock.array
5933
+ # curarray.mask = curarray.data == value
5934
+
5935
+ # self.count()
5172
5936
 
5173
5937
  def mask_union(self, source:"WolfArrayMB"):
5938
+ """
5939
+ Union of the masks of two arrays
5174
5940
 
5941
+ Applying for each block iteratively.
5942
+ """
5175
5943
  if isinstance(self, type(source)):
5176
5944
  if self.check_consistency(source):
5177
5945
  i=0
@@ -5181,14 +5949,22 @@ class WolfArrayMB(WolfArray):
5181
5949
  self.reset_plot()
5182
5950
 
5183
5951
  def read_data(self):
5952
+ """ Lecture du tableau en binaire """
5953
+
5184
5954
  with open(self.filename, 'rb') as f:
5185
5955
  for i in range(self.nb_blocks):
5186
- curblock = WolfArray(whichtype=WOLF_ARRAY_FULL_SINGLE)
5956
+
5957
+ if self.wolftype == WOLF_ARRAY_MB_SINGLE:
5958
+ curblock = WolfArray(whichtype=WOLF_ARRAY_FULL_SINGLE, srcheader=self.head_blocks[getkeyblock(i)])
5959
+ elif self.wolftype == WOLF_ARRAY_MB_INTEGER:
5960
+ curblock = WolfArray(whichtype=WOLF_ARRAY_FULL_INTEGER)
5961
+
5187
5962
  curblock.isblock = True
5188
5963
  curblock.blockindex = i
5189
5964
  curblock.idx = getkeyblock(i)
5190
- curblock.set_header(self.head_blocks[getkeyblock(i)])
5965
+
5191
5966
  curblock._read_binary_data(f)
5967
+
5192
5968
  self.myblocks[getkeyblock(i)] = curblock
5193
5969
 
5194
5970
  def write_array(self):
@@ -5199,9 +5975,31 @@ class WolfArrayMB(WolfArray):
5199
5975
  f.write(curarray.array.data.transpose().tobytes())
5200
5976
 
5201
5977
  def get_ij_from_xy(self, x:float, y:float, z:float=0., scale:float=1., aswolf:bool=False, abs:bool=True, which_block:int=1):
5978
+ """
5979
+ alias for get_ij_from_xy for the block `which_block
5980
+
5981
+ :param x: x coordinate
5982
+ :param y: y coordinate
5983
+ :param z: z coordinate
5984
+ :param scale: scale factor
5985
+ :param aswolf: if True, then the indices are 1-based like Fortran, otherwise 0-based like Python
5986
+ :param abs: if True, then the translation is taken into account
5987
+ :param which_block: block index 1-based
5988
+ """
5202
5989
  return self.myblocks[getkeyblock(which_block, False)].get_ij_from_xy(x, y, z, scale, aswolf, abs)
5203
5990
 
5204
- def get_values_as_wolf(self, i, j, which_block=1):
5991
+ def get_values_as_wolf(self, i:int, j:int, which_block:int=1):
5992
+ """
5993
+ Return the value at indices (i,j) of the block `which_block.
5994
+
5995
+ :param i: i index
5996
+ :param j: j index
5997
+ :param which_block: block index 1-based
5998
+ """
5999
+ h = np.NaN
6000
+ if which_block == 0:
6001
+ logging.warning("Block index is probably 0-based. It should be 1-based.")
6002
+ return h
5205
6003
 
5206
6004
  keyblock = getkeyblock(which_block, False)
5207
6005
  curblock = self.myblocks[keyblock]
@@ -5214,13 +6012,20 @@ class WolfArrayMB(WolfArray):
5214
6012
 
5215
6013
  return h
5216
6014
 
5217
- def get_value(self, x, y, abs=True):
5218
- """ Read the value at world coordinate (x,y). if `abs` is
6015
+ def get_value(self, x:float, y:float, abs:bool=True):
6016
+ """
6017
+ Read the value at world coordinate (x,y). if `abs` is
5219
6018
  given, then the translation is is taken into account.
5220
6019
 
5221
6020
  If no block covers the coordinate, then np.NaN is returned
5222
6021
  If several blocks cover the given coordinate then the first
5223
6022
  match is returned (and thus, the others are ignored).
6023
+
6024
+ :param x: x coordinate
6025
+ :param y: y coordinate
6026
+ :param abs: if True, then the translation is taken into account
6027
+
6028
+ :return: the value at (x,y) or np.NaN if no block covers the coordinate
5224
6029
  """
5225
6030
 
5226
6031
  h = np.NaN
@@ -5238,13 +6043,37 @@ class WolfArrayMB(WolfArray):
5238
6043
 
5239
6044
  return h
5240
6045
 
5241
- def get_xy_from_ij(self, i, j, which_block, aswolf=False, abs=True):
6046
+ def get_xy_from_ij(self, i:int, j:int, which_block:int, aswolf:bool=False, abs:bool=True):
6047
+ """
6048
+ Return the world coordinates (x,y) of the indices (i,j) of the block `which_block.
6049
+
6050
+ :param i: i index -- 1-based like Fortran or 0-based like Python, see 'aswolf' parameter
6051
+ :param j: j index -- 1-based like Fortran or 0-based like Python, see 'aswolf' parameter
6052
+ :param which_block: block index 1-based
6053
+ :param aswolf: if True, (i,j) are 1-based like Fortran, otherwise 0-based like Python
6054
+ :param abs: if True, then the translation is taken into account
6055
+ """
6056
+
6057
+ if which_block == 0:
6058
+ logging.warning("Block index is probably 0-based. It should be 1-based.")
6059
+ return
6060
+
5242
6061
  k = getkeyblock(which_block, False)
5243
6062
  assert k in self.myblocks, f"The block '{k}' you ask for doesn't exist."
6063
+
5244
6064
  x, y = self.myblocks[k].get_xy_from_ij(i, j, aswolf=aswolf, abs=abs)
5245
6065
  return x, y
5246
6066
 
5247
- def get_blockij_from_xy(self, x, y, abs=True):
6067
+ def get_blockij_from_xy(self, x:float, y:float, abs:bool=True):
6068
+ """
6069
+ Return the block indices (i,j) of the block covering the world coordinate (x,y)
6070
+
6071
+ :param x: x coordinate
6072
+ :param y: y coordinate
6073
+ :param abs: if True, then the translation is taken into account
6074
+
6075
+ :return: the block indices (i,j,[k]) or (-1,-1,-1) if no block covers the coordinate
6076
+ """
5248
6077
 
5249
6078
  exists = False
5250
6079
  k = 1
@@ -5267,11 +6096,19 @@ class WolfArrayMB(WolfArray):
5267
6096
  return -1, -1, -1
5268
6097
 
5269
6098
  def link_palette(self):
5270
- """Lie les palettes des blocs à la palette de l'objet parent"""
6099
+ """Lier les palettes des blocs à la palette de l'objet parent"""
6100
+
5271
6101
  for curblock in self.myblocks.values():
5272
6102
  curblock.mypal = self.mypal
5273
6103
 
5274
- def updatepalette(self, which=0, onzoom=[]):
6104
+ def updatepalette(self, which:int=0, onzoom:list[float]=[]):
6105
+ """
6106
+ Update the palette/colormap of the array
6107
+
6108
+ :param which: which colormap to use
6109
+ :param onzoom: if not empty, then only the values within the zoom are used to update the palette -- [xmin,xmax,ymin,ymax]
6110
+
6111
+ """
5275
6112
 
5276
6113
  if len(self.myblocks) == 0:
5277
6114
  return
@@ -5308,17 +6145,19 @@ class WolfArrayMB(WolfArray):
5308
6145
  self.myops.update_palette()
5309
6146
 
5310
6147
  def delete_lists(self):
6148
+ """ Delete OpenGL lists """
5311
6149
  for curblock in self.myblocks.values():
5312
6150
  curblock.delete_lists()
5313
6151
 
5314
6152
  def mimic_plotdata(self):
6153
+ """ Copy plot flags to children """
5315
6154
  for curblock in self.myblocks.values():
5316
6155
  curblock: WolfArray
5317
6156
  curblock.plotted = self.plotted
5318
6157
  curblock.plotting = self.plotting
5319
6158
 
5320
6159
  def plot(self, sx=None, sy=None, xmin=None, ymin=None, xmax=None, ymax=None):
5321
-
6160
+ """ Plot the array """
5322
6161
  self.plotting = True
5323
6162
  self.mimic_plotdata()
5324
6163
 
@@ -5332,8 +6171,8 @@ class WolfArrayMB(WolfArray):
5332
6171
  for curblock in self.myblocks.values():
5333
6172
  curblock.fillonecellgrid(curscale, loci, locj, force)
5334
6173
 
5335
- def check_consistency(self,other):
5336
-
6174
+ def check_consistency(self, other):
6175
+ """ Vérifie la cohérence entre deux matrices """
5337
6176
  test = isinstance(self, type(other))
5338
6177
 
5339
6178
  if test:
@@ -5502,6 +6341,7 @@ class WolfArrayMB(WolfArray):
5502
6341
  return newArray
5503
6342
 
5504
6343
  def reset(self):
6344
+ """ Reset each block"""
5505
6345
  for i in range(self.nb_blocks):
5506
6346
  self[i].reset()
5507
6347
 
@@ -5632,7 +6472,19 @@ class WolfArrayMB(WolfArray):
5632
6472
 
5633
6473
  return fig,ax
5634
6474
 
5635
-
6475
+ def allocate_ressources(self):
6476
+ """ Allocate memory ressources """
6477
+
6478
+ if len(self.myblocks)==0:
6479
+ for id, (key, curhead) in enumerate(self.head_blocks.items()):
6480
+ if self.wolftype == WOLF_ARRAY_MB_SINGLE:
6481
+ self.myblocks[key] = WolfArray(srcheader=curhead, whichtype=WOLF_ARRAY_FULL_SINGLE)
6482
+ elif self.wolftype == WOLF_ARRAY_MB_INTEGER:
6483
+ self.myblocks[key] = WolfArray(srcheader=curhead, whichtype=WOLF_ARRAY_FULL_INTEGER)
6484
+
6485
+ self.myblocks[key].isblock = True
6486
+ self.myblocks[key].blockindex = id
6487
+ self.myblocks[key].idx = key
5636
6488
  class WolfArrayMNAP(WolfArrayMB):
5637
6489
  """
5638
6490
  Matrice MNAP d'une modélisation WOLF2D
@@ -5678,8 +6530,6 @@ class WolfArrayMNAP(WolfArrayMB):
5678
6530
  v : wolfvertex
5679
6531
  f.write(padf(v.x) + padf(v.y) + "\n")
5680
6532
 
5681
-
5682
-
5683
6533
  def read_data(self):
5684
6534
  if os.path.exists(self.filename + '.mnap'):
5685
6535
  with open(self.filename + '.mnap') as f:
@@ -5778,21 +6628,35 @@ class WolfArray_Sim2D(WolfArray):
5778
6628
  """
5779
6629
  Surcharge de WolfArray pour les matrices fines de simulation
5780
6630
  Objectif :
5781
- - reporter la matrice de mask depuis une source
6631
+ - lier la matrice de mask à une source commune
5782
6632
  """
5783
-
5784
- # FIXME Shouldn't we overload mask_reset() since this one
5785
- # creates a mask of its own ?
5786
-
5787
- def __init__(self, fname=None, mold=None, masknull=True, crop=None, whichtype=WOLF_ARRAY_FULL_SINGLE, preload=True, create=False, mapviewer=None, nullvalue=0, srcheader=None,masksrc=None):
5788
- self.masksrc=masksrc
6633
+ def __init__(self,
6634
+ fname:str=None,
6635
+ mold:"WolfArray"=None,
6636
+ masknull:bool=True,
6637
+ crop:list[float]=None,
6638
+ whichtype=WOLF_ARRAY_FULL_SINGLE,
6639
+ preload:bool=True,
6640
+ create:bool=False,
6641
+ mapviewer=None,
6642
+ nullvalue:float=0,
6643
+ srcheader:header_wolf=None,
6644
+ masksrc:np.ndarray=None):
6645
+
6646
+ # link to mask source
6647
+ self.masksrc = masksrc
5789
6648
 
5790
6649
  # FIXME __init__ will initialize a mask of its own and so self.masksrc
5791
6650
  # will be ignored at this point. That's misleading, I'd thought
5792
6651
  # that passing a mask would wire it to this array, forever.
5793
- super().__init__(fname, mold, masknull, crop, whichtype, preload, create, mapviewer, nullvalue, srcheader)
6652
+ super().__init__(fname, mold, masknull, crop, whichtype, preload, create, mapviewer, nullvalue, srcheader, mask_source=masksrc)
5794
6653
 
5795
6654
  def check_plot(self):
6655
+ """
6656
+ Surcharge de la fonction de vérification du plot
6657
+
6658
+ Utile notamment pour lier le masque
6659
+ """
5796
6660
  self.plotted = True
5797
6661
 
5798
6662
  if not self.loaded and self.filename != '':
@@ -5804,7 +6668,7 @@ class WolfArray_Sim2D(WolfArray):
5804
6668
  return
5805
6669
 
5806
6670
  if self.masksrc is not None:
5807
- self.array.mask = self.masksrc
6671
+ self.array.mask = self.masksrc
5808
6672
 
5809
6673
  self.loaded = True
5810
6674
 
@@ -5812,6 +6676,12 @@ class WolfArray_Sim2D(WolfArray):
5812
6676
  self.updatepalette(0)
5813
6677
 
5814
6678
  def read_all(self):
6679
+ """
6680
+ Surcharge de la fonction de lecture de la matrice
6681
+
6682
+ Utile notamment puor le fichier zbin
6683
+ """
6684
+
5815
6685
  if self.filename[-4:]=='zbin':
5816
6686
  fileold = self.filename
5817
6687
 
@@ -5827,6 +6697,11 @@ class WolfArray_Sim2D(WolfArray):
5827
6697
  return super().read_all()
5828
6698
 
5829
6699
  def read_data(self):
6700
+ """
6701
+ Surcharge de la fonction de lecture de la matrice
6702
+
6703
+ Utile notamment puor le fichier zbin
6704
+ """
5830
6705
 
5831
6706
  if self.filename[-4:]=='zbin':
5832
6707
  fileold = self.filename
@@ -5859,6 +6734,11 @@ class WolfArray_Sim2D(WolfArray):
5859
6734
  return super().write_all()
5860
6735
 
5861
6736
  def write_array(self):
6737
+ """
6738
+ Surcharge de la fonction d'écriture de la matrice
6739
+
6740
+ Utile notamment puor le fichier zbin
6741
+ """
5862
6742
  if self.filename[-4:]=='zbin':
5863
6743
  fileold = self.filename
5864
6744