{ "cells": [ { "cell_type": "markdown", "metadata": { "tags": [] }, "source": [ "# Interactive plots with Ipyleaflet" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Overview:\n", "1. Interactively display images from remote map servers with ipyleaflet\n", "2. Overlay gridded data on the interactive map " ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "
\n", " Older Enivronment Note that we are using an older Python 3 environment in this notebook! The ipyleaflet package is currently incompatible with the version of Jupyterlab that we have been using this semester.\n", "
" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Prerequisites\n", "\n", "| Concepts | Importance | Notes |\n", "| --- | --- | --- |\n", "| Contextily | |\n", "| Xarray | |\n", "\n", "* **Time to learn**: 20 minutes\n", "***" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Imports" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from datetime import datetime, timedelta\n", "from ipyleaflet import Map, TileLayer, basemaps, basemap_to_tiles\n", "from ipyleaflet.velocity import Velocity\n", "import xarray as xr" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Interactively display images from remote map servers with ipyleaflet" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "While we've made some nice looking maps so far in the course, they are all *static* images ... i.e., they can only be viewed ... not interacted with. Most of us have browsed interactive map-based websites, such as Google Maps, where we can use our mouse or touch-enabled screen to move around, zoom in or out, or otherwise intereact with the plot. The **Javascript** programming language is what makes this interactivity possible. Here, we will use a Python package, [ipyleaflet](https://ipyleaflet.readthedocs.io/en/latest/), as a \"bridge\" to Javascript." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "
\n", " Note: The \"leaflet\" in ipyleaflet refers to the Leaflet open-source Javascript library.\n", "
" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Use a satellite composite as the base image.\n", "Now, specify an image tile server and product, using the same sources as those provided by Contextily. Center the map and specify a zoom level." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Create time strings" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "We'll need to select a date and time, and then pass in date/time objects or strings to various functions below; their format will be function-specific." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "timeObj = datetime(2023,10,11,0)\n", "timeStr1 = timeObj.strftime(\"%Y-%m-%d\")\n", "timeStr2 = timeObj.strftime(\"%Y%m%d_%H%M\")" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "timeStr2" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "m = Map(\n", " basemap=basemap_to_tiles(basemaps.NASAGIBS.ModisTerraTrueColorCR, timeStr1),\n", " center=(42.204793, -74.121558),\n", " zoom=4\n", ")\n", "\n", "m" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Add a background cartographic layer.\n", "We'll use ipyleaflet's `add_layer` method. We simply add another remote tile server. The default `opacity` is 1.0, so reduce that a bit so we still see the satellite layer." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "basemapLayer = basemap_to_tiles(basemaps.CartoDB.Positron,opacity=0.5)\n", "m.add_layer(basemapLayer)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Interact with the dynamic map" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Zoom in and out with the +/- buttons to see both layers dynamically update." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Overlay gridded data on the interactive map" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "We will display an animated wind velocity flow field, a la https://earth.nullschool.net or https://www.windy.com/. " ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "
\n", " Note: ipyleaflet can read in Xarray DataArray objects so we will exploit that here.\n", "
" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Read in a dataset based on the 0.5-degree resolution GFS on a given date and time." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "ds = xr.open_dataset('https://thredds.ucar.edu/thredds/dodsC/grib/NCEP/GFS/Global_0p5deg_ana/GFS_Global_0p5deg_ana_'+ timeStr2 +'.grib2')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Examine the dataset" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "ds" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Specify an analysis time, and then create `DataArray` objects that are subset for the specified time and the 20m AGL level (this is the lowest level in the dataset)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "u = ds['u-component_of_wind_height_above_ground'].sel(time=timeObj,height_above_ground1=20)\n", "v = ds['v-component_of_wind_height_above_ground'].sel(time=timeObj,height_above_ground1=20)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Create a Dataset that contains just the wind components" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "ipyleaflet's `Velocity` method produces an animated streamline effect. It accepts an Xarray dataset that contains just two arrays ... specifically, the u- and v- components of the wind vector we have chosen for visualization.\n", "\n", "(Perhaps there is a more efficient way of doing this, but the following cell will create a new Xarray Dataset that consists of just two arrays ... u and v.)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# First create the Dataset out of the 'u' Dataarray\n", "dsWind = u.to_dataset()\n", "\n", "# Append v to the Dataset\n", "dsWind['v'] = v\n", "\n", "# Rename the u DataArray to 'u'\n", "name_dict = {'u-component_of_wind_height_above_ground':'u'}\n", "dsWind = dsWind.rename_vars(name_dict)\n", "\n", "# Verify that the Dataset looks ok\n", "dsWind" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Create the animation" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Center it, specify a zoom level, and select a background map; create and add a velocity layer from the Dataset that contains u and v." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "center = (40.0, -70.0)\n", "zoom = 6\n", "m = Map(center=center, zoom=zoom, interpolation='nearest', basemap=basemaps.CartoDB.DarkMatter)\n", "\n", "\n", "display_options = {\n", " 'velocityType': 'Global Wind',\n", " 'displayPosition': 'bottomleft',\n", " 'displayEmptyString': 'No wind data'\n", "}\n", "wind = Velocity(data=dsWind,\n", " zonal_speed='u',\n", " meridional_speed='v',\n", " latitude_dimension='lat',\n", " longitude_dimension='lon',\n", " velocity_scale=0.01,\n", " max_velocity=120,\n", " display_options=display_options)\n", "m.add_layer(wind)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Display the map." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "m" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "If you move your mouse/touchpad, you will see direction and speed at the nearest gridpoint displayed at the bottom left of the frame." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "
\n", " Things to try:
\n", "
  1. Create a velocity map using gridded data from another source ... such as RDA or another datasource from the Unidata THREDDS Server.
  2. \n", "
  3. Make this notebook dynamic in terms of date so it will always provide a recent (e.g. one day ago) visualization.
" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "---\n", "## Summary\n", "* Ipyleaflet provides Javascript-enabled interactivity in a Jupyter notebook.\n", "* Ipyleaflet's `Velocity` function creates a flow-vector field that can be layered onto a basemap.\n", "* The `Velocity` function accepts an Xarray dataset that contains the u- and v- wind components.\n", "\n", "### What's Next?\n", "We'll continue to explore interactive visualizations, next with the **Holoviz** suite of packages." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## References:\n", "1. [ipyleaflet](https://ipyleaflet.readthedocs.io/en/latest/)\n", "1. [Leaflet](https://leafletjs.com)\n", "1. [ipyleaflet Map](https://ipyleaflet.readthedocs.io/en/latest/api_reference/map.html)\n", "1. [ipyleaflet Basemaps](https://ipyleaflet.readthedocs.io/en/latest/api_reference/basemaps.html)\n", "1. [ipyleaflet tile_layer](https://ipyleaflet.readthedocs.io/en/latest/api_reference/tile_layer.html)\n", "1. [Remote basemap providers](https://contextily.readthedocs.io/en/latest/providers_deepdive.html)\n", "1. [ipyleaflet Velocity](https://ipyleaflet.readthedocs.io/en/latest/api_reference/velocity.html)" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3 August 2022 Environment", "language": "python", "name": "aug22" }, "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.10.5" } }, "nbformat": 4, "nbformat_minor": 4 }