Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file added assets/geohash-layer.jpeg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
289 changes: 289 additions & 0 deletions examples/geohash-layer.ipynb
Original file line number Diff line number Diff line change
@@ -0,0 +1,289 @@
{

Check failure on line 1 in examples/geohash-layer.ipynb

View workflow job for this annotation

GitHub Actions / tests (3.11)

ruff (ANN001)

examples/geohash-layer.ipynb:1:1: ANN001 Missing type annotation for function argument `df`

Check failure on line 1 in examples/geohash-layer.ipynb

View workflow job for this annotation

GitHub Actions / tests (3.11)

ruff (ANN201)

examples/geohash-layer.ipynb:1:1: ANN201 Missing return type annotation for public function `aggregate_cell_data` help: Add return type annotation

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

CI is failing on some formatting and linting on this notebook

"cells": [
{
"cell_type": "markdown",

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Similar to other examples like https://github.com/developmentseed/lonboard/blob/main/examples/data-filter-extension.ipynb, could you create the example notebook with juv, so that dependencies are automatically managed with uv?

Assuming you have uv installed already, you can use something like uvx juv init [name] to create the notebook, uvx juv add [name] [dependency] to add dependencies, and uvx juv run [name] to run the notebook.

Then you can copy in the markdown instructional cell:

Image

"id": "geohash-intro",
"metadata": {},
"source": [
"This example aggregates public NYC 311 requests into geohash cells and renders their density with `GeohashLayer`. Brighter cells received fewer reports; darker red cells received more.\n",
"\n",
"The data comes from [NYC Open Data's 311 Service Requests dataset](https://data.cityofnewyork.us/Social-Services/311-Service-Requests-from-2010-to-Present/erm2-nwe9/about_data)."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "geohash-dependencies",
"metadata": {
"jupyter": {
"source_hidden": true
}
},
"outputs": [],
"source": [
"# /// script\n",
"# requires-python = \">=3.12\"\n",
"# dependencies = [\n",
"# \"geohash2\",\n",

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

geohash2 is 10 years old: https://github.com/dbarthe/geohash/

Can we use another more modern geohash library like https://pypi.org/project/pygeohash/?

"# \"lonboard\",\n",
"# \"matplotlib\",\n",
"# \"palettable\",\n",
"# \"pandas\",\n",
"# \"pyarrow\",\n",
"# \"requests\",\n",
"# ]\n",
"# ///"
]
},
{
"cell_type": "code",
"execution_count": 6,
"id": "geohash-imports",
"metadata": {},
"outputs": [],
"source": [
"import geohash2\n",
"import pandas as pd\n",
"import requests\n",
"from matplotlib.colors import LogNorm\n",
"from palettable.colorbrewer.sequential import YlOrRd_9\n",
"\n",
"from lonboard import GeohashLayer, Map\n",
"from lonboard.basemap import CartoStyle, MaplibreBasemap\n",
"from lonboard.colormap import apply_continuous_cmap\n",
"from lonboard.view_state import MapViewState"
]
},
{
"cell_type": "markdown",
"id": "geohash-download-note",
"metadata": {},
"source": [
"## Download and aggregate the data"
]
},
{
"cell_type": "code",
"execution_count": 7,
"id": "geohash-aggregate",
"metadata": {},
"outputs": [
{
"data": {
"text/html": [
"<div>\n",
"<style scoped>\n",
" .dataframe tbody tr th:only-of-type {\n",
" vertical-align: middle;\n",
" }\n",
"\n",
" .dataframe tbody tr th {\n",
" vertical-align: top;\n",
" }\n",
"\n",
" .dataframe thead th {\n",
" text-align: right;\n",
" }\n",
"</style>\n",
"<table border=\"1\" class=\"dataframe\">\n",
" <thead>\n",
" <tr style=\"text-align: right;\">\n",
" <th></th>\n",
" <th>geohash</th>\n",
" <th>request_count</th>\n",
" <th>primary_borough</th>\n",
" <th>top_complaint</th>\n",
" <th>top_complaint_count</th>\n",
" </tr>\n",
" </thead>\n",
" <tbody>\n",
" <tr>\n",
" <th>0</th>\n",
" <td>dr5nqr</td>\n",
" <td>1</td>\n",
" <td>STATEN ISLAND</td>\n",
" <td>Noise - Commercial</td>\n",
" <td>1</td>\n",
" </tr>\n",
" <tr>\n",
" <th>1</th>\n",
" <td>dr5nqw</td>\n",
" <td>1</td>\n",
" <td>STATEN ISLAND</td>\n",
" <td>Noise - Residential</td>\n",
" <td>1</td>\n",
" </tr>\n",
" <tr>\n",
" <th>2</th>\n",
" <td>dr5nqx</td>\n",
" <td>1</td>\n",
" <td>STATEN ISLAND</td>\n",
" <td>Illegal Parking</td>\n",
" <td>1</td>\n",
" </tr>\n",
" <tr>\n",
" <th>3</th>\n",
" <td>dr5nqz</td>\n",
" <td>1</td>\n",
" <td>STATEN ISLAND</td>\n",
" <td>Damaged Tree</td>\n",
" <td>1</td>\n",
" </tr>\n",
" <tr>\n",
" <th>4</th>\n",
" <td>dr5nw9</td>\n",
" <td>1</td>\n",
" <td>STATEN ISLAND</td>\n",
" <td>Building/Use</td>\n",
" <td>1</td>\n",
" </tr>\n",
" </tbody>\n",
"</table>\n",
"</div>"
],
"text/plain": [
" geohash request_count primary_borough top_complaint \\\n",
"0 dr5nqr 1 STATEN ISLAND Noise - Commercial \n",
"1 dr5nqw 1 STATEN ISLAND Noise - Residential \n",
"2 dr5nqx 1 STATEN ISLAND Illegal Parking \n",
"3 dr5nqz 1 STATEN ISLAND Damaged Tree \n",
"4 dr5nw9 1 STATEN ISLAND Building/Use \n",
"\n",
" top_complaint_count \n",
"0 1 \n",
"1 1 \n",
"2 1 \n",
"3 1 \n",
"4 1 "
]
},
"execution_count": 7,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"API_URL = \"https://data.cityofnewyork.us/resource/erm2-nwe9.json\"\n",
"PARAMS = {\n",
" \"$select\": \"latitude, longitude, complaint_type, borough\",\n",
" \"$where\": (\n",
" \"created_date >= '2025-01-01T00:00:00' AND \"\n",
" \"created_date < '2025-01-08T00:00:00' AND \"\n",
" \"latitude IS NOT NULL AND longitude IS NOT NULL\"\n",
" ),\n",
" \"$order\": \"created_date ASC\",\n",
" \"$limit\": 10_000,\n",
"}\n",
"\n",
"try:\n",
" response = requests.get(API_URL, params=PARAMS, timeout=30)\n",
" response.raise_for_status()\n",
"except requests.RequestException as exc:\n",
" raise RuntimeError(\"Unable to download NYC 311 data. Please try again later.\") from exc\n",
"\n",
"requests_df = pd.DataFrame(response.json())\n",
"if requests_df.empty:\n",
" raise RuntimeError(\"The NYC 311 query returned no requests.\")\n",
"\n",
"requests_df = requests_df.astype({\"latitude\": \"float64\", \"longitude\": \"float64\"})\n",
"requests_df[\"geohash\"] = [\n",
" geohash2.encode(latitude, longitude, precision=6)\n",
" for latitude, longitude in zip(requests_df[\"latitude\"], requests_df[\"longitude\"], strict=True)\n",
"]\n",
Comment on lines +205 to +212

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would be nice if the geohash library provided a vectorized API, so that we could nudge people towards a faster approach. But this is fine.

"def aggregate_cell_data(df):\n",
" top_complaint = df[\"complaint_type\"].mode()[0] if not df[\"complaint_type\"].empty else \"N/A\"\n",
" return pd.Series({\n",
" \"request_count\": len(df),\n",
" \"primary_borough\": df[\"borough\"].mode()[0] if \"borough\" in df.columns else \"Unspecified\",\n",
" \"top_complaint\": top_complaint,\n",
" \"top_complaint_count\": (df[\"complaint_type\"] == top_complaint).sum(),\n",
" })\n",
"\n",
"cells = requests_df.groupby(\"geohash\", as_index=False).apply(aggregate_cell_data)\n",
"cells.head()"
]
},
{
"cell_type": "markdown",
"id": "geohash-render-note",
"metadata": {},
"source": [
"## Explore neighborhood-scale demand\n",
"\n",
"Hover over a cell to inspect its geohash and request count."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "geohash-render",
"metadata": {},
"outputs": [
{
"data": {
"application/vnd.jupyter.widget-view+json": {
"model_id": "bc8ae637553b4e1b87cf53bd3ef192e6",
"version_major": 2,
"version_minor": 0
},
"text/plain": [
"VBox(children=(<lonboard._map.Map object at 0x118b095b0>, VBox(children=(ErrorOutput(), ErrorOutput(), ErrorOu…"
]
},
"execution_count": 8,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"color_scale = LogNorm(\n",
" vmin=cells[\"request_count\"].min(),\n",
" vmax=cells[\"request_count\"].max(),\n",
")\n",
"\n",
"layer = GeohashLayer.from_pandas(\n",
" cells,\n",
" get_geohash=cells[\"geohash\"],\n",
" get_fill_color=apply_continuous_cmap(\n",
" color_scale(cells[\"request_count\"]), YlOrRd_9, alpha=0.88\n",
" ),\n",
" get_line_color=[35, 12, 8, 160],\n",
" line_width_min_pixels=0.75,\n",
" pickable=True,\n",
")\n",
"\n",
"map_ = Map(\n",
" layer,\n",
" basemap=MaplibreBasemap(style=CartoStyle.DarkMatter),\n",
" view_state=MapViewState(longitude=-73.96, latitude=40.73, zoom=10),\n",
" height=700,\n",
" show_tooltip=True,\n",
" show_side_panel=False,\n",
" picking_radius=5,\n",
")\n",
"map_"
]
}
],
"metadata": {
"kernelspec": {
"display_name": "lonboard (3.12.x)",
"language": "python",
"name": "python3"
},
"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.12.13"
}
},
"nbformat": 4,
"nbformat_minor": 5
}
1 change: 1 addition & 0 deletions examples/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
- [Global boundaries ![](../assets/boundaries.png)](../examples/global-boundaries) using [`PolygonLayer`][lonboard.PolygonLayer]
- [Raster PMTiles ![](../assets/raster-pmtiles.jpg)](../examples/raster-pmtiles) using [`RasterLayer`][lonboard.RasterLayer]
- [H3 Population Data ![](../assets/kontur-h3.jpg)](../examples/kontur_pop) using [`H3HexagonLayer`][lonboard.H3HexagonLayer]
- [NYC 311 Data ![](../assets/geohash-layer.jpeg)](../examples/geohash-layer) using [`GeohashLayer`][lonboard.GeohashLayer]
- [U.S. County-to-County Migration ![](../assets/arc-layer-migration-example.gif)](../examples/migration) using [`ArcLayer`][lonboard.ArcLayer] and [`BrushingExtension`][lonboard.layer_extension.BrushingExtension]
- [Scatterplot with GPU data filtering ![](../assets/data-filter-extension.gif)](../examples/data-filter-extension) using [`ScatterplotLayer`][lonboard.ScatterplotLayer] and [`DataFilterExtension`][lonboard.layer_extension.DataFilterExtension]
- [Categorical Filtering ![](../assets/data-filter-extension-categorical.gif)](../examples/data-filter-extension-categorical) using [`ScatterplotLayer`][lonboard.ScatterplotLayer] and [`DataFilterExtension`][lonboard.layer_extension.DataFilterExtension]
Expand Down
1 change: 1 addition & 0 deletions mkdocs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ nav:
- examples/interleaved-labels.ipynb
- examples/linked-maps.ipynb
- examples/clicked-point.ipynb
- examples/geohash-layer.ipynb
- NYC Taxi Trips: examples/marimo/nyc_taxi_trips.md
- Integrations:
- examples/duckdb.ipynb
Expand Down
Loading