"""Lidaxe (flow-accumulation / catchment delineation) companion for WolfMapViewer.
All Lidaxe-related state and logic lives here.
``WolfMapViewer`` holds a single instance as ``self._lidaxe`` and exposes
one-line delegators so external callers remain unaffected.
Design notes
------------
* ``LidaxeManager`` owns the ``active`` reference (the ``Lidaxe`` instance)
and all menu state (``_menu`` hierarchy + item references).
* The wx parent for dialogs is always ``self._viewer``.
* ``menu_build()`` is idempotent — the first call creates and appends the
'Lidaxe' menu to the menubar; subsequent calls are no-ops.
* ``on_menu()`` is bound to the wx menu and dispatches all Lidaxe commands.
* ``activate()`` creates the ``Lidaxe`` instance and calls ``menu_build()``.
"""
from __future__ import annotations
import logging
from typing import TYPE_CHECKING
import wx
from .PyTranslate import _
from .hydrology.flowaccdir import Lidaxe
if TYPE_CHECKING:
from .PyDraw import WolfMapViewer
__all__ = ['LidaxeManager']
[docs]
class LidaxeManager:
"""Companion object that owns Lidaxe state for WolfMapViewer.
Instantiated once as ``viewer._lidaxe = LidaxeManager(viewer)`` inside
``WolfMapViewer.__init__``.
"""
def __init__(self, viewer: 'WolfMapViewer') -> None:
# ── Object reference ─────────────────────────────────────────────────
[docs]
self.active: Lidaxe | None = None
# ── Menu state ───────────────────────────────────────────────────────
[docs]
self._item_pick: wx.MenuItem | None = None
[docs]
self._item_picks: wx.MenuItem | None = None
[docs]
self._item_picks_border: wx.MenuItem | None = None
[docs]
self._item_path: wx.MenuItem | None = None
[docs]
self._item_acc_onzoom: wx.MenuItem | None = None
[docs]
self._item_dir_onzoom: wx.MenuItem | None = None
# ------------------------------------------------------------------
# Activation
# ------------------------------------------------------------------
[docs]
def activate(self) -> None:
"""Create a Lidaxe instance and build the menu (idempotent)."""
self.active = Lidaxe(flow_direction=None, flow_accumulation=None)
self.menu_build()
# ------------------------------------------------------------------
# Menu construction (idempotent)
# ------------------------------------------------------------------
# ------------------------------------------------------------------
# Menu handler
# ------------------------------------------------------------------
[docs]
def _on_find_catchment(self, event: wx.MenuEvent) -> None:
self._viewer.start_action('pick catchment from lidaxe')
[docs]
def _on_find_catchments(self, event: wx.MenuEvent) -> None:
v = self._viewer
if self.active is None:
logging.warning(_('No Lidaxe data available -- Please activate the data and retry !'))
return
if v.active_vector is None:
logging.warning(_('No vector selected -- Please select a vector first !'))
return
catchment, accumulation, direction = self.active.get_catchments(v.active_vector)
if catchment is not None:
v.add_object('array', newobj=catchment, ToCheck=True,
id='catchments_{}'.format(v.active_vector.myname))
if accumulation is not None:
v.add_object('array', newobj=accumulation, ToCheck=True,
id='accumulations_{}'.format(v.active_vector.myname))
accumulation.mypal.distribute_values(0., 10_000.)
v.Refresh()
[docs]
def _on_find_catchments_border(self, event: wx.MenuEvent) -> None:
v = self._viewer
if self.active is None:
logging.warning(_('No Lidaxe data available -- Please activate the data and retry !'))
return
if v.active_vector is None:
logging.warning(_('No vector selected -- Please select a vector first !'))
return
threshold = wx.NumberEntryDialog(
v,
_('Threshold for catchment size (in square meters)'),
_('Threshold'), _('Threshold'),
value=100, min=0, max=1000)
if threshold.ShowModal() == wx.ID_OK:
threshold_value = threshold.GetValue()
else:
threshold_value = 100
threshold.Destroy()
catchment, accumulation, direction = self.active.get_catchments_on_border(
v.active_vector, ignore_smaller_than=threshold_value)
if catchment is not None:
v.add_object('array', newobj=catchment, ToCheck=True,
id='catchments_{}'.format(v.active_vector.myname))
if accumulation is not None:
v.add_object('array', newobj=accumulation, ToCheck=True,
id='accumulations_{}'.format(v.active_vector.myname))
accumulation.mypal.distribute_values(0., 10_000.)
v.Refresh()
[docs]
def _on_find_path(self, event: wx.MenuEvent) -> None:
self._viewer.start_action('pick path from lidaxe')
[docs]
def _on_load_acc_onzoom(self, event: wx.MenuEvent) -> None:
v = self._viewer
a = self.active.get_accumulation_array(v.get_bounds_as_polygon())
if a is not None:
v.add_object('array', newobj=a, ToCheck=True, id='Lidaxe accumulation')
a.mypal.distribute_values(0., 10_000.)
v.Refresh()
[docs]
def _on_load_dir_onzoom(self, event: wx.MenuEvent) -> None:
v = self._viewer
a = self.active.get_direction_array(v.get_bounds_as_polygon())
if a is not None:
v.add_object('array', newobj=a, ToCheck=True, id='Lidaxe direction')
v.Refresh()