Last active
March 19, 2021 18:33
-
-
Save jdbcode/48bbdd7f1855234b7a751e619c43f7b0 to your computer and use it in GitHub Desktop.
[EE Animation] GFS precipitable water 2021 atmospheric river
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
{ | |
"nbformat": 4, | |
"nbformat_minor": 0, | |
"metadata": { | |
"colab": { | |
"name": "ee_filmstrip_gif_precipitable_water.ipynb", | |
"provenance": [], | |
"collapsed_sections": [], | |
"include_colab_link": true | |
}, | |
"kernelspec": { | |
"name": "python3", | |
"display_name": "Python 3" | |
} | |
}, | |
"cells": [ | |
{ | |
"cell_type": "markdown", | |
"metadata": { | |
"id": "view-in-github", | |
"colab_type": "text" | |
}, | |
"source": [ | |
"<a href=\"https://colab.research.google.com/gist/jdbcode/48bbdd7f1855234b7a751e619c43f7b0/ee_filmstrip_gif_precipitable_water.ipynb\" target=\"_parent\"><img src=\"https://colab.research.google.com/assets/colab-badge.svg\" alt=\"Open In Colab\"/></a>" | |
] | |
}, | |
{ | |
"cell_type": "markdown", | |
"metadata": { | |
"id": "IgbXrpiFdqH5" | |
}, | |
"source": [ | |
"This notebook makes an animated GIF image from an Earth Engine time series image collection. A filmstrip is created that is chopped into frames. The frames are annotated with the image date according to text position and style you define. The resulting frames are combined into an animated GIF file using the image magick utility. The final step optionally compresses the GIF." | |
] | |
}, | |
{ | |
"cell_type": "markdown", | |
"metadata": { | |
"id": "dFq7l4K3GiYb" | |
}, | |
"source": [ | |
"Set up Earth Engine" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"metadata": { | |
"id": "-sh6uAlUqokF" | |
}, | |
"source": [ | |
"import ee\r\n", | |
"ee.Authenticate()\r\n", | |
"ee.Initialize()" | |
], | |
"execution_count": null, | |
"outputs": [] | |
}, | |
{ | |
"cell_type": "markdown", | |
"metadata": { | |
"id": "WGZIJB_PGmL_" | |
}, | |
"source": [ | |
"Develop the image collection\r\n", | |
"\r\n", | |
"Note that setting the 'date' property in the `visImg` function is important for annotating the frames." | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"metadata": { | |
"id": "tkleH9M1qntL" | |
}, | |
"source": [ | |
"region = ee.Geometry.Polygon(\r\n", | |
" [[[137.815625, 62.97367101641437],\r\n", | |
" [137.815625, -12.040987259736767],\r\n", | |
" [310.4328125, -12.040987259736767],\r\n", | |
" [310.4328125, 62.97367101641437]]], None, False)\r\n", | |
"\r\n", | |
"waterVapor = (ee.ImageCollection('NOAA/GFS0P25')\r\n", | |
" .filter(ee.Filter.date('2021-01-27', '2021-01-28'))\r\n", | |
" .limit(120)\r\n", | |
" .select('precipitable_water_entire_atmosphere'))\r\n", | |
"\r\n", | |
"landWater = ee.Image('MERIT/DEM/v1_0_3').mask();\r\n", | |
"\r\n", | |
"minWaterVaporVal = 15\r\n", | |
"\r\n", | |
"landWaterVisParams = {'min': 0, 'max': 1, 'palette': ['D3D3D3', '383838']}\r\n", | |
"\r\n", | |
"waterVaporVisParams = {\r\n", | |
" 'min': minWaterVaporVal,\r\n", | |
" 'max': 50,\r\n", | |
" 'palette': ['c6dbef', '9ecae1', '6baed6', '4292c6', '2171b5', '084594'],\r\n", | |
" 'opacity': 0.65\r\n", | |
"}\r\n", | |
"\r\n", | |
"shadowVisParams = {'min': 1, 'max': 1, 'palette': '808080', 'opacity': 0.5}\r\n", | |
"\r\n", | |
"def visImg(img):\r\n", | |
" date = ee.Date(img.get('forecast_time')).format()\r\n", | |
" img = (img.resample('bicubic'))\r\n", | |
" \r\n", | |
" mask = img.gt(minWaterVaporVal)\r\n", | |
" height = img.multiply(10000)\r\n", | |
" hillshade = ee.Terrain.hillshade(height, 310, 45).visualize()\r\n", | |
" shadow = (ee.Terrain.hillShadow(\r\n", | |
" height.updateMask(mask).unmask(0), 310, 70, 100, False).Not().selfMask())\r\n", | |
" \r\n", | |
" landWaterVis = landWater.visualize(**landWaterVisParams)\r\n", | |
" waterVaporVis = img.visualize(**waterVaporVisParams)\r\n", | |
" shadowVis = shadow.visualize(**shadowVisParams)\r\n", | |
" \r\n", | |
" return (landWaterVis.blend(\r\n", | |
" hillshade.blend(waterVaporVis).updateMask(mask))\r\n", | |
" .blend(shadowVis).set('date', date))\r\n", | |
"\r\n", | |
"visCol = waterVapor.map(visImg)\r\n", | |
"\r\n", | |
"dates = visCol.aggregate_array('date').getInfo()" | |
], | |
"execution_count": null, | |
"outputs": [] | |
}, | |
{ | |
"cell_type": "markdown", | |
"metadata": { | |
"id": "fbBAQi48Gy0V" | |
}, | |
"source": [ | |
"Generate a thumbnail filmstrip URL from the image collection" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"metadata": { | |
"id": "pYW-m73lqvKM" | |
}, | |
"source": [ | |
"filmstrip_params = {\r\n", | |
" 'dimensions': 475,\r\n", | |
" 'region': region,\r\n", | |
" 'crs': 'EPSG:3832'\r\n", | |
"}\r\n", | |
"\r\n", | |
"filmstrip_url = visCol.getFilmstripThumbURL(filmstrip_params)" | |
], | |
"execution_count": null, | |
"outputs": [] | |
}, | |
{ | |
"cell_type": "markdown", | |
"metadata": { | |
"id": "N-oDhptSC4jM" | |
}, | |
"source": [ | |
"Get some packages and modules for dealing with images" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"metadata": { | |
"id": "vaJ4rEDmC30t" | |
}, | |
"source": [ | |
"import urllib.request\r\n", | |
"from PIL import Image, ImageDraw, ImageFont\r\n", | |
"import glob\r\n", | |
"from IPython.display import Image as ImageGIF" | |
], | |
"execution_count": null, | |
"outputs": [] | |
}, | |
{ | |
"cell_type": "markdown", | |
"metadata": { | |
"id": "NkGqFx9vDBJ9" | |
}, | |
"source": [ | |
"Fetch the image collection thumbnail filmstrip" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"metadata": { | |
"id": "mkj3mHcUsvuU" | |
}, | |
"source": [ | |
"filmstrip_name = 'filmstrip.png'\r\n", | |
"urllib.request.urlretrieve(filmstrip_url, filmstrip_name)" | |
], | |
"execution_count": null, | |
"outputs": [] | |
}, | |
{ | |
"cell_type": "markdown", | |
"metadata": { | |
"id": "nq_D30L6HFuo" | |
}, | |
"source": [ | |
"Get some info about the filmstrip" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"metadata": { | |
"id": "2BmLoCX3w5SZ" | |
}, | |
"source": [ | |
"filmstrip_im = Image.open(filmstrip_name)\r\n", | |
"width, height = filmstrip_im.size\r\n", | |
"crop_interval = height / len(dates)\r\n", | |
"print(width, height, crop_interval)" | |
], | |
"execution_count": null, | |
"outputs": [] | |
}, | |
{ | |
"cell_type": "markdown", | |
"metadata": { | |
"id": "u2Ons-0e2aA_" | |
}, | |
"source": [ | |
"Preview the first frame" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"metadata": { | |
"id": "9c4rq1wB2VvV" | |
}, | |
"source": [ | |
"frame_0 = filmstrip_im.crop((0, 0, width, crop_interval))\r\n", | |
"print(frame_0)\r\n", | |
"display(frame_0)" | |
], | |
"execution_count": null, | |
"outputs": [] | |
}, | |
{ | |
"cell_type": "markdown", | |
"metadata": { | |
"id": "OAHaPElT5pgM" | |
}, | |
"source": [ | |
"Get a font (Consolas)" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"metadata": { | |
"id": "CMAxzPjV3-lh" | |
}, | |
"source": [ | |
"font_url = 'https://github.com/tsenart/sight/raw/master/fonts/Consolas.ttf'\r\n", | |
"font_name = 'Consolas.ttf'\r\n", | |
"urllib.request.urlretrieve(font_url, font_name)" | |
], | |
"execution_count": null, | |
"outputs": [] | |
}, | |
{ | |
"cell_type": "markdown", | |
"metadata": { | |
"id": "sEt2Uo1z2Bmf" | |
}, | |
"source": [ | |
"Figure out annotation position and size" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"metadata": { | |
"id": "912ruKnNyOg4" | |
}, | |
"source": [ | |
"right = 235\r\n", | |
"down = 230\r\n", | |
"font_size = 22\r\n", | |
"fill = '#000'\r\n", | |
"\r\n", | |
"font = ImageFont.truetype(font_name, font_size)\r\n", | |
"anno_test = filmstrip_im.crop((0, 0, width, crop_interval))\r\n", | |
"ImageDraw.Draw(anno_test).text((right, down), dates[0], fill=fill, font=font)\r\n", | |
"display(anno_test)" | |
], | |
"execution_count": null, | |
"outputs": [] | |
}, | |
{ | |
"cell_type": "markdown", | |
"metadata": { | |
"id": "UvluIrJD5--S" | |
}, | |
"source": [ | |
"Loop through the frames to crop and annotate" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"metadata": { | |
"id": "rkDV9e5O6BWa" | |
}, | |
"source": [ | |
"!rm *_frame.png\r\n", | |
"for frame, date in enumerate(dates):\r\n", | |
" frame_im = filmstrip_im.crop(\r\n", | |
" (0, frame*crop_interval, width, (frame+1)*crop_interval))\r\n", | |
" ImageDraw.Draw(frame_im).text((right, down), date, fill=fill, font=font)\r\n", | |
" frame_im.save(str(frame).zfill(3)+'_frame.png')" | |
], | |
"execution_count": null, | |
"outputs": [] | |
}, | |
{ | |
"cell_type": "markdown", | |
"metadata": { | |
"id": "QFCy_qyV8YvK" | |
}, | |
"source": [ | |
"Did it work?" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"metadata": { | |
"id": "KFjXhHOz7zKy" | |
}, | |
"source": [ | |
"print('Found these frames:\\n')\r\n", | |
"!ls *_frame.png\r\n", | |
"print('\\nFinal frame:\\n')\r\n", | |
"Image.open(str(frame).zfill(3)+'_frame.png')" | |
], | |
"execution_count": null, | |
"outputs": [] | |
}, | |
{ | |
"cell_type": "markdown", | |
"metadata": { | |
"id": "Qx4-uXekCoeM" | |
}, | |
"source": [ | |
"Make the GIF using ImageMagick" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"metadata": { | |
"id": "8mSOTDXDTECQ" | |
}, | |
"source": [ | |
"!apt install imagemagick" | |
], | |
"execution_count": null, | |
"outputs": [] | |
}, | |
{ | |
"cell_type": "code", | |
"metadata": { | |
"id": "oaTEIE9XVMe3" | |
}, | |
"source": [ | |
"import os" | |
], | |
"execution_count": null, | |
"outputs": [] | |
}, | |
{ | |
"cell_type": "code", | |
"metadata": { | |
"id": "Tfy8FZZWXS9l" | |
}, | |
"source": [ | |
"gif_name = 'filmstrip_magick.gif'\r\n", | |
"delay = 15\r\n", | |
"\r\n", | |
"cmd = f'convert -loop 0 -delay {delay} *_frame.png {gif_name}'\r\n", | |
"print(cmd)\r\n", | |
"os.system(cmd)" | |
], | |
"execution_count": null, | |
"outputs": [] | |
}, | |
{ | |
"cell_type": "code", | |
"metadata": { | |
"id": "h4MhCZmsUNE4" | |
}, | |
"source": [ | |
"with open('filmstrip_magick.gif','rb') as f:\r\n", | |
" display(ImageGIF(data=f.read(), format='png'))" | |
], | |
"execution_count": null, | |
"outputs": [] | |
}, | |
{ | |
"cell_type": "markdown", | |
"metadata": { | |
"id": "FAtE0nXZa3X2" | |
}, | |
"source": [ | |
"Compress the GIF" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"metadata": { | |
"id": "xFBWenEqa29n" | |
}, | |
"source": [ | |
"in_gif = 'filmstrip_magick.gif'\r\n", | |
"out_gif = 'filmstrip_magick_compress.gif'\r\n", | |
"fuzz = 5 # percent\r\n", | |
"\r\n", | |
"cmd = f'convert -fuzz {fuzz}% -layers Optimize {in_gif} {out_gif}'\r\n", | |
"print(cmd)\r\n", | |
"os.system(cmd)" | |
], | |
"execution_count": null, | |
"outputs": [] | |
}, | |
{ | |
"cell_type": "markdown", | |
"metadata": { | |
"id": "b8v3r_UPfuOl" | |
}, | |
"source": [ | |
"Preview the compressed version" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"metadata": { | |
"id": "1owj6DL3bgCZ" | |
}, | |
"source": [ | |
"with open('filmstrip_magick.gif','rb') as f:\r\n", | |
" display(ImageGIF(data=f.read(), format='png'))" | |
], | |
"execution_count": null, | |
"outputs": [] | |
}, | |
{ | |
"cell_type": "markdown", | |
"metadata": { | |
"id": "ybSM5tcruVwo" | |
}, | |
"source": [ | |
"Download the animation" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"metadata": { | |
"id": "4NdlyxBJuYXh" | |
}, | |
"source": [ | |
"from google.colab import files\r\n", | |
"files.download(out_gif) " | |
], | |
"execution_count": null, | |
"outputs": [] | |
} | |
] | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment