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.
- wolfhece/PyConfig.py +77 -4
- wolfhece/PyDraw.py +765 -13
- wolfhece/PyPalette.py +36 -0
- wolfhece/PyParams.py +2 -2
- wolfhece/PyVertexvectors.py +560 -64
- wolfhece/apps/version.py +1 -1
- wolfhece/coupling/hydrology_2d.py +295 -192
- wolfhece/eikonal.py +505 -0
- wolfhece/hydrology/Catchment.py +48 -48
- wolfhece/hydrology/PyWatershed.py +93 -93
- wolfhece/lagrange_multiplier.py +205 -0
- wolfhece/lazviewer/laz_viewer.py +28 -3
- wolfhece/math_parser/calculator.py +1 -0
- wolfhece/pybridges.py +2 -2
- wolfhece/pypolygons_scen.py +2 -2
- wolfhece/scenario/config_manager.py +12 -12
- wolfhece/wolf_array.py +1048 -42
- wolfhece/wolfresults_2D.py +204 -13
- {wolfhece-2.1.126.dist-info → wolfhece-2.1.128.dist-info}/METADATA +2 -3
- {wolfhece-2.1.126.dist-info → wolfhece-2.1.128.dist-info}/RECORD +23 -21
- {wolfhece-2.1.126.dist-info → wolfhece-2.1.128.dist-info}/WHEEL +1 -1
- {wolfhece-2.1.126.dist-info → wolfhece-2.1.128.dist-info}/entry_points.txt +0 -0
- {wolfhece-2.1.126.dist-info → wolfhece-2.1.128.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,
|
@@ -1576,6 +1697,14 @@ class CropDialog(wx.Dialog):
|
|
1576
1697
|
self.ex.SetValue(str(header.origx + header.nbx * header.dx))
|
1577
1698
|
self.ey.SetValue(str(header.origy + header.nby * header.dy))
|
1578
1699
|
|
1700
|
+
def get_crop(self):
|
1701
|
+
""" Return the crop values """
|
1702
|
+
try:
|
1703
|
+
return [[float(self.ox.Value), float(self.oy.Value)], [float(self.ex.Value), float(self.ey.Value)]]
|
1704
|
+
except:
|
1705
|
+
logging.error(_('Values must be numbers'))
|
1706
|
+
return None
|
1707
|
+
|
1579
1708
|
import string
|
1580
1709
|
class IntValidator(wx.Validator):
|
1581
1710
|
''' Validates data as it is entered into the text controls. '''
|
@@ -1912,6 +2041,8 @@ class Ops_Array(wx.Frame):
|
|
1912
2041
|
|
1913
2042
|
self.labelling = wx.Button(self.tools, wx.ID_ANY, _("Labelling"), wx.DefaultPosition,wx.DefaultSize, 0)
|
1914
2043
|
|
2044
|
+
self.statistics = wx.Button(self.tools, wx.ID_ANY, _("Statistics"), wx.DefaultPosition,wx.DefaultSize, 0)
|
2045
|
+
|
1915
2046
|
self.clean = wx.Button(self.tools, wx.ID_ANY, _("Clean"), wx.DefaultPosition,wx.DefaultSize, 0)
|
1916
2047
|
|
1917
2048
|
self.extract_selection = wx.Button(self.tools, wx.ID_ANY, _("Extract selection"), wx.DefaultPosition,wx.DefaultSize, 0)
|
@@ -1930,6 +2061,7 @@ class Ops_Array(wx.Frame):
|
|
1930
2061
|
Toolssizer.Add(self.labelling, 1, wx.EXPAND)
|
1931
2062
|
Toolssizer.Add(self.clean, 1, wx.EXPAND)
|
1932
2063
|
Toolssizer.Add(self.extract_selection, 1, wx.EXPAND)
|
2064
|
+
Toolssizer.Add(self.statistics, 1, wx.EXPAND)
|
1933
2065
|
Toolssizer.Add(cont_sizer, 1, wx.EXPAND)
|
1934
2066
|
|
1935
2067
|
self.ApplyTools.SetToolTip(_("Apply Nullvalue into memory/object"))
|
@@ -1938,6 +2070,7 @@ class Ops_Array(wx.Frame):
|
|
1938
2070
|
self.labelling.SetToolTip(_("Labelling of contiguous zones using Scipy.label function\n\nReplacing the current values by the labels"))
|
1939
2071
|
self.clean.SetToolTip(_("Clean the array\n\nRemove small isolated patches of data"))
|
1940
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"))
|
1941
2074
|
|
1942
2075
|
self.tools.SetSizer(Toolssizer)
|
1943
2076
|
self.tools.Layout()
|
@@ -2310,6 +2443,7 @@ class Ops_Array(wx.Frame):
|
|
2310
2443
|
self.filter_zone.Bind(wx.EVT_BUTTON, self.OnFilterZone)
|
2311
2444
|
self.clean.Bind(wx.EVT_BUTTON, self.OnClean)
|
2312
2445
|
self.labelling.Bind(wx.EVT_BUTTON, self.OnLabelling)
|
2446
|
+
self.statistics.Bind(wx.EVT_BUTTON, self.OnStatistics)
|
2313
2447
|
self.extract_selection.Bind(wx.EVT_BUTTON, self.OnExtractSelection)
|
2314
2448
|
self._contour_int.Bind(wx.EVT_BUTTON, self.OnContourInt)
|
2315
2449
|
self._contour_list.Bind(wx.EVT_BUTTON, self.OnContourList)
|
@@ -2784,6 +2918,40 @@ class Ops_Array(wx.Frame):
|
|
2784
2918
|
|
2785
2919
|
self.parentarray.labelling()
|
2786
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
|
+
|
2787
2955
|
def OnExtractSelection(self, event:wx.MouseEvent):
|
2788
2956
|
""" Extract the current selection """
|
2789
2957
|
|
@@ -5122,7 +5290,7 @@ class WolfArray(Element_To_Draw, header_wolf):
|
|
5122
5290
|
:param fname: filename/filepath - if provided, the file will be read on disk
|
5123
5291
|
:param mold: initialize from a copy a the mold object --> must be a WolArray if not None
|
5124
5292
|
:param masknull: mask data based on the nullvalue
|
5125
|
-
:param crop: crop data based on the spatial extent [[xmin, xmax],[ymin,ymax]]
|
5293
|
+
:param crop: crop data based on the spatial extent [[xmin, xmax], [ymin,ymax]]
|
5126
5294
|
:param whichtype: type of the numpy array (float32 as default)
|
5127
5295
|
:param preload: True = load data during initialization ; False = waits for the display to be required
|
5128
5296
|
:param create: True = create a new array from wxDialog
|
@@ -5885,6 +6053,30 @@ class WolfArray(Element_To_Draw, header_wolf):
|
|
5885
6053
|
if reset_plot:
|
5886
6054
|
self.reset_plot()
|
5887
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
|
+
|
5888
6080
|
def export_geotif(self, outdir:str= '', extent:str= '', EPSG:int = 31370):
|
5889
6081
|
"""
|
5890
6082
|
Export de la matrice au format Geotiff (Lambert 72 - EPSG:31370)
|
@@ -5975,7 +6167,15 @@ class WolfArray(Element_To_Draw, header_wolf):
|
|
5975
6167
|
band.SetNoDataValue(nullvalue)
|
5976
6168
|
band.WriteArray(np.flipud(arr.data.transpose()))
|
5977
6169
|
band.FlushCache()
|
5978
|
-
|
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
|
+
|
5979
6179
|
|
5980
6180
|
|
5981
6181
|
def get_dxdy_min(self):
|
@@ -6073,12 +6273,16 @@ class WolfArray(Element_To_Draw, header_wolf):
|
|
6073
6273
|
|
6074
6274
|
fn_crop = fn + '_crop.tif'
|
6075
6275
|
if type(crop) is np.ndarray:
|
6076
|
-
|
6276
|
+
if crop.shape == (4,):
|
6277
|
+
logging.error(_('Crop must be a list or a numpy array with 4 values - [[xmin, xmax], [ymin, ymax]]'))
|
6278
|
+
crop = [[crop[0], crop[1]], [crop[2], crop[3]]]
|
6077
6279
|
elif type(crop) is list:
|
6078
|
-
|
6280
|
+
if len(crop) == 4:
|
6281
|
+
logging.error(_('Crop must be a list or a numpy array with 4 values - [[xmin, xmax], [ymin, ymax]]'))
|
6282
|
+
crop = [[crop[0], crop[1]], [crop[2], crop[3]]]
|
6079
6283
|
else:
|
6080
6284
|
if not self.wx_exists:
|
6081
|
-
logging.error(_('
|
6285
|
+
logging.error(_('WX App is required to display the UI'))
|
6082
6286
|
return
|
6083
6287
|
|
6084
6288
|
raster_in:gdal.Dataset
|
@@ -6141,8 +6345,8 @@ class WolfArray(Element_To_Draw, header_wolf):
|
|
6141
6345
|
newcrop.Destroy()
|
6142
6346
|
return
|
6143
6347
|
else:
|
6144
|
-
crop = [float(newcrop.ox.Value), float(newcrop.ex.Value),
|
6145
|
-
float(newcrop.oy.Value), float(newcrop.ey.Value)]
|
6348
|
+
crop = [[float(newcrop.ox.Value), float(newcrop.ex.Value)],
|
6349
|
+
[float(newcrop.oy.Value), float(newcrop.ey.Value)]]
|
6146
6350
|
|
6147
6351
|
tmpdx = float(newcrop.dx.Value)
|
6148
6352
|
tmpdy = float(newcrop.dy.Value)
|
@@ -6153,7 +6357,7 @@ class WolfArray(Element_To_Draw, header_wolf):
|
|
6153
6357
|
|
6154
6358
|
newcrop.Destroy()
|
6155
6359
|
|
6156
|
-
xmin, xmax, ymin, ymax = crop
|
6360
|
+
[xmin, xmax], [ymin, ymax] = crop
|
6157
6361
|
|
6158
6362
|
gdal.Translate(tmpfile, fn, projWin=[xmin, ymax, xmax, ymin])
|
6159
6363
|
else:
|
@@ -7800,7 +8004,8 @@ class WolfArray(Element_To_Draw, header_wolf):
|
|
7800
8004
|
|
7801
8005
|
return newArray
|
7802
8006
|
|
7803
|
-
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'):
|
7804
8009
|
"""
|
7805
8010
|
Mask nodes outside a polygon and set values to nullvalue
|
7806
8011
|
|
@@ -7811,7 +8016,7 @@ class WolfArray(Element_To_Draw, header_wolf):
|
|
7811
8016
|
# (mesh coord, 0-based)
|
7812
8017
|
|
7813
8018
|
# trouve les indices dans le polygone
|
7814
|
-
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)
|
7815
8020
|
|
7816
8021
|
self.array.mask.fill(True) # Mask everything
|
7817
8022
|
|
@@ -7824,7 +8029,8 @@ class WolfArray(Element_To_Draw, header_wolf):
|
|
7824
8029
|
|
7825
8030
|
self.count()
|
7826
8031
|
|
7827
|
-
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'):
|
7828
8034
|
"""
|
7829
8035
|
Mask nodes inside a polygon and set values to nullvalue
|
7830
8036
|
|
@@ -7835,7 +8041,7 @@ class WolfArray(Element_To_Draw, header_wolf):
|
|
7835
8041
|
# (mesh coord, 0-based)
|
7836
8042
|
|
7837
8043
|
# trouve les indices dans le polygone
|
7838
|
-
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)
|
7839
8045
|
|
7840
8046
|
if set_nullvalue:
|
7841
8047
|
# annulation des valeurs en dehors du polygone
|
@@ -7846,12 +8052,47 @@ class WolfArray(Element_To_Draw, header_wolf):
|
|
7846
8052
|
|
7847
8053
|
self.count()
|
7848
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
|
+
|
7849
8078
|
# *************************************************************************************************************************
|
7850
8079
|
# POSITION and VALUES associated to a vector/polygon/polyline
|
7851
8080
|
# These functions can not be stored in header_wolf, because wa can use the mask of the array to limit the search
|
7852
8081
|
# These functions are also present in WolfResults_2D, but they are not exactly the same due to the structure of the results
|
7853
8082
|
# *************************************************************************************************************************
|
7854
|
-
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):
|
7855
8096
|
"""
|
7856
8097
|
Return the coordinates inside a polygon
|
7857
8098
|
|
@@ -7880,7 +8121,10 @@ class WolfArray(Element_To_Draw, header_wolf):
|
|
7880
8121
|
|
7881
8122
|
return mypointsxy
|
7882
8123
|
|
7883
|
-
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):
|
7884
8128
|
"""
|
7885
8129
|
Return the coordinates inside a polygon
|
7886
8130
|
|
@@ -7889,15 +8133,29 @@ class WolfArray(Element_To_Draw, header_wolf):
|
|
7889
8133
|
"""
|
7890
8134
|
|
7891
8135
|
if isinstance(myvect, vector):
|
7892
|
-
# force la mise à jour des min/max
|
7893
8136
|
myvect.find_minmax()
|
7894
|
-
polygon = myvect.
|
8137
|
+
polygon = myvect.polygon
|
7895
8138
|
elif isinstance(myvect, Polygon):
|
7896
8139
|
polygon = myvect
|
7897
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
|
7898
8146
|
mypointsxy, mypointsij = self.get_xy_infootprint_vect(myvect)
|
7899
8147
|
|
7900
|
-
|
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
|
7901
8159
|
|
7902
8160
|
mypointsxy = mypointsxy[np.where(inside)]
|
7903
8161
|
|
@@ -7908,6 +8166,33 @@ class WolfArray(Element_To_Draw, header_wolf):
|
|
7908
8166
|
|
7909
8167
|
return mypointsxy
|
7910
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
|
+
|
7911
8196
|
def get_xy_under_polyline(self, myvect: vector, usemask:bool=True):
|
7912
8197
|
"""
|
7913
8198
|
Return the coordinates along a polyline
|
@@ -7921,7 +8206,27 @@ class WolfArray(Element_To_Draw, header_wolf):
|
|
7921
8206
|
|
7922
8207
|
return mypoints
|
7923
8208
|
|
7924
|
-
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.):
|
7925
8230
|
"""
|
7926
8231
|
Return the indices inside a polygon
|
7927
8232
|
|
@@ -7931,7 +8236,8 @@ class WolfArray(Element_To_Draw, header_wolf):
|
|
7931
8236
|
"""
|
7932
8237
|
|
7933
8238
|
# force la mise à jour des min/max
|
7934
|
-
myvect
|
8239
|
+
if isinstance(myvect, vector):
|
8240
|
+
myvect.find_minmax()
|
7935
8241
|
|
7936
8242
|
mypointsxy, mypointsij = self.get_xy_infootprint_vect(myvect, eps=eps)
|
7937
8243
|
|
@@ -7949,32 +8255,201 @@ class WolfArray(Element_To_Draw, header_wolf):
|
|
7949
8255
|
|
7950
8256
|
return mypointsij
|
7951
8257
|
|
7952
|
-
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:
|
7953
8348
|
""" Return True if the array intersects the polygon
|
7954
8349
|
|
7955
8350
|
:param myvect: target vector
|
7956
8351
|
:param usemask: limit potential nodes to unmaksed nodes
|
7957
8352
|
"""
|
7958
8353
|
|
7959
|
-
|
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
|
7960
8359
|
|
7961
|
-
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:
|
7962
8364
|
""" Return True if the array intersects the polygon
|
7963
8365
|
|
7964
8366
|
:param myvect: target vector
|
7965
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]:
|
8381
|
+
"""
|
8382
|
+
Element-wise intersection with a list of polygons
|
7966
8383
|
"""
|
7967
|
-
return self.get_xy_inside_polygon_shapely(myvect, usemask).shape[0] > 0
|
7968
8384
|
|
7969
|
-
|
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))
|
8394
|
+
|
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.):
|
7970
8434
|
"""
|
7971
8435
|
Return the indices along a polyline
|
7972
8436
|
|
7973
8437
|
:param myvect: target vector
|
7974
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)
|
7975
8440
|
"""
|
7976
8441
|
|
7977
|
-
|
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)
|
7978
8453
|
pts = myvect._refine2D(ds)
|
7979
8454
|
|
7980
8455
|
allij = np.asarray([self.get_ij_from_xy(curpt.x, curpt.y) for curpt in pts])
|
@@ -7990,7 +8465,7 @@ class WolfArray(Element_To_Draw, header_wolf):
|
|
7990
8465
|
|
7991
8466
|
return allij
|
7992
8467
|
|
7993
|
-
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]:
|
7994
8469
|
"""
|
7995
8470
|
Récupération des valeurs contenues dans un polygone
|
7996
8471
|
|
@@ -8005,6 +8480,44 @@ class WolfArray(Element_To_Draw, header_wolf):
|
|
8005
8480
|
else:
|
8006
8481
|
return myvalues, None
|
8007
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
|
+
|
8008
8521
|
def get_values_underpoly(self, myvect: vector, usemask:bool=True, getxy:bool=False):
|
8009
8522
|
"""
|
8010
8523
|
Récupération des valeurs contenues sous une polyligne
|
@@ -8047,6 +8560,63 @@ class WolfArray(Element_To_Draw, header_wolf):
|
|
8047
8560
|
|
8048
8561
|
return self.get_values_underpoly(myvect, usemask, getxy)
|
8049
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
|
+
|
8050
8620
|
# *************************************************************************************************************************
|
8051
8621
|
# END POSITION and VALUES associated to a vector/polygon/polyline
|
8052
8622
|
# *************************************************************************************************************************
|
@@ -9073,7 +9643,7 @@ class WolfArray(Element_To_Draw, header_wolf):
|
|
9073
9643
|
else:
|
9074
9644
|
self.mypal.isopop(self.get_working_array(), self.nbnotnull)
|
9075
9645
|
|
9076
|
-
if VERSION_RGB==1 :
|
9646
|
+
if VERSION_RGB==1 or which == 1:
|
9077
9647
|
if self.nbx * self.nby > 1_000_000 : logging.info(_('Computing colors'))
|
9078
9648
|
if self.wolftype not in [WOLF_ARRAY_FULL_SINGLE, WOLF_ARRAY_FULL_INTEGER8, WOLF_ARRAY_FULL_UINTEGER8]:
|
9079
9649
|
# FIXME: Currently, only some types are supported in Cython/OpenGL library
|
@@ -9098,7 +9668,7 @@ class WolfArray(Element_To_Draw, header_wolf):
|
|
9098
9668
|
self._tmp_float32 = None
|
9099
9669
|
|
9100
9670
|
|
9101
|
-
if VERSION_RGB==1 :
|
9671
|
+
if VERSION_RGB==1 or which == 1:
|
9102
9672
|
if self.shading:
|
9103
9673
|
pond = (self.shaded.array-.5)*2.
|
9104
9674
|
pmin = (1. - self.shaded.alpha) * self.rgb
|
@@ -9107,7 +9677,7 @@ class WolfArray(Element_To_Draw, header_wolf):
|
|
9107
9677
|
self.rgb[pond<0,i] = self.rgb[pond<0,i] * (1.+pond[pond<0]) - pmin[pond<0,i] * pond[pond<0]
|
9108
9678
|
self.rgb[pond>0,i] = self.rgb[pond>0,i] * (1.-pond[pond>0]) + pmax[pond>0,i] * pond[pond>0]
|
9109
9679
|
|
9110
|
-
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.]
|
9111
9681
|
|
9112
9682
|
if self.myops is not None:
|
9113
9683
|
# update the wx
|
@@ -9254,22 +9824,94 @@ class WolfArray(Element_To_Draw, header_wolf):
|
|
9254
9824
|
self.mygrid = {}
|
9255
9825
|
self.gridmaxscales = -1
|
9256
9826
|
|
9257
|
-
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'):
|
9258
9834
|
"""
|
9259
9835
|
Plot the array - Matplotlib version
|
9260
9836
|
|
9261
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
|
9262
9884
|
"""
|
9263
9885
|
|
9264
9886
|
self.mask_data(self.nullvalue)
|
9265
|
-
|
9887
|
+
if update_palette:
|
9888
|
+
self.updatepalette(0)
|
9889
|
+
|
9266
9890
|
if figax is None:
|
9267
|
-
fig, ax = plt.subplots()
|
9891
|
+
fig, ax = plt.subplots(figsize=figsize)
|
9268
9892
|
else:
|
9269
9893
|
fig, ax = figax
|
9270
9894
|
|
9271
|
-
|
9272
|
-
|
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)
|
9273
9915
|
ax.set_aspect('equal')
|
9274
9916
|
|
9275
9917
|
if getdata_im:
|
@@ -9601,7 +10243,8 @@ class WolfArray(Element_To_Draw, header_wolf):
|
|
9601
10243
|
|
9602
10244
|
return indicesX,indicesY,contourgen,interior
|
9603
10245
|
|
9604
|
-
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]:
|
9605
10248
|
"""
|
9606
10249
|
Create Matplotlib image from WolfArray
|
9607
10250
|
"""
|
@@ -9616,11 +10259,11 @@ class WolfArray(Element_To_Draw, header_wolf):
|
|
9616
10259
|
|
9617
10260
|
if cmap is None:
|
9618
10261
|
# update local colors if not already done
|
9619
|
-
if self
|
9620
|
-
self
|
10262
|
+
if self.rgb is None:
|
10263
|
+
self.updatepalette(1)
|
9621
10264
|
|
9622
10265
|
# Pointing RGB
|
9623
|
-
colors = self
|
10266
|
+
colors = self.rgb
|
9624
10267
|
colors[self.array.mask,3] = 0.
|
9625
10268
|
# Plot
|
9626
10269
|
colors = colors.swapaxes(0,1)
|
@@ -9847,6 +10490,292 @@ class WolfArray(Element_To_Draw, header_wolf):
|
|
9847
10490
|
|
9848
10491
|
return zones
|
9849
10492
|
|
10493
|
+
def inpaint(self, mask_array:"WolfArray" = None, test_array:"WolfArray" = None,
|
10494
|
+
ignore_last:int = 1, multiprocess:bool = True):
|
10495
|
+
""" InPaintaing holes in the array
|
10496
|
+
|
10497
|
+
:param mask_array: where computation is done
|
10498
|
+
:param test_array: used in test -- interpolation is accepted if new value is over test_array
|
10499
|
+
:param ignore_last: number of last patches to ignore
|
10500
|
+
:param multiprocess: use multiprocess for inpainting
|
10501
|
+
"""
|
10502
|
+
|
10503
|
+
from .eikonal import inpaint_array
|
10504
|
+
|
10505
|
+
if mask_array is None:
|
10506
|
+
mask_array = self.array.mask
|
10507
|
+
elif isinstance(mask_array, WolfArray):
|
10508
|
+
mask_array = mask_array.array.data
|
10509
|
+
|
10510
|
+
if test_array is None:
|
10511
|
+
test_array = np.ones(self.array.data.shape) * self.array.data.min()
|
10512
|
+
elif isinstance(test_array, WolfArray):
|
10513
|
+
test_array = test_array.array.data
|
10514
|
+
|
10515
|
+
time, wl_np, wd_np = inpaint_array(self.array.data,
|
10516
|
+
mask_array,
|
10517
|
+
test_array,
|
10518
|
+
ignore_last_patches= ignore_last,
|
10519
|
+
dx = self.dx,
|
10520
|
+
dy = self.dy,
|
10521
|
+
NoDataValue = self.nullvalue,
|
10522
|
+
inplace=True,
|
10523
|
+
multiprocess= multiprocess)
|
10524
|
+
|
10525
|
+
wd = WolfArray(mold=self)
|
10526
|
+
wd.array[:,:] = wd_np[:,:]
|
10527
|
+
wd.mask_data(self.nullvalue)
|
10528
|
+
|
10529
|
+
wl = self
|
10530
|
+
|
10531
|
+
self.mask_data(self.nullvalue)
|
10532
|
+
self.reset_plot()
|
10533
|
+
return time, wl, wd
|
10534
|
+
|
10535
|
+
def _inpaint_waterlevel_dem_dtm(self, dem:"WolfArray", dtm:"WolfArray",
|
10536
|
+
ignore_last:int = 1, use_fortran:bool = False,
|
10537
|
+
multiprocess:bool = True):
|
10538
|
+
""" InPaintaing waterlevel holes in the array.
|
10539
|
+
|
10540
|
+
We use DEM and DTM to mask and constraint the inpainting process.
|
10541
|
+
|
10542
|
+
:param dem: Digital Elevation Model (same as simulation model)
|
10543
|
+
:param dtm: Digital Terrain Model
|
10544
|
+
:param ignore_last: number of last patches to ignore
|
10545
|
+
:param use_fortran: use Fortran inpainting code
|
10546
|
+
:param multiprocess: use multiprocess for inpainting
|
10547
|
+
"""
|
10548
|
+
|
10549
|
+
if use_fortran:
|
10550
|
+
import shutil
|
10551
|
+
from tempfile import TemporaryDirectory
|
10552
|
+
# check if hoels.exe is available in PATH
|
10553
|
+
if shutil.which('holes.exe') is None:
|
10554
|
+
logging.error(_('holes.exe not found in PATH'))
|
10555
|
+
logging.info(_('We use the Python version of inpainting'))
|
10556
|
+
use_fortran = False
|
10557
|
+
|
10558
|
+
else:
|
10559
|
+
with TemporaryDirectory() as tmpdirname:
|
10560
|
+
# create mask from DEM, DTM and WL
|
10561
|
+
|
10562
|
+
mask = self._create_building_holes_dem_dtm(dem, dtm, ignore_last)
|
10563
|
+
|
10564
|
+
# save mask and dtm to temporary files
|
10565
|
+
locdir = Path(tmpdirname)
|
10566
|
+
wl_name = locdir / 'array.bin'
|
10567
|
+
mask_name = locdir / 'mask.bin'
|
10568
|
+
dtm_name = locdir / 'dtm.bin'
|
10569
|
+
|
10570
|
+
mask.write_all(mask_name)
|
10571
|
+
dtm.write_all(dtm_name)
|
10572
|
+
|
10573
|
+
oldname = self.filename
|
10574
|
+
self.write_all(wl_name)
|
10575
|
+
self.filename = oldname
|
10576
|
+
|
10577
|
+
# call holes.exe
|
10578
|
+
olddir = os.getcwd()
|
10579
|
+
|
10580
|
+
# change to temporary directory
|
10581
|
+
os.chdir(locdir)
|
10582
|
+
shell_command = f'holes.exe inpaint in={wl_name} mask={mask_name} dem={dtm_name} avoid_last={ignore_last} out=temp'
|
10583
|
+
logging.info(shell_command)
|
10584
|
+
logging.info('Inpainting holes using holes.exe')
|
10585
|
+
os.system(shell_command)
|
10586
|
+
logging.info('Done - reading inpainted array')
|
10587
|
+
|
10588
|
+
# read inpainted array
|
10589
|
+
wl = WolfArray(locdir / 'temp_combl.tif')
|
10590
|
+
wd = WolfArray(locdir / 'temp_h.tif')
|
10591
|
+
|
10592
|
+
wd.array[wd.array < 0.] = 0.
|
10593
|
+
wd.mask_data(0.)
|
10594
|
+
|
10595
|
+
os.chdir(olddir)
|
10596
|
+
|
10597
|
+
self.array.data[:,:] = wl.array.data[:,:]
|
10598
|
+
|
10599
|
+
time = None
|
10600
|
+
|
10601
|
+
if not use_fortran:
|
10602
|
+
|
10603
|
+
from .eikonal import inpaint_waterlevel
|
10604
|
+
|
10605
|
+
time, wl_np, wd_np = inpaint_waterlevel(self.array.data,
|
10606
|
+
dem.array.data,
|
10607
|
+
dtm.array.data,
|
10608
|
+
ignore_last_patches= ignore_last,
|
10609
|
+
dx = self.dx,
|
10610
|
+
dy = self.dy,
|
10611
|
+
NoDataValue = self.nullvalue,
|
10612
|
+
inplace=True,
|
10613
|
+
multiprocess= multiprocess)
|
10614
|
+
|
10615
|
+
wd = WolfArray(mold=self)
|
10616
|
+
wd.array[:,:] = wd_np[:,:]
|
10617
|
+
wd.mask_data(self.nullvalue)
|
10618
|
+
|
10619
|
+
wl = self
|
10620
|
+
|
10621
|
+
self.mask_data(self.nullvalue)
|
10622
|
+
self.reset_plot()
|
10623
|
+
return time, wl, wd
|
10624
|
+
|
10625
|
+
def count_holes(self, mask:"WolfArray" = None):
|
10626
|
+
""" Count holes in the array """
|
10627
|
+
from .eikonal import count_holes
|
10628
|
+
|
10629
|
+
if mask is None:
|
10630
|
+
mask = self.array.mask
|
10631
|
+
elif isinstance(mask, WolfArray):
|
10632
|
+
mask = mask.array.data
|
10633
|
+
|
10634
|
+
return count_holes(mask)
|
10635
|
+
|
10636
|
+
def select_holes(self, mask:"WolfArray" = None, ignore_last:int = 1):
|
10637
|
+
""" Select holes in the array """
|
10638
|
+
|
10639
|
+
if mask is None:
|
10640
|
+
mask = self.array.mask
|
10641
|
+
elif isinstance(mask, WolfArray):
|
10642
|
+
mask = mask.array.data
|
10643
|
+
|
10644
|
+
labels, numfeatures = label(mask)
|
10645
|
+
|
10646
|
+
# count cells in holes
|
10647
|
+
nb_cells = np.bincount(labels.ravel())
|
10648
|
+
|
10649
|
+
# sort by number of cells
|
10650
|
+
idx = np.argsort(nb_cells)
|
10651
|
+
|
10652
|
+
for i in range(ignore_last):
|
10653
|
+
labels[labels == idx[-1-i]] = 0
|
10654
|
+
|
10655
|
+
# select holes but ignoring last ones
|
10656
|
+
ij = np.argwhere(labels)
|
10657
|
+
|
10658
|
+
# Convert i,j to x,y
|
10659
|
+
|
10660
|
+
xy = self.ij2xy_np(ij)
|
10661
|
+
|
10662
|
+
self.SelectionData.myselection = list(xy)
|
10663
|
+
self.SelectionData.update_nb_nodes_selection()
|
10664
|
+
|
10665
|
+
def create_mask_holes(self, ignore_last:int = 1) -> "WolfArray":
|
10666
|
+
""" Select holes in the array and create a new aray """
|
10667
|
+
|
10668
|
+
labels, numfeatures = label(self.array.mask)
|
10669
|
+
|
10670
|
+
# count cells in holes
|
10671
|
+
nb_cells = np.bincount(labels.ravel())
|
10672
|
+
|
10673
|
+
# sort by number of cells
|
10674
|
+
idx = np.argsort(nb_cells)
|
10675
|
+
|
10676
|
+
for i in range(ignore_last):
|
10677
|
+
labels[labels == idx[-1-i]] = 0
|
10678
|
+
|
10679
|
+
newarray = WolfArray(mold=self)
|
10680
|
+
# newarray.allocate_ressources()
|
10681
|
+
newarray.array.data[:,:] = labels
|
10682
|
+
newarray.array.mask[:,:] = ~labels.astype(bool)
|
10683
|
+
newarray.set_nullvalue_in_mask()
|
10684
|
+
|
10685
|
+
return newarray
|
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
|
+
|
10749
|
+
def _create_building_holes_dem_dtm(self, dem:"WolfArray", dtm:"WolfArray", ignore_last:int = 1) -> "WolfArray":
|
10750
|
+
""" Select holes in the array and create a new aray """
|
10751
|
+
|
10752
|
+
buildings = dem - dtm
|
10753
|
+
buildings.array[buildings.array < 0.] = 0.
|
10754
|
+
buildings.array[buildings.array > 0.] = dtm.array[buildings.array > 0.]
|
10755
|
+
buildings.array[buildings.array == 0.] = dem.array.max() + 1.
|
10756
|
+
|
10757
|
+
return buildings
|
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
|
+
|
9850
10779
|
class WolfArrayMB(WolfArray):
|
9851
10780
|
"""
|
9852
10781
|
Matrice multiblocks
|
@@ -10073,6 +11002,7 @@ class WolfArrayMB(WolfArray):
|
|
10073
11002
|
arr.isblock = True
|
10074
11003
|
arr.blockindex = len(self.myblocks) - 1
|
10075
11004
|
|
11005
|
+
|
10076
11006
|
def share_palette(self):
|
10077
11007
|
"""Partage de la palette de couleurs entre matrices liées"""
|
10078
11008
|
for cur in self.linkedarrays:
|
@@ -10775,11 +11705,26 @@ class WolfArrayMB(WolfArray):
|
|
10775
11705
|
self.translx = 0.
|
10776
11706
|
self.transly = 0.
|
10777
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
|
+
|
10778
11722
|
def as_WolfArray(self, abs:bool=True, forced_header:header_wolf = None) -> WolfArray:
|
10779
11723
|
"""
|
10780
|
-
Convert to WolfArray
|
11724
|
+
Convert to WolfArray -- Rebin blocks if necessary
|
10781
11725
|
|
10782
|
-
|
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
|
10783
11728
|
"""
|
10784
11729
|
|
10785
11730
|
newArray = WolfArray()
|
@@ -10889,6 +11834,67 @@ class WolfArrayMB(WolfArray):
|
|
10889
11834
|
|
10890
11835
|
return newArray
|
10891
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
|
+
|
10892
11898
|
|
10893
11899
|
class WolfArrayMNAP(WolfArrayMB):
|
10894
11900
|
"""
|