aggie.cc
← Back to posts

Tracking Permian Basin Flaring from Space — for Free

Using Sentinel-2 SWIR bands and Google Earth Engine to build a zero-cost flaring detection system for the Permian Basin.

The Permian Basin flares over 500 million cubic feet of natural gas per day. Operators, investors, and regulators all want to know who’s flaring, how much, and whether it’s getting better or worse — but public filings lag by months.

What if you could see it from space, for free?

The Stack

Sentinel-2, ESA’s free Earth observation satellite, carries two shortwave infrared (SWIR) bands — B11 (1.6μm) and B12 (2.2μm) — at 20-meter resolution. Gas flares burn at extremely high temperatures and saturate these bands, creating a distinctive thermal signature.

The detection algorithm is straightforward: compute a Thermal Anomaly Index (TAI) as the normalized ratio between SWIR bands. Flaring pixels show anomalously high values compared to normal background.

import ee

ee.Initialize()

# Define Permian Basin AOI
permian = ee.Geometry.Rectangle([-104.5, 30.5, -101.0, 33.5])

# Load Sentinel-2 L2A imagery
s2 = (ee.ImageCollection('COPERNICUS/S2_SR_HARMONIZED')
      .filterBounds(permian)
      .filterDate('2025-01-01', '2025-12-31')
      .filter(ee.Filter.lt('CLOUDY_PIXEL_PERCENTAGE', 20)))

def compute_tai(image):
    """Compute Thermal Anomaly Index from SWIR bands."""
    b11 = image.select('B11')
    b12 = image.select('B12')
    tai = b12.subtract(b11).divide(b12.add(b11)).rename('TAI')
    return image.addBands(tai)

flare_collection = s2.map(compute_tai)

# Threshold for flare detection
tai_composite = flare_collection.select('TAI').max()
flare_mask = tai_composite.gt(0.3)

Cross-Referencing with Texas RRC Data

Once you have flare locations from satellite, match them to API numbers in the Texas Railroad Commission database. This tells you which operator runs each well, what they reported producing, and whether their flaring matches what you see from orbit.

import pandas as pd
import geopandas as gpd

# Load RRC well locations
wells = gpd.read_file('rrc_wells_permian.geojson')

# Load detected flare points
flares = gpd.read_file('detected_flares.geojson')

# Spatial join: find wells within 500m of detected flares
matched = gpd.sjoin_nearest(
    flares, wells,
    max_distance=500,
    distance_col='dist_m'
)

print(f"Matched {len(matched)} flare detections to {matched['API'].nunique()} unique wells")

What This Enables

  • Asset screening: Identify chronically flaring facilities before making acquisition decisions
  • ESG due diligence: Verify operator emissions claims against satellite evidence
  • Competitive intelligence: Track completion activity across the basin
  • Regulatory monitoring: Flag facilities that may be out of compliance

Limitations

Sentinel-2 revisits every 3–5 days, and cloud cover reduces usable passes further. Intermittent flaring events will be missed. For daily monitoring, commercial providers like Planet Labs offer higher cadence at a cost.


Tools used: Python, Google Earth Engine, Sentinel-2 L2A, Texas RRC bulk data, pandas, geopandas