diff --git a/examples/serve/panels-demo/my_viewer_ext/my_panel_3.py b/examples/serve/panels-demo/my_viewer_ext/my_panel_3.py index 1a108a8b5..ddeb7d856 100644 --- a/examples/serve/panels-demo/my_viewer_ext/my_panel_3.py +++ b/examples/serve/panels-demo/my_viewer_ext/my_panel_3.py @@ -1,30 +1,65 @@ +import pandas as pd +from typing import Any +import altair as alt +import pyproj +import shapely +import shapely.ops + from chartlets import Component, Input, State, Output from chartlets.components import Box, Button, CircularProgress, Plot, Select, Typography -from xcube.webapi.viewer.contrib import Panel -from xcube.webapi.viewer.contrib import get_dataset +from xcube.webapi.viewer.contrib import Panel, get_dataset +from xcube.webapi.viewer.contrib import get_datasets_ctx from xcube.server.api import Context - +from xcube.constants import CRS_CRS84 +from xcube.core.geom import mask_dataset_by_geometry, normalize_geometry +from xcube.core.gridmapping import GridMapping panel = Panel(__name__, title="Spectral") @panel.layout( - State("@app", "selectedDatasetId",), + State( + "@app", + "selectedDatasetId", + ), State("@app", "selectedTimeLabel"), - State("@app", "selectedPlace"), + State("@app", "selectedPlaceGeometry"), + State("@app", "selectedVariableName"), ) def render_panel( ctx: Context, dataset_id: str, time_label: float, - place_geometry: str, + place_geometry: dict[str, Any], + variable_name: str, ) -> Component: + dataset = get_dataset(ctx, dataset_id) - # plot = Plot(id="plot", chart=None, style={"paddingTop": 6}) - place_text = Typography(id="text", children=[f"selected place_{type(place_geometry)}"], color='pink') - button = Button(id="button", text="ADD", style={"maxWidth": 100}) + plot = Plot(id="plot", chart=None, style={"paddingTop": 6}) + + ds_ctx = get_datasets_ctx(ctx) + ds_configs = ds_ctx.get_dataset_configs() + + text = ( + f"{ds_configs[0]['Title']} " + f"/ {time_label[0:-1]} / " + f"{round(place_geometry['coordinates'][0], 3)}, {round(place_geometry['coordinates'][1], 3)} / " + f"{variable_name}" + # f"{dataset}______________ " + # f"{dataset.variables}_______________" + # f"{dataset.data_vars}" + ) + + place_text = Typography(id="text", children=[text], color="pink") + + wavelengths = {} + for var_name, var in dataset.items(): + if "wavelength" in var.attrs: + wavelengths[var_name] = var.attrs["wavelength"] + + button = Button(id="button", text="ADD Spectral View") # , style={"maxWidth": 100}) controls = Box( children=[button], @@ -38,7 +73,7 @@ def render_panel( ) return Box( - children=[place_text, controls], + children=[place_text, plot, controls], style={ "display": "flex", "flexDirection": "column", @@ -50,3 +85,92 @@ def render_panel( }, ) + +def get_wavelength( + dataset, + time_label: float, + place_geometry: dict[str, Any], +) -> pd.DataFrame: + + grid_mapping = GridMapping.from_dataset(dataset) + place_geometry = normalize_geometry(place_geometry) + if place_geometry is not None and not grid_mapping.crs.is_geographic: + project = pyproj.Transformer.from_crs( + CRS_CRS84, grid_mapping.crs, always_xy=True + ).transform + place_geometry = shapely.ops.transform(project, place_geometry) + + dataset = mask_dataset_by_geometry(dataset, place_geometry) + if dataset is None: + # TODO: set error message in panel UI + print("dataset is None after masking, invalid geometry?") + return None + + if "time" in dataset.coords: + if time_label: + dataset = dataset.sel(time=pd.Timestamp(time_label[0:-1]), method="nearest") + else: + dataset = dataset.isel(time=-1) + + variables = [] + wavelengths = [] + for var_name, var in dataset.items(): + if "wavelength" in var.attrs: + wavelengths.append(var.attrs["wavelength"]) + variables.append(var_name) + + source = [] + for var in variables: + value = dataset[var].item() + source.append({"variable": var, "reflectance": value}) + + results_df = pd.DataFrame(source) + results_df["wavelength"] = wavelengths + return results_df + + # import random + # #reflectances = range(len(wavelengths)) + # reflectances = [random.uniform(min(wavelengths), max(wavelengths)) for _ in + # range(len(wavelengths))] + # + # source = pd.DataFrame( + # {'wavelength': wavelengths, 'reflectance': reflectances} + # ) + # return source + + +@panel.callback( + State("@app", "selectedDatasetId"), + State("@app", "selectedTimeLabel"), + State("@app", "selectedPlaceGeometry"), + Input("button", "clicked"), + Output("plot", "chart"), +) +def update_plot( + ctx: Context, + dataset_id: str | None = None, + time_label: float | None = None, + place_geometry: dict[str, Any] | None = None, + _clicked: bool | None = None, # trigger, will always be True +) -> alt.Chart | None: + dataset = get_dataset(ctx, dataset_id) + + source = get_wavelength(dataset, time_label, place_geometry) + # source = pd.DataFrame( + # {'wavelength': [1, 2, 3, 4], 'reflectance': [1, 2, 3, 4]} + # ) + + # TODO - message if source==None + + chart = ( + alt.Chart(source) + .mark_line(point=True) + .encode( + x="wavelength", + y="reflectance", + # color=alt.Color("reflectance"), + tooltip=["wavelength", "reflectance"], # ,'variable' + ) + ).properties(title="Spectral Chart", width=360, height=160) + + return chart diff --git a/xcube/webapi/viewer/contrib/helpers.py b/xcube/webapi/viewer/contrib/helpers.py index befa9d44c..c1ef469fd 100644 --- a/xcube/webapi/viewer/contrib/helpers.py +++ b/xcube/webapi/viewer/contrib/helpers.py @@ -10,11 +10,3 @@ def get_datasets_ctx(ctx: Context) -> DatasetsContext: def get_dataset(ctx: Context, dataset_id: str | None = None) -> xr.Dataset | None: return get_datasets_ctx(ctx).get_dataset(dataset_id) if dataset_id else None - - -def get_places_ctx(ctx: Context) -> DatasetsContext: - return ctx.get_api_ctx("places") - - -def get_places(ctx: Context, dataset_id: str | None = None) -> xr.Dataset | None: - return get_datasets_ctx(ctx).get_dataset(dataset_id) if dataset_id else None