Last active
April 18, 2024 07:49
-
-
Save jdbcode/79814fb15c98327707617771772df9ab to your computer and use it in GitHub Desktop.
g4g22_ndvi_time_series_viz.ipynb
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": { | |
"provenance": [], | |
"authorship_tag": "ABX9TyNv3CDhb0lw/+cbcgdCgp5J", | |
"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/79814fb15c98327707617771772df9ab/g4g22_ndvi_time_series_viz.ipynb\" target=\"_parent\"><img src=\"https://colab.research.google.com/assets/colab-badge.svg\" alt=\"Open In Colab\"/></a>" | |
] | |
}, | |
{ | |
"cell_type": "markdown", | |
"source": [ | |
"Earth Engine setup" | |
], | |
"metadata": { | |
"id": "CCBMoKLV8sFp" | |
} | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": null, | |
"metadata": { | |
"id": "I_zWJ1bEimTm" | |
}, | |
"outputs": [], | |
"source": [ | |
"import ee\n", | |
"ee.Authenticate()\n", | |
"ee.Initialize(project='MY-PROJECT')" | |
] | |
}, | |
{ | |
"cell_type": "markdown", | |
"source": [ | |
"Import a tool to view thumbnail images" | |
], | |
"metadata": { | |
"id": "-WAB3ztm8wfe" | |
} | |
}, | |
{ | |
"cell_type": "code", | |
"source": [ | |
"from IPython.display import Image" | |
], | |
"metadata": { | |
"id": "Li-GGlvMj7dN" | |
}, | |
"execution_count": null, | |
"outputs": [] | |
}, | |
{ | |
"cell_type": "markdown", | |
"source": [ | |
"Get an image collection" | |
], | |
"metadata": { | |
"id": "LZE-Pddyucjk" | |
} | |
}, | |
{ | |
"cell_type": "code", | |
"source": [ | |
"col = (ee.ImageCollection('MODIS/006/MOD13A2')\n", | |
" .select('NDVI')\n", | |
" .filterDate('2021-01-01', '2022-01-01'))" | |
], | |
"metadata": { | |
"id": "GeeORvtRjlBh" | |
}, | |
"execution_count": null, | |
"outputs": [] | |
}, | |
{ | |
"cell_type": "markdown", | |
"source": [ | |
"Animate to see what we're working with" | |
], | |
"metadata": { | |
"id": "PEN4VK3BugHR" | |
} | |
}, | |
{ | |
"cell_type": "code", | |
"source": [ | |
"Image(url=col.getVideoThumbURL({\n", | |
" 'dimensions': 300,\n", | |
" 'region': ee.Geometry.BBox(-180, -89, 180, 89)\n", | |
"}))" | |
], | |
"metadata": { | |
"id": "ttHJEYvtj-c_" | |
}, | |
"execution_count": null, | |
"outputs": [] | |
}, | |
{ | |
"cell_type": "markdown", | |
"source": [ | |
"Hard to tell, data are outside of 8-bit range, get some stats for scaling" | |
], | |
"metadata": { | |
"id": "liFGmn40ul6Y" | |
} | |
}, | |
{ | |
"cell_type": "code", | |
"source": [ | |
"minMax = col.filterDate('2021-07-01', '2021-08-01').first().reduceRegion(**{\n", | |
" 'reducer': ee.Reducer.percentile([1, 99]),\n", | |
" 'scale': 10e3,\n", | |
" 'geometry': ee.Geometry.BBox(-180, -89, 180, 89)\n", | |
"})\n", | |
"print(minMax.getInfo())" | |
], | |
"metadata": { | |
"id": "Db0-w7Kqk-EU" | |
}, | |
"execution_count": null, | |
"outputs": [] | |
}, | |
{ | |
"cell_type": "markdown", | |
"source": [ | |
"Alright! now we can see some patterns" | |
], | |
"metadata": { | |
"id": "rfXrtsZXu4uY" | |
} | |
}, | |
{ | |
"cell_type": "code", | |
"source": [ | |
"Image(url=col.getVideoThumbURL({\n", | |
" 'dimensions': 300,\n", | |
" 'region': ee.Geometry.BBox(-180, -89, 180, 89),\n", | |
" 'min': 0,\n", | |
" 'max': 9000\n", | |
"}))" | |
], | |
"metadata": { | |
"id": "nVzP2VvnodRj" | |
}, | |
"execution_count": null, | |
"outputs": [] | |
}, | |
{ | |
"cell_type": "markdown", | |
"source": [ | |
"A little hard to interpret grayscale, add a self-evident color palette" | |
], | |
"metadata": { | |
"id": "Gl9t1nvEvAom" | |
} | |
}, | |
{ | |
"cell_type": "code", | |
"source": [ | |
"Image(url=col.getVideoThumbURL({\n", | |
" 'dimensions': 300,\n", | |
" 'region': ee.Geometry.BBox(-180, -89, 180, 89),\n", | |
" 'min': 0,\n", | |
" 'max': 9000,\n", | |
" 'palette': ['white', 'green']\n", | |
"}))" | |
], | |
"metadata": { | |
"id": "dU3Kvb5Lor3w" | |
}, | |
"execution_count": null, | |
"outputs": [] | |
}, | |
{ | |
"cell_type": "markdown", | |
"source": [ | |
"Good, that's easier to interpret. Not a big fan of the MODIS projection, let's try with World Equidistant Cylindrical" | |
], | |
"metadata": { | |
"id": "PodqmUkdvMZB" | |
} | |
}, | |
{ | |
"cell_type": "code", | |
"source": [ | |
"Image(url=col.getVideoThumbURL({\n", | |
" 'dimensions': 300,\n", | |
" 'region': ee.Geometry.BBox(-180, -89, 180, 89),\n", | |
" 'min': 0,\n", | |
" 'max': 9000,\n", | |
" 'palette': ['white', 'green'],\n", | |
" 'crs': 'EPSG:4087'\n", | |
"}))" | |
], | |
"metadata": { | |
"id": "chlXW48kpI54" | |
}, | |
"execution_count": null, | |
"outputs": [] | |
}, | |
{ | |
"cell_type": "markdown", | |
"source": [ | |
"Let's focus on Africa" | |
], | |
"metadata": { | |
"id": "ab9yhbucwkR-" | |
} | |
}, | |
{ | |
"cell_type": "code", | |
"source": [ | |
"Image(url=col.getVideoThumbURL({\n", | |
" 'dimensions': 300,\n", | |
" 'region': ee.Geometry.BBox(-18.7, -36.2, 52.2, 38.1),\n", | |
" 'min': 0,\n", | |
" 'max': 9000,\n", | |
" 'palette': ['white', 'green'],\n", | |
" 'crs': 'EPSG:4087'\n", | |
"}))" | |
], | |
"metadata": { | |
"id": "4MrfvDSvxBr0" | |
}, | |
"execution_count": null, | |
"outputs": [] | |
}, | |
{ | |
"cell_type": "markdown", | |
"source": [ | |
"Now we're on to something, make bigger" | |
], | |
"metadata": { | |
"id": "frIYMs51zljv" | |
} | |
}, | |
{ | |
"cell_type": "code", | |
"source": [ | |
"Image(url=col.getVideoThumbURL({\n", | |
" 'dimensions': 500,\n", | |
" 'region': ee.Geometry.BBox(-18.7, -36.2, 52.2, 38.1),\n", | |
" 'min': 0,\n", | |
" 'max': 9000,\n", | |
" 'palette': ['white', 'green'],\n", | |
" 'crs': 'EPSG:4087'\n", | |
"}))" | |
], | |
"metadata": { | |
"id": "eKBKC_CszlE1" | |
}, | |
"execution_count": null, | |
"outputs": [] | |
}, | |
{ | |
"cell_type": "markdown", | |
"source": [ | |
"What a neat intra-annual pattern, let's increase the frame rate" | |
], | |
"metadata": { | |
"id": "DLLtGruHz1WW" | |
} | |
}, | |
{ | |
"cell_type": "code", | |
"source": [ | |
"Image(url=col.getVideoThumbURL({\n", | |
" 'dimensions': 500,\n", | |
" 'region': ee.Geometry.BBox(-18.7, -36.2, 52.2, 38.1),\n", | |
" 'min': 0,\n", | |
" 'max': 9000,\n", | |
" 'palette': ['white', 'green'],\n", | |
" 'crs': 'EPSG:4087',\n", | |
" 'framesPerSecond': 12\n", | |
"}))" | |
], | |
"metadata": { | |
"id": "6n6tzJr5zujH" | |
}, | |
"execution_count": null, | |
"outputs": [] | |
}, | |
{ | |
"cell_type": "markdown", | |
"source": [ | |
"We can do a better palette" | |
], | |
"metadata": { | |
"id": "n9Nw6Hn80FaA" | |
} | |
}, | |
{ | |
"cell_type": "code", | |
"source": [ | |
"Image(url=col.getVideoThumbURL({\n", | |
" 'dimensions': 500,\n", | |
" 'region': ee.Geometry.BBox(-18.7, -36.2, 52.2, 38.1),\n", | |
" 'min': 0,\n", | |
" 'max': 9000,\n", | |
" 'palette': [\n", | |
" 'FFFFFF', 'CE7E45', 'DF923D', 'F1B555', 'FCD163', '99B718', '74A901',\n", | |
" '66A000', '529400', '3E8601', '207401', '056201', '004C00', '023B01',\n", | |
" '012E01', '011D01', '011301'\n", | |
" ],\n", | |
" 'crs': 'EPSG:4087',\n", | |
" 'framesPerSecond': 12\n", | |
"}))" | |
], | |
"metadata": { | |
"id": "89Pc1HjN0KHu" | |
}, | |
"execution_count": null, | |
"outputs": [] | |
}, | |
{ | |
"cell_type": "markdown", | |
"source": [ | |
"Awesome! but there is a lot of noise, probably from clouds/masking, these are 16-day composites. Let's create median inter-annual composites for each 16-day period to clean it up." | |
], | |
"metadata": { | |
"id": "yP3-NN-80ajD" | |
} | |
}, | |
{ | |
"cell_type": "markdown", | |
"source": [ | |
"First step is to add a property to all images that we can join by - we can use \"day of year\"." | |
], | |
"metadata": { | |
"id": "Hm-nRVpD1FUz" | |
} | |
}, | |
{ | |
"cell_type": "code", | |
"source": [ | |
"full_col = ee.ImageCollection('MODIS/006/MOD13A2').select('NDVI')\n", | |
"\n", | |
"def add_doy_prop(img):\n", | |
" doy = ee.Date(img.get('system:time_start')).getRelative('day', 'year');\n", | |
" return img.set('doy', doy)\n", | |
"\n", | |
"full_col = full_col.map(add_doy_prop)" | |
], | |
"metadata": { | |
"id": "LXRWzPdQ1Oej" | |
}, | |
"execution_count": null, | |
"outputs": [] | |
}, | |
{ | |
"cell_type": "markdown", | |
"source": [ | |
"Perform a \"saveAll\" join to group all images from the same day of year into a list" | |
], | |
"metadata": { | |
"id": "YP8oXP7t2EaU" | |
} | |
}, | |
{ | |
"cell_type": "code", | |
"source": [ | |
"distinct_doy = full_col.filterDate('2021-01-01', '2022-01-01')\n", | |
"filter = ee.Filter.equals(**{'leftField': 'doy', 'rightField': 'doy'})\n", | |
"join = ee.Join.saveAll('doy_matches')\n", | |
"join_col = ee.Join.saveAll('doy_matches').apply(distinct_doy, full_col, filter)" | |
], | |
"metadata": { | |
"id": "TEbjWeD12QnF" | |
}, | |
"execution_count": null, | |
"outputs": [] | |
}, | |
{ | |
"cell_type": "markdown", | |
"source": [ | |
"Here is what a list of same-day images looks like" | |
], | |
"metadata": { | |
"id": "BPVXcKD94Kvy" | |
} | |
}, | |
{ | |
"cell_type": "code", | |
"source": [ | |
"img_list = join_col.first().get('doy_matches').getInfo()\n", | |
"for img in img_list: \n", | |
" print(img['id'])" | |
], | |
"metadata": { | |
"id": "j4RecUGU4QeN" | |
}, | |
"execution_count": null, | |
"outputs": [] | |
}, | |
{ | |
"cell_type": "markdown", | |
"source": [ | |
"We need to turn these lists into image collections and compute the per-pixel median" | |
], | |
"metadata": { | |
"id": "TMuTmptI5Zfb" | |
} | |
}, | |
{ | |
"cell_type": "code", | |
"source": [ | |
"def median_composite(feature):\n", | |
" doy_col = ee.ImageCollection.fromImages(feature.get('doy_matches'))\n", | |
" return doy_col.median()\n", | |
"\n", | |
"median_col = ee.ImageCollection(join_col.map(median_composite))" | |
], | |
"metadata": { | |
"id": "xr0tyP835Jl7" | |
}, | |
"execution_count": null, | |
"outputs": [] | |
}, | |
{ | |
"cell_type": "markdown", | |
"source": [ | |
"Let's see what the animation looks like now" | |
], | |
"metadata": { | |
"id": "jn63JA2x6eYB" | |
} | |
}, | |
{ | |
"cell_type": "code", | |
"source": [ | |
"Image(url=median_col.getVideoThumbURL({\n", | |
" 'dimensions': 500,\n", | |
" 'region': ee.Geometry.BBox(-18.7, -36.2, 52.2, 38.1),\n", | |
" 'min': 0,\n", | |
" 'max': 9000,\n", | |
" 'palette': [\n", | |
" 'FFFFFF', 'CE7E45', 'DF923D', 'F1B555', 'FCD163', '99B718', '74A901',\n", | |
" '66A000', '529400', '3E8601', '207401', '056201', '004C00', '023B01',\n", | |
" '012E01', '011D01', '011301'\n", | |
" ],\n", | |
" 'crs': 'EPSG:4087',\n", | |
" 'framesPerSecond': 12\n", | |
"}))" | |
], | |
"metadata": { | |
"id": "myfZEeSf6EKL" | |
}, | |
"execution_count": null, | |
"outputs": [] | |
}, | |
{ | |
"cell_type": "markdown", | |
"source": [ | |
"Smoooooth! Looking good, but I think adding hillshade will add some character" | |
], | |
"metadata": { | |
"id": "bv8BaKkr6jm3" | |
} | |
}, | |
{ | |
"cell_type": "code", | |
"source": [ | |
"hillshade = ee.Terrain.hillshade(ee.Image('MERIT/DEM/v1_0_3').multiply(50))\n", | |
"\n", | |
"ndvi_vis = {\n", | |
" 'min': 0,\n", | |
" 'max': 9000,\n", | |
" 'palette': [\n", | |
" 'FFFFFF', 'CE7E45', 'DF923D', 'F1B555', 'FCD163', '99B718', '74A901',\n", | |
" '66A000', '529400', '3E8601', '207401', '056201', '004C00', '023B01',\n", | |
" '012E01', '011D01', '011301'\n", | |
" ],\n", | |
" 'opacity': 0.7\n", | |
"}\n", | |
"\n", | |
"def add_hillshade(img):\n", | |
" return hillshade.blend(img.visualize(**ndvi_vis))\n", | |
"\n", | |
"vis_col = median_col.map(add_hillshade)\n", | |
"\n", | |
"Image(url=vis_col.getVideoThumbURL({\n", | |
" 'dimensions': 500,\n", | |
" 'region': ee.Geometry.BBox(-18.7, -36.2, 52.2, 38.1),\n", | |
" 'crs': 'EPSG:4087',\n", | |
" 'framesPerSecond': 12\n", | |
"}))" | |
], | |
"metadata": { | |
"id": "OtRk5nk37NJg" | |
}, | |
"execution_count": null, | |
"outputs": [] | |
} | |
] | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment