How to work with GEDI Level 2B V002 Data

This tutorial was developed as a walkthrough at the SPACE AND SUSTAINABILITY ,first international colloquiqm, on Nov 16, 2023 in Guadalajara, Jal., Mexico.

This tutorial will show how to use Python to open GEDI L2B Version 2 files, subset layer and to a region of interest, filter by quality, and visualize GEDI Elevation, Canopy Elevation, Plant Area Index,and Canopy Height.

A small area of Reserva de la Biósfera de Calakmul National Forest is used as the ROI for this tutorial.

Note: Follow the steps provided in setup instructions to create a local Python environment to execute this notebook.

1. Set Up the Working Environment

Import the required packages and set the input/working directory to run this Jupyter Notebook locally.

import os
import h5py
import pandas
import geopandas 
from shapely.geometry import Point
import geoviews
from geoviews import opts, tile_sources as gvts
import shapely
import earthaccess
import warnings
from datetime import datetime

from shapely.errors import ShapelyDeprecationWarning
geoviews.extension('bokeh','matplotlib')
warnings.filterwarnings("ignore", category=ShapelyDeprecationWarning) 
inDir = os.getcwd()   # Set input directory to the current working directory
# os.chdir(inDir)
data_dir = inDir.rsplit('2023-ssc')[0] + 'shared/2023SSC/'
data_dir
'/home/jovyan/shared/2023SSC/'

2. Authentication and Search for GEDI Data

Login to your NASA Earthdata account and create a .netrc file using the login function from the earthaccess library. If you do not have an Earthdata Account, you can create one here.

# authenticate
earthaccess.login()
EARTHDATA_USERNAME and EARTHDATA_PASSWORD are not set in the current environment, try setting them or use a different strategy (netrc, interactive)
You're now authenticated with NASA Earthdata Login
Using token with expiration date: 12/24/2023
Using .netrc file for EDL
<earthaccess.auth.Auth at 0x7fe0af014160>

Below, earthaccess library is used to find GEDI L2B V2 Granules for an area of interest and a temporal range. The .h5 file will be downloaded to the data folder. You will need to download the files in order to execute this tutorial.

# Define query parameters
conceptID = ["C1908350066-LPDAAC_ECS"]
bbx = (-91.5,18,-89,19)  # Lower lon, lower lat, upper lon, upper lat
tempRange = ('2022-06-01', '2022-06-30') #'2022-04-01', '2022-08-30'
# search 
results = earthaccess.search_data(
    # short_name = 'GEDI02_B'
    concept_id = conceptID, 
    bounding_box = bbx,      
    temporal=tempRange,
    count=500
)
Granules found: 5

Next, download the granules.
Note: Considering the time limitations in this workshop, the download section will be skipped. The downloaded granules are stored in shared folder and they are accessible to execute this notebook.

# # download
# downloaded_files = earthaccess.download(
#     results,
#     local_path= f'{data_dir}/data',  # Update the directory only to avoid overwritting by attendees 
# )

Next, retrieve the granule downloaded.

gediFiles = [g for g in os.listdir(data_dir) if g.startswith('GEDI02_B') and g.endswith('.h5')]  # List all GEDI L2B .h5 files in inDir
gediFiles
['GEDI02_B_2022155015315_O19683_03_T05652_02_003_01_V002.h5',
 'GEDI02_B_2022159001702_O19744_03_T08957_02_003_01_V002.h5',
 'GEDI02_B_2022158145957_O19738_02_T03565_02_003_01_V002.h5',
 'GEDI02_B_2022154163608_O19677_02_T01836_02_003_01_V002.h5',
 'GEDI02_B_2022162224038_O19805_03_T06723_02_003_02_V002.h5']

The standard format for GEDI Version 2 filenames is as follows:

GEDI02_B: Product Short Name
2022120091720: Julian Date and Time of Acquisition (YYYYDDDHHMMSS)
O19145: Orbit Number
02: Sub-Orbit Granule Number (1-4)
T09106: Track Number (Reference Ground Track)
02: Positioning and Pointing Determination System (PPDS) type (00 is predict, 01 rapid, 02 and higher is final)
003: PGE Version Number
01: Granule Production Version
V002: Product Version

2. Open a GEDI HDF5 File and Read File Metadata

Read in a GEDI HDF5 file using the h5py package and navigate the HDF5 file. The GEDI HDF5 file contains groups in which data and metadata are stored.

L2B = h5py.File(f'{data_dir}{gediFiles[0]}', 'r')  # Read file using h5py
L2B
<HDF5 file "GEDI02_B_2022155015315_O19683_03_T05652_02_003_01_V002.h5" (mode r)>
list(L2B.keys())
['BEAM0000',
 'BEAM0001',
 'BEAM0010',
 'BEAM0011',
 'BEAM0101',
 'BEAM0110',
 'BEAM1000',
 'BEAM1011',
 'METADATA']

#### The METADATA group contains the file-level metadata such as the creation date, PGEVersion, and VersionID. Below, print the file-level metadata attributes and their values.

for g in L2B['METADATA']['DatasetIdentification'].attrs:
    print(g, ':', L2B['METADATA']['DatasetIdentification'].attrs[g]) 
    
PGEVersion : 003
VersionID : 01
abstract : The GEDI L2B standard data product contains precise latitude, longitude, elevation, height, cover and vertical profile metrics for each laser footprint located on the land surface.
characterSet : utf8
creationDate : 2022-09-22T17:38:15.690108Z
credit : The software that generates the L2B product was implemented within the GEDI Science Data Processing System at the NASA Goddard Space Flight Center (GSFC) in Greenbelt, Maryland in collaboration with the Department of Geographical Sciences at the University of Maryland (UMD).
fileName : GEDI02_B_2022155015315_O19683_03_T05652_02_003_01_V002.h5
language : eng
originatorOrganizationName : UMD/GSFC GEDI-SDPS > GEDI Science Data Processing System
purpose : The purpose of the L2B dataset is to extract biophysical metrics from each GEDI waveform. These metrics are based on the directional gap probability profile derived from the L1B waveform and include canopy cover, Plant Area Index (PAI), Plant Area Volume Density (PAVD) and Foliage Height Diversity (FHD).
shortName : GEDI_L2B
spatialRepresentationType : along-track
status : onGoing
topicCategory : geoscientificInformation
uuid : f7ecc77a-3372-4f53-9131-f110b4bbfb5c

3. Read SDS Metadata and Subset by Beam

The GEDI instrument consists of 3 lasers producing a total of 8 beam ground transects. The eight remaining groups contain data for each of the eight GEDI beam transects. For additional information, be sure to check out: https://gedi.umd.edu/instrument/specifications/.

One useful piece of metadata to retrieve from each beam transect is whether it is a full power beam or a coverage beam. GEDI coverage beams will not penetrate dense forest. The GEDI coverage beams were only designed to penetrate canopies of up to 95% canopy cover under “average” conditions, so users should use GEDI Full power beams in the case of dense forest.

for beam in list(L2B.keys()):
    if beam == 'METADATA':
        continue
    else:
        print(beam, 'is:', L2B[beam].attrs['description'])
BEAM0000 is: Coverage beam
BEAM0001 is: Coverage beam
BEAM0010 is: Coverage beam
BEAM0011 is: Coverage beam
BEAM0101 is: Full power beam
BEAM0110 is: Full power beam
BEAM1000 is: Full power beam
BEAM1011 is: Full power beam

Identify all the datasets in the GEDI HDF5 file below.

# list(L2B['BEAM1011'].keys())
L2B_objs = []
L2B.visit(L2B_objs.append)                                           # Retrieve list of datasets
SDS = [o for o in L2B_objs if isinstance(L2B[o], h5py.Dataset)]  # Search for relevant SDS inside data file
len(SDS)
1576

Print the datasets available in this L2B granule and print the description for desired datasets.

SDS[:30]
['BEAM0000/algorithmrun_flag',
 'BEAM0000/ancillary/dz',
 'BEAM0000/ancillary/l2a_alg_count',
 'BEAM0000/ancillary/maxheight_cuttoff',
 'BEAM0000/ancillary/rg_eg_constraint_center_buffer',
 'BEAM0000/ancillary/rg_eg_mpfit_max_func_evals',
 'BEAM0000/ancillary/rg_eg_mpfit_maxiters',
 'BEAM0000/ancillary/rg_eg_mpfit_tolerance',
 'BEAM0000/ancillary/signal_search_buff',
 'BEAM0000/ancillary/tx_noise_stddev_multiplier',
 'BEAM0000/beam',
 'BEAM0000/channel',
 'BEAM0000/cover',
 'BEAM0000/cover_z',
 'BEAM0000/fhd_normal',
 'BEAM0000/geolocation/degrade_flag',
 'BEAM0000/geolocation/delta_time',
 'BEAM0000/geolocation/digital_elevation_model',
 'BEAM0000/geolocation/elev_highestreturn',
 'BEAM0000/geolocation/elev_lowestmode',
 'BEAM0000/geolocation/elevation_bin0',
 'BEAM0000/geolocation/elevation_bin0_error',
 'BEAM0000/geolocation/elevation_lastbin',
 'BEAM0000/geolocation/elevation_lastbin_error',
 'BEAM0000/geolocation/height_bin0',
 'BEAM0000/geolocation/height_lastbin',
 'BEAM0000/geolocation/lat_highestreturn',
 'BEAM0000/geolocation/lat_lowestmode',
 'BEAM0000/geolocation/latitude_bin0',
 'BEAM0000/geolocation/latitude_bin0_error']
print('rh100: ', L2B['BEAM0101/rh100'].attrs['description'])
print('Quality Flag: ', L2B['BEAM0101/l2b_quality_flag'].attrs['description'])
rh100:  Height above ground of the received waveform signal start (rh[101] from L2A)
Quality Flag:  Flag simpilfying selection of most useful data for Level 2B

View the GEDI L2B Dictionary for more details.

4. Subset by Layer and Filter by Quality

below are the list of datasets will be read and then used to generate a pandas dataframe.

Label Description Units
lat_lowestmode Latitude of center of lowest mode degree
lon_lowestmode Longitude of center of lowest mode degree
elev_lowestmode elevation of center of lowest mode relative to reference ellipsoid m
elev_highestreturn elevation of highest detected return relative to reference ellipsoid m
shot_number Unique shot ID counter
l2b_quality_flag Flag simpilfying selection of most useful data for Level 2B** -
degrade_flag Non-zero values indicate the shot occured during a degraded period. A non-zero tens digit indicates degraded attitude, a non-zero ones digit indicates a degraded trajectory. 3X=ADF CHU solution unavailable (ST-2); 4X=Platform attitude; 5X=Poor solution (filter covariance large); 6X=Data outage (platform attitude gap also); 7X=ST 1+2 unavailable (similar boresight FOV); 8X=ST 1+2+3 unavailable; 9X=ST 1+2+3 and ISS unavailable; X1=Maneuver; X2=GPS data gap; X3=ST blinding; X4=Other; X5=GPS receiver clock drift; X6=X5+X1; X7=X5+X2; X8=X5+X3; X9=X5+X4 -
pai Total plant area index m2/m2
rh100 Height above ground of the received waveform signal start (rh[101] from L2A) cm

** quality_flag is a summation of several individual quality assessment parameters and other flags and is intended to provide general guidance only. A quality_flag value of 1 indicates the cover and vertical profile metrics represent the land surface and meet criteria based on waveform shot energy, sensitivity, amplitude, and real-time surface tracking quality, and the quality of extended Gaussian fitting to the lowest mode.


columns= ['Beam', 'Shot Number', 'Longitude', 'Latitude', 'Quality Flag', 'Canopy Elevation (m)',
          'Elevation (m)', 'Plant Area Index', 'Canopy Height/rh100 (cm)', 'Degrade Flag']
latslons_f1 = pandas.DataFrame(columns=columns)

beamNames = ['BEAM0000', 'BEAM0001', 'BEAM0010', 'BEAM0011', 'BEAM0101', 'BEAM0110',  'BEAM1000', 'BEAM1011' ]
for beamname in beamNames:
    # Open the SDS
    lats = L2B[f'{beamname}/geolocation/lat_lowestmode'][()]
    lons = L2B[f'{beamname}/geolocation/lon_lowestmode'][()]
    elevs = L2B[f'{beamname}/geolocation/elev_lowestmode'][()]
    shots = L2B[f'{beamname}/geolocation/shot_number'][()].astype(str)
    quality = L2B[f'{beamname}/l2b_quality_flag'][()]
    pai = L2B[f'{beamname}/pai'][()]
    rh100 = L2B[f'{beamname}/rh100'][()]
    degrade_flag = L2B[f'{beamname}/geolocation/degrade_flag'][()] 
    canopy = L2B[f'{beamname}/geolocation/elev_highestreturn'][()]

    latslons = pandas.DataFrame({'Beam':beamname, 'Shot Number':shots, 'Longitude':lons, 'Latitude':lats, 'Quality Flag':quality,
                                 'Canopy Elevation (m)':canopy, 'Elevation (m)':elevs, 'Plant Area Index':pai, 
                                 'Canopy Height/rh100 (cm)':rh100, 'Degrade Flag':degrade_flag}) 
        

    latslons_f1 = pandas.concat([latslons_f1, latslons],join="inner")
    
    
latslons_f1
Beam Shot Number Longitude Latitude Quality Flag Canopy Elevation (m) Elevation (m) Plant Area Index Canopy Height/rh100 (cm) Degrade Flag
0 BEAM0000 196830000300204328 -150.407079 51.480311 0 3148.330566 3148.330566 -9999.0 0 80
1 BEAM0000 196830000300204329 -150.406674 51.480271 0 3148.306885 3148.306885 -9999.0 0 80
2 BEAM0000 196830000300204330 -150.406268 51.480231 0 3148.282959 3148.282959 -9999.0 0 80
3 BEAM0000 196830000300204331 -150.405862 51.480191 0 3148.854736 3148.854736 -9999.0 0 80
4 BEAM0000 196830000300204332 -150.405457 51.480151 0 3148.234863 3148.234863 -9999.0 0 80
... ... ... ... ... ... ... ... ... ... ...
152101 BEAM1011 196831100300353743 -77.493929 0.424150 0 -59.045544 -59.045544 -9999.0 0 0
152102 BEAM1011 196831100300353744 -77.493631 0.423730 0 -59.179871 -59.179871 -9999.0 0 0
152103 BEAM1011 196831100300353745 -77.493332 0.423310 0 -58.958637 -58.958637 -9999.0 0 0
152104 BEAM1011 196831100300353746 -77.493034 0.422891 0 -59.209381 -59.209381 -9999.0 0 0
152105 BEAM1011 196831100300353747 -77.492735 0.422470 0 -58.726070 -58.726070 -9999.0 0 0

1233557 rows × 10 columns

We can generate the data frame for all GEDI granules intersecting our region of interest.

columns= ['Date', 'Beam', 'Shot Number', 'Longitude', 'Latitude', 'Quality Flag', 
          'Canopy Elevation (m)','Elevation (m)', 'Plant Area Index', 'Canopy Height/rh100 (cm)' , 'Degrade Flag']

latslons_all = pandas.DataFrame(columns=columns)

for f in gediFiles:
    print(f)
    date_str = f.rsplit('_')[2]
    date = datetime.strptime(date_str, '%Y%j%H%M%S').date().strftime("%m-%d-%Y") 
    L2B = h5py.File(f'{data_dir}{f}', 'r')  # Read file using h5py
    for beamname in beamNames:
        # Open the SDS
        lats = L2B[f'{beamname}/geolocation/lat_lowestmode'][()]
        lons = L2B[f'{beamname}/geolocation/lon_lowestmode'][()]
        elevs = L2B[f'{beamname}/geolocation/elev_lowestmode'][()]
        shots = L2B[f'{beamname}/geolocation/shot_number'][()].astype(str)
        quality = L2B[f'{beamname}/l2b_quality_flag'][()]
        pai = L2B[f'{beamname}/pai'][()]
        rh100 = L2B[f'{beamname}/rh100'][()]
        degrade_flag = L2B[f'{beamname}/geolocation/degrade_flag'][()] 
        canopy = L2B[f'{beamname}/geolocation/elev_highestreturn'][()]

        latslons = pandas.DataFrame({'Date': date ,'Beam':beamname, 'Shot Number':shots, 'Longitude':lons, 'Latitude':lats, 'Quality Flag':quality,
                                     'Canopy Elevation (m)':canopy, 'Elevation (m)':elevs, 'Plant Area Index':pai, 
                                     'Canopy Height/rh100 (cm)':rh100, 'Degrade Flag':degrade_flag}) 


        latslons_all = pandas.concat([latslons_all, latslons],join="inner")

del(lats, lons, elevs, shots, quality, pai, rh100, degrade_flag, canopy, latslons)
GEDI02_B_2022155015315_O19683_03_T05652_02_003_01_V002.h5
GEDI02_B_2022159001702_O19744_03_T08957_02_003_01_V002.h5
GEDI02_B_2022158145957_O19738_02_T03565_02_003_01_V002.h5
GEDI02_B_2022154163608_O19677_02_T01836_02_003_01_V002.h5
GEDI02_B_2022162224038_O19805_03_T06723_02_003_02_V002.h5

Create a list of geodataframe columns to be included as attributes in the output map

Plotting the entire sub-orbit for all granules will take a long time, and might include information that we do not need necessarily. So lets make a subset of data to only keep the data we need.

below, the shots that occured during a degraded period and low quality are removed from the dataframe.

latslons_all  = latslons_all [latslons_all ['Degrade Flag'] == 0].drop(columns = 'Degrade Flag') 
latslons_all = latslons_all [latslons_all['Quality Flag'] == 1].drop(columns = 'Quality Flag') 
 
latslons_all
Date Beam Shot Number Longitude Latitude Canopy Elevation (m) Elevation (m) Plant Area Index Canopy Height/rh100 (cm)
105447 06-04-2022 BEAM0000 196830000300309775 -92.335071 20.843354 -12.388881 -14.957901 0.320302 255
105448 06-04-2022 BEAM0000 196830000300309776 -92.334736 20.842946 -12.912969 -15.556454 0.318079 264
105454 06-04-2022 BEAM0000 196830000300309782 -92.332736 20.840486 -12.179410 -15.567541 0.409812 338
105459 06-04-2022 BEAM0000 196830000300309787 -92.331073 20.838435 -14.535298 -16.880930 0.267779 233
105461 06-04-2022 BEAM0000 196830000300309789 -92.330405 20.837615 -13.029422 -15.300590 0.184147 226
... ... ... ... ... ... ... ... ... ...
146057 06-11-2022 BEAM1011 198051100300346228 -78.356099 4.286391 12.798177 10.115704 0.079636 268
146058 06-11-2022 BEAM1011 198051100300346229 -78.355801 4.285969 13.224768 10.095215 0.269218 311
146059 06-11-2022 BEAM1011 198051100300346230 -78.355503 4.285547 13.374081 11.324969 0.015392 203
150614 06-11-2022 BEAM1011 198051100300350785 -76.984106 2.347054 1283.832886 1276.046265 0.723939 778
150615 06-11-2022 BEAM1011 198051100300350786 -76.983813 2.346651 1271.422974 1256.035889 2.641645 1538

404288 rows × 9 columns

# reset the index and drop the NAs
latslons_all = latslons_all.dropna() 
latslons_all = latslons_all.reset_index(drop=True)
latslons_all
Date Beam Shot Number Longitude Latitude Canopy Elevation (m) Elevation (m) Plant Area Index Canopy Height/rh100 (cm)
0 06-04-2022 BEAM0000 196830000300309775 -92.335071 20.843354 -12.388881 -14.957901 0.320302 255
1 06-04-2022 BEAM0000 196830000300309776 -92.334736 20.842946 -12.912969 -15.556454 0.318079 264
2 06-04-2022 BEAM0000 196830000300309782 -92.332736 20.840486 -12.179410 -15.567541 0.409812 338
3 06-04-2022 BEAM0000 196830000300309787 -92.331073 20.838435 -14.535298 -16.880930 0.267779 233
4 06-04-2022 BEAM0000 196830000300309789 -92.330405 20.837615 -13.029422 -15.300590 0.184147 226
... ... ... ... ... ... ... ... ... ...
404283 06-11-2022 BEAM1011 198051100300346228 -78.356099 4.286391 12.798177 10.115704 0.079636 268
404284 06-11-2022 BEAM1011 198051100300346229 -78.355801 4.285969 13.224768 10.095215 0.269218 311
404285 06-11-2022 BEAM1011 198051100300346230 -78.355503 4.285547 13.374081 11.324969 0.015392 203
404286 06-11-2022 BEAM1011 198051100300350785 -76.984106 2.347054 1283.832886 1276.046265 0.723939 778
404287 06-11-2022 BEAM1011 198051100300350786 -76.983813 2.346651 1271.422974 1256.035889 2.641645 1538

404288 rows × 9 columns

5. Create a Geodataframe and Subset Spatially

Below, an additional column is created and called ‘geometry’ that contains a shapely point generated from each lat/lon location from the shot is created. Next, the dataframe is converted to a Geopandas GeoDataFrame.

# Take the lat/lon dataframe and convert each lat/lon to a shapely point and convert to a Geodataframe
latslons_all = geopandas.GeoDataFrame(latslons_all, geometry=latslons_all.apply(lambda row: Point(row.Longitude, row.Latitude), axis=1))
latslons_all = latslons_all.set_crs('EPSG:4326')

Import a GeoJSON of a section of Reserva de la Biósfera de Calakmul National Forest as an additional GeoDataFrame.

ROI = geopandas.GeoDataFrame.from_file(f'{data_dir}calakmul.geojson')
ROI.crs = 'EPSG:4326'
ROI['geometry'][0]

Next, filter the shots that are within the ROI boundaries.

shot_list = []
for num, geom in enumerate(latslons_all['geometry']):
    if ROI.contains(geom)[0]:
        shot_n = latslons_all.loc[num, 'Shot Number']
        shot_list.append(shot_n)
DF = latslons_all.where(latslons_all['Shot Number'].isin(shot_list))
DF = DF.dropna().reset_index(drop=True)
DF
Date Beam Shot Number Longitude Latitude Canopy Elevation (m) Elevation (m) Plant Area Index Canopy Height/rh100 (cm) geometry
0 06-04-2022 BEAM0000 196830000300314188 -90.882501 18.994573 -1.029900 -4.943680 0.099078 391 POINT (-90.88250 18.99457)
1 06-04-2022 BEAM0000 196830000300314189 -90.882217 18.994137 1.437216 -4.340271 0.254931 576 POINT (-90.88222 18.99414)
2 06-04-2022 BEAM0000 196830000300314190 -90.881934 18.993701 -0.111586 -4.547207 0.081328 442 POINT (-90.88193 18.99370)
3 06-04-2022 BEAM0000 196830000300314191 -90.881651 18.993265 0.287704 -4.408836 0.100879 469 POINT (-90.88165 18.99326)
4 06-04-2022 BEAM0000 196830000300314192 -90.881367 18.992827 0.891814 -3.618357 0.039722 451 POINT (-90.88137 18.99283)
... ... ... ... ... ... ... ... ... ... ...
26121 06-11-2022 BEAM1011 198051100300312342 -89.001523 18.442686 169.125259 150.570358 2.720562 1854 POINT (-89.00152 18.44269)
26122 06-11-2022 BEAM1011 198051100300312343 -89.001190 18.442281 154.575348 149.321854 0.006572 525 POINT (-89.00119 18.44228)
26123 06-11-2022 BEAM1011 198051100300312344 -89.000857 18.441875 154.223221 148.857941 0.101806 536 POINT (-89.00086 18.44187)
26124 06-11-2022 BEAM1011 198051100300312345 -89.000526 18.441474 157.639175 142.810150 4.159529 1481 POINT (-89.00053 18.44147)
26125 06-11-2022 BEAM1011 198051100300312346 -89.000191 18.441063 153.560394 147.859802 0.053789 569 POINT (-89.00019 18.44106)

26126 rows × 10 columns

All_DF = geopandas.GeoDataFrame(DF).drop(columns=['Longitude', 'Latitude'])

6. Visualize a GeoDataFrame

In this section, the GeoDataFrame and the geoviews python package are used to spatially visualize the location of the GEDI shots on a basemap layer and import a GeoJSON file of the spatial region of interest for this use case example.

Defining the vdims below will allow you to hover over specific shots and view information about them.

# Create a list of geodataframe columns to be included as attributes in the output map
vdims = []
for f in All_DF:
    if f not in ['geometry']:
        vdims.append(f)

vdims
['Date',
 'Beam',
 'Shot Number',
 'Canopy Elevation (m)',
 'Elevation (m)',
 'Plant Area Index',
 'Canopy Height/rh100 (cm)']
# Define a function for visualizing GEDI points
def pointVisual(features, vdims):
    return (gvts.EsriImagery * geoviews.Points(features, vdims=vdims).options(tools=['hover'], height=500, width=900, size=4, 
                                                                        color='yellow', fontsize={'xticks': 10, 'yticks': 10, 
                                                                                                  'xlabel':16, 'ylabel': 16}))
# Visualize GEDI data
geoviews.Polygons(ROI['geometry']).opts(line_color='red', color=None)* pointVisual(All_DF, vdims = vdims)

Below, the shots are mapped to enable selection of datasets using dropdown menu to better visualize the spatial variations for each dataset.

import panel 
panel.extension()

mask_name = panel.widgets.Select(name='Datasets',options=vdims, value='Elevation (m)', disabled_options=['Beam', 'Shot Number', 'Date'])
@panel.depends(mask_name)
def visual_map(mask_name):
    map = (gvts.EsriImagery * geoviews.Points(All_DF,
                                              vdims=vdims).options(color=mask_name,
                                                                   cmap='gnuplot', size=4, tools=['hover'],
                                                                   clim=(int(min(DF[mask_name])), 
                                                                         round(max(DF[mask_name]))),
                                                                   colorbar=True, 
                                                                   title=f'{mask_name} (Calakmul National Forest): 2022',
                                                                   fontsize={'xticks': 10, 'yticks': 10, 'xlabel':16, 'clabel':12,
                                                                             'cticks':10,'title':10,
                                                                             'ylabel':16})).options(height=500,width=700)
    return map 

panel.Row(panel.WidgetBox(mask_name), visual_map)

7. Export Subsets as GeoJSON Files

Finally, export the GeoDataFrame as a .geojson file that can be easily opened in your favorite remote sensing and/or GIS software and will include an attribute table with all of the shots/values for each of the SDS layers in the dataframe.

outName = f'{data_dir}GEDI02_B_Calakmul.geojson'    # Create an output file name using the input file name
print(outName)
All_DF.to_file(outName, driver='GeoJSON')  # Export to GeoJSON
/home/jovyan/shared/2023SSC/GEDI02_B_Calakmul.geojson

Contact Info:

Email: LPDAAC@usgs.gov
Voice: +1-866-573-3222
Organization: Land Processes Distributed Active Archive Center (LP DAAC)¹
Website: https://lpdaac.usgs.gov/
Date last modified: 11-15-2023

¹Work performed under USGS contract G15PD00467 for NASA contract NNG14HH33I.