import ee
import geemap
Analyzing Land Surface Temperature (LST) with Landsat 8 Data in Google Earth Engine
Note: this code is adapted from Muhammad Ridho’s excellent Medium tutorial. I have converted his JavaScript code to Python using the geemap
package, with only minor changes. I am quoting his writeup almost verbatim.
This notebook is exclusively for educational purposes. All credit for the work goes to Muhammad.
Land Surface Temperature (LST) is a crucial environmental parameter with applications ranging from climate change monitoring to urban planning and agriculture management. This summary provides a concise overview of how to calculate and analyze LST using Landsat 8 imagery and the powerful toolset of Google Earth Engine (GEE).
Landsat 8, a satellite operated by the United States Geological Survey (USGS) and NASA, provides a wealth of multispectral data. Its thermal infrared band, known as the Thermal Infrared Sensor (TIRS), is particularly valuable for estimating LST. Google Earth Engine is a cloud-based platform for geospatial analysis, making it an ideal choice for processing Landsat data and deriving LST information.
Step 1: Accessing Google Earth Engine
1.1 Sign up for an Earth Engine account if you don’t have one already.
1.2 Load the ee
and geemap
libraries in Google Colab.
1.3 Authenticate your Earth Engine account in Google Colab.
ee.Authenticate()
1.4 Initialize your session.
='remotesensing-musa6500') ee.Initialize(project
Step 2: Define the Area of Interest (AOI)
In this part of the script, an Area of Interest (AOI) is defined using the ee.FeatureCollection
function. The AOI is obtained from the “FAO/GAUL_SIMPLIFIED_500m/2015/level1” dataset, specifically filtering for the administrative division (ADM1_NAME) named ‘Daerah Istimewa Yogyakarta.’ This effectively creates a region of interest for subsequent analysis.
= ee.FeatureCollection("FAO/GAUL_SIMPLIFIED_500m/2015/level1") \
aoi filter(ee.Filter.eq('ADM1_NAME', 'Daerah Istimewa Yogyakarta')) .
Step 3: Import Landsat 8 Data
3.1 Define the start and end dates.
These two variables, startDate and endDate, can be used in various geospatial analyses, particularly in Google Earth Engine, to specify a specific time range for data retrieval, processing, or visualization. In this case, the time period defined by these dates spans from June 1, 2023, to September 21, 2023.
from datetime import datetime
= datetime(2023, 6, 1)
startDate = datetime(2023, 9, 21)
endDate
# Format dates into strings that Earth Engine expects ("YYYY-MM-DD")
= startDate.strftime('%Y-%m-%d')
startDate= endDate.strftime('%Y-%m-%d') endDate_str
3.2 Apply scaling factors
Applying a scale factor involves adjusting pixel values in satellite imagery to ensure they represent physical properties accurately. For example, it’s used to convert digital numbers into physical units, like radiance or reflectance, making the data more meaningful for analysis.
# Function to apply scaling factors.
def apply_scale_factors(image):
# Scale and offset values for optical bands
= image.select('SR_B.').multiply(0.0000275).add(-0.2)
optical_bands
# Scale and offset values for thermal bands
= image.select('ST_B.*').multiply(0.00341802).add(149.0)
thermal_bands
# Add scaled bands to the original image
return image.addBands(optical_bands, None, True) \
None, True) .addBands(thermal_bands,
3.3 Apply cloud masking
Cloud masking is a process to identify and remove cloud-affected pixels from satellite imagery. It’s essential for ensuring that the analysis focuses on clear and cloud-free areas, minimizing the impact of clouds and their shadows on the results.
# Function to mask clouds and cloud shadows in Landsat 8 imagery
def cloud_mask(image):
# Define cloud shadow and cloud bitmask (Bits 3 and 5)
= 1 << 3
cloud_shadow_bit_mask = 1 << 5
cloud_bit_mask
# Select the Quality Assessment (QA) band for pixel quality information
= image.select('QA_PIXEL')
qa
# Create a binary mask to identify clear conditions (both cloud and cloud shadow bits set to 0)
= qa.bitwiseAnd(cloud_shadow_bit_mask).eq(0) \
mask 0))
.And(qa.bitwiseAnd(cloud_bit_mask).eq(
# Update the original image, masking out cloud and cloud shadow-affected pixels
return image.updateMask(mask)
3.4 Import Landsat 8 imagery
Importing images involves fetching satellite or remote sensing data into a geospatial analysis platform like Google Earth Engine. After data import, creating visualization involves defining how the data should be visually represented, and specifying parameters like color bands and brightness levels for effective interpretation and analysis.
# Import and preprocess Landsat 8 imagery
= ee.ImageCollection("LANDSAT/LC08/C02/T1_L2") \
image \
.filterBounds(aoi) \
.filterDate(startDate, endDate) map(apply_scale_factors) \
.map(cloud_mask) \
.\
.median() .clip(aoi)
Step 4: Calculate NDVI, Proportion of Vegetation, and Emissivity
Step 4.1 Calculate NDVI
NDVI (Normalized Difference Vegetation Index) is a widely used vegetation index in remote sensing and environmental science. It quantifies the presence and health of vegetation based on the reflectance of visible and near-infrared light. NDVI values typically range from -1 to 1, with higher values indicating healthier or denser vegetation.
NDVI is calculated using the following formula:
NDVI=(NIR+Red)/(NIR−Red)
- NIR (Near-Infrared) is the reflectance in the near-infrared part of the electromagnetic spectrum (typically Landsat band 5 or similar).
- Red is the reflectance in the red part of the spectrum (typically Landsat band 4 or similar).
# Calculate Normalized Difference Vegetation Index (NDVI)
= image.normalizedDifference(['SR_B5', 'SR_B4']).rename('NDVI') ndvi
Step 4.2. Calculate Minimum and Maximum NDVI Values
The minimum (min) and maximum (max) values of the Normalized Difference Vegetation Index (NDVI) are used to calculate the proportion of vegetation cover (PV) in the region of interest.
Minimum NDVI Calculation (ndviMin
):
- The script uses the
reduceRegion
method to calculate the minimum NDVI value within the specified AOI. - The reducer parameter is set to
ee.Reducer.min()
, indicating that we want to find the minimum value. - The
geometry
parameter is set toaoi
, which defines the area of interest. - The
scale
parameter sets the pixel size for the analysis to 30 meters. - The
maxPixels
parameter specifies the maximum number of pixels to include in the calculation (1e9 represents one billion pixels, a large number to cover extensive areas).
Maximum NDVI Calculation (ndviMax
):
- Similarly, this part of the script calculates the maximum NDVI value within the same AOI.
- It uses
ee.Reducer.max()
as the reducer to find the maximum value.
# Calculate the minimum and maximum NDVI values within the AOI
= ndvi.reduceRegion(
ndvi_min =ee.Reducer.min(),
reducer=aoi,
geometry=30,
scale=1e9
maxPixels'NDVI').getInfo()
).get(
= ndvi.reduceRegion(
ndvi_max =ee.Reducer.max(),
reducer=aoi,
geometry=30,
scale=1e9
maxPixels'NDVI').getInfo() ).get(
Step 4.3. Calculate Proportion of Vegetation (PV) and Emissivity (EM)
The proportion of vegetation cover is calculated using the following formula:
Proportion of Vegetation (PV) = (NDVI — NDVI_min) / (NDVI_max — NDVI_min)
Where:
- NDVI represents the pixel’s NDVI value.
- NDVI_min is the minimum NDVI value specified.
- NDVI_max is the maximum NDVI value specified.
The Proportion of Vegetation (PV) is a metric used to quantify the relative abundance of vegetation within a specified area by analyzing Normalized Difference Vegetation Index (NDVI) values. It provides valuable insights into land cover and ecosystem health, with higher PV values indicating a greater presence of vegetation. On the other hand, Emissivity (EM) is a critical parameter for accurate Land Surface Temperature (LST) calculations. In the provided code, EM is computed as a function of PV, reflecting how efficiently a surface emits thermal radiation. EM values near 1.0 are typical for natural surfaces like soil and vegetation, while lower values are often associated with water bodies or urban areas. Both PV and EM play essential roles in remote sensing and environmental studies, contributing to a more comprehensive understanding of land characteristics and thermal behavior.
# Convert NDVI_min and NDVI_max to ee.Number for operations
= ee.Number(ndvi_min)
ndvi_min = ee.Number(ndvi_max)
ndvi_max
# Fraction of Vegetation (FV) Calculation
# Formula: ((NDVI - NDVI_min) / (NDVI_max - NDVI_min))^2
= ndvi.subtract(ndvi_min).divide(ndvi_max.subtract(ndvi_min)).pow(2).rename('FV')
fv
# Emissivity Calculation
# Formula: 0.004 * FV + 0.986
= fv.multiply(0.004).add(0.986).rename('EM') em
Step 5: Estimate Land Surface Temperature (LST)
Select Thermal Band: In this section, the script selects the thermal band (Band 10) from the input image and renames it as ‘thermal’. This band contains thermal infrared information.
# Select Thermal Band (Band 10) and Rename It
= image.select('ST_B10').rename('thermal') thermal
The script calculates Land Surface Temperature (LST) using Planck’s Law. The formula is provided as a mathematical expression. It involves using the thermal band (TB) and emissivity (em) values to compute LST.
# Land Surface Temperature (LST) Calculation
# Formula: (TB / (1 + (λ * (TB / 1.438)) * log(em))) - 273.15
= thermal.expression(
lst '(TB / (1 + (0.00115 * (TB / 1.438)) * log(em))) - 273.15',
{'TB': thermal.select('thermal'), # Thermal band as the temperature in Kelvin
'em': em # Emissivity
}'LST Yogyakarta 2023') ).rename(
Step 6: Map Results with Custom Title and Legend
Now, we can combine our results with custom visualizations to view them on the map. (This can be done step-by-step throughout the script or at the end.)
First, we initialize a map using the geemap
package. We add our AOI to it as a black line using an empty palette and center the map on our AOI.
= geemap.Map() # initialize a map
Map 'AOI - Yogyakarta')
Map.addLayer(aoi, {}, 10) # center the map on the aoi Map.centerObject(aoi,
Next, we add our true color imagery to the map.
# Define visualization parameters for True Color imagery (bands 4, 3, and 2)
= {
visualization 'bands': ['SR_B4', 'SR_B3', 'SR_B2'],
'min': 0.0,
'max': 0.15,
}
# Add the processed image to the map with the specified visualization
'True Color 432') Map.addLayer(image, visualization,
We add the NDVI layer, too.
# Define NDVI Visualization Parameters
= {
ndviPalette 'min': -1,
'max': 1,
'palette': ['blue', 'white', 'green']
}
'NDVI Yogyakarta') Map.addLayer(ndvi, ndviPalette,
Finally, we add the LST layer with a custom pallete and visualize the map with a custom title and legend.
# Visualization parameters for LST
= {
lst_vis_params 'min': 18.47,
'max': 42.86,
'palette': [
'040274', '040281', '0502a3', '0502b8', '0502ce', '0502e6',
'0602ff', '235cb1', '307ef3', '269db1', '30c8e2', '32d3ef',
'3be285', '3ff38f', '86e26f', '3ae237', 'b5e22e', 'd6e21f',
'fff705', 'ffd611', 'ffb613', 'ff8b13', 'ff6e08', 'ff500d',
'ff0000', 'de0101', 'c21301', 'a71001', '911003'
]
}
# Add the LST layer to the map with custom visualization parameters
'Land Surface Temperature 2023')
Map.addLayer(lst, lst_vis_params,
# Define the color palette for the legend (updated to include the missing color '210300')
= [
palette '040274', '040281', '0502a3', '0502b8', '0502ce', '0502e6',
'0602ff', '235cb1', '307ef3', '269db1', '30c8e2', '32d3ef',
'3be285', '3ff38f', '86e26f', '3ae237', 'b5e22e', 'd6e21f',
'fff705', 'ffd611', 'ffb613', 'ff8b13', 'ff6e08', 'ff500d',
'ff0000', 'de0101', 'c21301', 'a71001', '911003', '210300'
]
# Convert hex colors to the format required by the add_legend method
= ['#' + color for color in palette]
legend_colors
# Calculate the legend keys based on the number of colors
# Assuming minLST and maxLST represent the range of your data
= 15, 45
minLST, maxLST = [str(round(minLST + i * (maxLST - minLST) / (len(palette) - 1), 2)) for i in range(len(palette))]
legend_keys
# Add the custom legend to the map
=legend_keys, colors=legend_colors, position='bottomright')
Map.add_legend(keys
# Set the center of the map (Example coordinates for Yogyakarta)
110.3668, -7.8032, 10)
Map.set_center(
# Display the map
Map