Skip to main content
Version: 0.13.0

Line aggregations

geo_line and line take a bucket of points and return the path they trace when placed in order.

A bucket of positions has no inherent order, and the order is the whole answer. The same fixes sorted by time are a track; sorted by altitude they are a climb profile; sorted by nothing at all they are a scatter of dots. So these aggregations take two fields — where, and in what order — and give you a line.

  • geo_line works on geo_point fields and returns longitude/latitude.
  • line works on point fields and returns unprojected x/y.

Everything below applies to both; the examples use geo_line.

Example

GET /fleet/_search
{
"size": 0,
"aggs": {
"by_vehicle": {
"terms": { "field": "vehicle_id", "size": 500 },
"aggs": {
"track": {
"geo_line": {
"field": "location",
"sort": "@timestamp",
"size": 1000
}
}
}
}
}
}

One line per bucket. Put it under a terms aggregation for a line per vehicle, per vessel, or per device; under a date_histogram for one per hour.

The result is a GeoJSON Feature, which a map client can hand straight to a renderer:

"track": {
"type": "Feature",
"geometry": {
"type": "LineString",
"coordinates": [[-97.74, 30.26], [-97.73, 30.27]]
},
"properties": {
"complete": true,
"count": 842,
"size": 1000
}
}

A bucket holding a single point comes back as a Point rather than a one-vertex LineString, which most GeoJSON readers reject. An empty bucket comes back with a null geometry.

Parameters

ParameterDefaultDescription
fieldThe geo_point (or point) field. "point": {"field": ...} is accepted as a synonym.
sorta time fieldThe field whose values order the points.
size1000Vertex budget: at most this many points come back. Maximum 10000.
sort_orderascWhich direction the line runs in.
include_sortfalseReturn each vertex's sort value alongside the line.
overflowsimplifyWhat to do when the bucket holds more points than the budget.

sort defaults to time

Leave sort out and the aggregation looks for @timestamp, then timestamp, then time, and uses the first one your index actually maps. If none is mapped it says so and names the fields it tried, rather than picking some other numeric field and returning a confident wrong answer.

The sort field must be numeric or a date. Dates and integers are carried exactly; floating-point fields are mapped onto an order-preserving integer, so ordering is exact for those too. A document with several sort values contributes one vertex, using the lowest value when sorting ascending and the highest when descending — the same rule the search sort uses, so a line and a sorted hit list order the same documents the same way.

A document without a sort value is not on the line. There is nowhere to put it.

size is a budget, not a filter

It bounds what comes back over the wire and, under a terms parent, it is multiplied by the bucket count — five hundred vehicles at a thousand vertices each is half a million coordinates in one response.

properties.complete tells you whether the budget was enough. properties.count is how many points the bucket actually held before any budget was applied, so you always know what the line is a summary of.

overflow

simplify (the default) keeps the whole extent of the track and gives up detail. The least significant vertices are dropped until the budget is met, using Visvalingam–Whyatt: each vertex is ranked by the area it would cost to remove it, and the cheapest go first. Both endpoints always survive.

This is the default because a truncated track drawn on a map is not a shorter track — it is a track that appears to stop somewhere it did not. That is a wrong answer, where a simplified one is merely coarse.

truncate keeps one end at full fidelity and discards the rest: the first size points in sort order, or the last when sorting descending. Use it when you asked about a window rather than a shape ("the last 500 fixes", "where did this start"), or when you need exactly the points that were indexed, unaltered.

Truncation is also cheaper: once a bucket is full, a point outside the retained window is rejected without being stored.

Simplification on a sphere

For geo_line, a vertex's importance is measured with longitude rescaled by the cosine of its latitude. Measuring in raw degrees would rate a wiggle in Svalbard as far more important than the same physical wiggle in Ecuador, and simplification would keep the wrong vertices. Tracks crossing the antimeridian are measured across the seam rather than the long way round the planet.

Cost

The per-document cost does not depend on how long the track is. A document contributes two array writes to a bounded per-bucket window; ordering happens later, in bulk, once per size documents. On a 2026 laptop that is around 2 ns per document, and it stays there whether the bucket holds a thousand points or a million.

Once a track is past its budget, simplify costs more, because keeping the whole extent means discarding one vertex for every one accepted — around 130–185 ns per document on time-ordered data. truncate, which can reject an out-of-window document on a single comparison, stays at about 3 ns.

Memory is bounded by size × 32 bytes × buckets, in the same paged, circuit-broken arrays every other aggregation uses. A thousand vehicles at a thousand vertices each is about 32 MB per shard.

On the coordinating node each shard's line arrives already in order, so reduction is a k-way merge rather than a sort, and its memory does not depend on the shard count. An index with a thousand shards costs the coordinating node the same as one with a single shard.

Things worth knowing

  • Sub-aggregations are not accepted. A line is a leaf metric.
  • Concurrent segment search is supported. Slices reduce through the same merge that reduces shards, so the answer does not depend on how segments were divided.
  • Multi-valued point fields contribute every position, all sharing the document's sort value.
  • Simplification is not exactly the same as one global pass. A shard simplifies its own line, and the coordinating node simplifies the merged result. The second pass can only remove what the first already ranked as least important, and never removes an endpoint — but a line reduced in two steps may differ slightly from one reduced in a single step over all the data.
info

If you need the exact points that were indexed rather than a shape, use overflow: truncate, which never alters a vertex.