Temporal Operations
This page provides an overview of several temporal processes offered by openEO for analyzing and manipulating time dimension in an EO application. These processes can be handy for tasks such as filtering observations by date, building temporal composites, and applying calculations along the time dimension.
Note: Not all temporal processes might be covered in this page. For a complete list, refer to the openEO processes documentation.
The buttons above let you filter processes supported by different backends. Selecting or deselecting a backend will show or hide the relevant sections in the documentation. However, please note that it is based on the latest documentation rendering. Thus, please refer to the openEO Hub for the most up-to-date information.
Select a time range
When working with the satellite data, the temporal frequency of observation can vary for different sensors and acquisition strategies. While some sensors may provide daily observations, others might only capture data every few weeks. To make it easier for analysis, openEO provides temporal filtering capabilities through the filter_temporal process. While user can define temporal extent when loading the collection using load_collection, filter_temporal allows further refinement of the time range.
It limits the data cube to the specified interval of dates and/or times. More precisely, the filter checks whether each of the temporal dimension labels is greater than or equal to the lower boundary (start date/time) and less than the value of the upper boundary (end date/time). This corresponds to a left-closed interval, which contains the lower boundary but not the upper boundary.
import openeo
connection = openeo.connect("openeofed.dataspace.copernicus.eu").authenticate_oidc()
cube = connection.load_collection("SENTINEL2_L2A", bands=["B04", "B08"])
growing_season = cube.filter_temporal(["2024-04-01", "2024-10-01"])library(openeo)
connection <- connect("openeofed.dataspace.copernicus.eu") %>% authenticate_oidc()
cube <- connection %>% load_collection("SENTINEL2_L2A", bands=c("B04", "B08"))
growing_season <- cube %>% filter_temporal(c("2024-04-01", "2024-10-01"))import OpenEO from "openeo-js-client";
const connection = await OpenEO.connect("openeofed.dataspace.copernicus.eu").authenticate_oidc();
const cube = connection.load_collection("SENTINEL2_L2A", { bands: ["B04", "B08"] });
const growing_season = cube.filter_temporal(["2024-04-01", "2024-10-01"]);This results in a datacube restricted to the specified temporal extent. The dimensions and dimension properties (name, type, labels, reference system and resolution) remain unchanged, except that the temporal dimensions (determined by dimensions parameter) may have less dimension labels.
Aggregate data based on temporal periods
It is often noticed that clouds and irregular acquisition dates makes satellite data difficult to compare or there are usecases where one is interested in aggregating data over specific temporal periods. For such scenarios, aggregate_temporal_period is particularly useful. The aggregate_temporal_period process computes a temporal aggregation based on calendar hierarchies such as years, months or seasons.
For each interval, all data along the dimension will be passed through the reducer. If the dimension is not set or is set to null, the data cube is expected to only have one temporal dimension.
monthly_median = growing_season.aggregate_temporal_period(
period="month",
reducer="median",
)
monthly_median.download("monthly_median.tif", format="GTiff")monthly_median <- growing_season %>% aggregate_temporal_period(
period="month",
reducer="median"
)
download_result(monthly_median, "monthly_median.tif", format="GTiff")const monthly_median = growing_season.aggregate_temporal_period({
period: "month",
reducer: "median"
});
await monthly_median.download("monthly_median.tif", { format: "GTiff" });Aggregate data over explicit time intervals
In contrast to aggregate_temporal_period, which aggregates over regular calendar periods, aggregate_temporal allows you to define custom intervals for aggregation. It computes a temporal aggregation based on an array of date and/or time intervals.
Calendar hierarchies such as year, month, week etc. must be transformed into specific intervals by the clients. For each interval, all data along the dimension will be passed through the reducer. The computed values will be projected to the labels, so the number of labels and the number of intervals need to be equal.
seasonal <- growing_season %>% aggregate_temporal(
intervals=list(c("2024-04-01", "2024-06-01")),
reducer="mean"
)const seasonal = growing_season.aggregate_temporal({
intervals: [["2024-04-01", "2024-06-01"]],
reducer: "mean"
});Reduce time dimension
The reduce_dimension process with dimension=“t” collapses the time dimension by applying a specified reducer, such as “max” or “mean”. In other words when using reduce_dimension with dimension="t", you end up with a single raster for each band, representing the aggregated result over the entire time series. This process is useful for generating summary statistics or single-value representations of temporal data.
seasonal_maximum = growing_season.reduce_dimension(
dimension="t",
reducer="max",
)seasonal_maximum <- growing_season %>% reduce_dimension(
dimension="t",
reducer="max"
)const seasonal_maximum = growing_season.reduce_dimension({
dimension: "t",
reducer: "max"
});aggregate_temporal_period retains a time dimension with one label per period. reduce_dimension removes the reduced dimension entirely. Choose the former for a monthly series and the latter for a single seasonal product.
Align temporal labels
The process resample_cube_temporal can be used to align the temporal labels of one cube with another, ensuring that observations from different cubes correspond to the same points in time. Resamples one or more given temporal dimensions from a source data cube to align with the corresponding dimensions of the given target data cube using the nearest neighbor method. Returns a new data cube with the resampled dimensions.
By default, this process simply takes the nearest neighbor independent of the value (including no-data values). Depending on the data cubes this may lead to values being assigned to two target timestamps. To only consider valid values in a specific range around the target timestamps, use the parameter valid_within.
aligned = source.resample_cube_temporal(target)aligned <- source %>% resample_cube_temporal(target)const aligned = source.resample_cube_temporal(target);Apply a process along time
Use apply_dimension with dimension="t" when every pixel needs the same custom time-series calculation, such as smoothing or a threshold-based event detector. The callback describes an openEO graph; it is not arbitrary local Python execution.
smoothed = cube.apply_dimension(
dimension="t",
process=lambda series: series.median(),
)Count observations over time
The count_time process counts the number of images with a valid mask in a time series for all bands of the input dataset. This is useful for understanding the data coverage and quality over time.
observation_count = cube.count_time()observation_count <- cube %>% count_time()const observation_count = cube.count_time();Fit a temporal curve
The fit_curve process uses non-linear least squares to fit a model function y = f(x, parameters) to data. It throws an InvalidValues exception if invalid values are encountered. It is recommended to use fit_curve when a time series should be represented by a model, such as a linear trend or seasonal curve. The fitted parameters can then be passed to predict_curve.
model = cube.fit_curve(reducer="linear", parameters={"order": 1})model <- cube %>% fit_curve(reducer="linear", parameters=list(order=1))const model = cube.fit_curve({reducer:"linear", parameters:{order:1}});Predict from a temporal curve
The process predict_curve predicts values using a model function and pre-computed parameters. The process is intended to compute values for new labels. It can be used to evaluate a model created by fit_curve at new timestamps or labels. This can fill a regular time axis or estimate values at dates not directly observed.
prediction = model.predict_curve(labels=["2025-01-01", "2025-07-01"])prediction <- model %>% predict_curve(labels=c("2025-01-01", "2025-07-01"))const prediction = model.predict_curve({labels:["2025-01-01", "2025-07-01"]});Calculate a climatological normal
Use climatological_normal to calculate a typical value for recurring periods, such as the mean NDVI for each month across several years. It provides the baseline needed for anomaly analysis. In climatology, the climatological normal period is usually a 30 year average of a weather variable. Climatological normals are used as an average or baseline to evaluate climate events and provide context for yearly, monthly, daily or seasonal variability.
normal = cube.climatological_normal(period="month", reducer="mean")normal <- cube %>% climatological_normal(period="month", reducer="mean")const normal = cube.climatological_normal({period:"month", reducer:"mean"});Calculate temporal anomalies
The anomaly process computes anomalies based on normals for temporal periods. It compares the data for each label in the temporal dimension with the corresponding data in the normals data cube by subtracting the normal from the data. Therefore, it is recommended to first calculate a climatological normal using climatological_normal before computing anomalies. Positive and negative values then indicate conditions above or below the expected baseline.
anomalies = cube.anomaly(normal=normal)anomalies <- cube %>% anomaly(normal=normal)const anomalies = cube.anomaly({normal:normal});