-
Notifications
You must be signed in to change notification settings - Fork 54
feat: Add an example for Geohash Layer #1242
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 1 commit
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,289 @@ | ||
| { | ||
|
Check failure on line 1 in examples/geohash-layer.ipynb
|
||
| "cells": [ | ||
| { | ||
| "cell_type": "markdown", | ||
|
Member
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. Similar to other examples like https://github.com/developmentseed/lonboard/blob/main/examples/data-filter-extension.ipynb, could you create the example notebook with Assuming you have Then you can copy in the markdown instructional cell:
|
||
| "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", | ||
|
Member
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. 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
Member
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. 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 | ||
| } | ||

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.
CI is failing on some formatting and linting on this notebook