---
title: Clusters
description: "Candidate fires: centroids of spatially linked hotspots, how they are formed and merged, and the one-way active lifecycle."
---

`deepfire:clusters` — centroids of clusters of hotspots: **candidate fires**, current and
historical (since January 2025). Geometry: **Point** (EPSG:4326). Feature IDs:
`clusters.<uuid>`.

A cluster groups nearby detections into a single candidate fire, so this is the collection
to use when you want "fires" rather than raw per-satellite detections. See
[how clusters are formed](#how-clusters-are-formed).

## Attributes

| Property         | Type                 | Notes                                        |
| ---------------- | -------------------- | -------------------------------------------- |
| `id`             | uuid                 | Feature key (surfaced as `clusters.<uuid>`)  |
| `first_observed` | timestamp (ISO 8601) | First detection in the cluster               |
| `last_observed`  | timestamp (ISO 8601) | Most recent detection in the cluster         |
| `active`         | boolean              | Live working set vs. historical — [see below](#the-active-lifecycle) |

[Hotspots](/api/hotspots) and
[satellite perimeters](/api/satellite-perimeters) join back to this collection via
their `cluster_id` attribute.

## The `active` lifecycle

`active` is a lifecycle flag maintained by Deepfire's ingestion pipeline — it is written
as data changes, not evaluated per request:

- New detections are ingested `active = true` and grouped into clusters, which are
  created `active = true` as well.
- A background job runs hourly and **deactivates every cluster whose most recent
  detection (`last_observed`) is older than 24 hours**, together with all of that
  cluster's hotspots. `active = true` therefore means *"this fire has had at least one
  satellite detection within roughly the last 24 hours"* (up to ~25 h at the edge, since
  the check runs hourly).
- Deactivation is **one-way**: a cluster is never flipped back to active. If a fire is
  detected again at the same location later, it appears as a **new** cluster with a new
  `id` — not a revival of the old one.
- Bulk-imported historical archives are written directly as `active = false`, so
  `active = false` covers both burned-out fires and backfilled history — it does not
  imply the row was ever part of the live set.

The flag propagates outward from the cluster: [hotspots](/api/hotspots) carry
their owning cluster's state, and
[satellite perimeters](/api/satellite-perimeters) expose it at query time.

## How clusters are formed

Clustering is **spatial, single-linkage grouping of live detections**: two detections
belong to the same cluster when they are within **4.5 km** (great-circle distance) of each
other — directly, or through a chain of intermediate detections. There is no minimum
size; an isolated detection forms a cluster of one.

As detections are ingested (roughly every minute):

1. The new detections are first grouped among themselves: detections within 4.5 km of
   each other link up, and each connected group becomes a candidate cluster.
2. Each candidate is then compared against the **existing active clusters**. If any of
   its detections lies within 4.5 km of an active detection of an existing cluster, the
   candidate merges into that cluster — distance is measured between the nearest
   detections, not between centroids. Otherwise it is saved as a new cluster.
3. If a candidate bridges **two or more** existing active clusters, those clusters merge
   into one as well — see [cluster IDs and merges](#cluster-ids-and-merges).

All [satellite sources](/api/data-sources) feed the same pipeline, so a cluster
freely mixes detections from different satellites and resolutions.

Because the 4.5 km rule chains, a large fire front can form a single cluster spanning far
more than 4.5 km end-to-end — 4.5 km is the *link* distance between neighboring
detections, not a cap on cluster size.

Time enters only through the [`active` lifecycle](#the-active-lifecycle): only **active**
clusters participate in merging, and membership freezes once a cluster is deactivated.
That is why a re-burn at the same location months later becomes a new cluster rather than
extending the old one.

### Geometry and timestamps

- The Point geometry is the **centroid** — the arithmetic mean of the member detections'
  coordinates — recomputed as the cluster grows. It is a summary location, not a
  footprint; for the fire's extent use
  [satellite perimeters](/api/satellite-perimeters).
- `first_observed` / `last_observed` are the earliest / latest `observed_at` across the
  member detections, and extend as new detections merge in.

### Cluster IDs and merges

Two clusters that started as separate fires can grow together until a new detection
bridges the gap between them. When that happens the cluster with the earliest
`first_observed` **survives and keeps its `id`**; the other is deleted and its hotspots
are re-pointed at the survivor (their `cluster_id` changes). Consequences:

- **Active cluster IDs are not permanent** — a `clusters.<uuid>` you stored may stop
  resolving after a merge. The detections themselves are never lost: re-resolve the fire
  through its hotspots' current `cluster_id`.
- Once a cluster goes `active = false` it no longer participates in merging, so
  **historical cluster IDs are stable**.

## Examples

Active clusters in a bounding box (Iberia):

```bash
curl -sG -H "Authorization: Bearer $TOKEN" \
  "https://api.deepfire.co/ogc/features/v1/collections/deepfire:clusters/items" \
  --data-urlencode "bbox=-10,36,4,44" \
  --data-urlencode "filter-lang=cql2-text" \
  --data-urlencode "filter=active = true" \
  --data-urlencode "f=application/geo+json"
```

Clusters with recent detections:

```bash
curl -sG -H "Authorization: Bearer $TOKEN" \
  "https://api.deepfire.co/ogc/features/v1/collections/deepfire:clusters/items" \
  --data-urlencode "filter-lang=cql2-text" \
  --data-urlencode "filter=last_observed > TIMESTAMP('2026-07-20T00:00:00Z')" \
  --data-urlencode "f=application/geo+json"
```

Full parameter reference: [Get clusters](/reference/clusters/items-deepfire-clusters).
