EMIT Methane Point Source Plume Complexes

Daily aggregated, global point source methane emission plume estimates from the EMIT instrument on the International Space Station (ISS)
Author

Siddharth Chaudhary, Vishal Gaur

Approach

  1. 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.
  2. Pass the STAC item into the raster API /stac/tilejson.json endpoint.
  3. Using folium.Map, visualize the plumes.
  4. After the visualization, perform zonal statistics for a given polygon.

About the Data

The Earth Surface Mineral Dust Source Investigation (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 Jet Propulsion Laboratory (JPL) Open Science and Open Data policy. For more information regarding this dataset, please visit the EMIT Methane Point Source Plume Complexes data overview page.

Install the Required Libraries

Required libraries are pre-installed on the GHG Center Hub. If you need to run this notebook elsewhere, please install them with this line in a code cell:

%pip install requests folium rasterstats pystac_client pandas matplotlib –quiet

Querying the STAC API

First, we are going to import the required libraries. Once imported, they allow better executing a query in the GHG Center Spatio Temporal Asset Catalog (STAC) Application Programming Interface (API) where the granules for this collection are stored.

# Import the following libraries
import requests
import folium
import folium.plugins
from folium import Map, TileLayer
from pystac_client import Client
import branca
import pandas as pd
import matplotlib.pyplot as plt
# Provide the STAC and RASTER API endpoints
# The endpoint is referring to a location within the API that executes a request on a data collection nesting on the server.

# The STAC API is a catalog of all the existing data collections that are stored in the GHG Center.
STAC_API_URL = "http://ghg.center/api/stac"

# The RASTER API is used to fetch collections for visualization
RASTER_API_URL = "https://ghg.center/api/raster"

# The collection name is used to fetch the dataset from the STAC API. First, we define the collection name as a variable
# Name of the collection for methane emission plumes 
collection_name = "emit-ch4plume-v1"
# Fetch the collection from the STAC API using the appropriate endpoint
# The 'requests' library allows a HTTP request possible
collection = requests.get(f"{STAC_API_URL}/collections/{collection_name}").json()

# Print the properties of the collection to the console
collection

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).

def get_item_count(collection_id):
    count = 0
    items_url = f"{STAC_API_URL}/collections/{collection_id}/items"

    while True:
        response = requests.get(items_url)

        if not response.ok:
            print("error getting items")
            exit()

        stac = response.json()
        count += int(stac["context"].get("returned", 0))
        next = [link for link in stac["links"] if link["rel"] == "next"]

        if not next:
            break
        items_url = next[0]["href"]

    return count
# Check total number of items available
number_of_items = get_item_count(collection_name)
items = requests.get(f"{STAC_API_URL}/collections/{collection_name}/items?limit={number_of_items}").json()["features"]
print(f"Found {len(items)} items")
# Examining the first item in the collection
items[0]

Below, we are entering the minimum and maximum values to provide our upper and lower bounds in rescale_values.

Exploring Methane Emission Plumes (CH₄) using the Raster API

In this notebook, we will explore global methane emission plumes from point sources. We will visualize the outputs on a map using folium.

# 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)
items = {item["id"][20:]: item for item in items} 
asset_name = "ch4-plume-emissions"
# Fetching the min and max values for a specific item
rescale_values = {"max":items[list(items.keys())[0]]["assets"][asset_name]["raster:bands"][0]["histogram"]["max"], "min":items[list(items.keys())[0]]["assets"][asset_name]["raster:bands"][0]["histogram"]["min"]}

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.

# 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.
item_id = "20230418T200118_000829"
color_map = "magma"
methane_plume_tile = requests.get(
    f"{RASTER_API_URL}/stac/tilejson.json?collection={items[item_id]['collection']}&item={items[item_id]['id']}"
    f"&assets={asset_name}"
    f"&color_formula=gamma+r+1.05&colormap_name={color_map}"
    f"&rescale={rescale_values['min']},{rescale_values['max']}", 
).json()
methane_plume_tile

Visualizing CH₄ Emission Plume

# Set initial zoom and center of map for plume Layer
map_ = folium.Map(location=(methane_plume_tile["center"][1], methane_plume_tile["center"][0]), zoom_start=13)

# December 2001
map_layer = TileLayer(
    tiles=methane_plume_tile["tiles"][0],
    attr="GHG",
    opacity=1,
)
map_layer.add_to(map_)

# visualising the map
map_

Calculating Zonal Statistics

To perform zonal statistics, first we need to create a polygon. In this use case we will create a polygon around the plume.

# Plume AOI 
plumes_coordinates = items[item_id]["geometry"]["coordinates"]
methane_plume_aoi = {
    "type": "Feature",
    "properties": {},
    "geometry": {
        "coordinates":
            plumes_coordinates,
        "type": "Polygon",
    },
}
# We'll plug in the coordinates for a location
# central to the study area and a reasonable zoom level
region_name = "Place_Holder" # please put the name of the place you are trying to visualize
aoi_map = Map(
    tiles="OpenStreetMap",
    location=[
        plumes_coordinates[0][0][1],
        plumes_coordinates[0][0][0]
    ],
    zoom_start=12,
)

folium.GeoJson(methane_plume_aoi, name=region_name).add_to(aoi_map)
aoi_map
# Check total number of items available
items = requests.get(
    f"{STAC_API_URL}/collections/{collection_name}/items?limit={number_of_items}"
).json()["features"]
print(f"Found {len(items)} items")
# Explore the first item
items[0]
# The bounding box should be passed to the geojson param as a geojson Feature or FeatureCollection
def generate_stats(item, geojson):
    result = requests.post(
        f"{RASTER_API_URL}/cog/statistics",
        params={"url": item["assets"][asset_name]["href"]},
        json=geojson,
    ).json()
    print(result)
    return {
        **result["properties"],
        "item_id": item["id"][20:],
    }
for item in items:
    print(item["id"])
    break

With the function above, we can generate the statistics for the area of interest.

%%time
stats = [generate_stats(item, methane_plume_aoi) for item in items]
stats = [ stat for stat in stats if stat["statistics"]["b1"]["mean"] != None]

stats
def clean_stats(stats_json) -> pd.DataFrame:
    df = pd.json_normalize(stats_json)
    df.columns = [col.replace("statistics.b1.", "") for col in df.columns]
    # df["date"] = pd.to_datetime(df["datetime"])
    return df


df = clean_stats(stats)
df
plume_tile_2 = requests.get(
    f"{RASTER_API_URL}/stac/tilejson.json?collection={items[0]['collection']}&item={items[0]['id']}"
    f"&assets={asset_name}"
    f"&color_formula=gamma+r+1.05&colormap_name={color_map}"
    f"&rescale={rescale_values['min']},{rescale_values['max']}",
).json()
plume_tile_2
# Use bbox initial zoom and map
# Set up a map located w/in event bounds
plume_tile_2_coordinates = items[0]["geometry"]["coordinates"]
aoi_map_bbox = Map(
    tiles="OpenStreetMap",
    location=[
        plume_tile_2_coordinates[0][0][1],
        plume_tile_2_coordinates[0][0][0]
    ],
    zoom_start=10,
)

map_layer = TileLayer(
    tiles=plume_tile_2["tiles"][0],
    attr="GHG", opacity = 1
)

map_layer.add_to(aoi_map_bbox)

aoi_map_bbox

Summary

In this notebook we have successfully completed the following steps for the STAC collection for the EMIT Methane Point Source Plume Complexes dataset: 1. Install and import the necessary libraries 2. Fetch the collection from STAC collections using the appropriate endpoints 3. Count the number of existing granules within the collection 4. Map the methane emission plumes 5. Generate statistics for the area of interest (AOI)

If you have any questions regarding this user notebook, please contact us using the feedback form.

Back to top