Part 14: Spectral and Temporal Analysis (2)
Chapter 2: Time Series Analysis of NDVI for Sample Points
2.1 Input Manually Sampled Points
# Define sample points for phenology analysis
sugarcane = ee.Feature(
geom=ee.Geometry.MultiPoint([[107.566527, 22.333124], [107.552301, 22.337652], [107.589958, 22.332473]]),
opt_properties={'LandCover': 'Sugarcane'}
)
forest = ee.Feature(
ee.Geometry.MultiPoint([[107.550482, 22.332677], [107.58648, 22.32705]]),
opt_properties={'LandCover': 'Eucalyptus'}
)
# Combine samples into a feature collection
samples = ee.FeatureCollection([sugarcane.buffer(30), forest.buffer(30)])
2.2 Display Sample Points
# Define display styles for each land cover type
class_styles = ee.Dictionary({
'Eucalyptus': {'color': '#00FF00', 'pointSize': 5, 'pointShape': 'circle'},
'Sugarcane': {'color': '#EEB422', 'pointSize': 6, 'pointShape': 'square'}
})
# Apply styles to the samples
samples = samples.map(lambda f: f.set('style', class_styles.get(f.get('LandCover'))))
# Style the FeatureCollection according to each feature's "style" property
samples_VisCustom = samples.style(
styleProperty='style',
neighborhood=8 # maximum "pointSize" + "width" among features
)
# Display the samples on the map
Map = geemap.Map(basemap='HYBRID')
Map.addLayer(samples_VisCustom, None, 'samples_all_VisCustom')
Map.centerObject(samples, 14)
Map
2.3 Get Landsat Images for the Time Range
imgs = pyeepkg.Landsat().get_readyDat_Landsat5789_C2L2SR(
bound=samples.geometry().buffer(5000).bounds(),
yearS=2018,
yearE=2021,
clip_with_bound=True
)
2.4 Moving Window Smoothing Function
# Define the smoothing function
def avg_smoothing_imgs(imgs, days_window_radius, bands):
""" Smoothing the imageCollection using mean value
Args:
imgs (ee.ImageCollection): ImageCollection to be processed
days_window_radius (int): Days window to smooth the time series
bands (list): Bands to be smoothed, e.g. ['LSWI']
Returns:
ee.ImageCollection: Smoothed time series
"""
join = ee.Join.saveAll(matchesKey='images')
days_millis = days_window_radius * 24 * 60 * 60 * 1000
filter_days_differ = ee.Filter.maxDifference(
difference=days_millis,
leftField='system:time_start',
rightField='system:time_start'
)
smoothingJoin = join.apply(
primary=imgs,
secondary=imgs,
condition=filter_days_differ
)
band_types = imgs.first().select(bands).bandTypes()
new_bnames = [band + 'm' for band in bands]
def avg_imgs(image):
col = ee.ImageCollection.fromImages(image.get('images'))
return image.addBands(col.select(bands).mean().cast(band_types).rename(new_bnames))
return ee.ImageCollection(smoothingJoin).map(avg_imgs)
2.5 Calculate NDVI and Apply Smoothing
# Function to add NDVI band to images
def addNDVI(image):
return image.addBands(image.normalizedDifference(['nir', 'red']).rename('NDVI'))
# Apply NDVI calculation and smoothing
with_ndvi = imgs.map(addNDVI)
with_ndvim = avg_smoothing_imgs(with_ndvi, 32, ['NDVI'])
2.6 Extract Sample Pixel Values from All Images
def get_time_series(img):
img = ee.Image(img)
dat_regions = img.reduceRegions(
collection=samples,
reducer=ee.Reducer.mean(),
scale=30
)
return dat_regions.map(lambda f: f.set({'t': img.get('system:time_start')}))
2.7 Iteratively Extract Sample Pixel Values
fc_dat = with_ndvim.sort('CLOUD_COVER_LAND').select(['NDVI', 'NDVIm']).map(get_time_series)
lst_fc_dat = ee.FeatureCollection(fc_dat).toList(500).map(lambda l: ee.FeatureCollection(l).toList(10))
fc_dat_ = ee.FeatureCollection(lst_fc_dat.flatten())
df_samples = geemap.ee_to_pandas(fc_dat_)
df_samples.drop('style', axis=1, inplace=True)
df_samples
df_samples.to_excel('../Data/ndvim.xlsx')
2.8 Add Image Timestamp Field
# Convert milliseconds to datetime
df_samples['dt'] = pd.to_datetime(df_samples['t'], unit='ms')
df_samples.sort_values('t')
2.9 Plot NDVI Time Series for Two Tree Species
import seaborn as sns
import matplotlib.pyplot as plt
fig, ax = plt.subplots(figsize=(16, 4))
sns.set_theme(style='ticks', font_scale=1.4)
# Plot original NDVI data
g = sns.lineplot(x='dt', y='NDVI', hue='LandCover', data=df_samples, style='LandCover', markers=["o", "d"], palette=['#54b345', '#BB9727'], sizes=20)
# Modify plot parameters
g.set(ylabel='NDVI', ylim=(0, 1), xlabel='')
g.spines['right'].set_visible(False)
g.spines['top'].set_visible(False)
ax.fill_between(['2020-01-01', '2020-05-01'], 0, 1, facecolor='#ff2500', alpha=0.1)
g.get_legend().set_title('')
g.grid(ls='-.')
fig.savefig('../Figures/Pheno.tif', dpi=400, bbox_inches='tight')
2.10 Plot Original and Smoothed NDVI Time Series for Two Tree Species
from matplotlib.lines import Line2D
fig, ax = plt.subplots(figsize=(16, 4))
sns.set_theme(style='ticks', font_scale=1.5)
# Plot smoothed NDVI time series
g = sns.lineplot(x='dt', y='NDVIm', hue='LandCover', data=df_samples, style='LandCover', markers=["o", "d"], palette=['#F08080', '#A2CD5A'], sizes=10)
# Plot original NDVI time series
g = sns.scatterplot(x='dt', y='NDVI', hue='LandCover', data=df_samples, style='LandCover', markers=["o", "d"], palette=['#FF0000', '#008B00'], sizes=20)
# Modify plot parameters
g.set(ylabel='NDVI', ylim=(0, 1), xlabel='')
g.spines['right'].set_visible(False)
g.spines['top'].set_visible(False)
ax.fill_between(['2018-01-01', '2018-06-01'], 0, 1, facecolor='#87CEFA', alpha=0.2)
ax.fill_between(['2019-01-01', '2019-06-15'], 0, 1, facecolor='#87CEFA', alpha=0.2)
ax.fill_between(['2020-01-01', '2020-05-01'], 0, 1, facecolor='#87CEFA', alpha=0.2)
g.grid(ls='-.')
# Custom legend
legend_elements = [
Line2D([], [], marker='o', color='#FF0000', label='$Sugarcane_{original-value}$', ls=''),
Line2D([], [], marker='d', color='#008B00', label='$Eucalyptus_{original-value}$', ls=''),
Line2D([], [], marker='o', color='#F08080', label='$Sugarcane_{moving-avg}$', ls='-'),
Line2D([], [], marker='d', color='#A2CD5A', label='$Eucalyptus_{moving-avg}$', ls='-'),
]
g.legend(ncol=4, handles=legend_elements, prop={'size': 14})
fig.savefig('../Figures/Pheno_with_Fit.tif', dpi=400, bbox_inches='tight')
Last updated