wolfhece 2.1.127__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
@@ -1124,8 +1169,8 @@ class vector:
1124
1169
  self._rotation_center = None
1125
1170
  self._rotation_step = None
1126
1171
 
1127
- self.linestring = None
1128
- self.polygon = None
1172
+ self._linestring = None
1173
+ self._polygon = None
1129
1174
 
1130
1175
  # Utile surtout pour les sections en travers
1131
1176
  self.zdatum = 0.
@@ -1139,6 +1184,56 @@ class vector:
1139
1184
  if fromnumpy is not None:
1140
1185
  self.add_vertices_from_array(fromnumpy)
1141
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
+
1142
1237
  def set_legend_text(self, text:str):
1143
1238
  """ Set the legend text """
1144
1239
 
@@ -1165,6 +1260,14 @@ class vector:
1165
1260
 
1166
1261
  self.myprop.update_myprops()
1167
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
+
1168
1271
  def set_legend_position(self, x:str | float, y:str | float):
1169
1272
  """ Set the legend position """
1170
1273
 
@@ -1392,14 +1495,16 @@ class vector:
1392
1495
  def import_shapelyobj(self, obj):
1393
1496
  """ Importation d'un objet shapely """
1394
1497
 
1498
+ self.myvertices = []
1395
1499
  if isinstance(obj, LineString):
1396
- xy = np.array(obj.xy).T
1397
- self.is2D = xy.shape[1]==2
1398
- 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
+
1399
1504
  elif isinstance(obj, Polygon):
1400
- xy = np.array(obj.exterior.xy).T
1401
- self.is2D = xy.shape[1]==2
1402
- 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)
1403
1508
  self.close_force()
1404
1509
  else:
1405
1510
  logging.warning(_('Object type {} not supported -- Update "import_shapelyobj"').format(type(obj)))
@@ -1513,7 +1618,7 @@ class vector:
1513
1618
  if self.nbvertices==0:
1514
1619
  return False
1515
1620
 
1516
- poly = self.asshapely_pol()
1621
+ poly = self.polygon
1517
1622
  inside2 = poly.contains(Point([x,y]))
1518
1623
  return inside2
1519
1624
 
@@ -1580,7 +1685,7 @@ class vector:
1580
1685
  xyz[:,2]+=self.zdatum
1581
1686
  return xyz
1582
1687
 
1583
- def prepare_shapely(self, prepare_shapely:bool = True):
1688
+ def prepare_shapely(self, prepare_shapely:bool = True, linestring:bool = True, polygon:bool = True):
1584
1689
  """
1585
1690
  Conversion Linestring Shapely et rétention de l'objet afin d'éviter de multiples appel
1586
1691
  par ex. dans une boucle.
@@ -1588,17 +1693,23 @@ class vector:
1588
1693
  :param prepare_shapely: Préparation de l'objet Shapely pour une utilisation optimisée
1589
1694
  - True par défaut
1590
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
1591
1698
 
1592
1699
  """
1593
1700
 
1594
1701
  self.reset_linestring()
1595
1702
 
1596
- self.linestring = self.asshapely_ls()
1597
- self.polygon = self.asshapely_pol()
1703
+ if linestring:
1704
+ self._linestring = self.asshapely_ls()
1705
+ if polygon:
1706
+ self._polygon = self.asshapely_pol()
1598
1707
 
1599
1708
  if prepare_shapely:
1600
- prepare(self.linestring)
1601
- prepare(self.polygon)
1709
+ if linestring:
1710
+ prepare(self._linestring)
1711
+ if polygon:
1712
+ prepare(self._polygon)
1602
1713
 
1603
1714
 
1604
1715
  def projectontrace(self, trace):
@@ -1663,6 +1774,8 @@ class vector:
1663
1774
  def intersection(self, vec2:"vector" = None, eval_dist=False,norm=False, force_single=False):
1664
1775
  """
1665
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.
1666
1779
 
1667
1780
  Return :
1668
1781
  - le point d'intersection
@@ -1670,8 +1783,13 @@ class vector:
1670
1783
 
1671
1784
  Utilisation de Shapely
1672
1785
  """
1673
- ls1 = self.asshapely_ls() if self.linestring is None else self.linestring
1674
- 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
+
1675
1793
 
1676
1794
  myinter = ls1.intersection(ls2)
1677
1795
 
@@ -1702,15 +1820,15 @@ class vector:
1702
1820
  def reset_linestring(self):
1703
1821
  """Remise à zéro de l'objet Shapely"""
1704
1822
 
1705
- if self.linestring is not None:
1706
- if is_prepared(self.linestring):
1707
- destroy_prepared(self.linestring)
1708
- 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
1709
1827
 
1710
- if self.polygon is not None:
1711
- if is_prepared(self.polygon):
1712
- destroy_prepared(self.polygon)
1713
- 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
1714
1832
 
1715
1833
  def add_vertex(self,addedvert: Union[list[wolfvertex], wolfvertex]):
1716
1834
  """
@@ -1980,7 +2098,7 @@ class vector:
1980
2098
 
1981
2099
  if self.myprop.filled:
1982
2100
 
1983
- ls = self.asshapely_pol()
2101
+ ls = self.polygon
1984
2102
 
1985
2103
  if False:
1986
2104
 
@@ -2059,6 +2177,20 @@ class vector:
2059
2177
  else:
2060
2178
  self.textimage = None
2061
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
+
2062
2194
  def plot_image(self, sx=None, sy=None, xmin=None, ymin=None, xmax=None, ymax=None, size=None):
2063
2195
  """ plot attached images """
2064
2196
 
@@ -2076,6 +2208,28 @@ class vector:
2076
2208
  else:
2077
2209
  logging.warning(_('No image texture available for plot'))
2078
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
+
2079
2233
  def _get_textfont(self):
2080
2234
  """ Retunr a 'Text_Infos' instance for the legend """
2081
2235
 
@@ -2177,7 +2331,7 @@ class vector:
2177
2331
  while k<self.nbvertices:
2178
2332
  self.myvertices.pop(k)
2179
2333
 
2180
- 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:
2181
2335
  self.prepare_shapely()
2182
2336
 
2183
2337
  self._reset_listogl()
@@ -2783,6 +2937,11 @@ class vector:
2783
2937
  copied_vector = vector(name=name)
2784
2938
 
2785
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
2786
2945
 
2787
2946
  return copied_vector
2788
2947
 
@@ -2793,19 +2952,35 @@ class vector:
2793
2952
 
2794
2953
  return self.deepcopy_vector(name, parentzone)
2795
2954
 
2955
+ @property
2956
+ def centroid(self):
2957
+ """
2958
+ Return the centroid of the vector
2959
+ """
2960
+
2961
+ return self.polygon.centroid
2962
+
2796
2963
  def set_legend_to_centroid(self, text:str='', visible:bool=True):
2797
2964
  """
2798
2965
  Positionne la légende au centre du vecteur
2799
2966
  """
2800
2967
  self.myprop.legendvisible = visible
2801
2968
 
2802
- ls = self.asshapely_pol()
2803
- centroid = ls.centroid
2969
+ centroid = self.centroid
2804
2970
 
2805
2971
  self.myprop.legendx = centroid.x
2806
2972
  self.myprop.legendy = centroid.y
2807
2973
  self.myprop.legendtext = text if text else self.myname
2808
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
+
2809
2984
  def set_z(self, new_z:np.ndarray):
2810
2985
  """ Set the z values of the vertices """
2811
2986
  self.z = new_z
@@ -3111,7 +3286,6 @@ class vector:
3111
3286
  if self.parentzone is not None:
3112
3287
  self.parentzone.reset_listogl()
3113
3288
 
3114
-
3115
3289
  def select_points_inside(self, xy:cloud_vertices | np.ndarray):
3116
3290
  """ Select the points inside a polygon
3117
3291
 
@@ -3119,7 +3293,7 @@ class vector:
3119
3293
  :return: list of boolean
3120
3294
  """
3121
3295
 
3122
- self.prepare_shapely()
3296
+ self.prepare_shapely(True)
3123
3297
 
3124
3298
  if isinstance(xy, cloud_vertices):
3125
3299
  xy = xy.get_xyz()[:,0:2]
@@ -3176,7 +3350,7 @@ class vector:
3176
3350
  """
3177
3351
 
3178
3352
  if self.closed:
3179
- return self.asshapely_pol().area
3353
+ return self.polygon.area
3180
3354
  else:
3181
3355
  return 0.
3182
3356
 
@@ -3266,6 +3440,62 @@ class zone:
3266
3440
  # Object can be created from a shapely object
3267
3441
  self.import_shapelyobj(fromshapely)
3268
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
+
3269
3499
  def set_legend_text(self, text:str):
3270
3500
  """
3271
3501
  Set the legend text for the zone
@@ -3274,6 +3504,14 @@ class zone:
3274
3504
  for curvect in self.myvectors:
3275
3505
  curvect.set_legend_text(text)
3276
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
+
3277
3515
  def set_legend_position(self, x, y):
3278
3516
  """
3279
3517
  Set the legend position for the zone
@@ -3285,7 +3523,12 @@ class zone:
3285
3523
  @property
3286
3524
  def area(self):
3287
3525
  """ Compute the area of the zone """
3288
- 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]
3289
3532
 
3290
3533
  def set_cache(self):
3291
3534
  """
@@ -3644,6 +3887,16 @@ class zone:
3644
3887
  for curvect in self.myvectors:
3645
3888
  curvect.plot_image(sx, sy, xmin, ymin, xmax, ymax, size)
3646
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)
3647
3900
 
3648
3901
  def select_vectors_from_point(self,x:float,y:float,inside=True):
3649
3902
  """
@@ -5294,11 +5547,11 @@ class zone:
5294
5547
 
5295
5548
  def set_legend_to_centroid(self):
5296
5549
  """
5297
- Set the legend to the centroid of the zone
5550
+ Set the legend to the centroid of the vectors
5298
5551
  """
5299
5552
 
5300
5553
  for curvec in self.myvectors:
5301
- curvec.set_legend_to_centroid()
5554
+ curvec.set_legend_position_to_centroid()
5302
5555
 
5303
5556
  class Zones(wx.Frame, Element_To_Draw):
5304
5557
  """
@@ -5413,24 +5666,29 @@ class Zones(wx.Frame, Element_To_Draw):
5413
5666
  self._rotation_center = None
5414
5667
  self._rotation_step = None
5415
5668
 
5669
+ only_firstlast = False # By default, we are using all vertices
5416
5670
  if self.filename!='':
5417
5671
  # lecture du fichier
5418
5672
 
5419
5673
  if self.filename.endswith('.dxf'):
5420
5674
  self.is2D=False
5421
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
5422
5677
 
5423
5678
  elif self.filename.endswith('.shp'):
5424
5679
  self.is2D=False
5425
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
5426
5682
 
5427
5683
  elif self.filename.endswith('.gpkg'):
5428
5684
  self.is2D=False
5429
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
5430
5687
 
5431
5688
  elif Path(filename).is_dir() and self.filename.endswith('.gdb'):
5432
5689
  self.is2D=False
5433
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
5434
5692
 
5435
5693
  elif self.filename.endswith('.vec') or self.filename.endswith('.vecz'):
5436
5694
 
@@ -5476,13 +5734,149 @@ class Zones(wx.Frame, Element_To_Draw):
5476
5734
  logging.warning(_('Error while reading extra properties of {}'.format(self.filename)))
5477
5735
 
5478
5736
  if find_minmax:
5479
- self.find_minmax(True)
5737
+ logging.info(_('Finding min and max values'))
5738
+ self.find_minmax(True, only_firstlast)
5480
5739
 
5481
5740
  if colors is not None:
5482
5741
  self.colorize_data(colors, filled=True)
5483
5742
 
5484
5743
  if plotted and self.has_OGLContext and not self.shared:
5744
+ logging.info(_('Preparing OpenGL lists'))
5485
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
5486
5880
 
5487
5881
  def set_cache(self):
5488
5882
  """ Set cache for all zones """
@@ -5571,6 +5965,22 @@ class Zones(wx.Frame, Element_To_Draw):
5571
5965
  for curzone in self.myzones:
5572
5966
  curzone.set_legend_text(text)
5573
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
+
5574
5984
  def set_legend_position(self, x, y):
5575
5985
  """
5576
5986
  Set the legend position for the zones
@@ -5583,7 +5993,8 @@ class Zones(wx.Frame, Element_To_Draw):
5583
5993
  def nbzones(self):
5584
5994
  return len(self.myzones)
5585
5995
 
5586
- def import_shapefile(self, fn:str, bbox:Polygon = None):
5996
+ def import_shapefile(self, fn:str,
5997
+ bbox:Polygon = None, colname:str = None):
5587
5998
  """
5588
5999
  Import shapefile by using geopandas
5589
6000
 
@@ -5591,41 +6002,69 @@ class Zones(wx.Frame, Element_To_Draw):
5591
6002
 
5592
6003
  """
5593
6004
 
6005
+ logging.info(_('Importing shapefile {}'.format(fn)))
5594
6006
  content = gpd.read_file(fn, bbox=bbox)
5595
6007
 
5596
- for idx, row in content.iterrows():
5597
- 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:
5598
6036
  name = row['NAME']
5599
- elif 'location' in row.keys():
6037
+ elif 'location' in keys:
5600
6038
  name = row['location'] # tuilage gdal
5601
- elif 'name' in row.keys():
6039
+ elif 'Communes' in keys:
6040
+ name = row['Communes']
6041
+ elif 'name' in keys:
5602
6042
  name = row['name']
5603
- elif 'MAJ_NIV3T' in row.keys():
6043
+ elif 'MAJ_NIV3T' in keys:
5604
6044
  # WALOUS
5605
6045
  name = row['MAJ_NIV3T']
5606
- elif 'NATUR_DESC' in row.keys():
6046
+ elif 'NATUR_DESC' in keys:
5607
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']
5608
6052
  else:
5609
6053
  name = str(idx)
5610
6054
 
5611
6055
  poly = row['geometry']
5612
6056
 
5613
6057
  newzone = zone(name=name, parent = self, fromshapely = poly)
5614
- self.add_zone(newzone)
5615
-
5616
- def export_to_shapefile(self, filename:str):
5617
- """
5618
- Export to shapefile.
6058
+ return newzone
5619
6059
 
5620
- The first vector of each zone will be exported.
6060
+ self.myzones = list(map(add_zone_from_row, content.iterrows()))
5621
6061
 
5622
- If you want to export all vectors, you have to use "export_shape" of the zone object.
6062
+ pass
5623
6063
 
5624
- FIXME: Add support of data fields
6064
+ def export_GeoDataFrame(self) -> gpd.GeoDataFrame:
6065
+ """
6066
+ Export to a GeoDataFrame
5625
6067
  """
5626
-
5627
- import geopandas as gpd
5628
-
5629
6068
  names=[]
5630
6069
  geoms=[]
5631
6070
 
@@ -5642,9 +6081,9 @@ class Zones(wx.Frame, Element_To_Draw):
5642
6081
  for curvect in curzone.myvectors[:1]:
5643
6082
  if curvect.is2D:
5644
6083
  if curvect.closed:
5645
- geoms.append(curvect.asshapely_pol())
6084
+ geoms.append(curvect.polygon)
5646
6085
  else:
5647
- geoms.append(curvect.asshapely_ls())
6086
+ geoms.append(curvect.polygon)
5648
6087
  else:
5649
6088
  if curvect.closed:
5650
6089
  geoms.append(curvect.asshapely_pol3D())
@@ -5654,6 +6093,20 @@ class Zones(wx.Frame, Element_To_Draw):
5654
6093
  gdf = gpd.GeoDataFrame({'id':names,'geometry':geoms})
5655
6094
  gdf.crs = 'EPSG:31370'
5656
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()
5657
6110
  gdf.to_file(filename)
5658
6111
 
5659
6112
  def export_active_zone_to_shapefile(self, filename:str):
@@ -6070,10 +6523,14 @@ class Zones(wx.Frame, Element_To_Draw):
6070
6523
  :param only_firstlast : si True, ne prend en compte que le premier et le dernier vertex de chaque vecteur
6071
6524
  """
6072
6525
 
6073
- if update or self._first_find_minmax:
6074
- for zone in self.myzones:
6075
- zone.find_minmax(update or self._first_find_minmax, only_firstlast)
6076
- 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)
6077
6534
 
6078
6535
  if self.nbzones > 0:
6079
6536
 
@@ -6100,6 +6557,12 @@ class Zones(wx.Frame, Element_To_Draw):
6100
6557
  for curzone in self.myzones:
6101
6558
  curzone.plot(sx=sx, sy=sy, xmin=xmin, ymin=ymin, xmax=xmax, ymax=ymax, size=size)
6102
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
+
6103
6566
  def select_vectors_from_point(self, x:float, y:float, inside=True):
6104
6567
  """
6105
6568
  Sélection de vecteurs dans chaque zones sur base d'une coordonnée
@@ -8170,9 +8633,10 @@ class Zones(wx.Frame, Element_To_Draw):
8170
8633
  Zones (a new object).
8171
8634
  """
8172
8635
  copied_Zones = Zones(idx=name)
8173
- for zne in self.myzones:
8174
- new_zne = zne.deepcopy_zone(parent= copied_Zones)
8175
- 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)
8176
8640
  copied_Zones.find_minmax(True)
8177
8641
  return copied_Zones
8178
8642