wolfhece 2.1.46__py3-none-any.whl → 2.1.49__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.
@@ -0,0 +1,189 @@
1
+ from pyproj import Transformer
2
+ from concurrent.futures import ProcessPoolExecutor
3
+ import numpy as np
4
+ from osgeo import gdal
5
+ import os
6
+ import logging
7
+
8
+ from .PyTranslate import _
9
+
10
+ def transform_chunk_coordinates(inputEPSG:str, outputEPSG:str, chunk:np.ndarray):
11
+ """
12
+ Transforms a chunk of coordinates
13
+
14
+ :param inputEPSG: input EPSG code (e.g. "EPSG:3812")
15
+ :type inputEPSG: str
16
+ :param outputEPSG: output EPSG code (e.g. "EPSG:31370")
17
+ :type outputEPSG: str
18
+ :param chunk: list of points to be transformed
19
+ :type chunk: np.ndarray
20
+ """
21
+ COO_TRANSFORMER = Transformer.from_crs(inputEPSG, outputEPSG, always_xy=True)
22
+ return COO_TRANSFORMER.transform(chunk[:, 0], chunk[:, 1])
23
+
24
+ def transform_coordinates(points:np.ndarray, inputEPSG:str="EPSG:3812", outputEPSG:str="EPSG:31370", chunk_size:int=1000):
25
+ """
26
+ Transforms coordinates in batches using multiprocessing. If more than chunk_size points are provided, the
27
+ function will split the points into chunks and transform them in parallel => requiring in the main script
28
+ to use the statement if __name__ == '__main__':.
29
+
30
+ :param points: Array of coordinates to be transformed
31
+ :type points: numpy.ndarray
32
+ :param inputEPSG: (optional) Input EPSG code. Defaults to "EPSG:3812"
33
+ :type inputEPSG: str
34
+ :param outputEPSG: (optional) Output EPSG code. Defaults to "EPSG:31370"
35
+ :type outputEPSG: str
36
+ :param chunk_size: (optional) Size of each batch for transformation. Defaults to 100000
37
+ :type chunk_size: int
38
+
39
+ :return numpy.ndarray: Transformed coordinates
40
+ """
41
+
42
+ # sanitize inputs
43
+ inputEPSG = str(inputEPSG)
44
+ outputEPSG = str(outputEPSG)
45
+
46
+ if not "EPSG" in inputEPSG or not "EPSG" in inputEPSG:
47
+ logging.error(_("EPSG code must be in the format 'EPSG:XXXX'"))
48
+ return
49
+
50
+ num_points = len(points)
51
+ results = []
52
+
53
+ total_steps = (num_points + chunk_size - 1) // chunk_size
54
+
55
+ if total_steps == 1:
56
+ result_x, result_y = transform_chunk_coordinates(inputEPSG, outputEPSG, points)
57
+ return np.vstack((result_x, result_y)).T
58
+
59
+ with ProcessPoolExecutor() as executor:
60
+ futures = []
61
+ for i in range(0, num_points, chunk_size):
62
+ chunk = points[i:i + chunk_size]
63
+ futures.append(executor.submit(transform_chunk_coordinates, inputEPSG, outputEPSG, chunk))
64
+
65
+ for step, future in enumerate(futures):
66
+ result_x, result_y = future.result()
67
+ results.append(np.vstack((result_x, result_y)).T)
68
+
69
+ return np.vstack(results)
70
+
71
+ def reproject_and_resample_raster(input_raster_path:str, output_raster_path:str,
72
+ input_srs:str='EPSG:3812', output_srs:str='EPSG:31370',
73
+ resampling_method:str|int=gdal.GRA_Bilinear,
74
+ xRes:float=0.5, yRes:float=0.5, debug:bool=False):
75
+ """
76
+ Use gdal to open a tiff raster in a given input EPSG 'inputEPSG' and transforms the raster into another EPSG system 'outputEPSG'.
77
+ The resolution can be forced through xRes and yRes (the origin will be rounded to the nearest multiple of the resolution). The
78
+ resampling method can be chosen among the gdal GRA_* constants (gdal.GRA_Average; gdal.GRA_Bilinear; gdal.GRA_Cubic; gdal.GRA_CubicSpline;
79
+ gdal.GRA_Lanczos; gdal.GRA_Mode; gdal.GRA_NearestNeighbor).
80
+
81
+ :param input_raster_path: the path to the input raster file (.tif or .tiff)
82
+ :type input_raster_path: str
83
+ :param output_raster_path: the path to the output raster file (.tif or .tiff) that will be created
84
+ :type output_raster_path: str
85
+ :param input_srs: Input EPSG code. Defaults to Lambert 2008 "EPSG:3812"
86
+ :type input_srs: str
87
+ :param output_srs: Output EPSG code. Defaults to Lambert 72 "EPSG:31370"
88
+ :type output_srs: str
89
+ :param resampling_method: Resampling method. Defaults to gdal.GRA_Bilinear
90
+ :type resampling_method: int, str
91
+ :param xRes: Resolution along X. Defaults to 0.5
92
+ :type xRes: float
93
+ :param yRes: Resolution along Y. Defaults to 0.5
94
+ :type yRes: float
95
+ :param debug: If True, print debug information. Defaults to False
96
+ :type debug: bool
97
+ """
98
+ from osgeo import osr
99
+
100
+ # santitize inputs
101
+ input_raster_path = str(input_raster_path)
102
+ output_raster_path = str(output_raster_path)
103
+
104
+ # ATTENTION: mask values should be negative in the tiff corresponding to input_raster_path!
105
+ if not(input_raster_path.endswith('.tif') or input_raster_path.endswith('.tiff')):
106
+ logging.error(_("Input raster must be a GeoTIFF file"))
107
+ return
108
+ if not(output_raster_path.endswith('.tif') or output_raster_path.endswith('.tiff')):
109
+ logging.error(_("Output raster must be a GeoTIFF file"))
110
+ return
111
+
112
+ # check the output file
113
+ if os.path.exists(output_raster_path):
114
+ try:
115
+ os.remove(output_raster_path)
116
+ except PermissionError as e:
117
+ logging.error(_(f"Permission denied while trying to delete {output_raster_path}. Ensure the file is not open in another program and you have sufficient privileges."))
118
+ return
119
+ except Exception as e:
120
+ logging.error(_(f"An unexpected error occurred while trying to delete {output_raster_path}: {str(e)}"))
121
+ return
122
+
123
+ # Open the input raster
124
+ input_raster = gdal.Open(input_raster_path)
125
+ if input_raster is None:
126
+ logging.error(_(f"Unable to open input raster: {input_raster_path}"))
127
+ return
128
+
129
+ # Get the source SRS from the input raster
130
+ source_srs = osr.SpatialReference()
131
+ if debug:
132
+ print('Initial projection: ',input_raster.GetProjection())
133
+ print('set projection init: ',int(input_srs.split(':')[1]))
134
+ source_srs.ImportFromEPSG(int(input_srs.split(':')[1]))
135
+
136
+ # Create the target SRS
137
+ target_srs = osr.SpatialReference()
138
+ if debug:
139
+ print('set projection out: ',int(output_srs.split(':')[1]))
140
+ target_srs.ImportFromEPSG(int(output_srs.split(':')[1]))
141
+
142
+ # Define the options for the reprojection
143
+ # Load the initial array to obtain the origin and the limits
144
+ ulx, xres, xskew, uly, yskew, yres = input_raster.GetGeoTransform()
145
+ Orig = np.array([[ulx,uly],
146
+ [ulx+input_raster.RasterXSize*xres, uly+input_raster.RasterYSize*yres]])
147
+ Orig.sort(0)
148
+
149
+ # Transform the origin and the limits into the new projection 'Lambert 72'
150
+ Orig_out = transform_coordinates(Orig, inputEPSG=input_srs, outputEPSG=output_srs)
151
+
152
+ # Round each coordinate to the nearest multiple of the wanted resolution
153
+ Orig_out[:,0] = np.round(Orig_out[:,0]/xRes)*xRes
154
+ Orig_out[:,1] = np.round(Orig_out[:,1]/yRes)*yRes
155
+ if debug:
156
+ print(Orig_out)
157
+ print(tuple(Orig_out.reshape(-1)))
158
+
159
+ # Define the reprojection options
160
+ # outputBounds=tuple(Orig_out.reshape(-1)),
161
+ # xRes=xRes, yRes=yRes,
162
+ reproject_options = gdal.WarpOptions(
163
+ outputBounds=tuple(Orig_out.reshape(-1)), # Output bounds: (minX, minY, maxX, maxY)
164
+ xRes=xRes, yRes=yRes,
165
+ srcSRS=source_srs.ExportToWkt(),
166
+ dstSRS=target_srs.ExportToWkt(),
167
+ resampleAlg=resampling_method
168
+ )
169
+
170
+ # Reproject and resample the input raster
171
+ output_raster = gdal.Warp(
172
+ destNameOrDestDS=output_raster_path,
173
+ srcDSOrSrcDSTab=input_raster,
174
+ options=reproject_options
175
+ )
176
+
177
+ if output_raster is None:
178
+ logging.error(_(f"Reprojection failed for input raster: {input_raster_path}"))
179
+ return
180
+
181
+ # Flush cache to ensure the output is written to disk
182
+ output_raster.FlushCache()
183
+
184
+ # Close the datasets IMPORTANT to set to None in order to close them
185
+ input_raster = None
186
+ output_raster = None
187
+
188
+ if debug:
189
+ print(f"Reprojection and resampling completed successfully. Output saved to: {output_raster_path}")
wolfhece/PyDraw.py CHANGED
@@ -6543,6 +6543,8 @@ class WolfMapViewer(wx.Frame):
6543
6543
  if myobj in curlist:
6544
6544
  curlist.pop(curlist.index(myobj))
6545
6545
 
6546
+ myobj.hide_properties()
6547
+
6546
6548
  def upobj(self):
6547
6549
 
6548
6550
  """Up selected item into general tree"""
@@ -885,6 +885,21 @@ if :\n \
885
885
  self.myprops.SetSizeHints(500,800)
886
886
  self.myprops.Show()
887
887
 
888
+ self.myprops.SetTitle(_('Vector properties - {}'.format(self.parent.myname)))
889
+ self.myprops.Center()
890
+ self.myprops.Raise()
891
+
892
+ def show_properties(self):
893
+ """ Show the properties -- alias of show() """
894
+
895
+ self.show()
896
+
897
+ def hide_properties(self):
898
+ """ Hide the properties """
899
+
900
+ if self.myprops is not None:
901
+ self.myprops.Hide()
902
+
888
903
  def _convert_fontname2int(self, fontname:str) -> int:
889
904
 
890
905
  if fontname.lower() in 'arial.ttf':
@@ -1002,6 +1017,18 @@ class vector:
1002
1017
  if fromshapely is not None:
1003
1018
  self.import_shapelyobj(fromshapely)
1004
1019
 
1020
+ def show_properties(self):
1021
+ """ Show the properties """
1022
+
1023
+ if self.myprop is not None:
1024
+ self.myprop.show()
1025
+
1026
+ def hide_properties(self):
1027
+ """ Hide the properties """
1028
+
1029
+ if self.myprop is not None:
1030
+ self.myprop.hide_properties()
1031
+
1005
1032
  def get_mapviewer(self):
1006
1033
  """ Return the mapviewer """
1007
1034
 
@@ -1652,7 +1679,7 @@ class vector:
1652
1679
  logging.warning(_('No mapviewer available for legend plot'))
1653
1680
  return
1654
1681
 
1655
- if self.myprop.legendvisible:
1682
+ if self.myprop.legendvisible and self.myprop.used:
1656
1683
 
1657
1684
  self.textimage = Text_Image_Texture(self.myprop.legendtext,
1658
1685
  self.get_mapviewer(), # mapviewer de l'instance Zones qui contient le vecteur
@@ -1671,7 +1698,7 @@ class vector:
1671
1698
  logging.warning(_('No mapviewer available for image plot'))
1672
1699
  return
1673
1700
 
1674
- if self.myprop.imagevisible:
1701
+ if self.myprop.imagevisible and self.myprop.used:
1675
1702
 
1676
1703
  if self.myprop.textureimage is None:
1677
1704
  self.myprop.load_unload_image()
@@ -2325,7 +2352,9 @@ class zone:
2325
2352
  is2D:bool=True,
2326
2353
  fromshapely:Union[LineString,Polygon,MultiLineString, MultiPolygon]=None) -> None:
2327
2354
 
2355
+ self.myprop = None
2328
2356
  self.myprops = None
2357
+
2329
2358
  self.myname = '' # name of the zone
2330
2359
  self.idgllist = -99999 # id of the zone in the gllist
2331
2360
  self.active_vector=None # current active vector
@@ -3526,6 +3555,7 @@ class zone:
3526
3555
  """
3527
3556
  Compute statistics on values dict resulting from 'get_values_linked_polygons'
3528
3557
  """
3558
+
3529
3559
  for curpol in vals.values():
3530
3560
  medianlist =curpol['median'] = []
3531
3561
  meanlist = curpol['mean'] = []
@@ -3570,7 +3600,10 @@ class zone:
3570
3600
  p95.append(None)
3571
3601
  p5.append(None)
3572
3602
 
3573
- def plot_linked_polygons(self, fig:Figure, ax:Axes, linked_arrays:dict, linked_vec:dict[str,"Zones"]=None, linestyle:str='-', onlymedian:bool=False, withtopography:bool = True, ds:float = None):
3603
+ def plot_linked_polygons(self, fig:Figure, ax:Axes,
3604
+ linked_arrays:dict, linked_vec:dict[str,"Zones"]=None,
3605
+ linestyle:str='-', onlymedian:bool=False,
3606
+ withtopography:bool = True, ds:float = None):
3574
3607
  """
3575
3608
  Création d'un graphique sur base des polygones
3576
3609
 
@@ -3591,6 +3624,7 @@ class zone:
3591
3624
  :param ds: pas spatial le long de l'axe
3592
3625
 
3593
3626
  """
3627
+
3594
3628
  colors=['red','blue','green','darkviolet','fuchsia','lime']
3595
3629
 
3596
3630
  #Vérifie qu'au moins une matrice liée est fournie, sinon rien à faire
@@ -3790,16 +3824,19 @@ class zone:
3790
3824
 
3791
3825
  def reset_listogl(self):
3792
3826
  """
3793
- Reset des liste OpenGL
3827
+ Reset OpenGL lists.
3828
+
3829
+ Force deletion of the OpenGL list.
3830
+ If the object is newly plotted, the lists will be recreated.
3794
3831
  """
3832
+
3795
3833
  if self.idgllist!=-99999:
3796
3834
  glDeleteLists(self.idgllist,1)
3797
3835
  self.idgllist=-99999
3798
3836
 
3799
3837
  def deepcopy_zone(self, name: str =None, parent: str= None):
3800
- """
3801
- Return a deep copy of the zone.
3802
- """
3838
+ """ Return a deep copy of the zone"""
3839
+
3803
3840
  if name is None:
3804
3841
  name = self.myname + '_copy'
3805
3842
  if parent:
@@ -3817,9 +3854,10 @@ class zone:
3817
3854
  def show_properties(self):
3818
3855
  """ Show properties of the zone --> will be applied to all vectors int he zone """
3819
3856
 
3820
- locvec = vector()
3821
- locvec.myprop.show()
3822
- self.myprops = locvec.myprop.myprops
3857
+ if self.myprops is None:
3858
+ locvec = vector()
3859
+ locvec.show_properties()
3860
+ self.myprops = locvec.myprop.myprops
3823
3861
 
3824
3862
  self.myprops[('Legend','X')] = 99999.
3825
3863
  self.myprops[('Legend','Y')] = 99999.
@@ -3827,11 +3865,31 @@ class zone:
3827
3865
  self.myprops.Populate()
3828
3866
  self.myprops.set_callbacks(self._callback_prop, self._callback_destroy_props)
3829
3867
 
3868
+ self.myprops.SetTitle(_('Zone properties - {}'.format(self.myname)))
3869
+ self.myprops.Center()
3870
+ self.myprops.Raise()
3871
+
3872
+ def hide_properties(self):
3873
+ """ Hide the properties window """
3874
+
3875
+ if self.myprops is not None:
3876
+ # window for general properties
3877
+ self.myprops.Hide()
3878
+
3879
+ for curvect in self.myvectors:
3880
+ curvect.hide_properties()
3881
+
3882
+
3830
3883
  def _callback_destroy_props(self):
3884
+ """ Callback to destroy the properties window """
3885
+
3886
+ if self.myprops is not None:
3887
+ self.myprops.Destroy()
3831
3888
 
3832
3889
  self.myprops = None
3833
3890
 
3834
3891
  def _callback_prop(self):
3892
+ """ Callback to update properties """
3835
3893
 
3836
3894
  if self.myprops is None:
3837
3895
  logging.warning(_('No properties available'))
@@ -3848,6 +3906,7 @@ class zone:
3848
3906
  """
3849
3907
  Set the legend to the centroid of the zone
3850
3908
  """
3909
+
3851
3910
  for curvec in self.myvectors:
3852
3911
  curvec.set_legend_to_centroid()
3853
3912
 
@@ -3914,6 +3973,8 @@ class Zones(wx.Frame, Element_To_Draw):
3914
3973
 
3915
3974
  Element_To_Draw.__init__(self, idx, plotted, mapviewer, need_for_wx)
3916
3975
 
3976
+ self._myprops = None # common properties of all zones
3977
+
3917
3978
  self.loaded=True
3918
3979
 
3919
3980
  self.active_vector:vector = None
@@ -4521,7 +4582,18 @@ class Zones(wx.Frame, Element_To_Draw):
4521
4582
  si parent est d'un autre type, il faut s'assurer que les options/actions sont consistantes
4522
4583
 
4523
4584
  """
4524
- self.showstructure(parent,forceupdate)
4585
+ self.showstructure(parent, forceupdate)
4586
+
4587
+ def hide_properties(self):
4588
+ """ Hide the properties window """
4589
+
4590
+ self.Hide()
4591
+
4592
+ if self._myprops is not None:
4593
+ self._myprops.Hide()
4594
+
4595
+ for curzone in self.myzones:
4596
+ curzone.hide_properties()
4525
4597
 
4526
4598
  def showstructure(self, parent=None, forceupdate=False):
4527
4599
  """
@@ -4547,6 +4619,8 @@ class Zones(wx.Frame, Element_To_Draw):
4547
4619
  self.init_ui()
4548
4620
 
4549
4621
  self.Show()
4622
+ self.Center()
4623
+ self.Raise()
4550
4624
 
4551
4625
  def init_ui(self):
4552
4626
  """
@@ -6056,10 +6130,11 @@ class Zones(wx.Frame, Element_To_Draw):
6056
6130
  def _edit_all_properties(self):
6057
6131
  """ Show properties of the zone --> will be applied to all vectors int he zone """
6058
6132
 
6059
- locvec = vector()
6060
- locvec.myprop.show()
6133
+ if self._myprops is None:
6134
+ locvec = vector()
6135
+ locvec.show_properties()
6061
6136
 
6062
- self._myprops = locvec.myprop.myprops
6137
+ self._myprops = locvec.myprop.myprops
6063
6138
 
6064
6139
  self._myprops[('Legend','X')] = 99999.
6065
6140
  self._myprops[('Legend','Y')] = 99999.
@@ -6067,6 +6142,10 @@ class Zones(wx.Frame, Element_To_Draw):
6067
6142
  self._myprops.Populate()
6068
6143
  self._myprops.set_callbacks(self._callback_prop, self._callback_destroy_props)
6069
6144
 
6145
+ self._myprops.SetTitle(_('Properties for all vectors in {}'.format(self.myname)))
6146
+ self._myprops.Center()
6147
+ self._myprops.Raise()
6148
+
6070
6149
 
6071
6150
  def OnRDown(self, event:TreeListEvent):
6072
6151
  """
@@ -6080,7 +6159,7 @@ class Zones(wx.Frame, Element_To_Draw):
6080
6159
  self._edit_all_properties()
6081
6160
 
6082
6161
  elif isinstance(self.last_active, vector):
6083
- self.active_vector.myprop.show()
6162
+ self.active_vector.show_properties()
6084
6163
 
6085
6164
  elif isinstance(self.last_active, zone):
6086
6165
  self.active_zone.show_properties()
wolfhece/apps/version.py CHANGED
@@ -5,7 +5,7 @@ class WolfVersion():
5
5
 
6
6
  self.major = 2
7
7
  self.minor = 1
8
- self.patch = 46
8
+ self.patch = 49
9
9
 
10
10
  def __str__(self):
11
11
 
wolfhece/drawing_obj.py CHANGED
@@ -92,6 +92,13 @@ class Element_To_Draw:
92
92
  logging.warning('No properties to show for this object !')
93
93
  pass
94
94
 
95
+ def hide_properties(self):
96
+ """
97
+ Generic function to hide properties of the object
98
+ """
99
+ logging.warning('No properties to hide for this object !')
100
+ pass
101
+
95
102
  def plot(self, sx=None, sy=None, xmin=None, ymin=None, xmax=None, ymax=None, size=None):
96
103
  """
97
104
  Plot data in OpenGL context
@@ -126,5 +133,5 @@ class Element_To_Draw:
126
133
  """
127
134
  if self.mapviewer is None:
128
135
  return False
129
-
136
+
130
137
  return self.mapviewer.SetCurrentContext()
wolfhece/wolf_array.py CHANGED
@@ -20,6 +20,7 @@ import numpy.ma as ma
20
20
  import math as m
21
21
  import logging
22
22
  import json
23
+ import tempfile
23
24
  from pathlib import Path
24
25
 
25
26
  try:
@@ -45,7 +46,11 @@ from os.path import dirname,basename,join
45
46
  import logging
46
47
  from typing import Literal
47
48
  from copy import deepcopy
49
+ from osgeo import gdal
50
+ from enum import Enum
48
51
 
52
+
53
+ from .Coordinates_operations import reproject_and_resample_raster
49
54
  from .PyTranslate import _
50
55
  from .GraphNotebook import PlotPanel
51
56
  from .CpGrid import CpGrid
@@ -90,6 +95,42 @@ WOLF_ARRAY_MB = [WOLF_ARRAY_MB_SINGLE, WOLF_ARRAY_MB_INTEGER, WOLF_ARRAY_MNAP_IN
90
95
 
91
96
  VERSION_RGB = 2
92
97
 
98
+ class Rebin_Ops(Enum):
99
+ MIN = 0
100
+ MEAN = 1
101
+ MAX = 2
102
+ SUM = 3
103
+ MEDIAN = 4
104
+
105
+ @classmethod
106
+ def get_numpy_ops(cls):
107
+ """ Return a list of numpy functions corresponding to the enum values """
108
+
109
+ # CAUTION : Order is important and must match the enum values
110
+ return [np.ma.min, np.ma.mean, np.ma.max, np.ma.sum, np.ma.median]
111
+
112
+ @classmethod
113
+ def get_ops(cls, name:str):
114
+ """ Return the numpy function corresponding to a string """
115
+
116
+ if isinstance(name, Rebin_Ops):
117
+ return cls.get_numpy_ops()[name.value]
118
+ elif isinstance(name, str):
119
+ if name == 'min':
120
+ return np.ma.min
121
+ elif name == 'mean':
122
+ return np.ma.mean
123
+ elif name == 'max':
124
+ return np.ma.max
125
+ elif name == 'sum':
126
+ return np.ma.sum
127
+ elif name == 'median':
128
+ return np.ma.median
129
+ else:
130
+ return None
131
+ else:
132
+ return None
133
+
93
134
  def getkeyblock(i, addone=True) -> str:
94
135
  """
95
136
  Name/Key of a block in the dictionnary of a WolfArrayMB instance
@@ -111,6 +152,9 @@ def decodekeyblock(key, addone=True) -> int:
111
152
  return int(key[5:])
112
153
  else:
113
154
  return int(key[5:]) - 1
155
+
156
+
157
+
114
158
  class header_wolf():
115
159
  """
116
160
  Header of WolfArray
@@ -284,9 +328,9 @@ class header_wolf():
284
328
  """
285
329
  Set translation
286
330
 
287
- :param tr_x = translation along X
288
- :param tr_y = translation along Y
289
- :param tr_z = translation along Z
331
+ :param tr_x: translation along X
332
+ :param tr_y: translation along Y
333
+ :param tr_z: translation along Z
290
334
  """
291
335
  self.translx = tr_x
292
336
  self.transly = tr_y
@@ -2277,6 +2321,15 @@ class Ops_Array(wx.Frame):
2277
2321
 
2278
2322
  self.myzones.showstructure()
2279
2323
 
2324
+ def hide_properties(self):
2325
+ """ Hide the properties panel """
2326
+
2327
+ try:
2328
+ self.myzones.hide_properties()
2329
+ self.Hide()
2330
+ except Exception as e:
2331
+ logging.error('Error in hide_properties : %s' % e)
2332
+
2280
2333
  def OnLoadvec(self, event:wx.MouseEvent):
2281
2334
  """ Load vector file """
2282
2335
 
@@ -4396,14 +4449,24 @@ class WolfArray(Element_To_Draw, header_wolf):
4396
4449
 
4397
4450
  def __del__(self):
4398
4451
  """ Destructeur de la classe """
4399
- import gc
4400
- self.delete_lists()
4401
- del self.array
4402
- if VERSION_RGB==1 : del self.rgb
4403
- del self._array3d
4404
- del self.mypal
4405
- del self.shaded
4406
- gc.collect()
4452
+ try:
4453
+ # Perform cleanup tasks safely
4454
+ self.delete_lists()
4455
+ if hasattr(self, 'array'):
4456
+ del self.array
4457
+ if VERSION_RGB == 1 and hasattr(self, 'rgb'):
4458
+ del self.rgb
4459
+ if hasattr(self, '_array3d'):
4460
+ del self._array3d
4461
+ if hasattr(self, 'mypal'):
4462
+ del self.mypal
4463
+ if hasattr(self, 'shaded'):
4464
+ del self.shaded
4465
+ # Perform garbage collection if gc is available
4466
+ import gc
4467
+ gc.collect()
4468
+ except Exception as e:
4469
+ print(f"Exception in WolfArray destructor: {e} -- Please report this issue")
4407
4470
 
4408
4471
  def extract_selection(self):
4409
4472
  """ Extract the current selection """
@@ -4491,6 +4554,15 @@ class WolfArray(Element_To_Draw, header_wolf):
4491
4554
  self.myops.SetTitle(_('Operations on array: ') + self.idx)
4492
4555
  self.myops.Show()
4493
4556
 
4557
+ self.myops.Center()
4558
+ self.myops.Raise()
4559
+
4560
+ def hide_properties(self):
4561
+ """ Hide the properties window """
4562
+
4563
+ if self.wx_exists and self.myops is not None:
4564
+ self.myops.hide_properties()
4565
+
4494
4566
  @property
4495
4567
  def nullvalue(self) -> float:
4496
4568
  """ Return the null value """
@@ -4802,7 +4874,7 @@ class WolfArray(Element_To_Draw, header_wolf):
4802
4874
  if reset_plot:
4803
4875
  self.reset_plot()
4804
4876
 
4805
- def export_geotif(self, outdir='', extent = ''):
4877
+ def export_geotif(self, outdir='', extent = '', EPSG:int = 31370):
4806
4878
  """
4807
4879
  Export de la matrice au format Geotiff (Lambert 72 - EPSG:31370)
4808
4880
 
@@ -4813,11 +4885,12 @@ class WolfArray(Element_To_Draw, header_wolf):
4813
4885
 
4814
4886
  :param outdir: directory
4815
4887
  :param extent: suffix to add to the filename before the extension '.tif'
4888
+ :param EPSG: EPSG code, by default 31370 (Lambert 72)
4816
4889
  """
4817
4890
  from osgeo import gdal, osr, gdalconst
4818
4891
 
4819
4892
  srs = osr.SpatialReference()
4820
- srs.ImportFromEPSG(31370)
4893
+ srs.ImportFromEPSG(EPSG)
4821
4894
 
4822
4895
  if outdir=='' and extent=='':
4823
4896
  filename = self.filename
@@ -4845,7 +4918,17 @@ class WolfArray(Element_To_Draw, header_wolf):
4845
4918
  out_ds: gdal.Dataset
4846
4919
  band: gdal.Band
4847
4920
  driver = gdal.GetDriverByName("GTiff")
4848
- out_ds = driver.Create(filename, arr.shape[0], arr.shape[1], 1, arr_type, options=['COMPRESS=LZW'])
4921
+ # bytes_per_pixel = arr.data.dtype.itemsize
4922
+ estimated_file_size = self.memory_usage #arr.shape[0] * arr.shape[1] * bytes_per_pixel
4923
+
4924
+ # Check if estimated file size exceeds 4GB
4925
+ if (estimated_file_size > 4 * 1024**3): # 4GB in bytes
4926
+ options = ['COMPRESS=LZW', 'BIGTIFF=YES']
4927
+ print('BigTIFF format will be used!')
4928
+ else:
4929
+ options = ['COMPRESS=LZW']
4930
+
4931
+ out_ds = driver.Create(filename, arr.shape[0], arr.shape[1], 1, arr_type, options=options)
4849
4932
  out_ds.SetProjection(srs.ExportToWkt())
4850
4933
 
4851
4934
 
@@ -5109,6 +5192,10 @@ class WolfArray(Element_To_Draw, header_wolf):
5109
5192
  except:
5110
5193
  logging.warning(_('Error during importing tif file'))
5111
5194
 
5195
+ # Close the raster
5196
+ raster.FlushCache()
5197
+ raster = None
5198
+
5112
5199
  def add_ops_sel(self):
5113
5200
  """
5114
5201
  Adding selection manager and operations array
@@ -6682,11 +6769,12 @@ class WolfArray(Element_To_Draw, header_wolf):
6682
6769
  if update_min_max:
6683
6770
  self.mypal.distribute_values(self.array.min(), self.array.max())
6684
6771
 
6685
- def write_all(self, newpath:str = None):
6772
+ def write_all(self, newpath:str = None, EPSG:int = 31370):
6686
6773
  """
6687
6774
  Ecriture de tous les fichiers d'un Wolf array
6688
6775
 
6689
6776
  :param newpath: new path and filename with extension -- if None, use the current filename
6777
+ :param EPSG: EPSG code for geotiff
6690
6778
  """
6691
6779
 
6692
6780
  if isinstance(newpath, Path):
@@ -6696,7 +6784,7 @@ class WolfArray(Element_To_Draw, header_wolf):
6696
6784
  self.filename = newpath
6697
6785
 
6698
6786
  if self.filename.endswith('.tif'):
6699
- self.export_geotif()
6787
+ self.export_geotif(EPSG=EPSG)
6700
6788
  elif self.filename.endswith('.npy'):
6701
6789
 
6702
6790
  writing_header = True
@@ -6730,18 +6818,26 @@ class WolfArray(Element_To_Draw, header_wolf):
6730
6818
  self.write_txt_header()
6731
6819
  self.write_array()
6732
6820
 
6733
- def rebin(self, factor:float, operation:Literal['mean', 'sum', 'min']='mean') -> None:
6821
+ def get_rebin_shape_size(self, factor:float) -> tuple[tuple[int, int], tuple[float, float]]:
6734
6822
  """
6735
- Change resolution - in place
6823
+ Return the new shape after rebinning.
6736
6824
 
6737
- :param factor: factor of resolution change -- > 1.0 : decrease resolution, < 1.0 : increase resolution
6825
+ newdx = dx * factor
6826
+ newdy = dy * factor
6738
6827
 
6739
- If you want to keep current data, copy the WolfArray into a new variable -> newWA = Wolfarray(mold=curWA)
6828
+ The shape is adjusted to be a multiple of the factor.
6829
+
6830
+ :param factor: factor of resolution change -- > 1.0 : decrease resolution, < 1.0 : increase resolution
6831
+ :type factor: float
6832
+ :return: new shape
6833
+ :rtype: Tuple[int, int]
6740
6834
  """
6741
- operation = operation.lower()
6742
- if not operation in ['sum', 'mean', 'min']:
6743
- raise ValueError("Operator not supported.")
6744
6835
 
6836
+ newdx = self.dx * float(factor)
6837
+ newdy = self.dy * float(factor)
6838
+
6839
+ newnbx = self.nbx
6840
+ newnby = self.nby
6745
6841
  if np.mod(self.nbx,factor) != 0 or np.mod(self.nby,factor) != 0 :
6746
6842
  newnbx = self.nbx
6747
6843
  newnby = self.nby
@@ -6750,8 +6846,85 @@ class WolfArray(Element_To_Draw, header_wolf):
6750
6846
  if np.mod(self.nby,factor) !=0:
6751
6847
  newnby = self.nby + factor - np.mod(self.nby,factor)
6752
6848
 
6753
- newarray = np.ma.zeros((newnbx,newnby))
6849
+ newnbx = int(newnbx / factor)
6850
+ newnby = int(newnby / factor)
6851
+
6852
+ return (newnbx, newnby), (newdx, newdy)
6853
+
6854
+ def get_rebin_header(self, factor:float) -> header_wolf:
6855
+ """
6856
+ Return a new header after rebinning.
6857
+
6858
+ :param factor: factor of resolution change -- > 1.0 : decrease resolution, < 1.0 : increase resolution
6859
+ :type factor: float
6860
+
6861
+ :return: new header
6862
+ :rtype: header_wolf
6863
+ """
6864
+
6865
+ newshape, newdx_dy = self.get_rebin_shape_size(factor)
6866
+
6867
+ newheader = self.get_header()
6868
+
6869
+ newheader.nbx = newshape[0]
6870
+ newheader.nby = newshape[1]
6871
+ newheader.dx = newdx_dy[0]
6872
+ newheader.dy = newdx_dy[1]
6873
+
6874
+ return newheader
6875
+
6876
+ def rebin(self,
6877
+ factor:float,
6878
+ operation:Literal['mean', 'sum', 'min', 'max', 'median'] ='mean',
6879
+ operation_matrix:"WolfArray"=None) -> None:
6880
+ """
6881
+ Change resolution - **in place**.
6882
+
6883
+ If you want to keep current data, copy the WolfArray into a new variable -> newWA = Wolfarray(mold=curWA).
6884
+
6885
+ :param factor: factor of resolution change -- > 1.0 : decrease resolution, < 1.0 : increase resolution
6886
+ :type factor: float
6887
+ :param operation: operation to apply on the blocks ('mean', 'sum', 'min', 'max', 'median')
6888
+ :type operation: str, Rebin_Ops
6889
+ :param operation_matrix: operation matrix to apply on the blocks -- see the Enum "Rebin_Ops" for more infos. The matrix must have the same shape as the new array
6890
+ :type operation_matrix: WolfArray
6891
+
6892
+ """
6893
+
6894
+ if operation_matrix is not None:
6895
+ tmp_header = self.get_rebin_header(factor)
6896
+ if not operation_matrix.is_like(tmp_header):
6897
+ logging.error(_("The operation matrix must have the same shape as the new array"))
6898
+ logging.info(_("You can use the get_rebin_header method to get the new header if you don't know it"))
6899
+ return
6900
+
6901
+ logging.info(_("Operation matrix detected"))
6902
+ logging.info(_("The operation matrix will be used to apply the operation on the blocks"))
6903
+ else:
6904
+
6905
+ operation = Rebin_Ops.get_ops(operation)
6906
+
6907
+ if operation is None:
6908
+ logging.error(_("Operator not supported -- Must be a string in ['sum', 'mean', 'min', 'max', 'median'] or a Rebin_Ops Enum"))
6909
+ return
6910
+
6911
+ if not callable(operation):
6912
+ logging.error(_("Operator not supported -- Must be a string in ['sum', 'mean', 'min', 'max', 'median'] or a Rebin_Ops Enum"))
6913
+
6914
+
6915
+ if np.mod(self.nbx,factor) != 0 or np.mod(self.nby,factor) != 0 :
6916
+ # The shape is adjusted to be a multiple of the factor.
6917
+ # Fill the array with nullvalue
6918
+ newnbx = self.nbx
6919
+ newnby = self.nby
6920
+ if np.mod(self.nbx,factor) !=0:
6921
+ newnbx = int(self.nbx + factor - np.mod(self.nbx,factor))
6922
+ if np.mod(self.nby,factor) !=0:
6923
+ newnby = int(self.nby + factor - np.mod(self.nby,factor))
6924
+
6925
+ newarray = np.ma.ones((newnbx,newnby), dtype = self.dtype) * self.nullvalue
6754
6926
  newarray[:self.nbx,:self.nby] = self.array
6927
+ newarray.mask[:self.nbx,:self.nby] = self.array.mask
6755
6928
  self.array = newarray
6756
6929
 
6757
6930
  self.nbx = newnbx
@@ -6765,20 +6938,41 @@ class WolfArray(Element_To_Draw, header_wolf):
6765
6938
  new_shape = (self.nbx, self.nby)
6766
6939
 
6767
6940
  if factor>1.:
6768
- compression_pairs = [(d, c // d) for d, c in zip(new_shape,
6769
- self.array.shape)]
6770
- flattened = [l for p in compression_pairs for l in p]
6771
- self.array = self.array.reshape(flattened)
6772
- for i in range(len(new_shape)):
6773
- op = getattr(self.array, operation)
6774
- self.array = np.float32(op(-1 * (i + 1)))
6941
+ if operation_matrix is not None:
6942
+ # Reshape the input array to split it into blocks of size f x f
6943
+ reshaped_a = self.array.reshape(new_shape[0], int(factor), new_shape[1], int(factor))
6944
+
6945
+ # Swap axes to make blocks as separate dimensions
6946
+ reshaped_a = reshaped_a.swapaxes(1, 2)
6947
+
6948
+ # Initialize the output matrix
6949
+ self.array = ma.masked_array(np.ones((new_shape[0], new_shape[1]), dtype= self.dtype) * self.nullvalue, dtype= self.dtype)
6950
+
6951
+ # Check the dtype of the newly initialized array
6952
+ assert self.array.dtype == self.dtype, _('Bad dtype')
6953
+
6954
+ # Vectorized operations
6955
+ for op_idx, operation in enumerate(Rebin_Ops.get_numpy_ops()):
6956
+ mask = (operation_matrix.array == op_idx)
6957
+ if np.any(mask):
6958
+ block_results = operation(reshaped_a, axis=(2, 3))
6959
+ self.array[mask] = block_results[mask]
6960
+
6961
+ else:
6962
+ compression_pairs = [(d, c // d) for d, c in zip(new_shape,
6963
+ self.array.shape)]
6964
+ flattened = [l for p in compression_pairs for l in p]
6965
+ self.array = operation(self.array.reshape(flattened), axis=(1, 3)).astype(self.dtype)
6966
+
6967
+ self.set_nullvalue_in_mask()
6775
6968
  else:
6776
6969
  self.array = np.kron(self.array, np.ones((int(1/factor), int(1/factor)), dtype=self.array.dtype))
6777
6970
 
6778
- self.mask_reset()
6779
-
6780
6971
  self.count()
6781
6972
 
6973
+ # rebin must not change the type of the array
6974
+ assert self.array.dtype == self.dtype, _('Bad dtype')
6975
+
6782
6976
  def read_txt_header(self):
6783
6977
  """
6784
6978
  Read header from txt file
@@ -8001,6 +8195,54 @@ class WolfArray(Element_To_Draw, header_wolf):
8001
8195
 
8002
8196
  self.reset_plot()
8003
8197
 
8198
+ @classmethod
8199
+ def from_other_epsg_coo(cls,
8200
+ input_raster_path:str,
8201
+ input_srs='EPSG:3812',
8202
+ output_srs='EPSG:31370',
8203
+ resampling_method=gdal.GRA_Bilinear,
8204
+ xRes:float=0.5, yRes:float=0.5):
8205
+ """
8206
+ Reprojects and resamples a raster file from an other EPSG coordinates and return it as a WolfArray.
8207
+
8208
+ :param input_raster_path: The path to the input raster file.
8209
+ :type input_raster_path: str
8210
+ :param input_srs: The input spatial reference system (SRS) in the format 'EPSG:XXXX'. Defaults to Lambert 2008 'EPSG:3812'.
8211
+ :type input_srs: str
8212
+ :param output_srs: The output spatial reference system (SRS) in the format 'EPSG:XXXX'. Defaults to Belgian Lambert 72 'EPSG:31370'.
8213
+ :type output_srs: str
8214
+ :param resampling_method: The resampling method to use. Defaults to gdal.GRA_Bilinear. Resampling method can be chosen among the gdal GRA_*
8215
+ constants (gdal.GRA_Average; gdal.GRA_Bilinear; gdal.GRA_Cubic; gdal.GRA_CubicSpline;
8216
+ gdal.GRA_Lanczos; gdal.GRA_Mode; gdal.GRA_NearestNeighbour)
8217
+ :type resampling_method: int
8218
+ :param xRes: The desired output resolution in the x direction. Defaults to 0.5.
8219
+ :type xRes (float): float
8220
+ :param yRes: The desired output resolution in the y direction. Defaults to 0.5.
8221
+ :type yRes (float): float
8222
+
8223
+ :raises AssertionError: If the input or output raster file is not a GeoTIFF file.
8224
+ :raises RuntimeError: If the input raster file cannot be opened.
8225
+ :raises PermissionError: If there is a permission error while trying to delete the output raster file.
8226
+ :raises Exception: If an unexpected error occurs while trying to delete the output raster file.
8227
+ :raises RuntimeError: If the reprojection fails for the input raster file.
8228
+
8229
+ :return: WolfArray
8230
+ """
8231
+
8232
+ #sanitize input
8233
+ input_raster_path = str(input_raster_path)
8234
+ input_srs = str(input_srs)
8235
+ output_srs = str(output_srs)
8236
+
8237
+ assert resampling_method in [gdal.GRA_Average, gdal.GRA_Bilinear, gdal.GRA_Cubic, gdal.GRA_CubicSpline, gdal.GRA_Lanczos, gdal.GRA_Mode, gdal.GRA_NearestNeighbour], "Invalid resampling method"
8238
+
8239
+ # Define temporary files
8240
+ with tempfile.TemporaryDirectory() as temp_dir:
8241
+ output_raster_path = os.path.join(temp_dir, "Array_72.tif")
8242
+ reproject_and_resample_raster(input_raster_path, output_raster_path, input_srs, output_srs, resampling_method, xRes, yRes)
8243
+ Array3 = WolfArray(output_raster_path, nullvalue=-9999)
8244
+ return Array3
8245
+
8004
8246
  class WolfArrayMB(WolfArray):
8005
8247
  """
8006
8248
  Matrice multiblocks
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: wolfhece
3
- Version: 2.1.46
3
+ Version: 2.1.49
4
4
  Author-email: Pierre Archambeau <pierre.archambeau@uliege.be>
5
5
  License: Copyright (c) 2024 University of Liege. All rights reserved.
6
6
  Project-URL: Homepage, https://uee.uliege.be/hece
@@ -13,14 +13,14 @@ Classifier: Topic :: Scientific/Engineering :: Physics
13
13
  Requires-Python: <3.11,>=3.10
14
14
  Description-Content-Type: text/markdown
15
15
  Requires-Dist: wxpython
16
- Requires-Dist: colorlog
16
+ Requires-Dist: colorlog ==6.7.*
17
17
  Requires-Dist: intel-fortran-rt
18
18
  Requires-Dist: scikit-learn
19
19
  Requires-Dist: cryptography
20
- Requires-Dist: jax
20
+ Requires-Dist: jax ==0.4.30
21
21
  Requires-Dist: triangle
22
- Requires-Dist: numpy
23
- Requires-Dist: pyopengl
22
+ Requires-Dist: numpy ==1.23.*
23
+ Requires-Dist: pyopengl ==3.1.*
24
24
  Requires-Dist: pandas
25
25
  Requires-Dist: geopandas
26
26
  Requires-Dist: scipy
@@ -33,7 +33,7 @@ Requires-Dist: graphviz
33
33
  Requires-Dist: beautifulsoup4
34
34
  Requires-Dist: requests
35
35
  Requires-Dist: notebook
36
- Requires-Dist: matplotlib
36
+ Requires-Dist: matplotlib ==3.6.*
37
37
  Requires-Dist: mkl
38
38
  Requires-Dist: python-gettext
39
39
  Requires-Dist: shapely
@@ -49,10 +49,10 @@ Requires-Dist: python-docx
49
49
  Requires-Dist: pygltflib
50
50
  Requires-Dist: ezdxf
51
51
  Requires-Dist: pyvista
52
- Requires-Dist: tqdm
52
+ Requires-Dist: tqdm ==4.64.*
53
53
  Requires-Dist: osmnx
54
54
  Requires-Dist: tifffile
55
- Requires-Dist: numba
55
+ Requires-Dist: numba ==0.58.*
56
56
  Requires-Dist: xmltodict
57
57
  Requires-Dist: opencv-python
58
58
  Requires-Dist: xarray
@@ -1,3 +1,4 @@
1
+ wolfhece/Coordinates_operations.py,sha256=YyWlAwKManb-ReQrmP37rEXxehunUCihmkeDYX6qTAQ,8037
1
2
  wolfhece/CpGrid.py,sha256=ke4n1khTUoed2asJl1GR25PsEkI4TpiBDCo4u0aSo9M,10658
2
3
  wolfhece/GraphNotebook.py,sha256=V1_Ak4F_hoIpKm2GAakuyKOALhujjIXB5FwzFHtM5-8,28021
3
4
  wolfhece/GraphProfile.py,sha256=OCgJo0YFFBI6H1z-5egJsOOoWF_iziiza0-bbPejNMc,69656
@@ -6,7 +7,7 @@ wolfhece/ManageParams.py,sha256=EeuUI5Vvh9ixCvYf8YShMC1s1Yacc7OxOCN7q81gqiQ,517
6
7
  wolfhece/Model1D.py,sha256=uL1DJVmDI2xVSE7H6n3icn3QbsPtTHeg8E-6wkDloKw,476914
7
8
  wolfhece/PyConfig.py,sha256=FB8u0belXOXTb03Ln6RdVWvMgjzi3oGPCmw2dWa3lNg,8332
8
9
  wolfhece/PyCrosssections.py,sha256=FnmM9DWY_SAF2EDH9Gu2PojXNtSTRF4-aYQuAAJXBh4,112771
9
- wolfhece/PyDraw.py,sha256=t3U7YQwBA5OYh-Pf0tffZCv9Ha9SH3S-XAhFXVOpeBs,390808
10
+ wolfhece/PyDraw.py,sha256=_Rfx59LMopR0Cx00d1RRF5Gb-WNxfA2v2RjkOfm84Yo,390847
10
11
  wolfhece/PyGui.py,sha256=aRWv9tBpRl7sKEd2gHWj8Bss0ZOKbGlUYIehWHFm8WY,105008
11
12
  wolfhece/PyGuiHydrology.py,sha256=f60E8K9eGTnRq5RDF6yvt-ahf2AYegwQ9t25zZ2Mk1A,14946
12
13
  wolfhece/PyHydrographs.py,sha256=jwtSNMMACwarxrtN1UeQYth99UNrhwPx1IGgUwcooHA,3774
@@ -15,7 +16,7 @@ wolfhece/PyParams.py,sha256=wwgmP-_7wiiPLTcyX8a5jR6FyC1D2c4oBPc1VWQqtSA,97383
15
16
  wolfhece/PyPictures.py,sha256=m1kY0saW6Y9Q0bDCo47lW6XxDkBrbQG-Fd8uVn8G5ic,2514
16
17
  wolfhece/PyTranslate.py,sha256=4appkmNeHHZLFmUtaA_k5_5QL-5ymxnbVN4R2OblmtE,622
17
18
  wolfhece/PyVertex.py,sha256=vJ-NbnhPiTuFvDfAF7wfrEzVmladx5Ts0zKsyfySV3Q,40390
18
- wolfhece/PyVertexvectors.py,sha256=9MT7Qa37dnItmL4ml9O0NUY4SAee2SMWLd2FfL1oMTg,233710
19
+ wolfhece/PyVertexvectors.py,sha256=KMXJB_zFi54I73jk_fGtSTxqTQhnCwngeEjJyLdgGDo,235914
19
20
  wolfhece/PyWMS.py,sha256=fyyzm2HFwq8aRwVYHKiBatcZOeKnFi6DWhv4nfscySQ,4602
20
21
  wolfhece/RatingCurve.py,sha256=bUjIrQjvIjkD4V-z8bZmA6pe1ILtYNM0-3fT6YUY1RU,22498
21
22
  wolfhece/RatingCurveData.py,sha256=5UvnIm89BwqjnEbLCcY3CA8WoFd_xHJbooNy62fX5iY,57660
@@ -26,7 +27,7 @@ wolfhece/__init__.py,sha256=FRDE8PiJAWxX9PMXsShRMZ8YADAY4WIgKMRh52rmhiw,23
26
27
  wolfhece/_add_path.py,sha256=nudniS-lsgHwXXq5o626XRDzIeYj76GoGKYt6lcu2Nc,616
27
28
  wolfhece/cli.py,sha256=3S1Hbp2tOl51LwLcloAm1DqV9QuUWJx_Ui_vdKNtGxo,2915
28
29
  wolfhece/color_constants.py,sha256=Snc5RX11Ydi756EkBp_83C7DiAQ_Z1aHD9jFIBsosAU,37121
29
- wolfhece/drawing_obj.py,sha256=znIQY8dkYhnmoq3mEsVkVzd-hSKDbFY6i8TV-Tf37jM,4040
30
+ wolfhece/drawing_obj.py,sha256=7vY04B6r08nurTTFmBXHyR5tVIF1YzAEw_uz4pqTDIw,4233
30
31
  wolfhece/flow_SPWMI.py,sha256=XDAelwAY-3rYOR0WKW3fgYJ_r8DU4IP6Y5xULW421tk,20956
31
32
  wolfhece/friction_law.py,sha256=MtZJLo-pTj3-Fw-w12z1LSgSIDrH-JGR0iD9wer_fpQ,5498
32
33
  wolfhece/gpuview.py,sha256=0Ld8glEijx5OhMEfcwqvUFEQ5ryRXLnzey3Dff_sn-k,24110
@@ -47,7 +48,7 @@ wolfhece/pywalous.py,sha256=yRaWJjKckXef1d9D5devP0yFHC9uc6kRV4G5x9PNq9k,18972
47
48
  wolfhece/rain_SPWMI.py,sha256=qCfcmF7LajloOaCwnTrrSMzyME03YyilmRUOqrPrv3U,13846
48
49
  wolfhece/textpillow.py,sha256=map7HsGYML_o5NHRdFg2s_TVQed_lDnpYNDv27MM0Vw,14130
49
50
  wolfhece/tools_mpl.py,sha256=gQ3Jg1iuZiecmMqa5Eli2ZLSkttu68VXL8YmMDBaEYU,564
50
- wolfhece/wolf_array.py,sha256=8UwY94icIbz2BGHtBBdjoia0ijlriJsLcvuJ0Ji-DTg,358915
51
+ wolfhece/wolf_array.py,sha256=HnrviXNSps9LFDERXyeZmI0L2ovFYY4rBHInPBZLRGw,368994
51
52
  wolfhece/wolf_hist.py,sha256=7jeVrgSkM3ErJO6SRMH_PGzfLjIdw8vTy87kesldggk,3582
52
53
  wolfhece/wolf_texture.py,sha256=DS5eobLxrq9ljyebYfpMSQPn8shkUAZZVfqrOKN_QUU,16951
53
54
  wolfhece/wolf_tiles.py,sha256=2Ho2I20rHRY81KXxjgLOYISdF4OkJ2d6omeY4shDoGI,10386
@@ -71,7 +72,7 @@ wolfhece/apps/check_install.py,sha256=SG024u18G7VRLKynbp7DKD1jImtHwuWwN4bJWHm-YH
71
72
  wolfhece/apps/curvedigitizer.py,sha256=_hRR2PWow7PU7rTHIbc6ykZ08tCXcK9uy7RFrb4EKkE,5196
72
73
  wolfhece/apps/isocurrent.py,sha256=MuwTodHxdc6PrqNpphR2ntYf1NLL2n9klTPndGrOHDQ,4109
73
74
  wolfhece/apps/splashscreen.py,sha256=SrustmIQeXnsiD-92OzjdGhBi-S7c_j-cSvuX4T6rtg,2929
74
- wolfhece/apps/version.py,sha256=NcO4o6jWbzNyEQy_q5jp-nVRYwCMSE9siAWq3vRNszg,388
75
+ wolfhece/apps/version.py,sha256=cUwL_sNDbbwu6AtD4FZAGrhp2E-myuuGl2doXoLXtBc,388
75
76
  wolfhece/apps/wolf.py,sha256=mM6Tyi4DlKQILmO49cDUCip9fYVy-hLXkY3YhZgIeUQ,591
76
77
  wolfhece/apps/wolf2D.py,sha256=yPQGee7fsegoQ8GfWKrWEjX1Az_ApL-UWlBiqPvaIyY,565
77
78
  wolfhece/apps/wolf_logo.bmp,sha256=ruJ4MA51CpGO_AYUp_dB4SWKHelvhOvd7Q8NrVOjDJk,3126
@@ -277,8 +278,8 @@ wolfhece/ui/wolf_multiselection_collapsiblepane.py,sha256=8PlMYrb_8jI8h9F0_EagpM
277
278
  wolfhece/ui/wolf_times_selection_comparison_models.py,sha256=ORy7fz4dcp691qKzaOZHrRLZ0uXNhL-LIHxmpDGL6BI,5007
278
279
  wolfhece/wintab/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
279
280
  wolfhece/wintab/wintab.py,sha256=8A-JNONV6ujgsgG3lM5Uw-pVgglPATwKs86oBzzljoc,7179
280
- wolfhece-2.1.46.dist-info/METADATA,sha256=Me3fak5XA8FOqijhfnRwcDvv4O8CNe51c17L0oHxDsY,2488
281
- wolfhece-2.1.46.dist-info/WHEEL,sha256=R0nc6qTxuoLk7ShA2_Y-UWkN8ZdfDBG2B6Eqpz2WXbs,91
282
- wolfhece-2.1.46.dist-info/entry_points.txt,sha256=Q5JuIWV4odeIJI3qc6fV9MwRoz0ezqPVlFC1Ppm_vdQ,395
283
- wolfhece-2.1.46.dist-info/top_level.txt,sha256=EfqZXMVCn7eILUzx9xsEu2oBbSo9liWPFWjIHik0iCI,9
284
- wolfhece-2.1.46.dist-info/RECORD,,
281
+ wolfhece-2.1.49.dist-info/METADATA,sha256=5OMCtgg9J68-JovF2B1Yy4PdFja_n8nT7_UTT_vlgsU,2548
282
+ wolfhece-2.1.49.dist-info/WHEEL,sha256=R0nc6qTxuoLk7ShA2_Y-UWkN8ZdfDBG2B6Eqpz2WXbs,91
283
+ wolfhece-2.1.49.dist-info/entry_points.txt,sha256=Q5JuIWV4odeIJI3qc6fV9MwRoz0ezqPVlFC1Ppm_vdQ,395
284
+ wolfhece-2.1.49.dist-info/top_level.txt,sha256=EfqZXMVCn7eILUzx9xsEu2oBbSo9liWPFWjIHik0iCI,9
285
+ wolfhece-2.1.49.dist-info/RECORD,,