wolfhece 2.1.126__py3-none-any.whl → 2.1.128__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.
@@ -25,6 +25,9 @@ from scipy.interpolate import interp1d
25
25
  import matplotlib.pyplot as plt
26
26
  from matplotlib.axes import Axes
27
27
  from matplotlib.figure import Figure
28
+ from matplotlib import cm
29
+ from matplotlib.colors import Colormap
30
+
28
31
  import struct
29
32
  import pyvista as pv
30
33
  from typing import Union, Literal
@@ -36,7 +39,7 @@ import io
36
39
  from typing import Union, Literal
37
40
  from pathlib import Path
38
41
  import triangle
39
-
42
+ from concurrent.futures import ThreadPoolExecutor, wait
40
43
 
41
44
 
42
45
  from .wolf_texture import genericImagetexture
@@ -47,6 +50,7 @@ from .PyParams import Wolf_Param, Type_Param, new_json
47
50
  from .wolf_texture import Text_Image_Texture,Text_Infos
48
51
  from .drawing_obj import Element_To_Draw
49
52
  from .matplotlib_fig import Matplotlib_Figure as MplFig
53
+ from .PyPalette import wolfpalette
50
54
 
51
55
  class Triangulation(Element_To_Draw):
52
56
  def __init__(self, fn='', pts=[], tri=[], idx: str = '', plotted: bool = True, mapviewer=None, need_for_wx: bool = False) -> None:
@@ -581,12 +585,53 @@ class vectorproperties:
581
585
 
582
586
  self.used=True
583
587
 
588
+ self._values = {}
589
+
584
590
  # FIXME : to be changed
585
591
  # if self.parent is not None:
586
592
  # self.closed = self.parent.closed
587
593
 
588
594
  self.init_extra()
589
595
 
596
+ def __getitem__(self, key:str) -> Union[int,float,bool,str]:
597
+ """ Get a value """
598
+ if key in self._values:
599
+ return self._values[key]
600
+ else:
601
+ return None
602
+
603
+ def __setitem__(self, key:str, value:Union[int,float,bool,str]):
604
+ """ Set a value """
605
+ self._values[key] = value
606
+
607
+ def set_color_from_value(self, key:str, cmap:wolfpalette | str | Colormap | cm.ScalarMappable, vmin:float= 0., vmax:float= 1.):
608
+ """ Set the color from a value """
609
+
610
+ if key in self._values:
611
+ val = self._values[key]
612
+
613
+ # Test if val is a numeric value or can be converted to a numeric value
614
+ try:
615
+ val = float(val)
616
+ except:
617
+ logging.warning('Value {} is not a numeric value'.format(val))
618
+ self.color = 0
619
+ return
620
+
621
+ val_adim = (val-vmin)/(vmax-vmin)
622
+
623
+ if isinstance(cmap, wolfpalette):
624
+ self.color = getIfromRGB(cmap.get_rgba_oneval(val))
625
+ elif isinstance(cmap, str):
626
+ cmap = cm.get_cmap(cmap)
627
+ self.color = getIfromRGB(cmap(val_adim, bytes=True))
628
+ elif isinstance(cmap, Colormap):
629
+ self.color = getIfromRGB(cmap(val_adim, bytes=True))
630
+ elif isinstance(cmap, cm.ScalarMappable):
631
+ self.color = getIfromRGB(cmap.to_rgba(val, bytes=True)[:3])
632
+ else:
633
+ self.color = 0
634
+
590
635
  def init_extra(self):
591
636
  """
592
637
  Init extra properties --> not compatible with VB6, Fortran codes
@@ -894,6 +939,10 @@ if :\n \
894
939
  self.myprops.addparam('Image','Attached image','',Type_Param.File, '', whichdict='Default')
895
940
  self.myprops.addparam('Image','To show',False,Type_Param.Logical,'',whichdict='Default')
896
941
 
942
+ self.myprops.addparam('Geometry','Length 2D',99999.,Type_Param.Float,'',whichdict='Default')
943
+ self.myprops.addparam('Geometry','Length 3D',99999.,Type_Param.Float,'',whichdict='Default')
944
+ self.myprops.addparam('Geometry','Surface',99999.,Type_Param.Float,'',whichdict='Default')
945
+
897
946
  def destroyprop(self):
898
947
  """
899
948
  Nullify the properties UI
@@ -1021,6 +1070,12 @@ if :\n \
1021
1070
 
1022
1071
  self.myprops[('Move','Delta X')] = 0.
1023
1072
  self.myprops[('Move','Delta Y')] = 0.
1073
+
1074
+ self.parent.update_lengths()
1075
+ self.myprops[( 'Geometry','Length 2D')] = self.parent.length2D
1076
+ self.myprops[( 'Geometry','Length 3D')] = self.parent.length3D
1077
+ self.myprops[( 'Geometry','Surface')] = self.parent.area
1078
+
1024
1079
  self.myprops.Populate()
1025
1080
  class vector:
1026
1081
  """
@@ -1114,8 +1169,8 @@ class vector:
1114
1169
  self._rotation_center = None
1115
1170
  self._rotation_step = None
1116
1171
 
1117
- self.linestring = None
1118
- self.polygon = None
1172
+ self._linestring = None
1173
+ self._polygon = None
1119
1174
 
1120
1175
  # Utile surtout pour les sections en travers
1121
1176
  self.zdatum = 0.
@@ -1129,6 +1184,56 @@ class vector:
1129
1184
  if fromnumpy is not None:
1130
1185
  self.add_vertices_from_array(fromnumpy)
1131
1186
 
1187
+ def add_value(self, key:str, value:Union[int,float,bool,str]):
1188
+ """ Add a value to the properties """
1189
+ self.myprop[key] = value
1190
+
1191
+ def get_value(self, key:str) -> Union[int,float,bool,str]:
1192
+ """ Get a value from the properties """
1193
+ return self.myprop[key]
1194
+
1195
+ def set_color_from_value(self, key:str, cmap:wolfpalette | Colormap | cm.ScalarMappable, vmin:float= 0., vmax:float= 1.):
1196
+ """ Set the color from a value """
1197
+ self.myprop.set_color_from_value(key, cmap, vmin, vmax)
1198
+
1199
+ def set_alpha(self, alpha:int):
1200
+ """ Set the alpha value """
1201
+ self.myprop.alpha = alpha
1202
+ self.myprop.transparent = alpha != 0
1203
+
1204
+ def set_filled(self, filled:bool):
1205
+ """ Set the filled value """
1206
+ self.myprop.filled = filled
1207
+
1208
+ @property
1209
+ def polygon(self) -> Polygon:
1210
+ """ Return the polygon """
1211
+
1212
+ if self._polygon is None:
1213
+ self.prepare_shapely(linestring=False)
1214
+ return self._polygon
1215
+
1216
+ @property
1217
+ def linestring(self) -> LineString:
1218
+ """ Return the linestring """
1219
+
1220
+ if self._linestring is None:
1221
+ self.prepare_shapely(polygon=False)
1222
+ return self._linestring
1223
+
1224
+ def buffer(self, distance:float, resolution:int=16, inplace:bool=True) -> "vector":
1225
+ """ Create a new vector buffered around the vector """
1226
+
1227
+ poly = self.polygon
1228
+ buffered = poly.buffer(distance, resolution=resolution)
1229
+
1230
+ if inplace:
1231
+ self.import_shapelyobj(buffered)
1232
+ return self
1233
+ else:
1234
+ newvec = vector(fromshapely=buffered, name=self.myname)
1235
+ return newvec
1236
+
1132
1237
  def set_legend_text(self, text:str):
1133
1238
  """ Set the legend text """
1134
1239
 
@@ -1155,6 +1260,14 @@ class vector:
1155
1260
 
1156
1261
  self.myprop.update_myprops()
1157
1262
 
1263
+ def set_legend_text_from_value(self, key:str, visible:bool=True):
1264
+ """ Set the legend text from a value """
1265
+
1266
+ if key in self.myprop._values:
1267
+ self.myprop.legendtext = str(self.myprop._values[key])
1268
+ self.myprop.legendvisible = visible
1269
+ self.myprop.update_myprops()
1270
+
1158
1271
  def set_legend_position(self, x:str | float, y:str | float):
1159
1272
  """ Set the legend position """
1160
1273
 
@@ -1382,14 +1495,16 @@ class vector:
1382
1495
  def import_shapelyobj(self, obj):
1383
1496
  """ Importation d'un objet shapely """
1384
1497
 
1498
+ self.myvertices = []
1385
1499
  if isinstance(obj, LineString):
1386
- xy = np.array(obj.xy).T
1387
- self.is2D = xy.shape[1]==2
1388
- self.add_vertices_from_array(xy)
1500
+ self.is2D = not obj.has_z
1501
+ xyz = np.array(obj.coords)
1502
+ self.add_vertices_from_array(xyz)
1503
+
1389
1504
  elif isinstance(obj, Polygon):
1390
- xy = np.array(obj.exterior.xy).T
1391
- self.is2D = xy.shape[1]==2
1392
- self.add_vertices_from_array(xy)
1505
+ self.is2D = not obj.has_z
1506
+ xyz = np.array(obj.exterior.coords)
1507
+ self.add_vertices_from_array(xyz)
1393
1508
  self.close_force()
1394
1509
  else:
1395
1510
  logging.warning(_('Object type {} not supported -- Update "import_shapelyobj"').format(type(obj)))
@@ -1503,7 +1618,7 @@ class vector:
1503
1618
  if self.nbvertices==0:
1504
1619
  return False
1505
1620
 
1506
- poly = self.asshapely_pol()
1621
+ poly = self.polygon
1507
1622
  inside2 = poly.contains(Point([x,y]))
1508
1623
  return inside2
1509
1624
 
@@ -1570,7 +1685,7 @@ class vector:
1570
1685
  xyz[:,2]+=self.zdatum
1571
1686
  return xyz
1572
1687
 
1573
- def prepare_shapely(self, prepare_shapely:bool = True):
1688
+ def prepare_shapely(self, prepare_shapely:bool = True, linestring:bool = True, polygon:bool = True):
1574
1689
  """
1575
1690
  Conversion Linestring Shapely et rétention de l'objet afin d'éviter de multiples appel
1576
1691
  par ex. dans une boucle.
@@ -1578,17 +1693,23 @@ class vector:
1578
1693
  :param prepare_shapely: Préparation de l'objet Shapely pour une utilisation optimisée
1579
1694
  - True par défaut
1580
1695
  - see https://shapely.readthedocs.io/en/stable/reference/shapely.prepare.html
1696
+ :param linestring: Préparation du linestring
1697
+ :param polygon: Préparation du polygon
1581
1698
 
1582
1699
  """
1583
1700
 
1584
1701
  self.reset_linestring()
1585
1702
 
1586
- self.linestring = self.asshapely_ls()
1587
- self.polygon = self.asshapely_pol()
1703
+ if linestring:
1704
+ self._linestring = self.asshapely_ls()
1705
+ if polygon:
1706
+ self._polygon = self.asshapely_pol()
1588
1707
 
1589
1708
  if prepare_shapely:
1590
- prepare(self.linestring)
1591
- prepare(self.polygon)
1709
+ if linestring:
1710
+ prepare(self._linestring)
1711
+ if polygon:
1712
+ prepare(self._polygon)
1592
1713
 
1593
1714
 
1594
1715
  def projectontrace(self, trace):
@@ -1653,6 +1774,8 @@ class vector:
1653
1774
  def intersection(self, vec2:"vector" = None, eval_dist=False,norm=False, force_single=False):
1654
1775
  """
1655
1776
  Calcul de l'intersection avec un autre vecteur
1777
+ Attention, le shapely du vecteur vec2 et self sont automatiquement préparés. Si modifié après intersection, il faut penser à les re-préparer
1778
+ avec self.reset_linestring() ou self.prepare_shapely(). Dans le cas contraire, les anciens vecteurs préparés seront utilisés.
1656
1779
 
1657
1780
  Return :
1658
1781
  - le point d'intersection
@@ -1660,8 +1783,13 @@ class vector:
1660
1783
 
1661
1784
  Utilisation de Shapely
1662
1785
  """
1663
- ls1 = self.asshapely_ls() if self.linestring is None else self.linestring
1664
- ls2 = vec2.asshapely_ls() if vec2.linestring is None else vec2.linestring
1786
+
1787
+ # check if the vectors are closed
1788
+ ls1 = self.linestring
1789
+ ls2 = vec2.linestring
1790
+ # ls1 = self.asshapely_ls() if self._linestring is None else self._linestring
1791
+ # ls2 = vec2.asshapely_ls() if vec2._linestring is None else vec2._linestring
1792
+
1665
1793
 
1666
1794
  myinter = ls1.intersection(ls2)
1667
1795
 
@@ -1692,15 +1820,15 @@ class vector:
1692
1820
  def reset_linestring(self):
1693
1821
  """Remise à zéro de l'objet Shapely"""
1694
1822
 
1695
- if self.linestring is not None:
1696
- if is_prepared(self.linestring):
1697
- destroy_prepared(self.linestring)
1698
- self.linestring=None
1823
+ if self._linestring is not None:
1824
+ if is_prepared(self._linestring):
1825
+ destroy_prepared(self._linestring)
1826
+ self._linestring=None
1699
1827
 
1700
- if self.polygon is not None:
1701
- if is_prepared(self.polygon):
1702
- destroy_prepared(self.polygon)
1703
- self.polygon=None
1828
+ if self._polygon is not None:
1829
+ if is_prepared(self._polygon):
1830
+ destroy_prepared(self._polygon)
1831
+ self._polygon=None
1704
1832
 
1705
1833
  def add_vertex(self,addedvert: Union[list[wolfvertex], wolfvertex]):
1706
1834
  """
@@ -1970,7 +2098,7 @@ class vector:
1970
2098
 
1971
2099
  if self.myprop.filled:
1972
2100
 
1973
- ls = self.asshapely_pol()
2101
+ ls = self.polygon
1974
2102
 
1975
2103
  if False:
1976
2104
 
@@ -2049,6 +2177,20 @@ class vector:
2049
2177
  else:
2050
2178
  self.textimage = None
2051
2179
 
2180
+ def plot_legend_mpl(self, ax:plt.Axes):
2181
+ """
2182
+ Plot Legend on Matplotlib Axes
2183
+ """
2184
+
2185
+ if self.myprop.legendvisible and self.myprop.used:
2186
+
2187
+ ax.text(self.myprop.legendx, self.myprop.legendy, self.myprop.legendtext,
2188
+ fontsize=self.myprop.legendfontsize,
2189
+ fontname=self.myprop.legendfontname,
2190
+ color=getRGBfromI(self.myprop.legendcolor),
2191
+ rotation=self.myprop.legendorientation,
2192
+ ha='center', va='center')
2193
+
2052
2194
  def plot_image(self, sx=None, sy=None, xmin=None, ymin=None, xmax=None, ymax=None, size=None):
2053
2195
  """ plot attached images """
2054
2196
 
@@ -2066,6 +2208,28 @@ class vector:
2066
2208
  else:
2067
2209
  logging.warning(_('No image texture available for plot'))
2068
2210
 
2211
+ def plot_matplotlib(self, ax:plt.Axes):
2212
+ """
2213
+ Plot Matplotlib
2214
+ """
2215
+
2216
+ if self.myprop.used:
2217
+
2218
+ if self.myprop.filled:
2219
+ rgb=getRGBfromI(self.myprop.color)
2220
+ if self.myprop.transparent:
2221
+ ax.fill([curvert.x for curvert in self.myvertices], [curvert.y for curvert in self.myvertices], color=(rgb[0]/255.,rgb[1]/255.,rgb[2]/255.,self.myprop.alpha))
2222
+ else:
2223
+ ax.fill([curvert.x for curvert in self.myvertices], [curvert.y for curvert in self.myvertices], color=(rgb[0]/255.,rgb[1]/255.,rgb[2]/255.))
2224
+ else:
2225
+ rgb=getRGBfromI(self.myprop.color)
2226
+ if self.myprop.transparent:
2227
+ ax.plot([curvert.x for curvert in self.myvertices], [curvert.y for curvert in self.myvertices], color=(rgb[0]/255.,rgb[1]/255.,rgb[2]/255.,self.myprop.alpha), linewidth=self.myprop.width)
2228
+ else:
2229
+ ax.plot([curvert.x for curvert in self.myvertices], [curvert.y for curvert in self.myvertices], color=(rgb[0]/255.,rgb[1]/255.,rgb[2]/255.), linewidth=self.myprop.width)
2230
+
2231
+ self.plot_legend_mpl(ax)
2232
+
2069
2233
  def _get_textfont(self):
2070
2234
  """ Retunr a 'Text_Infos' instance for the legend """
2071
2235
 
@@ -2167,7 +2331,7 @@ class vector:
2167
2331
  while k<self.nbvertices:
2168
2332
  self.myvertices.pop(k)
2169
2333
 
2170
- if self.linestring is not None or self.polygon is not None:
2334
+ if self._linestring is not None or self._polygon is not None:
2171
2335
  self.prepare_shapely()
2172
2336
 
2173
2337
  self._reset_listogl()
@@ -2773,6 +2937,11 @@ class vector:
2773
2937
  copied_vector = vector(name=name)
2774
2938
 
2775
2939
  copied_vector.myvertices = copy.deepcopy(self.myvertices)
2940
+ # FIXME : deepcopy of properties is not working
2941
+ # copied_vector.myprop = copy.deepcopy(self.myprop)
2942
+ copied_vector.zdatum = self.zdatum
2943
+ copied_vector.closed = self.closed
2944
+ copied_vector.add_zdatum = self.add_zdatum
2776
2945
 
2777
2946
  return copied_vector
2778
2947
 
@@ -2783,19 +2952,35 @@ class vector:
2783
2952
 
2784
2953
  return self.deepcopy_vector(name, parentzone)
2785
2954
 
2955
+ @property
2956
+ def centroid(self):
2957
+ """
2958
+ Return the centroid of the vector
2959
+ """
2960
+
2961
+ return self.polygon.centroid
2962
+
2786
2963
  def set_legend_to_centroid(self, text:str='', visible:bool=True):
2787
2964
  """
2788
2965
  Positionne la légende au centre du vecteur
2789
2966
  """
2790
2967
  self.myprop.legendvisible = visible
2791
2968
 
2792
- ls = self.asshapely_pol()
2793
- centroid = ls.centroid
2969
+ centroid = self.centroid
2794
2970
 
2795
2971
  self.myprop.legendx = centroid.x
2796
2972
  self.myprop.legendy = centroid.y
2797
2973
  self.myprop.legendtext = text if text else self.myname
2798
2974
 
2975
+ def set_legend_position_to_centroid(self):
2976
+ """
2977
+ Positionne la légende au centre du vecteur
2978
+ """
2979
+
2980
+ centroid = self.centroid
2981
+ self.myprop.legendx = centroid.x
2982
+ self.myprop.legendy = centroid.y
2983
+
2799
2984
  def set_z(self, new_z:np.ndarray):
2800
2985
  """ Set the z values of the vertices """
2801
2986
  self.z = new_z
@@ -3101,7 +3286,6 @@ class vector:
3101
3286
  if self.parentzone is not None:
3102
3287
  self.parentzone.reset_listogl()
3103
3288
 
3104
-
3105
3289
  def select_points_inside(self, xy:cloud_vertices | np.ndarray):
3106
3290
  """ Select the points inside a polygon
3107
3291
 
@@ -3109,7 +3293,7 @@ class vector:
3109
3293
  :return: list of boolean
3110
3294
  """
3111
3295
 
3112
- self.prepare_shapely()
3296
+ self.prepare_shapely(True)
3113
3297
 
3114
3298
  if isinstance(xy, cloud_vertices):
3115
3299
  xy = xy.get_xyz()[:,0:2]
@@ -3166,7 +3350,7 @@ class vector:
3166
3350
  """
3167
3351
 
3168
3352
  if self.closed:
3169
- return self.asshapely_pol().area
3353
+ return self.polygon.area
3170
3354
  else:
3171
3355
  return 0.
3172
3356
 
@@ -3256,6 +3440,62 @@ class zone:
3256
3440
  # Object can be created from a shapely object
3257
3441
  self.import_shapelyobj(fromshapely)
3258
3442
 
3443
+ def add_values(self, key:str, values:np.ndarray):
3444
+ """ add values to the zone """
3445
+
3446
+ if self.nbvectors != values.shape[0]:
3447
+ logging.warning(_('Number of vectors and values do not match'))
3448
+ return
3449
+
3450
+ for curvec, curval in zip(self.myvectors, values):
3451
+ curvec.add_value(key, curval)
3452
+
3453
+ def get_values(self, key:str) -> np.ndarray:
3454
+ """ get values from the zone """
3455
+
3456
+ return np.array([curvec.get_value(key) for curvec in self.myvectors])
3457
+
3458
+ def set_colors_from_value(self, key:str, cmap:wolfpalette | Colormap | cm.ScalarMappable, vmin:float= 0., vmax:float= 1.):
3459
+ """ Set the colors for the zone """
3460
+
3461
+ for curvec in self.myvectors:
3462
+ curvec.set_color_from_value(key, cmap, vmin, vmax)
3463
+
3464
+ def set_alpha(self, alpha:int):
3465
+ """ Set the alpha for the zone """
3466
+
3467
+ for curvec in self.myvectors:
3468
+ curvec.set_alpha(alpha)
3469
+
3470
+ def set_filled(self, filled:bool):
3471
+ """ Set the filled for the zone """
3472
+
3473
+ for curvec in self.myvectors:
3474
+ curvec.set_filled(filled)
3475
+
3476
+ def check_if_open(self):
3477
+ """ Check if the vectors in the zone are open """
3478
+ for curvec in self.myvectors:
3479
+ curvec.check_if_open()
3480
+
3481
+ def buffer(self, distance:float, resolution:int=16, inplace:bool = False) -> 'zone':
3482
+ """ Create a new zone with a buffer around each vector """
3483
+
3484
+ if inplace:
3485
+ newzone = self
3486
+ else:
3487
+ newzone = zone(name=self.myname)
3488
+
3489
+ retmap = list(map(lambda x: x.buffer(distance, resolution, inplace), self.myvectors))
3490
+
3491
+ if inplace:
3492
+ return self
3493
+
3494
+ for curvec in retmap:
3495
+ newzone.add_vector(curvec, forceparent=True)
3496
+
3497
+ return newzone
3498
+
3259
3499
  def set_legend_text(self, text:str):
3260
3500
  """
3261
3501
  Set the legend text for the zone
@@ -3264,6 +3504,14 @@ class zone:
3264
3504
  for curvect in self.myvectors:
3265
3505
  curvect.set_legend_text(text)
3266
3506
 
3507
+ def set_legend_text_from_values(self, key:str):
3508
+ """
3509
+ Set the legend text for the zone from a value
3510
+ """
3511
+
3512
+ for curvect in self.myvectors:
3513
+ curvect.set_legend_text_from_value(key)
3514
+
3267
3515
  def set_legend_position(self, x, y):
3268
3516
  """
3269
3517
  Set the legend position for the zone
@@ -3275,7 +3523,12 @@ class zone:
3275
3523
  @property
3276
3524
  def area(self):
3277
3525
  """ Compute the area of the zone """
3278
- return sum([curvec.surface for curvec in self.myvectors])
3526
+ return sum(self.areas)
3527
+
3528
+ @property
3529
+ def areas(self):
3530
+ """ List of all areas """
3531
+ return [curvec.surface for curvec in self.myvectors]
3279
3532
 
3280
3533
  def set_cache(self):
3281
3534
  """
@@ -3634,6 +3887,16 @@ class zone:
3634
3887
  for curvect in self.myvectors:
3635
3888
  curvect.plot_image(sx, sy, xmin, ymin, xmax, ymax, size)
3636
3889
 
3890
+ def plot_matplotlib(self, ax:plt.Axes):
3891
+ """
3892
+ Plot the zone using matplotlib
3893
+
3894
+ :param ax: matplotlib Axes
3895
+ :param kwargs: additional arguments
3896
+ """
3897
+
3898
+ for curvect in self.myvectors:
3899
+ curvect.plot_matplotlib(ax)
3637
3900
 
3638
3901
  def select_vectors_from_point(self,x:float,y:float,inside=True):
3639
3902
  """
@@ -5284,11 +5547,11 @@ class zone:
5284
5547
 
5285
5548
  def set_legend_to_centroid(self):
5286
5549
  """
5287
- Set the legend to the centroid of the zone
5550
+ Set the legend to the centroid of the vectors
5288
5551
  """
5289
5552
 
5290
5553
  for curvec in self.myvectors:
5291
- curvec.set_legend_to_centroid()
5554
+ curvec.set_legend_position_to_centroid()
5292
5555
 
5293
5556
  class Zones(wx.Frame, Element_To_Draw):
5294
5557
  """
@@ -5403,24 +5666,29 @@ class Zones(wx.Frame, Element_To_Draw):
5403
5666
  self._rotation_center = None
5404
5667
  self._rotation_step = None
5405
5668
 
5669
+ only_firstlast = False # By default, we are using all vertices
5406
5670
  if self.filename!='':
5407
5671
  # lecture du fichier
5408
5672
 
5409
5673
  if self.filename.endswith('.dxf'):
5410
5674
  self.is2D=False
5411
5675
  self.import_dxf(self.filename)
5676
+ only_firstlast = True # We limit the number of vertices to the first and last ones to accelerate the process
5412
5677
 
5413
5678
  elif self.filename.endswith('.shp'):
5414
5679
  self.is2D=False
5415
5680
  self.import_shapefile(self.filename, bbox=bbox)
5681
+ only_firstlast = True # We limit the number of vertices to the first and last ones to accelerate the process
5416
5682
 
5417
5683
  elif self.filename.endswith('.gpkg'):
5418
5684
  self.is2D=False
5419
5685
  self.import_gpkg(self.filename, bbox=bbox)
5686
+ only_firstlast = True # We limit the number of vertices to the first and last ones to accelerate the process
5420
5687
 
5421
5688
  elif Path(filename).is_dir() and self.filename.endswith('.gdb'):
5422
5689
  self.is2D=False
5423
5690
  self.import_gdb(self.filename, bbox=bbox)
5691
+ only_firstlast = True # We limit the number of vertices to the first and last ones to accelerate the process
5424
5692
 
5425
5693
  elif self.filename.endswith('.vec') or self.filename.endswith('.vecz'):
5426
5694
 
@@ -5466,13 +5734,149 @@ class Zones(wx.Frame, Element_To_Draw):
5466
5734
  logging.warning(_('Error while reading extra properties of {}'.format(self.filename)))
5467
5735
 
5468
5736
  if find_minmax:
5469
- self.find_minmax(True)
5737
+ logging.info(_('Finding min and max values'))
5738
+ self.find_minmax(True, only_firstlast)
5470
5739
 
5471
5740
  if colors is not None:
5472
5741
  self.colorize_data(colors, filled=True)
5473
5742
 
5474
5743
  if plotted and self.has_OGLContext and not self.shared:
5744
+ logging.info(_('Preparing OpenGL lists'))
5475
5745
  self.prep_listogl()
5746
+ logging.info(_('OpenGL lists ready'))
5747
+
5748
+ @property
5749
+ def mynames(self) -> list[str]:
5750
+ """ Return the names of all zones """
5751
+
5752
+ return [curzone.myname for curzone in self.myzones]
5753
+
5754
+ def add_values(self, key:str, values:np.ndarray | dict):
5755
+ """
5756
+ Add values to the zones
5757
+ """
5758
+
5759
+ if isinstance(values, dict):
5760
+ for k, val in values.items():
5761
+ if not isinstance(val, np.ndarray):
5762
+ val = np.asarray(val)
5763
+
5764
+ if k in self.mynames:
5765
+ self[k].add_values(key, val)
5766
+
5767
+ elif isinstance(values, np.ndarray):
5768
+ if values.shape[0] != self.nbzones:
5769
+ logging.warning(_('Number of values does not match the number of zones'))
5770
+ return
5771
+
5772
+ for idx, curzone in enumerate(self.myzones):
5773
+ curzone.add_values(key, values[idx])
5774
+
5775
+ def get_values(self, key:str) -> np.ndarray:
5776
+ """
5777
+ Get values from the zones
5778
+ """
5779
+
5780
+ return np.asarray([curzone.get_values(key) for curzone in self.myzones])
5781
+
5782
+ def set_colors_from_value(self, key:str, cmap:wolfpalette | str | Colormap | cm.ScalarMappable, vmin:float = 0., vmax:float = 1.):
5783
+ """
5784
+ Set colors to the zones
5785
+ """
5786
+
5787
+ if isinstance(cmap, str):
5788
+ cmap = cm.get_cmap(cmap)
5789
+
5790
+ for curzone in self.myzones:
5791
+ curzone.set_colors_from_value(key, cmap, vmin, vmax)
5792
+
5793
+ def set_alpha(self, alpha:int):
5794
+ """
5795
+ Set alpha to the zones
5796
+ """
5797
+
5798
+ for curzone in self.myzones:
5799
+ curzone.set_alpha(alpha)
5800
+
5801
+ def set_filled(self, filled:bool):
5802
+ """
5803
+ Set filled to the zones
5804
+ """
5805
+
5806
+ for curzone in self.myzones:
5807
+ curzone.set_filled(filled)
5808
+
5809
+ def check_if_open(self):
5810
+ """ Check if the vectors in the zone are open """
5811
+ for curzone in self.myzones:
5812
+ curzone.check_if_open()
5813
+
5814
+ def concatenate_all_vectors(self) -> list[vector]:
5815
+ """ Concatenate all vectors in the zones """
5816
+ ret = []
5817
+ for curzone in self.myzones:
5818
+ ret.extend(curzone.myvectors)
5819
+ return ret
5820
+
5821
+ def prepare_shapely(self):
5822
+ """ Prepare shapely objects for all vectors in zones """
5823
+ allvec = self.concatenate_all_vectors()
5824
+ list(map(lambda x: x.prepare_shapely(True), allvec))
5825
+
5826
+ def filter_contains(self, others:list[vector]) -> "Zones":
5827
+ """ Create a new "Zones" instance with
5828
+ vectors in 'others' contained in the zones """
5829
+
5830
+ if isinstance(others, Zones):
5831
+ allvec = others.concatenate_all_vectors()
5832
+ elif isinstance(others, list):
5833
+ allvec = others
5834
+ else:
5835
+ logging.warning(_('Unknown type for others'))
5836
+ return None
5837
+
5838
+ centroids = [curvec.centroid for curvec in allvec]
5839
+ newzones = Zones()
5840
+
5841
+ for curzone in self.myzones:
5842
+ if curzone.nbvectors != 1:
5843
+ logging.warning(_('Zone {} has more than one vector'.format(curzone.myname)))
5844
+ continue
5845
+
5846
+ poly = curzone[0].polygon
5847
+ # element-wise comparison
5848
+ contains = list(map(lambda x: poly.contains(x), centroids))
5849
+
5850
+ newzone = zone(name=curzone.myname, parent=newzones)
5851
+ newzone.myvectors = list(map(lambda x: allvec[x], np.where(contains)[0]))
5852
+ newzones.add_zone(newzone, forceparent=True)
5853
+
5854
+ return newzones
5855
+
5856
+ @property
5857
+ def areas(self):
5858
+ """ List of areas of all zones """
5859
+
5860
+ return [curzone.areas for curzone in self.myzones]
5861
+
5862
+ def buffer(self, distance:float, resolution:int = 16, inplace:bool = True):
5863
+ """ Buffer all zones """
5864
+
5865
+ if inplace:
5866
+ newzones = self
5867
+ else:
5868
+ newzones = Zones()
5869
+
5870
+ retmap = list(map(lambda x: x.buffer(distance, resolution, inplace=inplace), self.myzones))
5871
+
5872
+ for curzone in retmap:
5873
+ newzones.add_zone(curzone, forceparent=True)
5874
+
5875
+ newzones.find_minmax(True)
5876
+ if inplace:
5877
+ return self
5878
+
5879
+ return newzones
5476
5880
 
5477
5881
  def set_cache(self):
5478
5882
  """ Set cache for all zones """
@@ -5561,6 +5965,22 @@ class Zones(wx.Frame, Element_To_Draw):
5561
5965
  for curzone in self.myzones:
5562
5966
  curzone.set_legend_text(text)
5563
5967
 
5968
+ def set_legend_text_from_values(self, key:str):
5969
+ """
5970
+ Set the legend text for the zones from the values
5971
+ """
5972
+
5973
+ for curzone in self.myzones:
5974
+ curzone.set_legend_text_from_values(key)
5975
+
5976
+ def set_legend_to_centroid(self):
5977
+ """
5978
+ Set the legend to the centroid of the zones
5979
+ """
5980
+
5981
+ for curzone in self.myzones:
5982
+ curzone.set_legend_to_centroid()
5983
+
5564
5984
  def set_legend_position(self, x, y):
5565
5985
  """
5566
5986
  Set the legend position for the zones
@@ -5573,7 +5993,8 @@ class Zones(wx.Frame, Element_To_Draw):
5573
5993
  def nbzones(self):
5574
5994
  return len(self.myzones)
5575
5995
 
5576
- def import_shapefile(self, fn:str, bbox:Polygon = None):
5996
+ def import_shapefile(self, fn:str,
5997
+ bbox:Polygon = None, colname:str = None):
5577
5998
  """
5578
5999
  Import shapefile by using geopandas
5579
6000
 
@@ -5581,41 +6002,69 @@ class Zones(wx.Frame, Element_To_Draw):
5581
6002
 
5582
6003
  """
5583
6004
 
6005
+ logging.info(_('Importing shapefile {}'.format(fn)))
5584
6006
  content = gpd.read_file(fn, bbox=bbox)
5585
6007
 
5586
- for idx, row in content.iterrows():
5587
- if 'NAME' in row.keys():
6008
+ self.import_GeoDataFrame(content, colname=colname)
6009
+
6010
+ def import_GeoDataFrame(self, content:gpd.GeoDataFrame,
6011
+ bbox:Polygon = None, colname:str = None):
6012
+ """
6013
+ Import a GeoDataFrame geopandas
6014
+
6015
+ Shapefile == 1 zone
6016
+ """
6017
+
6018
+ logging.info(_('Importing GeoDataFrame'))
6019
+
6020
+ if bbox is not None:
6021
+ # filter content
6022
+ content = content.cx[bbox.bounds[0]:bbox.bounds[2], bbox.bounds[1]:bbox.bounds[3]]
6023
+
6024
+ logging.info(_('Converting DataFrame into zones'))
6025
+ if colname is not None and colname not in content.columns:
6026
+ logging.warning(_('Column {} not found in the DataFrame'.format(colname)))
6027
+ logging.info(_('We are using the available known columns'))
6028
+ colname = ''
6029
+
6030
+ def add_zone_from_row(row):
6031
+ idx, row = row
6032
+ keys = list(row.keys())
6033
+ if colname in keys:
6034
+ name = row[colname]
6035
+ elif 'NAME' in keys:
5588
6036
  name = row['NAME']
5589
- elif 'location' in row.keys():
6037
+ elif 'location' in keys:
5590
6038
  name = row['location'] # tuilage gdal
5591
- elif 'name' in row.keys():
6039
+ elif 'Communes' in keys:
6040
+ name = row['Communes']
6041
+ elif 'name' in keys:
5592
6042
  name = row['name']
5593
- elif 'MAJ_NIV3T' in row.keys():
6043
+ elif 'MAJ_NIV3T' in keys:
5594
6044
  # WALOUS
5595
6045
  name = row['MAJ_NIV3T']
5596
- elif 'NATUR_DESC' in row.keys():
6046
+ elif 'NATUR_DESC' in keys:
5597
6047
  name = row['NATUR_DESC']
6048
+ elif 'mun_name_f' in keys:
6049
+ name = row['mun_name_f'].replace('[','').replace(']','').replace("'",'')
6050
+ elif 'mun_name_fr' in keys:
6051
+ name = row['mun_name_fr']
5598
6052
  else:
5599
6053
  name = str(idx)
5600
6054
 
5601
6055
  poly = row['geometry']
5602
6056
 
5603
6057
  newzone = zone(name=name, parent = self, fromshapely = poly)
5604
- self.add_zone(newzone)
5605
-
5606
- def export_to_shapefile(self, filename:str):
5607
- """
5608
- Export to shapefile.
6058
+ return newzone
5609
6059
 
5610
- The first vector of each zone will be exported.
6060
+ self.myzones = list(map(add_zone_from_row, content.iterrows()))
5611
6061
 
5612
- If you want to export all vectors, you have to use "export_shape" of the zone object.
6062
+ pass
5613
6063
 
5614
- FIXME: Add support of data fields
6064
+ def export_GeoDataFrame(self) -> gpd.GeoDataFrame:
6065
+ """
6066
+ Export to a GeoDataFrame
5615
6067
  """
5616
-
5617
- import geopandas as gpd
5618
-
5619
6068
  names=[]
5620
6069
  geoms=[]
5621
6070
 
@@ -5632,9 +6081,9 @@ class Zones(wx.Frame, Element_To_Draw):
5632
6081
  for curvect in curzone.myvectors[:1]:
5633
6082
  if curvect.is2D:
5634
6083
  if curvect.closed:
5635
- geoms.append(curvect.asshapely_pol())
6084
+ geoms.append(curvect.polygon)
5636
6085
  else:
5637
- geoms.append(curvect.asshapely_ls())
6086
+ geoms.append(curvect.polygon)
5638
6087
  else:
5639
6088
  if curvect.closed:
5640
6089
  geoms.append(curvect.asshapely_pol3D())
@@ -5644,6 +6093,20 @@ class Zones(wx.Frame, Element_To_Draw):
5644
6093
  gdf = gpd.GeoDataFrame({'id':names,'geometry':geoms})
5645
6094
  gdf.crs = 'EPSG:31370'
5646
6095
 
6096
+ return gdf
6097
+
6098
+ def export_to_shapefile(self, filename:str):
6099
+ """
6100
+ Export to shapefile.
6101
+
6102
+ The first vector of each zone will be exported.
6103
+
6104
+ If you want to export all vectors, you have to use "export_shape" of the zone object.
6105
+
6106
+ FIXME: Add support of data fields
6107
+ """
6108
+
6109
+ gdf = self.export_GeoDataFrame()
5647
6110
  gdf.to_file(filename)
5648
6111
 
5649
6112
  def export_active_zone_to_shapefile(self, filename:str):
@@ -6060,10 +6523,14 @@ class Zones(wx.Frame, Element_To_Draw):
6060
6523
  :param only_firstlast : si True, ne prend en compte que le premier et le dernier vertex de chaque vecteur
6061
6524
  """
6062
6525
 
6063
- if update or self._first_find_minmax:
6064
- for zone in self.myzones:
6065
- zone.find_minmax(update or self._first_find_minmax, only_firstlast)
6066
- self._first_find_minmax = False
6526
+ if self.nbzones > 100:
6527
+ with ThreadPoolExecutor() as executor:
6528
+ # zone.find_minmax(update or self._first_find_minmax, only_firstlast)
6529
+ futures = [executor.submit(zone.find_minmax, update or self._first_find_minmax, only_firstlast) for zone in self.myzones]
6530
+ wait(futures)
6531
+ else:
6532
+ for curzone in self.myzones:
6533
+ curzone.find_minmax(update or self._first_find_minmax, only_firstlast)
6067
6534
 
6068
6535
  if self.nbzones > 0:
6069
6536
 
@@ -6090,6 +6557,12 @@ class Zones(wx.Frame, Element_To_Draw):
6090
6557
  for curzone in self.myzones:
6091
6558
  curzone.plot(sx=sx, sy=sy, xmin=xmin, ymin=ymin, xmax=xmax, ymax=ymax, size=size)
6092
6559
 
6560
+ def plot_matplotlib(self, ax:plt.Axes):
6561
+ """ Plot with matplotlib """
6562
+
6563
+ for curzone in self.myzones:
6564
+ curzone.plot_matplotlib(ax)
6565
+
6093
6566
  def select_vectors_from_point(self, x:float, y:float, inside=True):
6094
6567
  """
6095
6568
  Sélection de vecteurs dans chaque zones sur base d'une coordonnée
@@ -6322,10 +6795,20 @@ class Zones(wx.Frame, Element_To_Draw):
6322
6795
  boxright.Add(self.interpxyz,0,wx.EXPAND)
6323
6796
  boxright.Add(self.sascending,0,wx.EXPAND)
6324
6797
 
6798
+
6799
+ sizer_values_surface = wx.BoxSizer(wx.HORIZONTAL)
6325
6800
  self.butgetval = wx.Button(self,label=_('Get values'))
6326
6801
  self.butgetval.SetToolTip(_("Get values of the attached/active array (not working with 2D results) on each vertex of the active vector and update the editor"))
6327
6802
  self.butgetval.Bind(wx.EVT_BUTTON,self.Ongetvalues)
6328
- boxright.Add(self.butgetval,0,wx.EXPAND)
6803
+
6804
+ self.btn_surface = wx.Button(self,label=_('Surface'))
6805
+ self.btn_surface.SetToolTip(_("Compute the surface of the active vector/polygon"))
6806
+ self.btn_surface.Bind(wx.EVT_BUTTON,self.Onsurface)
6807
+
6808
+ sizer_values_surface.Add(self.butgetval,1,wx.EXPAND)
6809
+ sizer_values_surface.Add(self.btn_surface,1,wx.EXPAND)
6810
+
6811
+ boxright.Add(sizer_values_surface,0,wx.EXPAND)
6329
6812
 
6330
6813
  self.butgetvallinked = wx.Button(self,label=_('Get values (all)'))
6331
6814
  self.butgetvallinked.SetToolTip(_("Get values of all the visible arrays and 2D results on each vertex of the active vector \n\n Create a new zone containing the results"))
@@ -6893,6 +7376,18 @@ class Zones(wx.Frame, Element_To_Draw):
6893
7376
  except:
6894
7377
  raise Warning(_('Not supported in the current parent -- see PyVertexVectors in Ongetvalues function'))
6895
7378
 
7379
+ def Onsurface(self, e:wx.MouseEvent):
7380
+ """
7381
+ Calcul de la surface du vecteur actif
7382
+ """
7383
+
7384
+ if self.verify_activevec():
7385
+ return
7386
+
7387
+ dlg = wx.MessageDialog(None, _('The surface of the active vector is : {} m²'.format(self.active_vector.surface)), style = wx.OK)
7388
+ dlg.ShowModal()
7389
+ dlg.Destroy()
7390
+
6896
7391
  def Ongetvalueslinked(self, e:wx.MouseEvent):
6897
7392
  """
6898
7393
  Récupération des valeurs sous toutes les matrices liées pour le vecteur actif
@@ -8138,9 +8633,10 @@ class Zones(wx.Frame, Element_To_Draw):
8138
8633
  Zones (a new object).
8139
8634
  """
8140
8635
  copied_Zones = Zones(idx=name)
8141
- for zne in self.myzones:
8142
- new_zne = zne.deepcopy_zone(parent= copied_Zones)
8143
- copied_Zones.add_zone(new_zne,forceparent=True)
8636
+ copied_Zones.myzones = list(map(lambda zne: zne.deepcopy_zone(parent= copied_Zones), self.myzones))
8637
+ # for zne in self.myzones:
8638
+ # new_zne = zne.deepcopy_zone(parent= copied_Zones)
8639
+ # copied_Zones.add_zone(new_zne,forceparent=True)
8144
8640
  copied_Zones.find_minmax(True)
8145
8641
  return copied_Zones
8146
8642