GEDI_L2A Search and Visualize
Authors: Nathan Thomas (GSFC/UMD), Sumant Jha (MSFC/USRA), Samuel Ayers (UAH)
Date: March 9, 2023
Description: This tutorial aims to provide information and code to help users get started working with the Global Ecosystem Dynamics Investigation (GEDI) Level 2A (GEDI_L2A) product using the MAAP. We will search for the data within NASA’s Common Metadata Repository (CMR) and plot the orbit path.
Run This Notebook
To access and run this tutorial within MAAP’s Algorithm Development Environment (ADE), please refer to the “Getting started with the MAAP” section of our documentation.
Disclaimer: it is highly recommended to run a tutorial within MAAP’s ADE, which already includes packages specific to MAAP, such as maap-py. Running the tutorial outside of the MAAP ADE may lead to errors.
About the Data
GEDI L2A Elevation and Height Metrics Data Global Footprint Level
This dataset provides Global Ecosystem Dynamics Investigation (GEDI) Level 2A (L2A) data, which has the purpose of providing waveform interpretation and extracted products from the GEDI L1B waveforms. These products include ground elevation, canopy top height, and relative height (RH) metrics. GEDI is attached to the International Space Station (ISS) and collects data globally between 51.6° N and 51.6° S latitudes at the highest resolution and densest sampling of any light detection and ranging (lidar) instrument in orbit to date; specifically, GEDI L2A data has a spatial resolution of 25m. (Source: GEDI L2A Dataset Landing Page)
Additional Resources
Importing and Installing Packages
We will start by importing the packages which will allow us to search for, access, explore, and visualize GEDI_L2A product data.
Note: This Jupyter notebook utilizes the folium
package. If you do not have this installed, uncomment the line and run the following code block.
[ ]:
# !pip install folium
For this tutorial, we will import boto3
, folium
, h5py
, pandas
, and exists
from os.path
as shown in the following codeblock.
[2]:
# Install packages
import boto3
import folium
import h5py
import os
import pandas as pd
from maap.maap import MAAP
from os.path import exists
Search for GEDI_L2A Data
To search for data from the GEDI_L2A product using NASA’s CMR, we invoke the MAAP
constructor, setting the maap_host
argument to 'api.maap-project.org'
.
[ ]:
# Invoke the MAAP using the MAAP host argument
maap = MAAP(maap_host='api.maap-project.org')
Now we can use the searchGranule
function to find granule data within the collection, using the collection short name “GEDI02_A”. Note that we can use searchGranule
’s cmr_host
argument to specify a CMR instance external to MAAP and the readable_granule_name
argument to find granules matching either granule UR or producer granule id (please see the API documentation for more information). In order to download
data from NASA’s CMR, we will set a variable to the first result from the results
we obtained.
[4]:
# Search for granule data using CMR host name and collection short name, and readable_granule_name arguments
results = maap.searchGranule(
cmr_host='cmr.earthdata.nasa.gov',
short_name='GEDI02_A',
readable_granule_name = "GEDI02_A_2021272190541_O15849_04_T03030_02_003_02_V002.h5")
We’ll go ahead and create a new data directory (if it doesn’t already exist), and download the first result into it.
[12]:
# set data directory
dataDir = './data'
# check if directory exists -> if directory doesn't exist, directory is created
if not os.path.exists(dataDir):
os.mkdir(dataDir)
# Download first result
filename = results[0].getData(dataDir)
If desired, the print
function can be utilized to see the file name and directory.
[13]:
# Print file directory
print(filename)
./data/GEDI02_A_2021272190541_O15849_04_T03030_02_003_02_V002.h5
Explore
Now that we have downloaded the data, let’s look into what it contains.
[14]:
# Create variable containing info from the file we downloaded
gediL2A = h5py.File(filename, 'r')
GEDI_L2A data has data for 8 different beams. Let’s create a list of beam names to help explore the data.
[15]:
# Create list of beam names
beamNames = [g for g in gediL2A.keys() if g.startswith('BEAM')]
beamNames
[15]:
['BEAM0000',
'BEAM0001',
'BEAM0010',
'BEAM0011',
'BEAM0101',
'BEAM0110',
'BEAM1000',
'BEAM1011']
Now let’s explore the information available for one of the beams (in this case ‘BEAM0000’).
[16]:
# Get list of objects in the data pertaining to 'BEAM0000'
beam = beamNames[0]
gediL2A_objs = []
gediL2A.visit(gediL2A_objs.append)
gediSDS = [o for o in gediL2A_objs if isinstance(gediL2A[o], h5py.Dataset)]
[i for i in gediSDS if beam in i][0:20]
[16]:
['BEAM0000/ancillary/l2a_alg_count',
'BEAM0000/beam',
'BEAM0000/channel',
'BEAM0000/degrade_flag',
'BEAM0000/delta_time',
'BEAM0000/digital_elevation_model',
'BEAM0000/digital_elevation_model_srtm',
'BEAM0000/elev_highestreturn',
'BEAM0000/elev_lowestmode',
'BEAM0000/elevation_bias_flag',
'BEAM0000/elevation_bin0_error',
'BEAM0000/energy_total',
'BEAM0000/geolocation/elev_highestreturn_a1',
'BEAM0000/geolocation/elev_highestreturn_a2',
'BEAM0000/geolocation/elev_highestreturn_a3',
'BEAM0000/geolocation/elev_highestreturn_a4',
'BEAM0000/geolocation/elev_highestreturn_a5',
'BEAM0000/geolocation/elev_highestreturn_a6',
'BEAM0000/geolocation/elev_lowestmode_a1',
'BEAM0000/geolocation/elev_lowestmode_a2']
Visualize
Now that we’ve seen the various labels within the /BEAM0000 group, let’s use this information to visualize the GEDI orbit path for our scenes. To start, we shall get samples for various shots, the beam number, longitude, latitude, and quality flags. We can use these samples to create and display a pandas
dataframe.
[19]:
# Set variables for shot, beam number, longitude, latitude, and quality flag samples
lonSample, latSample, shotSample, qualitySample, beamSample = [], [], [], [], []
lats = gediL2A[f'{beamNames[0]}/lat_lowestmode'][()]
lons = gediL2A[f'{beamNames[0]}/lon_lowestmode'][()]
shots = gediL2A[f'{beamNames[0]}/shot_number'][()]
quality = gediL2A[f'{beamNames[0]}/quality_flag'][()]
for i in range(len(shots)):
if i % 100 == 0:
shotSample.append(str(shots[i]))
lonSample.append(lons[i])
latSample.append(lats[i])
qualitySample.append(quality[i])
beamSample.append(beamNames[0])
# Create a pandas dataframe containing the sample information
latslons = pd.DataFrame({'Beam': beamSample, 'Shot Number': shotSample, 'Longitude': lonSample,
'Latitude': latSample, 'Quality Flag': qualitySample})
# Display the dataframe
latslons
[19]:
Beam | Shot Number | Longitude | Latitude | Quality Flag | |
---|---|---|---|---|---|
0 | BEAM0000 | 158490000400502383 | 52.337698 | 0.599011 | 0 |
1 | BEAM0000 | 158490000400502483 | 52.367554 | 0.556129 | 0 |
2 | BEAM0000 | 158490000400502583 | 52.396932 | 0.514800 | 0 |
3 | BEAM0000 | 158490000400502683 | 52.426548 | 0.472746 | 0 |
4 | BEAM0000 | 158490000400502783 | 52.456248 | 0.430695 | 0 |
... | ... | ... | ... | ... | ... |
1685 | BEAM0000 | 158490000400670883 | 135.837810 | -51.605016 | 0 |
1686 | BEAM0000 | 158490000400670983 | 135.921291 | -51.605653 | 0 |
1687 | BEAM0000 | 158490000400671083 | 136.003206 | -51.606299 | 0 |
1688 | BEAM0000 | 158490000400671183 | 136.086629 | -51.606741 | 0 |
1689 | BEAM0000 | 158490000400671283 | 136.168523 | -51.607159 | 0 |
1690 rows × 5 columns
We can now create a map of the orbit path using the dataframe that we have created and utilizing the folium.Map
and folium.Circle
functions. Include map
in the code block to inspect the map which is created within the Jupyter notebook.
[20]:
# Create a map
map = folium.Map(
location=[
latslons.Latitude.mean(),
latslons.Longitude.mean()
], zoom_start=3, control_scale=True
)
# Add variables to the map
for index, location_info in latslons.iterrows():
folium.Circle(
[location_info["Latitude"], location_info["Longitude"]],
popup=f"Shot Number: {location_info['Shot Number']}"
).add_to(map)
# Display map
map
[20]: