{ "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 }