diff --git a/.examples/earthquakes_cluster/app.py b/.examples/earthquakes_cluster/app.py
index cfead397..ac7ac004 100644
--- a/.examples/earthquakes_cluster/app.py
+++ b/.examples/earthquakes_cluster/app.py
@@ -1,8 +1,7 @@
-from shiny import App, reactive, ui
-
from maplibre import Layer, Map, MapOptions, output_maplibregl, render_maplibregl
from maplibre.basemaps import Carto
from maplibre.sources import GeoJSONSource
+from shiny import App, reactive, ui
SOURCE_ID = "earthquakes"
LAYER_ID = "earthquakes"
diff --git a/.examples/experimental/app2.py b/.examples/experimental/app2.py
index 7bd84718..ae00f5ea 100644
--- a/.examples/experimental/app2.py
+++ b/.examples/experimental/app2.py
@@ -1,16 +1,9 @@
import requests as req
-from shiny import App, reactive, render, ui
-
-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.experimental import LineLayer
+from shiny import App, reactive, render, ui
LAYER_ID = "counties"
LAYER_ID_LINE = "us-states-line"
@@ -103,3 +96,4 @@ async def color():
if __name__ == "__main__":
app.run()
+un()
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/.examples/h3_hexagons/app.py b/.examples/h3_hexagons/app.py
index d621dd23..1ef8a02c 100644
--- a/.examples/h3_hexagons/app.py
+++ b/.examples/h3_hexagons/app.py
@@ -1,18 +1,11 @@
import h3
import pandas as pd
-from shiny import App, reactive, ui
-
# 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
LAYER_ID = "motor_vehicle_collisions"
diff --git a/.examples/motor_vehicle_collisions/app.py b/.examples/motor_vehicle_collisions/app.py
index 50df99ea..a197d634 100644
--- a/.examples/motor_vehicle_collisions/app.py
+++ b/.examples/motor_vehicle_collisions/app.py
@@ -1,6 +1,5 @@
import h3
import pandas as pd
-from shiny import App, reactive, ui
# import shapely
from maplibre import (
@@ -13,6 +12,7 @@
)
from maplibre.basemaps import Carto
from maplibre.utils import GeometryType, df_to_geojson, get_bounds
+from shiny import App, reactive, ui
LAYER_ID = "motor_vehicle_collisions"
diff --git a/.examples/text_layer/app.py b/.examples/text_layer/app.py
index cf4fab75..044bd919 100644
--- a/.examples/text_layer/app.py
+++ b/.examples/text_layer/app.py
@@ -1,5 +1,4 @@
-from maplibre import (Layer, LayerType, Map, output_maplibregl,
- render_maplibregl)
+from maplibre import Layer, LayerType, Map, output_maplibregl, render_maplibregl
from maplibre.basemaps import Carto
from shiny import App, reactive, render, ui
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 03ed3ca0..ca4e0022 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,10 @@
# Changelog for MapLibre for Python
+## maplibre v0.1.3
+
+* Display all properties in popup and tooltip if `prop = None` (#26)
+* Support [mustache](https://github.com/janl/mustache.js) templates for popups and tooltips (#27)
+
## maplibre v0.1.2
* Add `Map.set_data`
diff --git a/README.md b/README.md
index 63ab8ab1..907a0ced 100644
--- a/README.md
+++ b/README.md
@@ -41,7 +41,7 @@ poetry install
poetry run pytest
-poetry run pytest --doctest-modules maplibre
+poetry run pytest --ignore=maplibre/ipywidget.py --doctest-modules maplibre
```
### JavaScript
diff --git a/build-docs.sh b/build-docs.sh
new file mode 100755
index 00000000..ae81fe1a
--- /dev/null
+++ b/build-docs.sh
@@ -0,0 +1,6 @@
+#!/bin/sh
+set -e
+
+cp CHANGELOG.md docs/changelog.md
+
+poetry run mkdocs build
diff --git a/docs/changelog.md b/docs/changelog.md
new file mode 100644
index 00000000..ca4e0022
--- /dev/null
+++ b/docs/changelog.md
@@ -0,0 +1,19 @@
+# Changelog for MapLibre for Python
+
+## maplibre v0.1.3
+
+* Display all properties in popup and tooltip if `prop = None` (#26)
+* Support [mustache](https://github.com/janl/mustache.js) templates for popups and tooltips (#27)
+
+## 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, see [anywidget issue](https://github.com/manzt/anywidget/issues/374)
+* Remove `requests` dependency
+* Remove dead code
+* Add more examples
+
+## maplibre v0.1.1
+
+* Initial PyPI release
diff --git a/docs/examples/3d_indoor_mapping/index.md b/docs/examples/3d_indoor_mapping/index.md
index 9f231f34..ce42e75f 100644
--- a/docs/examples/3d_indoor_mapping/index.md
+++ b/docs/examples/3d_indoor_mapping/index.md
@@ -1,4 +1,6 @@
-See example in action
+
+
+
```python
-8<-- "3d_indoor_mapping/app.py"
diff --git a/docs/examples/airports/app.html b/docs/examples/airports/app.html
new file mode 100644
index 00000000..2c498ef9
--- /dev/null
+++ b/docs/examples/airports/app.html
@@ -0,0 +1,167 @@
+
+
+
+
Pymaplibregl
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/docs/examples/airports/app.py b/docs/examples/airports/app.py
index 39e2d94d..d754a3ba 100644
--- a/docs/examples/airports/app.py
+++ b/docs/examples/airports/app.py
@@ -1,3 +1,5 @@
+import sys
+
import pandas as pd
from maplibre import (
Layer,
@@ -64,6 +66,23 @@ def get_color(airport_type: str) -> str:
popup_options = PopupOptions(close_button=False)
+
+def create_map() -> Map:
+ m = Map(map_options)
+ m.add_layer(airport_circles)
+ for _, r in airports_data.iterrows():
+ marker = Marker(
+ lng_lat=r["coordinates"],
+ options=MarkerOptions(color=get_color(r["type"])),
+ popup=Popup(
+ text=r["name"],
+ options=popup_options,
+ ),
+ )
+ m.add_marker(marker)
+ return m
+
+
app_ui = ui.page_fluid(
ui.panel_title("Airports"),
output_maplibregl("maplibre", height=600),
@@ -73,22 +92,14 @@ def get_color(airport_type: str) -> str:
def server(input, output, session):
@render_maplibregl
def maplibre():
- m = Map(map_options)
- for _, r in airports_data.iterrows():
- marker = Marker(
- lng_lat=r["coordinates"],
- options=MarkerOptions(color=get_color(r["type"])),
- popup=Popup(
- text=r["name"],
- options=popup_options,
- ),
- )
- m.add_marker(marker)
- m.add_layer(airport_circles)
- return m
+ return create_map()
app = App(app_ui, server)
if __name__ == "__main__":
- app.run()
+ if len(sys.argv) == 2:
+ with open(sys.argv[1], "w") as f:
+ f.write(create_map().to_html())
+ else:
+ app.run()
diff --git a/docs/examples/airports/index.md b/docs/examples/airports/index.md
index 031c511d..cd813974 100644
--- a/docs/examples/airports/index.md
+++ b/docs/examples/airports/index.md
@@ -1,3 +1,5 @@
+
+
```python
-8<-- "airports/app.py"
```
diff --git a/docs/examples/custom_basemap/index.md b/docs/examples/custom_basemap/index.md
index 608ef7df..f23ec65c 100644
--- a/docs/examples/custom_basemap/index.md
+++ b/docs/examples/custom_basemap/index.md
@@ -1,9 +1,11 @@
+
+
+
+
```python
-8<-- "custom_basemap/app.py"
```
-See example in action
-
Run example:
``` bash
diff --git a/docs/examples/earthquake_clusters/app.html b/docs/examples/earthquake_clusters/app.html
new file mode 100644
index 00000000..d550851d
--- /dev/null
+++ b/docs/examples/earthquake_clusters/app.html
@@ -0,0 +1,167 @@
+
+
+
+Pymaplibregl
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/docs/examples/earthquake_clusters/app.py b/docs/examples/earthquake_clusters/app.py
index bba6fe29..1342b8ee 100644
--- a/docs/examples/earthquake_clusters/app.py
+++ b/docs/examples/earthquake_clusters/app.py
@@ -1,3 +1,5 @@
+import sys
+
from maplibre import (
Layer,
LayerType,
@@ -69,6 +71,17 @@
map_options = MapOptions(style=Carto.POSITRON, center=CENTER, zoom=3, hash=True)
+
+def create_map() -> Map:
+ m = Map(map_options)
+ m.add_source(EARTHQUAKE_SOURCE, earthquakes_source)
+ m.add_layer(earthquake_clusters)
+ m.add_layer(earthquake_circles)
+ m.add_tooltip(EARTHQUAKE_CLUSTERS, "maxMag")
+ m.add_layer(earthquake_labels)
+ return m
+
+
app_ui = ui.page_fluid(
ui.panel_title("Earthquakes Cluster"),
output_maplibregl("maplibre", height=500),
@@ -78,13 +91,7 @@
def server(input, output, session):
@render_maplibregl
def maplibre():
- m = Map(map_options)
- m.add_source(EARTHQUAKE_SOURCE, earthquakes_source)
- m.add_layer(earthquake_clusters)
- m.add_layer(earthquake_circles)
- m.add_tooltip(EARTHQUAKE_CLUSTERS, "maxMag")
- m.add_layer(earthquake_labels)
- return m
+ return create_map()
@reactive.Effect
@reactive.event(input.maplibre)
@@ -95,4 +102,9 @@ async def result():
app = App(app_ui, server)
if __name__ == "__main__":
- app.run()
+ if len(sys.argv) == 2:
+ file_name = sys.argv[1]
+ with open(file_name, "w") as f:
+ f.write(create_map().to_html())
+ else:
+ app.run()
diff --git a/docs/examples/earthquake_clusters/index.md b/docs/examples/earthquake_clusters/index.md
index 2c56003d..77f57d29 100644
--- a/docs/examples/earthquake_clusters/index.md
+++ b/docs/examples/earthquake_clusters/index.md
@@ -1,3 +1,5 @@
+
+
```python
-8<-- "earthquake_clusters/app.py"
```
diff --git a/docs/examples/every_person_in_manhattan/app.html b/docs/examples/every_person_in_manhattan/app.html
new file mode 100644
index 00000000..734fdd37
--- /dev/null
+++ b/docs/examples/every_person_in_manhattan/app.html
@@ -0,0 +1,167 @@
+
+
+
+Pymaplibregl
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/docs/examples/every_person_in_manhattan/app.py b/docs/examples/every_person_in_manhattan/app.py
index 164ef77f..89ea6278 100644
--- a/docs/examples/every_person_in_manhattan/app.py
+++ b/docs/examples/every_person_in_manhattan/app.py
@@ -1,4 +1,5 @@
import json
+import sys
import pandas as pd
import shapely
@@ -52,6 +53,14 @@
fit_bounds_options={"padding": 20},
)
+
+def create_map() -> Map:
+ m = Map(map_options)
+ m.add_control(ScaleControl(), position="bottom-left")
+ m.add_layer(every_person_in_manhattan_circles)
+ return m
+
+
app_ui = ui.page_fluid(
ui.panel_title("Every Person in Manhattan"),
output_maplibregl("maplibre", height=600),
@@ -62,10 +71,7 @@
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
+ return create_map()
@reactive.Effect
@reactive.event(input.radius, ignore_init=True)
@@ -77,4 +83,9 @@ async def radius():
app = App(app_ui, server)
if __name__ == "__main__":
- app.run()
+ if len(sys.argv) == 2:
+ file_name = sys.argv[1]
+ with open(file_name, "w") as f:
+ f.write(create_map().to_html())
+ else:
+ app.run()
diff --git a/docs/examples/every_person_in_manhattan/index.md b/docs/examples/every_person_in_manhattan/index.md
index b3e6fcd6..411b88a9 100644
--- a/docs/examples/every_person_in_manhattan/index.md
+++ b/docs/examples/every_person_in_manhattan/index.md
@@ -1,3 +1,5 @@
+
+
```python
-8<-- "every_person_in_manhattan/app.py"
```
diff --git a/docs/examples/geopandas/index.md b/docs/examples/geopandas/index.md
index fffccb96..516f4995 100644
--- a/docs/examples/geopandas/index.md
+++ b/docs/examples/geopandas/index.md
@@ -1,4 +1,6 @@
-See example in action
+
+
+
```python
-8<-- "geopandas/app.py"
diff --git a/docs/examples/jupyter/getting_started.ipynb b/docs/examples/jupyter/getting_started.ipynb
index 388953c0..1d293c23 100644
--- a/docs/examples/jupyter/getting_started.ipynb
+++ b/docs/examples/jupyter/getting_started.ipynb
@@ -70,14 +70,14 @@
},
{
"cell_type": "code",
- "execution_count": 4,
+ "execution_count": 27,
"id": "e6e2284c-1862-4697-ad94-f535b3682197",
"metadata": {},
"outputs": [
{
"data": {
"application/vnd.jupyter.widget-view+json": {
- "model_id": "ece4babe53924aa7886553d1b5d53bec",
+ "model_id": "8424f04853ea4a41ac6a3697349bbd3e",
"version_major": 2,
"version_minor": 0
},
@@ -85,7 +85,7 @@
"MapWidget(calls=[['addControl', ('ScaleControl', {'unit': 'metric'}, 'bottom-left')], ['addLayer', ({'id': 'ea…"
]
},
- "execution_count": 4,
+ "execution_count": 27,
"metadata": {},
"output_type": "execute_result"
}
@@ -94,7 +94,7 @@
"m = Map()\n",
"m.add_control(ScaleControl(), position=\"bottom-left\")\n",
"m.add_layer(earthquake_circles)\n",
- "m.add_tooltip(layer_id, \"mag\")\n",
+ "m.add_tooltip(layer_id)\n",
"m.add_marker(Marker(lng_lat=(100.507, 13.745)))\n",
"m"
]
@@ -109,39 +109,29 @@
},
{
"cell_type": "code",
- "execution_count": 6,
+ "execution_count": 28,
"id": "356960fa-b866-42c8-a58e-0c9a417c28eb",
"metadata": {},
"outputs": [
{
"data": {
"application/vnd.jupyter.widget-view+json": {
- "model_id": "60ef57a918734016b9766e41615c1935",
+ "model_id": "7998e7feb14045d6a8dfc637d6775c98",
"version_major": 2,
"version_minor": 0
},
"text/plain": [
- "interactive(children=(IntSlider(value=5, description='radius', max=15, min=-5), Output()), _dom_classes=('widg…"
+ "interactive(children=(IntSlider(value=4, description='radius', max=8, min=1), Output()), _dom_classes=('widget…"
]
},
"metadata": {},
"output_type": "display_data"
- },
- {
- "data": {
- "text/plain": [
- "(radius)>"
- ]
- },
- "execution_count": 6,
- "metadata": {},
- "output_type": "execute_result"
}
],
"source": [
- "widgets.interact(\n",
+ "change_radius = widgets.interact(\n",
" lambda radius: m.set_paint_property(layer_id, \"circle-radius\", radius),\n",
- " radius=5\n",
+ " radius=(1, 8, 1)\n",
")"
]
},
@@ -155,14 +145,14 @@
},
{
"cell_type": "code",
- "execution_count": 6,
+ "execution_count": 21,
"id": "8ecd93a6-f471-4350-a052-7a9171fa1606",
"metadata": {},
"outputs": [
{
"data": {
"application/vnd.jupyter.widget-view+json": {
- "model_id": "9b52cdb12c3b4af4ac8bbbfc885aea30",
+ "model_id": "ca7ad45e679d479795b3c05014c8e6b4",
"version_major": 2,
"version_minor": 0
},
@@ -172,20 +162,10 @@
},
"metadata": {},
"output_type": "display_data"
- },
- {
- "data": {
- "text/plain": [
- "(color)>"
- ]
- },
- "execution_count": 6,
- "metadata": {},
- "output_type": "execute_result"
}
],
"source": [
- "widgets.interact(\n",
+ "change_color = widgets.interact(\n",
" lambda color: m.set_paint_property(layer_id, \"circle-color\", color),\n",
" color=[\"green\", \"yellow\", \"orange\", \"red\"]\n",
")"
@@ -201,39 +181,29 @@
},
{
"cell_type": "code",
- "execution_count": 7,
+ "execution_count": 31,
"id": "0b73f056-f35a-46bb-a092-d899c64cd67e",
"metadata": {},
"outputs": [
{
"data": {
"application/vnd.jupyter.widget-view+json": {
- "model_id": "5a4891d6fb0e418bbfe22c4661363022",
+ "model_id": "405b4aba579b42248573eaf356f629d4",
"version_major": 2,
"version_minor": 0
},
"text/plain": [
- "interactive(children=(IntSlider(value=3, description='mag_min', max=9, min=-3), Output()), _dom_classes=('widg…"
+ "interactive(children=(IntSlider(value=4, description='mag_min', max=7, min=1), Output()), _dom_classes=('widge…"
]
},
"metadata": {},
"output_type": "display_data"
- },
- {
- "data": {
- "text/plain": [
- "(mag_min)>"
- ]
- },
- "execution_count": 7,
- "metadata": {},
- "output_type": "execute_result"
}
],
"source": [
- "widgets.interact(\n",
+ "filter_mag = widgets.interact(\n",
" lambda mag_min: m.set_filter(layer_id, [\">=\", [\"get\", \"mag\"], mag_min]),\n",
- " mag_min=3\n",
+ " mag_min=(1,7,1)\n",
")"
]
},
@@ -247,14 +217,14 @@
},
{
"cell_type": "code",
- "execution_count": 8,
+ "execution_count": 32,
"id": "a9c5ddf7-074e-45b0-8cfe-15750fd0b4d5",
"metadata": {},
"outputs": [
{
"data": {
"application/vnd.jupyter.widget-view+json": {
- "model_id": "119b58c9640f4b058fc218ce17203edf",
+ "model_id": "3957fc738b2643cfa207f9b62edbfdda",
"version_major": 2,
"version_minor": 0
},
@@ -262,7 +232,7 @@
"Output()"
]
},
- "execution_count": 8,
+ "execution_count": 32,
"metadata": {},
"output_type": "execute_result"
}
@@ -284,136 +254,10 @@
},
{
"cell_type": "code",
- "execution_count": 5,
+ "execution_count": null,
"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"
- ]
- }
- ],
+ "outputs": [],
"source": []
},
{
diff --git a/docs/examples/road_safety/app.py b/docs/examples/road_safety/app.py
index 2e8b9c8d..a2a73176 100644
--- a/docs/examples/road_safety/app.py
+++ b/docs/examples/road_safety/app.py
@@ -51,21 +51,28 @@ def create_h3_grid(res=RESOLUTION) -> dict:
source = GeoJSONSource(data=create_h3_grid())
+map_options = MapOptions(
+ center=(-1.415727, 52.232395),
+ zoom=7,
+ pitch=40,
+ bearing=-27,
+)
+
+h3_layer = Layer(
+ id="road-safety",
+ type=LayerType.FILL_EXTRUSION,
+ source=source,
+ paint={
+ "fill-extrusion-color": ["get", "color"],
+ "fill-extrusion-opacity": 0.7,
+ "fill-extrusion-height": ["*", 100, ["get", "count"]],
+ },
+)
+
def create_map() -> Map:
- m = Map(MapOptions(center=(-1.415727, 52.232395), zoom=7, pitch=40, bearing=-27))
- m.add_layer(
- Layer(
- id="road-safety",
- type=LayerType.FILL_EXTRUSION,
- source=source,
- paint={
- "fill-extrusion-color": ["get", "color"],
- "fill-extrusion-opacity": 0.7,
- "fill-extrusion-height": ["*", 100, ["get", "count"]],
- },
- )
- )
+ m = Map(map_options)
+ m.add_layer(h3_layer)
m.add_tooltip("road-safety", "count")
return m
diff --git a/docs/examples/road_safety/index.md b/docs/examples/road_safety/index.md
index 4173e20c..5ba821d2 100644
--- a/docs/examples/road_safety/index.md
+++ b/docs/examples/road_safety/index.md
@@ -1,4 +1,6 @@
-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 5af20d82..3e25f50c 100644
--- a/docs/examples/vancouver_blocks/index.md
+++ b/docs/examples/vancouver_blocks/index.md
@@ -1,4 +1,6 @@
-See example in action (without reactive effects)
+
+
+
```python
-8<-- "vancouver_blocks/app.py"
diff --git a/docs/jupyter.md b/docs/jupyter.md
index 0ca35638..600601f1 100644
--- a/docs/jupyter.md
+++ b/docs/jupyter.md
@@ -32,21 +32,21 @@ m.add_marker(Marker(lng_lat=(100.507, 13.745)))
m
# Change radius
-widgets.interact(
+_ = widgets.interact(
lambda radius: m.set_paint_property(layer_id, "circle-radius", radius),
- radius=5
+ radius=(1, 8, 1)
)
# Change color
-widgets.interact(
+_ = widgets.interact(
lambda color: m.set_paint_property(layer_id, "circle-color", color),
color=["green", "yellow", "orange", "red"]
)
# Set filter on magnitude
-widgets.interact(
+_ = widgets.interact(
lambda mag_min: m.set_filter(layer_id, [">=", ["get", "mag"], mag_min]),
- mag_min=3
+ mag_min=(1, 8, 1)
)
# Observe map-on-click event
diff --git a/maplibre/_constants.py b/maplibre/_constants.py
index 2e82e7f8..2b588e1a 100644
--- a/maplibre/_constants.py
+++ b/maplibre/_constants.py
@@ -1,4 +1,4 @@
-__version__ = "0.1.0"
+__version__ = "0.1.3"
_shiny_output_class = "shiny-maplibregl-output"
# tmp
diff --git a/maplibre/map.py b/maplibre/map.py
index ede455fb..8f5938f1 100644
--- a/maplibre/map.py
+++ b/maplibre/map.py
@@ -158,23 +158,32 @@ def add_marker(self, marker: Marker) -> None:
"""
self.add_call("addMarker", marker.to_dict())
- def add_popup(self, layer_id: str, prop: str) -> None:
+ def add_popup(self, layer_id: str, prop: str = None, template: str = None) -> None:
"""Add a popup to the map
Args:
layer_id (str): The layer to which the popup is added.
- prop (str): The property of the source to be displayed.
+ prop (str): The property of the source to be displayed. If `None`, all properties are displayed.
+ template (str): A mustache template. If supplied, `prop` is ignored.
"""
- self.add_call("addPopup", layer_id, prop)
+ self.add_call("addPopup", layer_id, prop, template)
- def add_tooltip(self, layer_id: str, prop: str) -> None:
+ def add_tooltip(
+ self, layer_id: str, prop: str = None, template: str = None
+ ) -> None:
"""Add a tooltip to the map
Args:
layer_id (str): The layer to which the tooltip is added.
- prop (str): The property of the source to be displayed.
+ prop (str): The property of the source to be displayed. If `None`, all properties are displayed.
+ template (str): A mustache template. If supplied, `prop` is ignored.
+
+ Examples:
+ >>> map = Map()
+ >>> # ...
+ >>> map.add_tooltip("test-layer", template="Name: {{ name }}")
"""
- self.add_call("addTooltip", layer_id, prop)
+ self.add_call("addTooltip", layer_id, prop, template)
def set_filter(self, layer_id: str, filter_: list):
"""Update the filter of a layer
diff --git a/maplibre/server.py b/maplibre/server.py
index 165932ae..72a7b24a 100644
--- a/maplibre/server.py
+++ b/maplibre/server.py
@@ -6,17 +6,10 @@
output_transformer,
resolve_value_fn,
)
-from shiny.session import get_current_session
from .map import Map
-async def maplibre_render(id_: str = None, color: str = "black") -> None:
- session = get_current_session()
- opts = {"id": id_, "color": color}
- await session.send_custom_message("maplibre", opts)
-
-
@output_transformer
async def render_maplibregl(
_meta: TransformerMetadata,
diff --git a/maplibre/srcjs/index.js b/maplibre/srcjs/index.js
index c1121f06..ecbff443 100644
--- a/maplibre/srcjs/index.js
+++ b/maplibre/srcjs/index.js
@@ -1,148 +1,12 @@
-(() => {
- // srcjs/pymaplibregl.js
- var PyMapLibreGL = class {
- constructor(mapOptions) {
- this._id = mapOptions.container;
- this._map = new maplibregl.Map(mapOptions);
- this._map.on("mouseover", () => {
- this._map.getCanvas().style.cursor = "pointer";
- });
- this._map.on("mouseout", () => {
- this._map.getCanvas().style.cursor = "";
- });
- this._map.addControl(new maplibregl.NavigationControl());
- }
- getMap() {
- return this._map;
- }
- applyMapMethod(name, params) {
- this._map[name](...params);
- }
- addControl(type, options, position) {
- this._map.addControl(new maplibregl[type](options), position);
- }
- addMarker({ lngLat, popup, options }) {
- const marker = new maplibregl.Marker(options).setLngLat(lngLat);
- if (popup) {
- const popup_ = new maplibregl.Popup(popup.options).setHTML(popup.text);
- marker.setPopup(popup_);
- }
- marker.addTo(this._map);
- }
- addLayer(layer) {
- this._map.addLayer(layer);
- if (typeof Shiny !== "undefined") {
- this._map.on("click", layer.id, (e) => {
- console.log(e, e.features[0]);
- const layerId_ = layer.id.replaceAll("-", "_");
- const inputName = `${this._id}_layer_${layerId_}`;
- const feature = {
- props: e.features[0].properties,
- layer_id: layer.id
- };
- console.log(inputName, feature);
- Shiny.onInputChange(inputName, feature);
- });
- }
- }
- addPopup(layerId, property) {
- const popupOptions = {
- closeButton: false
- };
- const popup = new maplibregl.Popup(popupOptions);
- this._map.on("click", layerId, (e) => {
- const feature = e.features[0];
- const text = feature.properties[property];
- popup.setLngLat(e.lngLat).setHTML(text).addTo(this._map);
- });
- }
- addTooltip(layerId, property) {
- const popupOptions = {
- closeButton: false,
- closeOnClick: false
- };
- const popup = new maplibregl.Popup(popupOptions);
- this._map.on("mousemove", layerId, (e) => {
- const feature = e.features[0];
- const text = feature.properties[property];
- popup.setLngLat(e.lngLat).setHTML(text).addTo(this._map);
- });
- this._map.on("mouseleave", layerId, () => {
- popup.remove();
- });
- }
- setSourceData(sourceId, data) {
- this._map.getSource(sourceId).setData(data);
- }
- render(calls) {
- calls.forEach(([name, params]) => {
- if ([
- "addLayer",
- "addPopup",
- "addTooltip",
- "addMarker",
- "addPopup",
- "addControl",
- "setSourceData"
- ].includes(name)) {
- console.log("Custom method", name, params);
- this[name](...params);
- return;
- }
- console.log("Map method", name);
- this.applyMapMethod(name, params);
- });
- }
- };
+(()=>{var B=Object.prototype.toString,_=Array.isArray||function(e){return B.call(e)==="[object Array]"};function R(r){return typeof r=="function"}function D(r){return _(r)?"array":typeof r}function P(r){return r.replace(/[\-\[\]{}()*+?.,\\\^$|#\s]/g,"\\$&")}function j(r,e){return r!=null&&typeof r=="object"&&e in r}function F(r,e){return r!=null&&typeof r!="object"&&r.hasOwnProperty&&r.hasOwnProperty(e)}var H=RegExp.prototype.test;function N(r,e){return H.call(r,e)}var W=/\S/;function q(r){return!N(W,r)}var G={"&":"&","<":"<",">":">",'"':""","'":"'","/":"/","`":"`","=":"="};function K(r){return String(r).replace(/[&<>"'`=\/]/g,function(t){return G[t]})}var V=/\s*/,z=/\s+/,I=/\s*=/,J=/\s*\}/,Q=/#|\^|\/|>|\{|&|=|!/;function X(r,e){if(!r)return[];var t=!1,n=[],a=[],s=[],o=!1,i=!1,p="",u=0;function h(){if(o&&!i)for(;s.length;)delete a[s.pop()];else s=[];o=!1,i=!1}var v,m,x;function U(y){if(typeof y=="string"&&(y=y.split(z,2)),!_(y)||y.length!==2)throw new Error("Invalid tags: "+y);v=new RegExp(P(y[0])+"\\s*"),m=new RegExp("\\s*"+P(y[1])),x=new RegExp("\\s*"+P("}"+y[1]))}U(e||g.tags);for(var l=new E(r),w,f,d,k,L,b;!l.eos();){if(w=l.pos,d=l.scanUntil(v),d)for(var M=0,A=d.length;M"?L=[f,d,w,l.pos,p,u,t]:L=[f,d,w,l.pos],u++,a.push(L),f==="#"||f==="^")n.push(L);else if(f==="/"){if(b=n.pop(),!b)throw new Error('Unopened section "'+d+'" at '+w);if(b[1]!==d)throw new Error('Unclosed section "'+b[1]+'" at '+w)}else f==="name"||f==="{"||f==="&"?i=!0:f==="="&&U(d)}if(h(),b=n.pop(),b)throw new Error('Unclosed section "'+b[1]+'" at '+l.pos);return Z(Y(a))}function Y(r){for(var e=[],t,n,a=0,s=r.length;a0?n[n.length-1][4]:e;break;default:t.push(a)}return e}function E(r){this.string=r,this.tail=r,this.pos=0}E.prototype.eos=function(){return this.tail===""};E.prototype.scan=function(e){var t=this.tail.match(e);if(!t||t.index!==0)return"";var n=t[0];return this.tail=this.tail.substring(n.length),this.pos+=n.length,n};E.prototype.scanUntil=function(e){var t=this.tail.search(e),n;switch(t){case-1:n=this.tail,this.tail="";break;case 0:n="";break;default:n=this.tail.substring(0,t),this.tail=this.tail.substring(t)}return this.pos+=n.length,n};function C(r,e){this.view=r,this.cache={".":this.view},this.parent=e}C.prototype.push=function(e){return new C(e,this)};C.prototype.lookup=function(e){var t=this.cache,n;if(t.hasOwnProperty(e))n=t[e];else{for(var a=this,s,o,i,p=!1;a;){if(e.indexOf(".")>0)for(s=a.view,o=e.split("."),i=0;s!=null&&i"?u=this.renderPartial(i,t,n,s):p==="&"?u=this.unescapedValue(i,t):p==="name"?u=this.escapedValue(i,t,s):p==="text"&&(u=this.rawValue(i)),u!==void 0&&(o+=u);return o};c.prototype.renderSection=function(e,t,n,a,s){var o=this,i="",p=t.lookup(e[1]);function u(m){return o.render(m,t,n,s)}if(p){if(_(p))for(var h=0,v=p.length;h0||!n)&&(s[o]=a+s[o]);return s.join(`
+`)};c.prototype.renderPartial=function(e,t,n,a){if(n){var s=this.getConfigTags(a),o=R(n)?n(e[1]):n[e[1]];if(o!=null){var i=e[6],p=e[5],u=e[4],h=o;p==0&&u&&(h=this.indentPartial(o,u,i));var v=this.parse(h,s);return this.renderTokens(v,t,n,h,a)}}};c.prototype.unescapedValue=function(e,t){var n=t.lookup(e[1]);if(n!=null)return n};c.prototype.escapedValue=function(e,t,n){var a=this.getConfigEscape(n)||g.escape,s=t.lookup(e[1]);if(s!=null)return typeof s=="number"&&a===g.escape?String(s):a(s)};c.prototype.rawValue=function(e){return e[1]};c.prototype.getConfigTags=function(e){return _(e)?e:e&&typeof e=="object"?e.tags:void 0};c.prototype.getConfigEscape=function(e){if(e&&typeof e=="object"&&!_(e))return e.escape};var g={name:"mustache.js",version:"4.2.0",tags:["{{","}}"],clearCache:void 0,escape:void 0,parse:void 0,render:void 0,Scanner:void 0,Context:void 0,Writer:void 0,set templateCache(r){T.templateCache=r},get templateCache(){return T.templateCache}},T=new c;g.clearCache=function(){return T.clearCache()};g.parse=function(e,t){return T.parse(e,t)};g.render=function(e,t,n,a){if(typeof e!="string")throw new TypeError('Invalid template! Template should be a "string" but "'+D(e)+'" was given as the first argument for mustache#render(template, view, partials)');return T.render(e,t,n,a)};g.escape=K;g.Scanner=E;g.Context=C;g.Writer=c;var $=g;function O(r,e,t){return t!==null?$.render(t,r.properties):e===null?Object.keys(r.properties).map(a=>`${a}: ${r.properties[a]}`).join(""):r.properties[e]}var S=class{constructor(e){this._id=e.container,this._map=new maplibregl.Map(e),this._map.on("mouseover",()=>{this._map.getCanvas().style.cursor="pointer"}),this._map.on("mouseout",()=>{this._map.getCanvas().style.cursor=""}),this._map.addControl(new maplibregl.NavigationControl)}getMap(){return this._map}applyMapMethod(e,t){this._map[e](...t)}addControl(e,t,n){this._map.addControl(new maplibregl[e](t),n)}addMarker({lngLat:e,popup:t,options:n}){let a=new maplibregl.Marker(n).setLngLat(e);if(t){let s=new maplibregl.Popup(t.options).setHTML(t.text);a.setPopup(s)}a.addTo(this._map)}addLayer(e){this._map.addLayer(e),typeof Shiny<"u"&&this._map.on("click",e.id,t=>{console.log(t,t.features[0]);let n=e.id.replaceAll("-","_"),a=`${this._id}_layer_${n}`,s={props:t.features[0].properties,layer_id:e.id};console.log(a,s),Shiny.onInputChange(a,s)})}addPopup(e,t=null,n=null){let a={closeButton:!1},s=new maplibregl.Popup(a);this._map.on("click",e,o=>{let i=o.features[0],p=O(i,t,n);s.setLngLat(o.lngLat).setHTML(p).addTo(this._map)})}addTooltip(e,t=null,n=null){let a={closeButton:!1,closeOnClick:!1},s=new maplibregl.Popup(a);this._map.on("mousemove",e,o=>{let i=o.features[0],p=O(i,t,n);s.setLngLat(o.lngLat).setHTML(p).addTo(this._map)}),this._map.on("mouseleave",e,()=>{s.remove()})}setSourceData(e,t){this._map.getSource(e).setData(t)}render(e){e.forEach(([t,n])=>{if(["addLayer","addPopup","addTooltip","addMarker","addPopup","addControl","setSourceData"].includes(t)){console.log("Custom method",t,n),this[t](...n);return}console.log("Map method",t),this.applyMapMethod(t,n)})}};var ee="0.1.0";console.log("pymaplibregl",ee);typeof Shiny>"u"&&(window.pymaplibregl=function({mapOptions:r,calls:e}){let t="pymaplibregl",n=document.getElementById(t),a=new S(Object.assign({container:n.id},r));a.getMap().on("load",()=>{a.render(e)})});if(typeof Shiny<"u"){class r extends Shiny.OutputBinding{find(t){return t.find(".shiny-maplibregl-output")}renderValue(t,n){console.log("id:",t.id,"payload:",n);let a=new S(Object.assign({container:t.id},n.mapData.mapOptions)),s=a.getMap();s.on("load",()=>{a.render(n.mapData.calls)}),s.on("click",i=>{console.log(i);let p=`${t.id}`,u={coords:i.lngLat,point:i.point};console.log(p,u),Shiny.onInputChange(p,u)});let o=`pymaplibregl-${t.id}`;console.log(o),Shiny.addCustomMessageHandler(o,({id:i,calls:p})=>{console.log(i,p),a.render(p)})}}Shiny.outputBindings.register(new r,"shiny-maplibregl-output")}})();
+/*! Bundled license information:
- // srcjs/index.js
- var version = "0.1.0";
- console.log("pymaplibregl", version);
- if (typeof Shiny === "undefined") {
- window.pymaplibregl = function({ mapOptions, calls }) {
- const id = "pymaplibregl";
- const container = document.getElementById(id);
- const pyMapLibreGL = new PyMapLibreGL(
- Object.assign({ container: container.id }, mapOptions)
- );
- const map = pyMapLibreGL.getMap();
- map.on("load", () => {
- pyMapLibreGL.render(calls);
- });
- };
- }
- if (typeof Shiny !== "undefined") {
- class MapLibreGLOutputBinding extends Shiny.OutputBinding {
- find(scope) {
- return scope.find(".shiny-maplibregl-output");
- }
- renderValue(el, payload) {
- console.log("id:", el.id, "payload:", payload);
- const pyMapLibreGL = new PyMapLibreGL(
- Object.assign({ container: el.id }, payload.mapData.mapOptions)
- );
- const map = pyMapLibreGL.getMap();
- map.on("load", () => {
- pyMapLibreGL.render(payload.mapData.calls);
- });
- map.on("click", (e) => {
- console.log(e);
- const inputName = `${el.id}`;
- const data = { coords: e.lngLat, point: e.point };
- console.log(inputName, data);
- Shiny.onInputChange(inputName, data);
- });
- const messageHandlerName = `pymaplibregl-${el.id}`;
- console.log(messageHandlerName);
- Shiny.addCustomMessageHandler(messageHandlerName, ({ id, calls }) => {
- console.log(id, calls);
- pyMapLibreGL.render(calls);
- });
- }
- }
- Shiny.outputBindings.register(
- new MapLibreGLOutputBinding(),
- "shiny-maplibregl-output"
- );
- }
-})();
+mustache/mustache.mjs:
+ (*!
+ * mustache.js - Logic-less {{mustache}} templates with JavaScript
+ * http://github.com/janl/mustache.js
+ *)
+*/
diff --git a/maplibre/srcjs/ipywidget.js b/maplibre/srcjs/ipywidget.js
index 3a488ce7..f7dbaabf 100644
--- a/maplibre/srcjs/ipywidget.js
+++ b/maplibre/srcjs/ipywidget.js
@@ -1,122 +1,12 @@
-// srcjs/ipywidget.js
-import maplibregl from "https://esm.sh/maplibre-gl@3.6.2";
+import R from"https://esm.sh/maplibre-gl@3.6.2";var H=Object.prototype.toString,T=Array.isArray||function(e){return H.call(e)==="[object Array]"};function U(n){return typeof n=="function"}function N(n){return T(n)?"array":typeof n}function L(n){return n.replace(/[\-\[\]{}()*+?.,\\\^$|#\s]/g,"\\$&")}function _(n,e){return n!=null&&typeof n=="object"&&e in n}function $(n,e){return n!=null&&typeof n!="object"&&n.hasOwnProperty&&n.hasOwnProperty(e)}var B=RegExp.prototype.test;function D(n,e){return B.call(n,e)}var z=/\S/;function K(n){return!D(z,n)}var G={"&":"&","<":"<",">":">",'"':""","'":"'","/":"/","`":"`","=":"="};function J(n){return String(n).replace(/[&<>"'`=\/]/g,function(t){return G[t]})}var Q=/\s*/,V=/\s+/,A=/\s*=/,X=/\s*\}/,Y=/#|\^|\/|>|\{|&|=|!/;function Z(n,e){if(!n)return[];var t=!1,r=[],s=[],a=[],o=!1,i=!1,c="",u=0;function p(){if(o&&!i)for(;a.length;)delete s[a.pop()];else a=[];o=!1,i=!1}var g,C,m;function O(y){if(typeof y=="string"&&(y=y.split(V,2)),!T(y)||y.length!==2)throw new Error("Invalid tags: "+y);g=new RegExp(L(y[0])+"\\s*"),C=new RegExp("\\s*"+L(y[1])),m=new RegExp("\\s*"+L("}"+y[1]))}O(e||v.tags);for(var l=new S(n),w,h,d,E,P,b;!l.eos();){if(w=l.pos,d=l.scanUntil(g),d)for(var x=0,q=d.length;x"?P=[h,d,w,l.pos,c,u,t]:P=[h,d,w,l.pos],u++,s.push(P),h==="#"||h==="^")r.push(P);else if(h==="/"){if(b=r.pop(),!b)throw new Error('Unopened section "'+d+'" at '+w);if(b[1]!==d)throw new Error('Unclosed section "'+b[1]+'" at '+w)}else h==="name"||h==="{"||h==="&"?i=!0:h==="="&&O(d)}if(p(),b=r.pop(),b)throw new Error('Unclosed section "'+b[1]+'" at '+l.pos);return te(ee(s))}function ee(n){for(var e=[],t,r,s=0,a=n.length;s0?r[r.length-1][4]:e;break;default:t.push(s)}return e}function S(n){this.string=n,this.tail=n,this.pos=0}S.prototype.eos=function(){return this.tail===""};S.prototype.scan=function(e){var t=this.tail.match(e);if(!t||t.index!==0)return"";var r=t[0];return this.tail=this.tail.substring(r.length),this.pos+=r.length,r};S.prototype.scanUntil=function(e){var t=this.tail.search(e),r;switch(t){case-1:r=this.tail,this.tail="";break;case 0:r="";break;default:r=this.tail.substring(0,t),this.tail=this.tail.substring(t)}return this.pos+=r.length,r};function k(n,e){this.view=n,this.cache={".":this.view},this.parent=e}k.prototype.push=function(e){return new k(e,this)};k.prototype.lookup=function(e){var t=this.cache,r;if(t.hasOwnProperty(e))r=t[e];else{for(var s=this,a,o,i,c=!1;s;){if(e.indexOf(".")>0)for(a=s.view,o=e.split("."),i=0;a!=null&&i"?u=this.renderPartial(i,t,r,a):c==="&"?u=this.unescapedValue(i,t):c==="name"?u=this.escapedValue(i,t,a):c==="text"&&(u=this.rawValue(i)),u!==void 0&&(o+=u);return o};f.prototype.renderSection=function(e,t,r,s,a){var o=this,i="",c=t.lookup(e[1]);function u(C){return o.render(C,t,r,a)}if(c){if(T(c))for(var p=0,g=c.length;p0||!r)&&(a[o]=s+a[o]);return a.join(`
+`)};f.prototype.renderPartial=function(e,t,r,s){if(r){var a=this.getConfigTags(s),o=U(r)?r(e[1]):r[e[1]];if(o!=null){var i=e[6],c=e[5],u=e[4],p=o;c==0&&u&&(p=this.indentPartial(o,u,i));var g=this.parse(p,a);return this.renderTokens(g,t,r,p,s)}}};f.prototype.unescapedValue=function(e,t){var r=t.lookup(e[1]);if(r!=null)return r};f.prototype.escapedValue=function(e,t,r){var s=this.getConfigEscape(r)||v.escape,a=t.lookup(e[1]);if(a!=null)return typeof a=="number"&&s===v.escape?String(a):s(a)};f.prototype.rawValue=function(e){return e[1]};f.prototype.getConfigTags=function(e){return T(e)?e:e&&typeof e=="object"?e.tags:void 0};f.prototype.getConfigEscape=function(e){if(e&&typeof e=="object"&&!T(e))return e.escape};var v={name:"mustache.js",version:"4.2.0",tags:["{{","}}"],clearCache:void 0,escape:void 0,parse:void 0,render:void 0,Scanner:void 0,Context:void 0,Writer:void 0,set templateCache(n){M.templateCache=n},get templateCache(){return M.templateCache}},M=new f;v.clearCache=function(){return M.clearCache()};v.parse=function(e,t){return M.parse(e,t)};v.render=function(e,t,r,s){if(typeof e!="string")throw new TypeError('Invalid template! Template should be a "string" but "'+N(e)+'" was given as the first argument for mustache#render(template, view, partials)');return M.render(e,t,r,s)};v.escape=J;v.Scanner=S;v.Context=k;v.Writer=f;var F=v;function j(n,e,t){return t!==null?F.render(t,n.properties):e===null?Object.keys(n.properties).map(s=>`${s}: ${n.properties[s]}`).join(""):n.properties[e]}function I(n,e){let[t,r]=e;console.log(t,r),n[t](...r)}function W(n,e){return{addTooltip:function(t,r=null,s=null){let a={closeButton:!1,closeOnClick:!1},o=new n.Popup(a);e.on("mousemove",t,i=>{let c=i.features[0],u=j(c,r,s);o.setLngLat(i.lngLat).setHTML(u).addTo(e)}),e.on("mouseleave",t,()=>{o.remove()})},addControl:function(t,r,s){e.addControl(new n[t](r),s)},addPopup:function(t,r=null,s=null){let a={closeButton:!1},o=new n.Popup(a);e.on("click",t,i=>{let c=i.features[0],u=j(c,r,s);o.setLngLat(i.lngLat).setHTML(u).addTo(e)})},addMarker:function({lngLat:t,popup:r,options:s}){let a=new n.Marker(s).setLngLat(t);if(r){let o=new n.Popup(r.options).setHTML(r.text);a.setPopup(o)}a.addTo(e)},setSourceData:function(t,r){e.getSource(t).setData(r)}}}function ne(n){let e="pymaplibregl",t=document.createElement("div");return t.id=e,t.style.height=n.get("height"),t}function re(n,e){let t=new R.Map(n);return n.navigationControl===void 0&&(n.navigationControl=!0),n.navigationControl&&t.addControl(new R.NavigationControl),t.on("mouseover",()=>{t.getCanvas().style.cursor="pointer"}),t.on("mouseout",()=>{t.getCanvas().style.cursor=""}),t.on("click",r=>{e.set("lng_lat",r.lngLat),e.save_changes()}),t.once("load",()=>{t.resize()}),t}function pe({model:n,el:e}){console.log("maplibregl",R.version);let t=ne(n),r=Object.assign({container:t},n.get("map_options"));console.log(r);let s=re(r,n),a=W(R,s),o=c=>{c.forEach(u=>{if(Object.keys(a).includes(u[0])){console.log("internal call",u);let[p,g]=u;a[p](...g);return}I(s,u)})},i=n.get("calls");s.on("load",()=>{console.log("init calls",i),o(i),n.set("_rendered",!0),n.save_changes()}),n.on("msg:custom",c=>{console.log("custom msg",c),o(c.calls)}),e.appendChild(t)}export{pe as render};
+/*! Bundled license information:
-// srcjs/mapmethods.js
-function applyMapMethod(map, call) {
- const [methodName, params] = call;
- console.log(methodName, params);
- map[methodName](...params);
-}
-function getCustomMapMethods(maplibregl2, map) {
- return {
- addTooltip: function(layerId, property) {
- const popupOptions = {
- closeButton: false,
- closeOnClick: false
- };
- const popup = new maplibregl2.Popup(popupOptions);
- map.on("mousemove", layerId, (e) => {
- const feature = e.features[0];
- const text = feature.properties[property];
- popup.setLngLat(e.lngLat).setHTML(text).addTo(map);
- });
- map.on("mouseleave", layerId, () => {
- popup.remove();
- });
- },
- addControl: function(type, options, position) {
- map.addControl(new maplibregl2[type](options), position);
- },
- addPopup: function(layerId, property) {
- const popupOptions = {
- closeButton: false
- };
- const popup = new maplibregl2.Popup(popupOptions);
- map.on("click", layerId, (e) => {
- const feature = e.features[0];
- const text = feature.properties[property];
- popup.setLngLat(e.lngLat).setHTML(text).addTo(map);
- });
- },
- addMarker: function({ lngLat, popup, options }) {
- const marker = new maplibregl2.Marker(options).setLngLat(lngLat);
- if (popup) {
- const popup_ = new maplibregl2.Popup(popup.options).setHTML(popup.text);
- marker.setPopup(popup_);
- }
- marker.addTo(map);
- },
- setSourceData: function(sourceId, data) {
- map.getSource(sourceId).setData(data);
- }
- };
-}
-
-// srcjs/ipywidget.js
-function createContainer(model) {
- const id = "pymaplibregl";
- const container = document.createElement("div");
- container.id = id;
- container.style.height = model.get("height");
- return container;
-}
-function createMap(mapOptions, model) {
- const map = new maplibregl.Map(mapOptions);
- if (mapOptions.navigationControl === void 0) {
- mapOptions.navigationControl = true;
- }
- if (mapOptions.navigationControl) {
- map.addControl(new maplibregl.NavigationControl());
- }
- map.on("mouseover", () => {
- map.getCanvas().style.cursor = "pointer";
- });
- map.on("mouseout", () => {
- map.getCanvas().style.cursor = "";
- });
- map.on("click", (e) => {
- model.set("lng_lat", e.lngLat);
- model.save_changes();
- });
- map.once("load", () => {
- map.resize();
- });
- return map;
-}
-function render({ model, el }) {
- console.log("maplibregl", maplibregl.version);
- const container = createContainer(model);
- const mapOptions = Object.assign(
- { container },
- model.get("map_options")
- );
- console.log(mapOptions);
- const map = createMap(mapOptions, model);
- const customMapMethods = getCustomMapMethods(maplibregl, map);
- const apply = (calls2) => {
- calls2.forEach((call) => {
- if (Object.keys(customMapMethods).includes(call[0])) {
- console.log("internal call", call);
- const [name, params] = call;
- customMapMethods[name](...params);
- return;
- }
- applyMapMethod(map, call);
- });
- };
- const calls = model.get("calls");
- map.on("load", () => {
- console.log("init calls", calls);
- apply(calls);
- model.set("_rendered", true);
- model.save_changes();
- });
- model.on("msg:custom", (msg) => {
- console.log("custom msg", msg);
- apply(msg.calls);
- });
- el.appendChild(container);
-}
-export {
- render
-};
+mustache/mustache.mjs:
+ (*!
+ * mustache.js - Logic-less {{mustache}} templates with JavaScript
+ * http://github.com/janl/mustache.js
+ *)
+*/
diff --git a/mkdocs.yml b/mkdocs.yml
index 95e5a0d3..24767f1b 100644
--- a/mkdocs.yml
+++ b/mkdocs.yml
@@ -15,6 +15,7 @@ nav:
- Layers: layers.md
- Shiny: shiny.md
- Jupyter: jupyter.md
+ - Changelog: changelog.md
- API Documentation:
- Map: api/map.md
- Layer: api/layer.md
@@ -29,7 +30,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
+ - H3 Grid UK Road Safety: examples/road_safety/index.md
- Where is the ISS: examples/where_is_the_iss/index.md
plugins:
diff --git a/package-lock.json b/package-lock.json
index da69116b..92d81e05 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,13 +1,16 @@
{
"name": "pymaplibregl",
- "version": "0.1.0",
+ "version": "0.1.1",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "pymaplibregl",
- "version": "0.1.0",
+ "version": "0.1.1",
"license": "MIT",
+ "dependencies": {
+ "mustache": "^4.2.0"
+ },
"devDependencies": {
"esbuild": "0.19.10",
"prettier": "3.1.1"
@@ -419,6 +422,14 @@
"@esbuild/win32-x64": "0.19.10"
}
},
+ "node_modules/mustache": {
+ "version": "4.2.0",
+ "resolved": "https://registry.npmjs.org/mustache/-/mustache-4.2.0.tgz",
+ "integrity": "sha512-71ippSywq5Yb7/tVYyGbkBggbU8H3u5Rz56fH60jGFgr8uHwxs+aSKeqmluIVzM0m0kB7xQjKS6qPfd0b2ZoqQ==",
+ "bin": {
+ "mustache": "bin/mustache"
+ }
+ },
"node_modules/prettier": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/prettier/-/prettier-3.1.1.tgz",
diff --git a/package.json b/package.json
index c4f6b08a..afbf809e 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "pymaplibregl",
- "version": "0.1.0",
+ "version": "0.1.1",
"description": "...",
"main": "index.js",
"directories": {
@@ -8,8 +8,10 @@
"test": "tests"
},
"scripts": {
- "build": "esbuild srcjs/index.js --bundle --outfile=maplibre/srcjs/index.js",
- "build-ipywidget": "esbuild srcjs/ipywidget.js --bundle --format=esm --outfile=maplibre/srcjs/ipywidget.js",
+ "build": "esbuild srcjs/index.js --bundle --minify --outfile=maplibre/srcjs/index.js",
+ "build-dev": "esbuild srcjs/index.js --bundle --outfile=maplibre/srcjs/index.js",
+ "build-ipywidget": "esbuild srcjs/ipywidget.js --bundle --minify --format=esm --outfile=maplibre/srcjs/ipywidget.js",
+ "build-ipywidget-dev": "esbuild srcjs/ipywidget.js --bundle --format=esm --outfile=maplibre/srcjs/ipywidget.js",
"build-rwidget": "esbuild srcjs/rwidget.js --bundle --minify --outfile=../r-maplibregl/inst/htmlwidgets/maplibre.js",
"prettier": "prettier srcjs --write",
"test": "echo \"Error: no test specified\" && exit 1"
@@ -19,5 +21,8 @@
"devDependencies": {
"esbuild": "0.19.10",
"prettier": "3.1.1"
+ },
+ "dependencies": {
+ "mustache": "^4.2.0"
}
}
diff --git a/pyproject.toml b/pyproject.toml
index 43e3cd7d..af4a8927 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -1,6 +1,6 @@
[tool.poetry]
name = "maplibre"
-version = "0.1.2"
+version = "0.1.3"
description = "Python bindings for MapLibre GL JS"
authors = ["Stefan Kuethe "]
readme = "README.md"
diff --git a/srcjs/mapmethods.js b/srcjs/mapmethods.js
index 17d094fd..573a2b0a 100644
--- a/srcjs/mapmethods.js
+++ b/srcjs/mapmethods.js
@@ -1,3 +1,5 @@
+import { getTextFromFeature } from "./utils";
+
function applyMapMethod(map, call) {
const [methodName, params] = call;
console.log(methodName, params);
@@ -8,7 +10,7 @@ function applyMapMethod(map, call) {
// Custom map methods
function getCustomMapMethods(maplibregl, map) {
return {
- addTooltip: function (layerId, property) {
+ addTooltip: function (layerId, property = null, template = null) {
const popupOptions = {
closeButton: false,
closeOnClick: false,
@@ -17,7 +19,9 @@ function getCustomMapMethods(maplibregl, map) {
map.on("mousemove", layerId, (e) => {
const feature = e.features[0];
- const text = feature.properties[property];
+
+ // const text = feature.properties[property];
+ const text = getTextFromFeature(feature, property, template);
popup.setLngLat(e.lngLat).setHTML(text).addTo(map);
});
@@ -30,14 +34,16 @@ function getCustomMapMethods(maplibregl, map) {
map.addControl(new maplibregl[type](options), position);
},
- addPopup: function (layerId, property) {
+ addPopup: function (layerId, property = null, template = null) {
const popupOptions = {
closeButton: false,
};
const popup = new maplibregl.Popup(popupOptions);
map.on("click", layerId, (e) => {
const feature = e.features[0];
- const text = feature.properties[property];
+
+ // const text = feature.properties[property];
+ const text = getTextFromFeature(feature, property, template);
popup.setLngLat(e.lngLat).setHTML(text).addTo(map);
});
},
diff --git a/srcjs/pymaplibregl.js b/srcjs/pymaplibregl.js
index 52e0e4c6..2538c850 100644
--- a/srcjs/pymaplibregl.js
+++ b/srcjs/pymaplibregl.js
@@ -1,4 +1,5 @@
// import { getCustomMapMethods } from "./mapmethods";
+import { getTextFromFeature } from "./utils";
export default class PyMapLibreGL {
constructor(mapOptions) {
@@ -59,19 +60,20 @@ export default class PyMapLibreGL {
}
}
- addPopup(layerId, property) {
+ addPopup(layerId, property = null, template = null) {
const popupOptions = {
closeButton: false,
};
const popup = new maplibregl.Popup(popupOptions);
this._map.on("click", layerId, (e) => {
const feature = e.features[0];
- const text = feature.properties[property];
+ // const text = feature.properties[property];
+ const text = getTextFromFeature(feature, property, template);
popup.setLngLat(e.lngLat).setHTML(text).addTo(this._map);
});
}
- addTooltip(layerId, property) {
+ addTooltip(layerId, property = null, template = null) {
const popupOptions = {
closeButton: false,
closeOnClick: false,
@@ -79,7 +81,7 @@ export default class PyMapLibreGL {
const popup = new maplibregl.Popup(popupOptions);
this._map.on("mousemove", layerId, (e) => {
const feature = e.features[0];
- const text = feature.properties[property];
+ const text = getTextFromFeature(feature, property, template);
popup.setLngLat(e.lngLat).setHTML(text).addTo(this._map);
});
diff --git a/srcjs/utils.js b/srcjs/utils.js
new file mode 100644
index 00000000..cedd917b
--- /dev/null
+++ b/srcjs/utils.js
@@ -0,0 +1,18 @@
+import mustache from "mustache";
+
+function getTextFromFeature(feature, property, template) {
+ if (template !== null) {
+ return mustache.render(template, feature.properties);
+ }
+
+ if (property === null) {
+ const text = Object.keys(feature.properties)
+ .map((key) => `${key}: ${feature.properties[key]}`)
+ .join("");
+ return text;
+ }
+
+ return feature.properties[property];
+}
+
+export { getTextFromFeature };