wolfhece 2.1.127__py3-none-any.whl → 2.1.129__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.
- wolfhece/PyDraw.py +52 -3
- wolfhece/PyPalette.py +36 -0
- wolfhece/PyParams.py +7 -3
- wolfhece/PyVertexvectors.py +579 -70
- wolfhece/apps/version.py +1 -1
- wolfhece/coupling/hydrology_2d.py +295 -192
- wolfhece/eikonal.py +13 -3
- wolfhece/hydrology/Catchment.py +153 -52
- wolfhece/hydrology/Comparison.py +29 -25
- wolfhece/hydrology/Optimisation.py +309 -178
- wolfhece/hydrology/PostProcessHydrology.py +6 -3
- wolfhece/hydrology/PyWatershed.py +93 -93
- wolfhece/hydrology/RetentionBasin.py +21 -14
- wolfhece/hydrology/SubBasin.py +128 -12
- wolfhece/hydrology/constant.py +3 -0
- wolfhece/hydrology/cst_exchanges.py +364 -38
- wolfhece/hydrology/plot_hydrology.py +32 -16
- wolfhece/hydrology/read.py +16 -6
- wolfhece/lagrange_multiplier.py +205 -0
- wolfhece/libs/WolfDll.dll +0 -0
- wolfhece/math_parser/calculator.py +1 -0
- wolfhece/pybridges.py +2 -2
- wolfhece/pypolygons_scen.py +2 -2
- wolfhece/radar/wolfradar.py +75 -22
- wolfhece/shapes/__init__.py +0 -0
- wolfhece/shapes/circle.py +335 -0
- wolfhece/wolf_array.py +834 -40
- wolfhece/wolfresults_2D.py +204 -13
- {wolfhece-2.1.127.dist-info → wolfhece-2.1.129.dist-info}/METADATA +5 -5
- {wolfhece-2.1.127.dist-info → wolfhece-2.1.129.dist-info}/RECORD +33 -30
- {wolfhece-2.1.127.dist-info → wolfhece-2.1.129.dist-info}/WHEEL +1 -1
- {wolfhece-2.1.127.dist-info → wolfhece-2.1.129.dist-info}/entry_points.txt +0 -0
- {wolfhece-2.1.127.dist-info → wolfhece-2.1.129.dist-info}/top_level.txt +0 -0
wolfhece/wolf_array.py
CHANGED
@@ -43,6 +43,7 @@ from scipy.ndimage import laplace, label, sum_labels
|
|
43
43
|
import pygltflib
|
44
44
|
from shapely.geometry import Point, LineString, MultiLineString, Polygon, MultiPolygon, MultiPoint
|
45
45
|
from shapely.ops import linemerge, substring, polygonize_full
|
46
|
+
from shapely import contains, contains_properly, contains_xy, touches, prepare, destroy_prepared, is_prepared
|
46
47
|
from os.path import dirname,basename,join
|
47
48
|
import logging
|
48
49
|
from typing import Literal
|
@@ -379,7 +380,51 @@ class header_wolf():
|
|
379
380
|
|
380
381
|
self.head_blocks[_key] = deepcopy(value)
|
381
382
|
|
382
|
-
|
383
|
+
@property
|
384
|
+
def resolution(self):
|
385
|
+
return self.get_resolution()
|
386
|
+
|
387
|
+
@resolution.setter
|
388
|
+
def resolution(self, value:tuple[float]):
|
389
|
+
|
390
|
+
if len(value) == 2:
|
391
|
+
self.set_resolution(value[0], value[1])
|
392
|
+
elif len(value) == 3:
|
393
|
+
self.set_resolution(value[0], value[1], value[2])
|
394
|
+
|
395
|
+
@property
|
396
|
+
def origin(self):
|
397
|
+
return self.get_origin()
|
398
|
+
|
399
|
+
@origin.setter
|
400
|
+
def origin(self, value:tuple[float]):
|
401
|
+
|
402
|
+
if len(value) == 2:
|
403
|
+
self.set_origin(value[0], value[1])
|
404
|
+
elif len(value) == 3:
|
405
|
+
self.set_origin(value[0], value[1], value[2])
|
406
|
+
|
407
|
+
@property
|
408
|
+
def translation(self):
|
409
|
+
return self.get_translation()
|
410
|
+
|
411
|
+
@translation.setter
|
412
|
+
def translation(self, value:tuple[float]):
|
413
|
+
if len(value) == 2:
|
414
|
+
self.set_translation(value[0], value[1])
|
415
|
+
elif len(value) == 3:
|
416
|
+
self.set_translation(value[0], value[1], value[2])
|
417
|
+
|
418
|
+
def set_resolution(self, dx:float, dy:float, dz:float= 0.):
|
419
|
+
"""
|
420
|
+
Set resolution
|
421
|
+
"""
|
422
|
+
|
423
|
+
self.dx = dx
|
424
|
+
self.dy = dy
|
425
|
+
self.dz = dz
|
426
|
+
|
427
|
+
def set_origin(self, x:float, y:float, z:float = 0.):
|
383
428
|
"""
|
384
429
|
Set origin
|
385
430
|
|
@@ -391,7 +436,28 @@ class header_wolf():
|
|
391
436
|
self.origy = y
|
392
437
|
self.origz = z
|
393
438
|
|
394
|
-
def
|
439
|
+
def get_origin(self):
|
440
|
+
"""
|
441
|
+
Return origin
|
442
|
+
"""
|
443
|
+
|
444
|
+
return (self.origx, self.origy, self.origz)
|
445
|
+
|
446
|
+
def get_resolution(self):
|
447
|
+
"""
|
448
|
+
Return resolution
|
449
|
+
"""
|
450
|
+
|
451
|
+
return (self.dx, self.dy, self.dz)
|
452
|
+
|
453
|
+
def get_translation(self):
|
454
|
+
"""
|
455
|
+
Return translation
|
456
|
+
"""
|
457
|
+
|
458
|
+
return (self.translx, self.transly, self.translz)
|
459
|
+
|
460
|
+
def set_translation(self, tr_x:float, tr_y:float, tr_z:float= 0.):
|
395
461
|
"""
|
396
462
|
Set translation
|
397
463
|
|
@@ -576,6 +642,44 @@ class header_wolf():
|
|
576
642
|
else:
|
577
643
|
raise Exception(_("The number of coordinates is not correct"))
|
578
644
|
|
645
|
+
def transform(self):
|
646
|
+
""" Return the affine transformation.
|
647
|
+
|
648
|
+
Similar to rasterio.transform.Affine
|
649
|
+
|
650
|
+
In WOLF, the convention is :
|
651
|
+
- origin is at the lower-left corner
|
652
|
+
- the origin is at the corner of the cell dx, dy, so the center of the cell is at dx/2, dy/2
|
653
|
+
- X axis is along the rows - i index
|
654
|
+
- Y axis is along the columns - j index
|
655
|
+
|
656
|
+
So, the affine transformation is :
|
657
|
+
(dx, 0, origx + translx + dx /2, 0, dy, origy + transly + dy/2)
|
658
|
+
"""
|
659
|
+
from rasterio.transform import Affine
|
660
|
+
return Affine(self.dx, 0, self.origx + self.translx + self.dx / 2,
|
661
|
+
0, self.dy, self.origy + self.transly + self.dy / 2)
|
662
|
+
|
663
|
+
def _transform_gmrio(self):
|
664
|
+
""" Return the affine transformation.
|
665
|
+
|
666
|
+
!! Inverted ij/ji convention !!
|
667
|
+
|
668
|
+
Similar to rasterio.transform.Affine
|
669
|
+
|
670
|
+
In WOLF, the convention is :
|
671
|
+
- origin is at the lower-left corner
|
672
|
+
- the origin is at the corner of the cell dx, dy, so the center of the cell is at dx/2, dy/2
|
673
|
+
- X axis is along the rows - i index
|
674
|
+
- Y axis is along the columns - j index
|
675
|
+
|
676
|
+
So, the affine transformation is :
|
677
|
+
(dx, 0, origx + translx + dx /2, 0, dy, origy + transly + dy/2)
|
678
|
+
"""
|
679
|
+
from rasterio.transform import Affine
|
680
|
+
return Affine(0, self.dy, -(self.origy + self.transly),
|
681
|
+
self.dx, 0, -(self.origx + self.transly))
|
682
|
+
|
579
683
|
def get_xy_from_ij_array(self, ij:np.ndarray, scale:float=1., aswolf:bool=False, abs:bool=True) -> np.ndarray:
|
580
684
|
"""
|
581
685
|
Converts array coordinates (numpy cells) to this array's world coodinates.
|
@@ -1028,6 +1132,23 @@ class header_wolf():
|
|
1028
1132
|
|
1029
1133
|
self.head_blocks[getkeyblock(i)] = curhead
|
1030
1134
|
|
1135
|
+
@classmethod
|
1136
|
+
def read_header(cls, filename:str) -> "header_wolf":
|
1137
|
+
"""
|
1138
|
+
alias for read_txt_header
|
1139
|
+
"""
|
1140
|
+
newhead = cls()
|
1141
|
+
newhead.read_txt_header(filename)
|
1142
|
+
return newhead
|
1143
|
+
|
1144
|
+
def write_header(self, filename:str,
|
1145
|
+
wolftype:int,
|
1146
|
+
forceupdate:bool=False):
|
1147
|
+
"""
|
1148
|
+
alias for write_txt_header
|
1149
|
+
"""
|
1150
|
+
self.write_txt_header(filename, wolftype, forceupdate)
|
1151
|
+
|
1031
1152
|
def write_txt_header(self,
|
1032
1153
|
filename:str,
|
1033
1154
|
wolftype:int,
|
@@ -1920,6 +2041,8 @@ class Ops_Array(wx.Frame):
|
|
1920
2041
|
|
1921
2042
|
self.labelling = wx.Button(self.tools, wx.ID_ANY, _("Labelling"), wx.DefaultPosition,wx.DefaultSize, 0)
|
1922
2043
|
|
2044
|
+
self.statistics = wx.Button(self.tools, wx.ID_ANY, _("Statistics"), wx.DefaultPosition,wx.DefaultSize, 0)
|
2045
|
+
|
1923
2046
|
self.clean = wx.Button(self.tools, wx.ID_ANY, _("Clean"), wx.DefaultPosition,wx.DefaultSize, 0)
|
1924
2047
|
|
1925
2048
|
self.extract_selection = wx.Button(self.tools, wx.ID_ANY, _("Extract selection"), wx.DefaultPosition,wx.DefaultSize, 0)
|
@@ -1938,6 +2061,7 @@ class Ops_Array(wx.Frame):
|
|
1938
2061
|
Toolssizer.Add(self.labelling, 1, wx.EXPAND)
|
1939
2062
|
Toolssizer.Add(self.clean, 1, wx.EXPAND)
|
1940
2063
|
Toolssizer.Add(self.extract_selection, 1, wx.EXPAND)
|
2064
|
+
Toolssizer.Add(self.statistics, 1, wx.EXPAND)
|
1941
2065
|
Toolssizer.Add(cont_sizer, 1, wx.EXPAND)
|
1942
2066
|
|
1943
2067
|
self.ApplyTools.SetToolTip(_("Apply Nullvalue into memory/object"))
|
@@ -1946,6 +2070,7 @@ class Ops_Array(wx.Frame):
|
|
1946
2070
|
self.labelling.SetToolTip(_("Labelling of contiguous zones using Scipy.label function\n\nReplacing the current values by the labels"))
|
1947
2071
|
self.clean.SetToolTip(_("Clean the array\n\nRemove small isolated patches of data"))
|
1948
2072
|
self.extract_selection.SetToolTip(_("Extract the current selection"))
|
2073
|
+
self.statistics.SetToolTip(_("Compute statistics on the array\n\nResults are displayed in a dialog box"))
|
1949
2074
|
|
1950
2075
|
self.tools.SetSizer(Toolssizer)
|
1951
2076
|
self.tools.Layout()
|
@@ -2318,6 +2443,7 @@ class Ops_Array(wx.Frame):
|
|
2318
2443
|
self.filter_zone.Bind(wx.EVT_BUTTON, self.OnFilterZone)
|
2319
2444
|
self.clean.Bind(wx.EVT_BUTTON, self.OnClean)
|
2320
2445
|
self.labelling.Bind(wx.EVT_BUTTON, self.OnLabelling)
|
2446
|
+
self.statistics.Bind(wx.EVT_BUTTON, self.OnStatistics)
|
2321
2447
|
self.extract_selection.Bind(wx.EVT_BUTTON, self.OnExtractSelection)
|
2322
2448
|
self._contour_int.Bind(wx.EVT_BUTTON, self.OnContourInt)
|
2323
2449
|
self._contour_list.Bind(wx.EVT_BUTTON, self.OnContourList)
|
@@ -2792,6 +2918,40 @@ class Ops_Array(wx.Frame):
|
|
2792
2918
|
|
2793
2919
|
self.parentarray.labelling()
|
2794
2920
|
|
2921
|
+
def OnStatistics(self, event:wx.MouseEvent):
|
2922
|
+
""" Statistics on the array """
|
2923
|
+
|
2924
|
+
ret = self.parentarray.statistics()
|
2925
|
+
|
2926
|
+
ret_frame = wx.Frame(None, -1, _('Statistics of {} on selected values').format(self.parentarray.idx), size = (300, 200))
|
2927
|
+
|
2928
|
+
icon = wx.Icon()
|
2929
|
+
icon_path = Path(__file__).parent / "apps/wolf_logo2.bmp"
|
2930
|
+
icon.CopyFromBitmap(wx.Bitmap(str(icon_path), wx.BITMAP_TYPE_ANY))
|
2931
|
+
ret_frame.SetIcon(icon)
|
2932
|
+
|
2933
|
+
ret_panel = wx.Panel(ret_frame, -1)
|
2934
|
+
|
2935
|
+
sizer = wx.BoxSizer(wx.VERTICAL)
|
2936
|
+
# Add a cpGrid and put statistics in it
|
2937
|
+
cp = CpGrid(ret_panel, wx.ID_ANY, style=wx.WANTS_CHARS | wx.TE_CENTER)
|
2938
|
+
cp.CreateGrid(len(ret), 2)
|
2939
|
+
|
2940
|
+
sizer.Add(cp, 1, wx.EXPAND)
|
2941
|
+
|
2942
|
+
for i, (key,val) in enumerate(ret.items()):
|
2943
|
+
if i != len(ret)-1:
|
2944
|
+
cp.SetCellValue(i, 0, key)
|
2945
|
+
cp.SetCellValue(i, 1, str(val))
|
2946
|
+
|
2947
|
+
ret_panel.SetSizer(sizer)
|
2948
|
+
ret_panel.Layout()
|
2949
|
+
|
2950
|
+
ret_frame.SetSize((300, 200))
|
2951
|
+
ret_frame.Centre()
|
2952
|
+
|
2953
|
+
ret_frame.Show()
|
2954
|
+
|
2795
2955
|
def OnExtractSelection(self, event:wx.MouseEvent):
|
2796
2956
|
""" Extract the current selection """
|
2797
2957
|
|
@@ -5893,6 +6053,30 @@ class WolfArray(Element_To_Draw, header_wolf):
|
|
5893
6053
|
if reset_plot:
|
5894
6054
|
self.reset_plot()
|
5895
6055
|
|
6056
|
+
def statistics(self, inside_polygon:vector | Polygon = None):
|
6057
|
+
"""
|
6058
|
+
Statistics on Selected data or the whole array if no selection
|
6059
|
+
|
6060
|
+
:param inside_polygon: vector or Polygon to select data inside the polygon
|
6061
|
+
:return: mean, std, median, sum, values
|
6062
|
+
"""
|
6063
|
+
|
6064
|
+
if inside_polygon is not None:
|
6065
|
+
ij = self.get_ij_inside_polygon(inside_polygon)
|
6066
|
+
vals = self.array[ij]
|
6067
|
+
elif self.SelectionData.nb == 0 or self.SelectionData.myselection == 'all':
|
6068
|
+
logging.info(_('No selection -- statistics on the whole array'))
|
6069
|
+
vals = self.array[~self.array.mask] # all values
|
6070
|
+
else:
|
6071
|
+
vals = self.SelectionData.get_values_sel()
|
6072
|
+
|
6073
|
+
mean = np.mean(vals)
|
6074
|
+
std = np.std(vals)
|
6075
|
+
median = np.median(vals)
|
6076
|
+
sum = np.sum(vals)
|
6077
|
+
|
6078
|
+
return {_('Mean'): mean, _('Std'): std, _('Median'): median, _('Sum'): sum, _('Values'): vals}
|
6079
|
+
|
5896
6080
|
def export_geotif(self, outdir:str= '', extent:str= '', EPSG:int = 31370):
|
5897
6081
|
"""
|
5898
6082
|
Export de la matrice au format Geotiff (Lambert 72 - EPSG:31370)
|
@@ -5983,7 +6167,15 @@ class WolfArray(Element_To_Draw, header_wolf):
|
|
5983
6167
|
band.SetNoDataValue(nullvalue)
|
5984
6168
|
band.WriteArray(np.flipud(arr.data.transpose()))
|
5985
6169
|
band.FlushCache()
|
5986
|
-
|
6170
|
+
try:
|
6171
|
+
band.ComputeStatistics(True)
|
6172
|
+
except RuntimeError as e:
|
6173
|
+
# If the array is mainly composed of null values, ComputeStatistics(True) will fail because ComputeStatistics(True) takes a subset of the data to compute statistics (and it may only find null values resulting to an error while in the array, it is not the case)
|
6174
|
+
try:
|
6175
|
+
band.ComputeStatistics(0) # Using 0 for exact stats instead of True (which implies approximate)
|
6176
|
+
except RuntimeError as e:
|
6177
|
+
print("Warning: ComputeStatistics failed:", e)
|
6178
|
+
|
5987
6179
|
|
5988
6180
|
|
5989
6181
|
def get_dxdy_min(self):
|
@@ -7812,7 +8004,8 @@ class WolfArray(Element_To_Draw, header_wolf):
|
|
7812
8004
|
|
7813
8005
|
return newArray
|
7814
8006
|
|
7815
|
-
def mask_outsidepoly(self, myvect: vector, eps:float = 0.,
|
8007
|
+
def mask_outsidepoly(self, myvect: vector, eps:float = 0.,
|
8008
|
+
set_nullvalue:bool=True, method:Literal['mpl', 'shapely_strict', 'shapely_wboundary', 'rasterio']='shapely_strict'):
|
7816
8009
|
"""
|
7817
8010
|
Mask nodes outside a polygon and set values to nullvalue
|
7818
8011
|
|
@@ -7823,7 +8016,7 @@ class WolfArray(Element_To_Draw, header_wolf):
|
|
7823
8016
|
# (mesh coord, 0-based)
|
7824
8017
|
|
7825
8018
|
# trouve les indices dans le polygone
|
7826
|
-
myij = self.get_ij_inside_polygon(myvect, usemask=True, eps=eps)
|
8019
|
+
myij = self.get_ij_inside_polygon(myvect, usemask=True, eps=eps, method=method)
|
7827
8020
|
|
7828
8021
|
self.array.mask.fill(True) # Mask everything
|
7829
8022
|
|
@@ -7836,7 +8029,8 @@ class WolfArray(Element_To_Draw, header_wolf):
|
|
7836
8029
|
|
7837
8030
|
self.count()
|
7838
8031
|
|
7839
|
-
def mask_insidepoly(self, myvect: vector, eps:float = 0.,
|
8032
|
+
def mask_insidepoly(self, myvect: vector, eps:float = 0.,
|
8033
|
+
set_nullvalue:bool=True, method:Literal['mpl', 'shapely_strict', 'shapely_wboundary', 'rasterio']='mpl'):
|
7840
8034
|
"""
|
7841
8035
|
Mask nodes inside a polygon and set values to nullvalue
|
7842
8036
|
|
@@ -7847,7 +8041,7 @@ class WolfArray(Element_To_Draw, header_wolf):
|
|
7847
8041
|
# (mesh coord, 0-based)
|
7848
8042
|
|
7849
8043
|
# trouve les indices dans le polygone
|
7850
|
-
myij = self.get_ij_inside_polygon(myvect, usemask=False, eps=eps)
|
8044
|
+
myij = self.get_ij_inside_polygon(myvect, usemask=False, eps=eps, method=method)
|
7851
8045
|
|
7852
8046
|
if set_nullvalue:
|
7853
8047
|
# annulation des valeurs en dehors du polygone
|
@@ -7858,12 +8052,47 @@ class WolfArray(Element_To_Draw, header_wolf):
|
|
7858
8052
|
|
7859
8053
|
self.count()
|
7860
8054
|
|
8055
|
+
def mask_insidepolys(self, myvects: list[vector], eps:float = 0.,
|
8056
|
+
set_nullvalue:bool=True, method:Literal['mpl', 'shapely_strict', 'shapely_wboundary', 'rasterio']='mpl'):
|
8057
|
+
"""
|
8058
|
+
Mask nodes inside a polygon and set values to nullvalue
|
8059
|
+
|
8060
|
+
:param myvect: target vector in global coordinates
|
8061
|
+
"""
|
8062
|
+
# The polygon here is in world coordinates
|
8063
|
+
# (coord will be converted back with translation, origin and dx/dy)
|
8064
|
+
# (mesh coord, 0-based)
|
8065
|
+
|
8066
|
+
# trouve les indices dans le polygone
|
8067
|
+
for myvect in myvects:
|
8068
|
+
myij = self.get_ij_inside_polygon(myvect, usemask=False, eps=eps, method=method)
|
8069
|
+
# masquage des mailles contenues
|
8070
|
+
self.array.mask[myij[:,0],myij[:,1]] = True
|
8071
|
+
|
8072
|
+
if set_nullvalue:
|
8073
|
+
# annulation des valeurs en dehors du polygone
|
8074
|
+
self.array.data[myij[:,0],myij[:,1]] = self.nullvalue
|
8075
|
+
|
8076
|
+
self.count()
|
8077
|
+
|
7861
8078
|
# *************************************************************************************************************************
|
7862
8079
|
# POSITION and VALUES associated to a vector/polygon/polyline
|
7863
8080
|
# These functions can not be stored in header_wolf, because wa can use the mask of the array to limit the search
|
7864
8081
|
# These functions are also present in WolfResults_2D, but they are not exactly the same due to the structure of the results
|
7865
8082
|
# *************************************************************************************************************************
|
7866
|
-
def get_xy_inside_polygon(self, myvect: vector | Polygon, usemask:bool=True
|
8083
|
+
def get_xy_inside_polygon(self, myvect: vector | Polygon, usemask:bool=True,
|
8084
|
+
method:Literal['mpl', 'shapely_strict', 'shapely_wboundary', 'rasterio']='shapely_strict'):
|
8085
|
+
|
8086
|
+
if method == 'mpl':
|
8087
|
+
return self.get_xy_inside_polygon_mpl(myvect, usemask)
|
8088
|
+
elif method == 'shapely_strict':
|
8089
|
+
return self.get_xy_inside_polygon_shapely(myvect, usemask, strictly=True)
|
8090
|
+
elif method == 'shapely_wboundary':
|
8091
|
+
return self.get_xy_inside_polygon_shapely(myvect, usemask, strictly=False)
|
8092
|
+
elif method == 'rasterio':
|
8093
|
+
return self.get_xy_inside_polygon_rasterio(myvect, usemask)
|
8094
|
+
|
8095
|
+
def get_xy_inside_polygon_mpl(self, myvect: vector | Polygon, usemask:bool=True):
|
7867
8096
|
"""
|
7868
8097
|
Return the coordinates inside a polygon
|
7869
8098
|
|
@@ -7892,7 +8121,10 @@ class WolfArray(Element_To_Draw, header_wolf):
|
|
7892
8121
|
|
7893
8122
|
return mypointsxy
|
7894
8123
|
|
7895
|
-
def get_xy_inside_polygon_shapely(self,
|
8124
|
+
def get_xy_inside_polygon_shapely(self,
|
8125
|
+
myvect: vector | Polygon,
|
8126
|
+
usemask:bool=True,
|
8127
|
+
strictly:bool=True):
|
7896
8128
|
"""
|
7897
8129
|
Return the coordinates inside a polygon
|
7898
8130
|
|
@@ -7901,15 +8133,29 @@ class WolfArray(Element_To_Draw, header_wolf):
|
|
7901
8133
|
"""
|
7902
8134
|
|
7903
8135
|
if isinstance(myvect, vector):
|
7904
|
-
# force la mise à jour des min/max
|
7905
8136
|
myvect.find_minmax()
|
7906
|
-
polygon = myvect.
|
8137
|
+
polygon = myvect.polygon
|
7907
8138
|
elif isinstance(myvect, Polygon):
|
7908
8139
|
polygon = myvect
|
7909
8140
|
|
8141
|
+
if not is_prepared(polygon):
|
8142
|
+
prepare(polygon) # Prepare the polygon for **faster** contains check -- VERY IMPORTANT
|
8143
|
+
to_destroy = True
|
8144
|
+
else:
|
8145
|
+
to_destroy = False
|
7910
8146
|
mypointsxy, mypointsij = self.get_xy_infootprint_vect(myvect)
|
7911
8147
|
|
7912
|
-
|
8148
|
+
if strictly:
|
8149
|
+
inside = contains_xy(polygon, mypointsxy[:,0], mypointsxy[:,1])
|
8150
|
+
else:
|
8151
|
+
points= np.array([Point(x,y) for x,y in mypointsxy])
|
8152
|
+
boundary = polygon.boundary
|
8153
|
+
inside = contains(polygon, points)
|
8154
|
+
on_border = list(map(lambda point: boundary.contains(point), points))
|
8155
|
+
inside = np.logical_or(inside, on_border)
|
8156
|
+
|
8157
|
+
if to_destroy:
|
8158
|
+
destroy_prepared(polygon) # Destroy the prepared polygon
|
7913
8159
|
|
7914
8160
|
mypointsxy = mypointsxy[np.where(inside)]
|
7915
8161
|
|
@@ -7920,6 +8166,33 @@ class WolfArray(Element_To_Draw, header_wolf):
|
|
7920
8166
|
|
7921
8167
|
return mypointsxy
|
7922
8168
|
|
8169
|
+
def get_xy_inside_polygon_rasterio(self, myvect: vector | Polygon, usemask:bool=True):
|
8170
|
+
"""
|
8171
|
+
Return the coordinates inside a polygon
|
8172
|
+
"""
|
8173
|
+
# force la mise à jour des min/max
|
8174
|
+
myvect.find_minmax()
|
8175
|
+
|
8176
|
+
mypointsxy, mypointsij = self.get_xy_infootprint_vect(myvect)
|
8177
|
+
|
8178
|
+
from rasterio.features import geometry_mask
|
8179
|
+
|
8180
|
+
poly = myvect.polygon
|
8181
|
+
mask = geometry_mask([poly], out_shape=(self.nbx, self.nby),
|
8182
|
+
transform=self._transform_gmrio(),
|
8183
|
+
invert=True,
|
8184
|
+
all_touched=False)
|
8185
|
+
# filter mypointsxy where mask is True
|
8186
|
+
# mypointsij is a vector of (i,j) indices
|
8187
|
+
mypointsxy = mypointsxy[mask[mypointsij[:, 0], mypointsij[:, 1]]]
|
8188
|
+
|
8189
|
+
if usemask:
|
8190
|
+
mypointsij = mypointsij[mask[mypointsij[:, 0], mypointsij[:, 1]]]
|
8191
|
+
mymask = np.logical_not(self.array.mask[mypointsij[:, 0], mypointsij[:, 1]])
|
8192
|
+
mypointsxy = mypointsxy[np.where(mymask)]
|
8193
|
+
|
8194
|
+
return mypointsxy
|
8195
|
+
|
7923
8196
|
def get_xy_under_polyline(self, myvect: vector, usemask:bool=True):
|
7924
8197
|
"""
|
7925
8198
|
Return the coordinates along a polyline
|
@@ -7933,7 +8206,27 @@ class WolfArray(Element_To_Draw, header_wolf):
|
|
7933
8206
|
|
7934
8207
|
return mypoints
|
7935
8208
|
|
7936
|
-
def get_ij_inside_polygon(self, myvect: vector
|
8209
|
+
def get_ij_inside_polygon(self, myvect: vector | Polygon,
|
8210
|
+
usemask:bool=True, eps:float = 0.,
|
8211
|
+
method:Literal['mpl', 'shapely_strict', 'shapely_wboundary', 'rasterio']='shapely_strict'):
|
8212
|
+
"""
|
8213
|
+
Return the indices inside a polygon
|
8214
|
+
|
8215
|
+
:param myvect: target vector
|
8216
|
+
:param usemask: limit potential nodes to unmaksed nodes
|
8217
|
+
:param eps: epsilon for the intersection
|
8218
|
+
"""
|
8219
|
+
|
8220
|
+
if method == 'mpl':
|
8221
|
+
return self.get_ij_inside_polygon_mpl(myvect, usemask, eps)
|
8222
|
+
elif method == 'shapely_strict':
|
8223
|
+
return self.get_ij_inside_polygon_shapely(myvect, usemask, eps, strictly=True)
|
8224
|
+
elif method == 'shapely_wboundary':
|
8225
|
+
return self.get_ij_inside_polygon_shapely(myvect, usemask, eps, strictly=False)
|
8226
|
+
elif method == 'rasterio':
|
8227
|
+
return self._get_ij_inside_polygon_rasterio(myvect, usemask, eps)
|
8228
|
+
|
8229
|
+
def get_ij_inside_polygon_mpl(self, myvect: vector | Polygon, usemask:bool=True, eps:float = 0.):
|
7937
8230
|
"""
|
7938
8231
|
Return the indices inside a polygon
|
7939
8232
|
|
@@ -7943,7 +8236,8 @@ class WolfArray(Element_To_Draw, header_wolf):
|
|
7943
8236
|
"""
|
7944
8237
|
|
7945
8238
|
# force la mise à jour des min/max
|
7946
|
-
myvect
|
8239
|
+
if isinstance(myvect, vector):
|
8240
|
+
myvect.find_minmax()
|
7947
8241
|
|
7948
8242
|
mypointsxy, mypointsij = self.get_xy_infootprint_vect(myvect, eps=eps)
|
7949
8243
|
|
@@ -7961,32 +8255,201 @@ class WolfArray(Element_To_Draw, header_wolf):
|
|
7961
8255
|
|
7962
8256
|
return mypointsij
|
7963
8257
|
|
7964
|
-
def
|
8258
|
+
def get_ij_inside_polygon_shapely(self, myvect: vector | Polygon,
|
8259
|
+
usemask:bool=True,
|
8260
|
+
eps:float = 0.,
|
8261
|
+
strictly:bool=True):
|
8262
|
+
"""
|
8263
|
+
Return the indices inside a polygon with the contains method of shapely
|
8264
|
+
|
8265
|
+
:param myvect: target vector
|
8266
|
+
:param usemask: limit potential nodes to unmaksed nodes
|
8267
|
+
:param eps: epsilon for the intersection
|
8268
|
+
:param strictly: if True, the points on the border are not considered inside
|
8269
|
+
"""
|
8270
|
+
|
8271
|
+
# force la mise à jour des min/max
|
8272
|
+
if isinstance(myvect, vector):
|
8273
|
+
myvect.find_minmax()
|
8274
|
+
poly = myvect.polygon
|
8275
|
+
elif isinstance(myvect, Polygon):
|
8276
|
+
poly = myvect
|
8277
|
+
|
8278
|
+
mypointsxy, mypointsij = self.get_xy_infootprint_vect(myvect, eps=eps)
|
8279
|
+
|
8280
|
+
if not is_prepared(poly):
|
8281
|
+
prepare(poly) # Prepare the polygon for **faster** contains check -- VERY IMPORTANT
|
8282
|
+
to_destroy = True
|
8283
|
+
else:
|
8284
|
+
to_destroy = False
|
8285
|
+
|
8286
|
+
if strictly:
|
8287
|
+
inside = contains_xy(poly, mypointsxy[:,0], mypointsxy[:,1])
|
8288
|
+
else:
|
8289
|
+
points= np.array([Point(x,y) for x,y in mypointsxy])
|
8290
|
+
boundary = poly.boundary
|
8291
|
+
inside = contains(poly, points)
|
8292
|
+
on_border = list(map(lambda point: boundary.contains(point), points))
|
8293
|
+
inside = np.logical_or(inside, on_border)
|
8294
|
+
|
8295
|
+
if to_destroy:
|
8296
|
+
destroy_prepared(poly) # Destroy the prepared polygon
|
8297
|
+
|
8298
|
+
mypointsij = mypointsij[np.where(inside)]
|
8299
|
+
|
8300
|
+
if usemask:
|
8301
|
+
mymask = np.logical_not(self.array.mask[mypointsij[:, 0], mypointsij[:, 1]])
|
8302
|
+
mypointsij = mypointsij[np.where(mymask)]
|
8303
|
+
|
8304
|
+
return mypointsij
|
8305
|
+
|
8306
|
+
def _get_ij_inside_polygon_rasterio(self, myvect: vector | Polygon, usemask:bool=True, eps:float = 0.):
|
8307
|
+
"""
|
8308
|
+
Return the indices inside a polygon with the geometry_mask method of rasterio.
|
8309
|
+
|
8310
|
+
:remark: get_ij_inside_polygon_shapely is more efficient
|
8311
|
+
|
8312
|
+
FIXME : geometry_mask seems strange -- it does not work as documented -- RatsrIO 1.3.x
|
8313
|
+
|
8314
|
+
:param myvect: target vector
|
8315
|
+
:param usemask: limit potential nodes to unmaksed nodes
|
8316
|
+
:param eps: epsilon for the intersection
|
8317
|
+
"""
|
8318
|
+
|
8319
|
+
# force la mise à jour des min/max
|
8320
|
+
if isinstance(myvect, vector):
|
8321
|
+
myvect.find_minmax()
|
8322
|
+
poly = myvect.polygon
|
8323
|
+
elif isinstance(myvect, Polygon):
|
8324
|
+
poly = myvect
|
8325
|
+
|
8326
|
+
mypointsxy, mypointsij = self.get_xy_infootprint_vect(myvect, eps=eps)
|
8327
|
+
|
8328
|
+
from rasterio.features import geometry_mask
|
8329
|
+
|
8330
|
+
mask = geometry_mask([poly], out_shape=(self.nbx, self.nby),
|
8331
|
+
transform=self._transform_gmrio(),
|
8332
|
+
invert=True,
|
8333
|
+
all_touched=False)
|
8334
|
+
# filter mypointsij where mask is True
|
8335
|
+
# mypointsij is a vector of (i,j) indices
|
8336
|
+
mypointsij = mypointsij[mask[mypointsij[:, 0], mypointsij[:, 1]]]
|
8337
|
+
|
8338
|
+
if usemask:
|
8339
|
+
mymask = np.logical_not(self.array.mask[mypointsij[:, 0], mypointsij[:, 1]])
|
8340
|
+
mypointsij = mypointsij[np.where(mymask)]
|
8341
|
+
|
8342
|
+
return mypointsij
|
8343
|
+
|
8344
|
+
def intersects_polygon(self, myvect: vector | Polygon,
|
8345
|
+
usemask:bool=True,
|
8346
|
+
method:Literal['mpl', 'shapely_strict', 'shapely_wboundary', 'rasterio']='shapely_strict',
|
8347
|
+
buffer:float= 0.) -> bool:
|
7965
8348
|
""" Return True if the array intersects the polygon
|
7966
8349
|
|
7967
8350
|
:param myvect: target vector
|
7968
8351
|
:param usemask: limit potential nodes to unmaksed nodes
|
7969
8352
|
"""
|
7970
8353
|
|
7971
|
-
|
8354
|
+
if buffer > 0.:
|
8355
|
+
bufvect = myvect.buffer(buffer, inplace= False)
|
8356
|
+
return self.get_xy_inside_polygon(bufvect, usemask, method).shape[0] > 0
|
8357
|
+
else:
|
8358
|
+
return self.get_xy_inside_polygon(myvect, usemask, method).shape[0] > 0
|
7972
8359
|
|
7973
|
-
def intersects_polygon_shapely(self, myvect: vector | Polygon,
|
8360
|
+
def intersects_polygon_shapely(self, myvect: vector | Polygon,
|
8361
|
+
eps:float = 0.,
|
8362
|
+
usemask:bool=True,
|
8363
|
+
buffer:float= 0.) -> bool:
|
7974
8364
|
""" Return True if the array intersects the polygon
|
7975
8365
|
|
7976
8366
|
:param myvect: target vector
|
7977
8367
|
:param usemask: limit potential nodes to unmaksed nodes
|
8368
|
+
:param eps: epsilon for the intersection
|
8369
|
+
:param buffer: buffer for the intersection - using buffer from Shapely [m]
|
8370
|
+
"""
|
8371
|
+
if buffer > 0.:
|
8372
|
+
poly = myvect.polygon.buffer(buffer)
|
8373
|
+
return self.get_xy_inside_polygon_shapely(poly, usemask).shape[0] > 0
|
8374
|
+
else:
|
8375
|
+
return self.get_xy_inside_polygon_shapely(myvect, usemask).shape[0] > 0
|
8376
|
+
|
8377
|
+
def interects_listofpolygons(self, myvects:zone | list[vector],
|
8378
|
+
usemask:bool=True,
|
8379
|
+
method:Literal['mpl', 'shapely_strict', 'shapely_wboundary', 'rasterio']='shapely_strict',
|
8380
|
+
buffer:float= 0.) -> list[bool]:
|
7978
8381
|
"""
|
7979
|
-
|
8382
|
+
Element-wise intersection with a list of polygons
|
8383
|
+
"""
|
8384
|
+
|
8385
|
+
if isinstance(myvects, zone):
|
8386
|
+
myvects = myvects.myvectors
|
8387
|
+
elif isinstance(myvects, list):
|
8388
|
+
pass
|
8389
|
+
else:
|
8390
|
+
logging.error("Bad type")
|
8391
|
+
return {}
|
8392
|
+
|
8393
|
+
return list(map(lambda x: self.intersects_polygon(x, usemask, method, buffer), myvects))
|
7980
8394
|
|
7981
|
-
def
|
8395
|
+
def intersects_zones(self, zones:Zones | list[zone],
|
8396
|
+
usemask:bool=True,
|
8397
|
+
method:Literal['mpl', 'shapely_strict', 'shapely_wboundary', 'rasterio']='shapely_strict',
|
8398
|
+
buffer:float = 0.) -> list[list[bool]]:
|
8399
|
+
"""
|
8400
|
+
Element-wise intersection with a list of zones
|
8401
|
+
"""
|
8402
|
+
|
8403
|
+
if isinstance(zones, Zones):
|
8404
|
+
zones = zones.myzones
|
8405
|
+
elif isinstance(zones, list):
|
8406
|
+
pass
|
8407
|
+
else:
|
8408
|
+
logging.error("Bad type")
|
8409
|
+
return {}
|
8410
|
+
|
8411
|
+
return list(map(lambda x: self.interects_listofpolygons(x, usemask, method, buffer), zones))
|
8412
|
+
|
8413
|
+
def get_total_area_if_intersects(self, myvects:Zones | list[vector],
|
8414
|
+
usemask:bool=True,
|
8415
|
+
method:Literal['mpl', 'shapely_strict', 'shapely_wboundary', 'rasterio']='shapely_strict',
|
8416
|
+
buffer:float= 0.,
|
8417
|
+
coefficient:float = 1.) -> float | list[float]:
|
8418
|
+
"""
|
8419
|
+
Return the area of the intersection with a list of polygons
|
8420
|
+
"""
|
8421
|
+
|
8422
|
+
if isinstance(myvects, Zones):
|
8423
|
+
ret = []
|
8424
|
+
for curzone in myvects.myzones:
|
8425
|
+
myvects = curzone.myvectors
|
8426
|
+
intersects = self.interects_listofpolygons(myvects, usemask, method, buffer)
|
8427
|
+
ret.append(sum([x.area for x in myvects if intersects[myvects.index(x)]]) * coefficient)
|
8428
|
+
return ret
|
8429
|
+
elif isinstance(myvects, list):
|
8430
|
+
intersects = self.interects_listofpolygons(myvects, usemask, method, buffer)
|
8431
|
+
return sum([x.area for x in myvects if intersects[myvects.index(x)]]) * coefficient
|
8432
|
+
|
8433
|
+
def get_ij_under_polyline(self, myvect: vector, usemask:bool=True, step_factor:float=1.):
|
7982
8434
|
"""
|
7983
8435
|
Return the indices along a polyline
|
7984
8436
|
|
7985
8437
|
:param myvect: target vector
|
7986
8438
|
:param usedmask: limit potential nodes to unmaksed nodes
|
8439
|
+
:param step_factor: step factor for the discretization of myvect (<1 for thinner, >1 for coarser)
|
7987
8440
|
"""
|
7988
8441
|
|
7989
|
-
|
8442
|
+
if step_factor>=1.0:
|
8443
|
+
logging.warning("Step_factor greater than 1.0 may lead to lots of missing (i,j) even if faster")
|
8444
|
+
else:
|
8445
|
+
def mod_fix(x, y, tol=1e-10):
|
8446
|
+
result = np.mod(x, y)
|
8447
|
+
return 0 if np.isclose(result, 0, atol=tol) else (y if np.isclose(result, y, atol=tol) else result)
|
8448
|
+
assert mod_fix(1.0,step_factor)==0, "1.0 should be a multiple of step_factor"
|
8449
|
+
|
8450
|
+
# assert step_factor<=1., "Step factor must be <= 1"
|
8451
|
+
|
8452
|
+
ds = step_factor*min(self.dx, self.dy)
|
7990
8453
|
pts = myvect._refine2D(ds)
|
7991
8454
|
|
7992
8455
|
allij = np.asarray([self.get_ij_from_xy(curpt.x, curpt.y) for curpt in pts])
|
@@ -8002,7 +8465,7 @@ class WolfArray(Element_To_Draw, header_wolf):
|
|
8002
8465
|
|
8003
8466
|
return allij
|
8004
8467
|
|
8005
|
-
def get_values_insidepoly(self, myvect: vector, usemask:bool=True, getxy:bool=False):
|
8468
|
+
def get_values_insidepoly(self, myvect: vector, usemask:bool=True, getxy:bool=False) -> tuple[np.ndarray, np.ndarray | None]:
|
8006
8469
|
"""
|
8007
8470
|
Récupération des valeurs contenues dans un polygone
|
8008
8471
|
|
@@ -8017,6 +8480,44 @@ class WolfArray(Element_To_Draw, header_wolf):
|
|
8017
8480
|
else:
|
8018
8481
|
return myvalues, None
|
8019
8482
|
|
8483
|
+
def get_values_inside_listofpolygons(self, myvects:zone | list[vector], usemask:bool=True, getxy:bool=False) -> dict:
|
8484
|
+
"""
|
8485
|
+
Récupération des valeurs contenues dans une instance Zones ou une liste de vecteurs
|
8486
|
+
"""
|
8487
|
+
|
8488
|
+
ret = {}
|
8489
|
+
if isinstance(myvects, zone):
|
8490
|
+
out = ret[myvects.myname] = {}
|
8491
|
+
myvects = myvects.myvectors
|
8492
|
+
elif isinstance(myvects, list):
|
8493
|
+
out = ret
|
8494
|
+
else:
|
8495
|
+
logging.error("Bad type")
|
8496
|
+
return {}
|
8497
|
+
|
8498
|
+
for vec in myvects:
|
8499
|
+
out[vec.myname] = self.get_values_insidepoly(vec, usemask, getxy)
|
8500
|
+
|
8501
|
+
|
8502
|
+
def get_values_inside_zones(self, zones:Zones | list[zone], usemask:bool=True, getxy:bool=False) -> dict:
|
8503
|
+
"""
|
8504
|
+
Récupération des valeurs contenues dans une instance Zones ou une liste de "zone"
|
8505
|
+
"""
|
8506
|
+
|
8507
|
+
ret = {}
|
8508
|
+
if isinstance(zones, Zones):
|
8509
|
+
out = ret[myzones.myname] = {}
|
8510
|
+
myzones = zones.myzones
|
8511
|
+
elif isinstance(zones, list):
|
8512
|
+
myzones = zones
|
8513
|
+
out = ret
|
8514
|
+
else:
|
8515
|
+
logging.error("Bad type")
|
8516
|
+
return {}
|
8517
|
+
|
8518
|
+
for zone in myzones:
|
8519
|
+
out[zone.myname] = self.get_values_inside_listofpolygons(zone, usemask, getxy)
|
8520
|
+
|
8020
8521
|
def get_values_underpoly(self, myvect: vector, usemask:bool=True, getxy:bool=False):
|
8021
8522
|
"""
|
8022
8523
|
Récupération des valeurs contenues sous une polyligne
|
@@ -8059,6 +8560,63 @@ class WolfArray(Element_To_Draw, header_wolf):
|
|
8059
8560
|
|
8060
8561
|
return self.get_values_underpoly(myvect, usemask, getxy)
|
8061
8562
|
|
8563
|
+
def count_insidepoly(self, myvect: vector,
|
8564
|
+
usemask:bool=True,
|
8565
|
+
method:Literal['mpl', 'shapely_strict', 'shapely_wboundary', 'rasterio']='shapely_strict',
|
8566
|
+
coefficient:float = 1.):
|
8567
|
+
"""
|
8568
|
+
Compte le nombre de valeurs contenues dans un polygone
|
8569
|
+
|
8570
|
+
:param myvect: target vector
|
8571
|
+
:param usemask: (optional) restreint les éléments aux éléments non masqués de la matrice
|
8572
|
+
:param method: (optional) method to use
|
8573
|
+
:param coefficient: (optional) coefficient to apply to the count (default 1.)
|
8574
|
+
"""
|
8575
|
+
|
8576
|
+
ij = self.get_ij_inside_polygon(myvect, usemask, method=method)
|
8577
|
+
return ij.shape[0] * coefficient
|
8578
|
+
|
8579
|
+
def count_inside_listofpolygons(self, myvects:zone | list[vector],
|
8580
|
+
usemask:bool=True,
|
8581
|
+
method:Literal['mpl', 'shapely_strict', 'shapely_wboundary', 'rasterio']='shapely_strict',
|
8582
|
+
coefficient:float = 1.):
|
8583
|
+
"""
|
8584
|
+
Compte le nombre de valeurs contenues dans une instance Zones ou une liste de vecteurs
|
8585
|
+
|
8586
|
+
:param myvects: target vectors or zone
|
8587
|
+
:param usemask: (optional) restreint les éléments aux éléments non masqués de la matrice
|
8588
|
+
:param method: (optional) method to use
|
8589
|
+
:param coefficient: (optional) coefficient to apply to the count (default 1.)
|
8590
|
+
"""
|
8591
|
+
|
8592
|
+
if isinstance(myvects, zone):
|
8593
|
+
myvects = myvects.myvectors
|
8594
|
+
else:
|
8595
|
+
logging.error("Bad type")
|
8596
|
+
return {}
|
8597
|
+
|
8598
|
+
return list(map(lambda vec: self.count_insidepoly(vec, usemask, method=method, coefficient=coefficient), myvects))
|
8599
|
+
|
8600
|
+
def count_inside_zones(self, zones:Zones | list[zone],
|
8601
|
+
usemask:bool=True,
|
8602
|
+
method:Literal['mpl', 'shapely_strict', 'shapely_wboundary', 'rasterio']='shapely_strict',
|
8603
|
+
coefficient:float = 1.):
|
8604
|
+
"""
|
8605
|
+
Compte le nombre de valeurs contenues dans une instance Zones ou une liste de "zone"
|
8606
|
+
|
8607
|
+
:param zones: target zones or list of zones
|
8608
|
+
:param usemask: (optional) restreint les éléments aux éléments non masqués de la matrice
|
8609
|
+
:param method: (optional) method to use
|
8610
|
+
:param coefficient: (optional) coefficient to apply to the count (default 1.)
|
8611
|
+
"""
|
8612
|
+
|
8613
|
+
if isinstance(zones, Zones):
|
8614
|
+
zones = zones.myzones
|
8615
|
+
else:
|
8616
|
+
logging.error("Bad type")
|
8617
|
+
|
8618
|
+
return list(map(lambda loczone: self.count_inside_listofpolygons(loczone, usemask, method=method, coefficient=coefficient), zones))
|
8619
|
+
|
8062
8620
|
# *************************************************************************************************************************
|
8063
8621
|
# END POSITION and VALUES associated to a vector/polygon/polyline
|
8064
8622
|
# *************************************************************************************************************************
|
@@ -9085,7 +9643,7 @@ class WolfArray(Element_To_Draw, header_wolf):
|
|
9085
9643
|
else:
|
9086
9644
|
self.mypal.isopop(self.get_working_array(), self.nbnotnull)
|
9087
9645
|
|
9088
|
-
if VERSION_RGB==1 :
|
9646
|
+
if VERSION_RGB==1 or which == 1:
|
9089
9647
|
if self.nbx * self.nby > 1_000_000 : logging.info(_('Computing colors'))
|
9090
9648
|
if self.wolftype not in [WOLF_ARRAY_FULL_SINGLE, WOLF_ARRAY_FULL_INTEGER8, WOLF_ARRAY_FULL_UINTEGER8]:
|
9091
9649
|
# FIXME: Currently, only some types are supported in Cython/OpenGL library
|
@@ -9110,7 +9668,7 @@ class WolfArray(Element_To_Draw, header_wolf):
|
|
9110
9668
|
self._tmp_float32 = None
|
9111
9669
|
|
9112
9670
|
|
9113
|
-
if VERSION_RGB==1 :
|
9671
|
+
if VERSION_RGB==1 or which == 1:
|
9114
9672
|
if self.shading:
|
9115
9673
|
pond = (self.shaded.array-.5)*2.
|
9116
9674
|
pmin = (1. - self.shaded.alpha) * self.rgb
|
@@ -9119,7 +9677,7 @@ class WolfArray(Element_To_Draw, header_wolf):
|
|
9119
9677
|
self.rgb[pond<0,i] = self.rgb[pond<0,i] * (1.+pond[pond<0]) - pmin[pond<0,i] * pond[pond<0]
|
9120
9678
|
self.rgb[pond>0,i] = self.rgb[pond>0,i] * (1.-pond[pond>0]) + pmax[pond>0,i] * pond[pond>0]
|
9121
9679
|
|
9122
|
-
if VERSION_RGB==1 : self.rgb[self.array.mask] = [1., 1., 1., 0.]
|
9680
|
+
if VERSION_RGB==1 or which == 1: self.rgb[self.array.mask] = [1., 1., 1., 0.]
|
9123
9681
|
|
9124
9682
|
if self.myops is not None:
|
9125
9683
|
# update the wx
|
@@ -9266,22 +9824,94 @@ class WolfArray(Element_To_Draw, header_wolf):
|
|
9266
9824
|
self.mygrid = {}
|
9267
9825
|
self.gridmaxscales = -1
|
9268
9826
|
|
9269
|
-
def plot_matplotlib(self, figax:tuple=None,
|
9827
|
+
def plot_matplotlib(self, figax:tuple=None,
|
9828
|
+
getdata_im:bool=False,
|
9829
|
+
update_palette:bool=True,
|
9830
|
+
vmin:float=None, vmax:float=None,
|
9831
|
+
figsize:tuple=None,
|
9832
|
+
Walonmap:bool=False,
|
9833
|
+
cat:str='IMAGERIE/ORTHO_2022_ETE'):
|
9270
9834
|
"""
|
9271
9835
|
Plot the array - Matplotlib version
|
9272
9836
|
|
9273
9837
|
Using imshow and RGB array
|
9838
|
+
|
9839
|
+
Plot the array using Matplotlib.
|
9840
|
+
This method visualizes the array data using Matplotlib's `imshow` function.
|
9841
|
+
It supports optional overlays, custom palettes, and value range adjustments.
|
9842
|
+
|
9843
|
+
Notes:
|
9844
|
+
------
|
9845
|
+
- The method applies a mask to the data using the `nullvalue` attribute before plotting.
|
9846
|
+
- If `Walonmap` is True, the method fetches and overlays a map image using the WalOnMap service.
|
9847
|
+
- The aspect ratio of the plot is set to 'equal'.
|
9848
|
+
|
9849
|
+
:param figax: A tuple containing a Matplotlib figure and axis (fig, ax). If None, a new figure and axis are created.
|
9850
|
+
:type figax: tuple, optional (Default value = None)
|
9851
|
+
:param getdata_im: If True, returns the image object along with the figure and axis. Default is False, then it only returns (fig, ax).
|
9852
|
+
:type getdata_im: bool, optional (Default value = False)
|
9853
|
+
:param update_palette: If True, updates the color palette before plotting. Default is True.
|
9854
|
+
:type update_palette: bool, optional (Default value = True)
|
9855
|
+
:param vmin: Minimum value for color scaling. If None, the minimum value is determined automatically. Default is None.
|
9856
|
+
:type vmin: float, optional (Default value = None)
|
9857
|
+
:param vmax: Maximum value for color scaling. If None, the maximum value is determined automatically. Default is None.
|
9858
|
+
:type vmax: float, optional (Default value = None)
|
9859
|
+
:param figsize: Size of the figure in inches (width, height). Only used if `figax` is None. Default is None.
|
9860
|
+
:type figsize: tuple, optional (Default value = None)
|
9861
|
+
:param Walonmap: If True, overlays a map image using the WalOnMap service. Default is False.
|
9862
|
+
:type Walonmap: bool, optional (Default value = False)
|
9863
|
+
:param cat: The category of the map to fetch from the WalOnMap service. Default is 'IMAGERIE/ORTHO_2022_ETE'.
|
9864
|
+
Available orthos:
|
9865
|
+
- `'IMAGERIE/ORTHO_1971'`
|
9866
|
+
- `'IMAGERIE/ORTHO_1994_2000'`
|
9867
|
+
- `'IMAGERIE/ORTHO_2006_2007'`
|
9868
|
+
- `'IMAGERIE/ORTHO_2009_2010'`
|
9869
|
+
- `'IMAGERIE/ORTHO_2012_2013'`
|
9870
|
+
- `'IMAGERIE/ORTHO_2015'`
|
9871
|
+
- `'IMAGERIE/ORTHO_2016'`
|
9872
|
+
- `'IMAGERIE/ORTHO_2017'`
|
9873
|
+
- `'IMAGERIE/ORTHO_2018'`
|
9874
|
+
- `'IMAGERIE/ORTHO_2019'`
|
9875
|
+
- `'IMAGERIE/ORTHO_2020'`
|
9876
|
+
- `'IMAGERIE/ORTHO_2021'`
|
9877
|
+
- `'IMAGERIE/ORTHO_2022_PRINTEMPS'`
|
9878
|
+
- `'IMAGERIE/ORTHO_2022_ETE'`
|
9879
|
+
- `'IMAGERIE/ORTHO_2023_ETE'`
|
9880
|
+
- `'IMAGERIE/ORTHO_LAST'`
|
9881
|
+
:type cat: str, optional (Default value = `'IMAGERIE/ORTHO_2022_ETE'`)
|
9882
|
+
:return: If `getdata_im` is False, returns (fig, ax), where `fig` is the Matplotlib figure and `ax` is the axis. If `getdata_im` is True, returns (fig, ax, im), where `im` is the image object created by `imshow`.
|
9883
|
+
:rtype: tuple
|
9274
9884
|
"""
|
9275
9885
|
|
9276
9886
|
self.mask_data(self.nullvalue)
|
9277
|
-
|
9887
|
+
if update_palette:
|
9888
|
+
self.updatepalette(0)
|
9889
|
+
|
9278
9890
|
if figax is None:
|
9279
|
-
fig, ax = plt.subplots()
|
9891
|
+
fig, ax = plt.subplots(figsize=figsize)
|
9280
9892
|
else:
|
9281
9893
|
fig, ax = figax
|
9282
9894
|
|
9283
|
-
|
9284
|
-
|
9895
|
+
if Walonmap:
|
9896
|
+
from .PyWMS import getWalonmap
|
9897
|
+
from PIL import Image
|
9898
|
+
|
9899
|
+
try:
|
9900
|
+
IO_image = getWalonmap(cat, self.origx, self.origy, self.origx + self.nbx * self.dx, self.origy +self.nby * self.dy, w=1000, tofile=False) # w=self.nbx, h=self.nby
|
9901
|
+
|
9902
|
+
image = Image.open(IO_image)
|
9903
|
+
|
9904
|
+
ax.imshow(image.transpose(Image.Transpose.FLIP_TOP_BOTTOM), extent=(self.origx, self.origx + self.dx * self.nbx, self.origy, self.origy + self.dy * self.nby), alpha=1, origin='lower')
|
9905
|
+
except Exception as e:
|
9906
|
+
logging.error(_('Error while fetching the map image from WalOnMap'))
|
9907
|
+
logging.error(e)
|
9908
|
+
|
9909
|
+
if (vmin is None) and (vmax is None):
|
9910
|
+
im = ax.imshow(self.array.transpose(), origin='lower', cmap=self.mypal,
|
9911
|
+
extent=(self.origx, self.origx + self.dx * self.nbx, self.origy, self.origy + self.dy * self.nby))
|
9912
|
+
else:
|
9913
|
+
im = ax.imshow(self.array.transpose(), origin='lower', cmap=self.mypal,
|
9914
|
+
extent=(self.origx, self.origx + self.dx * self.nbx, self.origy, self.origy + self.dy * self.nby), vmin=vmin, vmax=vmax)
|
9285
9915
|
ax.set_aspect('equal')
|
9286
9916
|
|
9287
9917
|
if getdata_im:
|
@@ -9613,7 +10243,8 @@ class WolfArray(Element_To_Draw, header_wolf):
|
|
9613
10243
|
|
9614
10244
|
return indicesX,indicesY,contourgen,interior
|
9615
10245
|
|
9616
|
-
def imshow(self, figax:tuple[Figure, Axis] = None,
|
10246
|
+
def imshow(self, figax:tuple[Figure, Axis] = None,
|
10247
|
+
cmap:Colormap = None, step_ticks=100.) -> tuple[Figure, Axis]:
|
9617
10248
|
"""
|
9618
10249
|
Create Matplotlib image from WolfArray
|
9619
10250
|
"""
|
@@ -9628,11 +10259,11 @@ class WolfArray(Element_To_Draw, header_wolf):
|
|
9628
10259
|
|
9629
10260
|
if cmap is None:
|
9630
10261
|
# update local colors if not already done
|
9631
|
-
if self
|
9632
|
-
self
|
10262
|
+
if self.rgb is None:
|
10263
|
+
self.updatepalette(1)
|
9633
10264
|
|
9634
10265
|
# Pointing RGB
|
9635
|
-
colors = self
|
10266
|
+
colors = self.rgb
|
9636
10267
|
colors[self.array.mask,3] = 0.
|
9637
10268
|
# Plot
|
9638
10269
|
colors = colors.swapaxes(0,1)
|
@@ -9859,13 +10490,14 @@ class WolfArray(Element_To_Draw, header_wolf):
|
|
9859
10490
|
|
9860
10491
|
return zones
|
9861
10492
|
|
9862
|
-
def inpaint(self, mask_array:"WolfArray" = None, test_array:"WolfArray" = None,
|
10493
|
+
def inpaint(self, mask_array:"WolfArray" = None, test_array:"WolfArray" = None,
|
10494
|
+
ignore_last:int = 1, multiprocess:bool = True):
|
9863
10495
|
""" InPaintaing holes in the array
|
9864
10496
|
|
9865
10497
|
:param mask_array: where computation is done
|
9866
10498
|
:param test_array: used in test -- interpolation is accepted if new value is over test_array
|
9867
10499
|
:param ignore_last: number of last patches to ignore
|
9868
|
-
|
10500
|
+
:param multiprocess: use multiprocess for inpainting
|
9869
10501
|
"""
|
9870
10502
|
|
9871
10503
|
from .eikonal import inpaint_array
|
@@ -9888,7 +10520,7 @@ class WolfArray(Element_To_Draw, header_wolf):
|
|
9888
10520
|
dy = self.dy,
|
9889
10521
|
NoDataValue = self.nullvalue,
|
9890
10522
|
inplace=True,
|
9891
|
-
multiprocess=
|
10523
|
+
multiprocess= multiprocess)
|
9892
10524
|
|
9893
10525
|
wd = WolfArray(mold=self)
|
9894
10526
|
wd.array[:,:] = wd_np[:,:]
|
@@ -9900,7 +10532,9 @@ class WolfArray(Element_To_Draw, header_wolf):
|
|
9900
10532
|
self.reset_plot()
|
9901
10533
|
return time, wl, wd
|
9902
10534
|
|
9903
|
-
def _inpaint_waterlevel_dem_dtm(self, dem:"WolfArray", dtm:"WolfArray",
|
10535
|
+
def _inpaint_waterlevel_dem_dtm(self, dem:"WolfArray", dtm:"WolfArray",
|
10536
|
+
ignore_last:int = 1, use_fortran:bool = False,
|
10537
|
+
multiprocess:bool = True):
|
9904
10538
|
""" InPaintaing waterlevel holes in the array.
|
9905
10539
|
|
9906
10540
|
We use DEM and DTM to mask and constraint the inpainting process.
|
@@ -9909,6 +10543,7 @@ class WolfArray(Element_To_Draw, header_wolf):
|
|
9909
10543
|
:param dtm: Digital Terrain Model
|
9910
10544
|
:param ignore_last: number of last patches to ignore
|
9911
10545
|
:param use_fortran: use Fortran inpainting code
|
10546
|
+
:param multiprocess: use multiprocess for inpainting
|
9912
10547
|
"""
|
9913
10548
|
|
9914
10549
|
if use_fortran:
|
@@ -9975,7 +10610,7 @@ class WolfArray(Element_To_Draw, header_wolf):
|
|
9975
10610
|
dy = self.dy,
|
9976
10611
|
NoDataValue = self.nullvalue,
|
9977
10612
|
inplace=True,
|
9978
|
-
multiprocess=
|
10613
|
+
multiprocess= multiprocess)
|
9979
10614
|
|
9980
10615
|
wd = WolfArray(mold=self)
|
9981
10616
|
wd.array[:,:] = wd_np[:,:]
|
@@ -10049,6 +10684,68 @@ class WolfArray(Element_To_Draw, header_wolf):
|
|
10049
10684
|
|
10050
10685
|
return newarray
|
10051
10686
|
|
10687
|
+
def create_binary_mask(self, threshold:float = 0.) -> "WolfArray":
|
10688
|
+
""" Create a binary mask from the array """
|
10689
|
+
|
10690
|
+
newarray = WolfArray(mold=self)
|
10691
|
+
newarray.array.data[:,:] = self.array.data > threshold
|
10692
|
+
newarray.array.mask[:,:] = ~newarray.array.data.astype(bool)
|
10693
|
+
newarray.set_nullvalue_in_mask()
|
10694
|
+
|
10695
|
+
return newarray
|
10696
|
+
|
10697
|
+
def fill_holes_with_value(self, value:float, mask:"WolfArray" = None, ignore_last:int = 1):
|
10698
|
+
""" Fill holes in the array with a value """
|
10699
|
+
|
10700
|
+
if mask is None:
|
10701
|
+
mask = self.array.mask
|
10702
|
+
|
10703
|
+
labels, numfeatures = label(mask)
|
10704
|
+
|
10705
|
+
# count cells in holes
|
10706
|
+
nb_cells = np.bincount(labels.ravel())
|
10707
|
+
|
10708
|
+
# sort by number of cells
|
10709
|
+
idx = np.argsort(nb_cells)
|
10710
|
+
|
10711
|
+
for i in range(ignore_last):
|
10712
|
+
labels[labels == idx[-1-i]] = 0
|
10713
|
+
|
10714
|
+
self.array.data[labels > 0] = value
|
10715
|
+
self.array.mask[labels > 0] = False # unmask filled data
|
10716
|
+
|
10717
|
+
def fill_holes_with_value_if_intersects(self, value:float,
|
10718
|
+
vects:list[vector] | Zones,
|
10719
|
+
method:Literal['matplotlib','shapely_strict', 'shapely'] = 'shapely_strict',
|
10720
|
+
buffer:float = 0.):
|
10721
|
+
""" Fill holes in the array with a value if it intersects with the mask """
|
10722
|
+
|
10723
|
+
if isinstance(vects, Zones):
|
10724
|
+
vects = vects.concatenate_all_vectors()
|
10725
|
+
|
10726
|
+
for vec in vects:
|
10727
|
+
self._fill_holes_with_value_if_intersects(value, vec, method, buffer)
|
10728
|
+
# list(map(self._fill_holes_with_value_if_intersects, [value]*len(vects), vects, [method]*len(vects), [buffer]*len(vects))) # parallel version
|
10729
|
+
|
10730
|
+
def _fill_holes_with_value_if_intersects(self, value:float,
|
10731
|
+
vect:vector,
|
10732
|
+
method:Literal['matplotlib','shapely_strict', 'shapely'] = 'shapely_strict',
|
10733
|
+
buffer:float = 0.) -> "WolfArray":
|
10734
|
+
|
10735
|
+
if buffer > 0.:
|
10736
|
+
# As we use a buffer, we check the intersction with the buffer...
|
10737
|
+
if self.intersects_polygon(vect, method=method, buffer=buffer):
|
10738
|
+
# ...but we fill the original polygon, not the buffered one
|
10739
|
+
ij = self.get_ij_inside_polygon(vect, method=method, usemask=False)
|
10740
|
+
self.array.data[ij[:,0], ij[:,1]] = value
|
10741
|
+
self.array.mask[ij[:,0], ij[:,1]] = False
|
10742
|
+
else:
|
10743
|
+
ij = self.get_ij_inside_polygon(vect, method=method, buffer=buffer, usemask=False)
|
10744
|
+
|
10745
|
+
if ij.size[0] > 0:
|
10746
|
+
self.array.data[ij[:,0], ij[:,1]] = value
|
10747
|
+
self.array.mask[ij[:,0], ij[:,1]] = False
|
10748
|
+
|
10052
10749
|
def _create_building_holes_dem_dtm(self, dem:"WolfArray", dtm:"WolfArray", ignore_last:int = 1) -> "WolfArray":
|
10053
10750
|
""" Select holes in the array and create a new aray """
|
10054
10751
|
|
@@ -10059,6 +10756,26 @@ class WolfArray(Element_To_Draw, header_wolf):
|
|
10059
10756
|
|
10060
10757
|
return buildings
|
10061
10758
|
|
10759
|
+
@classmethod
|
10760
|
+
def merge(cls, others:list["WolfArrayMB"], abs:bool=True, copy:bool = True) -> "WolfArray":
|
10761
|
+
"""
|
10762
|
+
Merge several WolfArray into a single WolfArray
|
10763
|
+
|
10764
|
+
:param others: list of WolfArray to merge
|
10765
|
+
:param abs: if True -> Global World Coordinates
|
10766
|
+
:param copy: if True -> copy data (**Not necessary** if data header CAN BE modified. It can be save memory)
|
10767
|
+
"""
|
10768
|
+
|
10769
|
+
newMBArray = WolfArrayMB()
|
10770
|
+
|
10771
|
+
for curarray in others:
|
10772
|
+
newMBArray.add_block(curarray, force_idx=True, copyarray=copy)
|
10773
|
+
|
10774
|
+
newMBArray.set_header_from_added_blocks()
|
10775
|
+
|
10776
|
+
return newMBArray.as_WolfArray(abs=abs)
|
10777
|
+
|
10778
|
+
|
10062
10779
|
class WolfArrayMB(WolfArray):
|
10063
10780
|
"""
|
10064
10781
|
Matrice multiblocks
|
@@ -10285,6 +11002,7 @@ class WolfArrayMB(WolfArray):
|
|
10285
11002
|
arr.isblock = True
|
10286
11003
|
arr.blockindex = len(self.myblocks) - 1
|
10287
11004
|
|
11005
|
+
|
10288
11006
|
def share_palette(self):
|
10289
11007
|
"""Partage de la palette de couleurs entre matrices liées"""
|
10290
11008
|
for cur in self.linkedarrays:
|
@@ -10987,11 +11705,26 @@ class WolfArrayMB(WolfArray):
|
|
10987
11705
|
self.translx = 0.
|
10988
11706
|
self.transly = 0.
|
10989
11707
|
|
11708
|
+
self.head_blocks = {}
|
11709
|
+
for curblock in self.myblocks.values():
|
11710
|
+
|
11711
|
+
new_trx = self.origx + self.translx
|
11712
|
+
new_try = self.origy + self.transly
|
11713
|
+
|
11714
|
+
ox = curblock.origx + curblock.translx
|
11715
|
+
oy = curblock.origy + curblock.transly
|
11716
|
+
|
11717
|
+
curblock.origin = (ox - new_trx, oy - new_try)
|
11718
|
+
curblock.translation = (new_trx, new_try)
|
11719
|
+
|
11720
|
+
self.head_blocks[curblock.idx] = curblock.get_header()
|
11721
|
+
|
10990
11722
|
def as_WolfArray(self, abs:bool=True, forced_header:header_wolf = None) -> WolfArray:
|
10991
11723
|
"""
|
10992
|
-
Convert to WolfArray
|
11724
|
+
Convert to WolfArray -- Rebin blocks if necessary
|
10993
11725
|
|
10994
|
-
|
11726
|
+
:param abs: if True, then the translation is taken into account
|
11727
|
+
:param forced_header: if not None, then the header is forced to this value
|
10995
11728
|
"""
|
10996
11729
|
|
10997
11730
|
newArray = WolfArray()
|
@@ -11101,6 +11834,67 @@ class WolfArrayMB(WolfArray):
|
|
11101
11834
|
|
11102
11835
|
return newArray
|
11103
11836
|
|
11837
|
+
def plot_matplotlib(self, figax:tuple=None, getdata_im:bool=False, update_palette:bool=True, vmin:float=None, vmax:float=None, figsize:tuple=None, Walonmap:bool=False, cat:str='IMAGERIE/ORTHO_2022_ETE'):
|
11838
|
+
"""
|
11839
|
+
Plot the multi-block (MB) array - Matplotlib version
|
11840
|
+
|
11841
|
+
Converts the MB array to single/mono-block and,
|
11842
|
+
Uses the `plot_matplotlib` method of the WolfArray class.
|
11843
|
+
|
11844
|
+
Using imshow and RGB array
|
11845
|
+
|
11846
|
+
Plot the array using Matplotlib.
|
11847
|
+
This method visualizes the array data using Matplotlib's `imshow` function.
|
11848
|
+
It supports optional overlays, custom palettes, and value range adjustments.
|
11849
|
+
|
11850
|
+
Notes:
|
11851
|
+
------
|
11852
|
+
- The method applies a mask to the data using the `nullvalue` attribute before plotting.
|
11853
|
+
- If `Walonmap` is True, the method fetches and overlays a map image using the WalOnMap service.
|
11854
|
+
- The aspect ratio of the plot is set to 'equal'.
|
11855
|
+
|
11856
|
+
:param figax: A tuple containing a Matplotlib figure and axis (fig, ax). If None, a new figure and axis are created.
|
11857
|
+
:type figax: tuple, optional (Default value = None)
|
11858
|
+
:param getdata_im: If True, returns the image object along with the figure and axis. Default is False, then it only returns (fig, ax).
|
11859
|
+
:type getdata_im: bool, optional (Default value = False)
|
11860
|
+
:param update_palette: If True, updates the color palette before plotting. Default is True.
|
11861
|
+
:type update_palette: bool, optional (Default value = True)
|
11862
|
+
:param vmin: Minimum value for color scaling. If None, the minimum value is determined automatically. Default is None.
|
11863
|
+
:type vmin: float, optional (Default value = None)
|
11864
|
+
:param vmax: Maximum value for color scaling. If None, the maximum value is determined automatically. Default is None.
|
11865
|
+
:type vmax: float, optional (Default value = None)
|
11866
|
+
:param figsize: Size of the figure in inches (width, height). Only used if `figax` is None. Default is None.
|
11867
|
+
:type figsize: tuple, optional (Default value = None)
|
11868
|
+
:param Walonmap: If True, overlays a map image using the WalOnMap service. Default is False.
|
11869
|
+
:type Walonmap: bool, optional (Default value = False)
|
11870
|
+
:param cat: The category of the map to fetch from the WalOnMap service. Default is 'IMAGERIE/ORTHO_2022_ETE'.
|
11871
|
+
Available orthos:
|
11872
|
+
- `'IMAGERIE/ORTHO_1971'`
|
11873
|
+
- `'IMAGERIE/ORTHO_1994_2000'`
|
11874
|
+
- `'IMAGERIE/ORTHO_2006_2007'`
|
11875
|
+
- `'IMAGERIE/ORTHO_2009_2010'`
|
11876
|
+
- `'IMAGERIE/ORTHO_2012_2013'`
|
11877
|
+
- `'IMAGERIE/ORTHO_2015'`
|
11878
|
+
- `'IMAGERIE/ORTHO_2016'`
|
11879
|
+
- `'IMAGERIE/ORTHO_2017'`
|
11880
|
+
- `'IMAGERIE/ORTHO_2018'`
|
11881
|
+
- `'IMAGERIE/ORTHO_2019'`
|
11882
|
+
- `'IMAGERIE/ORTHO_2020'`
|
11883
|
+
- `'IMAGERIE/ORTHO_2021'`
|
11884
|
+
- `'IMAGERIE/ORTHO_2022_PRINTEMPS'`
|
11885
|
+
- `'IMAGERIE/ORTHO_2022_ETE'`
|
11886
|
+
- `'IMAGERIE/ORTHO_2023_ETE'`
|
11887
|
+
- `'IMAGERIE/ORTHO_LAST'`
|
11888
|
+
:type cat: str, optional (Default value = `'IMAGERIE/ORTHO_2022_ETE'`)
|
11889
|
+
:return: If `getdata_im` is False, returns (fig, ax), where `fig` is the Matplotlib figure and `ax` is the axis. If `getdata_im` is True, returns (fig, ax, im), where `im` is the image object created by `imshow`.
|
11890
|
+
:rtype: tuple
|
11891
|
+
"""
|
11892
|
+
|
11893
|
+
# Convert to single block
|
11894
|
+
single_block = self.as_WolfArray()
|
11895
|
+
|
11896
|
+
return single_block.plot_matplotlib(figax=figax, getdata_im=getdata_im, update_palette=update_palette, vmin=vmin, vmax=vmax, figsize=figsize, Walonmap=Walonmap, cat=cat)
|
11897
|
+
|
11104
11898
|
|
11105
11899
|
class WolfArrayMNAP(WolfArrayMB):
|
11106
11900
|
"""
|