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

Elevation profile of a line (curl)
# 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}'

Sends a real request, with your key or the keyless allowance.

Request body

FieldTypeDescription
gpxstring
geometryobjectA GeoJSON line, as for route analysis. Limits: 5,000 points, 300 km, 1 MB.
elevationstring: auto | recorded | terrain
pointsintegerAt most this many points in the series.

Response

FieldTypeDescription
schemastring: elevation-profile/1
engineobject
inputobject
elevationobject
seriesobject
unknownsarray
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 to ascent_m.
  • Recorded: the file's heights at its own points, with missing heights filled along the line. ascent_m counts 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 geometryOne of them, as for route analysis. Limits: 5,000 points, 300 km, 1 MB
elevationauto \recorded \terrainDefault auto
pointswhole number, 2 to 6,000How 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
callerstringOptional label for logs and receipts

Response

FieldMeaning
schemaelevation-profile/1
settings_defaultedelevation, points when not given
inputAs for route analysis: format, parts, points, length_m (gaps excluded), heights_share, median_spacing_m, gaps_m
elevationRoute 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.pointsOne 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.gradeWhat the heights and the grade are, in words
series.spacing_mMedian distance between consecutive returned points of a part
series.thinned_fromHow 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
timingterrain_ms, total_ms

Errors

{ "error": { "code", "message" } }.

HTTPcodeWhen
400invalid_requestNot JSON, an unknown field, both or neither of gpx/geometry, a bad elevation or points
400invalid_gpx, invalid_coordinatesAs for route analysis: gpx not a string, not a GPX, or cut off (element names may carry a namespace prefix)
405method_not_allowedAny method but POST (and OPTIONS); the answer carries Allow: POST
413body_too_large, too_many_pointsOver 1 MB, or over 5,000 points
422too_long, no_lineOver 300 km, or no part of two or more points
422budget_exhaustedThe job's 25 s budget ran out, also while waiting for terrain lookups behind other requests
503terrain_unavailableThe 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" } } } }
}