From 88679e944a04630352f62b13eab2ecde7416f46d Mon Sep 17 00:00:00 2001 From: Tonio Fincke Date: Thu, 8 Feb 2024 14:19:58 +0100 Subject: [PATCH 1/3] access data from wmts --- doors_dashboards/components/scattermap.py | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/doors_dashboards/components/scattermap.py b/doors_dashboards/components/scattermap.py index 295f1ec..4a622f6 100644 --- a/doors_dashboards/components/scattermap.py +++ b/doors_dashboards/components/scattermap.py @@ -93,6 +93,26 @@ def get(self, font_family='Roboto, Helvetica, Arial, sans-serif' ) ) + # img = "http://localhost:8080/wmts/1.0.0/tile/doors-metu-levels~BlackSea_1d_20160101_20160331_bgc_phyto.levels/P4_p/5/8/38.png" + img = "http://localhost:8080/wmts/1.0.0/tile/doors-metu-levels~BlackSea_1d_20160101_20160331_bgc_phyto.levels/P1_Chl/5/8/39.png" + # lon0, lat0, lon1, lat1 = 33.75, 39.375, 39.375, 45.0 + # lon0, lat0, lon1, lat1 = 33.75, 45.0, 39.375, 39.375 + lon0, lat0, lon1, lat1 = 39.375, 45.0, 45.0, 39.375 + coordinates = [[lon0, lat0], [lon1, lat0], [lon1, lat1], [lon0, lat1]] + figure.update_layout( + mapbox_layers=[ + { + "below": 'traces', + "sourcetype": "image", + # "sourceattribution": "Brockmann Consult GmbH", + "source": img, + "coordinates": coordinates + # [ + # "http://localhost:8080/wmts/1.0.0/tile/doors-metu-levels~BlackSea_1d_20160101_20160331_bgc_phyto.levels/P4_p/{z}/{y}/{x}" + # ] + } + ] + ) figure.update_layout(mapbox=mapbox) scattermap_graph = dcc.Graph( From c48d9cf4ade33e40a95e0881cb807746a1f0a42d Mon Sep 17 00:00:00 2001 From: Tonio Fincke Date: Wed, 14 Aug 2024 18:30:17 +0200 Subject: [PATCH 2/3] intermediate state on integrating background image data --- doors_dashboards/components/scattermap.py | 164 +++++++++++++++++++--- 1 file changed, 146 insertions(+), 18 deletions(-) diff --git a/doors_dashboards/components/scattermap.py b/doors_dashboards/components/scattermap.py index 208472f..af4c738 100644 --- a/doors_dashboards/components/scattermap.py +++ b/doors_dashboards/components/scattermap.py @@ -16,6 +16,11 @@ from typing import List from typing import Tuple +# import numpy as np +import base64 +import matplotlib.pyplot as plt +import io + from doors_dashboards.components.constant import ( COLLECTION, @@ -153,25 +158,148 @@ def get( font=dict(family="sans-serif", size=12, color="black"), ), ) - # img = "http://localhost:8080/wmts/1.0.0/tile/doors-metu-levels~BlackSea_1d_20160101_20160331_bgc_phyto.levels/P4_p/5/8/38.png" - img = "http://localhost:8080/wmts/1.0.0/tile/doors-metu-levels~BlackSea_1d_20160101_20160331_bgc_phyto.levels/P1_Chl/5/8/39.png" - # lon0, lat0, lon1, lat1 = 33.75, 39.375, 39.375, 45.0 - # lon0, lat0, lon1, lat1 = 33.75, 45.0, 39.375, 39.375 - lon0, lat0, lon1, lat1 = 39.375, 45.0, 45.0, 39.375 - coordinates = [[lon0, lat0], [lon1, lat0], [lon1, lat1], [lon0, lat1]] + + tilevaluesets = { + 1: { + "lat_start_index": 0, + "lat_end_index": 0, + "lon_start_index": 2, + "lon_end_index": 2, + "resolution": 90, + }, + 2: { + "lat_start_index": 0, + "lat_end_index": 1, + "lon_start_index": 4, + "lon_end_index": 4, + "resolution": 45, + }, + 3: { + "lat_start_index": 1, + "lat_end_index": 2, + "lon_start_index": 9, + "lon_end_index": 9, + "resolution": 22.5, + }, + 4: { + "lat_start_index": 3, + "lat_end_index": 4, + "lon_start_index": 18, + "lon_end_index": 19, + "resolution": 11.25, + }, + 5: { + "lat_start_index": 7, + "lat_end_index": 8, + "lon_start_index": 36, + "lon_end_index": 39, + "resolution": 5.625, + }, + 6: { + "lat_start_index": 15, + "lat_end_index": 17, + "lon_start_index": 73, + "lon_end_index": 78, + "resolution": 2.8125, + }, + 7: { + "lat_start_index": 30, + "lat_end_index": 34, + "lon_start_index": 147, + "lon_end_index": 157, + "resolution": 1.40625, + }, + } + image_layers = [] + z = 6 + tvs = tilevaluesets.get(z) + for lat_index in range(tvs["lat_start_index"], tvs["lat_end_index"] + 1): + lat_0 = 90 - (lat_index * tvs["resolution"]) + lat_1 = lat_0 - tvs["resolution"] + for lon_index in range(tvs["lon_start_index"], tvs["lon_end_index"] + 1): + lon_0 = -180 + (lon_index * tvs["resolution"]) + lon_1 = lon_0 + tvs["resolution"] + img = f"https://doors.api.brockmann-consult.de/api/tiles/cmems-chl-bs/CHL/{z}/{lat_index}/{lon_index}?vmin=0&vmax=40&cbar=chl_DeM2&time=2024-06-29T00%3A00%3A00Z" + + coordinates = [ + [lon_0, lat_0], + [lon_1, lat_0], + [lon_1, lat_1], + [lon_0, lat_1], + ] + image_layers.append( + { + "below": "traces", + "sourcetype": "image", + "source": img, + "coordinates": coordinates, + } + ) + figure.update_layout(mapbox_layers=image_layers) + + # Create a color gradient as an image + gradient = np.linspace(0, 1, 256).reshape(256, 1) + # gradient = np.linspace(0, 1, 256) + # gradient = np.hstack([gradient] * 10) # Stack to create a color bar + gradient = np.hstack([gradient] * 1) # Stack to create a color bar + + # Use Matplotlib to create the image + fig_colorbar, ax = plt.subplots(figsize=(1, 20)) + ax.imshow(gradient, aspect="auto") + ax.axis("off") + + # Save to a BytesIO object + buf = io.BytesIO() + plt.savefig(buf, format="png", bbox_inches="tight", pad_inches=0) + buf.seek(0) + + # Convert the image to a base64 string + image_base64 = base64.b64encode(buf.read()).decode("ascii") + image_base64 = f"data:image/png;base64,{image_base64}" + + # Insert the image into the Plotly figure + figure.add_layout_image( + dict( + source=image_base64, + xref="paper", + yref="paper", + x=1.15, + # y=0.85, # Positioning of the image + y=0.9, # Positioning of the image + sizex=0.05, + sizey=0.9, # Size of the image + # sizey=1.0, # Size of the image + xanchor="left", + yanchor="top", + ) + ) + + # Annotations for the color bar labels + annotations = [ + dict( + x=1.12, + y=0.85, + xref="paper", + yref="paper", + text="High", + showarrow=False, + font=dict(size=12), + ), + dict( + x=1.12, + y=0.15, + xref="paper", + yref="paper", + text="Low", + showarrow=False, + font=dict(size=12), + ), + ] + + # Adjust the layout figure.update_layout( - mapbox_layers=[ - { - "below": 'traces', - "sourcetype": "image", - # "sourceattribution": "Brockmann Consult GmbH", - "source": img, - "coordinates": coordinates - # [ - # "http://localhost:8080/wmts/1.0.0/tile/doors-metu-levels~BlackSea_1d_20160101_20160331_bgc_phyto.levels/P4_p/{z}/{y}/{x}" - # ] - } - ] + annotations=annotations, + margin=dict(l=0, r=120, t=0, b=0), # Space for the color bar ) figure.update_layout(mapbox=mapbox) From c49e00057800250c87fba73713a86b3fd5ae412c Mon Sep 17 00:00:00 2001 From: Tonio Fincke Date: Thu, 15 Aug 2024 18:43:18 +0200 Subject: [PATCH 3/3] scattermap can show background layers --- CHANGES.md | 2 + doors_dashboards/components/scattermap.py | 370 +++++++++++++--------- 2 files changed, 230 insertions(+), 142 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 39e63cf..f6d7241 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,5 +1,7 @@ ## Changes in 0.1.1 (in development) +* Scattermap can show background layers + ## Changes in 0.1 Initial version. diff --git a/doors_dashboards/components/scattermap.py b/doors_dashboards/components/scattermap.py index af4c738..40f1533 100644 --- a/doors_dashboards/components/scattermap.py +++ b/doors_dashboards/components/scattermap.py @@ -9,8 +9,10 @@ import matplotlib import numpy as np import os +from PIL import Image import plotly.graph_objs as go import random +import requests from shapely.geometry import Point from typing import Dict from typing import List @@ -36,10 +38,210 @@ DEFAULT_COLOR_RANGE = "viridis" DEFAULT_SELECTION_COLOR = "#CC0000" -SELECTION_COLOR = DEFAULT_SELECTION_COLOR DEFAULT_SELECTION_SIZE = 17 +HEADERS = {"accept": "application/json"} +SELECTION_COLOR = DEFAULT_SELECTION_COLOR SELECTION_SIZE = DEFAULT_SELECTION_SIZE +TILE_VALUE_SETS = { + 1: { + "lat_start_index": 0, + "lat_end_index": 0, + "lon_start_index": 2, + "lon_end_index": 2, + "resolution": 90, + }, + 2: { + "lat_start_index": 0, + "lat_end_index": 1, + "lon_start_index": 4, + "lon_end_index": 4, + "resolution": 45, + }, + 3: { + "lat_start_index": 1, + "lat_end_index": 2, + "lon_start_index": 9, + "lon_end_index": 9, + "resolution": 22.5, + }, + 4: { + "lat_start_index": 3, + "lat_end_index": 4, + "lon_start_index": 18, + "lon_end_index": 19, + "resolution": 11.25, + }, + 5: { + "lat_start_index": 7, + "lat_end_index": 8, + "lon_start_index": 36, + "lon_end_index": 39, + "resolution": 5.625, + }, + 6: { + "lat_start_index": 15, + "lat_end_index": 17, + "lon_start_index": 73, + "lon_end_index": 78, + "resolution": 2.8125, + }, + 7: { + "lat_start_index": 30, + "lat_end_index": 34, + "lon_start_index": 147, + "lon_end_index": 157, + "resolution": 1.40625, + }, +} +TILE_ZOOM_LEVEL = 6 + +XCUBE_COLOR_BAR_URL = "https://doors.api.brockmann-consult.de/api/colorbars" + + +def get_color_bars(): + response = requests.get(XCUBE_COLOR_BAR_URL, headers=HEADERS) + if response.status_code == 200: + return response.json() + + +XCUBE_COLOR_BARS = get_color_bars() + + +def get_color_map_image_stream(var_name: str): + bd = BACKGROUND_DEFINITONS.get(var_name) + cm_name = bd.get("colormap") + for color_bar_group in XCUBE_COLOR_BARS: + for color_bar in color_bar_group[2]: + if color_bar[0] == cm_name: + cbar_png_str = color_bar[1] + cbar_png_bytes = base64.b64decode(cbar_png_str) + stream = io.BytesIO(cbar_png_bytes) + return stream + +XCUBE_SERVER_BASE_TILE_URL = "https://doors.api.brockmann-consult.de/api/tiles/{0}/{1}/{2}/{3}/{4}?vmin={5}&vmax={6}&cbar={7}&time={8}" +XCUBE_SERVER_BASE_TIME_URL = "https://doors.api.brockmann-consult.de/api/datasets/{0}/coords/time" + +BACKGROUND_DEFINITONS = { + "chlorophyll": { + "vmin": 0, + "vmax": 40, + "colormap": "chl_DeM2", + "dataset_name": "cmems-chl-bs", + "variable_name": "CHL", + "title": "CHL [milligram m^-3]" + }, + "salinity": { + "vmin": 10, + "vmax": 20, + "colormap": "haline", + "dataset_name": "cmcc-sal-bs", + "variable_name": "so", + "title": "Salinity [PSU]" + }, + "sst": { + "vmin": 280, + "vmax": 302, + "colormap": "thermal", + "dataset_name": "cmems-sst-bs", + "variable_name": "analysed_sst", + "title": "Analysed SST [Kelvin]" + }, +} + + +def get_time_coords(var_name: str) -> List[str]: + bd = BACKGROUND_DEFINITONS.get(var_name) + time_url = XCUBE_SERVER_BASE_TIME_URL.format(bd["dataset_name"]) + response = requests.get(time_url, headers=HEADERS) + if response.status_code == 200: + return response.json().get("coordinates") + + +def get_last_time_coord(var_name: str) -> List[str]: + return get_time_coords(var_name)[-1] + + +def get_background_image_layers(var_name: str) -> List[Dict]: + bd = BACKGROUND_DEFINITONS.get(var_name) + image_layers = [] + tvs = TILE_VALUE_SETS.get(TILE_ZOOM_LEVEL) + time = get_last_time_coord(var_name) + for lat_index in range(tvs["lat_start_index"], tvs["lat_end_index"] + 1): + lat_0 = 90 - (lat_index * tvs["resolution"]) + lat_1 = lat_0 - tvs["resolution"] + for lon_index in range(tvs["lon_start_index"], tvs["lon_end_index"] + 1): + lon_0 = -180 + (lon_index * tvs["resolution"]) + lon_1 = lon_0 + tvs["resolution"] + # time = "2024-06-29T00%3A00%3A00Z" + img = XCUBE_SERVER_BASE_TILE_URL.format( + bd["dataset_name"], + bd["variable_name"], + TILE_ZOOM_LEVEL, + lat_index, + lon_index, + bd["vmin"], + bd["vmax"], + bd["colormap"], + time + ) + coordinates = [ + [lon_0, lat_0], + [lon_1, lat_0], + [lon_1, lat_1], + [lon_0, lat_1], + ] + image_layers.append( + { + "below": "traces", + "sourcetype": "image", + "source": img, + "coordinates": coordinates, + } + ) + return image_layers + + +def get_annotations(var_name: str): + bd = BACKGROUND_DEFINITONS.get(var_name) + time = get_last_time_coord(var_name) + annotations = [ + dict( + x=0.02, + y=-0.05, + xref="paper", + yref="paper", + text=bd["variable_name"], + showarrow=False, + font=dict(size=14), + ), + dict( + x=0.02, + y=-0.1, + xref="paper", + yref="paper", + text=time, + showarrow=False, + font=dict(size=14), + ) + ] + vmin = bd["vmin"] + vmax = bd["vmax"] + vrange = (vmax - vmin) / 5 + for i in range(6): + annotations.append( + dict( + x=0.2 + (i * 0.15), + y=-0.075, + xref="paper", + yref="paper", + text=vmin + (i * vrange), + showarrow=True, + font=dict(size=12), + ) + ) + return annotations + def get_center(lons: List[float], lats: List[float]) -> Tuple[float, float]: center_lon = min(lons) + (max(lons) - min(lons)) / 2 @@ -71,6 +273,7 @@ def get( marker_size = sub_config.get("marker_size", 10) mapbox_style = sub_config.get("mapbox_style", "carto-positron") selected_variable = sub_config.get("selected_variable", "") + background_variable = sub_config.get("background_variable", "") figure = go.Figure() @@ -159,149 +362,32 @@ def get( ), ) - tilevaluesets = { - 1: { - "lat_start_index": 0, - "lat_end_index": 0, - "lon_start_index": 2, - "lon_end_index": 2, - "resolution": 90, - }, - 2: { - "lat_start_index": 0, - "lat_end_index": 1, - "lon_start_index": 4, - "lon_end_index": 4, - "resolution": 45, - }, - 3: { - "lat_start_index": 1, - "lat_end_index": 2, - "lon_start_index": 9, - "lon_end_index": 9, - "resolution": 22.5, - }, - 4: { - "lat_start_index": 3, - "lat_end_index": 4, - "lon_start_index": 18, - "lon_end_index": 19, - "resolution": 11.25, - }, - 5: { - "lat_start_index": 7, - "lat_end_index": 8, - "lon_start_index": 36, - "lon_end_index": 39, - "resolution": 5.625, - }, - 6: { - "lat_start_index": 15, - "lat_end_index": 17, - "lon_start_index": 73, - "lon_end_index": 78, - "resolution": 2.8125, - }, - 7: { - "lat_start_index": 30, - "lat_end_index": 34, - "lon_start_index": 147, - "lon_end_index": 157, - "resolution": 1.40625, - }, - } - image_layers = [] - z = 6 - tvs = tilevaluesets.get(z) - for lat_index in range(tvs["lat_start_index"], tvs["lat_end_index"] + 1): - lat_0 = 90 - (lat_index * tvs["resolution"]) - lat_1 = lat_0 - tvs["resolution"] - for lon_index in range(tvs["lon_start_index"], tvs["lon_end_index"] + 1): - lon_0 = -180 + (lon_index * tvs["resolution"]) - lon_1 = lon_0 + tvs["resolution"] - img = f"https://doors.api.brockmann-consult.de/api/tiles/cmems-chl-bs/CHL/{z}/{lat_index}/{lon_index}?vmin=0&vmax=40&cbar=chl_DeM2&time=2024-06-29T00%3A00%3A00Z" - - coordinates = [ - [lon_0, lat_0], - [lon_1, lat_0], - [lon_1, lat_1], - [lon_0, lat_1], - ] - image_layers.append( - { - "below": "traces", - "sourcetype": "image", - "source": img, - "coordinates": coordinates, - } + if background_variable in list(BACKGROUND_DEFINITONS.keys()): + image_layers = get_background_image_layers(background_variable) + figure.update_layout(mapbox_layers=image_layers) + + color_map_image_stream = get_color_map_image_stream(background_variable) + color_map_image = Image.open(color_map_image_stream) + figure.add_layout_image( + dict( + source=color_map_image, + xref="paper", + yref="paper", + x=0.2, + y=-0.075, # Positioning of the image + sizex=0.75, + sizey=0.5, # Size of the image + xanchor="left", + yanchor="top", ) - figure.update_layout(mapbox_layers=image_layers) - - # Create a color gradient as an image - gradient = np.linspace(0, 1, 256).reshape(256, 1) - # gradient = np.linspace(0, 1, 256) - # gradient = np.hstack([gradient] * 10) # Stack to create a color bar - gradient = np.hstack([gradient] * 1) # Stack to create a color bar - - # Use Matplotlib to create the image - fig_colorbar, ax = plt.subplots(figsize=(1, 20)) - ax.imshow(gradient, aspect="auto") - ax.axis("off") - - # Save to a BytesIO object - buf = io.BytesIO() - plt.savefig(buf, format="png", bbox_inches="tight", pad_inches=0) - buf.seek(0) - - # Convert the image to a base64 string - image_base64 = base64.b64encode(buf.read()).decode("ascii") - image_base64 = f"data:image/png;base64,{image_base64}" - - # Insert the image into the Plotly figure - figure.add_layout_image( - dict( - source=image_base64, - xref="paper", - yref="paper", - x=1.15, - # y=0.85, # Positioning of the image - y=0.9, # Positioning of the image - sizex=0.05, - sizey=0.9, # Size of the image - # sizey=1.0, # Size of the image - xanchor="left", - yanchor="top", ) - ) - - # Annotations for the color bar labels - annotations = [ - dict( - x=1.12, - y=0.85, - xref="paper", - yref="paper", - text="High", - showarrow=False, - font=dict(size=12), - ), - dict( - x=1.12, - y=0.15, - xref="paper", - yref="paper", - text="Low", - showarrow=False, - font=dict(size=12), - ), - ] - - # Adjust the layout - figure.update_layout( - annotations=annotations, - margin=dict(l=0, r=120, t=0, b=0), # Space for the color bar - ) - figure.update_layout(mapbox=mapbox) + color_map_image_stream.close() + annotations = get_annotations(background_variable) + figure.update_layout( + annotations=annotations, + margin=dict(l=0, r=0, t=0, b=100), # Space for the color bar + ) + figure.update_layout(mapbox=mapbox) scattermap_graph = dcc.Graph( id=sub_component_id, figure=figure, style={"height": "81.5vh"}