Find and retrieve Sentinel-2 imagery

This notebook demonstrates how to: - intersect Sentinel-2 scenes with a flight track using BigQuery - retrieve and visualized Sentinel-2 scenes

This notebook relies on optional dependencies in the sat group (pip install pycontrails[sat]).

[1]:
import matplotlib.pyplot as plt
import numpy as np
import pandas as pd
import pyproj
from matplotlib import dates

from pycontrails.core import Flight
from pycontrails.datalib import sentinel

Intersect Sentinel-2 scenes with IAGOS flight

Intersecting Sentinel-2 scenes with flight tracks requires a Google Cloud account and project with access to the BigQuery API. Users without BigQuery API access can skip this section.

[2]:
df = pd.read_csv("data/iagos-flight-sentinel.csv")
df["time"] = df["time"].apply(lambda t: pd.Timestamp(t))
[3]:
flight = Flight(data=df).resample_and_fill("1min")
[4]:
scenes = sentinel.intersect(flight)
scenes
[4]:
base_url granule_id sensing_time
5 gs://gcp-public-data-sentinel-2/tiles/30/U/WG/... L1C_T30UWG_A014649_20191226T112436 2019-12-26 11:25:47.117000+00:00

Retrieve and visualize Sentinel-2 scene

This section retrieves Landsat imagery from a Google Cloud public dataset. Retrieval uses anonymous access, so this section of the notebook can be run by users without a Google Cloud account.

base_url, granule_id, and sensing_time are hard-coded so this section can be run independently of the previous section.

[5]:
base_url = "gs://gcp-public-data-sentinel-2/tiles/30/U/WG/S2B_MSIL1C_20191226T112359_N0208_R037_T30UWG_20191226T115227.SAFE"
granule_id = "L1C_T30UWG_A014649_20191226T112436"
sensing_time = pd.Timestamp("2019-12-26 11:25:47.117000+00:00")

The IAGOS flight is in an ISSR at the time of imagery acquisition:

[6]:
plt.plot(df["time"], df["rhi"], "k-")
plt.ylabel("RHi (nondim)")
plt.gca().xaxis.set_major_formatter(dates.DateFormatter("%m/%d\n%H:%M"))
plt.gca().axhline(y=1, color="gray", ls="--")
plt.gca().axvline(x=sensing_time, color="gray", ls="--");
../_images/notebooks_Sentinel_9_0.png

True-color image

We first download bands 02, 03, and 04. These are used to create a true-color image.

Note that handler.get transfers satellite imagery from a Google Cloud bucket to your machine. This may take a while if you are running this notebook locally rather than on a Google Cloud VM.

[7]:
handler = sentinel.Sentinel(base_url, granule_id, bands=["B02", "B03", "B04"])
ds = handler.get()
ds
[7]:
<xarray.Dataset> Size: 1GB
Dimensions:  (y: 10980, x: 10980)
Coordinates:
  * y        (y) float64 88kB 6.2e+06 6.2e+06 6.2e+06 ... 6.09e+06 6.09e+06
  * x        (x) float64 88kB 5e+05 5e+05 5e+05 ... 6.098e+05 6.098e+05
Data variables:
    B02      (y, x) float32 482MB 0.9962 0.985 1.009 ... 0.8718 0.8674 0.8598
    B03      (y, x) float32 482MB 0.8292 0.832 0.831 ... 0.7194 0.7134 0.7148
    B04      (y, x) float32 482MB 0.933 0.9188 0.9322 ... 0.7936 0.8018 0.7992

We then generate and plot the true-color image.

[8]:
rgb, crs, extent = sentinel.extract_sentinel_visualization(ds, color_scheme="true")
[9]:
# crs artifact can be used to project flight latitude and longitude into image coordinates
proj = pyproj.Transformer.from_crs(crs.geodetic_crs, crs)
flight_lon = np.interp(
    sensing_time.timestamp(),
    df["time"].apply(lambda t: pd.Timestamp(t).timestamp()),
    df["longitude"],
)
flight_lat = np.interp(
    sensing_time.timestamp(),
    df["time"].apply(lambda t: pd.Timestamp(t).timestamp()),
    df["latitude"],
)
flight_x, flight_y = proj.transform(flight_lat, flight_lon)
[10]:
# seams in the image are from different detectors in the Sentinel-2 sensor
plt.figure(figsize=(10, 10))
plt.imshow(rgb, extent=extent)
plt.plot(flight_x, flight_y, "ro")
plt.xlabel("Easting (m)")
plt.ylabel("Northing (m)");
../_images/notebooks_Sentinel_15_0.png

If we zoom in, we can see the aircraft and contrail! The aircraft is offset slightly from its projected position because Sentinel-2 imagery is geolocated at the surface.

[11]:
plt.figure(figsize=(10, 10))
plt.imshow(rgb, extent=extent)
plt.plot(flight_x, flight_y, "ro")
plt.xlim([flight_x - 4e3, flight_x + 4e3])
plt.ylim([flight_y - 4e3, flight_y + 4e3])
plt.xlabel("Easting (m)")
plt.ylabel("Northing (m)");
../_images/notebooks_Sentinel_17_0.png

Cirrus band

Sentinel-2 does not have true longwave bands (i.e., bands where top-of-atmosphere radiances are dominated by emission rather than reflection). However, aircraft and contrails often show up clearly in the cirrus band (B10), which is designed to isolate high clouds.

This section also demonstrates plotting with the xarray Dataset returned by Sentinel.get.

[12]:
handler = sentinel.Sentinel(base_url, granule_id, bands=["B10"])
ds = handler.get()
[13]:
crs = ds["B10"].attrs["crs"]
proj = pyproj.Transformer.from_crs(crs.geodetic_crs, crs)
flight_x, flight_y = proj.transform(flight_lat, flight_lon)
[14]:
plt.figure(figsize=(12, 10))
ds["B10"].plot(vmin=0.3, vmax=0.5)
plt.gca().set_aspect("equal", "box")
plt.plot(flight_x, flight_y, "ko")
plt.xlim([flight_x - 4e3, flight_x + 4e3])
plt.ylim([flight_y - 4e3, flight_y + 4e3])
plt.xlabel("Easting (m)")
plt.ylabel("Northing (m)");
../_images/notebooks_Sentinel_21_0.png