diff --git a/.gitignore b/.gitignore
index aaa35be..0bdefda 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,4 +1,5 @@
/.quarto/
_site/
*.json
-*.zip
\ No newline at end of file
+*.zip
+*.nc4
diff --git a/interactive-session-3-large-point-source-methane-emission-events/emit-ch4plume-v1_User_Notebook.ipynb b/interactive-session-3-large-point-source-methane-emission-events/emit-ch4plume-v1_User_Notebook.ipynb
new file mode 100644
index 0000000..6a94316
--- /dev/null
+++ b/interactive-session-3-large-point-source-methane-emission-events/emit-ch4plume-v1_User_Notebook.ipynb
@@ -0,0 +1,511 @@
+{
+ "cells": [
+ {
+ "attachments": {},
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## About the Data\n",
+ "\n",
+ "The EMIT instrument builds upon NASA’s long history of developing advanced imaging spectrometers for new science and applications. EMIT launched to the International Space Station (ISS) on July 14, 2022. The data shows high-confidence research grade methane plumes from point source emitters - updated as they are identified - in keeping with JPL Open Science and Open Data policy."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## Requirements\n",
+ "\n",
+ "- NASA Earthdata Account\n",
+ "- Set up Python Environment - See setup_instructions.md in the '/setup/' folder"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## Learning Objectives\n",
+ "\n",
+ "- How to use 'U.S.GHG Center STAC Catalog' to access 'EMIT Methane Point Source Plume Complexes' data.\n",
+ "- How to visualize ISS Spatial coverage and detected plumes target mask.\n",
+ "- How to visualize datasets using 'folium'."
+ ]
+ },
+ {
+ "attachments": {},
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## Approach\n",
+ "\n",
+ "1. Visualize obsereved masks of the ISS and target masks of detected plumes. \n",
+ "2. Identify available dates and temporal frequency of observations for the given collection using the GHGC API `/stac` endpoint. The collection processed in this notebook is the Earth Surface Mineral Dust Source Investigation (EMIT) methane emission plumes data product.\n",
+ "3. Pass the STAC item into the raster API `/stac/tilejson.json` endpoint.\n",
+ "4. Using `folium.Map`, visualize the plumes.\n",
+ "\n",
+ " "
+ ]
+ },
+ {
+ "attachments": {},
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "# Setup\n",
+ "Import the required Python libraries."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "# Import required libraries\n",
+ "\n",
+ "# import earthaccess\n",
+ "import warnings\n",
+ "import requests\n",
+ "import pandas as pd\n",
+ "import geopandas\n",
+ "import folium\n",
+ "import folium.plugins\n",
+ "import numpy as np\n",
+ "import matplotlib.pyplot as plt\n",
+ "import branca.colormap as cm\n",
+ "\n",
+ "from folium import Map, TileLayer \n",
+ "from branca.element import Figure\n",
+ "from pystac_client import Client \n",
+ "from pyproj import Geod\n",
+ "from shapely import wkt"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## NASA Earth Data Login Credentials\n",
+ "\n",
+ "To download or stream NASA data you will need an Earthdata account, you can create one here https://urs.earthdata.nasa.gov/home. We will use the 'login' function from the 'earthaccess' library for authentication before downloading at the end of the notebook. This function can also be used to create a local '.netrc' file if it doesn’t exist or add your login info to an existing '.netrc' file. If no Earthdata Login credentials are found in the '.netrc' you’ll be prompted for them. This step is not necessary to conduct searches but is needed to download or stream data."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## Define Spatial Region of Interest\n",
+ "\n",
+ "For this example, our spatial region of interest (ROI) will be the Permian Basin, USA."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "# Loading Permian Basin shape file as a region of interest\n",
+ "# User can pass any json or shape file here\n",
+ "permian_basin = geopandas.read_file('permian.zip')"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "# ISS data coverage \n",
+ "coverage = geopandas.read_file('coverage.json')\n",
+ "coverage['new_start_time'] = coverage.start_time.astype('str')\n",
+ "\n",
+ "# location where plume was detected\n",
+ "target_mask = geopandas.read_file('target_mask.json')\n",
+ "\n",
+ "# ISS area intersecting with permian basin\n",
+ "result_coverage = geopandas.clip(coverage, permian_basin)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## Opening and Exploring Spatial Coverage of ISS and Mask of Identified Plumes\n",
+ "\n",
+ "In this cell, we will visualize the outputs on a map using 'folium'."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "# Initializing a map with a center at [43, -100] and a zoom level of 4. The use of tiles = None has been employed to remove the default basemap.\n",
+ "\n",
+ "colormap = cm.linear.YlGn_09.scale(\n",
+ " coverage.start_time.min(), coverage.start_time.max()\n",
+ ")\n",
+ "start_time_dict = coverage.set_index(\"fid\")[\"start_time\"]\n",
+ "m_ = folium.Map(location=[43, -100], zoom_start=4, tiles=None)\n",
+ "folium.TileLayer(tiles='https://server.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/{z}/{y}/{x}.png', name='ESRI World Imagery', attr='Tiles © Esri — Source: Esri, i-cubed, USDA, USGS, AEX, GeoEye, Getmapping, Aerogrid, IGN, IGP, UPR-EGP, and the GIS User Community',overlay='True').add_to(m_)\n",
+ "# Load other TileLayer from https://leaflet-extras.github.io/leaflet-providers/preview/\n",
+ "\n",
+ "# From covergae.json fid, geometry has been selected\n",
+ "# map_layer_target = folium.GeoJson(coverage[['fid', 'geometry', 'new_start_time']], name = 'ISS coverage').add_to(m_)\n",
+ "# map_layer_target.add_to(m_)\n",
+ "coverage[['fid', 'geometry', 'new_start_time']].explore(\n",
+ " \"fid\",\n",
+ " categorical=True,\n",
+ " tooltip=[\n",
+ " \"fid\",\n",
+ " \"new_start_time\",\n",
+ " ],\n",
+ " popup=True,\n",
+ " style_kwds=dict(fillOpacity=0.1, width=2),\n",
+ " name=\"EMIT\",\n",
+ " m=m_,\n",
+ " legend=False\n",
+ ")\n",
+ "\n",
+ "map_layer_coverage = folium.GeoJson(target_mask, name = ' Target Mask').add_to(m_)\n",
+ "map_layer_coverage.add_to(m_)\n",
+ "\n",
+ "# Layer control crates the toggle button for all the layers\n",
+ "folium.LayerControl(collapsed=False).add_to(m_)\n",
+ "m_"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## Opening and Exploring EMIT Methane Point Source Plume Complexes Data Using the Raster API\n",
+ "Search for EMIT Methane Point Source Plume Complexes Data"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "# Provide STAC and RASTER API endpoints\n",
+ "STAC_API_URL = \"http://ghg.center/api/stac\"\n",
+ "RASTER_API_URL = \"https://ghg.center/api/raster\"\n",
+ "\n",
+ "# Please use the collection name similar to the one used in STAC collection.\n",
+ "# Name of the collection for methane emission plumes. \n",
+ "collection_name = \"emit-ch4plume-v1\""
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "# Fetching the collection from STAC collections using appropriate endpoint.\n",
+ "# the 'requests' library allows a HTTP request possible\n",
+ "collection = requests.get(f\"{STAC_API_URL}/collections/{collection_name}\").json()\n",
+ "collection = {key: value for key, value in collection.items() if key != 'summaries'}\n",
+ "collection\n"
+ ]
+ },
+ {
+ "attachments": {},
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "Examining the contents of our `collection` under the `temporal` variable, we note that data is available from August 2022 to May 2023. By looking at the `dashboard: time density`, we can see that observations are conducted daily and non-periodically (i.e., there are plumes emissions for multiple places on the same dates)."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "# Create a function that would search for the number of items in above data collection in the STAC API\n",
+ "def get_item_count(collection_id):\n",
+ " count = 0\n",
+ " items_url = f\"{STAC_API_URL}/collections/{collection_id}/items\"\n",
+ "\n",
+ " while True:\n",
+ " response = requests.get(items_url)\n",
+ "\n",
+ " if not response.ok:\n",
+ " print(\"error getting items\")\n",
+ " exit()\n",
+ "\n",
+ " stac = response.json()\n",
+ " count += int(stac[\"context\"].get(\"returned\", 0))\n",
+ " next = [link for link in stac[\"links\"] if link[\"rel\"] == \"next\"]\n",
+ "\n",
+ " if not next:\n",
+ " break\n",
+ " items_url = next[0][\"href\"]\n",
+ "\n",
+ " return count"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "# Apply the above function and check the total number of items available within the collection\n",
+ "number_of_items = get_item_count(collection_name)\n",
+ "plume_complexes = requests.get(f\"{STAC_API_URL}/collections/{collection_name}/items?limit={number_of_items}\").json()[\"features\"]\n",
+ "parse_plume_complexes = plume_complexes\n",
+ "print(f\"Found {len(plume_complexes)} items\")"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "# Examine the first item in the collection\n",
+ "plume_complex_ids = list(map(lambda d: d.get('id', f\"id not found in dictionary\"), plume_complexes))\n",
+ "plume_complex_ids[:10]"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "# To access the year value from each item more easily, this will let us query more explicity by year and month (e.g., 2020-02)\n",
+ "plume_complexes = {plume_complex[\"id\"]: plume_complex for plume_complex in plume_complexes} \n",
+ "asset_name = \"ch4-plume-emissions\""
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "Below, we are entering the minimum and maximum values to provide our upper and lower bounds in `rescale_values`."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "# Fetching the min and max values for a specific item\n",
+ "rescale_values = {\"max\":plume_complexes[list(plume_complexes.keys())[0]][\"assets\"][asset_name][\"raster:bands\"][0][\"histogram\"][\"max\"], \"min\":plume_complexes[list(plume_complexes.keys())[0]][\"assets\"][asset_name][\"raster:bands\"][0][\"histogram\"][\"min\"]}"
+ ]
+ },
+ {
+ "attachments": {},
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "Now we will pass the item id, collection name, and `rescaling_factor` to the `Raster API` endpoint. We will do this for only one item so that we can visualize the event. "
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "color_map = \"magma\" # please refer to matplotlib library if you'd prefer choosing a different color ramp.\n",
+ "# For more information on Colormaps in Matplotlib, please visit https://matplotlib.org/stable/users/explain/colors/colormaps.html\n",
+ "\n",
+ "# Select the item ID which you want to visualize. Item ID is in the format yyyymmdd followed by the timestamp. This ID can be extracted from the COG name as well.\n",
+ "plume_complex_id = \"EMIT_L2B_CH4PLM_001_20230418T200118_000829\"\n",
+ "methane_plume_tile = requests.get(\n",
+ " f\"{RASTER_API_URL}/stac/tilejson.json?collection={plume_complexes[plume_complex_id]['collection']}&item={plume_complexes[plume_complex_id]['id']}\"\n",
+ " f\"&assets={asset_name}\"\n",
+ " f\"&color_formula=gamma+r+1.05&colormap_name={color_map}\"\n",
+ " f\"&rescale={rescale_values['min']},{rescale_values['max']}\", \n",
+ ").json()\n",
+ "methane_plume_tile"
+ ]
+ },
+ {
+ "attachments": {},
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## Visualizing CH₄ Emission Plume\n"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "colormap = \"magma\" # please refer to matplotlib library if you'd prefer choosing a different color ramp.\n",
+ "# For more information on Colormaps in Matplotlib, please visit https://matplotlib.org/stable/users/explain/colors/colormaps.html\n",
+ "\n",
+ "#Defining the breaks in the colormap \n",
+ "color_map = cm.LinearColormap(colors = ['#310597', '#4C02A1', '#6600A7', '#7E03A8', '#9511A1', '#AA2395', '#BC3587', '#CC4778', '#DA5A6A', '#E66C5C', '#F0804E', '#F89540','#FDAC33', '#FDC527', '#F8DF25'], vmin = 0, vmax = 1500 )\n",
+ "color_map.caption = 'ppm-m'\n",
+ "\n",
+ "# Select the item ID which you want to visualize. Item ID is in the format yyyymmdd followed by the timestamp. This ID can be extracted from the COG name as well.\n",
+ "plume_complex_id = \"EMIT_L2B_CH4PLM_001_20230418T200118_000829\"\n",
+ "\n",
+ "methane_plume_tile = requests.get(\n",
+ " f\"{RASTER_API_URL}/stac/tilejson.json?collection={plume_complexes[plume_complex_id]['collection']}&item={plume_complexes[plume_complex_id]['id']}\"\n",
+ " f\"&assets={asset_name}\"\n",
+ " f\"&color_formula=gamma+r+1.05&colormap_name={colormap}\"\n",
+ " f\"&rescale={rescale_values['min']},{rescale_values['max']}\", \n",
+ ").json()\n",
+ "methane_plume_tile\n",
+ "\n",
+ "# Set initial zoom and center of map for plume Layer\n",
+ "map_ = folium.Map(location=(methane_plume_tile[\"center\"][1], methane_plume_tile[\"center\"][0]), zoom_start=13, tiles=None, tooltip = 'test tool tip')\n",
+ "folium.TileLayer(tiles='https://server.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/{z}/{y}/{x}.png', name='ESRI World Imagery', attr='Tiles © Esri — Source: Esri, i-cubed, USDA, USGS, AEX, GeoEye, Getmapping, Aerogrid, IGN, IGP, UPR-EGP, and the GIS User Community',overlay='True').add_to(map_)\n",
+ "\n",
+ "\n",
+ "map_layer = TileLayer(\n",
+ " tiles=methane_plume_tile[\"tiles\"][0],\n",
+ " name='Plume Complex',\n",
+ " overlay='True',\n",
+ " attr=\"GHG\",\n",
+ " opacity=1,\n",
+ ")\n",
+ "map_layer.add_to(map_)\n",
+ "folium.LayerControl(collapsed=False, position='bottomleft').add_to(map_)\n",
+ "map_.add_child(color_map)\n",
+ "svg_style = ''\n",
+ "map_.get_root().header.add_child(folium.Element(svg_style))\n",
+ "# visualising the map\n",
+ "map_"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "# Creating a dataframe for each plume in the STAC and its corresponding area \n",
+ "\n",
+ "plume_df = pd.DataFrame()\n",
+ "for plume in parse_plume_complexes:\n",
+ " temp_polygon = geopandas.GeoDataFrame.from_features([plume])['geometry'].values[0]\n",
+ " geod = Geod(ellps='WGS84')\n",
+ " ply = wkt.loads(str(temp_polygon))\n",
+ "\n",
+ " temp_dict = {'id':plume['id'], 'geometry':temp_polygon, 'area(km2)':(abs(geod.geometry_area_perimeter(ply)[0])/1e+6)}\n",
+ " plume_df = plume_df._append(temp_dict, ignore_index = True)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "plume_df"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## Visualizing CH4 Emission Plumes in the Permian Basin"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "# Converting plume_df into a geo dataframe for further geospatial analysis\n",
+ "geo_temp_df = geopandas.GeoDataFrame(plume_df, geometry=plume_df.geometry, crs = 'EPSG:4326')"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "# Selecting all the plume tagets which are under permian basin\n",
+ "geo_temp_df = geopandas.clip(geo_temp_df, permian_basin)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "colormap = \"magma\" # please refer to matplotlib library if you'd prefer choosing a different color ramp.\n",
+ "# For more information on Colormaps in Matplotlib, please visit https://matplotlib.org/stable/users/explain/colors/colormaps.html\n",
+ "\n",
+ "color_map = cm.LinearColormap(colors = ['#310597', '#4C02A1', '#6600A7', '#7E03A8', '#9511A1', '#AA2395', '#BC3587', '#CC4778', '#DA5A6A', '#E66C5C', '#F0804E', '#F89540','#FDAC33', '#FDC527', '#F8DF25'], vmin = 0, vmax = 1500 )\n",
+ "color_map.caption = 'ppm-m'\n",
+ "# Select the item ID which you want to visualize. Item ID is in the format yyyymmdd followed by the timestamp. This ID can be extracted from the COG name as well.\n",
+ "\n",
+ "map_ = folium.Map(location=[32,-102], zoom_start=10, tiles=None)\n",
+ "folium.TileLayer(tiles='https://server.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/{z}/{y}/{x}.png', name='ESRI World Imagery', attr='Tiles © Esri — Source: Esri, i-cubed, USDA, USGS, AEX, GeoEye, Getmapping, Aerogrid, IGN, IGP, UPR-EGP, and the GIS User Community',overlay='True').add_to(map_)\n",
+ "\n",
+ "for tile_id in geo_temp_df['id']: \n",
+ " methane_plume_tile = requests.get(\n",
+ " f\"{RASTER_API_URL}/stac/tilejson.json?collection={plume_complexes[tile_id]['collection']}&item={plume_complexes[tile_id]['id']}\"\n",
+ " f\"&assets={asset_name}\"\n",
+ " f\"&color_formula=gamma+r+1.05&colormap_name={colormap}\"\n",
+ " f\"&rescale={rescale_values['min']},{rescale_values['max']}\", \n",
+ " ).json()\n",
+ " methane_plume_tile\n",
+ "\n",
+ " map_layer = TileLayer(\n",
+ " tiles=methane_plume_tile[\"tiles\"][0],\n",
+ " name=tile_id,\n",
+ " overlay='True',\n",
+ " attr=\"GHG\",\n",
+ " opacity=1,\n",
+ " )\n",
+ " map_layer.add_to(map_)\n",
+ "\n",
+ "map_layer_permian = folium.GeoJson(permian_basin, name= 'Permian Shape').add_to(m_)\n",
+ "map_layer_permian.add_to(map_)\n",
+ "folium.LayerControl(collapsed=True, position='bottomleft').add_to(map_)\n",
+ "map_.add_child(color_map)\n",
+ "svg_style = ''\n",
+ "map_.get_root().header.add_child(folium.Element(svg_style))\n",
+ "# visualising the map\n",
+ "map_"
+ ]
+ }
+ ],
+ "metadata": {
+ "kernelspec": {
+ "display_name": "Python 3",
+ "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.9.16"
+ },
+ "vscode": {
+ "interpreter": {
+ "hash": "31f2aee4e71d21fbe5cf8b01ff0e069b9275f58929596ceb00d14d90e3e16cd6"
+ }
+ }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 4
+}