Route Enhancement API
On this page
Use the Route Enhancement API to get the GeoJSON geometry of a GPX or KML file. You can extend it with elevation, road surface and highway type, travel time estimates, or turn-by-turn instructions by adding extra parameters.
Base URL
https://enhance.maptoolkit.net/routeUse a POST request when the geometry is large, otherwise a GET request.
Authentication
Add your API key as ?api_key=YOUR_API_KEY to every request. See Authentication for details.
Parameters
Route Data
| Parameter | Type | Required | Description |
|---|---|---|---|
gpx | URL | Yes (or kml/geometry) | URL to a GPX file. |
kml | URL | Yes (or gpx/geometry) | URL to a KML file. |
geometry | GeoJSON string | Yes (or gpx/kml) | A GeoJSON LineString or MultiLineString. |
Data to add
| Parameter | Type | Required | Description |
|---|---|---|---|
elevation | 0 or 1 | No | Include an elevation profile in the response. Default: 0. |
surface | 0, 1 or object | No | Include road surface and highway type data. Pass a JSON object to tune the map match. Default: 0. |
timings | 0 or 1 | No | Include travel time estimates. Default: 0. |
mapmatch | 0 or 1 | No | Include turn-by-turn directions matched to the road network. Default: 0. |
Configuration Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
routeType | string | No | Vehicle type: car, bike, or foot. |
language | string | No | Language for turn instructions, as a 2-letter code (e.g., en). |
cache | 0 or 1 | No | Use cached results if available. Default: 1. |
callback | string | No | Wrap the response in a JSONP callback. |
densify | 0 or 1 | No | Add vertices to the geometry, see Densified geometry. Default: 1 with elevation. |
densifyGap | number | No | Maximum distance between vertices in meters when densify is on. Minimum 50. Default: 100. |
Response
GeoJSON from input route
GET https://enhance.maptoolkit.net/route?kml=https://maptoolkit.net/export/ts_demo_tours/_1603292819.kml&api_key=YOUR_API_KEYResponse:
{
"geometry": {
"type": "MultiLineString",
"coordinates": [
[
[15.460422, 47.349047, 1181],
[15.460593, 47.349027, 1179],
...
]
]
}
}Coordinates are [longitude, latitude, elevation_in_meters].
Add Elevation Data
GET https://enhance.maptoolkit.net/route?geometry={"type":"LineString","coordinates":[[10,50],[10.1,50.1]]}&elevation=1&api_key=YOUR_API_KEYResponse:
{
"geometry": { "type": "MultiLineString", "coordinates": [[...]] },
"elevation": [{
"samples": [289, 292, 302, 305, 299],
"distance": 13230.5,
"ascent": 220,
"descent": 160,
"yrange": [200, 600],
"labels": {
"x": [[0, 0], [0.23, 3], [0.45, 6]],
"y": [[0.25, 300], [0.5, 400], [0.75, 500]]
}
}]
}Elevation samples are taken approximately every 100 meters. yrange gives the min/max values for the y-axis. Label values are relative positions (0-1) paired with the label value.
Densified geometry
With elevation=1 the returned geometry is densified as well: vertices are added so that no
two are more than densifyGap meters apart (default 100, minimum 50). The original
vertices are kept, so a sparse line drawn with a few clicks comes back with an elevation value
along its whole length, which is what an elevation profile drawn from the geometry needs.
Elevations of the added vertices come from the terrain model. If every vertex of the input already has an elevation, for example a recorded GPS track, the added vertices are interpolated between those values instead, so recorded and modeled heights are not mixed.
Pass densify=0 to keep the input vertices unchanged:
GET https://enhance.maptoolkit.net/route?geometry={"type":"LineString","coordinates":[[10,50],[10.1,50.1]]}&elevation=1&densify=0&api_key=YOUR_API_KEYdensify has no effect without elevation.
Add Surface Data
surface=1 returns the road surface and highway type under each part of the route, so you
can tell a reader how much of a ride is asphalt and how much is gravel, color a route line
by what it runs on, or filter a set of tours down to the ones a road bike can handle.
GET https://enhance.maptoolkit.net/route?kml=https://example.com/route.kml&surface=1&api_key=YOUR_API_KEYResponse:
{
"geometry": { "type": "MultiLineString", "coordinates": [[...]] },
"surface": [[
{ "from": 0.053, "to": 0.088, "highway": "road", "surface": "asphalt" },
{ "from": 0.088, "to": 0.106, "highway": "road", "surface": "paved" },
{ "from": 0.106, "to": 0.241, "highway": "path", "surface": "unpaved" }
]]
}| Field | Description |
|---|---|
from | Start of this segment as a relative position along the route. 0 is the start, 1 is the end. |
to | End of this segment as a relative position along the route. |
highway | Road or path type for this segment. |
surface | Surface material for this segment. |
surface is an array of arrays. There is one inner array per segment of the
MultiLineString geometry, in the same order, so a route split into three segments returns
three arrays. Reading response.surface[0] and assuming it covers the whole route is correct
only for a single-segment route.
Positions are relative, not meters. from and to are fractions of the route, so
turning a surface breakdown into distances means multiplying by the total length. Segments are
contiguous and do not overlap, which makes a percentage breakdown a matter of summing
to - from per surface value.
Tuning the map match
Surface data is not stored against your geometry. The service map-matches each part of the route onto the road network and reads the tags off whatever it matched. A recorded track that drifts, or a line drawn across open ground, can match a road it never touched.
surface therefore also accepts a JSON object instead of 1:
GET https://enhance.maptoolkit.net/route?kml=https://example.com/route.kml&surface={"mapMatchingThreshold":0.02}&api_key=YOUR_API_KEY| Option | Type | Description |
|---|---|---|
mapMatchingThreshold | number | Largest relative distance deviation that still counts as a match. A segment is classified as other when abs(1 - matchDistance / origDistance) reaches the threshold. Default: 0.05. |
Lower the threshold to reject doubtful matches, which trades coverage for confidence and
returns more other. Raise it when a clean track keeps coming back as other.
other in either field means no confident match, not an unusual road. Treat it as unknown
rather than as a surface type, because presenting it as a real category is how a route ends up
described as unpaved when nothing is known about it.
Highway types:
motorway, primary, road, street, pedestrian, cycleway, path, hiking, mountain_hiking, otherSurface types:
asphalt, paved, unpaved, natural, alpine, otherAdd Travel Time Estimates
GET https://enhance.maptoolkit.net/route?geometry={"type":"LineString","coordinates":[[10,50],[10.1,50.1]]}&timings=1&api_key=YOUR_API_KEYResponse:
{
"geometry": { "type": "MultiLineString", "coordinates": [[...]] },
"timings": [{
"walking": 300,
"cycling": 100,
"cycling_offroad": 110,
"cycling_racing": 50
}]
}Times are in seconds, calculated from route distance and elevation.
Add Turn-by-Turn Directions
Map matching generates turn-by-turn directions matched to the road network.
GET https://enhance.maptoolkit.net/route?kml=https://example.com/route.kml&mapmatch=1&language=en&api_key=YOUR_API_KEYResponse:
{
"geometry": { "type": "MultiLineString", "coordinates": [[...]] },
"mapmatch": [[{
"instructions": [{
"distance": 336.799,
"name": "Kaufbeurer Straße",
"text": "Turn right onto Kaufbeurer Straße",
"sign": 2,
"time": 67357,
"coordinate": [47.83471, 10.82657],
"tags": { "surface": "asphalt", "highway": "road" }
}],
"geometry": {
"type": "LineString",
"coordinates": [[10.82623, 47.83423], ...]
}
}]]
}Errors
Errors return the HTTP status code with a plain-text message in the body.
| Status | Meaning |
|---|---|
403 | The API key is missing (Access denied!) or not recognized (Api-key not found!). See First Steps. |
400 | The geometry or a parameter could not be parsed, or an upstream service failed. |
Common 400 messages
| Message | Cause |
|---|---|
no valid geometry found, LineString & MultiLineString's are allowed | geometry is missing or has an unsupported GeoJSON type. |
invalid geometry type | The GeoJSON parsed, but is not a LineString or MultiLineString. |
param '{name}' seems to be a JSON String, but it cannot be parsed: {message} | A parameter value starts with { but is not valid JSON. |
routing request failed: {message} | The upstream surface or map-matching service returned an error. |
Rate limits
Limits are monthly request quotas tied to your plan rather than a per-second rate: the
service returns no RateLimit-* or Retry-After headers. Current allowances are on the
pricing page.
The free Basic plan has a hard limit, so requests are refused once it is reached. On Pro and Ultra, requests past the allowance are billed rather than refused. Enterprise plans have no request limit.
Calls to this API count toward your plan’s API request allowance, which is the smaller of the two allowances.
Pricing
Self-service plans (Basic, Pro, and Ultra) are billed through RapidAPI; see RapidAPI for how to subscribe and authenticate. Plan limits and prices are listed on the Maptoolkit pricing page. Enterprise customers call the native hosts directly with a Maptoolkit API key.