From 0d70242697b684522ca628b45bec0ddf9bab500f Mon Sep 17 00:00:00 2001 From: Stefan Kuethe Date: Mon, 15 Jan 2024 17:40:40 +0100 Subject: [PATCH 01/58] black --- examples/experimental/flights.py | 10 ++++++++-- maplibre/server.py | 8 ++++++-- 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/examples/experimental/flights.py b/examples/experimental/flights.py index 5026d750..24e783b6 100644 --- a/examples/experimental/flights.py +++ b/examples/experimental/flights.py @@ -2,8 +2,14 @@ import pandas as pd import shapely -from maplibre import (Layer, LayerType, Map, MapContext, output_maplibregl, - render_maplibregl) +from maplibre import ( + Layer, + LayerType, + Map, + MapContext, + output_maplibregl, + render_maplibregl, +) from maplibre.basemaps import Carto from maplibre.utils import GeometryType, df_to_geojson from shiny import App, reactive, ui diff --git a/maplibre/server.py b/maplibre/server.py index b4827073..165932ae 100644 --- a/maplibre/server.py +++ b/maplibre/server.py @@ -1,7 +1,11 @@ from __future__ import annotations -from shiny.render.transformer import (TransformerMetadata, ValueFn, - output_transformer, resolve_value_fn) +from shiny.render.transformer import ( + TransformerMetadata, + ValueFn, + output_transformer, + resolve_value_fn, +) from shiny.session import get_current_session from .map import Map From f588e780dbf3292d5e6146f4a185382c3a4308bd Mon Sep 17 00:00:00 2001 From: Stefan Kuethe Date: Mon, 15 Jan 2024 17:59:25 +0100 Subject: [PATCH 02/58] Add description --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 2faec20e..e5da501b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,7 +1,7 @@ [tool.poetry] name = "maplibre" version = "0.1.1" -description = "" +description = "Python bindings for MapLibre GL JS" authors = ["Stefan Kuethe "] readme = "README.md" license = "MIT" From 3d223bf338ca51389831d04d737489aabd1683d3 Mon Sep 17 00:00:00 2001 From: Stefan Kuethe Date: Tue, 16 Jan 2024 07:59:58 +0100 Subject: [PATCH 03/58] Add docstrings --- maplibre/controls.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/maplibre/controls.py b/maplibre/controls.py index 62b0cab8..ceba25eb 100644 --- a/maplibre/controls.py +++ b/maplibre/controls.py @@ -92,6 +92,8 @@ def type(self): class AttributionControl(Control): + """Attribution control""" + # _name: str = ControlType.ATTRIBUTION.value compact: bool = None custom_attribution: Union[str, list] = Field( @@ -115,6 +117,8 @@ class FullscreenControl(Control): class GeolocateControl(Control): + """Geolocate control""" + # _name: str = ControlType.GEOLOCATE.value position_options: dict = Field(None, serialization_alias="positionOptions") show_accuracy_circle: bool = Field(True, serialization_alias="showAccuracyCircle") @@ -124,8 +128,10 @@ class GeolocateControl(Control): class NavigationControl(Control): + """Navigation control""" + # _name: str = ControlType.NAVIGATION.value - sho_compass: bool = Field(True, serialization_alias="showCompass") + show_compass: bool = Field(True, serialization_alias="showCompass") show_zoom: bool = Field(True, serialization_alias="showZoom") visualize_pitch: bool = Field(False, serialization_alias="visualizePitch") @@ -137,6 +143,8 @@ class ScaleUnit(Enum): class ScaleControl(Control): + """Scale control""" + # _name: str = ControlType.SCALE.value max_width: int = Field(None, serialization_alias="maxWidth") unit: Literal["imperial", "metric", "nautical"] = "metric" From 5800d2e60ec70ac57217f079a7a35b7b51c33ebf Mon Sep 17 00:00:00 2001 From: Stefan Kuethe Date: Tue, 16 Jan 2024 08:38:57 +0100 Subject: [PATCH 04/58] Update docs --- docs/api/controls.md | 6 ++++++ docs/api/map.md | 4 +++- maplibre/ipywidget.py | 13 +++++++++++++ maplibre/mapcontext.py | 13 +++++++++++++ 4 files changed, 35 insertions(+), 1 deletion(-) diff --git a/docs/api/controls.md b/docs/api/controls.md index 5a0b6198..90e065f0 100644 --- a/docs/api/controls.md +++ b/docs/api/controls.md @@ -11,3 +11,9 @@ docstring_section_style: list ::: maplibre.controls.FullscreenControl + +::: maplibre.controls.ScaleControl + +::: maplibre.controls.NavigationControl + +::: maplibre.controls.GeolocateControl diff --git a/docs/api/map.md b/docs/api/map.md index 8e92fd89..a98122cb 100644 --- a/docs/api/map.md +++ b/docs/api/map.md @@ -2,7 +2,9 @@ ::: maplibre.MapOptions +::: maplibre.MapContext + ::: maplibre.ipywidget.MapWidget options: - inherited_members: true + inherited_members: false \ No newline at end of file diff --git a/maplibre/ipywidget.py b/maplibre/ipywidget.py index 3d9b2175..8feae38a 100644 --- a/maplibre/ipywidget.py +++ b/maplibre/ipywidget.py @@ -65,6 +65,19 @@ def add_tooltip(self, layer_id: str, prop: str) -> None: # TODO: Rename to MapWidget or IpyMap class MapWidget(AnyWidget, Map): + """MapWidget + + Use this class to display and update maps in Jupyter Notebooks. + + See `maplibre.Map` for available methods. + + Examples: + >>> from maplibre import MapOptions + >>> from maplibre.ipywidget import MapWidget as Map + >>> m = Map(MapOptions(center=(-123.13, 49.254), zoom=11, pitch=45)) + >>> m # doctest: +SKIP + """ + _esm = join(Path(__file__).parent, "srcjs", "ipywidget.js") _css = join(Path(__file__).parent, "srcjs", "maplibre-gl.css") _use_message_queue = False diff --git a/maplibre/mapcontext.py b/maplibre/mapcontext.py index 06fdca82..33eebff7 100644 --- a/maplibre/mapcontext.py +++ b/maplibre/mapcontext.py @@ -4,6 +4,19 @@ class MapContext(Map): + """MapContext + + Use this class to update a `Map` instance in a Shiny app. + Must be used inside an async function. + + See `maplibre.Map` for available methods. + + Args: + id (string): The id of the map to be updated. + session (Session): A Shiny session. + If `None`, the active session is used. + """ + def __init__(self, id: str, session: Session = None) -> None: self.id = id self._session = require_active_session(session) From 24d49175df05470747b523a4f5d333bc82a20ec3 Mon Sep 17 00:00:00 2001 From: Stefan Kuethe Date: Tue, 16 Jan 2024 14:41:04 +0100 Subject: [PATCH 05/58] Render html --- docs/examples/3d_indoor_mapping/app.html | 163 +++++++++++++++++++++++ docs/examples/3d_indoor_mapping/app.py | 11 +- docs/examples/3d_indoor_mapping/index.md | 2 + docs/examples/custom_basemap/app.html | 163 +++++++++++++++++++++++ docs/examples/custom_basemap/app.py | 10 +- docs/examples/custom_basemap/index.md | 2 + docs/examples/geopandas/app.html | 163 +++++++++++++++++++++++ docs/examples/geopandas/app.py | 10 +- docs/examples/geopandas/index.md | 2 + 9 files changed, 517 insertions(+), 9 deletions(-) create mode 100644 docs/examples/3d_indoor_mapping/app.html create mode 100644 docs/examples/custom_basemap/app.html create mode 100644 docs/examples/geopandas/app.html diff --git a/docs/examples/3d_indoor_mapping/app.html b/docs/examples/3d_indoor_mapping/app.html new file mode 100644 index 00000000..a8eb1593 --- /dev/null +++ b/docs/examples/3d_indoor_mapping/app.html @@ -0,0 +1,163 @@ + + + +Pymaplibregl + + + + +
+ + + \ No newline at end of file diff --git a/docs/examples/3d_indoor_mapping/app.py b/docs/examples/3d_indoor_mapping/app.py index 337b8638..dcf072fd 100644 --- a/docs/examples/3d_indoor_mapping/app.py +++ b/docs/examples/3d_indoor_mapping/app.py @@ -1,10 +1,12 @@ +import sys import webbrowser from maplibre import Layer, LayerType, Map, MapOptions from maplibre.basemaps import background from maplibre.sources import GeoJSONSource, RasterTileSource -TEMP_FILE = "/tmp/pymaplibregl_temp.html" +file_name = "/tmp/pymaplibregl_temp.html" + FLOORPLAN_SOURCE_ID = "floorplan" raster_source = RasterTileSource( @@ -52,7 +54,10 @@ def create_map(): if __name__ == "__main__": m = create_map() - with open(TEMP_FILE, "w") as f: + if len(sys.argv) == 2: + file_name = sys.argv[1] + + with open(file_name, "w") as f: f.write(m.to_html()) - webbrowser.open(TEMP_FILE) + webbrowser.open(file_name) diff --git a/docs/examples/3d_indoor_mapping/index.md b/docs/examples/3d_indoor_mapping/index.md index 7b9d38fd..3219f96b 100644 --- a/docs/examples/3d_indoor_mapping/index.md +++ b/docs/examples/3d_indoor_mapping/index.md @@ -2,6 +2,8 @@ -8<-- "3d_indoor_mapping/app.py" ``` +See example in action + Run example: ``` bash diff --git a/docs/examples/custom_basemap/app.html b/docs/examples/custom_basemap/app.html new file mode 100644 index 00000000..bc0913ba --- /dev/null +++ b/docs/examples/custom_basemap/app.html @@ -0,0 +1,163 @@ + + + +Pymaplibregl + + + + +
+ + + \ No newline at end of file diff --git a/docs/examples/custom_basemap/app.py b/docs/examples/custom_basemap/app.py index df831ecb..a2fb7967 100644 --- a/docs/examples/custom_basemap/app.py +++ b/docs/examples/custom_basemap/app.py @@ -1,10 +1,11 @@ +import sys import webbrowser from maplibre import Layer, LayerType, Map, MapOptions from maplibre.basemaps import construct_basemap_style from maplibre.sources import GeoJSONSource -TEMP_FILE = "/tmp/pymaplibregl_temp.html" +file_name = "/tmp/pymaplibregl_temp.html" bg_layer = Layer( type=LayerType.BACKGROUND, @@ -60,7 +61,10 @@ def create_map(): if __name__ == "__main__": m = create_map() - with open(TEMP_FILE, "w") as f: + if len(sys.argv) == 2: + file_name = sys.argv[1] + + with open(file_name, "w") as f: f.write(m.to_html()) - webbrowser.open(TEMP_FILE) + webbrowser.open(file_name) diff --git a/docs/examples/custom_basemap/index.md b/docs/examples/custom_basemap/index.md index 6bbd4385..608ef7df 100644 --- a/docs/examples/custom_basemap/index.md +++ b/docs/examples/custom_basemap/index.md @@ -2,6 +2,8 @@ -8<-- "custom_basemap/app.py" ``` +See example in action + Run example: ``` bash diff --git a/docs/examples/geopandas/app.html b/docs/examples/geopandas/app.html new file mode 100644 index 00000000..e8578429 --- /dev/null +++ b/docs/examples/geopandas/app.html @@ -0,0 +1,163 @@ + + + +Pymaplibregl + + + + +
+ + + \ No newline at end of file diff --git a/docs/examples/geopandas/app.py b/docs/examples/geopandas/app.py index fefca9d9..bafebd69 100644 --- a/docs/examples/geopandas/app.py +++ b/docs/examples/geopandas/app.py @@ -1,3 +1,4 @@ +import sys import webbrowser from maplibre import Layer, LayerType, Map, MapOptions @@ -6,7 +7,7 @@ import geopandas as gpd -TEMP_FILE = "/tmp/pymaplibregl_temp.html" +file_name = "/tmp/pymaplibregl_temp.html" LAYER_ID = "wilderness" df_geo = gpd.read_file( @@ -34,7 +35,10 @@ def create_map(): if __name__ == "__main__": m = create_map() - with open(TEMP_FILE, "w") as f: + if len(sys.argv) == 2: + file_name = sys.argv[1] + + with open(file_name, "w") as f: f.write(m.to_html()) - webbrowser.open(TEMP_FILE) + webbrowser.open(file_name) diff --git a/docs/examples/geopandas/index.md b/docs/examples/geopandas/index.md index b856f565..de01d4b2 100644 --- a/docs/examples/geopandas/index.md +++ b/docs/examples/geopandas/index.md @@ -2,6 +2,8 @@ -8<-- "geopandas/app.py" ``` +See example in action + Run example: ``` bash From 2cdad9c751cb7b515339213ee636657a371ce37f Mon Sep 17 00:00:00 2001 From: Stefan Kuethe Date: Tue, 16 Jan 2024 16:58:01 +0100 Subject: [PATCH 06/58] Update example --- docs/examples/vancouver_blocks/app.html | 163 ++++++++++++++++++++++++ docs/examples/vancouver_blocks/app.py | 28 +++- docs/examples/vancouver_blocks/index.md | 2 + docs/index.md | 4 +- 4 files changed, 189 insertions(+), 8 deletions(-) create mode 100644 docs/examples/vancouver_blocks/app.html diff --git a/docs/examples/vancouver_blocks/app.html b/docs/examples/vancouver_blocks/app.html new file mode 100644 index 00000000..d5cebb70 --- /dev/null +++ b/docs/examples/vancouver_blocks/app.html @@ -0,0 +1,163 @@ + + + +Pymaplibregl + + + + +
+ + + \ No newline at end of file diff --git a/docs/examples/vancouver_blocks/app.py b/docs/examples/vancouver_blocks/app.py index 648389fe..01a23c6c 100644 --- a/docs/examples/vancouver_blocks/app.py +++ b/docs/examples/vancouver_blocks/app.py @@ -1,3 +1,5 @@ +import sys + from maplibre import ( Layer, LayerType, @@ -59,6 +61,17 @@ bearing=0, ) + +def create_map() -> Map: + m = Map(map_options) + m.add_control(ScaleControl(), position="bottom-left") + m.add_source(SOURCE_ID, vancouver_blocks_source) + m.add_layer(vancouver_blocks_lines) + m.add_layer(vancouver_blocks_fill) + m.add_tooltip(LAYER_ID_FILL, "valuePerSqm") + return m + + app_ui = ui.page_fluid( ui.panel_title("Vancouver Property Value"), ui.div( @@ -78,12 +91,7 @@ def server(input, output, session): @render_maplibregl def maplibre(): - m = Map(map_options) - m.add_control(ScaleControl(), position="bottom-left") - m.add_source(SOURCE_ID, vancouver_blocks_source) - m.add_layer(vancouver_blocks_lines) - m.add_layer(vancouver_blocks_fill) - m.add_tooltip(LAYER_ID_FILL, "valuePerSqm") + m = create_map() return m @reactive.Effect @@ -97,4 +105,10 @@ async def filter(): app = App(app_ui, server) if __name__ == "__main__": - app.run() + if len(sys.argv) == 2: + file_name = sys.argv[1] + m = create_map() + with open(file_name, "w") as f: + f.write(m.to_html()) + else: + app.run() diff --git a/docs/examples/vancouver_blocks/index.md b/docs/examples/vancouver_blocks/index.md index 3b668664..0afed974 100644 --- a/docs/examples/vancouver_blocks/index.md +++ b/docs/examples/vancouver_blocks/index.md @@ -2,6 +2,8 @@ -8<-- "vancouver_blocks/app.py" ``` +See example in action (without reactive effects) + Run example: ```bash diff --git a/docs/index.md b/docs/index.md index 250242a4..bfa67997 100644 --- a/docs/index.md +++ b/docs/index.md @@ -8,7 +8,9 @@ It integrates seamlessly into [Shiny for Python](https://github.com/posit-dev/py ```bash # Stable -pip install git+https://github.com/eodaGmbH/py-maplibregl +pip install maplibre + +pip install maplibre[all] # Dev pip install git+https://github.com/eodaGmbH/py-maplibregl@dev From caffd8f097d9d5ee1f713979fa7ffcdbc400d12b Mon Sep 17 00:00:00 2001 From: Stefan Kuethe Date: Tue, 16 Jan 2024 17:00:13 +0100 Subject: [PATCH 07/58] Fix typo --- docs/index.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/index.md b/docs/index.md index bfa67997..d7aae84e 100644 --- a/docs/index.md +++ b/docs/index.md @@ -10,7 +10,7 @@ It integrates seamlessly into [Shiny for Python](https://github.com/posit-dev/py # Stable pip install maplibre -pip install maplibre[all] +pip install "maplibre[all]" # Dev pip install git+https://github.com/eodaGmbH/py-maplibregl@dev From 0aebb08195a03b97b3bc0717344029ca4c7abc2d Mon Sep 17 00:00:00 2001 From: Stefan Kuethe Date: Wed, 17 Jan 2024 17:51:02 +0100 Subject: [PATCH 08/58] Add example --- examples/every_person_in_manhattan/app2.py | 77 ++++++++++++++++++++++ 1 file changed, 77 insertions(+) create mode 100644 examples/every_person_in_manhattan/app2.py diff --git a/examples/every_person_in_manhattan/app2.py b/examples/every_person_in_manhattan/app2.py new file mode 100644 index 00000000..4c57839f --- /dev/null +++ b/examples/every_person_in_manhattan/app2.py @@ -0,0 +1,77 @@ +import geopandas as gpd +import pandas as pd +from maplibre import ( + Layer, + LayerType, + Map, + MapContext, + MapOptions, + output_maplibregl, + render_maplibregl, +) +from maplibre.basemaps import Carto +from maplibre.controls import ScaleControl +from maplibre.sources import GeoJSONSource +from maplibre.utils import geopandas_to_geojson +from shiny import App, reactive, ui + +MALE_COLOR = "rgb(0, 128, 255)" +FEMALE_COLOR = "rgb(255, 0, 128)" +LAYER_ID = "every-person-in-manhattan-circles" +CIRCLE_RADIUS = 2 + +point_data = pd.read_json( + "https://raw.githubusercontent.com/visgl/deck.gl-data/master/examples/scatterplot/manhattan.json" +) + +point_data.columns = ["lng", "lat", "sex"] + +point_data = gpd.GeoDataFrame( + point_data["sex"], geometry=gpd.points_from_xy(point_data.lng, point_data.lat) +) + +every_person_in_manhattan_source = GeoJSONSource(data=geopandas_to_geojson(point_data)) + + +every_person_in_manhattan_circles = Layer( + type=LayerType.CIRCLE, + id=LAYER_ID, + source=every_person_in_manhattan_source, + paint={ + "circle-color": ["match", ["get", "sex"], 1, MALE_COLOR, FEMALE_COLOR], + "circle-radius": CIRCLE_RADIUS, + }, +) + +map_options = MapOptions( + style=Carto.POSITRON, + bounds=point_data.total_bounds, + fit_bounds_options={"padding": 20}, +) + +app_ui = ui.page_fluid( + ui.panel_title("Every Person in Manhattan"), + output_maplibregl("maplibre", height=600), + ui.input_slider("radius", "Radius", value=CIRCLE_RADIUS, min=1, max=5), +) + + +def server(input, output, session): + @render_maplibregl + def maplibre(): + m = Map(map_options) + m.add_control(ScaleControl(), position="bottom-left") + m.add_layer(every_person_in_manhattan_circles) + return m + + @reactive.Effect + @reactive.event(input.radius, ignore_init=True) + async def radius(): + async with MapContext("maplibre") as m: + m.set_paint_property(LAYER_ID, "circle-radius", input.radius()) + + +app = App(app_ui, server) + +if __name__ == "__main__": + app.run() From 6672b75f029f4f5798de56686ad5ebde8cf3ca53 Mon Sep 17 00:00:00 2001 From: Stefan Kuethe Date: Wed, 17 Jan 2024 18:58:15 +0100 Subject: [PATCH 09/58] Add Dockerfile --- docs/examples/every_person_in_manhattan/Dockerfile | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 docs/examples/every_person_in_manhattan/Dockerfile diff --git a/docs/examples/every_person_in_manhattan/Dockerfile b/docs/examples/every_person_in_manhattan/Dockerfile new file mode 100644 index 00000000..eeb4538f --- /dev/null +++ b/docs/examples/every_person_in_manhattan/Dockerfile @@ -0,0 +1,7 @@ +FROM python:3.11.5 + +COPY ./app.py ./app.py + +RUN pip install maplibre pandas shapely + +CMD ["uvicorn", "app:app", "--host", "0.0.0.0", "--port", "8050"] From e547468cf191ec121d3e92c202c3f034d8a4e222 Mon Sep 17 00:00:00 2001 From: Stefan Kuethe Date: Wed, 17 Jan 2024 19:02:50 +0100 Subject: [PATCH 10/58] Add Dockerfile --- docs/examples/vancouver_blocks/Dockerfile | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 docs/examples/vancouver_blocks/Dockerfile diff --git a/docs/examples/vancouver_blocks/Dockerfile b/docs/examples/vancouver_blocks/Dockerfile new file mode 100644 index 00000000..fa28b486 --- /dev/null +++ b/docs/examples/vancouver_blocks/Dockerfile @@ -0,0 +1,7 @@ +FROM python:3.11.5 + +COPY ./app.py ./app.py + +RUN pip install maplibre + +CMD ["uvicorn", "app:app", "--host", "0.0.0.0", "--port", "8050"] From bc4edbbcf8d0a5b5fa9042c9b48c9be8fe717314 Mon Sep 17 00:00:00 2001 From: Stefan Kuethe Date: Wed, 17 Jan 2024 19:09:44 +0100 Subject: [PATCH 11/58] Update Dockerfiles --- docs/examples/earthquake_clusters/Dockerfile | 7 +++++++ docs/examples/every_person_in_manhattan/Dockerfile | 4 ++-- docs/examples/vancouver_blocks/Dockerfile | 4 ++-- 3 files changed, 11 insertions(+), 4 deletions(-) create mode 100644 docs/examples/earthquake_clusters/Dockerfile diff --git a/docs/examples/earthquake_clusters/Dockerfile b/docs/examples/earthquake_clusters/Dockerfile new file mode 100644 index 00000000..e8bec61c --- /dev/null +++ b/docs/examples/earthquake_clusters/Dockerfile @@ -0,0 +1,7 @@ +FROM python:3.11.5 + +RUN pip install maplibre + +COPY ./app.py ./app.py + +CMD ["uvicorn", "app:app", "--host", "0.0.0.0", "--port", "8050"] diff --git a/docs/examples/every_person_in_manhattan/Dockerfile b/docs/examples/every_person_in_manhattan/Dockerfile index eeb4538f..389b411b 100644 --- a/docs/examples/every_person_in_manhattan/Dockerfile +++ b/docs/examples/every_person_in_manhattan/Dockerfile @@ -1,7 +1,7 @@ FROM python:3.11.5 -COPY ./app.py ./app.py - RUN pip install maplibre pandas shapely +COPY ./app.py ./app.py + CMD ["uvicorn", "app:app", "--host", "0.0.0.0", "--port", "8050"] diff --git a/docs/examples/vancouver_blocks/Dockerfile b/docs/examples/vancouver_blocks/Dockerfile index fa28b486..e8bec61c 100644 --- a/docs/examples/vancouver_blocks/Dockerfile +++ b/docs/examples/vancouver_blocks/Dockerfile @@ -1,7 +1,7 @@ FROM python:3.11.5 -COPY ./app.py ./app.py - RUN pip install maplibre +COPY ./app.py ./app.py + CMD ["uvicorn", "app:app", "--host", "0.0.0.0", "--port", "8050"] From 9266e58b62c4d621540ab9c9bef2bcca5c387496 Mon Sep 17 00:00:00 2001 From: Stefan Kuethe Date: Wed, 17 Jan 2024 19:18:47 +0100 Subject: [PATCH 12/58] Add Dockerfile --- docs/examples/airports/Dockerfile | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 docs/examples/airports/Dockerfile diff --git a/docs/examples/airports/Dockerfile b/docs/examples/airports/Dockerfile new file mode 100644 index 00000000..71835dd1 --- /dev/null +++ b/docs/examples/airports/Dockerfile @@ -0,0 +1,7 @@ +FROM bitnami/python:3.11.5 + +RUN pip install maplibre pandas + +COPY ./app.py ./app.py + +CMD ["uvicorn", "app:app", "--host", "0.0.0.0", "--port", "8050"] From 7c3768b6a19c0e651a33cb29af3472e28432f0c1 Mon Sep 17 00:00:00 2001 From: Stefan Kuethe Date: Thu, 18 Jan 2024 08:24:39 +0100 Subject: [PATCH 13/58] Add Dockerfile --- docs/examples/getting_started/Dockerfile | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 docs/examples/getting_started/Dockerfile diff --git a/docs/examples/getting_started/Dockerfile b/docs/examples/getting_started/Dockerfile new file mode 100644 index 00000000..6ee41fe2 --- /dev/null +++ b/docs/examples/getting_started/Dockerfile @@ -0,0 +1,7 @@ +FROM python:3.11.5 + +RUN pip install maplibre + +COPY ./basic_usage_shiny.py ./app.py + +CMD ["uvicorn", "app:app", "--host", "0.0.0.0", "--port", "8050"] From e76747efcbf575c238ef4c9862b06fbffc75c7cb Mon Sep 17 00:00:00 2001 From: Stefan Kuethe Date: Thu, 18 Jan 2024 08:29:52 +0100 Subject: [PATCH 14/58] Fix path --- .github/workflows/docker-image.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/docker-image.yml b/.github/workflows/docker-image.yml index 9c166dcd..1b3348d3 100644 --- a/.github/workflows/docker-image.yml +++ b/.github/workflows/docker-image.yml @@ -16,5 +16,5 @@ jobs: - uses: actions/checkout@v3 - name: Build the Docker image run: | - cd docs/examples/vancouver-blocks + cd docs/examples/vancouver_blocks docker build . --file Dockerfile --tag my-image-name:$(date +%s) From 28a5b2f980d762eda8a21d81d8663e1c6f3c7454 Mon Sep 17 00:00:00 2001 From: Stefan Kuethe Date: Thu, 18 Jan 2024 09:57:14 +0100 Subject: [PATCH 15/58] Login to registry --- .github/workflows/docker-image.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/docker-image.yml b/.github/workflows/docker-image.yml index 1b3348d3..cf33df95 100644 --- a/.github/workflows/docker-image.yml +++ b/.github/workflows/docker-image.yml @@ -18,3 +18,5 @@ jobs: run: | cd docs/examples/vancouver_blocks docker build . --file Dockerfile --tag my-image-name:$(date +%s) + - name: Log in to registry + run: echo "${{ secrets.GITHUB_TOKEN }}" | docker login ghcr.io -u $ --password-stdin From b69d55281759c2c2c66ee2c700e1c822d9f50295 Mon Sep 17 00:00:00 2001 From: Stefan Kuethe Date: Thu, 18 Jan 2024 11:11:48 +0100 Subject: [PATCH 16/58] Push image --- .github/workflows/docker-image.yml | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/.github/workflows/docker-image.yml b/.github/workflows/docker-image.yml index cf33df95..70cef649 100644 --- a/.github/workflows/docker-image.yml +++ b/.github/workflows/docker-image.yml @@ -6,6 +6,10 @@ on: pull_request: branches: [ "main" ] +env: + IMAGE_NAME: vancouver-blocks + VERSION: latest + jobs: build: @@ -17,6 +21,11 @@ jobs: - name: Build the Docker image run: | cd docs/examples/vancouver_blocks - docker build . --file Dockerfile --tag my-image-name:$(date +%s) + docker build . --file Dockerfile --tag IMAGE_NAME - name: Log in to registry run: echo "${{ secrets.GITHUB_TOKEN }}" | docker login ghcr.io -u $ --password-stdin + - name: Push image + run: | + IMAGE_ID=ghcr.io/${{ github.repository_owner }}/$IMAGE_NAME + docker tag $IMAGE_NAME $IMAGE_ID:$VERSION + docker push $IMAGE_ID:$VERSION From 9902c0dacfd4cf93321ed7f73972879866ebe8d4 Mon Sep 17 00:00:00 2001 From: Stefan Kuethe Date: Thu, 18 Jan 2024 11:14:26 +0100 Subject: [PATCH 17/58] Typo --- .github/workflows/docker-image.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/docker-image.yml b/.github/workflows/docker-image.yml index 70cef649..ba63d7ed 100644 --- a/.github/workflows/docker-image.yml +++ b/.github/workflows/docker-image.yml @@ -21,7 +21,7 @@ jobs: - name: Build the Docker image run: | cd docs/examples/vancouver_blocks - docker build . --file Dockerfile --tag IMAGE_NAME + docker build . --file Dockerfile --tag $IMAGE_NAME - name: Log in to registry run: echo "${{ secrets.GITHUB_TOKEN }}" | docker login ghcr.io -u $ --password-stdin - name: Push image From 81ab56eecce2da87c44133f315b7480ac0167f9b Mon Sep 17 00:00:00 2001 From: Stefan Kuethe Date: Thu, 18 Jan 2024 11:16:48 +0100 Subject: [PATCH 18/58] Fix repo name --- .github/workflows/docker-image.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/docker-image.yml b/.github/workflows/docker-image.yml index ba63d7ed..a884cf22 100644 --- a/.github/workflows/docker-image.yml +++ b/.github/workflows/docker-image.yml @@ -27,5 +27,7 @@ jobs: - name: Push image run: | IMAGE_ID=ghcr.io/${{ github.repository_owner }}/$IMAGE_NAME + # repository name must be lowercase + IMAGE_ID=$(echo $IMAGE_ID | tr '[A-Z]' '[a-z]') docker tag $IMAGE_NAME $IMAGE_ID:$VERSION docker push $IMAGE_ID:$VERSION From 9d7414fc8a1255f2a0bda404383379249af33a60 Mon Sep 17 00:00:00 2001 From: Stefan Kuethe Date: Thu, 18 Jan 2024 11:24:28 +0100 Subject: [PATCH 19/58] Add repo name to image name --- .github/workflows/docker-image.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/docker-image.yml b/.github/workflows/docker-image.yml index a884cf22..013080be 100644 --- a/.github/workflows/docker-image.yml +++ b/.github/workflows/docker-image.yml @@ -26,7 +26,7 @@ jobs: run: echo "${{ secrets.GITHUB_TOKEN }}" | docker login ghcr.io -u $ --password-stdin - name: Push image run: | - IMAGE_ID=ghcr.io/${{ github.repository_owner }}/$IMAGE_NAME + IMAGE_ID=ghcr.io/$GITHUB_REPOSITORY/$IMAGE_NAME # repository name must be lowercase IMAGE_ID=$(echo $IMAGE_ID | tr '[A-Z]' '[a-z]') docker tag $IMAGE_NAME $IMAGE_ID:$VERSION From 39e81cca9c6f4737627da945938c95ebc089e6f4 Mon Sep 17 00:00:00 2001 From: Stefan Kuethe Date: Thu, 18 Jan 2024 11:39:35 +0100 Subject: [PATCH 20/58] Add docker to README --- README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/README.md b/README.md index 4ceb2ad4..557145e7 100644 --- a/README.md +++ b/README.md @@ -20,6 +20,10 @@ pip install "maplibre[all] @ git+https://github.com/eodaGmbH/py-maplibregl@dev" ## Getting started +```bash +docker run --rm -p 8050:8050 ghcr.io/eodagmbh/py-maplibregl/vancouver-blocks:latest +``` + * [Basic usage](https://eodagmbh.github.io/py-maplibregl/) * [API Documentation](https://eodagmbh.github.io/py-maplibregl/api/map/) * [Examples](https://eodagmbh.github.io/py-maplibregl/examples/every_person_in_manhattan/) From 54580dac92ed3401fccdd5c3bdbc704b7a369ccf Mon Sep 17 00:00:00 2001 From: Stefan Kuethe Date: Thu, 18 Jan 2024 13:23:04 +0100 Subject: [PATCH 21/58] Add custom call to example --- docs/examples/getting_started/basic_usage_shiny.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/docs/examples/getting_started/basic_usage_shiny.py b/docs/examples/getting_started/basic_usage_shiny.py index 3cf092b4..f0418037 100644 --- a/docs/examples/getting_started/basic_usage_shiny.py +++ b/docs/examples/getting_started/basic_usage_shiny.py @@ -4,6 +4,7 @@ app_ui = ui.page_fluid( output_maplibregl("maplibre", height=600), + ui.div("Click on map to set a marker"), ) @@ -18,7 +19,10 @@ def maplibre(): async def coords(): async with MapContext("maplibre") as m: print(input.maplibre()) - m.add_marker(Marker(lng_lat=input.maplibre()["coords"].values())) + lng_lat = tuple(input.maplibre()["coords"].values()) + marker = Marker(lng_lat=lng_lat) + m.add_marker(marker) + m.add_call("flyTo", {"center": lng_lat}) app = App(app_ui, server) From 74c6eeb74f2c798056fc6e930d7d0eb43f80fe5f Mon Sep 17 00:00:00 2001 From: Stefan Kuethe Date: Thu, 18 Jan 2024 18:55:38 +0100 Subject: [PATCH 22/58] Build docker image --- .github/workflows/docker-image2.yml | 33 +++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) create mode 100644 .github/workflows/docker-image2.yml diff --git a/.github/workflows/docker-image2.yml b/.github/workflows/docker-image2.yml new file mode 100644 index 00000000..e7d9ced8 --- /dev/null +++ b/.github/workflows/docker-image2.yml @@ -0,0 +1,33 @@ +name: Docker Image CI + +on: + push: + branches: [ "dev" ] + pull_request: + branches: [ "main" ] + +env: + IMAGE_NAME: airports + VERSION: latest + +jobs: + + build: + + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v3 + - name: Build the Docker image + run: | + cd docs/examples/airports + docker build . --file Dockerfile --tag $IMAGE_NAME + - name: Log in to registry + run: echo "${{ secrets.GITHUB_TOKEN }}" | docker login ghcr.io -u $ --password-stdin + - name: Push image + run: | + IMAGE_ID=ghcr.io/$GITHUB_REPOSITORY/$IMAGE_NAME + # repository name must be lowercase + IMAGE_ID=$(echo $IMAGE_ID | tr '[A-Z]' '[a-z]') + docker tag $IMAGE_NAME $IMAGE_ID:$VERSION + docker push $IMAGE_ID:$VERSION From 46b5d0b3a41968e9b86a7b456a7d32962ddc53be Mon Sep 17 00:00:00 2001 From: Stefan Kuethe Date: Fri, 19 Jan 2024 07:59:27 +0100 Subject: [PATCH 23/58] Add hike example --- docs/examples/hike/app.py | 59 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 59 insertions(+) create mode 100644 docs/examples/hike/app.py diff --git a/docs/examples/hike/app.py b/docs/examples/hike/app.py new file mode 100644 index 00000000..9f3446a4 --- /dev/null +++ b/docs/examples/hike/app.py @@ -0,0 +1,59 @@ +import asyncio + +import geopandas as gpd +from maplibre import ( + Layer, + LayerType, + Map, + MapContext, + MapOptions, + output_maplibregl, + render_maplibregl, +) +from maplibre.controls import Marker +from maplibre.sources import GeoJSONSource +from shiny import App, reactive, ui + +SOURCE_ID = "hike" +LAYER_ID = "hike" +data = gpd.read_file("https://docs.mapbox.com/mapbox-gl-js/assets/hike.geojson") +# print(data) +print(data.get_coordinates().to_numpy()[0]) + +hike = GeoJSONSource(data="https://docs.mapbox.com/mapbox-gl-js/assets/hike.geojson") +layer = Layer( + type=LayerType.LINE, + id=LAYER_ID, + source="hike", + paint={"line-color": "yellow", "line-width": 3}, +) + +app_ui = ui.page_fluid( + output_maplibregl("mapylibre", height=600), + ui.input_action_button("run", "Run"), +) + + +def server(input, output, session): + @render_maplibregl + def mapylibre(): + m = Map( + MapOptions(bounds=data.total_bounds, fit_bounds_options={"padding": 20}) + ) + m.add_source(SOURCE_ID, hike) + m.add_layer(layer) + # m.add_marker(Marker(lng_lat=data.get_coordinates().to_numpy()[0])) + return m + + @reactive.Effect + @reactive.event(input.run) + async def run(): + print("run") + for lng_lat in data.get_coordinates().to_numpy(): + async with MapContext("mapylibre") as m: + m.add_marker(Marker(lng_lat=lng_lat)) + m.add_call("setCenter", tuple(lng_lat)) + # await asyncio.sleep(2) + + +app = App(app_ui, server) From 69ed9b2e5f73bc87297c2bbef638f6318f372195 Mon Sep 17 00:00:00 2001 From: Stefan Kuethe Date: Fri, 19 Jan 2024 09:32:11 +0100 Subject: [PATCH 24/58] Add iss live data example --- docs/examples/where_is_the_iss/app.py | 65 +++++++++++++++++++++++++++ maplibre/srcjs/index.js | 6 ++- srcjs/pymaplibregl.js | 5 +++ 3 files changed, 75 insertions(+), 1 deletion(-) create mode 100644 docs/examples/where_is_the_iss/app.py diff --git a/docs/examples/where_is_the_iss/app.py b/docs/examples/where_is_the_iss/app.py new file mode 100644 index 00000000..b785006d --- /dev/null +++ b/docs/examples/where_is_the_iss/app.py @@ -0,0 +1,65 @@ +import requests +from maplibre import ( + Layer, + LayerType, + Map, + MapContext, + MapOptions, + output_maplibregl, + render_maplibregl, +) +from maplibre.controls import Marker +from maplibre.sources import GeoJSONSource +from shiny import App, reactive, ui + + +def where_is_the_iss() -> tuple: + r = requests.get("https://api.wheretheiss.at/v1/satellites/25544").json() + return (r["longitude"], r["latitude"]) + + +def create_feature(lng_lat) -> dict: + return { + "type": "Feature", + "geometry": {"type": "Point", "coordinates": lng_lat}, + } + + +lng_lat = where_is_the_iss() +print(lng_lat) +feature = create_feature(lng_lat) +print(feature) + +app_ui = ui.page_fluid( + ui.h1("Where is the ISS"), + output_maplibregl("mapylibre", height=600), + ui.input_action_button("update", "Update"), +) + + +def server(input, output, session): + @render_maplibregl + def mapylibre(): + m = Map(MapOptions(center=lng_lat, zoom=3)) + m.add_source("iss", GeoJSONSource(data=feature)) + m.add_layer( + Layer( + type=LayerType.CIRCLE, + source="iss", + paint={"circle-color": "yellow", "circle-radius": 5}, + ), + ) + return m + + @reactive.Effect + @reactive.event(input.update) + async def update(): + print("Fetching new position") + lng_lat = where_is_the_iss() + print(lng_lat) + async with MapContext("mapylibre") as m: + # m.add_marker(Marker(lng_lat=lng_lat)) + m.add_call("setSourceData", "iss", create_feature(lng_lat)) + + +app = App(app_ui, server) diff --git a/maplibre/srcjs/index.js b/maplibre/srcjs/index.js index e333e08f..c1121f06 100644 --- a/maplibre/srcjs/index.js +++ b/maplibre/srcjs/index.js @@ -71,6 +71,9 @@ popup.remove(); }); } + setSourceData(sourceId, data) { + this._map.getSource(sourceId).setData(data); + } render(calls) { calls.forEach(([name, params]) => { if ([ @@ -79,7 +82,8 @@ "addTooltip", "addMarker", "addPopup", - "addControl" + "addControl", + "setSourceData" ].includes(name)) { console.log("Custom method", name, params); this[name](...params); diff --git a/srcjs/pymaplibregl.js b/srcjs/pymaplibregl.js index 5cebe38a..52e0e4c6 100644 --- a/srcjs/pymaplibregl.js +++ b/srcjs/pymaplibregl.js @@ -88,6 +88,10 @@ export default class PyMapLibreGL { }); } + setSourceData(sourceId, data) { + this._map.getSource(sourceId).setData(data); + } + render(calls) { calls.forEach(([name, params]) => { // Custom method @@ -99,6 +103,7 @@ export default class PyMapLibreGL { "addMarker", "addPopup", "addControl", + "setSourceData", ].includes(name) ) { console.log("Custom method", name, params); From aee072e50828c670c8488a7cd7fdf667b83583d4 Mon Sep 17 00:00:00 2001 From: Stefan Kuethe Date: Fri, 19 Jan 2024 11:08:01 +0100 Subject: [PATCH 25/58] Add function to update the data of a source --- docs/examples/where_is_the_iss/app.py | 22 ++++++++++++++++------ maplibre/map.py | 4 ++++ 2 files changed, 20 insertions(+), 6 deletions(-) diff --git a/docs/examples/where_is_the_iss/app.py b/docs/examples/where_is_the_iss/app.py index b785006d..d6c734e3 100644 --- a/docs/examples/where_is_the_iss/app.py +++ b/docs/examples/where_is_the_iss/app.py @@ -8,10 +8,11 @@ output_maplibregl, render_maplibregl, ) -from maplibre.controls import Marker from maplibre.sources import GeoJSONSource from shiny import App, reactive, ui +MAX_FEATURES = 30 + def where_is_the_iss() -> tuple: r = requests.get("https://api.wheretheiss.at/v1/satellites/25544").json() @@ -26,9 +27,13 @@ def create_feature(lng_lat) -> dict: lng_lat = where_is_the_iss() -print(lng_lat) +# print(lng_lat) + feature = create_feature(lng_lat) -print(feature) +# print(feature) + +feature_collection = {"type": "FeatureCollection", "features": [feature]} +# print(feature_collection) app_ui = ui.page_fluid( ui.h1("Where is the ISS"), @@ -41,7 +46,7 @@ def server(input, output, session): @render_maplibregl def mapylibre(): m = Map(MapOptions(center=lng_lat, zoom=3)) - m.add_source("iss", GeoJSONSource(data=feature)) + m.add_source("iss", GeoJSONSource(data=feature_collection)) m.add_layer( Layer( type=LayerType.CIRCLE, @@ -57,9 +62,14 @@ async def update(): print("Fetching new position") lng_lat = where_is_the_iss() print(lng_lat) + if len(feature_collection["features"]) == MAX_FEATURES: + feature_collection["features"] = [] + async with MapContext("mapylibre") as m: - # m.add_marker(Marker(lng_lat=lng_lat)) - m.add_call("setSourceData", "iss", create_feature(lng_lat)) + feature_collection["features"].append(create_feature(lng_lat)) + print(feature_collection) + m.set_data("iss", feature_collection) + m.add_call("flyTo", {"center": lng_lat, "speed": 0.5}) app = App(app_ui, server) diff --git a/maplibre/map.py b/maplibre/map.py index 4f3d7c61..32a8f76f 100644 --- a/maplibre/map.py +++ b/maplibre/map.py @@ -205,6 +205,10 @@ def set_layout_property(self, layer_id: str, prop: str, value: any) -> None: """ self.add_call("setLayoutProperty", layer_id, prop, value) + def set_data(self, source_id: str, data: dict) -> None: + """Update the data of a GeoJSON source""" + self.add_call("setSourceData", source_id, data) + def to_html(self, **kwargs) -> str: """Render to html From 8bcbda780f3aaafd059693dfb33b2e7577b4f5a3 Mon Sep 17 00:00:00 2001 From: Stefan Kuethe Date: Fri, 19 Jan 2024 13:19:07 +0100 Subject: [PATCH 26/58] Add animation example --- docs/examples/hike/app.py | 38 +++++++++++++++++++++++++++++++++----- 1 file changed, 33 insertions(+), 5 deletions(-) diff --git a/docs/examples/hike/app.py b/docs/examples/hike/app.py index 9f3446a4..60437c6e 100644 --- a/docs/examples/hike/app.py +++ b/docs/examples/hike/app.py @@ -18,14 +18,27 @@ LAYER_ID = "hike" data = gpd.read_file("https://docs.mapbox.com/mapbox-gl-js/assets/hike.geojson") # print(data) -print(data.get_coordinates().to_numpy()[0]) +# print(data.get_coordinates().to_numpy()[0]) + + +def create_feature(lng_lat) -> dict: + return { + "type": "Feature", + "geometry": {"type": "LineString", "coordinates": [lng_lat]}, + } + + +feature = create_feature(tuple(data.get_coordinates().to_numpy()[0])) +feature_collection = {"type": "FeatureCollection", "features": [feature]} +print(feature_collection) hike = GeoJSONSource(data="https://docs.mapbox.com/mapbox-gl-js/assets/hike.geojson") layer = Layer( type=LayerType.LINE, id=LAYER_ID, source="hike", - paint={"line-color": "yellow", "line-width": 3}, + paint={"line-color": "orange", "line-width": 3}, + layout={"visibility": "visible"}, ) app_ui = ui.page_fluid( @@ -42,6 +55,15 @@ def mapylibre(): ) m.add_source(SOURCE_ID, hike) m.add_layer(layer) + m.add_source("hike-animation", GeoJSONSource(data=feature_collection)) + m.add_layer( + Layer( + type=LayerType.LINE, + id="hike-animation", + source="hike-animation", + paint={"line-color": "yellow", "line-width": 5}, + ) + ) # m.add_marker(Marker(lng_lat=data.get_coordinates().to_numpy()[0])) return m @@ -50,10 +72,16 @@ def mapylibre(): async def run(): print("run") for lng_lat in data.get_coordinates().to_numpy(): + print(lng_lat) + feature_collection["features"][0]["geometry"]["coordinates"].append( + tuple(lng_lat) + ) async with MapContext("mapylibre") as m: - m.add_marker(Marker(lng_lat=lng_lat)) - m.add_call("setCenter", tuple(lng_lat)) - # await asyncio.sleep(2) + m.set_data("hike-animation", feature_collection) + m.add_call("panTo", tuple(lng_lat)) + await asyncio.sleep(0.005) + + feature_collection["features"][0]["geometry"]["coordinates"] = [] app = App(app_ui, server) From 90c68decfea0215532a3c62de1c57714a5d37209 Mon Sep 17 00:00:00 2001 From: Stefan Kuethe Date: Fri, 19 Jan 2024 13:33:38 +0100 Subject: [PATCH 27/58] Update example --- docs/examples/hike/app.py | 24 ++++++++++-------------- 1 file changed, 10 insertions(+), 14 deletions(-) diff --git a/docs/examples/hike/app.py b/docs/examples/hike/app.py index 60437c6e..06ec65ba 100644 --- a/docs/examples/hike/app.py +++ b/docs/examples/hike/app.py @@ -21,15 +21,12 @@ # print(data.get_coordinates().to_numpy()[0]) -def create_feature(lng_lat) -> dict: - return { - "type": "Feature", - "geometry": {"type": "LineString", "coordinates": [lng_lat]}, - } - - -feature = create_feature(tuple(data.get_coordinates().to_numpy()[0])) -feature_collection = {"type": "FeatureCollection", "features": [feature]} +feature_collection = { + "type": "FeatureCollection", + "features": [ + {"type": "Feature", "geometry": {"type": "LineString", "coordinates": []}} + ], +} print(feature_collection) hike = GeoJSONSource(data="https://docs.mapbox.com/mapbox-gl-js/assets/hike.geojson") @@ -51,7 +48,8 @@ def server(input, output, session): @render_maplibregl def mapylibre(): m = Map( - MapOptions(bounds=data.total_bounds, fit_bounds_options={"padding": 20}) + # MapOptions(center=data.get_coordinates().to_numpy()[0], zoom=16, pitch=30) + MapOptions(bounds=data.total_bounds, fit_bounds_options={"padding": 10}) ) m.add_source(SOURCE_ID, hike) m.add_layer(layer) @@ -64,22 +62,20 @@ def mapylibre(): paint={"line-color": "yellow", "line-width": 5}, ) ) - # m.add_marker(Marker(lng_lat=data.get_coordinates().to_numpy()[0])) return m @reactive.Effect @reactive.event(input.run) async def run(): - print("run") for lng_lat in data.get_coordinates().to_numpy(): - print(lng_lat) + # print(lng_lat) feature_collection["features"][0]["geometry"]["coordinates"].append( tuple(lng_lat) ) async with MapContext("mapylibre") as m: m.set_data("hike-animation", feature_collection) m.add_call("panTo", tuple(lng_lat)) - await asyncio.sleep(0.005) + # await asyncio.sleep(0.005) feature_collection["features"][0]["geometry"]["coordinates"] = [] From 4f078e0739f6ab72de7c69a5bc1b0da5f1885bd0 Mon Sep 17 00:00:00 2001 From: Stefan Kuethe Date: Fri, 19 Jan 2024 13:35:15 +0100 Subject: [PATCH 28/58] Add Dockerfile --- .github/workflows/docker-image-animation.yml | 33 ++++++++++++++++++++ 1 file changed, 33 insertions(+) create mode 100644 .github/workflows/docker-image-animation.yml diff --git a/.github/workflows/docker-image-animation.yml b/.github/workflows/docker-image-animation.yml new file mode 100644 index 00000000..62e7bcea --- /dev/null +++ b/.github/workflows/docker-image-animation.yml @@ -0,0 +1,33 @@ +name: Docker Image CI + +on: + push: + branches: [ "feature/examples" ] + pull_request: + branches: [ "main" ] + +env: + IMAGE_NAME: hike-animation + VERSION: latest + +jobs: + + build: + + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v3 + - name: Build the Docker image + run: | + cd docs/examples/hike + docker build . --file Dockerfile --tag $IMAGE_NAME + - name: Log in to registry + run: echo "${{ secrets.GITHUB_TOKEN }}" | docker login ghcr.io -u $ --password-stdin + - name: Push image + run: | + IMAGE_ID=ghcr.io/$GITHUB_REPOSITORY/$IMAGE_NAME + # repository name must be lowercase + IMAGE_ID=$(echo $IMAGE_ID | tr '[A-Z]' '[a-z]') + docker tag $IMAGE_NAME $IMAGE_ID:$VERSION + docker push $IMAGE_ID:$VERSION From 902a9ca01f42f36ffb05406e0d3b3fa499ce3435 Mon Sep 17 00:00:00 2001 From: Stefan Kuethe Date: Fri, 19 Jan 2024 13:37:03 +0100 Subject: [PATCH 29/58] Add Dockerfile --- docs/examples/hike/Dockerfile | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 docs/examples/hike/Dockerfile diff --git a/docs/examples/hike/Dockerfile b/docs/examples/hike/Dockerfile new file mode 100644 index 00000000..f8c85c1f --- /dev/null +++ b/docs/examples/hike/Dockerfile @@ -0,0 +1,7 @@ +FROM python:3.11.5 + +RUN pip install maplibre geopandas + +COPY ./app.py ./app.py + +CMD ["uvicorn", "app:app", "--host", "0.0.0.0", "--port", "8050"] From f0ee5ce47231f8b1904efef15fef73f22587c83b Mon Sep 17 00:00:00 2001 From: Stefan Kuethe Date: Fri, 19 Jan 2024 13:42:00 +0100 Subject: [PATCH 30/58] Install latest maplibre --- docs/examples/hike/Dockerfile | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/examples/hike/Dockerfile b/docs/examples/hike/Dockerfile index f8c85c1f..d41f777c 100644 --- a/docs/examples/hike/Dockerfile +++ b/docs/examples/hike/Dockerfile @@ -1,6 +1,7 @@ FROM python:3.11.5 -RUN pip install maplibre geopandas +# RUN pip install maplibre geopandas +RUN pip install "maplibre[all] @ git+https://github.com/eodaGmbH/py-maplibregl@feature/examples" COPY ./app.py ./app.py From d0c701f0c5e66a2a3c14adf1c9d6d2258fecb752 Mon Sep 17 00:00:00 2001 From: Stefan Kuethe Date: Sat, 20 Jan 2024 13:59:41 +0100 Subject: [PATCH 31/58] Add road safety example --- docs/examples/road_safety/app.py | 41 ++++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) create mode 100644 docs/examples/road_safety/app.py diff --git a/docs/examples/road_safety/app.py b/docs/examples/road_safety/app.py new file mode 100644 index 00000000..55eea3d4 --- /dev/null +++ b/docs/examples/road_safety/app.py @@ -0,0 +1,41 @@ +import webbrowser + +import h3 +import pandas as pd +from maplibre import Layer, Map, MapOptions +from maplibre.sources import GeoJSONSource +from maplibre.utils import df_to_geojson, get_bounds + +RESOLUTION = 5 + +road_safety = pd.read_csv( + "https://raw.githubusercontent.com/visgl/deck.gl-data/master/examples/3d-heatmap/heatmap-data.csv" +).dropna() + +road_safety["h3"] = road_safety.apply( + lambda x: h3.geo_to_h3(x["lat"], x["lng"], resolution=RESOLUTION), axis=1 +) + +df = road_safety.groupby("h3").h3.agg("count").to_frame("count").reset_index() + +df["hexagon"] = df.apply( + lambda x: [h3.h3_to_geo_boundary(x["h3"], geo_json=True)], axis=1 +) + +source = GeoJSONSource( + data=df_to_geojson(df, "hexagon", geometry_type="Polygon", properties=["count"]) +) +bounds = get_bounds(source.data) + +m = Map(MapOptions(bounds=bounds)) +m.add_layer( + Layer(id="road-safety", type="line", source=source, paint={"line-color": "yellow"}) +) +m.add_tooltip("road-safety", "count") + +filename = "/tmp/road_safety.html" +with open(filename, "w") as f: + f.write(m.to_html()) + + +webbrowser.open(filename) From 4945fdf117a1d30830f8860c7dd22754d2f21317 Mon Sep 17 00:00:00 2001 From: Stefan Kuethe Date: Sat, 20 Jan 2024 17:45:03 +0100 Subject: [PATCH 32/58] Use fill layer --- docs/examples/road_safety/app.py | 34 ++++++++++++++++++++++++-------- 1 file changed, 26 insertions(+), 8 deletions(-) diff --git a/docs/examples/road_safety/app.py b/docs/examples/road_safety/app.py index 55eea3d4..2270ad1b 100644 --- a/docs/examples/road_safety/app.py +++ b/docs/examples/road_safety/app.py @@ -2,11 +2,11 @@ import h3 import pandas as pd -from maplibre import Layer, Map, MapOptions +from maplibre import Layer, LayerType, Map, MapOptions from maplibre.sources import GeoJSONSource from maplibre.utils import df_to_geojson, get_bounds -RESOLUTION = 5 +RESOLUTION = 7 road_safety = pd.read_csv( "https://raw.githubusercontent.com/visgl/deck.gl-data/master/examples/3d-heatmap/heatmap-data.csv" @@ -22,19 +22,37 @@ lambda x: [h3.h3_to_geo_boundary(x["h3"], geo_json=True)], axis=1 ) +df["color"] = pd.cut( + df["count"], + bins=6, + labels=("lightblue", "darkblue", "lightgreen", "yellow", "orange", "darkred"), +) + source = GeoJSONSource( - data=df_to_geojson(df, "hexagon", geometry_type="Polygon", properties=["count"]) + data=df_to_geojson( + df, "hexagon", geometry_type="Polygon", properties=["count", "color"] + ) ) bounds = get_bounds(source.data) -m = Map(MapOptions(bounds=bounds)) -m.add_layer( - Layer(id="road-safety", type="line", source=source, paint={"line-color": "yellow"}) -) -m.add_tooltip("road-safety", "count") + +def create_map() -> Map: + m = Map(MapOptions(bounds=bounds)) + m.add_layer( + Layer( + id="road-safety", + type=LayerType.FILL, + source=source, + paint={"fill-color": ["get", "color"], "fill-opacity": 0.5}, + ) + ) + m.add_tooltip("road-safety", "count") + return m + filename = "/tmp/road_safety.html" with open(filename, "w") as f: + m = create_map() f.write(m.to_html()) From d52044ac7c123f872de638aed38863ae8a08c982 Mon Sep 17 00:00:00 2001 From: Stefan Kuethe Date: Sat, 20 Jan 2024 17:53:44 +0100 Subject: [PATCH 33/58] Use fill extrusion --- docs/examples/road_safety/app.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/docs/examples/road_safety/app.py b/docs/examples/road_safety/app.py index 2270ad1b..a435c686 100644 --- a/docs/examples/road_safety/app.py +++ b/docs/examples/road_safety/app.py @@ -41,9 +41,13 @@ def create_map() -> Map: m.add_layer( Layer( id="road-safety", - type=LayerType.FILL, + type=LayerType.FILL_EXTRUSION, source=source, - paint={"fill-color": ["get", "color"], "fill-opacity": 0.5}, + paint={ + "fill-extrusion-color": ["get", "color"], + "fill-extrusion-opacity": 0.7, + "fill-extrusion-height": ["*", 100, ["get", "count"]], + }, ) ) m.add_tooltip("road-safety", "count") From 157c65815a1bea8dbe1b0157da21bcab4e8dd525 Mon Sep 17 00:00:00 2001 From: Stefan Kuethe Date: Sat, 20 Jan 2024 19:18:33 +0100 Subject: [PATCH 34/58] Refactor --- docs/examples/road_safety/app.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/docs/examples/road_safety/app.py b/docs/examples/road_safety/app.py index a435c686..506b0622 100644 --- a/docs/examples/road_safety/app.py +++ b/docs/examples/road_safety/app.py @@ -22,10 +22,12 @@ lambda x: [h3.h3_to_geo_boundary(x["h3"], geo_json=True)], axis=1 ) +colors = ("lightblue", "turquoise", "lightgreen", "yellow", "orange", "darkred") + df["color"] = pd.cut( df["count"], - bins=6, - labels=("lightblue", "darkblue", "lightgreen", "yellow", "orange", "darkred"), + bins=len(colors), + labels=colors, ) source = GeoJSONSource( @@ -37,7 +39,8 @@ def create_map() -> Map: - m = Map(MapOptions(bounds=bounds)) + # m = Map(MapOptions(bounds=bounds)) + m = Map(MapOptions(center=(-1.415727, 52.232395), zoom=7, pitch=40, bearing=-27)) m.add_layer( Layer( id="road-safety", From 04e5591e39bdb905569408123ba02ea99f2b8c54 Mon Sep 17 00:00:00 2001 From: Stefan Kuethe Date: Sat, 20 Jan 2024 19:23:03 +0100 Subject: [PATCH 35/58] Refactor --- docs/examples/road_safety/app.py | 31 ++++++++++++++++--------------- 1 file changed, 16 insertions(+), 15 deletions(-) diff --git a/docs/examples/road_safety/app.py b/docs/examples/road_safety/app.py index 506b0622..68ecf21b 100644 --- a/docs/examples/road_safety/app.py +++ b/docs/examples/road_safety/app.py @@ -7,28 +7,30 @@ from maplibre.utils import df_to_geojson, get_bounds RESOLUTION = 7 +COLORS = ("lightblue", "turquoise", "lightgreen", "yellow", "orange", "darkred") road_safety = pd.read_csv( "https://raw.githubusercontent.com/visgl/deck.gl-data/master/examples/3d-heatmap/heatmap-data.csv" ).dropna() -road_safety["h3"] = road_safety.apply( - lambda x: h3.geo_to_h3(x["lat"], x["lng"], resolution=RESOLUTION), axis=1 -) - -df = road_safety.groupby("h3").h3.agg("count").to_frame("count").reset_index() -df["hexagon"] = df.apply( - lambda x: [h3.h3_to_geo_boundary(x["h3"], geo_json=True)], axis=1 -) +def create_h3_grid(res=RESOLUTION) -> pd.DataFrame: + road_safety["h3"] = road_safety.apply( + lambda x: h3.geo_to_h3(x["lat"], x["lng"], resolution=res), axis=1 + ) + df = road_safety.groupby("h3").h3.agg("count").to_frame("count").reset_index() + df["hexagon"] = df.apply( + lambda x: [h3.h3_to_geo_boundary(x["h3"], geo_json=True)], axis=1 + ) + df["color"] = pd.cut( + df["count"], + bins=len(COLORS), + labels=COLORS, + ) + return df -colors = ("lightblue", "turquoise", "lightgreen", "yellow", "orange", "darkred") -df["color"] = pd.cut( - df["count"], - bins=len(colors), - labels=colors, -) +df = create_h3_grid() source = GeoJSONSource( data=df_to_geojson( @@ -39,7 +41,6 @@ def create_map() -> Map: - # m = Map(MapOptions(bounds=bounds)) m = Map(MapOptions(center=(-1.415727, 52.232395), zoom=7, pitch=40, bearing=-27)) m.add_layer( Layer( From c7058eaba1f8ef91891148f91c075c394b91d3f7 Mon Sep 17 00:00:00 2001 From: Stefan Kuethe Date: Sat, 20 Jan 2024 19:45:29 +0100 Subject: [PATCH 36/58] Add res slider --- docs/examples/road_safety/app.py | 51 ++++++++++++++++++++++---------- 1 file changed, 36 insertions(+), 15 deletions(-) diff --git a/docs/examples/road_safety/app.py b/docs/examples/road_safety/app.py index 68ecf21b..c2afed6b 100644 --- a/docs/examples/road_safety/app.py +++ b/docs/examples/road_safety/app.py @@ -2,11 +2,13 @@ import h3 import pandas as pd -from maplibre import Layer, LayerType, Map, MapOptions +from maplibre import (Layer, LayerType, Map, MapContext, MapOptions, + output_maplibregl, render_maplibregl) from maplibre.sources import GeoJSONSource from maplibre.utils import df_to_geojson, get_bounds +from shiny import App, reactive, ui -RESOLUTION = 7 +RESOLUTION = 6 COLORS = ("lightblue", "turquoise", "lightgreen", "yellow", "orange", "darkred") road_safety = pd.read_csv( @@ -14,7 +16,7 @@ ).dropna() -def create_h3_grid(res=RESOLUTION) -> pd.DataFrame: +def create_h3_grid(res=RESOLUTION) -> dict: road_safety["h3"] = road_safety.apply( lambda x: h3.geo_to_h3(x["lat"], x["lng"], resolution=res), axis=1 ) @@ -27,16 +29,14 @@ def create_h3_grid(res=RESOLUTION) -> pd.DataFrame: bins=len(COLORS), labels=COLORS, ) - return df + return df_to_geojson( + df, "hexagon", geometry_type="Polygon", properties=["count", "color"] + ) -df = create_h3_grid() +geojson = create_h3_grid() -source = GeoJSONSource( - data=df_to_geojson( - df, "hexagon", geometry_type="Polygon", properties=["count", "color"] - ) -) +source = GeoJSONSource(data=geojson) bounds = get_bounds(source.data) @@ -58,10 +58,31 @@ def create_map() -> Map: return m -filename = "/tmp/road_safety.html" -with open(filename, "w") as f: - m = create_map() - f.write(m.to_html()) +app_ui = ui.page_fluid( + ui.panel_title("Road safety in UK"), + output_maplibregl("mapylibre", height=700), + ui.input_slider("res", "Resolution", min=4, max=8, step=1, value=RESOLUTION), +) + + +def server(input, output, session): + @render_maplibregl + def mapylibre(): + return create_map() + + @reactive.Effect + @reactive.event(input.res, ignore_init=True) + async def resolutiom(): + async with MapContext("mapylibre", session=session) as m: + m.set_data("road-safety", create_h3_grid(input.res())) + + +app = App(app_ui, server) +if __name__ == "__main__": + filename = "/tmp/road_safety.html" + with open(filename, "w") as f: + m = create_map() + f.write(m.to_html()) -webbrowser.open(filename) + webbrowser.open(filename) From 09a180fcd38602e6858852b4591f49476ee7a8b1 Mon Sep 17 00:00:00 2001 From: Stefan Kuethe Date: Sat, 20 Jan 2024 20:12:00 +0100 Subject: [PATCH 37/58] Add progress bar --- docs/examples/road_safety/app.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/docs/examples/road_safety/app.py b/docs/examples/road_safety/app.py index c2afed6b..574a72c9 100644 --- a/docs/examples/road_safety/app.py +++ b/docs/examples/road_safety/app.py @@ -72,9 +72,13 @@ def mapylibre(): @reactive.Effect @reactive.event(input.res, ignore_init=True) - async def resolutiom(): + async def resolution(): async with MapContext("mapylibre", session=session) as m: - m.set_data("road-safety", create_h3_grid(input.res())) + with ui.Progress() as p: + p.set(message="H3 calculation in progress") + geojson = create_h3_grid(input.res()) + m.set_data("road-safety", geojson) + p.set(1, message="Calculation finished") app = App(app_ui, server) From 063abf328714d0a8eb3c3dc6ba7ada0e35acafe2 Mon Sep 17 00:00:00 2001 From: Stefan Kuethe Date: Sat, 20 Jan 2024 23:55:12 +0100 Subject: [PATCH 38/58] Update docs --- docs/examples/road_safety/app.py | 24 ++++++++++++++++++------ docs/examples/road_safety/index.md | 9 +++++++++ mkdocs.yml | 1 + 3 files changed, 28 insertions(+), 6 deletions(-) create mode 100644 docs/examples/road_safety/index.md diff --git a/docs/examples/road_safety/app.py b/docs/examples/road_safety/app.py index 574a72c9..f47c0bad 100644 --- a/docs/examples/road_safety/app.py +++ b/docs/examples/road_safety/app.py @@ -2,14 +2,28 @@ import h3 import pandas as pd -from maplibre import (Layer, LayerType, Map, MapContext, MapOptions, - output_maplibregl, render_maplibregl) +from maplibre import ( + Layer, + LayerType, + Map, + MapContext, + MapOptions, + output_maplibregl, + render_maplibregl, +) from maplibre.sources import GeoJSONSource -from maplibre.utils import df_to_geojson, get_bounds +from maplibre.utils import df_to_geojson from shiny import App, reactive, ui RESOLUTION = 6 -COLORS = ("lightblue", "turquoise", "lightgreen", "yellow", "orange", "darkred") +COLORS = ( + "lightblue", + "turquoise", + "lightgreen", + "yellow", + "orange", + "darkred", +) road_safety = pd.read_csv( "https://raw.githubusercontent.com/visgl/deck.gl-data/master/examples/3d-heatmap/heatmap-data.csv" @@ -35,9 +49,7 @@ def create_h3_grid(res=RESOLUTION) -> dict: geojson = create_h3_grid() - source = GeoJSONSource(data=geojson) -bounds = get_bounds(source.data) def create_map() -> Map: diff --git a/docs/examples/road_safety/index.md b/docs/examples/road_safety/index.md new file mode 100644 index 00000000..e7ac5483 --- /dev/null +++ b/docs/examples/road_safety/index.md @@ -0,0 +1,9 @@ +```python +-8<-- "road_safety/app.py" +``` + +Run example: + +``` bash +poetry run uvicorn docs.examples.road_safety.app:app --reload +``` diff --git a/mkdocs.yml b/mkdocs.yml index 67d226bb..e504b74a 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -29,6 +29,7 @@ nav: - 3D Indoor mapping: examples/3d_indoor_mapping/index.md - Custom basemap: examples/custom_basemap/index.md - GeoPandas: examples/geopandas/index.md + - UK Road Safety: examples/road_safety/index.md plugins: - search: From a3608ab663571b2bf4f0046678125e67ea5998f0 Mon Sep 17 00:00:00 2001 From: Stefan Kuethe Date: Sat, 20 Jan 2024 23:59:27 +0100 Subject: [PATCH 39/58] Refactor --- docs/examples/road_safety/app.py | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/docs/examples/road_safety/app.py b/docs/examples/road_safety/app.py index f47c0bad..b79786f7 100644 --- a/docs/examples/road_safety/app.py +++ b/docs/examples/road_safety/app.py @@ -48,8 +48,7 @@ def create_h3_grid(res=RESOLUTION) -> dict: ) -geojson = create_h3_grid() -source = GeoJSONSource(data=geojson) +source = GeoJSONSource(data=create_h3_grid()) def create_map() -> Map: @@ -85,11 +84,10 @@ def mapylibre(): @reactive.Effect @reactive.event(input.res, ignore_init=True) async def resolution(): - async with MapContext("mapylibre", session=session) as m: + async with MapContext("mapylibre") as m: with ui.Progress() as p: p.set(message="H3 calculation in progress") - geojson = create_h3_grid(input.res()) - m.set_data("road-safety", geojson) + m.set_data("road-safety", create_h3_grid(input.res())) p.set(1, message="Calculation finished") From 5c7983ecad88d66d6e609fbc9045ed01a2dd0c1a Mon Sep 17 00:00:00 2001 From: Stefan Kuethe Date: Sun, 21 Jan 2024 11:13:07 +0100 Subject: [PATCH 40/58] Update examples --- docs/examples/3d_indoor_mapping/index.md | 4 +- docs/examples/geopandas/index.md | 4 +- docs/examples/road_safety/app.html | 167 +++++++++++++++++++++++ docs/examples/road_safety/app.py | 7 +- docs/examples/road_safety/index.md | 2 + docs/examples/vancouver_blocks/index.md | 4 +- 6 files changed, 179 insertions(+), 9 deletions(-) create mode 100644 docs/examples/road_safety/app.html diff --git a/docs/examples/3d_indoor_mapping/index.md b/docs/examples/3d_indoor_mapping/index.md index 3219f96b..9f231f34 100644 --- a/docs/examples/3d_indoor_mapping/index.md +++ b/docs/examples/3d_indoor_mapping/index.md @@ -1,9 +1,9 @@ +See example in action + ```python -8<-- "3d_indoor_mapping/app.py" ``` -See example in action - Run example: ``` bash diff --git a/docs/examples/geopandas/index.md b/docs/examples/geopandas/index.md index de01d4b2..fffccb96 100644 --- a/docs/examples/geopandas/index.md +++ b/docs/examples/geopandas/index.md @@ -1,9 +1,9 @@ +See example in action + ```python -8<-- "geopandas/app.py" ``` -See example in action - Run example: ``` bash diff --git a/docs/examples/road_safety/app.html b/docs/examples/road_safety/app.html new file mode 100644 index 00000000..1482e9a7 --- /dev/null +++ b/docs/examples/road_safety/app.html @@ -0,0 +1,167 @@ + + + +Pymaplibregl + + + + +
+ + + \ No newline at end of file diff --git a/docs/examples/road_safety/app.py b/docs/examples/road_safety/app.py index b79786f7..2e8b9c8d 100644 --- a/docs/examples/road_safety/app.py +++ b/docs/examples/road_safety/app.py @@ -1,3 +1,4 @@ +import sys import webbrowser import h3 @@ -15,7 +16,7 @@ from maplibre.utils import df_to_geojson from shiny import App, reactive, ui -RESOLUTION = 6 +RESOLUTION = 7 COLORS = ( "lightblue", "turquoise", @@ -94,9 +95,9 @@ async def resolution(): app = App(app_ui, server) if __name__ == "__main__": - filename = "/tmp/road_safety.html" + filename = sys.argv[1] if len(sys.argv) == 2 else "/tmp/road_safety.html" with open(filename, "w") as f: m = create_map() - f.write(m.to_html()) + f.write(m.to_html(style="height: 700px;")) webbrowser.open(filename) diff --git a/docs/examples/road_safety/index.md b/docs/examples/road_safety/index.md index e7ac5483..4173e20c 100644 --- a/docs/examples/road_safety/index.md +++ b/docs/examples/road_safety/index.md @@ -1,3 +1,5 @@ +See example in action (without reactive effects) + ```python -8<-- "road_safety/app.py" ``` diff --git a/docs/examples/vancouver_blocks/index.md b/docs/examples/vancouver_blocks/index.md index 0afed974..5af20d82 100644 --- a/docs/examples/vancouver_blocks/index.md +++ b/docs/examples/vancouver_blocks/index.md @@ -1,9 +1,9 @@ +See example in action (without reactive effects) + ```python -8<-- "vancouver_blocks/app.py" ``` -See example in action (without reactive effects) - Run example: ```bash From 6481815a70cb34e3257a2b0c48ee16a3f210ae76 Mon Sep 17 00:00:00 2001 From: Stefan Kuethe Date: Sun, 21 Jan 2024 12:29:18 +0100 Subject: [PATCH 41/58] Update example --- docs/examples/where_is_the_iss/app.py | 47 +++++++++++++++++++-------- 1 file changed, 33 insertions(+), 14 deletions(-) diff --git a/docs/examples/where_is_the_iss/app.py b/docs/examples/where_is_the_iss/app.py index d6c734e3..d1daf24c 100644 --- a/docs/examples/where_is_the_iss/app.py +++ b/docs/examples/where_is_the_iss/app.py @@ -12,6 +12,8 @@ from shiny import App, reactive, ui MAX_FEATURES = 30 +SOURCE_ID_ISS_POSITION = "iss_position" +SOURCE_ID_ISS_LAST_POSITIONS = "iss-last-positions" def where_is_the_iss() -> tuple: @@ -19,24 +21,27 @@ def where_is_the_iss() -> tuple: return (r["longitude"], r["latitude"]) -def create_feature(lng_lat) -> dict: +def create_feature(lng_lat: tuple) -> dict: return { "type": "Feature", - "geometry": {"type": "Point", "coordinates": lng_lat}, + "geometry": { + "type": "Point", + "coordinates": lng_lat, + }, + "properties": {"coords": ", ".join(map(str, lng_lat))}, } lng_lat = where_is_the_iss() -# print(lng_lat) - -feature = create_feature(lng_lat) -# print(feature) - +feature = create_feature(where_is_the_iss()) feature_collection = {"type": "FeatureCollection", "features": [feature]} -# print(feature_collection) app_ui = ui.page_fluid( - ui.h1("Where is the ISS"), + ui.panel_title("Where is the ISS"), + ui.div("Click on Update to get the current position of the ISS."), + ui.div( + "The yellow dots show the positions before. Hover the blue dot to display the coordinates." + ), output_maplibregl("mapylibre", height=600), ui.input_action_button("update", "Update"), ) @@ -46,14 +51,27 @@ def server(input, output, session): @render_maplibregl def mapylibre(): m = Map(MapOptions(center=lng_lat, zoom=3)) - m.add_source("iss", GeoJSONSource(data=feature_collection)) + m.set_paint_property("water", "fill-color", "darkblue") + m.add_source( + SOURCE_ID_ISS_LAST_POSITIONS, GeoJSONSource(data=feature_collection) + ) m.add_layer( Layer( type=LayerType.CIRCLE, - source="iss", + source=SOURCE_ID_ISS_LAST_POSITIONS, paint={"circle-color": "yellow", "circle-radius": 5}, ), ) + m.add_source(SOURCE_ID_ISS_POSITION, GeoJSONSource(data=feature)) + m.add_layer( + Layer( + type=LayerType.CIRCLE, + id="iss-position", + source=SOURCE_ID_ISS_POSITION, + paint={"circle-color": "lightblue", "circle-radius": 7}, + ) + ) + m.add_tooltip("iss-position", "coords") return m @reactive.Effect @@ -66,9 +84,10 @@ async def update(): feature_collection["features"] = [] async with MapContext("mapylibre") as m: - feature_collection["features"].append(create_feature(lng_lat)) - print(feature_collection) - m.set_data("iss", feature_collection) + feature = create_feature(lng_lat) + m.set_data(SOURCE_ID_ISS_POSITION, feature) + feature_collection["features"].append(feature) + m.set_data(SOURCE_ID_ISS_LAST_POSITIONS, feature_collection) m.add_call("flyTo", {"center": lng_lat, "speed": 0.5}) From 4800661aa0206d46f492ca9d49f7958de07177c8 Mon Sep 17 00:00:00 2001 From: Stefan Kuethe Date: Sun, 21 Jan 2024 12:35:50 +0100 Subject: [PATCH 42/58] Add example to docs --- docs/examples/where_is_the_iss/index.md | 9 +++++++++ mkdocs.yml | 1 + 2 files changed, 10 insertions(+) create mode 100644 docs/examples/where_is_the_iss/index.md diff --git a/docs/examples/where_is_the_iss/index.md b/docs/examples/where_is_the_iss/index.md new file mode 100644 index 00000000..89bbed77 --- /dev/null +++ b/docs/examples/where_is_the_iss/index.md @@ -0,0 +1,9 @@ +```python +-8<-- "where_is_the_iss/app.py" +``` + +Run example: + +```bash +poetry run uvicorn docs.examples.where_is_the_iss.app:app --reload +``` diff --git a/mkdocs.yml b/mkdocs.yml index e504b74a..95e5a0d3 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -30,6 +30,7 @@ nav: - Custom basemap: examples/custom_basemap/index.md - GeoPandas: examples/geopandas/index.md - UK Road Safety: examples/road_safety/index.md + - Where is the ISS: examples/where_is_the_iss/index.md plugins: - search: From d14fa671c2e79f8b905b994e40bb771e6d29f9cc Mon Sep 17 00:00:00 2001 From: Stefan Kuethe Date: Sun, 21 Jan 2024 15:56:54 +0100 Subject: [PATCH 43/58] Set func to change visibility of a layer --- docs/examples/vancouver_blocks/app.py | 20 +++++++++++++++++--- maplibre/map.py | 17 ++++++++++++++++- 2 files changed, 33 insertions(+), 4 deletions(-) diff --git a/docs/examples/vancouver_blocks/app.py b/docs/examples/vancouver_blocks/app.py index 01a23c6c..8b1b7c97 100644 --- a/docs/examples/vancouver_blocks/app.py +++ b/docs/examples/vancouver_blocks/app.py @@ -44,8 +44,8 @@ [0, "grey"], [1000, "yellow"], [5000, "orange"], - [10000, "red"], - [50000, "darkred"], + [10000, "darkred"], + [50000, "lightblue"], ], }, "fill-extrusion-height": ["*", 10, ["sqrt", ["get", "valuePerSqm"]]], @@ -78,13 +78,19 @@ def create_map() -> Map: "Height of polygons - average property value per square meter of lot", style="padding: 10px;", ), - output_maplibregl("maplibre", height=700), + output_maplibregl("maplibre", height=600), ui.input_select( "filter", "max property value per square meter", choices=[0, 1000, 5000, 10000, 50000, 100000, MAX_FILTER_VALUE], selected=MAX_FILTER_VALUE, ), + ui.input_checkbox_group( + "layers", + "Layers", + choices=[LAYER_ID_FILL, LAYER_ID_LINES], + selected=[LAYER_ID_FILL, LAYER_ID_LINES], + ), ) @@ -101,6 +107,14 @@ async def filter(): filter_ = ["<=", ["get", "valuePerSqm"], int(input.filter())] m.set_filter(LAYER_ID_FILL, filter_) + @reactive.Effect + @reactive.event(input.layers) + async def layers(): + visible_layers = input.layers() + async with MapContext("maplibre") as m: + for layer in [LAYER_ID_FILL, LAYER_ID_LINES]: + m.set_visibility(layer, layer in visible_layers) + app = App(app_ui, server) diff --git a/maplibre/map.py b/maplibre/map.py index 32a8f76f..ede455fb 100644 --- a/maplibre/map.py +++ b/maplibre/map.py @@ -206,9 +206,24 @@ def set_layout_property(self, layer_id: str, prop: str, value: any) -> None: self.add_call("setLayoutProperty", layer_id, prop, value) def set_data(self, source_id: str, data: dict) -> None: - """Update the data of a GeoJSON source""" + """Update the data of a GeoJSON source + + Args: + source_id (str): The name of the source to be updated. + data (dict): The data of the source. + """ self.add_call("setSourceData", source_id, data) + def set_visibility(self, layer_id: str, visible: bool = True) -> None: + """Update the visibility of a layer + + Args: + layer_id (str): The name of the layer to be updated. + visible (bool): Whether the layer is visible or not. + """ + value = "visible" if visible else "none" + self.add_call("setLayoutProperty", layer_id, "visibility", value) + def to_html(self, **kwargs) -> str: """Render to html From 3eba6d8b26700b698095b07731f1d5489c14d5db Mon Sep 17 00:00:00 2001 From: Stefan Kuethe Date: Sun, 21 Jan 2024 15:57:37 +0100 Subject: [PATCH 44/58] Increase version --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index e5da501b..6db24df4 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "maplibre" -version = "0.1.1" +version = "0.1.2" description = "Python bindings for MapLibre GL JS" authors = ["Stefan Kuethe "] readme = "README.md" From 5344f29a1c75effa68daf37bcf06232209793127 Mon Sep 17 00:00:00 2001 From: Stefan Kuethe Date: Sun, 21 Jan 2024 16:01:49 +0100 Subject: [PATCH 45/58] Add set_data support for ipywidget --- maplibre/srcjs/ipywidget.js | 3 +++ srcjs/mapmethods.js | 4 ++++ 2 files changed, 7 insertions(+) diff --git a/maplibre/srcjs/ipywidget.js b/maplibre/srcjs/ipywidget.js index ec8c48bc..3a488ce7 100644 --- a/maplibre/srcjs/ipywidget.js +++ b/maplibre/srcjs/ipywidget.js @@ -45,6 +45,9 @@ function getCustomMapMethods(maplibregl2, map) { marker.setPopup(popup_); } marker.addTo(map); + }, + setSourceData: function(sourceId, data) { + map.getSource(sourceId).setData(data); } }; } diff --git a/srcjs/mapmethods.js b/srcjs/mapmethods.js index 35820ee1..17d094fd 100644 --- a/srcjs/mapmethods.js +++ b/srcjs/mapmethods.js @@ -50,6 +50,10 @@ function getCustomMapMethods(maplibregl, map) { } marker.addTo(map); }, + + setSourceData: function (sourceId, data) { + map.getSource(sourceId).setData(data); + }, }; } From e394196de2c34228f8c643fc7a80635f911679be Mon Sep 17 00:00:00 2001 From: Stefan Kuethe Date: Sun, 21 Jan 2024 18:34:24 +0100 Subject: [PATCH 46/58] Remove dead code --- docs/examples/jupyter/getting_started.ipynb | 152 ++++++++++++++++++-- maplibre/ipywidget.py | 52 ------- 2 files changed, 143 insertions(+), 61 deletions(-) diff --git a/docs/examples/jupyter/getting_started.ipynb b/docs/examples/jupyter/getting_started.ipynb index 1e7a794d..388953c0 100644 --- a/docs/examples/jupyter/getting_started.ipynb +++ b/docs/examples/jupyter/getting_started.ipynb @@ -2,7 +2,7 @@ "cells": [ { "cell_type": "code", - "execution_count": 8, + "execution_count": 1, "id": "8ae5dd75-d944-4304-88c6-ec2db700dcec", "metadata": {}, "outputs": [], @@ -70,22 +70,22 @@ }, { "cell_type": "code", - "execution_count": 9, + "execution_count": 4, "id": "e6e2284c-1862-4697-ad94-f535b3682197", "metadata": {}, "outputs": [ { "data": { "application/vnd.jupyter.widget-view+json": { - "model_id": "32df09ba11cb4c64b7c79c3e389e4662", + "model_id": "ece4babe53924aa7886553d1b5d53bec", "version_major": 2, "version_minor": 0 }, "text/plain": [ - "MapWidget(height='400px', map_options={'style': 'https://basemaps.cartocdn.com/gl/dark-matter-gl-style/style.j…" + "MapWidget(calls=[['addControl', ('ScaleControl', {'unit': 'metric'}, 'bottom-left')], ['addLayer', ({'id': 'ea…" ] }, - "execution_count": 9, + "execution_count": 4, "metadata": {}, "output_type": "execute_result" } @@ -109,14 +109,14 @@ }, { "cell_type": "code", - "execution_count": 5, + "execution_count": 6, "id": "356960fa-b866-42c8-a58e-0c9a417c28eb", "metadata": {}, "outputs": [ { "data": { "application/vnd.jupyter.widget-view+json": { - "model_id": "437bd09dbdd24f0896193788a76610b4", + "model_id": "60ef57a918734016b9766e41615c1935", "version_major": 2, "version_minor": 0 }, @@ -133,7 +133,7 @@ "(radius)>" ] }, - "execution_count": 5, + "execution_count": 6, "metadata": {}, "output_type": "execute_result" } @@ -284,9 +284,143 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 5, "id": "7ad74d91-1137-45b4-8791-83dc3546535e", "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Help on _InteractFactory in module ipywidgets.widgets.interaction object:\n", + "\n", + "class _InteractFactory(builtins.object)\n", + " | _InteractFactory(cls, options, kwargs={})\n", + " | \n", + " | Factory for instances of :class:`interactive`.\n", + " | \n", + " | This class is needed to support options like::\n", + " | \n", + " | >>> @interact.options(manual=True)\n", + " | ... def greeting(text=\"World\"):\n", + " | ... print(\"Hello {}\".format(text))\n", + " | \n", + " | Parameters\n", + " | ----------\n", + " | cls : class\n", + " | The subclass of :class:`interactive` to construct.\n", + " | options : dict\n", + " | A dict of options used to construct the interactive\n", + " | function. By default, this is returned by\n", + " | ``cls.default_options()``.\n", + " | kwargs : dict\n", + " | A dict of **kwargs to use for widgets.\n", + " | \n", + " | Methods defined here:\n", + " | \n", + " | __call__(self, _InteractFactory__interact_f=None, **kwargs)\n", + " | Make the given function interactive by adding and displaying\n", + " | the corresponding :class:`interactive` widget.\n", + " | \n", + " | Expects the first argument to be a function. Parameters to this\n", + " | function are widget abbreviations passed in as keyword arguments\n", + " | (``**kwargs``). Can be used as a decorator (see examples).\n", + " | \n", + " | Returns\n", + " | -------\n", + " | f : __interact_f with interactive widget attached to it.\n", + " | \n", + " | Parameters\n", + " | ----------\n", + " | __interact_f : function\n", + " | The function to which the interactive widgets are tied. The `**kwargs`\n", + " | should match the function signature. Passed to :func:`interactive()`\n", + " | **kwargs : various, optional\n", + " | An interactive widget is created for each keyword argument that is a\n", + " | valid widget abbreviation. Passed to :func:`interactive()`\n", + " | \n", + " | Examples\n", + " | --------\n", + " | Render an interactive text field that shows the greeting with the passed in\n", + " | text::\n", + " | \n", + " | # 1. Using interact as a function\n", + " | def greeting(text=\"World\"):\n", + " | print(\"Hello {}\".format(text))\n", + " | interact(greeting, text=\"Jupyter Widgets\")\n", + " | \n", + " | # 2. Using interact as a decorator\n", + " | @interact\n", + " | def greeting(text=\"World\"):\n", + " | print(\"Hello {}\".format(text))\n", + " | \n", + " | # 3. Using interact as a decorator with named parameters\n", + " | @interact(text=\"Jupyter Widgets\")\n", + " | def greeting(text=\"World\"):\n", + " | print(\"Hello {}\".format(text))\n", + " | \n", + " | Render an interactive slider widget and prints square of number::\n", + " | \n", + " | # 1. Using interact as a function\n", + " | def square(num=1):\n", + " | print(\"{} squared is {}\".format(num, num*num))\n", + " | interact(square, num=5)\n", + " | \n", + " | # 2. Using interact as a decorator\n", + " | @interact\n", + " | def square(num=2):\n", + " | print(\"{} squared is {}\".format(num, num*num))\n", + " | \n", + " | # 3. Using interact as a decorator with named parameters\n", + " | @interact(num=5)\n", + " | def square(num=2):\n", + " | print(\"{} squared is {}\".format(num, num*num))\n", + " | \n", + " | __init__(self, cls, options, kwargs={})\n", + " | Initialize self. See help(type(self)) for accurate signature.\n", + " | \n", + " | options(self, **kwds)\n", + " | Change options for interactive functions.\n", + " | \n", + " | Returns\n", + " | -------\n", + " | A new :class:`_InteractFactory` which will apply the\n", + " | options when called.\n", + " | \n", + " | widget(self, f)\n", + " | Return an interactive function widget for the given function.\n", + " | \n", + " | The widget is only constructed, not displayed nor attached to\n", + " | the function.\n", + " | \n", + " | Returns\n", + " | -------\n", + " | An instance of ``self.cls`` (typically :class:`interactive`).\n", + " | \n", + " | Parameters\n", + " | ----------\n", + " | f : function\n", + " | The function to which the interactive widgets are tied.\n", + " | \n", + " | ----------------------------------------------------------------------\n", + " | Data descriptors defined here:\n", + " | \n", + " | __dict__\n", + " | dictionary for instance variables (if defined)\n", + " | \n", + " | __weakref__\n", + " | list of weak references to the object (if defined)\n", + "\n" + ] + } + ], + "source": [] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "9d34e0d6-7ff9-4f72-9af7-e8a19d51604a", + "metadata": {}, "outputs": [], "source": [] } diff --git a/maplibre/ipywidget.py b/maplibre/ipywidget.py index 8feae38a..08a8c5a1 100644 --- a/maplibre/ipywidget.py +++ b/maplibre/ipywidget.py @@ -12,58 +12,6 @@ from .sources import Source -# DEPRECATED: MapWidget now uses map.Map as base class -class BaseMap(object): - def __init__(self, map_options=MapOptions(), **kwargs) -> None: - self.map_options = map_options.to_dict() | kwargs - - # This method must be overwritten - def add_call(self, method_name: str, *args) -> None: - """Add a method call that is executed on the map instance - - Args: - method_name (str): The name of the map method to be executed. - *args (any): The arguments to be passed to the map method. - """ - # TODO: Pass as dict? {"name": method_name, "args": args} - call = [method_name, args] - print(call) - - def add_source(self, source_id: str, source: Source) -> None: - """Add a source to the map""" - self.add_call("addSource", source_id, source.to_dict()) - - def add_layer(self, layer: Layer) -> None: - """Add a layer to the map""" - self.add_call("addLayer", layer.to_dict()) - - def add_control( - self, - control: Control, - position: [str | ControlPosition] = ControlPosition.TOP_RIGHT, - ) -> None: - """Add a control to the map""" - self.add_call( - "addControl", - control.type, - control.to_dict(), - ControlPosition(position).value, - ) - - def set_paint_property(self, layer_id: str, prop: str, value: any) -> None: - """Update a paint property of a layer""" - self.add_call("setPaintProperty", layer_id, prop, value) - - def set_layout_property(self, layer_id: str, prop: str, value: any) -> None: - """Update a layout property of a layer""" - self.add_call("setLayoutProperty", layer_id, prop, value) - - def add_tooltip(self, layer_id: str, prop: str) -> None: - """Add a tooltip to the map""" - self.add_call("addPopup", layer_id, prop) - - -# TODO: Rename to MapWidget or IpyMap class MapWidget(AnyWidget, Map): """MapWidget From c08545a54c0ca04aa12a0374a747ce7aa1c72b92 Mon Sep 17 00:00:00 2001 From: Stefan Kuethe Date: Sun, 21 Jan 2024 18:45:06 +0100 Subject: [PATCH 47/58] Remove requests dependency --- .github/workflows/pytest.yml | 30 ++++++++++++++++++++++++++++++ poetry.lock | 8 ++++---- pyproject.toml | 1 - 3 files changed, 34 insertions(+), 5 deletions(-) create mode 100644 .github/workflows/pytest.yml diff --git a/.github/workflows/pytest.yml b/.github/workflows/pytest.yml new file mode 100644 index 00000000..a2682349 --- /dev/null +++ b/.github/workflows/pytest.yml @@ -0,0 +1,30 @@ +name: Python package + +on: [push] + +jobs: + build: + + runs-on: ubuntu-latest + strategy: + matrix: + python-version: ["3.9", "3.10", "3.11", "3.12"] + + steps: + - uses: actions/checkout@v4 + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v4 + with: + python-version: ${{ matrix.python-version }} + # You can test your matrix by printing the current Python version + - name: Display Python version + run: python -c "import sys; print(sys.version)" + - name: Install Poetry and pytest + run: pip install poetry pytest + - name: Install package + run: poetry install + - name: Test package + run: | + poetry run pytest + poetry run pytest --doctest-modules maplibre + diff --git a/poetry.lock b/poetry.lock index 358abc41..51db4cb3 100644 --- a/poetry.lock +++ b/poetry.lock @@ -203,7 +203,7 @@ files = [ name = "charset-normalizer" version = "3.3.2" description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." -category = "main" +category = "dev" optional = false python-versions = ">=3.7.0" files = [ @@ -1845,7 +1845,7 @@ files = [ name = "requests" version = "2.31.0" description = "Python HTTP for Humans." -category = "main" +category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -2105,7 +2105,7 @@ test = ["coverage", "pytest", "pytest-cov"] name = "urllib3" version = "2.1.0" description = "HTTP library with thread-safe connection pooling, file post, and more." -category = "main" +category = "dev" optional = false python-versions = ">=3.8" files = [ @@ -2394,4 +2394,4 @@ all = ["geopandas", "pandas"] [metadata] lock-version = "2.0" python-versions = ">=3.9,<4" -content-hash = "f63acd17c501b755d5a19a20a822b8673678bb1eb09a5dea577d6fefaf1a9b6d" +content-hash = "47ba10b55a403b30b258b7298c519ac5c9e0a4b5c611c578f0d3a665233d7d88" diff --git a/pyproject.toml b/pyproject.toml index 6db24df4..43e3cd7d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -13,7 +13,6 @@ include = [ python = ">=3.9,<4" shiny = "^0.6.1" htmltools = "^0.5.1" -requests = "^2.31.0" jinja2 = "^3.1.3" pydantic = "^2.5.3" anywidget = "^0.8.1" From 2b40bf134d9c58fd471b3d1d782d34ae0f5d75d7 Mon Sep 17 00:00:00 2001 From: Stefan Kuethe Date: Sun, 21 Jan 2024 18:51:23 +0100 Subject: [PATCH 48/58] Remove or skip some tests --- tests/test_controls.py | 3 --- tests/test_experimental.py | 19 ------------------- tests/test_mapwidget.py | 2 +- 3 files changed, 1 insertion(+), 23 deletions(-) delete mode 100644 tests/test_experimental.py diff --git a/tests/test_controls.py b/tests/test_controls.py index 72abd426..f7414a73 100644 --- a/tests/test_controls.py +++ b/tests/test_controls.py @@ -7,6 +7,3 @@ def test_scale_control(): assert control.type == ControlType.SCALE.value assert control.unit == "metric" - - -# def test_scale_control(): diff --git a/tests/test_experimental.py b/tests/test_experimental.py deleted file mode 100644 index 4d16ec31..00000000 --- a/tests/test_experimental.py +++ /dev/null @@ -1,19 +0,0 @@ -from maplibre.experimental import PydanticSer -from maplibre.layer import Layer, LayerType - - -def test_pydantic_model(): - m = PydanticSer(a=1, b=2) - - print(m) - print(m.model_dump()) - print(dict(m)) - print(m.to_dict()) - - -def test_layer(): - layer = Layer(type=LayerType.LINE) - print("line", LayerType(LayerType.LINE).value) - - print(layer.model_dump()) - # print(layer.to_dict()) diff --git a/tests/test_mapwidget.py b/tests/test_mapwidget.py index ce8ddc38..c51b6411 100644 --- a/tests/test_mapwidget.py +++ b/tests/test_mapwidget.py @@ -2,7 +2,7 @@ from maplibre.ipywidget import MapWidget -# @pytest.mark.skip("enable me") +@pytest.mark.skip("enable me") def test_maplibre_widget(): widget = MapWidget(height=200) print(widget.map_options) From 34fd2d353181b38f1ab770ce8c86854fedc52d42 Mon Sep 17 00:00:00 2001 From: Stefan Kuethe Date: Sun, 21 Jan 2024 18:59:27 +0100 Subject: [PATCH 49/58] Add dep --- .github/workflows/pytest.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/pytest.yml b/.github/workflows/pytest.yml index a2682349..d5c08321 100644 --- a/.github/workflows/pytest.yml +++ b/.github/workflows/pytest.yml @@ -25,6 +25,7 @@ jobs: run: poetry install - name: Test package run: | + apt-get install -y libxcb-xinerama0 poetry run pytest poetry run pytest --doctest-modules maplibre From 5e8b621ce4e00f9acc22620e71ce92280e9a05af Mon Sep 17 00:00:00 2001 From: Stefan Kuethe Date: Sun, 21 Jan 2024 19:01:31 +0100 Subject: [PATCH 50/58] Skip 3.9 --- .github/workflows/pytest.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.github/workflows/pytest.yml b/.github/workflows/pytest.yml index d5c08321..cd38d553 100644 --- a/.github/workflows/pytest.yml +++ b/.github/workflows/pytest.yml @@ -8,7 +8,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - python-version: ["3.9", "3.10", "3.11", "3.12"] + python-version: ["3.10", "3.11", "3.12"] steps: - uses: actions/checkout@v4 @@ -25,7 +25,6 @@ jobs: run: poetry install - name: Test package run: | - apt-get install -y libxcb-xinerama0 poetry run pytest poetry run pytest --doctest-modules maplibre From 84dc26d8c480c7347bdb8b909d18c1850a755b6e Mon Sep 17 00:00:00 2001 From: Stefan Kuethe Date: Sun, 21 Jan 2024 19:03:07 +0100 Subject: [PATCH 51/58] Add dep --- .github/workflows/pytest.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/pytest.yml b/.github/workflows/pytest.yml index cd38d553..51cc6e74 100644 --- a/.github/workflows/pytest.yml +++ b/.github/workflows/pytest.yml @@ -25,6 +25,7 @@ jobs: run: poetry install - name: Test package run: | + sudo apt-get install -y libxcb-xinerama0 poetry run pytest poetry run pytest --doctest-modules maplibre From 5f68d831fb955244c13b873914d5db1f90070b18 Mon Sep 17 00:00:00 2001 From: Stefan Kuethe Date: Sun, 21 Jan 2024 19:35:19 +0100 Subject: [PATCH 52/58] Fix anywidget error when running tests --- .github/workflows/pytest.yml | 2 +- maplibre/__init__.py | 3 ++- tests/test_mapwidget.py | 5 ++++- 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/.github/workflows/pytest.yml b/.github/workflows/pytest.yml index 51cc6e74..7782eafb 100644 --- a/.github/workflows/pytest.yml +++ b/.github/workflows/pytest.yml @@ -8,7 +8,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - python-version: ["3.10", "3.11", "3.12"] + python-version: ["3.9", "3.10", "3.11", "3.12"] steps: - uses: actions/checkout@v4 diff --git a/maplibre/__init__.py b/maplibre/__init__.py index b9b8cea0..d1bd37cc 100644 --- a/maplibre/__init__.py +++ b/maplibre/__init__.py @@ -1,5 +1,6 @@ from .controls import ControlPosition, ControlType -from .ipywidget import MapWidget + +# from .ipywidget import MapWidget from .layer import Layer, LayerType from .map import Map, MapOptions from .mapcontext import MapContext diff --git a/tests/test_mapwidget.py b/tests/test_mapwidget.py index c51b6411..ee6198e0 100644 --- a/tests/test_mapwidget.py +++ b/tests/test_mapwidget.py @@ -1,7 +1,9 @@ import pytest -from maplibre.ipywidget import MapWidget +# anywidget causes "core dumped" error +# from maplibre.ipywidget import MapWidget +""" @pytest.mark.skip("enable me") def test_maplibre_widget(): widget = MapWidget(height=200) @@ -9,3 +11,4 @@ def test_maplibre_widget(): print(widget.height) assert widget.height == "200px" +""" From 745f76e8eebae3c3fe6d72fabb75074c2075bbbf Mon Sep 17 00:00:00 2001 From: Stefan Kuethe Date: Sun, 21 Jan 2024 19:42:23 +0100 Subject: [PATCH 53/58] Ignore ipywidget file --- .github/workflows/pytest.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.github/workflows/pytest.yml b/.github/workflows/pytest.yml index 7782eafb..ae7cc3df 100644 --- a/.github/workflows/pytest.yml +++ b/.github/workflows/pytest.yml @@ -25,7 +25,6 @@ jobs: run: poetry install - name: Test package run: | - sudo apt-get install -y libxcb-xinerama0 poetry run pytest - poetry run pytest --doctest-modules maplibre + poetry run pytest --doctest-modules maplibre --ignore maplibre/ipywidget.py From f2dce5beecc826983d7b5f5d283d9345660e3df9 Mon Sep 17 00:00:00 2001 From: Stefan Kuethe Date: Sun, 21 Jan 2024 19:48:41 +0100 Subject: [PATCH 54/58] Add CHANGELOG to track changes --- CHANGELOG.md | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100644 CHANGELOG.md diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 00000000..30ce42ca --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,12 @@ +# maplibre v0.1.2 + +* Add `Map.set_data` +* Add `Map.set_visibility` +* Do not import `ipywidget.MapWidget` in `__init__` and skip tests for `MapWidget`, because it causes a `core dumped` error +* Remove `requests` dependency +* Remove dead code +* Add more examples + +# maplibre v0.1.1 + +* Initial PyPI release From 5011ca9360dedc814dcfe9d4e7cc9809d0ea845f Mon Sep 17 00:00:00 2001 From: Stefan Kuethe Date: Sun, 21 Jan 2024 19:50:25 +0100 Subject: [PATCH 55/58] Update --- CHANGELOG.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 30ce42ca..bc9e7310 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,6 @@ -# maplibre v0.1.2 +# Changelog for MapLibre for Python + +## maplibre v0.1.2 * Add `Map.set_data` * Add `Map.set_visibility` @@ -7,6 +9,6 @@ * Remove dead code * Add more examples -# maplibre v0.1.1 +## maplibre v0.1.1 * Initial PyPI release From 3b5fb3c0ae10dc566547231508371b9b00816c1c Mon Sep 17 00:00:00 2001 From: Stefan Kuethe Date: Sun, 21 Jan 2024 23:54:47 +0100 Subject: [PATCH 56/58] Move old examples --- {examples => .examples}/airports/marker.py | 11 ++--------- {examples => .examples}/circle_layer/app.py | 0 {examples => .examples}/earthquakes_cluster/app.py | 0 .../every_person_in_manhattan/app.py | 11 +++++++++-- .../every_person_in_manhattan/app2.py | 0 {examples => .examples}/experimental/app.py | 0 {examples => .examples}/experimental/app2.py | 0 {examples => .examples}/experimental/app3.py | 0 {examples => .examples}/experimental/flights.py | 0 .../experimental/points_to_h3_hexagons.py | 0 {examples => .examples}/experimental/readme.txt | 0 {examples => .examples}/fill_extrusion_layer/app.py | 0 {examples => .examples}/fill_layer/app.py | 0 {examples => .examples}/h3_hexagons/app.py | 11 ++--------- {examples => .examples}/heatmap_layer/app.py | 0 {examples => .examples}/marker/app.py | 0 .../motor_vehicle_collisions/app.py | 0 {examples => .examples}/text_layer/app.py | 0 {examples => .examples}/to_html/app.py | 0 {examples => .examples}/vancouver_blocks/line.py | 0 20 files changed, 13 insertions(+), 20 deletions(-) rename {examples => .examples}/airports/marker.py (94%) rename {examples => .examples}/circle_layer/app.py (100%) rename {examples => .examples}/earthquakes_cluster/app.py (100%) rename {examples => .examples}/every_person_in_manhattan/app.py (93%) rename {examples => .examples}/every_person_in_manhattan/app2.py (100%) rename {examples => .examples}/experimental/app.py (100%) rename {examples => .examples}/experimental/app2.py (100%) rename {examples => .examples}/experimental/app3.py (100%) rename {examples => .examples}/experimental/flights.py (100%) rename {examples => .examples}/experimental/points_to_h3_hexagons.py (100%) rename {examples => .examples}/experimental/readme.txt (100%) rename {examples => .examples}/fill_extrusion_layer/app.py (100%) rename {examples => .examples}/fill_layer/app.py (100%) rename {examples => .examples}/h3_hexagons/app.py (96%) rename {examples => .examples}/heatmap_layer/app.py (100%) rename {examples => .examples}/marker/app.py (100%) rename {examples => .examples}/motor_vehicle_collisions/app.py (100%) rename {examples => .examples}/text_layer/app.py (100%) rename {examples => .examples}/to_html/app.py (100%) rename {examples => .examples}/vancouver_blocks/line.py (100%) diff --git a/examples/airports/marker.py b/.examples/airports/marker.py similarity index 94% rename from examples/airports/marker.py rename to .examples/airports/marker.py index f3b9f321..9c7565d0 100644 --- a/examples/airports/marker.py +++ b/.examples/airports/marker.py @@ -1,15 +1,8 @@ import json import pandas as pd -from maplibre import ( - Layer, - LayerType, - Map, - MapContext, - MapOptions, - output_maplibregl, - render_maplibregl, -) +from maplibre import (Layer, LayerType, Map, MapContext, MapOptions, + output_maplibregl, render_maplibregl) from maplibre.basemaps import Carto from maplibre.controls import Marker, MarkerOptions, Popup, PopupOptions from maplibre.sources import GeoJSONSource diff --git a/examples/circle_layer/app.py b/.examples/circle_layer/app.py similarity index 100% rename from examples/circle_layer/app.py rename to .examples/circle_layer/app.py diff --git a/examples/earthquakes_cluster/app.py b/.examples/earthquakes_cluster/app.py similarity index 100% rename from examples/earthquakes_cluster/app.py rename to .examples/earthquakes_cluster/app.py diff --git a/examples/every_person_in_manhattan/app.py b/.examples/every_person_in_manhattan/app.py similarity index 93% rename from examples/every_person_in_manhattan/app.py rename to .examples/every_person_in_manhattan/app.py index 36b96e66..b5523dac 100644 --- a/examples/every_person_in_manhattan/app.py +++ b/.examples/every_person_in_manhattan/app.py @@ -2,8 +2,15 @@ import pandas as pd import shapely -from maplibre import (Layer, LayerType, Map, MapContext, MapOptions, - output_maplibregl, render_maplibregl) +from maplibre import ( + Layer, + LayerType, + Map, + MapContext, + MapOptions, + output_maplibregl, + render_maplibregl, +) from maplibre.basemaps import Carto from maplibre.utils import df_to_geojson from shiny import App, reactive, ui diff --git a/examples/every_person_in_manhattan/app2.py b/.examples/every_person_in_manhattan/app2.py similarity index 100% rename from examples/every_person_in_manhattan/app2.py rename to .examples/every_person_in_manhattan/app2.py diff --git a/examples/experimental/app.py b/.examples/experimental/app.py similarity index 100% rename from examples/experimental/app.py rename to .examples/experimental/app.py diff --git a/examples/experimental/app2.py b/.examples/experimental/app2.py similarity index 100% rename from examples/experimental/app2.py rename to .examples/experimental/app2.py diff --git a/examples/experimental/app3.py b/.examples/experimental/app3.py similarity index 100% rename from examples/experimental/app3.py rename to .examples/experimental/app3.py diff --git a/examples/experimental/flights.py b/.examples/experimental/flights.py similarity index 100% rename from examples/experimental/flights.py rename to .examples/experimental/flights.py diff --git a/examples/experimental/points_to_h3_hexagons.py b/.examples/experimental/points_to_h3_hexagons.py similarity index 100% rename from examples/experimental/points_to_h3_hexagons.py rename to .examples/experimental/points_to_h3_hexagons.py diff --git a/examples/experimental/readme.txt b/.examples/experimental/readme.txt similarity index 100% rename from examples/experimental/readme.txt rename to .examples/experimental/readme.txt diff --git a/examples/fill_extrusion_layer/app.py b/.examples/fill_extrusion_layer/app.py similarity index 100% rename from examples/fill_extrusion_layer/app.py rename to .examples/fill_extrusion_layer/app.py diff --git a/examples/fill_layer/app.py b/.examples/fill_layer/app.py similarity index 100% rename from examples/fill_layer/app.py rename to .examples/fill_layer/app.py diff --git a/examples/h3_hexagons/app.py b/.examples/h3_hexagons/app.py similarity index 96% rename from examples/h3_hexagons/app.py rename to .examples/h3_hexagons/app.py index 8031eca5..1ef8a02c 100644 --- a/examples/h3_hexagons/app.py +++ b/.examples/h3_hexagons/app.py @@ -1,15 +1,8 @@ import h3 import pandas as pd - # import shapely -from maplibre import ( - Layer, - LayerType, - Map, - MapContext, - output_maplibregl, - render_maplibregl, -) +from maplibre import (Layer, LayerType, Map, MapContext, output_maplibregl, + render_maplibregl) from maplibre.basemaps import Carto from maplibre.utils import GeometryType, df_to_geojson, get_bounds from shiny import App, reactive, ui diff --git a/examples/heatmap_layer/app.py b/.examples/heatmap_layer/app.py similarity index 100% rename from examples/heatmap_layer/app.py rename to .examples/heatmap_layer/app.py diff --git a/examples/marker/app.py b/.examples/marker/app.py similarity index 100% rename from examples/marker/app.py rename to .examples/marker/app.py diff --git a/examples/motor_vehicle_collisions/app.py b/.examples/motor_vehicle_collisions/app.py similarity index 100% rename from examples/motor_vehicle_collisions/app.py rename to .examples/motor_vehicle_collisions/app.py diff --git a/examples/text_layer/app.py b/.examples/text_layer/app.py similarity index 100% rename from examples/text_layer/app.py rename to .examples/text_layer/app.py diff --git a/examples/to_html/app.py b/.examples/to_html/app.py similarity index 100% rename from examples/to_html/app.py rename to .examples/to_html/app.py diff --git a/examples/vancouver_blocks/line.py b/.examples/vancouver_blocks/line.py similarity index 100% rename from examples/vancouver_blocks/line.py rename to .examples/vancouver_blocks/line.py From 077061e5f04c7b83513728126af550c70c3bb279 Mon Sep 17 00:00:00 2001 From: Stefan Kuethe Date: Mon, 22 Jan 2024 00:04:03 +0100 Subject: [PATCH 57/58] Add badges --- README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/README.md b/README.md index 557145e7..a1388f7c 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,9 @@ # MapLibre for Python +[![Release](https://img.shields.io/github/v/release/eodaGmbH/py-maplibregl)](https://img.shields.io/github/v/release/eodaGmbH/py-maplibregl) +[![Build status](https://img.shields.io/github/actions/workflow/status/eodaGmbH/py-maplibregl/pytest.yaml?branch=main)](https://img.shields.io/github/actions/workflow/status/eodaGmbH/py-maplibregl/pytest.yaml?branch=main) +[![License](https://img.shields.io/github/license/eodaGmbH/py-maplibregl)](https://img.shields.io/github/license/eodaGmbH/py-maplibregl) + MapLibre for Python provides Python bindings for [MapLibre GL JS](https://github.com/maplibre/maplibre-gl-js). It integrates seamlessly into [Shiny for Python](https://github.com/posit-dev/py-shiny) and [Jupyter](https://jupyter.org/). From 43c9d5f3f6eceb8166d1d6a52629ff3efad6f762 Mon Sep 17 00:00:00 2001 From: Stefan Kuethe Date: Mon, 22 Jan 2024 08:16:30 +0100 Subject: [PATCH 58/58] Add issue link --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index bc9e7310..03ed3ca0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,7 +4,7 @@ * Add `Map.set_data` * Add `Map.set_visibility` -* Do not import `ipywidget.MapWidget` in `__init__` and skip tests for `MapWidget`, because it causes a `core dumped` error +* Do not import `ipywidget.MapWidget` in `__init__` and skip tests for `MapWidget`, because it causes a `core dumped` error, see [anywidget issue](https://github.com/manzt/anywidget/issues/374) * Remove `requests` dependency * Remove dead code * Add more examples