Skip to content

Instantly share code, notes, and snippets.

@jdbcode
Last active April 8, 2024 23:22
Show Gist options
  • Save jdbcode/5650cf896ea84cf30b654536cfadbdfe to your computer and use it in GitHub Desktop.
Save jdbcode/5650cf896ea84cf30b654536cfadbdfe to your computer and use it in GitHub Desktop.
ee_goes_16_eclipse_2024.ipynb
Display the source blob
Display the rendered blob
Raw
{
"nbformat": 4,
"nbformat_minor": 0,
"metadata": {
"colab": {
"provenance": [],
"authorship_tag": "ABX9TyNR0opLDesHFSHQmfUxryFa",
"include_colab_link": true
},
"kernelspec": {
"name": "python3",
"display_name": "Python 3"
},
"language_info": {
"name": "python"
}
},
"cells": [
{
"cell_type": "markdown",
"metadata": {
"id": "view-in-github",
"colab_type": "text"
},
"source": [
"<a href=\"https://colab.research.google.com/gist/jdbcode/5650cf896ea84cf30b654536cfadbdfe/ee_goes_16_eclipse_2024.ipynb\" target=\"_parent\"><img src=\"https://colab.research.google.com/assets/colab-badge.svg\" alt=\"Open In Colab\"/></a>"
]
},
{
"cell_type": "markdown",
"source": [
"# Video of the 2024 solar eclipse as seen from the GOES-16 satellite\n",
"\n",
"This Colab notebook creates a time series animation of the 2024 solar eclipse from GOES-16 imagery using Google Earth Engine. A Google cloud project that is Earth Engine-enabled is required ([learn more](https://developers.google.com/earth-engine/cloud/earthengine_cloud_project_setup#get-access-to-earth-engine))."
],
"metadata": {
"id": "xdLIpggqRLXE"
}
},
{
"cell_type": "code",
"source": [
"#@title Set these parameters\n",
"\n",
"ee_project = 'my-cloud-project' # @param {type:\"string\"}\n",
"goes_16_id = 'NOAA/GOES/16/MCMIPF' # @param {type:\"string\"}\n",
"start_time = '2024-04-08T16:00:00' # @param {type:\"string\"}\n",
"end_time = '2024-04-08T20:30:00' # @param {type:\"string\"}"
],
"metadata": {
"cellView": "form",
"id": "q8AqIfnWHAcw"
},
"execution_count": null,
"outputs": []
},
{
"cell_type": "code",
"source": [
"#@title Import libraries and initialize Earth Engine\n",
"\n",
"!pip install -q retry\n",
"!apt-get -y install ffmpeg\n",
"\n",
"import ee\n",
"ee.Authenticate()\n",
"ee.Initialize(project=ee_project, opt_url='https://earthengine-highvolume.googleapis.com')\n",
"\n",
"import logging\n",
"import multiprocessing\n",
"import requests\n",
"import shutil\n",
"import glob\n",
"from PIL import Image as pil_image\n",
"from retry import retry\n",
"from IPython.display import Image\n",
"from IPython.display import Video"
],
"metadata": {
"cellView": "form",
"id": "OlzBeskqn04y"
},
"execution_count": null,
"outputs": []
},
{
"cell_type": "code",
"source": [
"#@title Functions to prepare GOES imagery\n",
"\n",
"# Applies scaling factors.\n",
"def apply_scale_and_offset(img):\n",
" def get_factor_img(factor_names):\n",
" factor_list = img.toDictionary().select(factor_names).values()\n",
" return ee.Image.constant(factor_list)\n",
"\n",
" scale_img = get_factor_img(['CMI_C.._scale'])\n",
" offset_img = get_factor_img(['CMI_C.._offset'])\n",
" scaled = img.select('CMI_C..').multiply(scale_img).add(offset_img)\n",
" return img.addBands(srcImg=scaled, overwrite=True)\n",
"\n",
"\n",
"# Adds a synthetic green band.\n",
"def add_green_band(img):\n",
" green = img.expression(\n",
" expression='CMI_GREEN = 0.45 * red + 0.10 * nir + 0.45 * blue',\n",
" opt_map={\n",
" 'blue': img.select('CMI_C01'),\n",
" 'red': img.select('CMI_C02'),\n",
" 'nir': img.select('CMI_C03')\n",
" })\n",
" return img.addBands(green)\n",
"\n",
"\n",
"# Scales select bands for visualization.\n",
"def scale_for_vis(img):\n",
" return (img.select(['CMI_C01', 'CMI_GREEN', 'CMI_C02', 'CMI_C03', 'CMI_C05'])\n",
" .log10()\n",
" .interpolate([-1.6, 0.176], [0, 1], 'clamp')\n",
" .unmask(0)\n",
" .set('system:time_start', img.get('system:time_start')))\n",
"\n",
"# Wraps previous functions.\n",
"def process_for_vis(img):\n",
" return scale_for_vis(add_green_band(apply_scale_and_offset(img)))\n",
"\n",
"\n",
"# Paint GOES image onto EPSG:3857 for easier region definition.\n",
"def change_proj(img):\n",
" goes_16_proj = ee.Projection('PROJCS[\"unnamed\", ' +\n",
" 'GEOGCS[\"unknown\", ' +\n",
" ' DATUM[\"unknown\", ' +\n",
" ' SPHEROID[\"Spheroid\", 6378137.0, 298.2572221]], ' +\n",
" ' PRIMEM[\"Greenwich\", 0.0], ' +\n",
" ' UNIT[\"degree\", 0.017453292519943295], ' +\n",
" ' AXIS[\"Longitude\", EAST], ' +\n",
" ' AXIS[\"Latitude\", NORTH]], ' +\n",
" 'PROJECTION[\"GEOS\"], ' +\n",
" 'PARAMETER[\"central_meridian\", -89.5], ' +\n",
" 'PARAMETER[\"satellite_height\", 35786023.0], ' +\n",
" 'PARAMETER[\"false_easting\", 0.0], ' +\n",
" 'PARAMETER[\"false_northing\", 0.0], ' +\n",
" 'PARAMETER[\"sweep\", 0.0], ' +\n",
" 'UNIT[\"m\", 1.0], ' +\n",
" 'AXIS[\"x\", EAST], ' +\n",
" 'AXIS[\"y\", NORTH]]'\n",
" );\n",
"\n",
" return img.changeProj(goes_16_proj, 'EPSG:3857').set(\n",
" 'system:start_time', img.get('system:start_time'))"
],
"metadata": {
"cellView": "form",
"id": "88RxFq-voqM_"
},
"execution_count": null,
"outputs": []
},
{
"cell_type": "code",
"source": [
"#@title Process GOES imagery\n",
"\n",
"IC = ee.ImageCollection(goes_16_id).filterDate(\n",
" start_time, end_time)\n",
"IC = IC.map(process_for_vis).map(change_proj)\n",
"timestamps = IC.aggregate_array('system:time_start').getInfo()\n",
"print('N images:', len(timestamps))"
],
"metadata": {
"cellView": "form",
"id": "9jtCLI2ep_Hy"
},
"execution_count": null,
"outputs": []
},
{
"cell_type": "code",
"source": [
"#@title Test visualization and extent parameters\n",
"\n",
"geom = ee.Geometry.Polygon(\n",
" [[[-40.03174642343608, 45.57152286631389],\n",
" [-40.03174642343608, 0.15896988330457215],\n",
" [33.62059732656392, 0.15896988330457215],\n",
" [33.62059732656392, 45.57152286631389]]], None, False)\n",
"\n",
"THUMB_PARAMS = {\n",
" 'bands': ['CMI_C05', 'CMI_C03', 'CMI_GREEN'],\n",
" 'min': 0,\n",
" 'max': 0.8,\n",
" 'gamma': 0.8,\n",
" 'dimensions': 1000,\n",
" 'region': geom,\n",
" 'crs': 'EPSG:3857',\n",
" 'format': 'png'\n",
"};\n",
"\n",
"ee_image = IC.first()\n",
"Image(url = ee_image.getThumbURL(THUMB_PARAMS))"
],
"metadata": {
"cellView": "form",
"id": "zKz-PGIEqw61"
},
"execution_count": null,
"outputs": []
},
{
"cell_type": "code",
"source": [
"#@title Download images\n",
"\n",
"@retry(tries=10, delay=1, backoff=2)\n",
"def get_result(index, timestamp):\n",
" img = IC.filter(ee.Filter.eq('system:time_start', timestamp)).first()\n",
"\n",
" url = img.getThumbURL(THUMB_PARAMS)\n",
"\n",
" r = requests.get(url, stream=True)\n",
" if r.status_code != 200:\n",
" raise r.raise_for_status()\n",
"\n",
" filename = 'img_%d.png' % timestamp\n",
" with open(filename, 'wb') as out_file:\n",
" shutil.copyfileobj(r.raw, out_file)\n",
" print(\"Done: \", timestamp)\n",
"\n",
"!rm *.png\n",
"pool = multiprocessing.Pool(4)\n",
"pool.starmap(get_result, enumerate(timestamps))\n",
"pool.close()\n",
"pool.join()"
],
"metadata": {
"cellView": "form",
"id": "QgR-pWWc1E3p"
},
"execution_count": null,
"outputs": []
},
{
"cell_type": "code",
"source": [
"#@title Create an MP4 video\n",
"\n",
"image_list = sorted(glob.glob('/content/*.png'))\n",
"\n",
"image = pil_image.open(image_list[0])\n",
"width, height = image.size\n",
"if width % 2 != 0:\n",
" width -= 1\n",
"if height % 2 != 0:\n",
" height -= 1\n",
"scale = f\"scale={width}:{height}\"\n",
"\n",
"with open('image_list.txt', 'w') as f:\n",
" for file in image_list:\n",
" f.write(f\"file '{file}'\\n\")\n",
"\n",
"!rm -f out.mp4\n",
"!ffmpeg -f concat -safe 0 -r 6 -i image_list.txt -vf {scale} -c:v libx264 -pix_fmt yuv420p out.mp4"
],
"metadata": {
"cellView": "form",
"id": "LcU6pLL6KEd-"
},
"execution_count": null,
"outputs": []
},
{
"cell_type": "code",
"source": [
"#@title Watch the video\n",
"\n",
"Video('out.mp4', embed=True)"
],
"metadata": {
"id": "esR1FD_u9d5L"
},
"execution_count": null,
"outputs": []
}
]
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment