Skip to content

Instantly share code, notes, and snippets.

@jdbcode
Last active April 18, 2024 07:49
Show Gist options
  • Save jdbcode/79814fb15c98327707617771772df9ab to your computer and use it in GitHub Desktop.
Save jdbcode/79814fb15c98327707617771772df9ab to your computer and use it in GitHub Desktop.
g4g22_ndvi_time_series_viz.ipynb
Display the source blob
Display the rendered blob
Raw
{
"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