{ "cells": [ { "cell_type": "markdown", "id": "4168516b", "metadata": {}, "source": [ "# GEDI_L2A Search and Visualize\n", "\n", "Authors: Nathan Thomas (GSFC/UMD), Sumant Jha (MSFC/USRA), Samuel Ayers (UAH)\n", "\n", "Date: March 9, 2023\n", "\n", "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." ] }, { "cell_type": "markdown", "id": "15bc5e8b-1b4d-402c-9234-4bc505cb527f", "metadata": {}, "source": [ "## Run This Notebook\n", "To access and run this tutorial within MAAP's Algorithm Development Environment (ADE), please refer to the [\"Getting started with the MAAP\"](https://docs.maap-project.org/en/latest/getting_started/getting_started.html) section of our documentation.\n", "\n", "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." ] }, { "cell_type": "markdown", "id": "05de6275-9069-49b1-99d5-216cc8159fd9", "metadata": { "tags": [] }, "source": [ "## About the Data\n", "\n", "GEDI L2A Elevation and Height Metrics Data Global Footprint Level\n", "\n", "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](https://lpdaac.usgs.gov/products/gedi02_av002/))" ] }, { "cell_type": "markdown", "id": "ce544ed5-b587-4b8a-b3ea-07997e27fbdf", "metadata": {}, "source": [ "## Additional Resources\n", "- [Earthdata Search](https://search.earthdata.nasa.gov/search?q=GEDI02_A)\n", "- [LPDAAC - Getting Started with GEDI L2A v2 Data in Python](https://lpdaac.usgs.gov/resources/e-learning/getting-started-gedi-l2a-version-2-data-python/)\n", "- [The GEDI Website](https://gedi.umd.edu/)\n", "- [GEDI_L2A v2 User Guide](https://lpdaac.usgs.gov/documents/998/GEDI02_UserGuide_V21.pdf)" ] }, { "cell_type": "markdown", "id": "7f8be536-a372-442f-ad57-daf72e4ce60d", "metadata": {}, "source": [ "## Importing and Installing Packages" ] }, { "cell_type": "markdown", "id": "527fcd0c", "metadata": {}, "source": [ "We will start by importing the packages which will allow us to search for, access, explore, and visualize GEDI_L2A product data." ] }, { "cell_type": "markdown", "id": "f98c1f44", "metadata": {}, "source": [ "Note: This Jupyter notebook utilizes the `folium` package. If you do not have this installed, uncomment the line and run the following code block." ] }, { "cell_type": "code", "execution_count": null, "id": "cee0ff75", "metadata": {}, "outputs": [], "source": [ "# !pip install folium" ] }, { "cell_type": "markdown", "id": "89df1636", "metadata": {}, "source": [ "For this tutorial, we will import `boto3`, `folium`, `h5py`, `pandas`, and `exists` from `os.path` as shown in the following codeblock." ] }, { "cell_type": "code", "execution_count": 2, "id": "facf4ba9", "metadata": {}, "outputs": [], "source": [ "# Install packages\n", "import boto3\n", "import folium\n", "import h5py\n", "import os\n", "import pandas as pd\n", "from maap.maap import MAAP\n", "from os.path import exists" ] }, { "cell_type": "markdown", "id": "9abed45c", "metadata": {}, "source": [ "## Search for GEDI_L2A Data" ] }, { "cell_type": "markdown", "id": "160c4f3e", "metadata": {}, "source": [ "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'`." ] }, { "cell_type": "code", "execution_count": null, "id": "4aec3326", "metadata": {}, "outputs": [], "source": [ "# Invoke the MAAP using the MAAP host argument\n", "maap = MAAP(maap_host='api.maap-project.org')" ] }, { "cell_type": "markdown", "id": "a1ee3c84", "metadata": {}, "source": [ "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](https://cmr.earthdata.nasa.gov/search/site/docs/search/api.html) 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." ] }, { "cell_type": "code", "execution_count": 4, "id": "10da6bf9", "metadata": {}, "outputs": [], "source": [ "# Search for granule data using CMR host name and collection short name, and readable_granule_name arguments\n", "results = maap.searchGranule(\n", " cmr_host='cmr.earthdata.nasa.gov',\n", " short_name='GEDI02_A',\n", " readable_granule_name = \"GEDI02_A_2021272190541_O15849_04_T03030_02_003_02_V002.h5\")" ] }, { "cell_type": "markdown", "id": "7923e5c9-8fb1-4b46-a815-f3f42ab5e2a4", "metadata": {}, "source": [ "We'll go ahead and create a new data directory (if it doesn't already exist), and download the first result into it." ] }, { "cell_type": "code", "execution_count": 12, "id": "91071041-2455-4530-abef-754f7fefb65d", "metadata": {}, "outputs": [], "source": [ "# set data directory\n", "dataDir = './data'\n", "\n", "# check if directory exists -> if directory doesn't exist, directory is created\n", "if not os.path.exists(dataDir):\n", " os.mkdir(dataDir)\n", " \n", "# Download first result\n", "filename = results[0].getData(dataDir)" ] }, { "cell_type": "markdown", "id": "031f2aa8", "metadata": {}, "source": [ "If desired, the `print` function can be utilized to see the file name and directory." ] }, { "cell_type": "code", "execution_count": 13, "id": "213b49d3", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "./data/GEDI02_A_2021272190541_O15849_04_T03030_02_003_02_V002.h5\n" ] } ], "source": [ "# Print file directory\n", "print(filename)" ] }, { "cell_type": "markdown", "id": "ead3c431", "metadata": {}, "source": [ "## Explore" ] }, { "cell_type": "markdown", "id": "373bf12a", "metadata": {}, "source": [ "Now that we have downloaded the data, let's look into what it contains." ] }, { "cell_type": "code", "execution_count": 14, "id": "18fdc6a5", "metadata": {}, "outputs": [], "source": [ "# Create variable containing info from the file we downloaded\n", "gediL2A = h5py.File(filename, 'r')" ] }, { "cell_type": "markdown", "id": "7c03afc4", "metadata": {}, "source": [ "GEDI_L2A data has data for 8 different beams. Let's create a list of beam names to help explore the data." ] }, { "cell_type": "code", "execution_count": 15, "id": "7350c381", "metadata": {}, "outputs": [ { "data": { "text/plain": [ "['BEAM0000',\n", " 'BEAM0001',\n", " 'BEAM0010',\n", " 'BEAM0011',\n", " 'BEAM0101',\n", " 'BEAM0110',\n", " 'BEAM1000',\n", " 'BEAM1011']" ] }, "execution_count": 15, "metadata": {}, "output_type": "execute_result" } ], "source": [ "# Create list of beam names\n", "beamNames = [g for g in gediL2A.keys() if g.startswith('BEAM')]\n", "beamNames" ] }, { "cell_type": "markdown", "id": "66b27296", "metadata": {}, "source": [ "Now let's explore the information available for one of the beams (in this case 'BEAM0000')." ] }, { "cell_type": "code", "execution_count": 16, "id": "840d18b0", "metadata": {}, "outputs": [ { "data": { "text/plain": [ "['BEAM0000/ancillary/l2a_alg_count',\n", " 'BEAM0000/beam',\n", " 'BEAM0000/channel',\n", " 'BEAM0000/degrade_flag',\n", " 'BEAM0000/delta_time',\n", " 'BEAM0000/digital_elevation_model',\n", " 'BEAM0000/digital_elevation_model_srtm',\n", " 'BEAM0000/elev_highestreturn',\n", " 'BEAM0000/elev_lowestmode',\n", " 'BEAM0000/elevation_bias_flag',\n", " 'BEAM0000/elevation_bin0_error',\n", " 'BEAM0000/energy_total',\n", " 'BEAM0000/geolocation/elev_highestreturn_a1',\n", " 'BEAM0000/geolocation/elev_highestreturn_a2',\n", " 'BEAM0000/geolocation/elev_highestreturn_a3',\n", " 'BEAM0000/geolocation/elev_highestreturn_a4',\n", " 'BEAM0000/geolocation/elev_highestreturn_a5',\n", " 'BEAM0000/geolocation/elev_highestreturn_a6',\n", " 'BEAM0000/geolocation/elev_lowestmode_a1',\n", " 'BEAM0000/geolocation/elev_lowestmode_a2']" ] }, "execution_count": 16, "metadata": {}, "output_type": "execute_result" } ], "source": [ "# Get list of objects in the data pertaining to 'BEAM0000'\n", "beam = beamNames[0]\n", "gediL2A_objs = []\n", "gediL2A.visit(gediL2A_objs.append)\n", "gediSDS = [o for o in gediL2A_objs if isinstance(gediL2A[o], h5py.Dataset)]\n", "[i for i in gediSDS if beam in i][0:20]" ] }, { "cell_type": "markdown", "id": "a36e8449", "metadata": {}, "source": [ "## Visualize" ] }, { "cell_type": "markdown", "id": "d20bee6a", "metadata": {}, "source": [ "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." ] }, { "cell_type": "code", "execution_count": 19, "id": "40a2b299", "metadata": {}, "outputs": [ { "data": { "text/html": [ "
\n", "\n", "\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
BeamShot NumberLongitudeLatitudeQuality Flag
0BEAM000015849000040050238352.3376980.5990110
1BEAM000015849000040050248352.3675540.5561290
2BEAM000015849000040050258352.3969320.5148000
3BEAM000015849000040050268352.4265480.4727460
4BEAM000015849000040050278352.4562480.4306950
..................
1685BEAM0000158490000400670883135.837810-51.6050160
1686BEAM0000158490000400670983135.921291-51.6056530
1687BEAM0000158490000400671083136.003206-51.6062990
1688BEAM0000158490000400671183136.086629-51.6067410
1689BEAM0000158490000400671283136.168523-51.6071590
\n", "

1690 rows × 5 columns

\n", "
" ], "text/plain": [ " Beam Shot Number Longitude Latitude Quality Flag\n", "0 BEAM0000 158490000400502383 52.337698 0.599011 0\n", "1 BEAM0000 158490000400502483 52.367554 0.556129 0\n", "2 BEAM0000 158490000400502583 52.396932 0.514800 0\n", "3 BEAM0000 158490000400502683 52.426548 0.472746 0\n", "4 BEAM0000 158490000400502783 52.456248 0.430695 0\n", "... ... ... ... ... ...\n", "1685 BEAM0000 158490000400670883 135.837810 -51.605016 0\n", "1686 BEAM0000 158490000400670983 135.921291 -51.605653 0\n", "1687 BEAM0000 158490000400671083 136.003206 -51.606299 0\n", "1688 BEAM0000 158490000400671183 136.086629 -51.606741 0\n", "1689 BEAM0000 158490000400671283 136.168523 -51.607159 0\n", "\n", "[1690 rows x 5 columns]" ] }, "execution_count": 19, "metadata": {}, "output_type": "execute_result" } ], "source": [ "# Set variables for shot, beam number, longitude, latitude, and quality flag samples\n", "lonSample, latSample, shotSample, qualitySample, beamSample = [], [], [], [], [] \n", "lats = gediL2A[f'{beamNames[0]}/lat_lowestmode'][()]\n", "lons = gediL2A[f'{beamNames[0]}/lon_lowestmode'][()]\n", "shots = gediL2A[f'{beamNames[0]}/shot_number'][()]\n", "quality = gediL2A[f'{beamNames[0]}/quality_flag'][()]\n", "for i in range(len(shots)):\n", " if i % 100 == 0:\n", " shotSample.append(str(shots[i]))\n", " lonSample.append(lons[i])\n", " latSample.append(lats[i])\n", " qualitySample.append(quality[i])\n", " beamSample.append(beamNames[0])\n", " \n", "# Create a pandas dataframe containing the sample information\n", "latslons = pd.DataFrame({'Beam': beamSample, 'Shot Number': shotSample, 'Longitude': lonSample, \n", " 'Latitude': latSample, 'Quality Flag': qualitySample})\n", "\n", "# Display the dataframe\n", "latslons" ] }, { "cell_type": "markdown", "id": "50a3ea0f", "metadata": {}, "source": [ "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." ] }, { "cell_type": "code", "execution_count": 20, "id": "83cf8a5d", "metadata": {}, "outputs": [ { "data": { "text/html": [ "
Make this Notebook Trusted to load map: File -> Trust Notebook
" ], "text/plain": [ "" ] }, "execution_count": 20, "metadata": {}, "output_type": "execute_result" } ], "source": [ "# Create a map\n", "map = folium.Map(\n", " location=[\n", " latslons.Latitude.mean(),\n", " latslons.Longitude.mean()\n", " ], zoom_start=3, control_scale=True\n", ")\n", "\n", "# Add variables to the map\n", "for index, location_info in latslons.iterrows():\n", " folium.Circle(\n", " [location_info[\"Latitude\"], location_info[\"Longitude\"]],\n", " popup=f\"Shot Number: {location_info['Shot Number']}\"\n", " ).add_to(map)\n", "\n", "# Display map\n", "map" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3 (ipykernel)", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.10.8" } }, "nbformat": 4, "nbformat_minor": 5 }