Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 18 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -145,4 +145,21 @@ ui-tests/benchmark-results
ui-tests/jlab_root

.yarn/
yarn.lock
yarn.lock

# Local working files: agent configs, design notes and teaching material that
# live alongside the repo but are not part of it.
.codex
AGENTS.md
docs/
notebooks/
sampling-theory/

# Artifacts of running the app or the test suite against the bundled sample map.
# aa_test_congo.tif itself IS tracked (it backs the "Sample map" button); GDAL
# writes the .aux.xml statistics sidecar next to whatever raster it opens.
tests/data/*.aux.xml
tests/data/sae_design_aa_test_congo/

# Scratch raster for manual testing, 46M and referenced by nothing.
tests/data/hansen_bolivia.tif
6 changes: 3 additions & 3 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ Features
accepts a separate area CSV. Confidence levels of 90/95/99% are supported.
Collect Earth Online ingestion and accuracy standard errors are planned for a
later release.
- **SEPAL-UI Integration:** Built using ``sepal-ui`` for a seamless experience within the SEPAL environment.
- **pysepal Integration:** Built using ``pysepal`` for a seamless experience within the SEPAL environment.

Prerequisites
-------------
Expand All @@ -49,7 +49,7 @@ Prerequisites
- The following Python libraries (and their dependencies) should be available in your SEPAL environment. The application checks for these and prints warnings if they are missing:

- ``ipyvuetify``
- ``sepal_ui``
- ``pysepal``
- ``pandas``
- ``numpy``
- ``ipywidgets``
Expand Down Expand Up @@ -126,7 +126,7 @@ Technology Stack
----------------

- **UI Framework:** ``ipyvuetify`` (for Jupyter-based UI components)
- **SEPAL Integration:** ``sepal-ui`` (for SEPAL-specific widgets and model structure)
- **SEPAL Integration:** ``pysepal`` (for SEPAL-specific widgets and model structure)
- **Mapping:** ``ipyleaflet`` (for interactive map display)
- **Core Processing:** ``pandas``, ``numpy``
- **Geospatial Libraries:**
Expand Down
63 changes: 20 additions & 43 deletions app.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,19 +15,30 @@
if os.path.exists(proj_data):
os.environ["PROJ_DATA"] = proj_data

# Both tile servers bind 127.0.0.1 inside the kernel, so the browser needs a route
# that reaches them. SEPAL sets LOCALTILESERVER_CLIENT_PREFIX to a
# jupyter-server-proxy route that forwards any port in the sandbox, so it carries
# PMTiles as well as raster tiles -- but vectortileserver never autodetects one,
# and left alone its layers keep a URL the browser cannot reach, so the points
# silently never arrive. Only borrow the generic /proxy/{port} form: it forwards
# any port by construction, while a route namespaced to one server (localtileserver's
# own autodetected prefix) would not serve a vector port.
_raster_prefix = os.environ.get("LOCALTILESERVER_CLIENT_PREFIX")
if _raster_prefix and "/proxy/{port}" in _raster_prefix:
os.environ.setdefault("VECTORTILESERVER_CLIENT_PREFIX", _raster_prefix)

import logging

import solara
from sepal_ui.logger import setup_logging
from sepal_ui.sepalwidgets.vue_app import MapApp, ThemeToggle
from sepal_ui.solara import (
ThemeState,
from pysepal.logger import setup_logging
from pysepal.sepalwidgets.vue_app import MapApp
from pysepal.solara import (
NotificationProvider,
get_current_theme_state,
setup_sessions,
setup_solara_server,
setup_theme_colors,
)
from sepal_ui.solara.notifications import NotificationProvider
from solara.lab.components.theming import theme

from component.model.app_model import AppModel
from component.tile.upload import RasterMapWatcher
Expand All @@ -48,33 +59,6 @@
USE_GEE = False


@solara.component
def _TileLoopbackBridge():
"""Mount jupyter_loopback's comm bridge so localhost tile fetches survive a proxy.

localtileserver + vectortileserver serve tiles on ``127.0.0.1:<port>``, which
the browser can't reach behind SEPAL / ``run-solara --serve`` (CSP forbids
connecting to localhost). The bridge reroutes those fetches over Solara's
websocket, but must be enabled BEFORE any tile client calls
``intercept_localhost`` (on layer build) or that shim is a no-op -- hence
mounting it here at Page load. Default on; opt out with
``LOCALTILESERVER_COMM_BRIDGE=0``.
"""
_flag = os.environ.get("LOCALTILESERVER_COMM_BRIDGE", "1").strip().lower()
enabled = _flag not in ("0", "false", "no", "off")

def _enable():
if not enabled:
return None
import jupyter_loopback

return jupyter_loopback.enable_comm_bridge(display=False)

bridge = solara.use_memo(_enable, [])
if bridge is not None:
solara.display(bridge) # its ESM installs the browser interceptor


@solara.lab.on_kernel_start
def on_kernel_start():
return setup_sessions()
Expand All @@ -84,10 +68,7 @@ def on_kernel_start():
# @with_sepal_sessions(module_name="sbae_app")
def Page():
"""Main SBAE application page using MapApp layout."""
# pysepal's MapApp requires a per-kernel ThemeState. In a local (non-SEPAL)
# run the session manager is active but has no theme_state component, so
# get_current_theme_state() would raise; provide an explicit one instead.
theme_state = solara.use_memo(ThemeState, [])
theme_state = get_current_theme_state()

# Notification system (pysepal): mount the provider once at the app root,
# before any component that calls use_notifications(). Kept in the same page
Expand All @@ -96,14 +77,11 @@ def Page():
# process-local default and the toasts/pill stay light under a dark app.
NotificationProvider(theme_state=theme_state)
ErrorToastBridge()
_TileLoopbackBridge()

app_model = AppModel()

setup_theme_colors()
theme_toggle = ThemeToggle()
theme_toggle.observe(lambda e: setattr(theme, "dark", e["new"]), "dark")
sbae_map = SbaeMap(theme_toggle=theme_toggle, gee=USE_GEE)
sbae_map = SbaeMap(theme_state=theme_state, gee=USE_GEE)

RasterMapWatcher(sbae_map)
# Floating legend overlay for the sample/reference points (bottom-center).
Expand Down Expand Up @@ -135,7 +113,7 @@ def Page():
# longer appear while the user is on the Analysis tab.
right_panel_content = [
{
"content": [SampleConfiguration(sbae_map, theme_toggle=theme_toggle)],
"content": [SampleConfiguration(sbae_map, theme_state=theme_state)],
},
]

Expand All @@ -146,7 +124,6 @@ def Page():
main_map=[sbae_map],
steps_data=steps_data,
initial_step=4,
theme_toggle=[theme_toggle],
theme_state=theme_state,
dialog_width=900,
right_panel_config=right_panel_config,
Expand Down
2 changes: 1 addition & 1 deletion component/model/app_model.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from sepal_ui.model import Model
from pysepal.model import Model
from traitlets import Int


Expand Down
3 changes: 3 additions & 0 deletions component/model/state_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -532,6 +532,9 @@ def clear_file_data(self):
self.raster_optimization_status.value = "idle"
self.raster_optimization_error.value = None
self.optimized_raster_path.value = None
# the map layer is gated on this being non-empty, so a stale palette
# would let the next file render in the previous file's colours
self.class_colors.value = {}

def clear_aoi_data(self):
"""Clear all AOI-related data (for simple/systematic sampling).
Expand Down
2 changes: 1 addition & 1 deletion component/scripts/logging_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@

# rio-tiler emits ``NoOverviewWarning`` once per tile when a source raster has no
# overviews. The raster-add paths now build overviews up front
# (``SbaeMap.add_class_raster`` -> ``prepare_for_tiles``), so this warning is
# (``SepalMap.add_raster`` -> ``prepare_for_tiles``), so this warning is
# non-actionable noise here; silence it so any raster that still slips through a
# fallback path cannot flood the logs with hundreds of identical lines.
try:
Expand Down
Loading
Loading