Field Boundaries

last updated: April 26, 2024

Planet Field Boundaries is used to obtain agricultural field boundaries over an Area of Interest (AOI). The polygons are the input to a multitude of applications, ranging from the management of agricultural resources, such as Area Monitoring, precision farming, to the estimation of damages to crop yield due to natural disasters, like drought and floods, or human-made disasters, like war.

Automatic estimation of parcels with high fidelity in a timely manner allows for characterizing changes of agricultural landscapes due to agricultural practices and climate change consequences.

Using the Subscriptions API, you can access, download, and prepare data for analysis and visualization.

Workflow: Obtain and Visualize Field Boundaries over an AOI in Northern France

Setting up your script and connecting with Planet services

To execute the code in this example, you will need:

  • A Planet API Key
  • Access to the FIELD_BOUNDARIES_v1.0.0_S2_P1M product for the provided field geometry
  • Configured credentials for storage of the results to cloud storage. Supported services include: Google Cloud Platform, Amazon Web Services, Microsoft Azure, or Oracle Collaboration Suite.

The code examples in this workflow are written for Python 3.8 or greater. In addition to the Python standard library, the following packages are required:

- rasterio
- cond
- geopandas
- python-dotenv
- matplotlib

Import necessary libraries and enter your API key. In this example, the Planet API Key is set and read using the Planet Github library.

# Import requirements
import requests
import geopandas as gpd
from dotenv import dotenv_values
from shapely.geometry import mapping as geom_mapping
from shapely.geometry import Polygon, MultiPolygon
from shapely import wkt
from typing import Dict
import datetime as dt
from matplotlib import pyplot as plt
from requests.auth import HTTPBasicAuth

DOT_ENV_VALS = dotenv_values(".env")
PL_API_KEY = DOT_ENV_VALS["PL_API_KEY"]

Call Planet services to confirm your API key. You should receive an HTTP 200 response:

# Planet's Subscriptions API base URL for making restFUL requests
BASE_URL = "https://api.planet.com/subscriptions/v1"


auth = HTTPBasicAuth(PL_API_KEY, '')
response = requests.get(BASE_URL, auth=auth)
print(response)

Creating a Planetary Variables Subscription with the Subscriptions API

To create a subscription, provide a JSON request object that details the subscription parameters, including:

  • Subscription name (required)
  • Planetary Variable source type (required)
  • Data product ID (required)
  • Subscription location in GeoJSON format (required)
  • Start date for the subscription (required)
  • End date for the subscription (optional)

See Create a Planetary Variables Subscription in Subscribing to Planetary Variables for details about available parameters.

Create your JSON Subscription Description Object

The following example creates a subscription for Field Boundaries over an AOI in Northern France.

To confirm if the provided geometry fits into a specific area of access (AOA) see the following code example. Please note that the geometry is in (geo)json format, and the expected coordinate reference system is WGS84 (EPSG:4326).

A Subscription must be created with a delivery parameter which specifies a storage location to deliver the vector results. The following example creates a subscription with a delivery parameter to deliver results directly to an AWS storage bucket.

Refer to the documentation for the required AWS delivery parameters. If you use GCP, Azure, or OCS, use the appropriate credentials for those platforms.

Let's define some helper functions for working with the Subscriptions API:

def _parse_time(t: str):
    return dt.datetime.fromisoformat(t).strftime("%Y-%m-%dT%H:%M:%S.%fZ")

def create_payload(
    name: str, 
    time_interval: tuple[str, str],
    geometry: dict | Polygon | MultiPolygon,
    delivery_config: dict | None = None,
) -> dict:

    start_time = _parse_time(time_interval[0])
    end_time = _parse_time(time_interval[1])
    _geometry = geometry if isinstance(geometry, dict) else geom_mapping(geometry)

    payload = {
        "name": name,
        "source": {
            "type": "field_boundaries_sentinel_2_p1m",
            "parameters": {
                "id": "FIELD_BOUNDARIES_v1.0.0_S2_P1M",
                "start_time": start_time,
                "end_time": end_time,
                "geometry": _geometry,
            },
        }
    }

    if delivery_config:
        payload["delivery"] = delivery_config

    return payload

def create_subscription(subscription_payload: dict, auth: HTTPBasicAuth) -> str:
    headers = {"content-type": "application/json"}
    try:
        response = requests.post(BASE_URL, json=subscription_payload, auth=auth, headers=headers)
        response.raise_for_status()
    except requests.exceptions.HTTPError:
        print(f"Request failed with {response.text}")
    else:
        response_json = response.json()
        subscription_id = response_json["id"]
        print(f"Successfully created new subscription with ID={subscription_id}")
        return subscription_id

def get_subscription_status(subscription_id: str, auth: HTTPBasicAuth) -> str:
    subscription_url = f"{BASE_URL}/{subscription_id}"
    response = requests.get(subscription_url, auth=auth)
    response_json = response.json()
    return response_json.get("status")

In the following code snippets we set up the input parameters for our Field Boundaries subscription. Feel free to tweak the parameters (e.g., change subscription_name, use your own geometry, etc.). Note that if the delivery is something else other than an Amazon S3 bucket, the delivery_config needs to be modified appropriately, as per documentation.

subscription_name = "fb-example-run" 

geometry = {
    "type": "Polygon",
    "coordinates": [[
        [3.04892014150768, 50.01307542230753],
        [3.04892014150768, 49.96334042385603],
        [3.099216060994349, 49.9632157747371],
        [3.099278385553812, 50.013013097748065],
        [3.04892014150768, 50.01307542230753]
    ]]
}

delivery_config = {
    "type": "amazon_s3",
    "parameters": {
        "bucket": "<FILLED BY USER>", 
        "aws_region": "<FILLED BY USER>>",
        "aws_access_key_id": "<FILLED BY USER>",
        "aws_secret_access_key": "<FILLED BY USER>",
        "path_prefix": "<FILLED BY USER>"
    }
}

time_interval = ("2023-05-01", "2023-06-01")

Now it's time to create the payload for the Subscriptions API and check if everything is ok:

payload = create_payload(
    subscription_name, 
    time_interval=time_interval, 
    geometry=geometry,
    delivery_config=delivery_config
)
print(payload)

You can also use the Features API to specify geometry of the subscription. In this case, the following could be used for geometry:

geometry = {
    "content": feature_reference, # i.e. "pl:features/[dataset]/[collection-id]/[feature-id]"
    "type": "ref"
}

Submitting the request for Field Boundaries

In the following snippet, the details (payload) we have set before are sent to the Subscriptions API to create a new subscription and receive its unique subscription ID.

# Create a New Subscription
subscription_id = create_subscription(payload, auth)
print(subscription_id)

Confirm the Subscription Status

To retrieve the status of the subscription, we need to make a GET request to the subscription endpoint. Once it is in a running or completed state, the delivery should either be in progress or completed, respectively. A subscription with an end date in the future remains in running state until the end_date is in the past. Refer to status descriptions for a complete overview of possible status descriptions.

status = get_subscription_status(subscription_id, auth)
print(status)

Retrieving and analyzing the subscription data

GeoPandas can be used to load the vector results directly. Additionally, we can load the results using any standard GIS software tools (such as QGIS, ArcGIS, etc.). We recommend downloading the files locally and loading, however they can be read directly from the cloud storage bucket using GeoPandas.

The file is delivered in the corresponding folder. The folder path is specified by the subscription name, subscription id, subscription start year, subscription start month and subscription start day.

subscription_start_year= time_interval[0].split("-")[0]
subscription_start_month = time_interval[0].split("-")[1]
subscription_start_day = time_interval[0].split("-")[2]
subs_dir = f"{subscription_name}/{subscription_id}/{subscription_start_year}/{subscription_start_month}/{subscription_start_day}"

print(subs_dir)

The filename of the results is set as the following:

fb_start =  dt.datetime.fromisoformat(time_interval[0]).strftime('%Y%m%dT%H%M%SZ')
fb_filename = f"FIELD_BOUNDARIES_v1.0.0_S2_P1M-{fb_start}_fb.gpkg"

print(fb_filename)

Read and plot the field boundaries using GeoPandas.

# Assumes the results are copied locally to the folder from which the code is run. If results are elsewhere, path needs to be adapted appropriately. 
fb = gpd.read_file(fb_filename) 

# Check the dataframe. 
fb.head() 

# Plot the results 
fig, ax = plt.subplots(figsize=(20, 20))
fb.plot(ax=ax)
plt.show()

Additionally, a vector file with data availability map is produced:

data_availability_filename = f"FIELD_BOUNDARIES_v1.0.0_S2_P1M-{fb_start}_da.gpkg"
print(data_availability_filename)

# Assumes the results are copied locally to the folder from which the code is run. If results are elsewhere, path needs to be adapted appropriately. 
da = gpd.read_file(data_availability_filename) 

# Check the dataframe.
da.head() 

# Plot the data availability map, showing which grid cells had valid observations available in the requested time period  
fig, ax = plt.subplots(figsize=(20, 20))
da.plot(ax=ax, column="available_data", alpha=0.4, legend=True)
plt.show()

 

Learning Resources

Get the details in the Field Boundaries Technical Specification. Read about Planet APIs at Get Started with Planet APIs. Find a collection of guides and tutorials on Planet University. Also, checkout Planet notebooks on GitHub, such as the Subscriptions tutorials: subscriptions_api_tutorial.

 


Rate this guide: