# Librairies
"""
Author: HECE - University of Liege, Pierre Archambeau
Date: 2024
Copyright (c) 2024 University of Liege. All rights reserved.
This script and its content are protected by copyright law. Unauthorized
copying or distribution of this file, via any medium, is strictly prohibited.
"""
from cmath import isnan
import numpy as np
import wx
import wx.lib.agw.aui as aui
import copy
from shapely.geometry import LineString,Point
from matplotlib.figure import Figure
from matplotlib.axis import Axis
import matplotlib.pyplot as plt
from matplotlib.backends.backend_wxagg import FigureCanvasWxAgg as FigureCanvas, NavigationToolbar2WxAgg as NavigationToolbar
from wx.grid import Grid
from PIL import Image
import logging
from .PyVertexvectors import Zones, vector, zone
from .PyCrosssections import profile,crosssections
from .PyVertexvectors import Zones,zone,vector
from .PyTranslate import _
from .PyVertex import getIfromRGB,getRGBfromI, wolfvertex
from .CpGrid import CpGrid
from .wolfresults_2D import Wolfresults_2D
from .PyWMS import getWalonmap
[docs]
class ProfilePanel(wx.Panel):
"""
A wx.panel on which the matplolib figure is displayed.
"""
def __init__(self, parent, id =-1, dpi= None, toolbar = True, **kwargs):
# test if wx App is running
# if not, wx commands must be discarded
self.wx_exists = wx.App.Get() is not None
#Figure
if self.wx_exists:
self.figure = Figure(dpi = dpi, figsize=(8,4))
else:
self.figure = plt.figure(dpi = dpi, figsize=(15,10)) # FIXME not exactly the same than mplfig.Figure, probably rendering machine
self.ax_cs_real= None
self.ax_img= None
self.ax_cs_anam = None
self.ax_dis= None
self.ax_hsw= None
self.add_ax()
# self.Layout()
if self.wx_exists:
# impossible to initialize wx.Panel if wx.App is not running
super().__init__(parent,id =id, **kwargs)
self.canvas = FigureCanvas(self,1, self.figure) #<-- wx.canvas for the matplolib figure
self.canvas.ClearBackground()
#Sizers
self.sizer = wx.BoxSizer(wx.VERTICAL)
self.sizerfig = wx.BoxSizer(wx.HORIZONTAL)
self.sizerfig.Add(self.canvas, 1, wx.EXPAND) #wx.EXPAND expands the given object proportions in case the windows is resized.
self.sizer.Add(self.sizerfig, 7, wx.EXPAND)
self.SetSizer(self.sizer)
[docs]
def add_ax(self): #Just in case a 3D projection is implemented
"""
This methods creates the axes of the Matplolib figure either 3d or 2d (defaults = five 2D axes).
"""
self.gs = self.figure.add_gridspec(2,3, wspace =0.35, hspace=0.35) #Utilized to merge the 2 first axes of the figure (next line)
self.ax_cs_real = self.figure.add_subplot(self.gs[0,:2])
self.ax_img = self.figure.add_subplot(233)
self.ax_cs_anam = self.figure.add_subplot(234)
self.ax_dis = self.figure.add_subplot(235)
self.ax_hsw = self.figure.add_subplot(236)
self.ax_img.clear()
self.ax_cs_anam.clear()
self.ax_cs_real.clear()
self.ax_dis.clear()
self.ax_hsw.clear()
return self.ax_cs_real, self.ax_img, self.ax_dis, self.ax_hsw
[docs]
def get_fig_ax(self) -> tuple[Figure, Axis, Axis, Axis, Axis, Axis]:
"""
This method returns the active axes of the matplotlib figure.
"""
return self.figure, self.ax_cs_real, self.ax_cs_anam, self.ax_img, self.ax_dis, self.ax_hsw
# class for plotting all figures at once
[docs]
class PlotCSAll(ProfilePanel):
"""
A class containing the necessary tools to draw the different graphs of a profile (Cross section).
"""
def __init__(self,
parent:aui.AuiNotebook,
id=-1,
dpi=None,
my_cross_sections:crosssections = None,
my_profile:profile=None,
**kwargs):
super().__init__(parent, id, dpi, **kwargs)
self.compare = None
self.grid_vertices = None
self.grid_simulations = None
self.parent = parent # aui.AuiNotebook
self.my_cross_sections = my_cross_sections
self.cs = 1
self.wdepth = 0
self.wlevel = 0
self.minwlevel = 0
self.maxwlevel = 10000000
self.wdis = 0
self.Manning = 0.04
self.Strickler = 1/self.Manning
self.slope = 0.001
self.set_cs(my_profile)
self.reset_models()
self.init_UI()
# self.Layout()
[docs]
def init_UI(self):
"""
Init UI if wx.app is running
Otherwise --> return
"""
if not self.wx_exists:
return
#FIXME (Add a tool box for the figure or each specific axis legend)
#Intialization of the panel tools --> FIXME (add water depth, level, discharges and slope message options)
##sizers
self.sizernextprev = wx.StaticBoxSizer(wx.HORIZONTAL, self, label = _('Cross section (Selection)'))
self.sizerposbank = wx.StaticBoxSizer(wx.HORIZONTAL,self, label = _('Geometric parameters'))
self.sizerparam = wx.StaticBoxSizer(wx.HORIZONTAL,self, label = _('Hydraulic parameters'))
self.sizerparam_box = wx.StaticBoxSizer(wx.HORIZONTAL,self, label = _('Parameters'))
self.sizersimulations = wx.StaticBoxSizer(wx.HORIZONTAL,self, label = _('Additional tools'))
##Buton for changing cross section (FIXME add a Txtcontrol to jump on a specific cross section)
self.cs = 1
self.ButPrev = wx.Button(self, label =_('Previous'))
self.ButNext = wx.Button(self,label =_('Next'))
self.txtselectcs = wx.TextCtrl(self, wx.ID_ANY, str(self.cs), wx.DefaultPosition, wx.DefaultSize, style =wx.TE_CENTER|wx.TE_PROCESS_ENTER, name =_('textselectcs'))
self.sizernextprev.Add(self.ButPrev,1, wx.LEFT|wx.EXPAND)
self.sizernextprev.Add(self.ButNext,1,wx.LEFT|wx.EXPAND)
self.sizernextprev.Add(self.txtselectcs,1,wx.LEFT|wx.EXPAND )
#Tool tips
self.ButPrev.SetToolTip(_("Previous profile if the cross sections are sorted."))
self.ButNext.SetToolTip(_("Next profile if the cross sections are sorted."))
self.txtselectcs.SetToolTip(_("Jump to the inserted profile."))
##General movements
curs = 5000
self.slidergenhor = wx.Slider(self, wx.ID_ANY, curs, 0, 1000, wx.DefaultPosition, wx.DefaultSize, wx.SL_TOP|wx.SL_VALUE_LABEL, name=_('sliderhor'))
self.txthor = wx.TextCtrl(self, wx.ID_ANY, str(curs), wx.DefaultPosition, wx.DefaultSize, style =wx.TE_CENTER|wx.TE_PROCESS_ENTER, name = _('texthor'))
self.slidergenver= wx.Slider(self, wx.ID_ANY, curs, 0, 10000, wx.DefaultPosition, wx.DefaultSize, wx.SL_LEFT|wx.SL_INVERSE|wx.SL_VALUE_LABEL,name=_('sliderver') )
self.txtver = wx.TextCtrl(self, wx.ID_ANY, str(curs), wx.DefaultPosition, wx.DefaultSize, style =wx.TE_CENTER|wx.TE_PROCESS_ENTER, name = _('textver'))
self.sizerslider0= wx.StaticBoxSizer(wx.VERTICAL, self, label="V. mvt (mm)")
self.sizerslider00= wx.StaticBoxSizer(wx.VERTICAL, self, label="H. mvt (mm)")
#Tool tips
self.slidergenhor.SetToolTip(_('This slider moves horizontally the whole current profile.'))
self.slidergenver.SetToolTip(_('This slider moves vertically the whole current profile.'))
self.txthor.SetToolTip(_('The input moves horizontally the whole current profile.'))
self.txtver.SetToolTip(_('The input moves vertically the whole current profile.'))
###Vertical
self.sizerslider0.Add(self.slidergenver,1, wx.ALIGN_CENTER)
self.sizerslider0.Add(self.txtver,0, wx.ALIGN_CENTER)
###Horizontal
self.sizerslider00.Add(self.slidergenhor,1,wx.ALIGN_CENTER)
self.sizerslider00.Add(self.txthor,0,wx.ALIGN_CENTER)
##sliders and Text controls
### From the left side of the screen (1st box) --> Left bank
self.sizerslider1 = wx.StaticBoxSizer(wx.VERTICAL, self, label = _('Left bank (mm)'))
curs = 0
self.sliderleft = wx.Slider(self, wx.ID_ANY, curs,0,10000, wx.DefaultPosition, wx.DefaultSize, wx.SL_TOP|wx.SL_VALUE_LABEL, name=_('sliderleft'))
self.txtleft = wx.TextCtrl(self, wx.ID_ANY, str(curs), wx.DefaultPosition, wx.DefaultSize, style =wx.TE_CENTER|wx.TE_PROCESS_ENTER, name = _('textleft'))
self.sizerslider1.Add(self.sliderleft, 1, wx.ALIGN_CENTER)
self.sizerslider1.Add(self.txtleft, 0, wx.ALIGN_CENTER)
#Tool tips
self.sliderleft.SetToolTip(_('This slider modifies the left bank position on the current profile.'))
self.txtleft.SetToolTip(_('The input modifies the left bank position on the current profile.'))
### From left side of the screen (2nd box) --> river bed
self.sizerslider2 =wx.StaticBoxSizer(wx.VERTICAL, self,label = _('River bed (mm)'))
curs = 0
self.sliderbed = wx.Slider(self, wx.ID_ANY, curs,0,10000, wx.DefaultPosition, wx.DefaultSize, wx.SL_TOP|wx.SL_VALUE_LABEL, name=_('sliderbed'))
self.txtbed = wx.TextCtrl(self, wx.ID_ANY, str(curs), wx.DefaultPosition, wx.DefaultSize, style =wx.TE_CENTER|wx.TE_PROCESS_ENTER, name = _('textbed'))
self.sizerslider2.Add(self.sliderbed, 1, wx.ALIGN_CENTER)
self.sizerslider2.Add(self.txtbed, 0, wx.ALIGN_CENTER)
#Tool tips
self.sliderbed.SetToolTip(_('This slider modifies the river bed position on the current profile.'))
self.txtbed.SetToolTip(_('The input modifies the river bed position on the current profile.'))
### From left side of the screen (3rd box) --> right bank
self.sizerslider3 = wx.StaticBoxSizer(wx.VERTICAL, self, label =_('Right bank (mm)'))
curs = 0
self.sliderright = wx.Slider(self, wx.ID_ANY, curs,0,10000, wx.DefaultPosition, wx.DefaultSize, wx.SL_TOP|wx.SL_VALUE_LABEL, name=_('sliderright'))
self.txtright = wx.TextCtrl(self, wx.ID_ANY, str(curs), wx.DefaultPosition, wx.DefaultSize, style =wx.TE_CENTER|wx.TE_PROCESS_ENTER, name =_('textright'))
self.sizerslider3.Add(self.sliderright,1, wx.ALIGN_CENTER)
self.sizerslider3.Add(self.txtright, 0, wx.ALIGN_CENTER)
#Tool tips
self.sliderright.SetToolTip(_('This slider modifies the right bank position on the current profile.'))
self.txtright.SetToolTip(_('The input modifies the right bank position on the current profile.'))
##Handling wx events with the class functions #FIXME when done update by adding an underscore to these functions (they should not be callled in other scripts)
###Buttons
self.ButPrev.Bind(wx.EVT_BUTTON, self.plot_up)
self.ButNext.Bind(wx.EVT_BUTTON, self.plot_down)
self.txtselectcs.Bind(wx.EVT_TEXT_ENTER, self.jump_to_cs)
###General movement of banks
#Sliders
self.slidergenhor.Bind(wx.EVT_SLIDER, self.movegenslider)
self.slidergenver.Bind(wx.EVT_SLIDER, self.movegenslider)
###sliders and text controls
####Sliders
self.sliderleft.Bind(wx.EVT_SLIDER, self.movebanksslider)
self.sliderbed.Bind(wx.EVT_SLIDER, self.movebanksslider)
self.sliderright.Bind(wx.EVT_SLIDER, self.movebanksslider)
#### Text controls
self.txtleft.Bind(wx.EVT_TEXT_ENTER, self.movebanksslider)
self.txtbed.Bind(wx.EVT_TEXT_ENTER, self.movebanksslider)
self.txtright.Bind(wx.EVT_TEXT_ENTER, self.movebanksslider)
self.txthor.Bind(wx.EVT_TEXT_ENTER, self.movegenslider)
self.txtver.Bind(wx.EVT_TEXT_ENTER, self.movegenslider)
#### Hydro sliders
#font = wx.Font( wx.FontInfo(10).Bold().Underline() ) FIXME (check about font and positions later)
self.sizerslider4= wx.StaticBoxSizer(wx.VERTICAL, self, label=_("Water depth (m)"))
self.sizerslider5= wx.StaticBoxSizer(wx.VERTICAL, self, label=_("Water level (m)"))
self.sizerslider6= wx.StaticBoxSizer(wx.VERTICAL, self, label=_("Discharge (m^3/s)")) #FIXME Notation
self.sizergeom = wx.StaticBoxSizer(wx.VERTICAL, self, label= _("Other parameters"))
##### water depth
self.wdepth = 0
#self.wdepth_n= wx.StaticText(self, id = wx.ID_ANY,label="Water depth - h\n(mm)")
#self.sizerslider4.Add(self.wdepth_n,0,wx.EXPAND)
#box1 = wx.StaticBox(self, wx.ID_ANY, "Water Depth")
#self.sizerslider4.Add(box1,0,wx.EXPAND)
self.txtwdepth = wx.TextCtrl(self, wx.ID_ANY, str(self.wdepth), wx.DefaultPosition, wx.DefaultSize, style =wx.TE_CENTER|wx.TE_PROCESS_ENTER, name = _('textwdepth'))
self.txtwdepth.Bind(wx.EVT_TEXT_ENTER, self.movewaterdepth)
self.sliderwdepth = wx.Slider(self, wx.ID_ANY, self.wdepth, 0, 100000, wx.DefaultPosition, wx.DefaultSize, wx.SL_LEFT|wx.SL_INVERSE|wx.SL_VALUE_LABEL,name= _('sliderwdepth') )
self.sizerslider4.Add(self.sliderwdepth,0, wx.ALIGN_CENTER)
self.sliderwdepth.Bind(wx.EVT_SLIDER, self.movewaterdepth)
self.sizerslider4.Add(self.txtwdepth,0,wx.CENTER|wx.EXPAND)
#Tool tips
self.sliderwdepth.SetToolTip(_('This slider adjusts the desired water depth on graphs.'))
self.txtwdepth.SetToolTip(_('The input adjusts the desired water depth on graphs.'))
###### water level
self.wlevel = 0
self.minwlevel = 0
self.maxwlevel = 10000000
self.txtwlevel = wx.TextCtrl(self, wx.ID_ANY, str(self.minwlevel), wx.DefaultPosition, wx.DefaultSize, style =wx.TE_CENTER|wx.TE_PROCESS_ENTER, name = _('textwlevel'))
self.txtwlevel.Bind(wx.EVT_TEXT_ENTER, self.movewaterlevel)
self.sliderwlevel = wx.Slider(self, wx.ID_ANY, self.wlevel, self.minwlevel, self.maxwlevel, wx.DefaultPosition, wx.DefaultSize, wx.SL_RIGHT|wx.SL_INVERSE|wx.SL_VALUE_LABEL,name=_('sliderwlevel') )
self.sizerslider5.Add(self.sliderwlevel,0,wx.ALIGN_CENTER)
self.sliderwlevel.Bind(wx.EVT_SLIDER, self.movewaterlevel)
self.sizerslider5.Add(self.txtwlevel,0,wx.ALIGN_CENTER)
#Tool tips
self.sliderwlevel.SetToolTip(_('This slider adjusts the desired water level on graphs.'))
self.txtwlevel.SetToolTip(_('The input adjusts the desired water level on graphs.'))
#####Discharges
self.wdis = 0
self.sliderwdis= wx.Slider(self, wx.ID_ANY, self.wdis, 0., 10000, wx.DefaultPosition, wx.DefaultSize, wx.SL_TOP|wx.SL_VALUE_LABEL,name=_('sliderwdis') )
self.txtwdis = wx.TextCtrl(self, wx.ID_ANY, str(self.wdis), wx.DefaultPosition, wx.DefaultSize, style =wx.TE_CENTER|wx.TE_PROCESS_ENTER, name = _('textwdis'))
self.txtwdis.Bind(wx.EVT_TEXT_ENTER, self.movewaterdis)
self.sizerslider6.Add(self.sliderwdis,1, wx.ALIGN_CENTER)
self.sliderwdis.Bind(wx.EVT_SLIDER, self.movewaterdis)
self.sizerslider6.Add(self.txtwdis,0,wx.ALIGN_CENTER)
#Tool tips
self.sliderwdis.SetToolTip(_('This slider plots the desired discharge and its geometric characteristics on the 2 last graphs.'))
self.txtwdis.SetToolTip(_('The input plots the desired discharge and its geometric characteristics on the 2 last graphs.'))
### Manning
self.Manning = 0.04
self.labelManning = wx.StaticText(self, wx.ID_ANY, _('Manning Coef.'))
self.txtManning = wx.TextCtrl(self, wx.ID_ANY, str(self.Manning), wx.DefaultPosition, wx.DefaultSize, style =wx.TE_CENTER|wx.TE_PROCESS_ENTER, name = 'textManning')
self.txtManning.Bind(wx.EVT_TEXT_ENTER, self.compute_relations)
self.sizergeom.Add(self.labelManning, 0, flag=wx.ALIGN_CENTER_HORIZONTAL)
self.sizergeom.Add(self.txtManning,0,wx.EXPAND)
#Tool Tip
self.txtManning.SetToolTip(_('The Manning coefficient modifies the friction values, and therefore, the plotted discharges.'))
### Strickler
self.Strickler = 1/self.Manning
self.labelStrickler = wx.StaticText(self, wx.ID_ANY, _('Strickler Coef.'))
self.txtStrickler = wx.TextCtrl(self, wx.ID_ANY, str(self.Strickler), wx.DefaultPosition, wx.DefaultSize, style =wx.TE_CENTER|wx.TE_PROCESS_ENTER, name = 'textStrickler')
self.txtStrickler.Bind(wx.EVT_TEXT_ENTER, self.compute_relations)
self.sizergeom.Add(self.labelStrickler, 0, flag=wx.ALIGN_CENTER_HORIZONTAL)
self.sizergeom.Add(self.txtStrickler,0,wx.EXPAND)
#Tool Tip
self.txtStrickler.SetToolTip(_('The Strickler coefficient modifies the friction values, and therefore, the plotted discharges.'))
### Slopes
self.slope = 0.001
self.labelslope = wx.StaticText(self, wx.ID_ANY, _('slope'))
self.txtslope = wx.TextCtrl(self, wx.ID_ANY, str(self.slope), wx.DefaultPosition, wx.DefaultSize, style =wx.TE_CENTER|wx.TE_PROCESS_ENTER, name = _('textslope'))
self.txtslope.Bind(wx.EVT_TEXT_ENTER, self.compute_relations)
self.sizergeom.Add(self.labelslope, 0, flag=wx.ALIGN_CENTER_HORIZONTAL)
self.sizergeom.Add(self.txtslope,0,wx.EXPAND)
#Tool tip
self.txtslope.SetToolTip(_('Modify the profile discharges according to a chosen bed slope.'))
#Other options
self.Butprofvert = wx.Button(self, label = _('Modify profile'))
self.Butsimul = wx.Button(self, label = _('Wolf models data'))
self.Butdrawprof = wx.Button(self, label = _('Draw on profile'))
self.Butupdateprof = wx.Button(self, label = _('Refresh the drawings'))
#self.Butprofvert_sz = wx.Button(self, label = _('Modify profile (SZ)'))
self.sizersimulations.Add(self.Butsimul, 1, wx.EXPAND)
self.sizersimulations.Add(self.Butprofvert,1,wx.EXPAND)
self.sizersimulations.Add(self.Butdrawprof, 1, wx.EXPAND)
#self.sizersimulations.Add(self.Butprofvert_sz,1,wx.EXPAND)
self.sizersimulations.Add(self.Butupdateprof,1, wx.EXPAND)
self.Butsimul.Bind(wx.EVT_BUTTON, self.simulations)
#self.Butprofvert.Bind(wx.EVT_BUTTON, self.modify_vertex) #FIXME
self.Butupdateprof.Bind(wx.EVT_BUTTON, self._draw_on_profile)
# self.Butprofvert_sz.Bind(wx.EVT_BUTTON, self.modify_vertex_sz) #FIXME
self.Butdrawprof.Bind(wx.EVT_BUTTON, self.add_zones)
#Tool tip
self.Butsimul.SetToolTip(_('Display the water level and discharges corresponding to the active wolf simulations.'))
self.Butprofvert.SetToolTip(_('Create a new page where the profile displayed on Figure 1 can be modified and compared to itself.\
\n A table containing its vertices and sz coordinate is also displayed)'))
#self.Butprofvert_sz.SetToolTip(_('Display a table containing the profile characterisitcs in sz mode (only available in modification mode -> Options).'))
self.Butupdateprof.SetToolTip(_('Update all modifications made on the profile'))
self.Butdrawprof.SetToolTip(_('Draw additional information on the current profile (structures).'))
#Sliders alignement (first added, first display (above))
#Groups
self.sizer.Add(self.sizernextprev, 0, wx.EXPAND)
self.sizerparam_box.Add(self.sizerparam, 1, wx.LEFT|wx.EXPAND)
self.sizerparam_box.Add(self.sizerposbank,1,wx.LEFT|wx.EXPAND) #check the meaning of 0 and 1
self.sizer.Add(self.sizerparam_box, 1, wx.EXPAND)
self.sizer.Add(self.sizersimulations,0.9, wx.EXPAND)
#Geometric
self.sizerposbank.Add(self.sizerslider1,1, wx.EXPAND) #FIXME Positions of the boxsizer
self.sizerposbank.Add(self.sizerslider2,1, wx.EXPAND) #FIXME Positions of the boxsizer
self.sizerposbank.Add(self.sizerslider3,1, wx.EXPAND) #FIXME Positions of the boxsizer
self.sizerposbank.Add(self.sizerslider00,1,wx.EXPAND)
self.sizerposbank.Add(self.sizerslider0,1,wx.EXPAND)
#Hydro
self.sizerparam.Add(self.sizerslider5,1,wx.EXPAND)
self.sizerparam.Add(self.sizerslider4,1,wx.EXPAND)
self.sizerparam.Add(self.sizerslider6,1,wx.EXPAND)
self.sizerparam.Add(self.sizergeom,1,wx.EXPAND)
#self.disprop = wx.StaticText(self,wx.ID_ANY,_('Critical characteristics: \n Hydraulic radius:%s \n wet area:%s \n Wet perimeter:%s \n Top Width:%s' % (cr_radius, cr_wetarea, cr_wetperimeter, cr_width)))
#self.sizerslider6.Add(self.disprop,1, wx.ALIGN_CENTER)
#Matplolib toolbar
self.toolbar = NavigationToolbar(self.canvas) #Ajout d'une barre d'outils pour la figure courante
self.toolbar.Realize()
self.sizer.Add(self.toolbar,0.9, wx.ALIGN_CENTER)
self.SetAutoLayout(True)
# Functions
[docs]
def set_cs(self, my_profile:profile=None, vec: list[vector] = [], Manning=0.04, plot=True) -> None:
"""
This function sets the cross section (profile of interest in the list of cross sections provided as vectors) and
initialise the slider positions as well as the values displayed on the wx panel.
"""
if my_profile is None:
self.mycs = None
return # Nothing to do
if self.mycs is not None:
self.mycs.uncolor_active_profile(plot_opengl= False)
my_profile.color_active_profile(plot_opengl= False)
#Initialisation and reinitialisation of the plot parameters
self.mycs = my_profile
self.mycs.prepare() #Précalcule les relations sz...
# Computations methods from Pycrossection.
self.mycs.relations()
if self.mycs.up and self.mycs.down:
slup,slope_center,sld = self.mycs.slopes()
self.set_Manning(Manning, slope_center)
else:
self.set_Manning(Manning)
self.wlevel = self.mycs.zmin
self.wdepth = 0
self.wdis= 0
self.vec:list[vector] = vec
self.update_UI()
# Modified to take into account the comparison of crossections.
if plot:
if self.compare is None:
self.update_plots(True)
elif self.compare is not None:
self.update_plots()
[docs]
def update_UI(self, reset = True ):
if not self.wx_exists:
return
ymax = self.mycs.zmax
ymin = self.mycs.zmin
self.minwlevel = ymin*10000
self.maxwlevel = ymax*10000
length = self.mycs.length3D
self.sliderwlevel.SetMin(self.minwlevel)
self.sliderwlevel.SetMax(self.maxwlevel)
self.sliderwlevel.SetValue(self.minwlevel)
self.sliderwdepth.SetMin(0)
self.sliderwdepth.SetMax((ymax-ymin)*100000)
self.sliderwdepth.SetValue(0)
self.txtwlevel.SetLabel("{0:.3f}".format(self.minwlevel/10000))
self.txtwdepth.SetValue("{0:.3f}".format(0))
self.txtwdis.SetValue("{0:.3f}".format(0))
self.txtManning.SetValue("{0:.3f}".format(self.Manning))
self.txtStrickler.SetValue("{0:.3f}".format(1/self.Manning))
self.sliderwdis.SetValue(self.wdis)
self.txtwdis.SetValue("{0:.3f}".format(self.wdis))
self.sliderleft.SetMax(10000*length)
self.sliderbed.SetMax(10000*length)
self.sliderright.SetMax(10000*length)
self.slidergenhor.SetMax(10000*(length/2))
self.slidergenhor.SetMin(-10000*(length/2))
self.slidergenver.SetMax(50000)
self.slidergenver.SetMin(-50000)
self.slidergenhor.SetValue(0)
self.slidergenver.SetValue(0)
self.txthor.SetLabel("{0:.3f}".format(0))
self.txtver.SetLabel("{0:.3f}".format(0))
sl,sb,sr,yl,yb,yr = self.mycs.get_sz_banksbed()
# Banks (sliders positions and text control values).
if self.mycs.bankleft is not None:
self.sliderleft.SetValue((sl)*10000)
self.txtleft.SetLabelText("{0:.3f}".format(sl))
if self.mycs.bed is not None:
self.sliderbed.SetValue((sb)*10000)
self.txtbed.SetLabelText("{0:.3f}".format(sb))
if self.mycs.bankright is not None:
self.sliderright.SetValue((sr)*10000)
self.txtright.SetLabelText("{0:.3f}".format(sr))
self.set_slope(newslope=self.slope, nManning=self.Manning) # FIXME (fixing bugs)
self.sliderwdis.SetMax(self.qmax*10000)
self.txtselectcs.SetValue(self.mycs.myname)
if reset:
self.zones = None
# self.copy_nbvertices = None
self.copy_vertices = None
# added
self.copy_compare_vertices = None
# self.copy_compare_nbvertices = None
[docs]
def update_plots(self, update_image=False):
#plots
if update_image: self.mapviewer_activector()
self.plot_profile()
self.plot_discharge()
self.plot_hspw()
self.plot_cs()
[docs]
def mapviewer_activector(self):
"""
This methods activates and colors the active profile in wolfhece.pydraw ,and therefore, in the wolfpy GUI too.
"""
if self.mapviewer is not None:
from wolfhece.PyDraw import WolfMapViewer
self.mapviewer:WolfMapViewer
ax3 = self.ax_img
fig = self.figure
# self.mapviewer.set_active_profile(self.mycs) #We set the active profile in wolfhece.pydraw
self.mapviewer.set_active_vector(self.mycs) #To avoid visual confusions, we set the profile as the active vector in wolfhece.pydraw
self.mycs.color_active_profile(plot_opengl= True) #we thicken and color the profile in red.
size = 200
self.mapviewer.zoom_on_active_profile(size=size) #We zoom on the profile in the gui.
self.mapviewer.Paint() #We take the visual information in the GUI necessary for a screen shot.
self.mapviewer.display_canvasogl(fig= fig, ax = ax3) #We return a clear screen shot of the wolfpy GUI as a matplolib graph (ax).
else:
self.mycs.color_active_profile(plot_opengl= False)
bounds = self.mycs.get_bounds(100.)
"""
FIXME find a way to better iterate over the different orthophotos
orthos = {'IMAGERIE': {'1971': 'ORTHO_1971', '1994-2000': 'ORTHO_1994_2000',
'2006-2007': 'ORTHO_2006_2007',
'2009-2010': 'ORTHO_2009_2010',
'2012-2013': 'ORTHO_2012_2013',
'2015': 'ORTHO_2015', '2016': 'ORTHO_2016', '2017': 'ORTHO_2017',
'2018': 'ORTHO_2018', '2019': 'ORTHO_2019', '2020': 'ORTHO_2020',
'2021': 'ORTHO_2021', '2022 printemps': 'ORTHO_2022_PRINTEMPS', '2022 été': 'ORTHO_2022_ETE',
'2023 été': 'ORTHO_2023_ETE'}}
"""
try:
mybytes = getWalonmap(cat='IMAGERIE/ORTHO_2022_PRINTEMPS',xl = bounds[0][0], yl = bounds[0][1], xr = bounds[1][0], yr = bounds[1][1], w=500, h=500, tofile=False)
image = Image.open(mybytes)
self.ax_img.clear()
self.ax_img.imshow(image, origin='upper', extent=(bounds[0][0],bounds[1][0],bounds[0][1],bounds[1][1]))
self.ax_img.plot([self.mycs.myvertices[0].x, self.mycs.myvertices[-1].x], [self.mycs.myvertices[0].y, self.mycs.myvertices[-1].y], c='red', linewidth=3)
except:
pass
[docs]
def set_Manning(self, nManning:float, slope:float=-99999):
if nManning>0:
if slope>0.:
self.slope=slope
self.Manning = nManning
self.Strickler = 1./nManning
self.mycs.ManningStrickler_profile(slope= self.slope, nManning= self.Manning)
return nManning
else:
return self.Manning
[docs]
def set_Strickler(self, kStrickler:float, slope:float=-99999):
if kStrickler>0:
if slope>0.:
self.slope=slope
self.Manning = 1./kStrickler
self.Strickler = kStrickler
self.mycs.ManningStrickler_profile(slope= self.slope, nManning= self.Manning)
return kStrickler
else:
return self.Strickler
[docs]
def set_slope(self, newslope:float, nManning:float=-99999):
if newslope>0:
if nManning>0.:
self.Manning=nManning
self.slope = newslope
self.qmax = self.mycs.ManningStrickler_profile(slope= self.slope, nManning= self.Manning)
return newslope
else:
return self.slope
[docs]
def compute_relations(self, event: wx.Event):
"""
This method computes the discharges based on a choosen friction coefficient (Manning or Strickler) and a slope.
The computations are trigged by wx.Events (user's inputs in txt ctrl).
"""
id = event.GetEventObject().GetName()
if id == 'textManning':
val= self.set_Manning(float(self.txtManning.Value))
self.txtManning.SetValue("{0:.3f}".format(self.Manning))
self.txtStrickler.SetValue("{0:.3f}".format(self.Strickler))
elif id == 'textStrickler':
val= self.set_Strickler(float(self.txtStrickler.Value))
self.txtStrickler.SetValue("{0:.3f}".format(self.Strickler))
self.txtManning.SetValue("{0:.3f}".format(self.Manning))
elif id == 'textslope':
val= self.set_slope(float(self.txtslope.Value))
self.txtslope.SetValue("{0:.4f}".format(self.slope))
self.plot_discharge()
self.plot_hspw()
self.plot_cs()
[docs]
def cs_setter(self, mycross: crosssections = None, active_profile : profile = None, compare : profile = None, mapviewer = None, plot=True):
"""
This method sets simultaneously the cross section (mycross : parent) and the profile (active vector : child) active in wolf GUI or defined by the user.
If no profile is given, the method returns the profile corresponding to the first key(string) in the cross section dictionary.
Moreover, if a comparison profile (compare) is given, the methods sets that profile as the reference against which modifications on the active profile are compared.
"""
#Initializations of the profile
self.mapviewer = mapviewer #Pointing to WolfMapViewer (This establishes the communication between the 2 interfaces)
self.my_cross_sections = mycross
if compare is not None:
self.compare = compare
self.compare.prepare()
elif compare is None:
self.compare = None
if active_profile is not None:
self.set_cs(active_profile)
else:
key1 = next(iter(self.my_cross_sections.myprofiles))
self.set_cs(self.my_cross_sections.myprofiles[key1]['cs'])
[docs]
def jump_to_cs(self, event: wx.Event):
"""
Based on the user's input, the method jumps to the selected profile in the cross section.
"""
id = event.GetEventObject().GetName()
if id == 'textselectcs':
index= str(self.txtselectcs.Value)
keylist = list(self.my_cross_sections.myprofiles.keys())
if index in keylist:
val = self.my_cross_sections.myprofiles[index]['cs']
else:
index = keylist[0]
val = self.my_cross_sections.myprofiles[index]['cs']
self.set_cs(val)
[docs]
def movegenslider(self,event: wx.Event):
"""
Using wx.Event, this methods moves (up or down, left or right) the whole river bed (banks and bed) displayed on the figure.
"""
if self.mycs is None:
return
id = event.GetEventObject().GetName()
cs:profile =self.mycs
length = cs.length3D
#Movemments of river bed
# Sliders
if id == 'sliderhor':
sdatum = float(self.slidergenhor.Value)/10000
cs.update_sdatum(sdatum)
self.txthor.SetValue("{0:.3f}".format(cs.sdatum))
elif id == 'sliderver':
zdatum = float(self.slidergenver.Value)/10000.
cs.update_zdatum(zdatum)
self.txtver.SetValue("{0:.3f}".format(cs.zdatum))
#Text controls
elif id == 'texthor':
curs = float(self.txthor.Value)
if curs <= -(length/2):
curs= -(length/2)
elif curs >= (length/2):
curs = (length/2)
cs.update_sdatum(curs)
self.slidergenhor.SetValue(10000*(curs))
self.txthor.SetValue("{0:.3f}".format(curs))
elif id == 'textver':
curz = float(self.txtver.Value)
if curz <= -5:
curz= - 5
elif curz >= 5:
curz = 5
cs.update_zdatum(curz)
self.slidergenver.SetValue(10000*(curz))
self.txtver.SetValue("{0:.3f}".format(curz))
#Update of plots
self.plot_profile()
self.plot_cs()
[docs]
def movebanksslider(self, event: wx.Event):
"""
This function updates a position of the river bank(left, bed, or right) based on the user's entry(sliding a slider or input of a float value).
"""
#Intialization
if self.mycs is None:
return
id = event.GetEventObject().GetName()
cs:profile =self.mycs
length = cs.length3D
#Updates from sliders
if id == 'sliderleft':
curs = float(self.sliderleft.Value)/10000.
self.txtleft.SetValue("{0:.3f}".format(curs))
cs.update_banksbed_from_s3d('left', curs)
elif id=='sliderbed':
curs=float(self.sliderbed.Value)/10000.
self.txtbed.SetValue("{0:.3f}".format(curs))
cs.update_banksbed_from_s3d('bed', curs)
elif id =='sliderright':
curs = float(self.sliderright.Value)/10000.
self.txtright.SetValue("{0:.3f}".format(curs))
cs.update_banksbed_from_s3d('right', curs)
#Updates from text controls
elif id=='textleft':
curs=float(self.txtleft.Value)
if curs<0.:
curs=0.
self.txtleft.SetValue("{0:.3f}".format(curs))
self.sliderleft.SetValue(curs*10000.)
elif curs>length:
curs=length
self.txtleft.SetValue("{0:.3f}".format(curs))
self.sliderleft.SetValue(curs*10000.)
curslider = curs*10000.
self.sliderleft.SetValue(curslider)
cs.update_banksbed_from_s3d('left', curs)
elif id=='textbed':
curs=float(self.txtbed.Value)
if curs<0.:
curs=0.
self.txtbed.SetValue("{0:.3f}".format(curs))
self.sliderbed.SetValue(curs*10000.)
elif curs>length:
curs=length
self.txtbed.SetValue("{0:.3f}".format(length))
self.sliderbed.SetValue(curs*10000.)
curslider = curs*10000.
self.sliderbed.SetValue(curslider)
cs.update_banksbed_from_s3d('bed', curs)
elif id =='textright':
curs = float(self.txtright.Value)
#Safelock for negative distance (limits of the cross section)
if curs < 0:
curs = 0.
self.txtright.SetValue("{0:.3f}".format(curs))
self.sliderright.SetValue(curs*10000.)
#Safelock for a distance beyond the crossection's length
elif curs > length:
curs = length
self.txtright.SetValue("{0:.3f}".format(length))
self.sliderright.SetValue(curs*10000.)
curslider = curs*10000.
self.sliderright.SetValue(curslider)
cs.update_banksbed_from_s3d('right', curs)
self.plot_profile()
self.plot_cs()
[docs]
def movewaterdepth(self, event: wx.Event):
"""
This method increases or decreases the water depth plotted on graphs.
"""
if self.mycs is None:
return
id = event.GetEventObject().GetName()
ymin = self.mycs.zmin
ymax = self.mycs.zmax
if id == 'sliderwdepth':
val = float(self.sliderwdepth.Value)/100000
if val + ymin > ymax:
self.wdepth = ymax-ymin
self.wlevel = self.wdepth + ymin
else:
self.wdepth = val
self.wlevel = self.wdepth + ymin
elif id== 'textwdepth':
val = float(self.txtwdepth.Value)
if val + ymin <= ymin:
self.wdepth = 0.
self.wlevel = self.wdepth + ymin
elif val + ymin > ymax: #FIXME (Is this limit realistic for all possible cases?)
self.wdepth = ymax-ymin
self.wlevel = self.wdepth + ymin
else:
self.wdepth = val
self.wlevel = self.wdepth + ymin
self.sliderwdepth.SetValue(self.wdepth*100000)
self.txtwdepth.SetValue("{0:.3f}".format(self.wdepth))
self.txtwlevel.SetValue("{0:.3f}".format(self.wlevel))
self.sliderwlevel.SetValue(self.wlevel*10000)
#Plots
self.update_plots()
[docs]
def movewaterlevel(self, event: wx.Event):
"""
This method increases or decreases the water level plotted on graphs.
"""
if self.mycs is None:
return
ymin = self.mycs.zmin
ymax = self.mycs.zmax
id = event.GetEventObject().GetName()
if id == 'sliderwlevel':
val = (float(self.sliderwlevel.Value))/10000
self.wlevel =val
self.wdepth = (self.wlevel - ymin)
elif id == 'textwlevel':
val = float(self.txtwlevel.Value)
if val <= ymin:
self.wlevel = ymin
self.wdepth = 0
elif val > ymax: #FIXME (Is this limit realistic for all possible cases?)
self.wlevel = ymax
self.wdepth = (self.wlevel - ymin)
else:
self.wlevel = val
self.wdepth = self.wlevel - ymin
self.sliderwlevel.SetValue(self.wlevel*10000)
self.txtwlevel.SetValue("{0:.3f}".format(self.wlevel))
self.txtwdepth.SetValue("{0:.3f}".format(self.wdepth))
self.sliderwdepth.SetValue(self.wdepth*100000)
self.update_plots()
[docs]
def movewaterdis(self, event: wx.Event):
"""
This method selects and plots a specific discharges in the GUI based on the user's input.
"""
if self.mycs is None:
return
id = event.GetEventObject().GetName()
if id == 'sliderwdis':
val= float(self.sliderwdis.Value)/10000
self.wdis = val
elif id == 'textwdis':
val = float(self.txtwdis.Value)
if val <= 0:
self.wdis = 0
elif val > self.qmax: #FIXME
self.wdis = self.qmax
else:
self.wdis = val
self.txtwdis.SetValue("{0:.3f}".format(self.wdis))
self.sliderwdis.SetValue(self.wdis*10000)
self.plot_discharge()
self.plot_hspw()
self.plot_cs()
[docs]
def plot_profile(self, figax:tuple[Figure, Axis] = None):
"""
This method plots the geometric profiles on the first and third graph.
The third graph is anamorphosed to allow comparisons with other graphs using the same scale.
"""
cs:profile = self.mycs
if figax is None:
fig = self.figure
ax1 = self.ax_cs_real
ax4 = self.ax_cs_anam
else:
fig, ax1=figax
ax4=None
#The profile
sl,sb,sr,yl,yb,yr=cs.plotcs_profile(fig = fig,
ax = ax1,
vecs = self.vec ,
compare = self.compare,
fwd=self.wdepth,
fwl=self.wlevel,
simuls = self.models,
forceaspect = True,
plotlaz=True)
#The neighbouring profiles (up and down)
if cs.up is not None and cs.up is not cs:
cs.up._plot_only_cs(fig=fig,
ax=ax1,
style='dashed',
label=cs.up.myname,
centerx=sb)
if cs.down is not None and cs.down is not cs:
cs.down._plot_only_cs(fig=fig,
ax=ax1,
style='dotted',
label=cs.down.myname,
centerx=sb)
if ax4 is not None:
# The anamorphosed view
sl,sb,sr,yl,yb,yr=cs.plotcs_profile(fig = fig,
ax = ax4,
vecs = self.vec ,
simuls = self.models,
fwd =self.wdepth,
fwl=self.wlevel,
forceaspect = False,
plotlaz=True)
#Legends
leg1 = ax1.legend(bbox_to_anchor = (0.,0.9, 1.1,0.2),
ncol= 4, mode = 'expand',
fontsize= 'xx-small',
markerscale = 0.5,
borderaxespad=-1.05)
if self.wx_exists:
leg1.set_draggable(True) #For legend displacements #FIXME
leg4 = ax4.legend(bbox_to_anchor = (0.,0.9, 1.1,0.2),
ncol= 4,
mode = 'expand',
fontsize= 'xx-small',
markerscale = 0.5,
borderaxespad=-1.05)
if self.wx_exists:
leg4.set_draggable(True)
[docs]
def plot_discharge(self, figax:tuple[Figure, Axis] = None):
"""
This method plots the discharge relationships of the active profile on the 5th axis of the matplotlib figure.
"""
if figax is None:
fig = self.figure
ax5 = self.ax_dis
else:
fig,ax5 = figax
labels = False
#graphs
self.mycs.plotcs_discharges(fig = fig,
ax = ax5,
fwl=self.wlevel,
fwd = self.wdepth,
fwq = self.wdis,
labels= labels,
simuls=self.models)
#Legend
leg5 = ax5.legend(bbox_to_anchor = (0.,0.95, 1.1,0.2),
ncol= 4, mode = 'expand',
fontsize= 'xx-small',
markerscale = 0.5,
borderaxespad=-1.05)
if self.wx_exists:
leg5.set_draggable(True)
[docs]
def plot_hspw(self, figax:tuple[Figure, Axis] = None):
"""
The methods plots the hydraulic radius - H, the wetted area - S, the wetted perimeter - P and top width - W on sixth axis and last graph of the figure.
"""
if figax is None:
fig = self.figure
ax6 = self.ax_hsw
else:
fig, ax6 = figax
labels = False
self.mycs.plotcs_hspw(fig= fig,
ax = ax6,
fwl=self.wlevel,
fwd = self.wdepth,
fwq = self.wdis,
labels= labels)
leg6 = ax6.legend(bbox_to_anchor = (0.,0.95, 1.1,0.2),
ncol= 4, mode = 'expand',
fontsize= 'xx-small',
markerscale = 0.5,
borderaxespad=-1.05)
if self.wx_exists:
leg6.set_draggable(True)
[docs]
def plot_cs(self):
"""
This method plots 6 matplotlib graphs of the active profile.
In order to do so, it uses the stored self graphs defined by other plotting methods.
Storing the graphs avoid unnecessary repetitions of plots each time the method is called.
"""
fig = self.figure
fig.suptitle('Cross section - %s'%(self.mycs.myname), size=15)
self.gs.tight_layout(fig)
#fig.tight_layout()
if self.wx_exists:
self.canvas.draw()
else:
fig.canvas.draw()
[docs]
def plot_up(self, event: wx.Event):
"""
This function plots the upstream's cross section by upgrading the current cross section with the next upstream.
"""
if self.mycs.up is None:
return
self.mycs.uncolor_active_profile(plot_opengl= False)
self.set_cs(self.mycs.up)
self.plot_cs()
[docs]
def plot_down(self,event: wx.Event):
"""
This function plots the downstream's cross section by upgrading the current cross section with the next downsrtream.
"""
if self.mycs.down is None:
return
self.mycs.uncolor_active_profile(plot_opengl= False)
self.set_cs(self.mycs.down)
self.plot_cs()
[docs]
def add_model(self, newsim):
"""Add a new sim to the sims dictionnary"""
self.models.append(newsim)
[docs]
def reset_models(self):
"""Reset the sims dictionnary"""
self.models=[]
[docs]
def simulations(self, event: wx.Event):
"""
This method displays on the profile and on the discharge graphs, the hydraulic characteristics in the numerical simulations.
Also, it triggers a new window containing a table (grid) of the displayed values.
FIXME add a plot method for 2D and 1D results.
"""
# Data from numerical simulations
# Set 'Model Key','Discharge','Water depth','Water level'
if self.grid_simulations is None:
self.models=[]
for curkey, cursim in self.sims.items():
ds = cursim.get_dxdy_min()
pts = self.mycs._refine2D(ds/2.)
allz = [cursim.get_value(curpt.x, curpt.y, nullvalue=-99999) for curpt in pts]
self.models = np.array([['Q25',250.5, 2,76],['Q50',500,3,77], ['Q100',800,4,78],['Q1000',1500,5,79.2]])
# Calling the grid's class and displaying as well as getting the data from its table (FIXME: Safety for future improvements)
self.grid_simulations = windowsgrid(parent=None)
self.grid_simulations.set_grid_sims(models = self.models,
title = 'C.S.- %s'%(self.mycs.myname))
self.plot_profile()
self.plot_discharge()
self.plot_cs()
else:
self.grid_simulations.Show()
[docs]
def _update_sims(self):
self.models = np.asarray(list(self.grid_simulations.get_vals_sims()))
self.grid_simulations = None
[docs]
def modify_vertex(self):
"""
This method displays a new window containing the vertices of the active cross sections for modifications.
"""
if self.compare is not None:
if self.grid_vertices is None:
self.grid_vertices = windowsgrid(parent=None)
self.grid_vertices.set_grid_vert(my_profile =self.mycs,
ref = self.compare,
title= 'C.S.- %s'%(self.mycs.myname),
callback= self._update_profile_callback,
call1 = self.update_profile,
call2 = self.update_profile_from_sz)
self.grid_vertices.Show()
else:
self.grid_vertices.Show()
[docs]
def show_vertices(self, event: wx.Event):
self.modify_vertex()
[docs]
def update_profile(self,event: wx.Event):
"""
This method allows the update of the profile plot
- if the profile vertices are modified or
- or if a structure is drawn (projected) on the profile.
"""
self._reset_profile_vertices()
if self.compare is not None and self.grid_vertices is not None:
self.mycs.updatefromgrid(gridfrom = self.grid_vertices.profile_xls)
self._update_drawings()
[docs]
def _reset_profile_vertices(self):
if self.copy_vertices: # and self.copy_nbvertices:
self.mycs.myvertices = self.copy_vertices
# added
self.compare.myvertices = self.copy_compare_vertices
[docs]
def update_profile_from_sz(self, event:wx.Event):
"""
Mise à jour depuis un CpGrid
"""
self._reset_profile_vertices()
curv:wolfvertex
gridfrom = self.grid_vertices.profile_xls
nbl=gridfrom.GetNumberRows()
k=0
S=[]
Z =[]
while k<nbl:
z=gridfrom.GetCellValue(k,2)
s=gridfrom.GetCellValue(k,4)
if z=='':
z=0.
if s!='':
S.append(float(s))
Z.append(float(z))
k+=1
elif s=='':
break
else:
raise Exception(_("Recheck your sz data"))
break
sz = np.array([S,Z])
length_compare = self.compare.asshapely_ls()
vertices = []
nbv = 0
for i in range(len(sz[0])) :
coords = length_compare.interpolate(sz[0][i])
curv = wolfvertex(coords.x, coords.y, sz[1][i])
vertices.append(curv)
nbv += 1
self.mycs.reset_prepare()
self.mycs.myvertices=vertices
self._update_drawings()
[docs]
def _update_profile_callback(self):
self.mycs.updatefromgrid(gridfrom = self.grid_vertices.profile_xls)
self.grid_vertices = None
[docs]
def project_zones_on_trace(self, new_zones:Zones = None, reset_vec=True):
if new_zones is None:
return
if reset_vec:
self.vec=[]
for zone in new_zones.myzones:
for vec in zone.myvectors:
if vec.myprop.used:
self.add_vec(vec= vec)
[docs]
def add_vec(self, vec: vector, reset_first=False):
"""
This method projects a vector on the profile traces and
appends the projection to the list utilised to plot the structures.
"""
if reset_first:self.vec=[]
curvec:vector = vec.projectontrace(self.mycs)
self.vec.append(curvec)
[docs]
def populate_xy_from_sz(self):
"""
This method populate xy colum based on s and z entries.
"""
self.Butgetxy_fromsz =wx.Button(self, label = _('Get xy from sz'))
self.zones.xls
[docs]
def add_zones(self, event: wx.Event):
"""
This method creates a new zones and displays its structure on the screen.
"""
# self.zones: Zones
if self.zones:
for i in range(self.zones.nbzones):
for vec in self.zones.myzones[i].myvectors:
self.zones.evaluate_s(vec= vec, dialog_box=False)
self.zones.showstructure()
else:
self.zones = Zones(parent = self.mapviewer)
self.zones.showstructure()
[docs]
def _update_drawings(self):
if self.compare is not None and self.grid_vertices is not None:
self.harmonize_profiles()
self.mycs.prepare()
#self.zones = Zones(parent = self.mapviewer) # Added to avoid bug in wolfpy
if self.compare is not None and self.grid_simulations is not None:
self.models = np.asarray(list(self.grid_simulations.get_vals_sims()))
if self.zones:
self.project_zones_on_trace(self.zones)
# added
self.mycs.prepare()
self.mycs.relations()
self.set_Manning(nManning=self.Manning)
#self.plot_profile()
self.update_UI(reset=False)
self.update_plots()
self.plot_cs()
[docs]
def _draw_on_profile(self, event: wx.Event):
"""
This method display a grid where vertor can be encoded and drawn on the figure.
"""
if self.zones:
self.zones.xls
self._update_drawings()
[docs]
def get_copy_compare(self):
"""
This method creates a new panel containing a copy of the active profile.
"""
page = PlotCSAll(None)
copy_cs = self.mycs.deepcopy_profile()
page.cs_setter(active_profile = copy_cs, compare = self.mycs, mapviewer=self.mapviewer)
page.vec = self.vec
# page.plot_profile()
# page.plot_cs()
return page
[docs]
def harmonize_profiles(self):
"""
This method harmonizes the profiles characteristic (vertices).
The method inserts missing vertices on both profiles to allow their comparison.
"""
if self.compare:
# A copy of vertices is stored in memory to conserve the discretization of the initial profile.
self.copy_vertices = copy.deepcopy(self.mycs.myvertices)
# self.copy_nbvertices = copy.deepcopy(self.mycs.nbvertices)
self.copy_compare_vertices = copy.deepcopy(self.compare.myvertices)
# self.copy_compare_nbvertices = copy.deepcopy(self.compare.nbvertices)
# Resetting the crossections characteristics after modifications from the table (cpgrid)
self.mycs.reset_prepare()
self.compare.reset_prepare()
# Getting sz data from the 2 profiles (modified and reference)
sz1 = self.mycs.get_sz()
sz2 = self.compare.get_sz()
# Creating a buffer with unique values of the sz(s) union
mycs = np.array(sz1[0])
reference = np.array(sz2[0])
# test
if mycs.shape[0] != reference.shape[0] or np.unique(mycs).shape[0] != np.sort(mycs).shape[0] or np.unique(reference).shape[0] != np.sort(reference).shape[0]:
t =0.000001
all = np.concatenate((mycs, reference), axis=None)
all_sort = np.sort(all)
all_dif = np.diff(all_sort)
dif = np.insert(all_dif,0,1) # FIXME find an elegant way.
new_sz = all_sort[dif>t]
#all_unique = np.unique(all)
else:
#check = np.allclose(mycs, reference)
#if check == False:
check_unique = np.isclose(mycs, reference)
# Finding all differences between sz data
dif_sz1 = mycs[check_unique == False]
dif_sz2 = reference[check_unique == False]
common_sz = mycs[check_unique == True]
new = np.concatenate((common_sz, dif_sz1,dif_sz2), axis=None)
new_sz = np.unique(new)
# Harmonizing the modified profile
length_cs = self.mycs.asshapely_ls()
length_compare = self.compare.asshapely_ls()
# Harmonized properties
cs_vertices = []
compare_vertices = []
# process
for i in new_sz:
# Shapely cordinates
coords_cs = length_cs.interpolate(i)
coords_compare = length_compare.interpolate(i)
# Wolf vertex
curvert_cs = wolfvertex(coords_cs.x, coords_cs.y, coords_cs.z)
curvert_compare = wolfvertex(coords_compare.x, coords_compare.y, coords_compare.z)
cs_vertices.append(curvert_cs)
compare_vertices.append(curvert_compare)
# Update of the 2 profiles characteristics
self.mycs.myvertices= cs_vertices
self.compare.myvertices= compare_vertices
[docs]
class windowsgrid(wx.Frame):
"""
A class containing the different grids used for plotting the graphs.
"""
def __init__(self, *args, callback=None, **kwargs):
super(windowsgrid, self).__init__(*args, **kwargs)
self.Bind(wx.EVT_CLOSE, self.Quit)
[docs]
def update_grid_vert(self, my_profile: profile, ref: profile = None, title : str = None, callback = None):
self.set_grid_vert(my_profile=my_profile, ref= ref, title = title, callback = callback)
self.Refresh(True)
[docs]
def set_grid_sz(self, my_profile: profile, ref: profile = None, title : str = None, callback = None):
self.SetBackgroundColour('white')
[docs]
def set_grid_vert( self, my_profile: profile, ref: profile = None, title : str = None, callback = None, call1 = None, call2 = None):
"""
This method displays a grid containing the active profile vertices.
Also,the method contains a grid with the profile of reference for simultaneous insertion of vertices.
"""
self.SetBackgroundColour('white')
self.SetSize(width = 500, height=600)
self.callback = callback
self.mycs = my_profile
self.ref = ref
vertices = self.mycs.myvertices
nb_rows = self.mycs.nbvertices
gridsizer = wx.BoxSizer(wx.VERTICAL)
self.SetSizer(gridsizer)
self.profile_xls: CpGrid
self.profile_xls = CpGrid(self, 1, wx.WANTS_CHARS)
#New grid
#self.ref_xls: CpGrid
#self.ref_xls(self, 1, wx.WANTS_CHARS)
gridsizer.Add(self.profile_xls, 1, wx.EXPAND)
#Grid filling
self.profile_xls.CreateGrid(nb_rows,5)
self.mycs.fillgrid(gridto = self.profile_xls)
self._evaluate_s()
# Update buttons
# Button from xyz
But_update_from_xyz = wx.Button(self, label = _('Update profile from xyz'))
gridsizer.Add(But_update_from_xyz, 0, wx.EXPAND)
But_update_from_xyz.Bind(wx.EVT_BUTTON, call1)
# Button from sz
But_update_from_sz = wx.Button(self, label = _('Update profile from sz'))
gridsizer.Add(But_update_from_sz, 0, wx.EXPAND)
But_update_from_sz.Bind(wx.EVT_BUTTON, call2)
#self.ref_xls.CreateGrid(nb_rows,5)
#self.ref.fillgrid(gridto = self.ref_xls)
#FIXME insert vertices on both profile and reference
#sizerrows = wx.StaticBoxSizer(wx.HORIZONTAL, self, label= _("Add rows (vertices)"))
#self.txtrows = wx.TextCtrl(self, wx.ID_ANY, str(0), wx.DefaultPosition, wx.DefaultSize, style =wx.TE_CENTER|wx.TE_PROCESS_ENTER, name =_('rows'))
#gridsizer.Add(sizerrows, 0, wx.EXPAND) #FIXME
#sizerrows.Add(self.txtrows,1, wx.EXPAND) #FIXME
#self.txtrows.Bind(wx.EVT_TEXT_ENTER, self.update_nbrows)
self.SetTitle('Profile vertices - %s'%((title)))
self.Center(dir = wx.VERTICAL)
#self.Show(True)
[docs]
def _evaluate_s(self):
"""
Compute and fill the s coordinates in the profile grid.
"""
curv = self.mycs
curv.update_lengths()
self.profile_xls.SetCellValue(0,4,'0.0')
s=0.
for k in range(curv.nbvertices-1):
s+=curv._lengthparts2D[k]
self.profile_xls.SetCellValue(k+1,4,str(s))
[docs]
def set_grid_sims(self, models, title : str = None, callback = None):
"""
This method displays a wx.grid containing the numerical models data.
"""
self.callback = callback
self.SetBackgroundColour('white')
self.SetSize(width = 600, height = 500)
panel = wx.Panel(self)
gridsizer = wx.BoxSizer()
gridsizer.Add(panel, 1, wx.EXPAND)
self.SetSizer(gridsizer)
menubar = wx.MenuBar()
fileButton = wx.Menu()
exitItem = fileButton.Append(wx.ID_EXIT, 'Exit', 'Close this window')
menubar.Append(fileButton, 'Options')
#grid creation
self.qgrid: CpGrid = Grid(panel, name='Models',pos=(0,0), size =(1200,900))
self.qgrid.CreateGrid(1,4)
self.qgrid.SetColLabelValue(0,_('Model'))
self.qgrid.SetColLabelValue(1,_('Discharge'))
self.qgrid.SetColLabelValue(2,_('Water depth'))
self.qgrid.SetColLabelValue(3,_('Water level'))
nb=self.qgrid.GetNumberRows()
if len(models) - nb > 0:
self.qgrid.AppendRows(len(models) - nb+10)
k=0
for curv in models:
self.qgrid.SetCellValue(k,0,str(curv[k]))
self.qgrid.SetCellValue(k,1,str(curv[k]))
self.qgrid.SetCellValue(k,2,str(curv[k]))
self.qgrid.SetCellValue(k,3,str(curv[k]))
k+=1
self.SetMenuBar(menubar)
self.Bind(wx.EVT_MENU, self.Quit, exitItem)
self.SetTitle('Available models - %s'%((title)))
self.Center(dir = wx.VERTICAL)
self.Show(True)
[docs]
def get_vals_sims(self):
# data
nb_rows = self.qgrid.GetNumberRows()
models = []
for row in range(nb_rows):
ids = self.qgrid.GetCellValue(row, 0)
q = float(self.qgrid.GetCellValue(row, 1))
h = float(self.qgrid.GetCellValue(row,2))
z = float(self.qgrid.GetCellValue(row,3))
if ids != '':
models.append([ids,q,h,z])
return models
[docs]
def Quit(self, event: wx.Event):
"""
This method closes the wx.Frame containing the grid.
"""
if self.callback is not None:
self.callback()
self.Hide()
#self.Destroy()
[docs]
class ProfileNotebook(wx.Frame):
"""
Fenêtre contenant potentiellement plusieurs graphiques Matplotlib.
"""
def __init__(self, mapviewer = None, show=True, framesize=(1024,768)): #Notebook (multiple plots) a wx.window should be passed inside (parent or you create your own)
"""
Initialisation
Si un parent est fourni, on l'attache, sinon on crée une fenêtre indépendante.
"""
super().__init__(None, wx.ID_ANY, 'Wolf - Plotter',size=framesize) #Inheritance
self.Maximize(True) #FIXME (Default solution for better visualisation)
self.mapviewer = mapviewer #Parent
self.notebook = aui.AuiNotebook(self) # ajout du notebook
sizers = wx.BoxSizer() #sizer pour servir de contenant au notebook
sizers.Add(self.notebook, 1, wx.EXPAND) #ajout du notebook au sizer et demande d'étendre l'objet en cas de redimensionnement
self.SetSizer(sizers) #applique le sizer
menubar = wx.MenuBar()
options_menu = wx.Menu()
#modifyButton = options_menu.Append(wx.ID_ANY,_('Modify C.S.'), _('Modify the current profile') )
legendButton = options_menu.Append(wx.ID_ANY,_('Legend parameters'), _('Modify the legends aspect'))
quitButton = options_menu.Append(wx.ID_EXIT, _('Quit'),_('Close the whole notebook....'))
menubar.Append(options_menu,_('Options'))
self.SetMenuBar(menubar)
#self.Bind(wx.EVT_MENU, self.modify_cs, modifyButton)
self.Bind(wx.EVT_MENU, self.OnClose, quitButton)
if show: #If true
self.Show() #show the frame
self.Bind(wx.EVT_CLOSE , self.OnClose) #closing the window
self.Bind(wx.EVT_WINDOW_DESTROY , self.OnClose)
self.page_number = 0
[docs]
def OnClose(self,event):
"""
This method closes the notebook.
"""
self.notebook.Close()
[docs]
def modify_cs( self,event):
"""
This method creates a new panel containing a copy of the active profile.
The copy is used to modify the profile and to compare the modifications with the original profile.
"""
n = self.notebook.GetPageCount()
self.page_number += 1
newpage = self.add("Modified: {}".format(self.page_number))
page_src = self.notebook.GetPage(0)
copy_cs = page_src.mycs.deepcopy_profile()
if page_src.mapviewer is not None: #Implemented for an utilisation outside of the interface.
page_src.mapviewer.display_canvasogl(fig = newpage.figure , ax = newpage.ax_img) #The view is set here to maintain the position of the active profile in the GUI.
newpage.cs_setter(active_profile= copy_cs, compare = page_src.mycs)
newpage.zones = page_src.zones # To avoid redrawing the vector (structures)
newpage.plot_profile()
newpage.plot_cs()
newpage.modify_vertex()
newpage.Show()
self.notebook.SetSelection(n)
[docs]
def add(self, name="plot"):
"""
Ajout d'un onglet au notebook
L'onglet contient une Figure Matplotlib
On retourne la figure du nouvel onglet
"""
page = PlotCSAll(self.notebook) # PLOTCSALL first page of the notebook
self.notebook.AddPage(page, name) #ajout de l'objet Plot au notebook
#page.Butprofvert.Bind(wx.EVT_BUTTON, self.modify_cs) # Uncomment in case a return to the first figure is not needed
if self.notebook.GetPageCount() == 1: # Uncomment in case a return to the first figure is needed
page.Butprofvert.Bind(wx.EVT_BUTTON, self.modify_cs)
else:
page.Butprofvert.Bind(wx.EVT_BUTTON, page.show_vertices)
return page
[docs]
class ProfileWithoutGUI():
"""
This class launches the notebook without passing through the wolfpy GUI.
Useful for scripts commands (an example is provide in )
"""
def __init__(self, fcrossection ='', fsupport= '', format='') :
self.cross = crosssections(fcrossection, format) #Crossection data
self.myvec = Zones(fsupport) #Vector data
self.app=wx.App()
self.curprofile : profile
self.plotter = ProfileNotebook()
self.plotter.add('Figure 1') # .figure.add_subplot()
if fsupport != '':
self.cross.sort_along(self.myvec.myzones[0].myvectors[0].asshapely_ls(),'vesdre')
[docs]
def plot_prof(self, index ='', cross = None, active_vector = None):
"""
This method launches the notebook without any wolf GUI.
"""
if index != '':
self.curprofile = self.cross.myprofiles[index]['cs']
self.plotter.notebook.GetPage(0).cs_setter(mycross = self.cross, active_profile = self.curprofile)
elif index == '':
self.plotter.notebook.GetPage(0).cs_setter(mycross = self.cross)
self.app.MainLoop()