-
Notifications
You must be signed in to change notification settings - Fork 39
feat: Table of contents #836
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from 17 commits
b3a1554
d9b4d32
a621dcf
0a6c69a
c747ef1
ebb3bd6
a55a09f
ebbf70f
5eeb709
63f22f1
167047a
5cce5f6
f345c60
b900dc7
a741012
6151737
0897f3d
d9f6c73
bd15c96
4bd6b2b
85b1793
28ff53d
4b31240
0db441d
fed8ac3
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,238 @@ | ||
{ | ||
"cells": [ | ||
{ | ||
"cell_type": "markdown", | ||
"id": "a033bf32-69c9-48bc-a275-8ce1a3901365", | ||
"metadata": {}, | ||
"source": [ | ||
"## Layer Control\n", | ||
"\n", | ||
"This notebook demonstrates the use of the lonbord map's `layer_control`, to control layer visibility and layer properties." | ||
] | ||
}, | ||
{ | ||
"cell_type": "code", | ||
"execution_count": null, | ||
"id": "bb983a26-ab11-4070-91bd-c78371ca6d68", | ||
"metadata": {}, | ||
"outputs": [], | ||
"source": [ | ||
"from pathlib import Path\n", | ||
"\n", | ||
"import geopandas as gpd\n", | ||
"from palettable.colorbrewer.sequential import Blues_8\n", | ||
"\n", | ||
"from lonboard import Map, PathLayer, PolygonLayer\n", | ||
"from lonboard.colormap import apply_continuous_cmap" | ||
] | ||
}, | ||
{ | ||
"cell_type": "markdown", | ||
"id": "3dcbf11b-6c10-46bc-a6de-1d1d37f4e8b6", | ||
"metadata": {}, | ||
"source": [ | ||
"### Get data\n", | ||
"\n", | ||
"Download data from the web and save as geoparquet so we can show some data on our Lonboard map and create a layer control." | ||
] | ||
}, | ||
{ | ||
"cell_type": "code", | ||
"execution_count": null, | ||
"id": "56896a28-4d72-426e-9ea0-2e3854e389f8", | ||
"metadata": {}, | ||
"outputs": [], | ||
"source": [ | ||
"file_urls = [\n", | ||
" (\n", | ||
" \"ne_10m_roads_north_america.parquet\",\n", | ||
" \"https://naciscdn.org/naturalearth/10m/cultural/ne_10m_roads_north_america.zip\",\n", | ||
" ),\n", | ||
" (\n", | ||
" \"geoBoundariesCGAZ_ADM1.parquet\",\n", | ||
" \"https://github.com/wmgeolab/geoBoundaries/raw/main/releaseData/CGAZ/geoBoundariesCGAZ_ADM1.geojson\",\n", | ||
" ),\n", | ||
" (\n", | ||
" \"rivers_asia_37331.parquet\",\n", | ||
" \"https://storage.googleapis.com/fao-maps-catalog-data/geonetwork/aquamaps/rivers_asia_37331.zip\",\n", | ||
" ),\n", | ||
"]\n", | ||
"for filename, url in file_urls:\n", | ||
" if Path(filename).exists() is False:\n", | ||
" print(f\"Reading {filename} from web and saving as geoparquet.\")\n", | ||
" gdf = gpd.read_file(url, engine=\"pyogrio\")\n", | ||
" gdf.to_parquet(filename)\n", | ||
" del gdf\n", | ||
" else:\n", | ||
" print(f\"{filename} already downloaded.\")" | ||
] | ||
}, | ||
{ | ||
"cell_type": "markdown", | ||
"id": "00e57959-6b12-4dc7-8621-6f725d928b96", | ||
"metadata": {}, | ||
"source": [ | ||
"### Read geoparquet files into geopandas dataframes" | ||
] | ||
}, | ||
{ | ||
"cell_type": "code", | ||
"execution_count": null, | ||
"id": "230e851e-8bb9-4697-9143-688537c746a0", | ||
"metadata": {}, | ||
"outputs": [], | ||
"source": [ | ||
"boundary_df = gpd.read_parquet(\"geoBoundariesCGAZ_ADM1.parquet\")\n", | ||
"road_df = gpd.read_parquet(\"ne_10m_roads_north_america.parquet\")\n", | ||
"river_df = gpd.read_parquet(\"rivers_asia_37331.parquet\")" | ||
] | ||
}, | ||
{ | ||
"cell_type": "markdown", | ||
"id": "f98bf950-2f66-47eb-8e3a-8123aa2083d5", | ||
"metadata": {}, | ||
"source": [ | ||
"### Create layers\n", | ||
"\n", | ||
"* Create a `PolygonLayer` from the boundary dataframe that is brown with a darker brown outline, that's 1 pixel wide.\n", | ||
"\n", | ||
"* Create a `PathLayer` from the road dataframe with a title and minimum width.\n", | ||
"\n", | ||
"* Create a `PathLayer` from the river dataframe that uses the 'Strahler' column for setting the color and width of the lines, as well as some defaults on the width to make the more prominent rivers darker and wider on the map." | ||
] | ||
}, | ||
{ | ||
"cell_type": "code", | ||
"execution_count": null, | ||
"id": "e3366f15-245f-4a8f-b87d-dcb7fd993392", | ||
"metadata": {}, | ||
"outputs": [], | ||
"source": [ | ||
"boundary_layer = PolygonLayer.from_geopandas(\n", | ||
" boundary_df,\n", | ||
" title=\"Boundaries\",\n", | ||
" get_fill_color=[137, 81, 41],\n", | ||
" get_line_color=[102, 60, 31],\n", | ||
" get_line_width=1,\n", | ||
" line_width_units=\"pixels\",\n", | ||
" stroked=True,\n", | ||
")\n", | ||
"\n", | ||
"road_layer = PathLayer.from_geopandas(road_df, width_min_pixels=0.8)\n", | ||
"\n", | ||
"river_layer = PathLayer.from_geopandas(\n", | ||
" river_df,\n", | ||
" title=\"Rivers\",\n", | ||
" get_color=apply_continuous_cmap(river_df[\"Strahler\"] / 7, Blues_8),\n", | ||
" get_width=river_df[\"Strahler\"],\n", | ||
" width_scale=3000,\n", | ||
" width_min_pixels=0.5,\n", | ||
")" | ||
] | ||
}, | ||
{ | ||
"cell_type": "markdown", | ||
"id": "9cdd3071-2dd6-4b77-b42d-7def62e3087d", | ||
"metadata": {}, | ||
"source": [ | ||
"### Create the Lonboard `Map` and `layer_control`\n", | ||
"\n", | ||
"Create a lonboard map, and then create a `layer_control` with the `include_settings` parameter to True then display them both.\n", | ||
"\n", | ||
"With `include_settings=True` we will get a layer control that includes the setttings cog, which when expanded will allow us to change some of the layer properties. Note that we did not give this layer a title, so when we make the layer control, the default title will show in the layer control.\n", | ||
"\n", | ||
"If the user unchecks the checkbox next to the layer's name the layer's visibility will be set to False.\n", | ||
"\n", | ||
"!!! note\n", | ||
"\n", | ||
" We're only adding the boundary and road layer at this point, not the river layer. We'll add that later, and when we do we can see the layer control automatically react to the new layer being added to the map, and it will show up in our layer control." | ||
] | ||
}, | ||
{ | ||
"cell_type": "code", | ||
"execution_count": null, | ||
"id": "ae4529e4-b656-47eb-8928-a51f02d038fb", | ||
"metadata": {}, | ||
"outputs": [], | ||
"source": [ | ||
"lonboard_map = Map([boundary_layer, road_layer])\n", | ||
"lc = lonboard_map.layer_control(include_settings=True)\n", | ||
"\n", | ||
"display(lonboard_map)\n", | ||
"display(lc)" | ||
] | ||
}, | ||
{ | ||
"cell_type": "markdown", | ||
"id": "9c291004-4d2d-4f7f-9dcb-f3f1d098114f", | ||
"metadata": {}, | ||
"source": [ | ||
"### Change the title of the road layer\n", | ||
"\n", | ||
"By default the title of the layer is the layer's type. When we change the title of the layer, it will automatically be changed in the layer control." | ||
] | ||
}, | ||
{ | ||
"cell_type": "code", | ||
"execution_count": null, | ||
"id": "2039d785-3edb-44b8-bbb7-ab61397bdae4", | ||
"metadata": {}, | ||
"outputs": [], | ||
"source": [ | ||
"road_layer.title = \"Roads\"" | ||
] | ||
}, | ||
{ | ||
"cell_type": "markdown", | ||
"id": "d475c61b-e0fd-4227-a3c1-aa2cabeec7d0", | ||
"metadata": {}, | ||
"source": [ | ||
"### Add the River layer\n", | ||
"\n", | ||
"When we add the river layer to the map, the layer control will automatically detect the new layer, and also add it to the layer control.\n", | ||
"\n", | ||
"When we expand the cog for the river layer we will see that the `Color` and the `Width` properties of the layer display `Custom` instead of a color picker/float widget. \n", | ||
"\"Custom\" is displayed in the layer control currently because the layer uses the values from the rows of data to render the lines. This may change in future releases of Lonboard." | ||
] | ||
}, | ||
{ | ||
"cell_type": "code", | ||
"execution_count": null, | ||
"id": "13a4bb69-6871-4e40-946f-27edccac9f05", | ||
"metadata": {}, | ||
"outputs": [], | ||
"source": [ | ||
"lonboard_map.add_layer(river_layer, reset_zoom=True)" | ||
] | ||
}, | ||
{ | ||
"cell_type": "code", | ||
"execution_count": null, | ||
"id": "0a3b72c2-8601-4ae6-8ebd-209a03f19aaf", | ||
"metadata": {}, | ||
"outputs": [], | ||
"source": [] | ||
} | ||
], | ||
"metadata": { | ||
"kernelspec": { | ||
"display_name": "lonboard_toc", | ||
"language": "python", | ||
"name": "lonboard_toc" | ||
}, | ||
"language_info": { | ||
"codemirror_mode": { | ||
"name": "ipython", | ||
"version": 3 | ||
}, | ||
"file_extension": ".py", | ||
"mimetype": "text/x-python", | ||
"name": "python", | ||
"nbconvert_exporter": "python", | ||
"pygments_lexer": "ipython3", | ||
"version": "3.11.11" | ||
} | ||
}, | ||
"nbformat": 4, | ||
"nbformat_minor": 5 | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -247,7 +247,7 @@ def _add_extension_traits(self, extensions: Sequence[BaseExtension]) -> None: | |
|
||
highlight_color = VariableLengthTuple( | ||
t.Int(), | ||
default_value=None, | ||
default_value=[0, 0, 128, 128], | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think this is unrelated? There's a question of where defaults should live: in JS code or in Python code. In this case, we don't override the upstream deck.gl default, so leaving this as There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I did set that intentionally, because it was giving me trouble when it was None, but looking at it right now with eyes from a different day, I think I may be able to change some other stuff in the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. oh I think I may have actually come across a bug in the existing code when I was doing this and thought it was something I was doing. when I try to access the highlight_color property of a layer with None as the default value with
I'm getting a Trait Error:
can you re-create that error on your end? |
||
minlen=3, | ||
maxlen=4, | ||
) | ||
|
@@ -581,6 +581,12 @@ def _weighted_centroid(self) -> WeightedCentroid: | |
# image should represent. | ||
return WeightedCentroid(x=center_x, y=center_y, num_items=100) | ||
|
||
title = t.CUnicode("BitmapLayer", allow_none=False).tag(sync=True) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't love the idea of repeating each layer name another time. Ideally we could use There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. that's a good idea, I hadn't considered setting it on the JS side, I'll see if i can do that. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I failed miserably on the js side to set it, I moved the title back to the BaseLayer and then in the init method I set it to check if the title is None, and if so use the _layer_type to give it a default title. if that's not a workable solution I'll need a hand on the JS end :( |
||
""" | ||
The title of the layer. The title of the layer is visible in the layer control | ||
produced by map.layer_control(). | ||
""" | ||
|
||
|
||
class BitmapTileLayer(BaseLayer): | ||
"""The BitmapTileLayer renders image tiles (e.g. PNG, JPEG, or WebP) in the web | ||
|
@@ -778,6 +784,12 @@ def __init__(self, **kwargs: BitmapTileLayerKwargs) -> None: | |
- Default: `[255, 255, 255]` | ||
""" | ||
|
||
title = t.CUnicode("BitmapTileLayer", allow_none=False).tag(sync=True) | ||
""" | ||
The title of the layer. The title of the layer is visible in the layer control | ||
produced by map.layer_control(). | ||
""" | ||
|
||
|
||
class ColumnLayer(BaseArrowLayer): | ||
"""The ColumnLayer renders extruded cylinders (tessellated regular polygons) at given | ||
|
@@ -1025,6 +1037,12 @@ def from_duckdb( | |
- Default: `1`. | ||
""" | ||
|
||
title = t.CUnicode("ColumnLayer", allow_none=False).tag(sync=True) | ||
""" | ||
The title of the layer. The title of the layer is visible in the layer control | ||
produced by map.layer_control(). | ||
""" | ||
|
||
|
||
class PolygonLayer(BaseArrowLayer): | ||
"""The `PolygonLayer` renders filled, stroked and/or extruded polygons. | ||
|
@@ -1279,6 +1297,12 @@ def from_duckdb( | |
- Default: `1000`. | ||
""" | ||
|
||
title = t.CUnicode("PolygonLayer", allow_none=False).tag(sync=True) | ||
""" | ||
The title of the layer. The title of the layer is visible in the layer control | ||
produced by map.layer_control(). | ||
""" | ||
|
||
|
||
class ScatterplotLayer(BaseArrowLayer): | ||
"""The `ScatterplotLayer` renders circles at given coordinates. | ||
|
@@ -1517,6 +1541,12 @@ def from_duckdb( | |
- Default: `1`. | ||
""" | ||
|
||
title = t.CUnicode("ScatterplotLayer", allow_none=False).tag(sync=True) | ||
""" | ||
The title of the layer. The title of the layer is visible in the layer control | ||
produced by map.layer_control(). | ||
""" | ||
|
||
|
||
class PathLayer(BaseArrowLayer): | ||
"""The `PathLayer` renders lists of coordinate points as extruded polylines with | ||
|
@@ -1699,6 +1729,12 @@ def from_duckdb( | |
- Default: `1`. | ||
""" | ||
|
||
title = t.CUnicode("PathLayer", allow_none=False).tag(sync=True) | ||
""" | ||
The title of the layer. The title of the layer is visible in the layer control | ||
produced by map.layer_control(). | ||
""" | ||
|
||
|
||
class PointCloudLayer(BaseArrowLayer): | ||
"""The `PointCloudLayer` renders a point cloud with 3D positions, normals and colors. | ||
|
@@ -1816,6 +1852,12 @@ def from_duckdb( | |
- Default: `1`. | ||
""" | ||
|
||
title = t.CUnicode("PointCloudLayer", allow_none=False).tag(sync=True) | ||
""" | ||
The title of the layer. The title of the layer is visible in the layer control | ||
produced by map.layer_control(). | ||
""" | ||
|
||
|
||
class SolidPolygonLayer(BaseArrowLayer): | ||
"""The `SolidPolygonLayer` renders filled and/or extruded polygons. | ||
|
@@ -1992,6 +2034,12 @@ def from_duckdb( | |
- Default: `[0, 0, 0, 255]`. | ||
""" | ||
|
||
title = t.CUnicode("SolidPolygonLayer", allow_none=False).tag(sync=True) | ||
""" | ||
The title of the layer. The title of the layer is visible in the layer control | ||
produced by map.layer_control(). | ||
""" | ||
|
||
|
||
class HeatmapLayer(BaseArrowLayer): | ||
"""The `HeatmapLayer` visualizes the spatial distribution of data. | ||
|
@@ -2167,3 +2215,9 @@ def from_duckdb( | |
for the object at the same row index. | ||
- Default: `1`. | ||
""" | ||
|
||
title = t.CUnicode("HeatmapLayer", allow_none=False).tag(sync=True) | ||
""" | ||
The title of the layer. The title of the layer is visible in the layer control | ||
produced by map.layer_control(). | ||
""" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Trying to render the geo boundaries file crashes VSCode for me and doesn't display well in Jupyter Lab. We should find a smaller file to work with, or just use the other two layers.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
oh, I'm sorry! I'll do something to limit what gets displayed