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]:
Make this Notebook Trusted to load map: File -> Trust Notebook