Skip to content

Commit 06db641

Browse files
committed
feat: Update documentation with new CLI usage examples and plugin details
1 parent 5e7162b commit 06db641

3 files changed

Lines changed: 24 additions & 191 deletions

File tree

.github/implementors-guide.md

Lines changed: 0 additions & 188 deletions
Original file line numberDiff line numberDiff line change
@@ -459,191 +459,3 @@ Curve labels are `station/variable` when multiple variables are selected, or jus
459459
- When `normalize_ref()` cannot determine a `station` value, set it to a non-empty
460460
fallback (e.g. the `source` basename) so the auto-derived catalog name is stable.
461461
462-
---
463-
464-
## 14. CLI Launch Pattern — Manager CRS and the `dvue ui --plugin` Command
465-
466-
### Manager-declared CRS
467-
468-
Every `DataUIManager` subclass has a `crs = None` class attribute. Override it as
469-
an **instance attribute** in `__init__` to declare the map projection:
470-
471-
```python
472-
import cartopy.crs as ccrs
473-
474-
class MyUIManager(RegistryUIManager):
475-
def __init__(self, **kwargs):
476-
super().__init__(**kwargs)
477-
self.crs = ccrs.epsg("26910") # NAD83 UTM 10N — California Delta
478-
```
479-
480-
`serve_session_app` and `serve_desktop_app` probe the manager's `crs` and
481-
`station_id_column` automatically when the **caller** does not supply them, so
482-
the serve calls in CLI commands can be written without those arguments:
483-
484-
```python
485-
def build_manager():
486-
return MyUIManager()
487-
488-
# ✅ CRS is probed automatically from MyUIManager().crs
489-
_serve(build_manager, title="My App", port=port)
490-
491-
# ❌ Repeating the same information — fragile if CRS ever changes
492-
_serve(build_manager, title="My App", port=port, crs=ccrs.epsg("26910"))
493-
```
494-
495-
Caller-provided values always win — the probe only fills in `None` gaps. This lets
496-
integration tests override CRS without modifying the manager class.
497-
498-
### Thin CLI wrapper recipe
499-
500-
```python
501-
import click
502-
from dvue.session_persistence import serve_desktop_app, serve_session_app
503-
504-
@click.command("mytool")
505-
@click.argument("files", nargs=-1, required=False)
506-
@click.option("--port", default=5006)
507-
@click.option("--desktop", is_flag=True, default=False)
508-
def show_my_ui(files, port, desktop):
509-
import panel as pn; pn.extension()
510-
511-
def build_manager():
512-
mgr = MyUIManager()
513-
if files:
514-
mgr.add_source_files(list(files))
515-
return mgr
516-
517-
_serve = serve_desktop_app if desktop else serve_session_app
518-
_serve(build_manager, title="My Tool", port=port)
519-
```
520-
521-
Register it in `dvue.cli` via `dvue ui --plugin mypackage.mymodule`:
522-
523-
```bash
524-
dvue ui --plugin mypackage.mymodule path/to/data.ext --desktop
525-
```
526-
527-
The `dvue ui` command imports the plugin module (which calls `ReaderRegistry.register()`
528-
at module level) then runs `serve_session_app` / `serve_desktop_app` with drag-and-drop
529-
support.
530-
531-
---
532-
533-
## 15. Geo Loading — `load_geo_dataframe` and `add_geo_source`
534-
535-
### `dvue.utils.load_geo_dataframe`
536-
537-
```python
538-
from dvue.utils import load_geo_dataframe
539-
540-
gdf = load_geo_dataframe(
541-
path, # .geojson / .json / .shp / .gpkg / .csv
542-
lat_col="lat", # CSV only: latitude column (WGS84)
543-
lon_col="lon", # CSV only: longitude column (WGS84)
544-
id_col=None, # informational; used by callers for join key tracking
545-
crs=None, # override auto-detected CRS
546-
)
547-
```
548-
549-
Auto-detection for CSV files (in priority order):
550-
551-
| Columns present | Auto-detected CRS |
552-
|-----------------|-------------------|
553-
| `lat` / `lon` (or user-specified) | EPSG:4326 (WGS84) |
554-
| `utm_easting` / `utm_northing` | EPSG:26910 (NAD83 UTM 10N) |
555-
| `easting` / `northing` | EPSG:26910 |
556-
557-
GeoJSON/shapefile/GeoPackage formats are passed directly to `geopandas.read_file`.
558-
559-
### `RegistryUIManager.add_geo_source`
560-
561-
```python
562-
class MyUIManager(RegistryUIManager):
563-
def on_file_added(self, path, refs):
564-
# Auto-load geometry when the first data file is added
565-
if not getattr(self, "_geo_loaded", False):
566-
self.add_geo_source(
567-
"/path/to/stations.geojson",
568-
id_column="STATION_ID", # column in geo file
569-
station_column="station", # column in catalog to join on
570-
)
571-
self._geo_loaded = True
572-
```
573-
574-
After `add_geo_source`:
575-
576-
- `get_data_catalog()` returns a `GeoDataFrame` with a `geometry` column.
577-
- Map display in `DataUI` automatically picks up geometry when `station_id_column`
578-
matches the join column.
579-
- If `self.crs` is `None` and the loaded geo file has a CRS, `self.crs` is set
580-
automatically from the file's EPSG code via `cartopy.crs.epsg()`.
581-
- The geo merge is re-applied automatically whenever the catalog grows (e.g. when
582-
additional files are dropped after the initial load).
583-
584-
### Geo merge rules
585-
586-
- The merge is a **left join** from catalog to geo data on `station_column`.
587-
Catalog rows without a matching geo entry retain `NaN` geometry.
588-
- Duplicate `id_column` values in the geo file are deduplicated before the merge
589-
(first occurrence wins).
590-
- If `geometry` already exists in the catalog DataFrame (from a previous `add_geo_source`
591-
call), it is stripped before re-merging to avoid pandas column conflicts.
592-
593-
---
594-
595-
## 16. Multi-`ref_type` Scanning — One Scanner, Several Loaders
596-
597-
A single scanner class (registered for an extension) can return refs of **mixed
598-
`ref_type` values** — each `ref_type` can resolve to a different loader.
599-
600-
### Pattern
601-
602-
```python
603-
class _BCRef(DataReference):
604-
ref_type = "bc_flow" # → BCFlowLoader
605-
606-
class _OutputRef(DataReference):
607-
ref_type = "dss_output" # → DSM2DSSReader (already registered)
608-
609-
class EchoScanner:
610-
def __init__(self, source): self._source = source
611-
612-
@classmethod
613-
def scan(cls, path):
614-
refs = []
615-
...
616-
# Input boundary ref — uses BCFlowLoader
617-
refs.append(_BCRef(source=path, FILE=dss_file, PATH=dss_path, SIGN=sign, ...))
618-
# Output channel ref — reuses DSM2DSSReader (source = DSS file)
619-
refs.append(_OutputRef(source=dss_file, NAME=name, VARIABLE=var, ...))
620-
return refs
621-
622-
# Scanner registered for .inp — loaders registered for each ref_type
623-
ReaderRegistry.register(EchoScanner, ref_type="echo_inp", extensions=[".inp"])
624-
ReaderRegistry.register(BCFlowLoader, ref_type="bc_flow")
625-
# DSM2DSSReader is already registered for ref_type="dsm2_dss" elsewhere
626-
```
627-
628-
### Key points
629-
630-
- `ref.source` is the path passed to the **loader**, not necessarily the scan path.
631-
When output refs point at a DSS file, set `source=dss_file` so the loader opens
632-
the right file.
633-
- The `ReaderRegistry` caches loaders by `(ref_type, source)`, so multiple refs from
634-
the same DSS file share one reader instance.
635-
- `RegistryUIManager._dvue_catalog` accepts mixed ref types in the same catalog.
636-
Use a `primary_key` that includes a `category` or `TABLE` column to keep input and
637-
output refs uniquely keyed:
638-
639-
```python
640-
self._dvue_catalog = DataCatalog(
641-
primary_key=["category", "TABLE", "station", "variable"]
642-
)
643-
```
644-
645-
- Conflict warning: registering two reader classes for the **same extension** triggers
646-
a `WARNING` log message at register time (last-write-wins). To avoid the conflict,
647-
do not register an extension entry for the "generic" reader — instead bypass the
648-
extension map in `add_source_files()` and call `YourReader.scan(path)` directly.
649-

AGENTS.md

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,6 @@ Use this file when working in the `dvue/` workspace root.
1717
3. Run examples:
1818
- `panel serve examples/ex_basic_tsdataui.py --show`
1919
- `panel serve examples/ex_tsdataui.py --show`
20-
4. Launch the generic file-viewer CLI (requires reader plugins to be installed):
21-
- `dvue ui --plugin dsm2ui.dsm2ui --desktop` ← DSM2 HDF5 + DSS files
22-
- `dvue ui --plugin <pkg.module> [files...]` ← any registered format
2320

2421
## Key Design Rules
2522

README.md

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,16 @@ dvue ui --plugin dsm2ui.dsm2ui --desktop
9797
# Pre-load specific files; mix .h5 and .dss freely
9898
dvue ui --plugin dsm2ui.dsm2ui run.h5 hist_qual.dss hist_hydro.dss
9999

100+
# Combined DSM2 input + output viewer from an echo .inp file
101+
dvue ui --plugin dsm2ui.echo_plugin output/run_hydro_echo.inp --desktop
102+
103+
# Generic DSS file browser (all C-parts, no DSM2 filter)
104+
dvue ui --plugin dsm2ui.dssui.dss_registry output.dss
105+
106+
# DSS browser with station geometry for map display
107+
dvue ui --plugin dsm2ui.dssui.dss_registry output.dss \
108+
--geo-file stations.geojson --geo-id-column STATION_ID
109+
100110
# Multiple plugin packages — each registers its own file extensions
101111
dvue ui --plugin dsm2ui.dsm2ui --plugin schismviz.readers output.staout run.h5
102112

@@ -122,6 +132,20 @@ the manager, so all registered readers are available when the catalog is
122132
built. Dropping additional files onto the running window after launch also
123133
works — the registry resolves the reader from the dropped file's extension.
124134

135+
### Known plugins (dsm2ui)
136+
137+
| `--plugin` module | File types handled | Equivalent CLI shortcut |
138+
|---|---|---|
139+
| `dsm2ui.dsm2ui` | `.h5` / `.hdf5` (tidefiles), `.dss` (DSM2 output channels) | `dsm2ui ui output` / `dsm2ui ui tide` |
140+
| `dsm2ui.echo_plugin` | `.inp` (DSM2 echo files — input BC + output channels) | `dsm2ui ui echo` |
141+
| `dsm2ui.dssui.dss_registry` | `.dss` (all C-parts, generic browser) | `dsm2ui ui dss` |
142+
143+
> **Extension conflict note:** `dsm2ui.dsm2ui` registers `.dss` for *DSM2 output channels only*
144+
> (filters to `FLOW`, `STAGE`, `EC`, etc.). `dsm2ui.dssui.dss_registry` reads *all* C-parts.
145+
> If both are loaded, the last `register()` call wins and a warning is logged. Load only the
146+
> plugin that matches your use-case, or use the dedicated `dsm2ui ui` sub-commands which each
147+
> load the correct reader automatically.
148+
125149
### `dvue show-version`
126150

127151
```bash

0 commit comments

Comments
 (0)