{
"cells": [
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# Pydeck + Earth Engine: Lines FeatureCollection\n",
"\n",
"This is an example of using [pydeck](https://pydeck.gl) to visualize a Google Earth Engine `FeatureCollection` of lines.\n",
"To install and run this notebook locally, refer to the [Pydeck Earth Engine documentation](https://earthengine-layers.com/docs/developer-guide/pydeck-integration).\n",
"\n",
"To see this example online, view the [JavaScript version][js-example].\n",
"\n",
"[js-example]: https://earthengine-layers.com/examples/noaa-hurricanes"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Import required packages:"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"from pydeck_earthengine_layers import EarthEngineLayer\n",
"import pydeck as pdk\n",
"import ee"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Authenticate with Earth Engine\n",
"\n",
"Using Earth Engine requires authentication. If you don't have a Google account approved for use with Earth Engine, you'll need to request access. For more information and to sign up, go to https://signup.earthengine.google.com/."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"scrolled": true
},
"outputs": [],
"source": [
"try:\n",
" ee.Initialize()\n",
"except Exception as e:\n",
" ee.Authenticate()\n",
" ee.Initialize()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### NOAA Hurricanes dataset\n",
"\n",
"This example uses the [NOAA Atlantic Hurricane catalog][noaa], a dataset with positions of hurricanes and related attributes from 1851 to 2018. In this example we'll look only at hurricanes in 2017.\n",
"\n",
"[noaa]: https://developers.google.com/earth-engine/datasets/catalog/NOAA_NHC_HURDAT2_atlantic"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"# Hurricane tracks and points for 2017.\n",
"hurricanes = ee.FeatureCollection('NOAA/NHC/HURDAT2/atlantic')\n",
"\n",
"year = '2017'\n",
"points = hurricanes.filter(ee.Filter.date(ee.Date(year).getRange('year')))\n",
"\n",
"# Find all of the hurricane ids.\n",
"def get_id(point):\n",
" return ee.Feature(point).get('id')\n",
"storm_ids = points.toList(1000).map(get_id).distinct()\n",
"\n",
"# Create a line for each hurricane.\n",
"def create_line(storm_id):\n",
" pts = points.filter(ee.Filter.eq('id', ee.String(storm_id)))\n",
" pts = pts.sort('system:time_start')\n",
" line = ee.Geometry.LineString(pts.geometry().coordinates())\n",
" feature = ee.Feature(line)\n",
" return feature.set('id', storm_id)\n",
"\n",
"lines = ee.FeatureCollection(storm_ids.map(create_line))"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Now we're ready to create the Pydeck layer. The `EarthEngineLayer` makes this simple. Just pass the Earth Engine object to the class."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"lines_layer = EarthEngineLayer(\n",
" lines,\n",
" {'color': 'red'},\n",
" id=\"tracks\",\n",
")"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"points_layer = EarthEngineLayer(\n",
" points,\n",
" {'color': 'black'},\n",
" id=\"points\",\n",
")"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Then just pass this layer to a `pydeck.Deck` instance, and call `.show()` to create a map:"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"view_state = pdk.ViewState(latitude=36, longitude=-53, zoom=3)\n",
"r = pdk.Deck(\n",
" layers=[points_layer, lines_layer], \n",
" initial_view_state=view_state\n",
")\n",
"r.show()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Vector\n",
"\n",
"Note that by default maps rendered with the `EarthEngineLayer` are rendered as _rasters_. That is, the vector geometries are converted into PNG images on Google's servers before downloading. deck.gl and pydeck excel at _vector_ rendering, where the geometries themselves are downloaded.\n",
"\n",
"Let's plot these layers again, but as a vector dataset, instead of a raster dataset"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"lines_layer = EarthEngineLayer(\n",
" lines,\n",
" # Download vector geometries\n",
" as_vector=True,\n",
" # GeoJsonLayer styling properties\n",
" get_line_color=[100, 100, 200],\n",
" getLineWidth=1000,\n",
" lineWidthMinPixels=3,\n",
" id=\"tracks\",\n",
")\n",
"points_layer = EarthEngineLayer(\n",
" points,\n",
" # Download vector geometries\n",
" as_vector=True,\n",
" # Properties from FeatureCollection to include in download\n",
" selectors=['name', 'max_wind_kts', 'datetime'],\n",
" # GeoJsonLayer styling properties\n",
" get_fill_color=[255, 125, 0, 180],\n",
" pointRadiusMinPixels=2,\n",
" # Point radius scales with wind speed\n",
" getRadius='properties.max_wind_kts * 500',\n",
" getLineColor=[255, 255, 255],\n",
" pointRadiusUnits='meters',\n",
" lineWidthMinPixels=0.5,\n",
" stroked=True,\n",
" id=\"points\",\n",
" pickable=True,\n",
" auto_highlight=True,\n",
")"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"view_state = pdk.ViewState(latitude=36, longitude=-53, zoom=3)\n",
"r = pdk.Deck(\n",
" layers=[lines_layer, points_layer], \n",
" initial_view_state=view_state\n",
")\n",
"r.show()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Event handling\n",
"\n",
"We can also use pydeck's event handling capabilities to render information about the feature that is clicked.\n",
"\n",
"**Important: pydeck's event handling is new in the 0.5.0 release.** To upgrade, run:\n",
"```\n",
"pip install -U 'pydeck>=0.5.0-beta.1'\n",
"```\n",
"\n",
"Here we render text information about a hurricane whenever a point on the map is clicked. This uses the `on_click` callback that Pydeck exposes."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"from ipywidgets import HTML\n",
"from dateutil.parser import parse\n",
"from datetime import datetime\n",
"\n",
"text = HTML(value='Click on a point')\n",
"\n",
"def create_text(payload):\n",
" properties = payload['data']['object']['properties']\n",
" name = properties['name']\n",
" wind_speed = properties['max_wind_kts']\n",
" time = parse(properties['datetime']).strftime('%c')\n",
" return f'Name: {name}
Max Wind Speed: {wind_speed} knots
Date: {time}'\n",
"\n",
"def on_click(widget_instance, payload):\n",
" text.value = create_text(payload)\n",
"\n",
"r.deck_widget.on_click(on_click)"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"scrolled": false
},
"outputs": [],
"source": [
"display(text)\n",
"r.show()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"After clicking on a point, you can also get the data underlying that point using the `selected_data` attribute, e.g.:"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"scrolled": false
},
"outputs": [],
"source": [
"r.selected_data"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Should show you something like:\n",
"\n",
"```json\n",
"[{'geometry': {'coordinates': [-73.09999665539969, 33.900003621748546],\n",
" 'type': 'Point'},\n",
" 'properties': {'datetime': '2017-09-26T18:00:00',\n",
" 'max_wind_kts': 65,\n",
" 'name': 'MARIA'},\n",
" 'type': 'Feature'}]\n",
"```"
]
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3",
"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.8.5"
}
},
"nbformat": 4,
"nbformat_minor": 4
}