Source code for wolfhece.pyvertexvectors._zones

"""GUI-enabled Zones class with wx.Frame, OpenGL, and treelist UI."""
from __future__ import annotations

import logging
import warnings
import copy
import numpy as np
import wx
import wx._dataview
from wx.dataview import *
from wx.core import BoxSizer, TreeItemId
from OpenGL.GL import *
from shapely.geometry import LineString, MultiLineString, Point, MultiPoint, Polygon, JOIN_STYLE, MultiPolygon
from shapely.ops import nearest_points
from shapely import delaunay_triangles
import geopandas as gpd
import matplotlib.pyplot as plt
from matplotlib.axes import Axes
from matplotlib.figure import Figure
from matplotlib import cm
from matplotlib.colors import Colormap
from scipy.interpolate import interp1d
from typing import Union, Literal
from pathlib import Path
from concurrent.futures import ThreadPoolExecutor, wait
from os import path

from .. import is_opengl_context_available
from ..PyTranslate import _
from ..CpGrid import CpGrid
from ..PyVertex import wolfvertex, cloud_vertices
from ..color_constants import getRGBfromI, getIfromRGB, Colors, RGB
from ..PyParams import Wolf_Param, Type_Param, new_json
from ..drawing_obj import Element_To_Draw
from ..matplotlib_fig import Matplotlib_Figure as MplFig
from ..PyPalette import wolfpalette
from ._models import ZonesModel
from ._vector import vector
from ._vectorproperties import vectorproperties
from ._zone import zone
from ._models import VectorOGLRenderer

try:
    from ..pydownloader import download_file, toys_dataset
except ImportError:
    pass

[docs] class Zones(ZonesModel, wx.Frame, Element_To_Draw): # ================================================================ # Constructor # ================================================================ """ Objet de gestion d'informations vectorielles Une instance 'Zones' contient une liste de 'zone' Une instance 'zone' contient une listde de 'vector' (segment, ligne, polyligne, polygone...) """
[docs] tx:float
[docs] ty:float
[docs] myzones:list[zone]
[docs] treelist:TreeListCtrl
[docs] xls:CpGrid
def __init__(self, filename:Union[str, Path]='', ox:float=0., oy:float=0., tx:float=0., ty:float=0., parent=None, is2D=True, idx: str = '', colname: str = None, plotted: bool = True, mapviewer=None, need_for_wx: bool = False, bbox:Polygon = None, find_minmax:bool = True, shared:bool = False, colors:dict = None) -> None: """Initialise a GUI-enabled Zones collection. :param filename: File path to read (empty to start empty). :param ox: X-origin offset. :param oy: Y-origin offset. :param tx: Translation along X. :param ty: Translation along Y. :param parent: Parent object (e.g. mapviewer). :param is2D: Whether the zones are 2-D. :param idx: Identifier string. :param colname: Column name for shapefile/GDB import. :param plotted: Whether the object is initially plotted. :param mapviewer: Map viewer instance. :param need_for_wx: Whether wx integration is required. :param bbox: Optional bounding box to filter features on import. :param find_minmax: Whether to compute bounds after loading. :param shared: If True, skip OpenGL list preparation. :param colors: Optional colour dictionary for ``colorize_data``. """ Element_To_Draw.__init__(self, idx, plotted, mapviewer, need_for_wx)
[docs] self.shared = shared
[docs] self.parent = parent
self.xls = None
[docs] self.labelactvect = None
[docs] self.labelactzone = None
[docs] self._wx_frame_initialized = False
[docs] self._rendering_machine = None # None = LIST by default
ZonesModel.__init__(self, filename=filename, ox=ox, oy=oy, tx=tx, ty=ty, is2D=is2D, idx=idx, colname=colname, bbox=bbox, find_minmax_init=find_minmax, colors=colors) if self.wx_exists: self.set_mapviewer() try: wx.Frame.__init__(self, None, size=(400, 400)) self._wx_frame_initialized = True self.Bind(wx.EVT_CLOSE, self.OnClose) except: raise Warning(_('Bad wx context -- see Zones.__init__')) if plotted and self.has_OGLContext and not self.shared: logging.debug(_('Preparing OpenGL lists')) self.prep_listogl() logging.debug(_('OpenGL lists ready')) # ================================================================ # Factory overrides # ================================================================
[docs] def _make_zone(self, **kwargs): """Factory: create a new GUI-enabled zone.""" return zone(**kwargs)
[docs] def _make_zones(self, **kwargs): """Factory: create a new GUI-enabled Zones collection.""" return Zones(**kwargs)
[docs] def _make_vector(self, **kwargs): """Factory: create a new GUI-enabled vector.""" return vector(**kwargs)
[docs] def find_nearest_vertex(self, x: float, y: float) -> wolfvertex | None: """Return the nearest vertex across all GUI zones.""" return super().find_nearest_vertex(x, y)
[docs] def find_nearest_vector(self, x: float, y: float) -> vector | None: """Return the nearest GUI vector across all GUI zones.""" return super().find_nearest_vector(x, y)
# ================================================================ # I/O / Serialization # ================================================================
[docs] def import_gdb(self, fn:str, bbox:Polygon = None, layers:list[str] = None, colname:str = None, value_columns: str | list[str] | tuple[str, ...] | None = None, share_multipart_values: bool = False): """Import a GDB file, showing a wx layer dialog when available. :param fn: Path to the GDB file. :param bbox: Optional bounding box to filter features. :param layers: Layer names to import (None = wx dialog or all). :param colname: Column name used to build zone names. :param value_columns: Columns to store into vector properties. :param share_multipart_values: If True, multipart geometries share the same underlying ``_values`` dictionary across split vectors. """ import fiona if layers is None and self.wx_exists: all_layers = fiona.listlayers(fn) dlg = wx.MultiChoiceDialog(None, _('Choose the layers to import'), _('Choose the layers'), all_layers) if dlg.ShowModal() == wx.ID_OK: layers = [all_layers[i] for i in dlg.GetSelections()] else: return super().import_gdb(fn, bbox=bbox, layers=layers, colname=colname, value_columns=value_columns, share_multipart_values=share_multipart_values)
[docs] def import_gpkg(self, fn:str, bbox:Polygon = None, layers:list[str] = None, colname:str = None, value_columns: str | list[str] | tuple[str, ...] | None = None, share_multipart_values: bool = False): """Import a GeoPackage file, showing a wx layer dialog when available. :param fn: Path to the GPKG file. :param bbox: Optional bounding box to filter features. :param layers: Layer names to import (None = wx dialog or all). :param colname: Column name used to build zone names. :param value_columns: Columns to store into vector properties. :param share_multipart_values: If True, multipart geometries share the same underlying ``_values`` dictionary across split vectors. """ import fiona if layers is None and self.wx_exists: all_layers = fiona.listlayers(fn) dlg = wx.MultiChoiceDialog(None, _('Choose the layers to import'), _('Choose the layers'), all_layers) if dlg.ShowModal() == wx.ID_OK: layers = [all_layers[i] for i in dlg.GetSelections()] else: return super().import_gpkg(fn, bbox=bbox, layers=layers, colname=colname, value_columns=value_columns, share_multipart_values=share_multipart_values)
[docs] def Onsaveimages(self, event:wx.MouseEvent): """ Enregistrement d'une image pour tous les vecteurs """ self.save_images_fromvec()
[docs] def save_images_fromvec(self, dir=''): """ Sauvegarde d'images des vecteurs dans un répertoire FIXME : pas encore vraiment au point """ if dir=='': if self.wx_exists: dlg = wx.DirDialog(None,"Choose directory to store images",style=wx.FD_SAVE) ret=dlg.ShowModal() if ret==wx.ID_CANCEL: dlg.Destroy() return dir = dlg.GetPath() dlg.Destroy() if dir=='': return for curzone in self.myzones: for curvec in curzone.myvectors: if curvec.nbvertices>1: oldwidth=curvec.myprop.width curvec.myprop.width=4 myname = curvec.myname self.Activate_vector(curvec) if self.mapviewer is not None: if self.mapviewer.linked: for curview in self.mapviewer.linkedList: title = curview.GetTitle() curview.zoomon_activevector() fn = path.join(dir,title + '_' + myname+'.png') curview.save_canvasogl(fn) else: self.mapviewer.zoomon_activevector() fn = path.join(dir,myname+'.png') self.mapviewer.save_canvasogl(fn) fn = path.join(dir,'palette_v_' + myname+'.png') self.mapviewer.active_array.mypal.export_image(fn,'v') fn = path.join(dir,'palette_h_' + myname+'.png') self.mapviewer.active_array.mypal.export_image(fn,'h') curvec.myprop.width = oldwidth
# ================================================================ # State management # ================================================================
[docs] def change_gui(self, parent): """Change the parent GUI object (e.g. mapviewer) and propagate to zones and vectors.""" self.parent = parent self.set_mapviewer()
[docs] def set_mapviewer(self): """ Recherche d'une instance WolfMapViewer depuis le parent """ from ..PyDraw import WolfMapViewer if self.parent is None: # Nothing to do because 'parent' is None return try: self.mapviewer = self.parent.get_mapviewer() except: self.mapviewer = None assert isinstance(self.mapviewer, WolfMapViewer), _('Bad mapviewer -- verify your code or bad parent')
[docs] def Activate_vector(self, object:vector): """ Mémorise l'objet passé en argument comme vecteur actif Pousse la même information dans l'objet parent s'il existe """ if self.wx_exists: self.active_vector = object if self.active_vector is None: logging.info(_('Active vector is now set to None')) if self.xls is not None: self.labelactvect.SetLabel('None') self.xls.ClearGrid() if self.parent is not None: try: self.parent.Active_vector(self.active_vector) except: raise Warning(_('Not supported in the current parent -- see PyVertexVectors in Activate_vector function')) return if self.xls is not None: self.xls_active_vector() if object.parentzone is not None: self.active_zone = object.parentzone object.parentzone.active_vector = object if self.parent is not None: try: self.parent.Active_vector(self.active_vector) except: raise Warning(_('Not supported in the current parent -- see PyVertexVectors in Activate_vector function')) if self.xls is not None: self.labelactvect.SetLabel(self.active_vector.myname) self.labelactzone.SetLabel(self.active_zone.myname) self.Layout()
[docs] def Activate_zone(self, object:zone): """ Mémorise l'objet passé en argument comme zone active Pousse la même information dans l'objet parent s'il existe """ if self.wx_exists: self.active_zone = object if self.active_zone is None: logging.info(_('Active zone is now set to None')) self.labelactzone.SetLabel('None') self.xls.ClearGrid() return if object.active_vector is not None: self.active_vector = object.active_vector elif object.nbvectors>0: self.Activate_vector(object.myvectors[0]) if self.active_vector is None: logging.warning(_('No vector in the active zone')) if self.parent is not None: try: self.parent.Active_zone(self.active_zone) except: raise Warning(_('Not supported in the current parent -- see PyVertexVectors in Activate_zone function')) else: self.labelactvect.SetLabel(self.active_vector.myname) if self.labelactzone is not None: self.labelactzone.SetLabel(self.active_zone.myname) self.Layout()
# ================================================================ # Properties / Callbacks # ================================================================
[docs] def show_properties(self, parent=None, forceupdate=False): """ Affichage des propriétés des zones :param parent: soit une instance 'WolfMapViewer', soit une instance 'Ops_Array' --> est utile pour transférer la propriété 'active_vector' et obtenir diverses informations si parent est d'un autre type, il faut s'assurer que les options/actions sont consistantes :param forceupdate: si True, on force la mise à jour de la structure """ self.showstructure(parent, forceupdate)
[docs] def hide_properties(self): """ Hide the properties window """ # Check if the wx frame was initialized before trying to hide it if self._wx_frame_initialized: self.Hide() if self._myprops is not None: self._myprops.Hide() for curzone in self.myzones: curzone.hide_properties()
[docs] def _callback_prop(self): """Callback invoked when global properties are applied to all vectors.""" if self._myprops is None: logging.warning(_('No properties available')) return old_mode = self.rendering_machine mode = self._myprops[('Rendering', 'Mode')] if self._myprops.is_in_default('Rendering', 'Mode') else 1 if mode == 1: self.rendering_machine = VectorOGLRenderer.SHADER else: self.rendering_machine = VectorOGLRenderer.LIST mode_changed = old_mode != self.rendering_machine for curzone in self.myzones: for curvec in curzone.myvectors: curvec.myprop.fill_property(self._myprops, updateOGL = False) posx = self._myprops[('Move','Start X')] posy = self._myprops[('Move','Start Y')] if posx != 99999. and posy != 99999.: self._move_start = (posx,posy) else: self._move_start = None step = self._myprops[('Move','Step [m]')] if step != 99999.: self._move_step = step else: self._move_step = None posx = self._myprops[('Rotation','Center X')] posy = self._myprops[('Rotation','Center Y')] if posx != 99999. and posy != 99999.: self._rotation_center = (posx,posy) else: self._rotation_center = None step = self._myprops[('Rotation','Step [degree]')] if step != 99999.: self._rotation_step = step else: self._rotation_step = None angle = self._myprops[('Rotation','Angle [degree]')] dx = self._myprops[('Move','Delta X')] dy = self._myprops[('Move','Delta Y')] if angle != 0. and (dx!= 0. or dy!=0.): logging.error(_('Rotation and move are not compatible - Choose one and only one')) elif angle!= 0.: if self._rotation_center is None: logging.error(_('No rotation center defined - Choose one')) else: self.rotate(angle, self._rotation_center) self.clear_cache() elif dx!= 0. or dy!=0.: self.move(dx,dy) self.clear_cache() if self.mapviewer is not None: self.prep_listogl() self.mapviewer.Refresh() if mode_changed: self._edit_all_properties()
[docs] def _callback_destroy_props(self): """Callback to release the global properties dialog reference.""" self._myprops = None
[docs] def _edit_all_properties(self): """ Show properties of the zone --> will be applied to all vectors int he zone """ if self._myprops is not None: try: # Accessing the page count raises if the underlying C++ object is already destroyed. self._myprops.prop.GetPageCount() except Exception: self._myprops = None if self._myprops is None: loczone = self._make_zone(parent=self) locvec = self._make_vector(parentzone=loczone) locvec.show_properties() self._myprops = locvec.myprop.myprops self._myprops[('Legend','X')] = str(99999.) self._myprops[('Legend','Y')] = str(99999.) self._myprops[('Legend','Text')] = _('Not used') if self._rotation_center is not None: self._myprops[('Rotation', 'Center X')] = self._rotation_center[0] self._myprops[('Rotation', 'Center Y')] = self._rotation_center[1] else: self._myprops[('Rotation', 'Center X')] = 99999. self._myprops[('Rotation', 'Center Y')] = 99999. if self._rotation_step is not None: self._myprops[('Rotation', 'Step [degree]')] = self._rotation_step else: self._myprops[('Rotation', 'Step [degree]')] = 99999. self._myprops['Rotation', 'Angle [degree]'] = 0. if self._move_start is not None: self._myprops[('Move', 'Start X')] = self._move_start[0] self._myprops[('Move', 'Start Y')] = self._move_start[1] else: self._myprops[('Move', 'Start X')] = 99999. self._myprops[('Move', 'Start Y')] = 99999. self._myprops[('Move', 'Delta X')] = 0. self._myprops[('Move', 'Delta Y')] = 0. if self._move_step is not None: self._myprops[('Move', 'Step [m]')] = self._move_step else: self._myprops[('Move', 'Step [m]')] = 99999. jsonstr = new_json({ _('Legacy display lists'): 0, _('Modern shader pipeline'): 1, }, _('Rendering backend for all zones')) self._myprops.addparam('Rendering', 'Mode', 0, Type_Param.Integer, '', whichdict='Default', jsonstr=jsonstr) self._myprops[('Rendering', 'Mode')] = 1 if self.rendering_machine == VectorOGLRenderer.SHADER else 0 self._myprops.Populate() self._myprops.set_callbacks(self._callback_prop, self._callback_destroy_props) self._myprops.SetTitle(_('Properties for all vectors in {}'.format(self.filename))) self._myprops.Center() self._myprops.Raise()
# ================================================================ # OpenGL rendering # ================================================================ @property
[docs] def rendering_machine(self): """Current rendering backend for all zones. :rtype: VectorOGLRenderer """ if self._rendering_machine is not None: return self._rendering_machine return VectorOGLRenderer.SHADER
@rendering_machine.setter def rendering_machine(self, value): """Set the rendering backend and reset display lists. :param value: A :class:`VectorOGLRenderer` enum member. """ self._rendering_machine = value self.reset_listogl()
[docs] def reset_listogl(self): """ Reset des listes OpenGL pour toutes les zones """ for curzone in self.myzones: curzone.reset_listogl()
[docs] def prep_listogl(self): """ Préparation des listes OpenGL pour augmenter la vitesse d'affichage """ if is_opengl_context_available(): try: for curzone in self.myzones: curzone.prep_listogl() except Exception: logging.exception( _('Error while preparing OpenGL lists -- zones=%s mapviewer=%s'), [curzone.myname for curzone in self.myzones], type(self.get_mapviewer()).__name__ if self.get_mapviewer() is not None else None, )
# ================================================================ # Matplotlib plotting # ================================================================
[docs] def plot_matplotlib(self, ax:Axes | tuple[Figure, Axes] = None, background:Literal['ORTHO_1971', 'ORTHO_1994_2000', 'ORTHO_2006_2007', 'ORTHO_2009_2010', 'ORTHO_2012_2013', 'ORTHO_2015', 'ORTHO_2016', 'ORTHO_2017', 'ORTHO_2018', 'ORTHO_2019', 'ORTHO_2020', 'ORTHO_2021', 'ORTHO_2022_PRINTEMPS', 'ORTHO_2022_ETE', 'ORTHO_2023_ETE', 'ORTHO_LAST', 'orthoimage_coverage', 'orthoimage_coverage_2016', 'orthoimage_coverage_2017', 'orthoimage_coverage_2018', 'orthoimage_coverage_2019', 'orthoimage_coverage_2020', 'orthoimage_coverage_2021', 'orthoimage_coverage_2022', 'crossborder', 'crossborder_grey', 'overlay', 'topo', 'topo_grey'] = None, xlim:tuple[float] | None = None, ylim:tuple[float] | None= None) -> tuple[Figure, Axes]: """Plot all zones using Matplotlib with optional WMS background. :param ax: Matplotlib Axes, ``(Figure, Axes)`` tuple, or None. :param background: WMS background layer name (WalOnMap, IGN or Cartoweb). :param xlim: Optional ``(xmin, xmax)`` limits. :param ylim: Optional ``(ymin, ymax)`` limits. :return: ``(Figure, Axes)`` tuple. """ available_Walonmap = ['ORTHO_1971', 'ORTHO_1994_2000', 'ORTHO_2006_2007', 'ORTHO_2009_2010', 'ORTHO_2012_2013', 'ORTHO_2015', 'ORTHO_2016', 'ORTHO_2017', 'ORTHO_2018', 'ORTHO_2019', 'ORTHO_2020', 'ORTHO_2021', 'ORTHO_2022_PRINTEMPS', 'ORTHO_2022_ETE', 'ORTHO_2023_ETE', 'ORTHO_LAST'] available_IGN = ['orthoimage_coverage', 'orthoimage_coverage_2016', 'orthoimage_coverage_2017', 'orthoimage_coverage_2018', 'orthoimage_coverage_2019', 'orthoimage_coverage_2020', 'orthoimage_coverage_2021', 'orthoimage_coverage_2022'] available_Cartoweb = ['crossborder', 'crossborder_grey', 'overlay', 'topo', 'topo_grey'] Walonmap = False IGN = False Cartoweb = False if background is not None: if background in available_Walonmap: Walonmap = True background = 'IMAGERIE/' + background elif background in available_IGN: IGN = True elif background in available_Cartoweb: Cartoweb = True else: logging.error(_('The background {} is not available').format(background)) logging.error(_('Available WalOnMap categories are: {}').format(', '.join(available_Walonmap))) logging.error(_('Available IGN categories are: {}').format(', '.join(available_IGN))) logging.error(_('Available Cartoweb categories are: {}').format(', '.join(available_Cartoweb))) if isinstance(ax, tuple): fig, ax = ax elif ax is None: fig, ax = plt.subplots() else: fig = ax.figure if Walonmap: from ..PyWMS import getWalonmap from PIL import Image if background.replace('IMAGERIE/', '') not in available_Walonmap: logging.error(_('The category {} is not available in WalOnMap').format(background)) logging.error(_('Available categories are: {}').format(', '.join(available_Walonmap))) else: try: bounds = self.get_bounds() if xlim is not None: bounds = (xlim, bounds[1]) if ylim is not None: bounds = (bounds[0], ylim) scale_covery = (bounds[0][1] - bounds[0][0]) / (bounds[1][1] - bounds[1][0]) if scale_covery > 1.: # x is larger than y w = 2000 h = int(2000 / scale_covery) else: # y is larger than x h = 2000 w = int(2000 * scale_covery) IO_image = getWalonmap(background, bounds[0][0], bounds[1][0], bounds[0][1], bounds[1][1], w=w, h=h, tofile=False) # w=self.nbx, h=self.nby image = Image.open(IO_image) ax.imshow(image.transpose(Image.Transpose.FLIP_TOP_BOTTOM), extent=(bounds[0][0], bounds[0][1], bounds[1][0], bounds[1][1]), alpha=1, origin='lower') except Exception as e: logging.error(_('Error while fetching the map image from WalOnMap')) logging.error(e) elif IGN: from ..PyWMS import getNGI from PIL import Image if background not in available_IGN: logging.error(_('The category {} is not available in IGN').format(background)) logging.error(_('Available categories are: {}').format(', '.join(available_IGN))) else: try: bounds = self.get_bounds() if xlim is not None: bounds = (xlim, bounds[1]) if ylim is not None: bounds = (bounds[0], ylim) scale_covery = (bounds[0][1] - bounds[0][0]) / (bounds[1][1] - bounds[1][0]) if scale_covery > 1.: # x is larger than y w = 2000 h = int(2000 / scale_covery) else: # y is larger than x h = 2000 w = int(2000 * scale_covery) IO_image = getNGI(background, bounds[0][0], bounds[1][0], bounds[0][1], bounds[1][1], w=w, h=h, tofile=False) # w=self.nbx, h=self.nby image = Image.open(IO_image) ax.imshow(image.transpose(Image.Transpose.FLIP_TOP_BOTTOM), extent=(bounds[0][0], bounds[0][1], bounds[1][0], bounds[1][1]), alpha=1, origin='lower') except Exception as e: logging.error(_('Error while fetching the map image from IGN')) logging.error(e) elif Cartoweb: from ..PyWMS import getCartoweb from PIL import Image if background not in available_Cartoweb: logging.error(_('The category {} is not available in Cartoweb').format(background)) logging.error(_('Available categories are: {}').format(', '.join(available_Cartoweb))) else: try: bounds = self.get_bounds() if xlim is not None: bounds = (xlim, bounds[1]) if ylim is not None: bounds = (bounds[0], ylim) scale_covery = (bounds[0][1] - bounds[0][0]) / (bounds[1][1] - bounds[1][0]) if scale_covery > 1.: # x is larger than y w = 2000 h = int(2000 / scale_covery) else: # y is larger than x h = 2000 w = int(2000 * scale_covery) IO_image = getCartoweb(background, bounds[0][0], bounds[1][0], bounds[0][1], bounds[1][1], w=w, h=h, tofile=False) # w=self.nbx, h=self.nby image = Image.open(IO_image) ax.imshow(image.transpose(Image.Transpose.FLIP_TOP_BOTTOM), extent=(bounds[0][0], bounds[0][1], bounds[1][0], bounds[1][1]), alpha=1, origin='lower') except Exception as e: logging.error(_('Error while fetching the map image from Cartoweb')) logging.error(e) for curzone in self.myzones: curzone.plot_matplotlib(ax, xlim, ylim) if xlim is not None: ax.set_xlim(xlim) if ylim is not None: ax.set_ylim(ylim) return fig, ax
[docs] def Onplotmpl(self, event:wx.MouseEvent): """ Plot active vector in matplotlib """ if self.verify_activevec(): return fig, ax = plt.subplots() self.active_vector.plot_matplotlib(ax) ax.set_aspect('equal') fig.show()
[docs] def Onplotmplsz(self, event:wx.MouseEvent): """ Plot active vector in matplotlib with sz values """ if self.verify_activevec(): return fig, ax = plt.subplots() s, z = self.active_vector.sz_curvi ax.plot(s, z) ax.set_xlabel('s') ax.set_ylabel('z') fig.show()
# ================================================================ # wx GUI / Window & Tree # ================================================================
[docs] def OnClose(self, e): """ Fermeture de la fenêtre """ if self.wx_exists: self.Hide()
[docs] def showstructure(self, parent=None, forceupdate=False): """ Affichage de la structure des zones :param parent: soit une instance 'WolfMapViewer', soit une instance 'Ops_Array' --> est utile pour transférer la propriété 'active_vector' et obtenir diverses informations si parent est d'un autre type, il faut s'assurer que les options/actions sont consistantes :param forceupdate: si True, on force la mise à jour de la structure """ if self.parent is None: self.parent = parent self.wx_exists = wx.App.Get() is not None if forceupdate: self.init_struct = True self.parent = parent if self.wx_exists: self.set_mapviewer() # Ensure wx.Frame.__init__ has been called before creating child widgets if not self._wx_frame_initialized: try: wx.Frame.__init__(self, None, size=(400, 400)) self._wx_frame_initialized = True self.Bind(wx.EVT_CLOSE, self.OnClose) except Exception: raise Warning(_('Bad wx context -- see Zones.showstructure')) # wx est initialisé et tourne --> on peut créer le Frame associé aux vecteurs if self.init_struct: self.init_ui() self.Show() self.Center() self.Raise()
[docs] def init_ui(self): """ Création de l'interface wx de gestion de l'objet """ if self.wx_exists: # la strcuture n'existe pas encore box = BoxSizer(orient=wx.HORIZONTAL) boxleft = BoxSizer(orient=wx.VERTICAL) boxright = BoxSizer(orient=wx.VERTICAL) boxadd = BoxSizer(orient=wx.VERTICAL) boxdelete = BoxSizer(orient=wx.VERTICAL) boxupdown = BoxSizer(orient=wx.HORIZONTAL) boxupdownv = BoxSizer(orient=wx.VERTICAL) boxupdownz = BoxSizer(orient=wx.VERTICAL) self.xls=CpGrid(self,-1,wx.WANTS_CHARS) self.xls.CreateGrid(10,6) sizer_add_update = BoxSizer(orient=wx.HORIZONTAL) self.addrows = wx.Button(self,label=_('Rows+')) self.addrows.SetToolTip(_("Add rows to the grid --> Useful for manually adding some points to a vector")) self.addrows.Bind(wx.EVT_BUTTON,self.Onaddrows) self.updatevertices = wx.Button(self,label=_('Update')) self.updatevertices.SetToolTip(_("Transfer the coordinates from the editor to the memory and update the plot")) self.updatevertices.Bind(wx.EVT_BUTTON,self.Onupdatevertices) self._test_interior = wx.Button(self,label=_('Test interior')) self._test_interior.SetToolTip(_("Test if some segments of the active vector are exactly the same")) self._test_interior.Bind(wx.EVT_BUTTON,self.Ontest_interior) self.plot_mpl = wx.Button(self,label=_('Plot xy')) self.plot_mpl.SetToolTip(_("Plot the active vector in a new window (matplotlib)")) self.plot_mpl.Bind(wx.EVT_BUTTON,self.Onplotmpl) self.plot_mplsz = wx.Button(self,label=_('Plot sz')) self.plot_mplsz.SetToolTip(_("Plot the active vector in a new window (matplotlib)")) self.plot_mplsz.Bind(wx.EVT_BUTTON,self.Onplotmplsz) sizer_add_update.Add(self.addrows,1, wx.EXPAND) sizer_add_update.Add(self.updatevertices, 1, wx.EXPAND) sizer_add_update.Add(self.plot_mpl,1, wx.EXPAND) sizer_add_update.Add(self.plot_mplsz,1, wx.EXPAND) self.capturevertices = wx.Button(self,label=_('Add')) self.capturevertices.SetToolTip(_("Capture new points from mouse clicks \n\n Keyboard 'Return' to stop the action ! ")) self.capturevertices.Bind(wx.EVT_BUTTON,self.Oncapture) self.modifyvertices = wx.Button(self,label=_('Modify')) self.modifyvertices.SetToolTip(_("Modify some point from mouse clicks \n\n - First click around the desired point \n - Move the position \n - Validate by a click \n\n Keyboard 'Return' to stop the action ! ")) self.modifyvertices.Bind(wx.EVT_BUTTON,self.Onmodify) self.dynapar = wx.Button(self,label=_('Add and parallel')) self.dynapar.SetToolTip(_("Capture new points from mouse clicks and create parallel \n\n - MAJ + Middle Mouse Button to adjust the semi-distance \n - CTRL + MAJ + Middle Mouse Button to choose specific semi-distance \n\n Keyboard 'Return' to stop the action ! ")) self.dynapar.Bind(wx.EVT_BUTTON,self.OncaptureandDynapar) self.createapar = wx.Button(self,label=_('Create parallel')) self.createapar.SetToolTip(_("Create a single parallel to the currently activated vector as a new vector in the same zone")) self.createapar.Bind(wx.EVT_BUTTON,self.OnAddPar) self._btn_simplify = wx.Button(self,label=_('Simplify')) self._btn_simplify.SetToolTip(_("Simplify the currently activated vector using the Douglas-Peucker algorithm")) self._btn_simplify.Bind(wx.EVT_BUTTON,self.Onsimplify) sizer_reverse_split = BoxSizer(orient=wx.HORIZONTAL) self.reverseorder = wx.Button(self,label=_('Reverse points order')) self.reverseorder.SetToolTip(_("Reverse the order/sens of the currently activated vector -- Overwrite the data")) self.reverseorder.Bind(wx.EVT_BUTTON,self.OnReverse) self.sascending = wx.Button(self,label=_('Verify vertices positions')) self.sascending.SetToolTip(_("Check whether the vertices of the activated vector are ordered according to increasing 's' defined as 2D geometric distance \n If needed, invert some positions and return information to the user")) self.sascending.Bind(wx.EVT_BUTTON,self.Onsascending) self._btn_buffer = wx.Button(self,label=_('Buffer')) self._btn_buffer.SetToolTip(_("Create a buffer around the currently activated vector\nThe buffer replaces the current vector")) self._btn_buffer.Bind(wx.EVT_BUTTON,self.Onbuffer) self.insertvertices = wx.Button(self,label=_('Insert')) self.insertvertices.SetToolTip(_("Insert new vertex into the currently active vector from mouse clicks \n The new vertex is inserted along the nearest segment \n\n Keyboard 'Return' to stop the action ! ")) self.insertvertices.Bind(wx.EVT_BUTTON,self.Oninsert) self.splitvertices = wx.Button(self,label=_('Copy and Split')) self.splitvertices.SetToolTip(_("Make a copy of the currently active vector and add new vertices according to a user defined length \n The new vertices are evaluated based on a 3D curvilinear distance")) self.splitvertices.Bind(wx.EVT_BUTTON,self.Onsplit) self.interpxyz = wx.Button(self,label=_('Interpolate coords')) self.interpxyz.SetToolTip(_("Linear Interpolation of the Z values if empty or egal to -99999 \n The interpolation uses the 's' value contained in the 5th column of the grid, X being the first one")) self.interpxyz.Bind(wx.EVT_BUTTON,self.Oninterpvec) self.evaluates = wx.Button(self,label=_('Evaluate s')) self.evaluates.SetToolTip(_("Calculate the curvilinear 's' distance using a '2D' or '3D' approach and store the result in the 5th column of the grid, X being the first one")) self.evaluates.Bind(wx.EVT_BUTTON,self.Onevaluates) self.update_from_s = wx.Button(self,label=_('Update from sz (support)')) self.update_from_s.SetToolTip(_("Update the coordinates of the vertices based on the 's' distance \n The interpolation uses the 's' value contained in the 5th column of the grid, X being the first one.\nThe support vector is in XY. It will be replaced.")) self.update_from_s.Bind(wx.EVT_BUTTON,self.Onupdate_from_sz_support) # Modified self.zoomonactive = wx.Button(self,label=_('Zoom on active vector')) self.zoomonactive.SetToolTip(_("Zoom on the active vector and a default view size of 500 m x 500 m")) self.zoomonactive.Bind(wx.EVT_BUTTON,self.Onzoom) # Added self.zoomonactivevertex = wx.Button(self, label =_('Zoom on active vertex')) self.zoomonactivevertex.SetToolTip(_("Zoom on the active vertex and a default view size of 50 m x 50 m")) self.zoomonactivevertex.Bind(wx.EVT_BUTTON, self.Onzoomvertex) boxzoom = BoxSizer(orient=wx.HORIZONTAL) boxzoom.Add(self.zoomonactive,1, wx.EXPAND) boxzoom.Add(self.zoomonactivevertex,1, wx.EXPAND) self.saveimages = wx.Button(self,label=_('Save images from active zone')) self.saveimages.SetToolTip(_("Save images of the currently active zone based on the current view in the main window \n The images are saved in a folder")) self.saveimages.Bind(wx.EVT_BUTTON,self.Onsaveimages) sizer_bin_cs = BoxSizer(orient=wx.HORIZONTAL) self.binfrom3 = wx.Button(self,label=_('Bin from 3 vectors')) self.binfrom3.SetToolTip(_("Create a bin/rectangular channel based on 3 vectors in the currently active zone \n Some parameters will be prompted to the user (lateral height, ...) and if a triangular mesh must be created --> Blender")) self.binfrom3.Bind(wx.EVT_BUTTON,self.Oncreatebin) self.cs_from_zone = wx.Button(self,label=_('Cross-sections from zone')) self.cs_from_zone.SetToolTip(_("Create cross-sections based on the vectors of the currently active zone")) self.cs_from_zone.Bind(wx.EVT_BUTTON,self.Oncreate_cs_from_active_zone) sizer_bin_cs.Add(self.binfrom3, 1, wx.EXPAND) sizer_bin_cs.Add(self.cs_from_zone, 1, wx.EXPAND) sizer_triangulation = wx.BoxSizer(wx.HORIZONTAL) self.trifromall = wx.Button(self,label=_('Triangulation')) self.trifromall.SetToolTip(_("Create a triangular mesh based on all vectors within the currently active zone.\nUse the vertices as they are after subdividing the vectors into a specified number of points.\nAdd the resulting mesh to the GUI.\nThis can be useful in certain interpolation methods.")) self.trifromall.Bind(wx.EVT_BUTTON,self.Oncreatemultibin) self.trifromall_proj = wx.Button(self,label=_('Triang. (projection)')) self.trifromall_proj.SetToolTip(_("Create a triangular mesh based on all vectors in the currently active zone.\nGenerate vertices by projecting the central polyline, or the nearest one if there is an even number of polylines, onto the other polylines.\nAdd the resulting mesh to the GUI.\nThis can be useful in certain interpolation methods.")) self.trifromall_proj.Bind(wx.EVT_BUTTON,self.Oncreatemultibin_project) sizer_triangulation.Add(self.trifromall, 1, wx.EXPAND) sizer_triangulation.Add(self.trifromall_proj, 1, wx.EXPAND) sizer_delaunay = wx.BoxSizer(wx.HORIZONTAL) self.constrainedDelaunay = wx.Button(self,label=_('Constr. Delaunay')) self.constrainedDelaunay.SetToolTip(_("Create a triangular mesh based on all vectors in the currently active zone.")) self.constrainedDelaunay.Bind(wx.EVT_BUTTON,self.OnconstrainedDelaunay) self.tri_cs = wx.Button(self,label=_('Triang. (cross-section)')) self.tri_cs.SetToolTip(_("Create a triangular mesh based on all vectors in the currently active zone.\nSupport vectors must have 'support' in their name.\nOthers must cross the supports.")) self.tri_cs.Bind(wx.EVT_BUTTON,self.Oncreatetricrosssection) sizer_delaunay.Add(self.constrainedDelaunay, 1, wx.EXPAND) sizer_delaunay.Add(self.tri_cs, 1, wx.EXPAND) sizer_polygons = wx.BoxSizer(wx.HORIZONTAL) self.polyfrompar = wx.Button(self,label=_('Polygons from paral.')) self.polyfrompar.SetToolTip(_("Create polygons in a new zone from parallels defined by " + _('Add and parallel') + _(" and a 2D curvilinear distance \n Useful for plotting some results or analyse data inside each polygon"))) self.polyfrompar.Bind(wx.EVT_BUTTON,self.Oncreatepolygons) self.slidingpoly = wx.Button(self,label=_('Sliding polygons')) self.slidingpoly.SetToolTip(_("Create sliding polygons in a new zone")) self.slidingpoly.Bind(wx.EVT_BUTTON,self.Oncreateslidingpoly) sizer_polygons.Add(self.polyfrompar, 1, wx.EXPAND) sizer_polygons.Add(self.slidingpoly, 1, wx.EXPAND) # Added self.getxyfromsz = wx.Button(self, label = _('Update from sz (2 points)')) self.getxyfromsz.SetToolTip(_("Populate the X an Y columns based on: \n - Given sz coordinates, \n - 2 Points \n - The X and Y coordinates of the initial point (s = 0) and, \n - The X and Y coordinates of a second point (for the direction)")) self.getxyfromsz.Bind(wx.EVT_BUTTON, self.get_xy_from_sz) boxright.Add(self.xls,1,wx.EXPAND) boxright.Add(sizer_add_update,0,wx.EXPAND) # boxright.Add(self.updatevertices,0,wx.EXPAND) subboxadd = BoxSizer(orient=wx.HORIZONTAL) subboxadd.Add(self.capturevertices,1,wx.EXPAND) subboxadd.Add(self.dynapar,1,wx.EXPAND) boxright.Add(subboxadd,0,wx.EXPAND) subboxmod = wx.BoxSizer(wx.HORIZONTAL) subboxmod.Add(self.modifyvertices,1,wx.EXPAND) subboxmod.Add(self.insertvertices,1,wx.EXPAND) boxright.Add(subboxmod,0,wx.EXPAND) subboxparsimpl = wx.BoxSizer(orient=wx.HORIZONTAL) subboxparsimpl.Add(self.createapar,1,wx.EXPAND) subboxparsimpl.Add(self._btn_simplify,1,wx.EXPAND) boxright.Add(subboxparsimpl,0,wx.EXPAND) sizer_reverse_split.Add(self.reverseorder,1,wx.EXPAND) sizer_reverse_split.Add(self.splitvertices,1,wx.EXPAND) boxright.Add(sizer_reverse_split,0,wx.EXPAND) # boxright.Add(self.splitvertices,0,wx.EXPAND) # boxright.Add(self.zoomonactive,0,wx.EXPAND) boxright.Add(boxzoom,0,wx.EXPAND) box_s = wx.BoxSizer(wx.HORIZONTAL) boxright.Add(box_s,0,wx.EXPAND) box_s.Add(self.evaluates,1,wx.EXPAND) box_s.Add(self.update_from_s,1,wx.EXPAND) box_s.Add(self.getxyfromsz,1,wx.EXPAND) # Added box_interp_indices = wx.BoxSizer(wx.HORIZONTAL) box_interp_indices.Add(self.interpxyz,1,wx.EXPAND) box_interp_indices.Add(self._test_interior, 1, wx.EXPAND) boxright.Add(box_interp_indices,0,wx.EXPAND) _sizer_ascbuffer = wx.BoxSizer(wx.HORIZONTAL) _sizer_ascbuffer.Add(self.sascending,1,wx.EXPAND) _sizer_ascbuffer.Add(self._btn_buffer,1,wx.EXPAND) boxright.Add(_sizer_ascbuffer,0,wx.EXPAND) sizer_values_surface = wx.BoxSizer(wx.HORIZONTAL) self.butgetval = wx.Button(self,label=_('Get values')) self.butgetval.SetToolTip(_("Get values of the attached/active array (not working with 2D results) on each vertex of the active vector and update the editor")) self.butgetval.Bind(wx.EVT_BUTTON,self.Ongetvalues) self.btn_surface = wx.Button(self,label=_('Surface')) self.btn_surface.SetToolTip(_("Compute the surface of the active vector/polygon")) self.btn_surface.Bind(wx.EVT_BUTTON,self.Onsurface) sizer_values_surface.Add(self.butgetval,1,wx.EXPAND) sizer_values_surface.Add(self.btn_surface,1,wx.EXPAND) boxright.Add(sizer_values_surface,0,wx.EXPAND) self.butgetvallinked = wx.Button(self,label=_('Get values (all)')) self.butgetvallinked.SetToolTip(_("Get values of all the visible arrays and 2D results on each vertex of the active vector \n\n Create a new zone containing the results")) self.butgetvallinked.Bind(wx.EVT_BUTTON,self.Ongetvalueslinked) self.butgetrefvallinked = wx.Button(self,label=_('Get values (all and remeshing)')) self.butgetrefvallinked.SetToolTip(_("Get values of all the visible arrays and 2D results on each vertex of the active vector \n and more is the step size of the array is more precise \n\n Create a new zone containing the results")) self.butgetrefvallinked.Bind(wx.EVT_BUTTON,self.Ongetvalueslinkedandref) self._move_rotate_sizer = wx.BoxSizer(wx.HORIZONTAL) self.butmove = wx.Button(self,label=_('Move')) self.butmove.SetToolTip(_("Move the active vector - If not defined in properties, the first right click is the origin of the move")) self.butmove.Bind(wx.EVT_BUTTON,self.OnMove) self.butrotate = wx.Button(self,label=_('Rotate')) self.butrotate.SetToolTip(_("Rotate the active vector - If not defined in properties, the first right click is the origin of the rotation")) self.butrotate.Bind(wx.EVT_BUTTON,self.OnRotate) self._move_rotate_sizer.Add(self.butmove, 1, wx.EXPAND) self._move_rotate_sizer.Add(self.butrotate, 1, wx.EXPAND) sizer_budget = wx.BoxSizer(wx.HORIZONTAL) sizer_budget.Add(self.butgetvallinked,1,wx.EXPAND) sizer_budget.Add(self.butgetrefvallinked,1,wx.EXPAND) boxright.Add(sizer_budget,0,wx.EXPAND) # boxright.Add(self.butgetrefvallinked,0,wx.EXPAND) boxright.Add(self._move_rotate_sizer, 0, wx.EXPAND) self.treelist = TreeListCtrl(self,style=TL_CHECKBOX|wx.TR_FULL_ROW_HIGHLIGHT|wx.TR_EDIT_LABELS) self.treelist.AppendColumn('Zones') self.treelist.Bind(EVT_TREELIST_ITEM_CHECKED, self.OnCheckItem) self.treelist.Bind(EVT_TREELIST_ITEM_ACTIVATED, self.OnActivateItem) self.treelist.Bind(EVT_TREELIST_ITEM_CONTEXT_MENU,self.OnRDown) self.treelist.Bind(wx.EVT_CHAR,self.OnEditLabel) self.labelactvect = wx.StaticText( self, wx.ID_ANY, _("None"), style=wx.ALIGN_CENTER_HORIZONTAL ) self.labelactvect.Wrap( -1 ) self.labelactvect.SetToolTip(_('Name of the active vector')) self.labelactzone = wx.StaticText( self, wx.ID_ANY, _("None"), style=wx.ALIGN_CENTER_HORIZONTAL ) self.labelactzone.Wrap( -1 ) self.labelactzone.SetToolTip(_('Name of the active zone')) sizer_addzonevector=wx.BoxSizer(wx.HORIZONTAL) self.addzone = wx.Button(self,label=_('Add zone')) self.addvector = wx.Button(self,label=_('Add vector')) sizer_addzonevector.Add(self.addzone,1,wx.EXPAND) sizer_addzonevector.Add(self.addvector,1,wx.EXPAND) self.duplicatezone = wx.Button(self,label=_('Duplicate zone')) self.duplicatevector = wx.Button(self,label=_('Duplicate vector')) self.deletezone = wx.Button(self,label=_('Delete zone')) self.findactivevector = wx.Button(self,label=_('Find in all')) self.findactivevector.SetToolTip(_("Search and activate the nearest vector by mouse click (Searching window : all zones)")) self.findactivevectorcurz = wx.Button(self,label=_('Find in active')) self.findactivevectorcurz.SetToolTip(_("Search and activate the nearest vector by mouse click (Searching window : active zone)")) self.deletevector = wx.Button(self,label=_('Delete vector')) self.upvector = wx.Button(self,label=_('Up vector')) self.downvector = wx.Button(self,label=_('Down vector')) self.upzone = wx.Button(self,label=_('Up zone')) self.downzone = wx.Button(self,label=_('Down zone')) # self.interpolate = wx.Button(self,label=_('Interpolate vector')) self._move_rotate_zone_sizer = wx.BoxSizer(wx.HORIZONTAL) self.butmove_zone = wx.Button(self,label=_('Move zone')) self.butmove_zone.SetToolTip(_("Move the active zone - If not defined in properties, the first right click is the origin of the move")) self.butmove_zone.Bind(wx.EVT_BUTTON,self.OnMoveZone) self.butrotate_zone = wx.Button(self,label=_('Rotate zone')) self.butrotate_zone.SetToolTip(_("Rotate the active zone - If not defined in properties, the first right click is the origin of the rotation")) self.butrotate_zone.Bind(wx.EVT_BUTTON,self.OnRotateZone) self._move_rotate_zone_sizer.Add(self.butmove_zone, 1, wx.EXPAND) self._move_rotate_zone_sizer.Add(self.butrotate_zone, 1, wx.EXPAND) self.addzone.Bind(wx.EVT_BUTTON,self.OnClickadd_zone) self.addvector.Bind(wx.EVT_BUTTON,self.OnClickadd_vector) self.duplicatezone.Bind(wx.EVT_BUTTON,self.OnClickduplicate_zone) self.duplicatevector.Bind(wx.EVT_BUTTON,self.OnClickduplicate_vector) self.deletezone.Bind(wx.EVT_BUTTON,self.OnClickdelete_zone) self.deletevector.Bind(wx.EVT_BUTTON,self.OnClickdelete_vector) self.upvector.Bind(wx.EVT_BUTTON,self.OnClickup_vector) self.downvector.Bind(wx.EVT_BUTTON,self.OnClickdown_vector) self.upzone.Bind(wx.EVT_BUTTON,self.OnClickup_zone) self.downzone.Bind(wx.EVT_BUTTON,self.OnClickdown_zone) # self.interpolate.Bind(wx.EVT_BUTTON,self.OnClickInterpolate) self.findactivevector.Bind(wx.EVT_BUTTON,self.OnClickfindactivate_vector) self.findactivevectorcurz.Bind(wx.EVT_BUTTON,self.OnClickfindactivate_vector2) boxadd.Add(self.labelactvect,1,wx.EXPAND) boxadd.Add(self.labelactzone,1,wx.EXPAND) boxadd.Add(sizer_addzonevector,1,wx.EXPAND) # boxadd.Add(self.addvector,1,wx.EXPAND) boxduplicate = wx.BoxSizer(wx.HORIZONTAL) boxduplicate.Add(self.duplicatezone,1,wx.EXPAND) boxduplicate.Add(self.duplicatevector,1,wx.EXPAND) boxadd.Add(boxduplicate,1,wx.EXPAND) subboxadd = wx.BoxSizer(wx.HORIZONTAL) subboxadd.Add(self.findactivevector,1,wx.EXPAND) subboxadd.Add(self.findactivevectorcurz,1,wx.EXPAND) boxadd.Add(subboxadd,1,wx.EXPAND) subboxdelete = wx.BoxSizer(wx.HORIZONTAL) subboxdelete.Add(self.deletezone,1,wx.EXPAND) subboxdelete.Add(self.deletevector,1,wx.EXPAND) boxdelete.Add(subboxdelete,1,wx.EXPAND) boxupdown.Add(boxupdownz,1,wx.EXPAND) boxupdown.Add(boxupdownv,1,wx.EXPAND) boxupdownv.Add(self.upvector,1,wx.EXPAND) boxupdownv.Add(self.downvector,1,wx.EXPAND) boxupdownz.Add(self.upzone,1,wx.EXPAND) boxupdownz.Add(self.downzone,1,wx.EXPAND) # boxdelete.Add(self.interpolate,1,wx.EXPAND) boxtri = wx.BoxSizer(wx.VERTICAL) boxtri.Add(self.saveimages,1,wx.EXPAND) boxtri.Add(sizer_bin_cs,1,wx.EXPAND) boxtri.Add(sizer_triangulation,1,wx.EXPAND) # boxtri.Add(self.trifromall,1,wx.EXPAND) # boxtri.Add(self.trifromall_proj,1,wx.EXPAND) boxtri.Add(sizer_delaunay,1,wx.EXPAND) boxtri.Add(sizer_polygons,1,wx.EXPAND) # boxtri.Add(self.polyfrompar,1,wx.EXPAND) # boxtri.Add(self.slidingpoly,1,wx.EXPAND) boxleft.Add(self.treelist,1,wx.EXPAND) boxleft.Add(boxadd,0,wx.EXPAND) boxleft.Add(boxdelete,0,wx.EXPAND) boxleft.Add(boxupdown,0,wx.EXPAND) boxleft.Add(boxtri,0,wx.EXPAND) boxleft.Add(self._move_rotate_zone_sizer, 0, wx.EXPAND) box.Add(boxleft,1,wx.EXPAND) box.Add(boxright,1,wx.EXPAND) self.fill_structure() self.treelist.SetSize(200,500) self.SetSize(650,700) self.SetSizer(box) icon_candidates = [ Path(__file__).parent / 'apps' / 'wolf.ico', Path(__file__).parent.parent / 'apps' / 'wolf.ico', Path(__file__).parent.parent.parent / 'apps' / 'wolf.ico', ] icon_path = next((p for p in icon_candidates if p.exists()), None) if icon_path is not None: try: icon = wx.Icon(str(icon_path), wx.BITMAP_TYPE_ANY) if icon.IsOk(): self.SetIcon(icon) except Exception: logging.debug('Unable to load icon for zones from %s', icon_path) if self.idx == '': if self.parent is not None: try: self.SetTitle(_('Zones associated to : {}'.format(self.parent.idx))) except: logging.warning(_('No parent idx found')) else: self.SetTitle(_('Zones : {}'.format(self.idx))) self.init_struct=False
[docs] def fill_structure(self): """ Remplissage de la structure wx """ def store_tree_state(tree:TreeListCtrl): """ Store the state of the tree control. Recursively store the state of the tree control in a list of item data. """ expended_items = [] root = tree.GetRootItem() if root is None: return def traverse_and_store(item:wx._dataview.TreeListItem): if not item.IsOk(): return if tree.IsExpanded(item): expended_items.append(tree.GetItemData(item)) item = tree.GetNextItem(item) traverse_and_store(item) traverse_and_store(root) return expended_items def restore_tree_state(tree:TreeListCtrl, expended_items): """ Restore the state of the tree control. Recursively restore the state of the tree control from a list of item data. """ if len(expanded)==0: # Nothing to do return root = tree.GetRootItem() if root is None: return def traverse_and_restore(item): if not item.IsOk(): return if tree.GetItemData(item) in expended_items: tree.Expand(item) item = tree.GetNextItem(item) traverse_and_restore(item) traverse_and_restore(root) if self.wx_exists: if self.xls is not None: expanded = store_tree_state(self.treelist) self.treelist.DeleteAllItems() root = self.treelist.GetRootItem() mynode=self.treelist.AppendItem(root, 'All zones', data=self) self.treelist.CheckItem(mynode) for curzone in self.myzones: curzone.add2tree(self.treelist,mynode) self.treelist.Expand(mynode) restore_tree_state(self.treelist, expanded)
[docs] def expand_tree(self, objzone=None): """ Développe la structure pour un objet spécifique stocké dans la self.treelist. L'objet peut être une 'zone' ou un 'vector' --> see more in 'fill_structure'. """ if self.wx_exists: if self.xls is not None: root = self.treelist.GetRootItem() curchild = self.treelist.GetFirstChild(root) curzone=self.treelist.GetItemData(curchild) while curchild is not None: if curzone is objzone: self.treelist.Expand(curchild) break else: curchild=self.treelist.GetNextItem(curchild) curzone=self.treelist.GetItemData(curchild)
[docs] def OnCheckItem(self, event:wx.MouseEvent): """ Coche/Décoche un ékement de la treelist """ if self.wx_exists: myitem=event.GetItem() check = self.treelist.GetCheckedState(myitem) myitemdata=self.treelist.GetItemData(myitem) if check: myitemdata.use() else: myitemdata.unuse()
[docs] def OnRDown(self, event:TreeListEvent): """ Affiche les propriétés du vecteur courant Clicl-droit """ if self.wx_exists: if self.active_zone is None and self.active_zone is None: logging.info(_('You will edit the properties of the entire instance (all zones and all vectors)')) self._edit_all_properties() elif isinstance(self.last_active, vector): self.active_vector.show_properties() elif isinstance(self.last_active, zone): self.active_zone.show_properties()
[docs] def OnActivateItem(self, event:TreeListEvent): """ Activation d'un élément dans le treelist """ if self.wx_exists: myitem=event.GetItem() myitemdata=self.treelist.GetItemData(myitem) if isinstance(myitemdata,vector): self.Activate_vector(myitemdata) elif isinstance(myitemdata,zone): self.Activate_zone(myitemdata) else: self.Activate_vector(None) self.Activate_zone(None) self.last_active = myitemdata
[docs] def OnEditLabel(self, event:wx.MouseEvent): """ Edition de la clé/label de l'élément actif du treelist """ if self.wx_exists: key=event.GetKeyCode() if key==wx.WXK_F2: if self.last_active is not None: curname=None dlg=wx.TextEntryDialog(None,_('Choose a new name'), value=self.last_active.myname) while curname is None: rc = dlg.ShowModal() if rc == wx.ID_OK: curname = str(dlg.GetValue()) dlg.Destroy() self.last_active.myname = curname self.fill_structure() else: dlg.Destroy() return
# ================================================================ # wx GUI / Grid & Editor # ================================================================
[docs] def xls_active_vector(self): """ Remplit le tableur """ if self.wx_exists: if self.xls is not None: self.xls.ClearGrid() self.active_vector.fillgrid(self.xls)
[docs] def Onaddrows(self, event:wx.MouseEvent): """ Ajout de lignes au tableur """ if self.wx_exists: nbrows=None dlg=wx.TextEntryDialog(None,_('How many rows?'),value='1') while nbrows is None: rc = dlg.ShowModal() if rc == wx.ID_OK: nbrows = int(dlg.GetValue()) self.xls.AppendRows(nbrows) else: return
[docs] def Onupdatevertices(self,event): """ Mie à jour des vertices sur base du tableur """ if self.verify_activevec(): return self.active_vector.updatefromgrid(self.xls) self.find_minmax(True)
[docs] def Ontest_interior(self, event:wx.MouseEvent): """ Test if the active vector has interior portions """ if self.verify_activevec(): return self.active_vector.check_if_interior_exists() self.active_vector._fillgrid_only_i(self.xls) self.get_mapviewer().Paint()
# ================================================================ # Zone & Vector management # ================================================================
[docs] def OnClickadd_zone(self, event:wx.MouseEvent): """ Ajout d'une zone au GUI """ if self.wx_exists: curname=None dlg=wx.TextEntryDialog(None,_('Choose a name for the new zone'),value='New_Zone') while curname is None: rc = dlg.ShowModal() if rc == wx.ID_OK: curname = str(dlg.GetValue()) newzone = self._make_zone(name=curname, parent=self) self.add_zone(newzone) self.fill_structure() self.active_zone = newzone else: return
[docs] def OnClickadd_vector(self, event:wx.MouseEvent): """ Ajout d'un vecteur à la zone courante """ if self.active_zone is None: logging.warning(_('No active zone - Can not add a vector to None !')) logging.warning(_('Please activate a zone first')) return if self.wx_exists: curname=None dlg=wx.TextEntryDialog(None,_('Choose a name for the new vector'),value='New_Vector') while curname is None: rc = dlg.ShowModal() if rc == wx.ID_OK: curname = str(dlg.GetValue()) newvec = self._make_vector(name=curname, parentzone=self.active_zone) self.active_zone.add_vector(newvec) self.fill_structure() self.Activate_vector(newvec) else: return
[docs] def OnClickduplicate_zone(self, event:wx.MouseEvent): """ Duplication de la zone active """ if self.wx_exists: if self.verify_activezone(): return newzone = self.active_zone.deepcopy_zone() newzone.myname = self.active_zone.myname + '_copy' self.add_zone(newzone, forceparent=True) self.fill_structure() self.Activate_zone(newzone)
[docs] def OnClickduplicate_vector(self, event:wx.MouseEvent): """ Duplication du vecteur actif """ if self.active_vector is None: logging.warning(_('No active vector - Can not duplicate None !')) logging.warning(_('Please activate a vector first')) return if self.wx_exists: if self.verify_activevec(): return newvec = self.active_vector.deepcopy_vector() newvec.myname = self.active_vector.myname + '_copy' self.active_zone.add_vector(newvec, forceparent=True) self.fill_structure() self.Activate_vector(newvec)
[docs] def OnClickdelete_zone(self, event:wx.MouseEvent): """ Suppression de la zone courante """ if self.active_zone is None: logging.warning(_('No active zone - Can not delete None !')) logging.warning(_('Please activate a zone first')) return if self.wx_exists: curname=self.active_zone.myname r = wx.MessageDialog( None, _('The zone {n} will be deleted. Continue?').format(n=curname), style=wx.YES_NO | wx.NO_DEFAULT | wx.ICON_QUESTION ).ShowModal() if r != wx.ID_YES: return self.delete_zone(self.active_zone)
[docs] def OnClickdelete_vector(self, event:wx.MouseEvent): """ Suppression du vecteur actif """ if self.wx_exists: if self.verify_activevec(): return curname=self.active_vector.myname r = wx.MessageDialog(None, _('The vector {n} will be deleted. Continue?').format(n=curname), style=wx.YES_NO | wx.ICON_QUESTION).ShowModal() if r != wx.ID_YES: return actzone =self.active_zone if actzone.nbvectors==0: return idx = int(actzone.myvectors.index(self.active_vector)) if idx >= 0 and idx < actzone.nbvectors: actzone.reset_listogl() actzone.myvectors.pop(idx) if actzone.nbvectors == 0: self.Activate_vector(None) elif idx < actzone.nbvectors: self.Activate_vector(actzone.myvectors[idx]) else: self.Activate_vector(actzone.myvectors[-1]) self.fill_structure() self.find_minmax(True) if self.get_mapviewer() is not None: self.get_mapviewer().Paint()
[docs] def delete_zone(self, zone_to_del:zone, update_ui:bool=True): """ Delete a zone from this Zones. :param zone: Zone to delete :param update_ui: if `True` reflects the deleteion in the user interface. """ assert zone_to_del is not None, "None can't be deleted" assert zone_to_del in self.myzones, "The zone you rpovided is nowhere to be found" # Even if one destroys the zone in the Zones, the active vector may # still have a reference to it. In that case it will continue to be # drawn in PyDraw and we don't want that. if self.active_vector is not None: self.Activate_vector(None) if self.active_zone == zone_to_del: self.Activate_zone(None) self.myzones.remove(zone_to_del) # Vectors of this zone are drawn into an opengl list. So if we clear the # list, then we clear the vectors too. Zones class has no GL list. zone_to_del.reset_listogl() if update_ui: self.fill_structure() self.find_minmax(True) if self.get_mapviewer() is not None: self.get_mapviewer().Paint()
[docs] def delete_all_zones(self): """ Delete all zone's from this Zones. """ for zone in set(self.myzones): # a set so that I don't iterate over myzones and delete from it simultaneously self.delete_zone(zone, update_ui=False) self.fill_structure() self.find_minmax(True) if self.get_mapviewer() is not None: self.get_mapviewer().Paint()
[docs] def OnClickup_vector(self, event:wx.MouseEvent): """Remonte le vecteur actif dans la liste de la zone""" if self.verify_activevec(): return for idx,curv in enumerate(self.active_zone.myvectors): if curv == self.active_vector: if idx==0: return self.active_zone.myvectors.pop(idx) self.active_zone.myvectors.insert(idx-1,curv) self.fill_structure() break
[docs] def OnClickdown_vector(self, event:wx.MouseEvent): """Descend le vecteur actif dans la liste de la zone""" if self.verify_activevec(): return for idx,curv in enumerate(self.active_zone.myvectors): if curv == self.active_vector: if idx==self.active_zone.nbvectors: return self.active_zone.myvectors.pop(idx) self.active_zone.myvectors.insert(idx+1,curv) self.fill_structure() break
[docs] def OnClickup_zone(self, event:wx.MouseEvent): """Remonte la zone active dans la liste de la zones self""" for idx,curz in enumerate(self.myzones): if curz == self.active_zone: if idx==0: return self.myzones.pop(idx) self.myzones.insert(idx-1,curz) self.fill_structure() break
[docs] def OnClickdown_zone(self, event:wx.MouseEvent): """Descend la zone active dans la liste de la zones self""" for idx,curz in enumerate(self.myzones): if curz == self.active_zone: if idx==self.nbzones: return self.myzones.pop(idx) self.myzones.insert(idx+1,curz) self.fill_structure() break
[docs] def OnClickfindactivate_vector(self, event:wx.MouseEvent): """ Recherche et activation d'un vecteur dans toutes les zones """ if self.wx_exists: dlg=wx.MessageDialog(None,"Search only closed polyline?",style=wx.YES_NO) ret=dlg.ShowModal() dlg.Destroy() if ret==wx.YES: self.mapviewer.start_action('select active vector inside', _('Select active vector inside')) else: self.mapviewer.start_action('select active vector all', _('Select active vector all')) self.mapviewer.active_zones=self
[docs] def OnClickfindactivate_vector2(self, event:wx.MouseEvent): """ Recherche et activation d'un vecteur dans la zone courante """ if self.wx_exists: dlg=wx.MessageDialog(None,"Search only closed polyline?",style=wx.YES_NO) ret=dlg.ShowModal() dlg.Destroy() if ret==wx.YES: self.mapviewer.start_action('select active vector2 inside', _('Select active vector2 inside')) else: self.mapviewer.start_action('select active vector2 all', _('Select active vector2 all')) self.mapviewer.active_zone=self.active_zone self.mapviewer.active_zones=self
# ================================================================ # Mouse capture / Interactive editing # ================================================================
[docs] def Oncapture(self, event:wx.MouseEvent): """ Ajoute de nouveaux vertices au vecteur courant Fonctionne par clicks souris via le GUI wx de WolfMapViewer """ if self.wx_exists: # N'est pas à strictement parlé dépendant de wx mais n'a de sens # que si le mapviewer est défini --> si un GUI wx existe if self.verify_activevec(): return self.mapviewer.start_action('capture vertices', _('Capture vertices')) firstvert=wolfvertex(0.,0.) self.active_vector.add_vertex(firstvert) self.active_vector._on_vertices_changed() self.mapviewer.mimicme()
[docs] def Onmodify(self, event:wx.MouseEvent): """ Permet la modification interactive de vertex dans le vector actif Premier click : recherche du vertex le plus proche Second click : figer la nouvelle position --> action active jusqu'à sélectionne une autre action ou touche Entrée """ if self.wx_exists: if self.verify_activevec(): return self.mapviewer.start_action('modify vertices', _('Modify vertices')) self.mapviewer.mimicme() self.active_zone.reset_listogl()
[docs] def OncaptureandDynapar(self, event:wx.MouseEvent): """ Ajoute des vertices au vecteur courant et crée des parallèles gauche-droite """ if self.wx_exists: if self.verify_activevec(): return if self.active_zone.nbvectors > 1: dlg = wx.MessageDialog(None, _('You already have more than one vector in the active zone. This action will conserve only the active vector.\nDo you want to continue?'), _('Warning'), style=wx.YES_NO | wx.ICON_WARNING) ret = dlg.ShowModal() if ret == wx.ID_NO: dlg.Destroy() return logging.warning(_('You already have more than one vector in the active zone. This action will conserve only the active vector and you want it.')) dlg.Destroy() self.mapviewer.start_action('dynamic parallel', _('Dynamic parallel')) firstvert=wolfvertex(0.,0.) self.active_vector.add_vertex(firstvert) self.mapviewer.mimicme() self.active_zone.reset_listogl()
[docs] def Oninsert(self, event:wx.MouseEvent): """ Insertion de vertex dans le vecteur courant """ if self.wx_exists: if self.verify_activevec(): return self.mapviewer.start_action('insert vertices', _('Insert vertices')) self.mapviewer.mimicme() self.active_zone.reset_listogl()
# def OnPlotIndices(self, event:wx.MouseEvent): # """ # Plot the indices of the active vector in the mapviewer # """ # if self.wx_exists: # if self.verify_activevec(): # return # self.mapviewer._force_to_plot_indices = True # def OnPlotLengths(self, event:wx.MouseEvent): # """ # Plot the lengths of the active vector in the mapviewer # """ # if self.wx_exists: # if self.verify_activevec(): # return # self.mapviewer._force_to_plot_lengths = True
[docs] def Onzoom(self, event:wx.MouseEvent): """ Zoom sur le vecteur actif dans le mapviewer """ if self.wx_exists: if self.verify_activevec(): return self.mapviewer.zoomon_activevector()
[docs] def Onzoomvertex(self, event:wx.MouseEvent): """ Zoom sur le vertex actif dans le mapviewer """ if self.wx_exists: if self.verify_activevec(): return self.mapviewer.zoomon_active_vertex()
[docs] def OnMove(self, event:wx.MouseEvent): """ Déplacement du vecteur actif """ if self.wx_exists: if self.verify_activevec(): return self.mapviewer.start_action('move vector', _('Move vector')) self.active_vector.set_cache() self.mapviewer.mimicme()
[docs] def OnMoveZone(self, event:wx.MouseEvent): """ Déplacement de la zone active """ if self.wx_exists: if self.verify_activezone(): return self.mapviewer.start_action('move zone', _('Move zone')) self.active_zone.set_cache() self.mapviewer.mimicme()
[docs] def OnRotate(self, event:wx.MouseEvent): """ Rotation du vecteur actif """ if self.wx_exists: if self.verify_activevec(): return self.mapviewer.start_action('rotate vector', _('Rotate vector')) self.active_vector.set_cache() self.mapviewer.mimicme()
[docs] def OnRotateZone(self, event:wx.MouseEvent): """ Rotation de la zone active """ if self.wx_exists: if self.verify_activezone(): return self.mapviewer.start_action('rotate zone', _('Rotate zone')) self.active_zone.set_cache() self.mapviewer.mimicme()
# ================================================================ # Geometric operations # ================================================================
[docs] def reverse(self, inplace:bool=True) -> 'Zones': """Reverse the order of vertices in all vectors. :param inplace: If True, modify in place; otherwise return a copy. :return: The reversed Zones object. """ if inplace: for curzone in self.myzones: curzone.reverse(inplace=True) self.reset_listogl() return self else: new_obj = self.copy() for curzone in new_obj.myzones: curzone.reverse(inplace=True) return new_obj
[docs] def OnReverse(self, event:wx.MouseEvent): """ Renverse le vecteur courant """ if self.wx_exists: # N'est pas à strictement parlé dépendant de wx mais n'a de sens # que si le mapviewer est défini --> si un GUI wx existe if self.verify_activevec(): return self.active_vector.reverse() self.fill_structure() self.active_vector._on_vertices_changed()
[docs] def Onsimplify(self, event:wx.MouseEvent): """ Simplify the active vector using the Douglas-Peucker algorithm """ if self.verify_activevec(): return tolerance = 1.0 if self.wx_exists: dlg = wx.TextEntryDialog(None, _('Tolerance ?'), value='1.0') ret = dlg.ShowModal() tolerance = dlg.GetValue() dlg.Destroy() try: tolerance = float(tolerance) except: logging.warning(_('Bad value -- Retry !')) return new_ls = self.active_vector.linestring.simplify(tolerance, preserve_topology=True) xy = new_ls.xy # shape (2, n) xy = np.array(xy).T # shape (n, 2) if len(xy) == 0: logging.warning(_('No points to add')) return tmp = self.active_vector.deepcopy().linestring self.active_vector.reset() for x, y in xy: pt = Point(x, y) self.active_vector.add_vertex(wolfvertex(x, y, tmp.interpolate(tmp.project(pt)).z)) self.xls_active_vector() self.active_vector._on_vertices_changed()
[docs] def OnAddPar(self, event:wx.MouseEvent): """ Ajout d'une parallèle au vecteur courant via le bouton adhoc """ if self.wx_exists: if self.verify_activevec(): return dlg = wx.TextEntryDialog(None,_('Normal distance ? \nd > 0 is right \n d < 0 is left'),value='0.0') ret=dlg.ShowModal() dist=dlg.GetValue() dlg.Destroy() try: dist = float(dist) except: logging.warning(_('Bad value -- Retry !')) return self.active_zone.add_parallel(dist) self.fill_structure() self.find_minmax(True) self.expand_tree(self.active_zone) self.active_zone.reset_listogl()
[docs] def Onsascending(self, e:wx.MouseEvent): """ S'assure que les points sont ordonnés avec une distance 2D croissante Retourne un message avec les valeurs modifiées le cas échéant """ if self.wx_exists: if self.verify_activevec(): return correct,wherec= self.active_vector.verify_s_ascending() self.xls_active_vector() if correct: msg=_('Modification on indices :\n') for curi in wherec: msg+= str(curi)+'<-->'+str(curi+1)+'\n' dlg=wx.MessageDialog(None,msg) dlg.ShowModal() dlg.Destroy()
[docs] def Onbuffer(self, e:wx.MouseEvent): """ Create a buffer around the currently activated vector. The buffer replaces the active vector in the same zone.""" if self.wx_exists: if self.verify_activevec(): return dlg = wx.TextEntryDialog(None, _('Buffer distance ?'), value='5.0') ret = dlg.ShowModal() dist = dlg.GetValue() dlg.Destroy() try: dist = float(dist) except: logging.warning(_('Bad value -- Retry !')) return if dist <= 0: logging.warning(_('Buffer distance must be > 0 -- Retry !')) return if self.active_vector.nbvertices == 1: self.active_vector.myvertices = self.active_vector.myvertices * 3 logging.warning(_('The active vector has only one vertex. It will be duplicated to create a buffer.')) if self.active_vector.nbvertices == 2: self.active_vector.myvertices = self.active_vector.myvertices + [self.active_vector.myvertices[0]] logging.warning(_('The active vector has only two vertices. The first one will be duplicated to create a buffer.')) self.active_vector.buffer(dist) self.active_vector._on_vertices_changed()
[docs] def Onsplit(self, event:wx.MouseEvent): """ Split le vecteur courant selon un pas spatial déterminé """ if self.wx_exists: if self.verify_activevec(): return dlg=wx.NumberEntryDialog(None,_('What is the desired longitudinal size [cm] ?'),'ds','ds size',100,1,100000) ret=dlg.ShowModal() if ret==wx.ID_CANCEL: dlg.Destroy() return ds=float(dlg.GetValue())/100. dlg.Destroy() self.active_vector.split(ds)
[docs] def Onsurface(self, e:wx.MouseEvent): """ Calcul de la surface du vecteur actif """ if self.verify_activevec(): return dlg = wx.MessageDialog(None, _('The surface of the active vector is : {} m²'.format(self.active_vector.surface)), style = wx.OK) dlg.ShowModal() dlg.Destroy()
# ================================================================ # Polygon / Triangulation operations # ================================================================
[docs] def Oncreatepolygons(self, event:wx.MouseEvent): """ Création de polygones depuis des paralèles contenues dans la zone active """ if self.active_zone is None: logging.warning(_('No active zone - Nothing to do !')) return if self.wx_exists: curz = self.active_zone if curz.nbvectors!=3: logging.warning(_('The active zone must contain 3 vectors and only 3')) return self.active_zone.myvectors[1].update_lengths() poly_dlg = wx.Dialog(None, title=_('Polygons from parallels options'), size=(400, 350)) poly_dlg.SetBackgroundColour(wx.Colour(240, 240, 240)) poly_sizer = wx.BoxSizer(wx.VERTICAL) poly_sizer.Add(wx.StaticText(poly_dlg, label=_('Polygons from parallels options')), 0, wx.ALL | wx.CENTER, 5) poly_sizer.Add(wx.StaticText(poly_dlg, label=_('This will create polygons from the parallels in the active zone')), 0, wx.ALL | wx.CENTER, 5) poly_sizer.Add(wx.StaticText(poly_dlg, label=_('Please enter the parameters below:')), 0, wx.ALL | wx.CENTER, 5) poly_sizer.Add(wx.StaticText(poly_dlg, label=_('Longitudinal size [cm]:')), 0, wx.ALL | wx.LEFT, 5) ds_text = wx.TextCtrl(poly_dlg, value='5000') # Default poly_sizer.Add(ds_text, 0, wx.ALL | wx.EXPAND, 5) poly_sizer.Add(wx.StaticText(poly_dlg, label=_('How many polygons? \n\n 1 = one large polygon from left to right\n 2 = two polygons - one left and one right')), 0, wx.ALL | wx.LEFT, 5) nb_text = wx.TextCtrl(poly_dlg, value='1') # Default poly_sizer.Add(nb_text, 0, wx.ALL | wx.EXPAND, 5) ok_button = wx.Button(poly_dlg, label=_('OK')) ok_button.Bind(wx.EVT_BUTTON, lambda evt: self._OnCreatePolygons(evt, ds_text, nb_text, poly_dlg)) poly_sizer.Add(ok_button, 0, wx.ALL | wx.CENTER, 5) poly_dlg.SetSizer(poly_sizer) poly_dlg.Layout() poly_dlg.CentreOnParent() poly_dlg.ShowModal()
# dlg=wx.NumberEntryDialog(None,_('What is the desired longitudinal size [cm] ?'),'ds','ds size',500,1,10000) # ret=dlg.ShowModal() # if ret==wx.ID_CANCEL: # dlg.Destroy() # return # ds=float(dlg.GetValue())/100. # dlg.Destroy() # dlg=wx.NumberEntryDialog(None,_('How many polygons ? \n\n 1 = one large polygon from left to right\n 2 = two polygons - one left and one right'),'Number','Polygons',1,1,2) # ret=dlg.ShowModal() # if ret==wx.ID_CANCEL: # dlg.Destroy() # return # nb=int(dlg.GetValue()) # dlg.Destroy()
[docs] def _OnCreatePolygons(self, event:wx.MouseEvent, ds_text:wx.TextCtrl, nb_text:wx.TextCtrl, option_dialog:wx.Dialog): """ Handle the creation of polygons based on user input from the dialog. """ try: ds = float(ds_text.GetValue()) / 100.0 # Convert cm to nb = int(nb_text.GetValue()) # Number of polygons if ds <= 0: wx.MessageBox(_('Please enter a valid distance greater than 0.'), _('Input Error'), wx.OK | wx.ICON_ERROR) return if ds > self.active_zone.myvectors[1].length2D: wx.MessageBox(_('The distance must be less than the length of the center vector in the active zone.'), _('Input Error'), wx.OK | wx.ICON_ERROR) return if nb < 1 or nb > 2: wx.MessageBox(_('Please enter a valid number of polygons (1 or 2).'), _('Input Error'), wx.OK | wx.ICON_ERROR) return except ValueError: wx.MessageBox(_('Please enter valid numeric values for all fields.'), _('Input Error'), wx.OK | wx.ICON_ERROR) return try: self.active_zone.create_polygon_from_parallel(ds,nb) except Exception as e: logging.error(_('Error during polygon creation: {}').format(str(e))) if self.get_mapviewer() is not None: self.get_mapviewer().Paint() option_dialog.Destroy()
[docs] def Oncreateslidingpoly(self, event:wx.MouseEvent): """ Create sliding polygons from a support vector """ if self.active_zone is None: logging.warning(_('No active zone - Nothing to do !')) return if self.active_zone.nbvectors!=1: logging.error(_('The active zone must contain 1 vector and only 1')) dlg = wx.MessageDialog(None,_('The active zone must contain 1 vector and only 1'),style=wx.OK) dlg.ShowModal() dlg.Destroy() return option_dialog = wx.Dialog(None, title=_('Sliding polygons options'), size=(450, 520)) option_dialog.SetBackgroundColour(wx.Colour(240, 240, 240)) option_sizer = wx.BoxSizer(wx.VERTICAL) option_sizer.Add(wx.StaticText(option_dialog, label=_('Sliding polygons options')), 0, wx.ALL | wx.CENTER, 5) option_sizer.Add(wx.StaticText(option_dialog, label=_('This will create sliding polygons from the active vector in the active zone')), 0, wx.ALL | wx.CENTER, 5) option_sizer.Add(wx.StaticText(option_dialog, label=_('Please enter the parameters below:')), 0, wx.ALL | wx.CENTER, 5) option_sizer.Add(wx.StaticText(option_dialog, label=_('Longitudinal size [cm]:')), 0, wx.ALL | wx.LEFT, 5) ds_text = wx.TextCtrl(option_dialog, value='5000') # Default value in cm option_sizer.Add(ds_text, 0, wx.ALL | wx.EXPAND, 5) option_sizer.Add(wx.StaticText(option_dialog, label=_('Sliding length [cm]:')), 0, wx.ALL | wx.LEFT, 5) sliding_text = wx.TextCtrl(option_dialog, value='5000') # Default value option_sizer.Add(sliding_text, 0, wx.ALL | wx.EXPAND, 5) option_sizer.Add(wx.StaticText(option_dialog, label=_('Farthest parallel [cm]:')), 0, wx.ALL | wx.LEFT, 5) farthest_text = wx.TextCtrl(option_dialog, value='10000') # Default option_sizer.Add(farthest_text, 0, wx.ALL | wx.EXPAND, 5) option_sizer.Add(wx.StaticText(option_dialog, label=_('Parallel interval [cm]:')), 0, wx.ALL | wx.LEFT, 5) interval_text = wx.TextCtrl(option_dialog, value='1000') # Default option_sizer.Add(interval_text, 0, wx.ALL | wx.EXPAND, 5) intersect_sizer = wx.BoxSizer(wx.HORIZONTAL) inter_checkbox = wx.CheckBox(option_dialog, label=_('Use intersect zone if available')) inter_checkbox.SetValue(True) # Default to True offset_text = wx.TextCtrl(option_dialog, value='10') # Default offset value intersect_sizer.Add(inter_checkbox, 0, wx.ALL | wx.LEFT, 5) intersect_sizer.Add(wx.StaticText(option_dialog, label=_('Offset [cm]:')), 0, wx.ALL | wx.LEFT, 5) intersect_sizer.Add(offset_text, 0, wx.ALL | wx.EXPAND, 5) option_sizer.Add(wx.StaticText(option_dialog, label=_('If you have a zone named "intersect", you can use it to constrain the polygons.\nWhen constraint vectors are present, they cannot intersect the central vector.\nLikewise, they must be drawn moving away from the central vector.')), 0, wx.ALL | wx.CENTER, 5) option_sizer.Add(intersect_sizer, 0, wx.ALL | wx.LEFT, 5) separate_checkbox = wx.CheckBox(option_dialog, label=_('Separate left and right polygons')) separate_checkbox.SetValue(False) # Default to False option_sizer.Add(separate_checkbox, 0, wx.ALL | wx.LEFT, 5) ok_button = wx.Button(option_dialog, label=_('OK')) ok_button.Bind(wx.EVT_BUTTON, lambda evt: self._OnCreateSlidingPolygon(evt, ds_text, sliding_text, farthest_text, interval_text, inter_checkbox, offset_text, separate_checkbox, option_dialog)) option_sizer.Add(ok_button, 0, wx.ALL | wx.CENTER, 5) option_dialog.SetSizer(option_sizer) option_dialog.Layout() option_dialog.Centre() try: option_dialog.ShowModal() except: logging.error(_('Error during sliding polygons calculation.')) option_dialog.Destroy()
[docs] def _OnCreateSlidingPolygon(self, event, ds_text, sliding_text, farthest_text, interval_text, inter_checkbox, offset_text, separate_checkbox, option_dialog:wx.Dialog): """ Handle the creation of sliding polygons based on user input from the dialog. """ try: ds = float(ds_text.GetValue()) / 100.0 # Convert cm to m sliding = float(sliding_text.GetValue()) / 100.0 # Convert cm farthest = float(farthest_text.GetValue()) / 100.0 # Convert cm to m interval = float(interval_text.GetValue()) / 100.0 # Convert cm to intersect = inter_checkbox.GetValue() # Boolean value separate = separate_checkbox.GetValue() # Boolean value offset = float(offset_text.GetValue())/100.0 # Offset value in m except ValueError: wx.MessageBox(_('Please enter valid numeric values for all fields.'), _('Input Error'), wx.OK | wx.ICON_ERROR) return if separate: howmany = 2 # Separate left and right polygons else: howmany = 1 # Single polygon # #dialog box for length, sliding length, farthest parallel and parallel interval # dlg=wx.NumberEntryDialog(None,_('What is the desired longitudinal size [cm] ?'),'ds','ds size',5000,1,100000) # ret=dlg.ShowModal() # if ret==wx.ID_CANCEL: # dlg.Destroy() # return # ds=float(dlg.GetValue())/100. # dlg.Destroy() # dlg=wx.NumberEntryDialog(None,_('What is the desired sliding length [cm] ?'),'sliding','sliding size',5000,1,100000) # ret=dlg.ShowModal() # if ret==wx.ID_CANCEL: # dlg.Destroy() # return # sliding=float(dlg.GetValue())/100. # dlg.Destroy() # dlg=wx.NumberEntryDialog(None,_('What is the desired farthest parallel [cm] ?'),'farthest','farthest size',10000,1,100000) # ret=dlg.ShowModal() # if ret==wx.ID_CANCEL: # dlg.Destroy() # return # farthest=float(dlg.GetValue())/100. # dlg.Destroy() # dlg=wx.NumberEntryDialog(None,_('What is the desired parallel interval [cm] ?'),'interval','interval size',int(farthest*10.),1,int(farthest*100.)) # ret=dlg.ShowModal() # if ret==wx.ID_CANCEL: # dlg.Destroy() # return # interval=float(dlg.GetValue())/100. # dlg.Destroy() zones_names=[curz.myname.lower() for curz in self.myzones] # if "intersect" in zones_names: # dlg = wx.MessageDialog(None,_('Do you want to use the intersect zone ?'),style=wx.YES_NO) # ret=dlg.ShowModal() # if ret==wx.ID_YES: # inter = True # else: # inter = False # dlg.Destroy() # else: # inter = False inter_zone = None if intersect: if "intersect" in zones_names: inter_zone = self.myzones[zones_names.index("intersect")] # dlg = wx.MessageDialog(None,_('Do you want to separate left and right polygons ?'),style=wx.YES_NO) # ret=dlg.ShowModal() # if ret==wx.ID_YES: # howmany = 2 # else: # howmany = 1 try: self.active_zone.create_sliding_polygon_from_parallel(ds, sliding, farthest, interval, inter_zone, howmany, eps_offset=offset) except: logging.error(_('Error during sliding polygons calculation.')) option_dialog.Close()
[docs] def Oncreate_cs_from_active_zone(self, event:wx.MouseEvent): """ Create a cross-section for each vector from the active zone. """ if self.active_zone is None: logging.warning(_('No active zone - Nothing to do !')) return wa = self.parent.active_array if self.parent is not None else None if wa is None: logging.warning(_('No active array - Cannot create cross-sections !')) return new_zone = self.active_zone.create_cs(wa) if new_zone is not None: self.add_zone(new_zone, forceparent= True) else: logging.warning(_('Cross-section creation failed !')) return self.fill_structure()
[docs] def Oncreatebin(self,event:wx.MouseEvent): """ Création d'un canal sur base de 3 parallèles """ if self.wx_exists: if self.active_zone is None: return curz = self.active_zone if curz.nbvectors!=3: logging.warning(_('The active zone must contain 3 vectors and only 3')) dlg=wx.MessageDialog(None,_('Do you want to copy the center elevations to the parallel sides ?'),style=wx.YES_NO) ret=dlg.ShowModal() if ret==wx.ID_YES: left:LineString center:LineString right:LineString left = curz.myvectors[0].asshapely_ls() center = curz.myvectors[1].asshapely_ls() right = curz.myvectors[2].asshapely_ls() for idx,coord in enumerate(left.coords): xy = Point(coord[0],coord[1]) curs = left.project(xy,True) curz.myvectors[0].myvertices[idx].z=center.interpolate(curs,True).z for idx,coord in enumerate(right.coords): xy = Point(coord[0],coord[1]) curs = right.project(xy,True) curz.myvectors[2].myvertices[idx].z=center.interpolate(curs,True).z dlg.Destroy() left:LineString center:LineString right:LineString left = curz.myvectors[0].asshapely_ls() center = curz.myvectors[1].asshapely_ls() right = curz.myvectors[2].asshapely_ls() dlg=wx.NumberEntryDialog(None,_('What is the desired lateral size [cm] ?'),'ds','ds size',500,1,10000) ret=dlg.ShowModal() if ret==wx.ID_CANCEL: dlg.Destroy() return addedz=float(dlg.GetValue())/100. dlg.Destroy() dlg=wx.NumberEntryDialog(None,_('How many points along center polyline ?')+'\n'+ _('Length size is {} meters').format(center.length),'nb','dl size',100,1,10000) ret=dlg.ShowModal() if ret==wx.ID_CANCEL: dlg.Destroy() return nb=int(dlg.GetValue()) dlg.Destroy() s = np.linspace(0.,1.,num=nb,endpoint=True) points=np.zeros((5*nb,3),dtype=np.float32) decal=0 for curs in s: ptl=left.interpolate(curs,True) ptc=center.interpolate(curs,True) ptr=right.interpolate(curs,True) points[0+decal,:] = np.asarray([ptl.coords[0][0],ptl.coords[0][1],ptl.coords[0][2]]) points[1+decal,:] = np.asarray([ptl.coords[0][0],ptl.coords[0][1],ptl.coords[0][2]]) points[2+decal,:] = np.asarray([ptc.coords[0][0],ptc.coords[0][1],ptc.coords[0][2]]) points[3+decal,:] = np.asarray([ptr.coords[0][0],ptr.coords[0][1],ptr.coords[0][2]]) points[4+decal,:] = np.asarray([ptr.coords[0][0],ptr.coords[0][1],ptr.coords[0][2]]) points[0+decal,2] += addedz points[4+decal,2] += addedz decal+=5 decal=0 triangles=[] nbpts=5 triangles.append([[i+decal,i+decal+1,i+decal+nbpts] for i in range(nbpts-1)]) triangles.append([[i+decal+nbpts,i+decal+1,i+decal+nbpts+1] for i in range(nbpts-1)]) for k in range(1,nb-1): decal=k*nbpts triangles.append([ [i+decal,i+decal+1,i+decal+nbpts] for i in range(nbpts-1)]) triangles.append([ [i+decal+nbpts,i+decal+1,i+decal+nbpts+1] for i in range(nbpts-1)]) triangles=np.asarray(triangles,dtype=np.uint32).reshape([(2*nbpts-2)*(nb-1),3]) from ._triangulation import Triangulation mytri=Triangulation(pts=points,tri=triangles) mytri.find_minmax(True) fn=mytri.export_to_gltf() dlg=wx.MessageDialog(None,_('Do you want to add triangulation to parent gui ?'),style=wx.YES_NO) ret=dlg.ShowModal() if ret==wx.ID_YES: self.mapviewer.add_object('triangulation',newobj=mytri) self.mapviewer.Refresh() dlg.Destroy()
[docs] def Oncreatemultibin(self, event:wx.MouseEvent): """ Création d'une triangulation sur base de plusieurs vecteurs """ if self.wx_exists: if self.active_zone is None: return myzone = self.active_zone if myzone.nbvectors<2: dlg = wx.MessageDialog(None,_('Not enough vectors/polylines in the active zone -- Add element and retry !!')) ret = dlg.ShowModal() dlg.Destroy() return mytri = myzone.create_multibin() self.mapviewer.add_object('triangulation',newobj=mytri) self.mapviewer.Refresh()
[docs] def Oncreatetricrosssection(self, event:wx.MouseEvent): """ Create a tringulation like cross-sections and support vectors """ if self.wx_exists: if self.get_mapviewer() is None: logging.warning(_('No mapviewer found')) return if self.active_zone is None: logging.warning(_('No active zone found')) return # dlg for ds value dlg = wx.NumberEntryDialog(None,_('What is the desired size [cm] ?'),'ds','ds size',50,1,10000) ret = dlg.ShowModal() if ret == wx.ID_CANCEL: dlg.Destroy() return ds = float(dlg.GetValue())/100. dlg.Destroy() myzone = self.active_zone mapviewer = self.get_mapviewer() mapviewer.set_interp_cs(myzone.create_tri_crosssection(ds), True)
[docs] def OnconstrainedDelaunay(self, event:wx.MouseEvent): """ Create a constrained Delaunay triangulation from the active zone """ if self.wx_exists: if self.active_zone is None: return myzone = self.active_zone mytri = myzone.create_constrainedDelaunay() self.mapviewer.add_object('triangulation',newobj=mytri) self.mapviewer.Refresh()
[docs] def Oncreatemultibin_project(self, event:wx.MouseEvent): """ Création d'une triangulation sur base de plusieurs vecteurs Les sommets sont recherchés par projection d'un vecteur sur l'autre """ if self.wx_exists: if self.active_zone is None: return myzone = self.active_zone if myzone.nbvectors<2: dlg = wx.MessageDialog(None,_('Not enough vectors/polylines in the active zone -- Add element and retry !!')) ret = dlg.ShowModal() dlg.Destroy() return mytri = myzone.createmultibin_proj() self.mapviewer.add_object('triangulation',newobj=mytri) self.mapviewer.Refresh()
# ================================================================ # Data access / manipulation # ================================================================
[docs] def get_xy_from_sz(self, event: wx.Event): """ Add vertices and their respectives xy coordinates from s and Z entries in the xls grid: - NB: The coordinates of the initial point s= 0 and one other points should be explicitly given in the xls grid. """ if self.wx_exists: if self.verify_activevec(): return curv = self.active_vector n_rows = self.xls.GetNumberRows() if n_rows < 2: logging.warning(_('You need at least 2 points to interpolate the XY coordinates from the SZ coordinates')) return # Getting the 2 first XY coordinates X = [] Y = [] z_row = 1 #Starting from the second row because the first one is the initial point # First row coordinates x1 = self.xls.GetCellValue(0,0) y1 = self.xls.GetCellValue(0,1) if x1 != '' and y1 != '': X.append(float(x1)) Y.append(float(y1)) else: raise Exception('Encode the coordinates of the initial point (S = 0 --> first point)') # Coordinates of the second points while z_row < n_rows: if len(X) < 2 and len(Y) < 2: x2 = self.xls.GetCellValue(z_row,0) y2 = self.xls.GetCellValue(z_row,1) if x2 != '' and y2 != '': X.append(float(x2)) Y.append(float(y2)) z_row += 1 else: break xy1 = np.array([X[0], Y[0]]) xy2 = np.array([X[1], Y[1]]) # xy2 /= np.linalg.norm(xy2 - xy1) # Collection of sz coordinates row = 0 SZ = [] while row < n_rows: s = self.xls.GetCellValue(row,4) z = self.xls.GetCellValue(row,2) if z=='': z=0. if s != '': SZ.append((s,z)) row += 1 elif s=='': #FIXME logging msg to notify the user a point is missing break else: raise Exception (_("Recheck your data inputs")) break sz = np.asarray(SZ,dtype='float64') # FIXME The type is required otherwise type == <U self.update_from_sz_direction(xy1, xy2, sz) # update of the xls grid for k in range(curv.nbvertices ): self.xls.SetCellValue(k,0,str(curv.myvertices[k].x)) self.xls.SetCellValue(k,1,str(curv.myvertices[k].y))
[docs] def Ongetvalues(self, e:wx.MouseEvent): """ Récupère les valeurs dans une matrice --> soit la matrice courante --> soit la matrice active de l'interface parent """ if self.verify_activevec(): return try: curarray = self.parent.active_array if curarray is not None: self.active_vector.get_values_on_vertices(curarray) self.active_vector.fillgrid(self.xls) else: logging.info(_('Please activate the desired array')) except: raise Warning(_('Not supported in the current parent -- see PyVertexVectors in Ongetvalues function'))
[docs] def Ongetvalueslinked(self, e:wx.MouseEvent): """ Récupération des valeurs sous toutes les matrices liées pour le vecteur actif Crée une nouvelle zone contenant une copie du vecteur Le nombre de vertices est conservé """ if self.parent is not None: if self.verify_activevec(): return try: linked = self.parent.get_linked_arrays() if len(linked)>0: newzone:zone newzone = self.active_vector.get_values_linked(linked, False) self.add_zone(newzone) newzone.parent=self self.fill_structure() except: raise Warning(_('Not supported in the current parent -- see PyVertexVectors in Ongetvalueslinked function'))
[docs] def Ongetvalueslinkedandref(self, e:wx.MouseEvent): """ Récupération des valeurs sous toutes les matrices liées pour le vecteur actif Crée une nouvelle zone contenant une copie du vecteur. Le nombre de vertices est adapté pour correspondre au mieux à la matrice de liée et ne pas perdre, si possible, d'information. """ if self.parent is not None: if self.verify_activevec(): logging.warning(_('No active vector - Nothing to do !')) return linked=self.parent.get_linked_arrays() if len(linked)>0: newzone:zone newzone=self.active_vector.get_values_linked(linked) self.add_zone(newzone) newzone.parent=self self.fill_structure() else: logging.warning(_('No parent found -- This function is not supported without a parent -- see PyVertexVectors in Ongetvalueslinkedandref function'))
[docs] def Onevaluates(self, event:wx.MouseEvent): """ Calcule la position curviligne du vecteur courant Le calcul peut être mené en 2D ou en 3D Remplissage du tableur dans la 5ème colonne """ if self.wx_exists: if self.verify_activevec(): return curv = self.active_vector curv.update_lengths() dlg = wx.SingleChoiceDialog(None, "Which mode?", "How to evaluate lengths?", ['2D','3D']) ret=dlg.ShowModal() if ret==wx.ID_CANCEL: dlg.Destroy() return method=dlg.GetStringSelection() dlg.Destroy() self.xls.SetCellValue(0,4,'0.0') s=0. if method=='2D': for k in range(curv.nbvertices-1): s+=curv._lengthparts2D[k] self.xls.SetCellValue(k+1,4,str(s)) else: for k in range(curv.nbvertices-1): s+=curv._lengthparts3D[k] self.xls.SetCellValue(k+1,4,str(s))
[docs] def Onupdate_from_sz_support(self, event:wx.MouseEvent): """ Update the active vector from the sz values in the xls grid """ if self.active_vector is None: logging.info(_('No active vector -- Nothing to do')) return sz = [] # Getting s values and Z values from the xls grid # s in column 4 and z in column 2 # The first row is the header nbrows = self.xls.GetNumberRows() if self.xls.GetCellValue(nbrows-1,4) != '': self.xls.AppendRows(1) i = 0 while self.xls.GetCellValue(i,4) != '': s = self.xls.GetCellValue(i,4) z = self.xls.GetCellValue(i,2) try: s = float(s) except: logging.error(_('Error during update from sz support - check your s data and types (only float)')) return try: if z == '': logging.warning(_('No z value -- setting to 0.0')) z = 0.0 else: z = float(z) except: logging.error(_('Error during update from sz support - check your z data and types (only float)')) return sz.append((s, z)) i += 1 if len(sz) == 0: logging.warning(_('No data to update -- Please set s in column "s curvi" (5th) and z in column Z (3th)')) return logging.info(f'Number of points: {len(sz)}') vec_sz = np.array(sz) memory_xyz = [] i = 0 while self.xls.GetCellValue(i,0) != '': memory_xyz.append((float(self.xls.GetCellValue(i,0)), float(self.xls.GetCellValue(i,1)), float(self.xls.GetCellValue(i,2)))) i += 1 try: self.update_from_sz_support(vec=self.active_vector, sz=vec_sz) # update of the xls grid for k in range(self.active_vector.nbvertices ): self.xls.SetCellValue(k,0,str(self.active_vector.myvertices[k].x)) self.xls.SetCellValue(k,1,str(self.active_vector.myvertices[k].y)) except: logging.error(_('Error during update from sz support - check your data')) logging.info(_('Resetting the active vector to its original state')) self.active_vector.myvertices = [] for cur in memory_xyz: self.active_vector.add_vertex(wolfvertex(cur[0], cur[1], cur[2])) self.active_vector._on_vertices_changed() self.active_vector.update_lengths() self.active_vector.find_minmax(True) for k in range(self.active_vector.nbvertices ): self.xls.SetCellValue(k,0,str(self.active_vector.myvertices[k].x)) self.xls.SetCellValue(k,1,str(self.active_vector.myvertices[k].y))
[docs] def update_from_sz_support(self, vec: vector, sz:np.ndarray, dialog_box = True, method:Literal['2D', '3D'] = '3D'): """ Update the coordinates from the support vector and a sz array. The support vector is used to interpolate the z values, at the s values. It must long enough to cover the s values. :param vec: The vector to update. It is also the support vector. :param sz: The sz array to use for the update :param dialog_box: If True, a dialog box will be shown to choose the method :param method: The method to use for the interpolation. '2D' or '3D' """ if sz.shape[0] ==0: logging.warning(_('No data to update')) return support_vec = vec.deepcopy_vector() support_vec.update_lengths() if support_vec.length2D is None or support_vec.length3D is None: logging.warning(_('The support vector must be updated before updating the active vector')) return if dialog_box: dlg = wx.SingleChoiceDialog(None, "Which mode?", "How to evaluate lengths?", ['2D','3D']) ret=dlg.ShowModal() if ret==wx.ID_CANCEL: dlg.Destroy() return method=dlg.GetStringSelection() dlg.Destroy() # else: # method = '2D' if method not in ['2D', '3D']: logging.warning(_('Method not supported -- only 2D and 3D are supported')) return if method == '2D': if sz[-1,0] > support_vec.length2D: logging.warning(_('The last point is beyond the vector length. You must add more points !')) return else: if sz[-1,0] > support_vec.length3D: logging.warning(_('The last point is beyond the vector length. You must add more points !')) return vec.myvertices = [] for s,z in sz: new_vertex = support_vec.interpolate(s, method == method, adim= False) new_vertex.z = z vec.add_vertex(new_vertex) vec._on_vertices_changed() vec.update_lengths() self.find_minmax(True)
[docs] def evaluate_s(self, vec: vector =None, dialog_box = True): """Compute the curvilinear abscissa for a vector. :param vec: The vector to evaluate. :param dialog_box: If True, show a wx dialog for 2-D/3-D choice. """ curv = vec curv.update_lengths() if dialog_box: dlg = wx.SingleChoiceDialog(None, "Which mode?", "How to evaluate lengths?", ['2D','3D']) ret=dlg.ShowModal() if ret==wx.ID_CANCEL: dlg.Destroy() return method=dlg.GetStringSelection() dlg.Destroy() else: method = '2D' self.xls.SetCellValue(0,4,'0.0') s=0. if curv.nbvertices > 0: if method=='2D': for k in range(curv.nbvertices-1): s+=curv._lengthparts2D[k] self.xls.SetCellValue(k+1,4,str(s)) else: for k in range(curv.nbvertices-1): s+=curv._lengthparts3D[k] self.xls.SetCellValue(k+1,4,str(s))
[docs] def Oninterpvec(self, event:wx.MouseEvent): """ Interpole les valeurs Z de l'éditeur sur base des seules valeurs connues, càd autre que vide ou -99999 """ if self.verify_activevec(): return curv = self.active_vector s=[] z=[] for k in range(curv.nbvertices): zgrid = self.xls.GetCellValue(k,2) sgrid = self.xls.GetCellValue(k,4) if zgrid!='' and float(zgrid)!=-99999.: z.append(float(zgrid)) s.append(float(sgrid)) if len(z)==0: return f = interp1d(s,z) for k in range(curv.nbvertices): zgrid = self.xls.GetCellValue(k,2) sgrid = self.xls.GetCellValue(k,4) if zgrid=='' or float(zgrid)==-99999.: z = f(float(sgrid)) self.xls.SetCellValue(k,2,str(z))
# ================================================================ # Utility / helpers # ================================================================
[docs] def verify_activevec(self): """ Vérifie si un vecteur actif est défini, si 'None' affiche un message Return : True if self.active_vector is None False otherwise """ if self.active_vector is None: if self.wx_exists: msg='' msg+=_('Active vector is None\n') msg+=_('\n') msg+=_('Retry !\n') wx.MessageBox(msg) else: logging.warning(_('Active vector is None - Retry !')) return True elif self.mapviewer is not None: if self.mapviewer.active_vector is not self.active_vector: logging.warning(_('Active vector in mapviewer is not well defined - Retry !')) return True return False
[docs] def verify_activezone(self): """ Vérifie si une zone active est définie, si 'None' affiche un message Return : True if self.active_zone is None False otherwise """ if self.active_zone is None: if self.wx_exists: msg='' msg+=_('Active zone is None\n') msg+=_('\n') msg+=_('Retry !\n') wx.MessageBox(msg) return True return False