Skip to main content

Agriculture — precision farming

The problem. A farm is a patchwork of fields, each with soil sensors streaming moisture and temperature, and each periodically photographed from a drone or satellite to spot disease and growth variation. The agronomist needs the sensor trends and the imagery in one place, tied to the field they belong to, so a dry-soil reading and a stressed-crop image line up.

This scenario is the one where files matter as much as numbers: attach imagery and scouting reports to the field, alongside its sensor series.

Set up demo data

New workspace? Run this once (Python) to create the field's soil series with two days of readings that dry out toward the irrigation threshold. (The drone-scan upload in step 2 needs a local image file — any file works.) Safe to re-run.

import datahub_sdk, numpy as np, pandas as pd

client = datahub_sdk.DataHubClient.from_env()

for s, u in [("field_north_40_soil_moisture", "pct"), ("field_north_40_soil_temp_c", "deg_c")]:
client.timeseries.create([datahub_sdk.TimeSeries(external_id=s, name=s, unit=u)])
idx = pd.date_range(end=pd.Timestamp.now(tz="UTC"), periods=48, freq="1h")
moisture = 35 - np.linspace(0, 15, 48) + np.random.normal(0, 1, 48) # drying out
client.timeseries.insert_from_lists(timestamps=idx, values=moisture, ts="field_north_40_soil_moisture")

1. Model fields and stream soil sensors

A farm contains fields; each field's sensors are series.

var moisture = Timeseries.of("field_north_40_soil_moisture").name("North 40 soil moisture");
moisture.setUnit("pct");
var soilTemp = Timeseries.of("field_north_40_soil_temp_c").name("North 40 soil temperature");
soilTemp.setUnit("deg_c");
client.timeseries().create(moisture, soilTemp);

client.timeseries().ingest(Map.of(
"field_north_40_soil_moisture", moistureReadings)); // List<Datapoint>

2. Attach the drone imagery to the field

Upload each scan into the field's folder, tagged so it's discoverable next to the field's sensor data. See Attach files to assets for the upload details.

byte[] scan = Files.readAllBytes(Path.of("north_40_2026_06_28.tif"));

client.files().upload(
FileUploadRequest.builder()
.path("fields/north_40/scans/2026_06_28.tif")
.content(scan)
.contentType("image/tiff")
.externalId("scan_north_40_2026_06_28")
.description("Drone NDVI scan — North 40")
.source("drone_survey")
.build());

Later, list a field's folder to pull up its scan history alongside the soil trend:

client.files().list("/fields/north_40/scans")
.getItems().forEach(node -> System.out.println(node.getName()));

3. Trigger irrigation on a dry trend

When soil moisture stays below target through the heat of the day, raise an irrigation_needed event the irrigation controller acts on.

See the result

Chart the soil moisture — the downward trend is what triggers irrigation:

import matplotlib.pyplot as plt

rf = datahub_sdk.RetrieveFilter(ts="field_north_40_soil_moisture",
start=pd.Timestamp.now(tz="UTC") - pd.Timedelta(days=2), end=pd.Timestamp.now(tz="UTC"))
s = pd.Series({pd.to_datetime(p.timestamp): float(p.value)
for p in client.timeseries.retrieve_datapoints(rf)[0].get_datapoints()}).sort_index()
s.plot(title="North 40 soil moisture (%)"); plt.show()

See also