Source code for wolfhece._lidaxe_manager

"""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:
[docs] self._viewer = viewer
# ── Object reference ─────────────────────────────────────────────────
[docs] self.active: Lidaxe | None = None
# ── Menu state ───────────────────────────────────────────────────────
[docs] self._menu: wx.Menu | None = None
[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) # ------------------------------------------------------------------
[docs] def menu_build(self) -> None: """Create and append the 'Lidaxe' menu to the viewer menubar. Safe to call multiple times — only the first call has any effect. """ if self._menu is not None: return v = self._viewer self._menu = wx.Menu() self._item_pick = self._menu.Append( wx.ID_ANY, _("Find catchment"), _("Find the catchment corresponding to the clicked pixel position")) self._item_picks = self._menu.Append( wx.ID_ANY, _("Find catchments"), _("Find the catchments corresponding to the active vector pixels positions")) self._item_picks_border = self._menu.Append( wx.ID_ANY, _("Find catchments on border"), _("Find the catchments corresponding to the active vector pixels positions on border only")) self._item_path = self._menu.Append( wx.ID_ANY, _("Find path"), _("Find the longest path from clicked pixel to upstream")) self._menu.AppendSeparator() self._item_acc_onzoom = self._menu.Append( wx.ID_ANY, _("Load accumulation on zoom"), _("Load the flow accumulation for the current view")) self._item_dir_onzoom = self._menu.Append( wx.ID_ANY, _("Load flow direction on zoom"), _("Load the flow direction for the current view")) v.menubar.Append(self._menu, _('Lidaxe')) self._menu.Bind(wx.EVT_MENU, self._on_find_catchment, self._item_pick) self._menu.Bind(wx.EVT_MENU, self._on_find_catchments, self._item_picks) self._menu.Bind(wx.EVT_MENU, self._on_find_catchments_border,self._item_picks_border) self._menu.Bind(wx.EVT_MENU, self._on_find_path, self._item_path) self._menu.Bind(wx.EVT_MENU, self._on_load_acc_onzoom, self._item_acc_onzoom) self._menu.Bind(wx.EVT_MENU, self._on_load_dir_onzoom, self._item_dir_onzoom)
# ------------------------------------------------------------------ # 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()