Get Started

Contrails API is a RESTful API that provides contrail modeling and mitigation tools.

This notebook shows basic usage of Contrails API from a Python notebook using the requests package to send HTTP requests.

You can interact with Contrails API with any HTTP compatible client (i.e. curl, Postman).

Authorization

  • Your API key must be provided in x-api-key request header.

  • In this notebook, we pluck it from a preset environment variable.

  • Similarly, we parametrize the URL endpoint as an environment variable. The production API base url for the v0 API is https://api.contrails.org/v0.

  • Contact api@contrails.org to request a user account.

[1]:
import os
from pprint import pprint
[2]:
URL = os.environ["API_URL_BASE"]  # https://api.contrails.org/v0
api_key = os.environ["CONTRAILS_API_KEY"]
headers = {"x-api-key": api_key}

Trajectory API

Endpoints in the /trajectory/ API each require POST requests. The POST body defines an individual flight: A sequence of discrete temporal spatial waypoints describing the 1D flight trajectory. See the Fleet Computation guide for posting multiple distinct flights in one request.

The request body defines the parameterization of the flight into discrete waypoints. Responses use the same parameterization defined by the request body. For example, if a flight with 85 waypoints is passed into the /trajectory/issr endpoint, the response object will contain 85 ISSR predictions. These are in one-to-one corresponding with the request body.

We create a synthetic flight to use in this notebook as an example.

Not every field defined in the flight dictionary below is required for each endpoint. Apart from the four necessary temporal spatial fields (longitude, latitude, altitude, time), additional aircraft performance variables (engine_efficiency, aircraft_mass, …) can be array-like (of the same length as the temporal spatial fields) or scalar-like if a constant value is to be used for all waypoints.

[3]:
import matplotlib.pyplot as plt  # pip install matplotlib
import numpy as np  # pip install numpy
import pandas as pd  # pip install pandas
[4]:
n_waypoints = 100
t0 = "2022-06-07T00:15:00"
t1 = "2022-06-07T02:30:00"

flight = {
    "longitude": np.linspace(-29, -50, n_waypoints).tolist(),
    "latitude": np.linspace(45, 42, n_waypoints).tolist(),
    "altitude": np.linspace(33000, 38000, n_waypoints).tolist(),
    "time": pd.date_range(t0, t1, periods=n_waypoints).astype(str).tolist(),
    "engine_efficiency": np.random.default_rng(42).uniform(0.2, 0.4, n_waypoints).tolist(),
    "aircraft_mass": np.linspace(65000, 62000, n_waypoints).tolist(),
    "aircraft_type": "A320",
}

SAC

POST/trajectory/sac

This endpoint calculates Schmidt-Appleman contrail formation criteria (SAC) along a flight trajectory.

The SAC is a binary model that indicates whether the flight forms an initial contrail. In particular, we use the following conventions.

  • A value of 1 indicates the waypoint satisfies the SAC.

  • A value of 0 indicates the waypoint does not satisfy the SAC.

  • A null value indicates that the SAC state is not known for the waypoint. This most often occurs at terminal waypoints when engine efficiency is not known, or when the waypoint is not contained within the domain of the meteorology data.

Engine efficiency is a critical parameter for the SAC. If the engine_efficiency field is not provided, it calculated via an aircraft performance model from the flight trajectory and the aircraft_type field.

[5]:
import requests

r = requests.post(f"{URL}/trajectory/sac", json=flight, headers=headers)
print(f"HTTP Response Code: {r.status_code} {r.reason}\n")

r_json = r.json()
for k, v in r_json.items():
    print(f"{k}: {v}")
HTTP Response Code: 200 OK

flight_id: 1720721133030796
met_source_provider: ECMWF
met_source_dataset: ERA5
met_source_product: reanalysis
pycontrails_version: 0.52.1
humidity_scaling_name: histogram_matching
humidity_scaling_formula: era5_quantiles -> iagos_quantiles
sac: [1.0, 0.0, 1.0, 1.0, 0.0, 1.0, 1.0, 1.0, 0.0, 0.0, 0.0, 1.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 1.0, 1.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 1.0, 1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 1.0, 1.0, 0.0, 1.0, 1.0, 1.0, 1.0, 1.0, 0.0, 1.0, 0.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0]
[6]:
flight_df = pd.DataFrame(flight).assign(sac=r_json["sac"])
flight_df.plot.scatter(x="longitude", y="latitude", c="sac", cmap="bwr", s=3);
../_images/notebooks_get_started_9_0.png

ISSR

POST/trajectory/issr

This endpoint calculates ice super-saturated regions (ISSR) along a flight trajectory. Waypoints for which the ambient atmosphere has relative humidity over ice greater than 100% are considered to be in an ISSR.

We use the same value conventions as with the SAC model.

[7]:
r = requests.post(f"{URL}/trajectory/issr", json=flight, headers=headers)
print(f"HTTP Response Code: {r.status_code} {r.reason}\n")

r_json = r.json()
for k, v in r_json.items():
    print(f"{k}: {v}")
HTTP Response Code: 200 OK

flight_id: 1720721137499696
met_source_provider: ECMWF
met_source_dataset: ERA5
met_source_product: reanalysis
pycontrails_version: 0.52.1
humidity_scaling_name: histogram_matching
humidity_scaling_formula: era5_quantiles -> iagos_quantiles
issr: [1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0]
[8]:
flight_df["issr"] = r_json["issr"]
flight_df.plot.scatter(x="longitude", y="latitude", c="issr", cmap="bwr", s=1);
../_images/notebooks_get_started_12_0.png

CoCiP

POST/trajectory/cocip

The /trajectory/cocip endpoint implements the Contrail Cirrus Prediction (CoCiP) model published in Schumann 2012 and Schumann et al 2012. The API implementation includes updates from Schumann 2015, Teoh 2020, and Teoh 2022.

The CoCiP model requires more meteorology data and compute time than the other models. Consequently, requesting predictions from the /trajectory/cocip endpoint takes some time.

[9]:
r = requests.post(f"{URL}/trajectory/cocip", json=flight, headers=headers)
print(f"HTTP Response Code: {r.status_code} {r.reason}\n")
r_json = r.json()

# The /trajectory/cocip endpoint includes many fields in the response.
for k, v in r_json.items():
    v = str(v)
    if len(v) > 80:
        v = v[:77] + "..."
    print(f"{k}: {v}")
HTTP Response Code: 200 OK

cocip_max_contrail_age: 12 hours
cocip_dt_integration: 10 minutes
flight_id: 1720721142008133
met_source_provider: ECMWF
met_source_dataset: ERA5
met_source_product: reanalysis
pycontrails_version: 0.52.1
nvpm_data_source: ICAO EDB
engine_uid: 01P08CM105
humidity_scaling_name: histogram_matching
humidity_scaling_formula: era5_quantiles -> iagos_quantiles
sac: [1.0, 0.0, 1.0, 1.0, 0.0, 1.0, 1.0, 1.0, 0.0, 0.0, 0.0, 1.0, 0.0, 1.0, 0.0, 0...
nox_ei: [0.0076, 0.0076, 0.0076, 0.0076, 0.0076, 0.0076, 0.0076, 0.0076, 0.0076, 0.00...
nvpm_ei_n: [647000000000000.0, 647000000000000.0, 648000000000000.0, 649000000000000.0, ...
energy_forcing: [0.0, 0.0, 680000000000.0, 0.0, 0.0, 790000000000.0, 540000000000.0, 0.0, 0.0...
contrail_age: [0.0, 0.0, 112.0, 0.0, 0.0, 128.0, 127.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, ...
initially_persistent: [1.0, 0.0, 1.0, 1.0, 0.0, 1.0, 1.0, 1.0, 0.0, 0.0, 0.0, 1.0, 0.0, 1.0, 0.0, 0...
[10]:
# Energy forcing is the primary model output.
ef = np.array(r_json["energy_forcing"], dtype=float)
ef
[10]:
array([0.00e+00, 0.00e+00, 6.80e+11, 0.00e+00, 0.00e+00, 7.90e+11,
       5.40e+11, 0.00e+00, 0.00e+00, 0.00e+00, 0.00e+00, 0.00e+00,
       0.00e+00, 0.00e+00, 0.00e+00, 0.00e+00, 0.00e+00, 0.00e+00,
       0.00e+00, 0.00e+00, 0.00e+00, 0.00e+00, 6.10e+11, 5.40e+11,
       0.00e+00, 0.00e+00, 0.00e+00, 0.00e+00, 0.00e+00, 0.00e+00,
       0.00e+00, 0.00e+00, 0.00e+00, 0.00e+00, 0.00e+00, 0.00e+00,
       0.00e+00, 0.00e+00, 0.00e+00, 0.00e+00, 0.00e+00, 0.00e+00,
       0.00e+00, 0.00e+00, 2.90e+11, 1.74e+12, 1.32e+12, 1.68e+12,
       0.00e+00, 0.00e+00, 0.00e+00, 0.00e+00, 2.92e+12, 2.54e+12,
       1.51e+12, 1.03e+12, 6.30e+11, 1.90e+11, 8.00e+10, 5.00e+10,
       5.00e+10, 6.00e+10, 6.00e+10, 7.00e+10, 1.20e+11, 1.50e+11,
       1.90e+11, 2.10e+11, 1.90e+11, 1.40e+11, 9.00e+10, 6.00e+10,
       2.00e+10, 0.00e+00, 0.00e+00, 0.00e+00, 0.00e+00, 0.00e+00,
       0.00e+00, 0.00e+00, 0.00e+00, 0.00e+00, 0.00e+00, 0.00e+00,
       0.00e+00, 0.00e+00, 0.00e+00, 0.00e+00, 0.00e+00, 0.00e+00,
       0.00e+00, 0.00e+00, 0.00e+00, 0.00e+00, 0.00e+00, 0.00e+00,
       0.00e+00, 0.00e+00, 0.00e+00, 0.00e+00])
[11]:
flight_df["ef"] = ef
flight_df.plot.scatter(x="longitude", y="latitude", c="ef", cmap="Reds", s=3);
../_images/notebooks_get_started_16_0.png

Emissions

POST/trajectory/emissions

The /trajectory/emissions endpoint implements the aircraft performance models described in Teoh 2020 and Teoh 2022.

Presently, the emissions endpoint provides per-waypoint predictions for NOx and nvPM emissions. These values are derived from the ICAO Aircraft Engine Emissions Databank. An engine_uid field can be supplied to specify the engine type to query the databank. If not provided, the API will assume the engine is the most common engine associated with the aircraft type. Below, we don’t supply the engine_uid, and so it is included in the response.

[12]:
r = requests.post(f"{URL}/trajectory/emissions", json=flight, headers=headers)
print(f"HTTP Response Code: {r.status_code} {r.reason}\n")

r_json = r.json()
for k, v in r_json.items():
    v = str(v)
    if len(v) > 80:
        v = v[:77] + "..."
    print(f"{k}: {v}")
HTTP Response Code: 200 OK

flight_id: 1720721165741892
met_source_provider: ECMWF
met_source_dataset: ERA5
met_source_product: reanalysis
pycontrails_version: 0.52.1
nvpm_data_source: ICAO EDB
engine_uid: 01P08CM105
humidity_scaling_name: histogram_matching
humidity_scaling_formula: era5_quantiles -> iagos_quantiles
nox_ei: [0.0076, 0.0076, 0.0076, 0.0076, 0.0076, 0.0076, 0.0076, 0.0076, 0.0076, 0.00...
nvpm_ei_n: [647000000000000.0, 647000000000000.0, 648000000000000.0, 649000000000000.0, ...
[13]:
time = pd.to_datetime(flight["time"])

fig, ax = plt.subplots(figsize=(8, 4))
ax.plot(time, r_json["nox_ei"], label="nox_ei")

ax2 = ax.twinx()
ax2.plot(time, r_json["nvpm_ei_n"], color="orange", label="nvpm_ei_n")

lines1, labels1 = ax.get_legend_handles_labels()
lines2, labels2 = ax2.get_legend_handles_labels()
ax.legend(lines1 + lines2, labels1 + labels2, loc=0)

ax.set_ylabel("NOx emissions index")
ax2.set_ylabel("Nonvolatile particulate matter emissions index number")
ax.set_title("Emissions index for NOx and Nonvolatile Particulate Matter");
../_images/notebooks_get_started_19_0.png

Grid API

Grid endpoints evaluate a model of interest over a 4D temporal spatial grid. Presently, each grid endpoint requires a single timestamp, and so the grid’s time coordinate contains a single value.

Unlike the trajectory endpoints, each grid endpoint is a GET request requiring query parameters. Commonly used query parameters include:

  • time: the timestamp for the grid. Must be an ISO 8601 datetime string or unix timestamp (seconds since epoch).

  • flight_level: one or multiple flight levels used to downselect the vertical coordinate of the grid. Must be a comma-separated string or list of integers. Available flight levels are 270, 280, 290, 300, 310, 320, 330, 340, 350, 360, 370, 380, 390, 400, 410, 420, 430, 440. If not provided, all flight levels are included in the response.

  • bbox: a horizontal bounding box for the grid. Must be a comma-separated string or list of length 4. The order of the coordinates is [min_lon, min_lat, max_lon, max_lat]. The default is the bounding box of the entire domain (most of the entire globe).

  • format: the format of the response data. This is a case-sensitive string (must be lower-case). Valid choices are:

    • netcdf: the response is a netCDF file. This is the default.

    • json: the response is a JSON representation of the netCDF file. This format is not recommended.

    • geojson: the response is GeoJSON polygons. See below for examples of GeoJSON responses.

    • kml: the response is a KML representation of the GeoJSON polygons.

Examples of netCDF and GeoJSON responses are shown below.

SAC

GET/grid/sac

The SAC, ISSR, and PCR grid endpoints all use similar conventions. We demonstrate the SAC grid endpoint here.

This endpoint accepts an optional engine_efficiency parameter that takes a default value of 0.3.

[14]:
import xarray as xr  # pip install xarray
[15]:
time = "2022-06-07T02"
bbox = "-50,0,50,50"
params = {"time": time, "bbox": bbox, "engine_efficiency": 0.32}
r = requests.get(f"{URL}/grid/sac", headers=headers, params=params)
print(f"HTTP Response Code: {r.status_code} {r.reason}")
print(f"Response content-type: {r.headers['content-type']}")
HTTP Response Code: 200 OK
Response content-type: application/netcdf

netCDF Response Format

The default response format is netCDF. For the SAC endpoint, the 4D grid holds a single sac variable. The response can be written out and read with xarray.

[16]:
with open("sac.nc", "wb") as f:
    f.write(r.content)

da = xr.open_dataarray("sac.nc", engine="netcdf4")  # pip install netCDF4
da.coords  # each netCDF served in the API is a 4D grid
[16]:
Coordinates:
  * time          (time) datetime64[ns] 2022-06-07T02:00:00
  * flight_level  (flight_level) int32 270 280 290 300 310 ... 410 420 430 440
  * longitude     (longitude) float32 -50.0 -49.75 -49.5 ... 49.5 49.75 50.0
  * latitude      (latitude) float32 0.0 0.25 0.5 0.75 ... 49.25 49.5 49.75 50.0
[17]:
# The grid contains 18 flight levels ranging from 270 to 440.
da.flight_level.values
[17]:
array([270, 280, 290, 300, 310, 320, 330, 340, 350, 360, 370, 380, 390,
       400, 410, 420, 430, 440], dtype=int32)
[18]:
# We "squeeze" on time and select a single flight level to plot.
da.squeeze("time").isel(flight_level=10).plot(x="longitude", y="latitude", cmap="bwr");
../_images/notebooks_get_started_27_0.png

GeoJSON Response Format

Contrails API supports two types of polygon formats: GeoJSON and KML.

We demonstrate the same grid endpoint using the GeoJSON representation here.

See the polygon documentation for additional examples including custom polygon simplification.

[19]:
params["format"] = "geojson"
r = requests.get(f"{URL}/grid/sac", headers=headers, params=params)

print(f"HTTP Response Code: {r.status_code} {r.reason}")
print(f"Response content-type: {r.headers['content-type']}")

r_json = r.json()
HTTP Response Code: 200 OK
Response content-type: application/json
[20]:
import shapely.geometry as sgeom  # pip install shapely
[21]:
# The response body is a GeoJSON FeatureCollection. Each feature contains polygons for each flight level.
print(f"GeoJSON type: {r_json['type']}")

# Extract a feature
feature = r_json["features"][8]
pprint(feature["properties"])  # print out the metadata

# Visualize with shapely
# Polygons can have both exterior and interior rings
polygons = sgeom.shape(feature["geometry"])
for poly in polygons.geoms:
    plt.plot(*poly.exterior.xy, color="red")  # color exterior red
    for interior in poly.interiors:
        plt.plot(*interior.xy, color="blue")  # and interior blue
GeoJSON type: FeatureCollection
{'description': 'Schmidt-Appleman contrail formation criteria',
 'engine_efficiency': 0.32,
 'humidity_scaling_formula': 'era5_quantiles -> iagos_quantiles',
 'humidity_scaling_name': 'histogram_matching',
 'level': 350,
 'level_long_name': 'Flight Level',
 'level_standard_name': 'FL',
 'level_units': 'hectofeet',
 'met_source_dataset': 'ERA5',
 'met_source_product': 'reanalysis',
 'met_source_provider': 'ECMWF',
 'name': 'sac',
 'polygon_iso_value': 0.5,
 'pycontrails_version': '0.52.1',
 'time': '2022-06-07T02:00:00Z'}
../_images/notebooks_get_started_31_1.png

CoCiP

GET/grid/cocip

The gridded CoCiP model is an abstraction of the original CoCiP model. Instead of working with a single flight trajectory, the gridded version starts with a 4D vector field of trajectory segments each assumed to be in a nominal cruising state. The gridded model then evolves the segments over time using the same rules as the classical model.

Given a fixed trajectory, the original CoCiP model gives a precise prediction of contrail climate forcing resulting from that trajectory. Consequently, the /trajectory/cocip endpoint should be favored when evaluating contrail forcing of a single flight. On the other hand, the gridded CoCiP model can be used to optimize an unknown trajectory over a 4D grid. The two models widely agree when a flight is in a nominal cruising state.

The /grid/cocip endpoints requires an aircraft_type query parameter for CoCiP initialization.

Unlike all other endpoints, the model underpinning the /grid/cocip endpoint has been precomputed. Only a limited set of 11 aircraft types are currently available.

  • A320

  • A20N

  • A321

  • A319

  • A21N

  • A333

  • A350

  • B737

  • B738

  • B789

  • B77W

netCDF Response Format

[22]:
params["aircraft_type"] = "A320"
params["format"] = "netcdf"

r = requests.get(f"{URL}/grid/cocip", params=params, headers=headers)
print(f"HTTP Response Code: {r.status_code} {r.reason}")
print(f"Content type: {r.headers['content-type']}")
HTTP Response Code: 200 OK
Content type: application/netcdf
[23]:
with open("cocipgrid.nc", "wb") as f:
    f.write(r.content)

ds = xr.open_dataset("cocipgrid.nc", engine="netcdf4")
ds.data_vars  # The CoCiP data comes with both energy forcing and contrail age
[23]:
Data variables:
    ef_per_m      (longitude, latitude, flight_level, time) float32 ...
    contrail_age  (longitude, latitude, flight_level, time) timedelta64[ns] ...
[24]:
# The energy forcing variable is the primary model output.
da = ds["ef_per_m"]

# Print some metadata present in the netCDF
pprint(ds.attrs | da.attrs)

# We "squeeze" on time and select a single flight level to plot.
da.squeeze("time").isel(flight_level=10).plot(
    x="longitude", y="latitude", vmin=-2e8, vmax=2e8, cmap="coolwarm"
);
{'aircraft_type': 'A320',
 'cocip_dt_integration': '5 minutes',
 'cocip_max_contrail_age': '12 hours',
 'humidity_scaling_formula': 'rhi -> (rhi / rhi_adj) ^ rhi_boost_exponent',
 'humidity_scaling_name': 'exponential_boost_latitude_customization',
 'long_name': 'Energy forcing per meter of flight trajectory',
 'met_source_dataset': 'ERA5',
 'met_source_product': 'reanalysis',
 'met_source_provider': 'ECMWF',
 'name': 'cocip',
 'pycontrails_version': '0.32.2',
 'units': 'J / m'}
../_images/notebooks_get_started_36_1.png

GeoJSON Response Format

We can specify an energy forcing threshold parameter when calling the endpoint. The response contains just polygons surrounding grid cells at which the ef_per_m exceeds the threshold.

In converting from the grid representation to the polygon representation, we exclude degenerate polygons and polygons whose area is doesn’t exceeds some minimal threshold. Excluding these edge cases allows us to focus on regions of high impact. See the polygon documentation for additional polygon simplification examples.

[25]:
params["format"] = "geojson"
params["threshold"] = 2e8
r = requests.get(f"{URL}/grid/cocip", headers=headers, params=params)

print(f"HTTP Response Code: {r.status_code} {r.reason}")
print(f"Response content-type: {r.headers['content-type']}")
r_json = r.json()
HTTP Response Code: 200 OK
Response content-type: application/json
[26]:
# Extract a feature
feature = r_json["features"][10]
pprint(feature["properties"])  # print out the metadata

# Visualize. We see polygons around regions of dark red in the previous plot.
polygons = sgeom.shape(feature["geometry"])
for poly in polygons.geoms:
    plt.plot(*poly.exterior.xy, color="red")  # color exterior red
    for interior in poly.interiors:
        plt.plot(*interior.xy, color="blue")  # and interior blue
{'aircraft_type': 'A320',
 'cocip_dt_integration': '5 minutes',
 'cocip_max_contrail_age': '12 hours',
 'humidity_scaling_formula': 'rhi -> (rhi / rhi_adj) ^ rhi_boost_exponent',
 'humidity_scaling_name': 'exponential_boost_latitude_customization',
 'level': 370,
 'level_long_name': 'Flight Level',
 'level_standard_name': 'FL',
 'level_units': 'hectofeet',
 'long_name': 'Energy forcing per meter of flight trajectory',
 'met_source_dataset': 'ERA5',
 'met_source_product': 'reanalysis',
 'met_source_provider': 'ECMWF',
 'name': 'ef_per_m',
 'polygon_iso_value': 200000000.0,
 'pycontrails_version': '0.32.2',
 'time': '2022-06-07T02:00:00Z',
 'units': 'J / m'}
../_images/notebooks_get_started_39_1.png

Data Availability

GET/grid/availability

Grid endpoints in Contrails API serve data with varying availability. The /grid/availability endpoint gives a range of times for which each /grid endpoint serves data.

[27]:
r = requests.get(f"{URL}/grid/availability", headers=headers)

print(f"HTTP Response Code: {r.status_code} {r.reason}")
pprint(r.json())
HTTP Response Code: 200 OK
{'cocip': ['2022-01-01T00:00:00Z', '2024-07-12T17:00:00Z'],
 'issr': ['2018-01-01T00:00:00Z', '2024-07-14T06:00:00Z'],
 'pcr': ['2018-01-01T00:00:00Z', '2024-07-14T06:00:00Z'],
 'sac': ['2018-01-01T00:00:00Z', '2024-07-14T06:00:00Z']}

Cleanup

[28]:
os.remove("sac.nc")
os.remove("cocipgrid.nc")