Elevation profile of a line
POST https://api.trailsplits.com/v1/elevation-profile
Checked by a live probe Key optional · 500 credits a day without one
The series a chart draws for a GPX or GeoJSON line: distance, height, grade and position at each point, as rows under named columns, with ascent, descent, extremes and the steepest stretches read from the same series (method named). Terrain heights by default, or the file's own heights when they pass the check. Each part of a file is its own run; nothing is drawn across a gap. Limits: 5,000 points, 300 km, 1 MB.
Example
# Without a key, leave out the Authorization line (500 credits a day per address).
curl --fail-with-body 'https://api.trailsplits.com/v1/elevation-profile' \
-H "Authorization: Bearer $TRAILSPLITS_API_KEY" \
-H 'Content-Type: application/json' \
--data '{"geometry":{"type":"LineString","coordinates":[[12.1352,46.5369],[12.133,46.536]]},"points":50}' Request body
| Field | Type | Description |
|---|---|---|
gpx | string | |
geometry | object | A GeoJSON line, as for route analysis. Limits: 5,000 points, 300 km, 1 MB. |
elevation | string: auto | recorded | terrain | |
points | integer | At most this many points in the series. |
Response
| Field | Type | Description |
|---|---|---|
schema | string: elevation-profile/1 | |
engine | object | |
input | object | |
elevation | object | |
series | object | |
unknowns | array |
Full response schema (JSON Schema)
{
"type": "object",
"properties": {
"schema": {
"type": "string",
"enum": [
"elevation-profile/1"
]
},
"engine": {
"type": "object"
},
"input": {
"type": "object",
"properties": {
"points": {
"type": "integer"
},
"length_m": {
"type": "number"
}
},
"required": [
"points",
"length_m"
]
},
"elevation": {
"type": "object",
"properties": {
"source": {
"type": "string",
"enum": [
"terrain",
"recorded"
]
},
"method": {
"type": "string"
},
"ascent_m": {
"type": "number"
},
"descent_m": {
"type": "number"
},
"min_m": {
"type": "number"
},
"max_m": {
"type": "number"
}
},
"required": [
"source",
"method",
"ascent_m",
"descent_m"
]
},
"series": {
"type": "object",
"properties": {
"columns": {
"type": "array",
"items": {
"type": "string"
},
"description": "distance_m, elevation_m, grade_pct, lon, lat"
},
"heights": {
"type": "string"
},
"parts": {
"type": "array",
"items": {
"type": "object",
"properties": {
"from_m": {
"type": "number"
},
"to_m": {
"type": "number"
},
"first": {
"type": "integer"
},
"last": {
"type": "integer"
}
},
"required": [
"from_m",
"to_m",
"first",
"last"
],
"additionalProperties": true
}
},
"points": {
"type": "array",
"items": {
"type": "array",
"items": {
"type": "number",
"nullable": true
}
}
}
},
"required": [
"columns",
"parts",
"points"
]
},
"unknowns": {
"type": "array",
"items": {
"type": "object",
"properties": {
"code": {
"type": "string"
},
"message": {
"type": "string"
}
},
"required": [
"code",
"message"
]
}
}
},
"required": [
"schema",
"engine",
"input",
"elevation",
"series",
"unknowns"
]
}Errors
400, 413, 422, plus the gateway's 401, 429 and 503 (error conventions). A refused request costs no credits.
Contract
Send a line. You get the series a chart draws: distance, height, grade and position at each point. You also get the climb read from that series, by the elevation contract, with its method named.
Route analysis gives the totals; this endpoint gives the curve behind them. Both read heights with the same code, so the same line has the same ascent, descent, extremes, steepest stretches and method in both answers.
Which heights
A line across the antimeridian is sampled the short way round. A terrain sample with no data (over open sea the model has none in places) counts as missing, never as a height.
The rules are route analysis's own:
- Terrain heights. Copernicus DEM GLO-30, sampled every 20 m along the line through the TrailSplits elevation sampler (the Planner's heights). On a line over 120 km the spacing grows, so there are never more than 6,000 samples.
-
elevation:-
auto(the default): the check decides. -
terrain: always the terrain, unless it does not answer. -
recorded: the file's heights whenever 90 % of its points carry one.
-
The series follows the heights used:
-
Terrain: the contract's smoothed series, a moving average over ±100 m along the line (
route-smoothed-100m). Its rises add up toascent_m. -
Recorded: the file's heights at its own points, with missing heights filled along the line.
ascent_mcounts rises of 5 m or more (recorded-hysteresis-5m), so it can be less than the sum of every wobble in the series.
series.heights says which one in words.
Grade at a point is the least-squares slope of the series within 50 m centred on it. Near the ends of a part the window is shortened, and it always holds at least the point and its neighbours. A steady climb reads its grade right up to the end of the line, and a noisy file's wobble averages out.
Parts. Each part of a file (a GPX track segment, one line of a MultiLineString) is a run of its own in series.parts. Distance does not run on across the gap between parts, and no point is drawn across it.
Request
{ "gpx": "<gpx…>" }
{ "geometry": { "type": "LineString", "coordinates": [[lon, lat], …] }, "elevation": "terrain", "points": 500 }
| Field | ||||
|---|---|---|---|---|
gpx or geometry | One of them, as for route analysis. Limits: 5,000 points, 300 km, 1 MB | |||
elevation | auto \ | recorded \ | terrain | Default auto |
points | whole number, 2 to 6,000 | How many points the series has at most; default 1,000. A longer series is thinned evenly to exactly this many: each part gets its share by length, and both of its ends are kept. Only a file with more than points / 2 parts returns more, because each part keeps at least two | ||
caller | string | Optional label for logs and receipts |
Response
| Field | Meaning | ||
|---|---|---|---|
schema | elevation-profile/1 | ||
settings_defaulted | elevation, points when not given | ||
input | As for route analysis: format, parts, points, length_m (gaps excluded), heights_share, median_spacing_m, gaps_m | ||
elevation | Route analysis's elevation block without its 500-point profile: source (terrain \ | recorded), kind (estimated \ | measured), method, ascent_m, descent_m, min_m, max_m (from every height, not only the points returned; on terrain heights from the samples before smoothing, so the smoothed curve can stay a few metres inside them), sparse, steepest[] (200 m and 1 km, up and down, placed), terrain (release, samples, step) or null, recorded (share, use, reason, offset, noise, correlation, both climbs), contract |
series.columns | ["distance_m", "elevation_m", "grade_pct", "lon", "lat"] | ||
series.points | One array per point in that order: distance along the line in metres (gaps excluded), height in metres (0.1), grade in % (0.1), longitude and latitude (6 decimals) | ||
series.parts[] | from_m, to_m, and first, last: indices into series.points | ||
series.heights, series.grade | What the heights and the grade are, in words | ||
series.spacing_m | Median distance between consecutive returned points of a part | ||
series.thinned_from | How many points the full series had, when it was thinned; else null | ||
unknowns[] | code, message: terrain_unavailable (the file's heights were used unchecked), recorded_heights_missing, recorded_heights_rejected (only with elevation: auto), recorded_heights_doubtful (with elevation: recorded, heights used though they fail the check), sparse_points | ||
timing | terrain_ms, total_ms |
Errors
{ "error": { "code", "message" } }.
| HTTP | code | When |
|---|---|---|
| 400 | invalid_request | Not JSON, an unknown field, both or neither of gpx/geometry, a bad elevation or points |
| 400 | invalid_gpx, invalid_coordinates | As for route analysis: gpx not a string, not a GPX, or cut off (element names may carry a namespace prefix) |
| 405 | method_not_allowed | Any method but POST (and OPTIONS); the answer carries Allow: POST |
| 413 | body_too_large, too_many_points | Over 1 MB, or over 5,000 points |
| 422 | too_long, no_line | Over 300 km, or no part of two or more points |
| 422 | budget_exhausted | The job's 25 s budget ran out, also while waiting for terrain lookups behind other requests |
| 503 | terrain_unavailable | The terrain model did not answer, and the line carries too few heights of its own; try again |
Versioning
Additive fields may appear in elevation-profile/1. A change of meaning, or a removed field or column, is elevation-profile/2.
JSON Schema
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://trailsplits.com/schemas/elevation-profile-1.request.json",
"type": "object",
"additionalProperties": false,
"oneOf": [{ "required": ["gpx"] }, { "required": ["geometry"] }],
"properties": {
"gpx": { "type": "string" },
"geometry": { "type": "object", "required": ["type"] },
"elevation": { "enum": ["auto", "recorded", "terrain"] },
"points": { "type": "integer" },
"caller": { "type": "string" }
}
}
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://trailsplits.com/schemas/elevation-profile-1.response.json",
"type": "object",
"required": ["schema", "engine", "settings_defaulted", "input", "elevation", "series", "unknowns", "timing"],
"properties": {
"schema": { "const": "elevation-profile/1" },
"settings_defaulted": { "type": "array", "items": { "enum": ["elevation", "points"] } },
"input": { "type": "object", "required": ["format", "parts", "points", "length_m", "heights_share", "median_spacing_m", "gaps_m"] },
"elevation": {
"type": "object",
"required": ["source", "kind", "method", "ascent_m", "descent_m", "min_m", "max_m", "sparse", "steepest", "terrain", "recorded", "contract"],
"properties": {
"source": { "enum": ["terrain", "recorded"] },
"kind": { "enum": ["estimated", "measured"] },
"method": { "type": "string" },
"terrain": { "type": ["object", "null"] },
"recorded": { "type": "object", "required": ["share", "use", "reason"] }
}
},
"series": {
"type": "object",
"required": ["columns", "heights", "grade", "spacing_m", "thinned_from", "parts", "points"],
"properties": {
"columns": { "type": "array", "items": { "enum": ["distance_m", "elevation_m", "grade_pct", "lon", "lat"] } },
"thinned_from": { "type": ["integer", "null"] },
"parts": { "type": "array", "items": { "type": "object", "required": ["from_m", "to_m", "first", "last"] } },
"points": { "type": "array", "items": { "type": "array", "items": { "type": "number" } } }
}
},
"unknowns": { "type": "array", "items": { "type": "object", "required": ["code", "message"] } },
"timing": { "type": "object", "required": ["terrain_ms", "total_ms"] }
}
}
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://trailsplits.com/schemas/elevation-profile-1.error.json",
"type": "object",
"required": ["error"],
"properties": { "error": { "type": "object", "required": ["code", "message"], "properties": { "code": { "type": "string" }, "message": { "type": "string" } } } }
}