Using Google Earth Engine to work with satellite imagery, We find the total precipitation for a coastal region in Northern Califnia as well as calculate the normalized difference vegetation index (NDVI), a simple graphical indicator that can be used to analyze remote sensing measurements. We use ERA5 Daily Aggregates data for precipitation and Landsat 8 data for NDVI.
This analysis was done in order to get familiar with the Google Earth Engine Python API. To run this code, an account with Google Earth Engine is required and student licences are available for free.
For this project I am choosing Arcata California. This is where I attended college, and I chose to go there for it’s rural location and its proximity to a variety of outdoor recreation areas. Arcata is a situated along the northern coast of California. The coastal zone surrounding the town includes beautiful coast redwoods (Sequoia sempervirens), Humboldt Bay, and low-lying areas around Humboldt Bay. Going east from the coastal zone, you quickly gain elevation and enter the southern Cascade mountain range and the Trinity Alps. This region contains the volcanoes Mt. Shasta and Mt Lassen, formed by the Cascadia subduction zone. Arcata’s climate falls into the temperate rainforest category, and is dominated by a rainy season and a dry season. Arcata’s population is also somewhat seasonal thanks to the college population leaving for the summer months.
Take a look through the Google Earth Engine data catalog.
I chose the the ERA5 Daily Aggregates dataset because my other choice Global Precipitation Measurement (GPM) v6 kept exceeding my user memory limit. I also investigated many other datasets (test_1 to test_5 files) but I did not really like how a few of them looked in comparison. I think the ERA5 Daily Aggregates suited my needs quite well and showed the scope of the storm event I was looking at quite well. It feels a bit like cheating, but I really did try to use other datasets. This dataset also gives reasonable spatial resolution.
# Import packages
import ee
import geemap
import pandas as pd
import matplotlib.pyplot as plt
import numpy as np
from IPython.display import Image
ee.Initialize()
= ee.ImageCollection('ECMWF/ERA5/DAILY')
gdat = gdat.propertyNames().getInfo()
gdat_prop = gdat.select('total_precipitation')
pr = pr.filter(ee.Filter.date('2019-02-24', '2019-02-28')).sum();
prdflt prdflt.getInfo()
{'type': 'Image',
'bands': [{'id': 'total_precipitation',
'data_type': {'type': 'PixelType', 'precision': 'double'},
'crs': 'EPSG:4326',
'crs_transform': [1, 0, 0, 0, 1, 0]}]}
Let’s do a new type of analysis on the data: the creation of a time series.
= -124
ar_lon = 40.8
ar_lat = ee.Geometry.Point(ar_lon, ar_lat)
ar_poi = 1000 # scale in m
scale = pr.getRegion(ar_poi, scale).getInfo()
ar_pr_ts = pd.DataFrame(ar_pr_ts)
df print(df)
0 1 2 3 4
0 id longitude latitude time total_precipitation
1 19790102 -123.99895 40.796989 284083200000 0
2 19790103 -123.99895 40.796989 284169600000 0
3 19790104 -123.99895 40.796989 284256000000 0.002583
4 19790105 -123.99895 40.796989 284342400000 0
... ... ... ... ... ...
15161 20200705 -123.99895 40.796989 1593907200000 0.000057
15162 20200706 -123.99895 40.796989 1593993600000 0.000298
15163 20200707 -123.99895 40.796989 1594080000000 0.000005
15164 20200708 -123.99895 40.796989 1594166400000 0.00015
15165 20200709 -123.99895 40.796989 1594252800000 0.000015
[15166 rows x 5 columns]
= df.loc[0] # Assign the first entry in the data frame to a variable called "headers"
headers print(headers)
0 id
1 longitude
2 latitude
3 time
4 total_precipitation
Name: 0, dtype: object
= pd.DataFrame(df.values[1:], columns=headers)
df # Make a new data frame out of the old one, but assigning the names we just retrieved as actual column headers
print(df)
0 id longitude latitude time total_precipitation
0 19790102 -123.99895 40.796989 284083200000 0
1 19790103 -123.99895 40.796989 284169600000 0
2 19790104 -123.99895 40.796989 284256000000 0.002583
3 19790105 -123.99895 40.796989 284342400000 0
4 19790106 -123.99895 40.796989 284428800000 0
... ... ... ... ... ...
15160 20200705 -123.99895 40.796989 1593907200000 0.000057
15161 20200706 -123.99895 40.796989 1593993600000 0.000298
15162 20200707 -123.99895 40.796989 1594080000000 0.000005
15163 20200708 -123.99895 40.796989 1594166400000 0.00015
15164 20200709 -123.99895 40.796989 1594252800000 0.000015
[15165 rows x 5 columns]
'datetime'] = pd.to_datetime(df['time'], unit = 'ms')
df[ df
id | longitude | latitude | time | total_precipitation | datetime | |
---|---|---|---|---|---|---|
0 | 19790102 | -123.99895 | 40.796989 | 284083200000 | 0 | 1979-01-02 |
1 | 19790103 | -123.99895 | 40.796989 | 284169600000 | 0 | 1979-01-03 |
2 | 19790104 | -123.99895 | 40.796989 | 284256000000 | 0.002583 | 1979-01-04 |
3 | 19790105 | -123.99895 | 40.796989 | 284342400000 | 0 | 1979-01-05 |
4 | 19790106 | -123.99895 | 40.796989 | 284428800000 | 0 | 1979-01-06 |
… | … | … | … | … | … | … |
15160 | 20200705 | -123.99895 | 40.796989 | 1593907200000 | 0.000057 | 2020-07-05 |
15161 | 20200706 | -123.99895 | 40.796989 | 1593993600000 | 0.000298 | 2020-07-06 |
15162 | 20200707 | -123.99895 | 40.796989 | 1594080000000 | 0.000005 | 2020-07-07 |
15163 | 20200708 | -123.99895 | 40.796989 | 1594166400000 | 0.00015 | 2020-07-08 |
15164 | 20200709 | -123.99895 | 40.796989 | 1594252800000 | 0.000015 | 2020-07-09 |
15165 rows × 6 columns
= (10, 6), dpi = 300) # create a new figure, set size and resolution (dpi)
plt.figure(figsize 'datetime'],df['total_precipitation']) # add data to the plot
plt.plot(df['Arcata Rainfall', fontsize=16)
plt.title('Date', fontsize=14)
plt.xlabel('Precip (m)', fontsize=14)
plt.ylabel(0, 0.12) plt.ylim(
(0.0, 0.12)
I chose to take the sum of the time period I examined in order to look at how much precipitation there was over the Mad River watershed. The sum of the values also lets you see the total amount of precipitation over the entire time period of the storm event. My map shows heavy precipitation over the Pacific, making landfall in northern California and Oregon, while staying to the north and largely avoiding Nevada.
# Base map
= geemap.Map(center=[40.8,-124], zoom=6)
Map # Min and Max
= {
VIS_PREC 'min':0,
'max':.02,
'palette': ['#FFFFFF', '#00FFFF', '#0080FF', '#DA00FF', '#FFA400', '#FF0000']
}# Add layer
'total precipitation',opacity=0.3)
Map.addLayer(prdflt, VIS_PREC,# Plot map
Map
Map(center=[40.8, -124], controls=(WidgetControl(options=['position', 'transparent_bg'], widget=HBox(children=…
= ee.Geometry.Point([-124.3652, 40.2922])
pt = pt.buffer(1600000)
roi # Create a URL to the styled image for a region around Mendocino County CA.
= prdflt.getThumbUrl({
url 'min': 0, 'max': .02, 'dimensions': 512, 'region': roi, 'opacity': 0.3,
'palette': ['#FFFFFF', '#00FFFF', '#0080FF', '#DA00FF', '#FFA400', '#FF0000']})
=url, embed=True, format = 'png') Image(url
The precipitation analysis discussed above provides useful context for identifying interesting weather events for a particular region. Now let’s see what (if any!) effect changes in weather patterns have had on the landscape, using Landsat imagery.
I was able to see a slight difference in the two images. It is unclear to me if the long term average includes more cloud cover and is therefore less bright/vivid, or if the rain increased the vegetation. If I had to guess I would say that there was not a very noticeable change because I chose an area that is already very wet and productive.
= ee.ImageCollection('LANDSAT/LC08/C01/T1_TOA')
land_8 = ee.Geometry.Point([-124, 40.8]) # Point corresponding to Arcata, CA
pt = land_8.filterBounds(pt)
land_8_pt = land_8_pt.filter('CLOUD_COVER < 20').mean();
land_8_least_cld = land_8_least_cld.select('B4')
red = land_8_least_cld.select('B5')
nir =(nir.subtract(red)).divide((nir.add(red))).rename('NDVI')
ndvi ndvi
<ee.image.Image at 0x22d22b1b280>
= {'min': -1,
ndviParams 'max': 1,
'palette': ['blue', 'white', 'green']
}
# Interactive only, no saved output
= geemap.Map(center=[40.8, -124], zoom=8)
Map_NDVI 'NDVI')
Map_NDVI.addLayer(ndvi, ndviParams, Map_NDVI
Map(center=[40.8, -124], controls=(WidgetControl(options=['position', 'transparent_bg'], widget=HBox(children=…
= ee.Geometry.Point([-124.3652, 40.2922])
pt = pt.buffer(120000)
roi # Create a URL to the styled image for a region around Mendocino County CA.
= ndvi.getThumbUrl({
url 'min': -1, 'max': 1, 'dimensions': 512, 'region': roi,
'palette': ['blue', 'white', 'green']})
=url, embed=True, format = 'png') Image(url
= land_8_pt.filter('CLOUD_COVER < 20')
land_8_least_cld_flt = land_8_least_cld_flt.filter(ee.Filter.date('2019-01-01', '2019-04-28')).mean();
land_8_least_cld_flt_dt = land_8_least_cld_flt_dt.select('B4')
red_2 = land_8_least_cld_flt_dt.select('B5')
nir_2 = (nir_2.subtract(red_2)).divide((nir_2.add(red_2))).rename('NDVI')
ndvi_2 ndvi_2
<ee.image.Image at 0x22d220f6820>
# Interactive only, no saved output
= geemap.Map(center=[40.8, -124], zoom=8)
Map_NDVI_flt 'NDVI')
Map_NDVI_flt.addLayer(ndvi_2, ndviParams, Map_NDVI_flt
Map(center=[40.8, -124], controls=(WidgetControl(options=['position', 'transparent_bg'], widget=HBox(children=…
= ee.Geometry.Point([-124.3652, 40.2922])
pt = pt.buffer(120000)
roi # Create a URL to the styled image for a region around Mendocino County CA.
= ndvi_2.getThumbUrl({
url 'min': -1, 'max': 1, 'dimensions': 512, 'region': roi,
'palette': ['blue', 'white', 'green']})
=url, embed=True, format = 'png') Image(url
For attribution, please cite this work as
Molitor (2021, Dec. 4). Cullen Molitor: Remote Sensing with Google Earth Engine. Retrieved from cullen-molitor.github.io/posts/2021-12-04-precipitation-and-ndvi/
BibTeX citation
@misc{molitor2021remote, author = {Molitor, Cullen}, title = {Cullen Molitor: Remote Sensing with Google Earth Engine}, url = {cullen-molitor.github.io/posts/2021-12-04-precipitation-and-ndvi/}, year = {2021} }