What Building a Trail Graph from OpenStreetMap Actually Takes
Ask WayMaker for a shaded 10k loop near Meeks Bay and it draws one in a couple of seconds, elevation profile and surface breakdown included. Not every loop survives contact with the ground. The graph has been known to route across a gate that a ranger district or a private owner closed years ago, because nothing in the data ever said so. The trail graph has to be built to close that gap.
The claim, stated plainly enough for a skeptic to push against: routing on trails is mostly a data-filtering problem. A routing engine such as GraphHopper is the easy 20 percent of the work. The other roughly 80 percent is narrowing OpenStreetMap down to the ways a hiker can legally walk, giving those ways honest elevation, and refusing to trust the ones that lie. WayMaker ships a dedicated trail-graph endpoint (waymaker.guide) because of that 80 percent, not despite it.
Filtering for the tags that decide whether a hiker gets home
OpenStreetMap's geometry is nearly always right about where a trail runs. The trouble lives in what the tags say about that geometry, because the tags that matter to a hiker are the tags a car router happily ignores.
Four groups carry most of the weight. surface separates asphalt from dirt, ground, grass and rock, which changes what you wear and what your ankles should prepare for. sac_scale grades alpine character on the Swiss Alpine Club's T1 to T6 scale: T1 is a flat, obvious family path, while T5 and beyond means terrain where a slip has consequences. trail_visibility runs from excellent down to no, and the distance between good and bad is the difference between following a signed corridor and navigating off a GPX track. tracktype grades unpaved vehicle tracks from a graded, hatchback-friendly grade1 to something barely recognizable as a road at grade5.
The first cut of our filter kept every highway=track, on the sensible-sounding theory that tracks form the legal connectors into most trailheads. The router then began recommending logging roads to people in running shoes, some boulder-strewn, some signed against public entry. Tracks now pass through the same gate as everything else, and smoothness often settles the argument when surface and tracktype disagree. A quieter rule from that same lesson: a missing tag must never read as a good one. Absence of data is not presence of quality.
Here is the shape of the Overpass query that pulls candidate ways for a region (bounding box in decimal degrees, one trailhead area as the example):
[out:json][timeout:90];
(
way["highway"~"^(path|footway|steps|pedestrian|track)%%CODEBLOCK_0%%quot;]
["foot"!~"^(no|private)%%CODEBLOCK_0%%quot;]
["access"!~"^(no|private)%%CODEBLOCK_0%%quot;]
(39.05,-120.20,39.15,-119.95);
);
out tags geometry;
Most ways pass. The failures are where the engineering lives.
Dropping motor-only ways without cutting the trails that cross them
Narrow the query results to walking ways and the next trap is already sprung. OpenStreetMap splits ways wherever they cross, so a trail that crosses a logging road shares its crossing node with that road. Both wrong answers are available here, and we have produced both at one time or another.
Delete the motor ways and their nodes together and you delete nodes that surviving trails still reference, which leaves holes that a router either chokes on or bridges with a straight line it has no business drawing. The workable order is the reverse: delete ways only, keep every node that a surviving way references, and audit what remains. The audit teaches more than the delete does. Count the connected components of the surviving graph and inspect the large gaps; a region that ought to be one network and comes back in pieces usually means a connector was classified as motor-only.
The deeper conflict underneath is that vehicle class and pedestrian legality are different questions, and OpenStreetMap answers both. A road can be closed to cars all summer and open to boots, and the last 200 meters of many trailhead approaches are exactly that: a highway=service tagged for agricultural or forestry vehicles that a filter keyed on "is this a road?" will amputate. Filter on foot access, never on vehicle class.
Elevation comes from a different planet file
OpenStreetMap rarely records height along a way, so the graph has to fetch its third dimension elsewhere. Open-Elevation serves terrain from SRTM, the space-shuttle radar mission released at roughly a 30 m grid on the ground. WayMaker samples each surviving way's geometry at a fixed interval of about 25 m, rounds each sample to a cache key, and batches the requests, so a region warms up once and stays warm.
Elevation is not decoration. Distances that a hiker actually walks are 3D lengths, and on steep ground the difference from the planar length shows up in the low single digits of percent, more on tightly switchbacked climbs. Accumulated gain and loss, the number every trail runner quotes, does not exist without it. And the router's cost model needs it: a planar graph believes that gaining 300 vertical meters over one kilometer costs the same as one flat kilometer, which is how a grade-blind router ends up recommending a wall to somebody in trail shoes.
We did the order wrong and would not repeat it. The router ran for most of WayMaker's first months on planar lengths before elevation joined the graph, which means every cumulative-gain figure from that era was a flat-earth approximation presented with complete confidence. Join elevation at build time, from the first commit.
The ways that lie
Some lines on the map are fiction. A way crossing open talus with trail_visibility=no is telling you the truth: you will be navigating, not walking. The same line with no visibility tag at all and a dashed reality underfoot is a lie the graph inherits wholesale. Closures are quieter liars: one stormy washout season leaves a way green on the map for years, seasonal closures are sometimes tagged and often not, and access tags disagree with landowner signs in both directions. We found the worst of these the honest way, which is that somebody walked the route and wrote in.
Direction is its own quiet trap. One-way tags on mountain bike descent trails apply to bikes, while a hiker walking up is usually legal and often the entire point of the climb. Direction has to be handled per activity type rather than as a property of the way itself; a graph that stores a single direction flag per way gets this wrong for exactly the two activities that share the most trails.
A filtered, elevation-joined way ends up shaped like this:
{
"id": 102938475,
"tags": {
"highway": "path",
"name": "Rim Trail",
"surface": "dirt",
"trail_visibility": "good",
"sac_scale": "hiking"
},
"node_ids": [47101, 47102, 47108],
"elevation_m": [1912.4, 1913.1, 1915.9],
"length_3d_m": 50.2,
"gain_m": 3.5,
"oneway": { "hiking": false, "mtb": true }
}
That oneway object is the whole direction story compressed into one field, and the router reads it differently depending on what you asked to do.
What the graph still cannot know
Three limits we now state in the open, because users deserve the fine print.
Coverage is uneven exactly where hiking is thin. The trail network is dense where hikers are dense. Deep in rural country, real trails go unmapped and mapped trails exist that nobody has walked with a GPS trace in years. We do not interpolate: if the graph lacks a connector, the route stops or detours, and an honest gap beats an invented line.
Attribution is an obligation, not a courtesy. WayMaker is built on OpenStreetMap, Open-Elevation's SRTM terrain, GraphHopper, Overpass, OpenTopoMap and OpenFreeMap tiles, credited in the app. Because the trail graph is a derived database of OSM data, the ODbL's share-alike terms travel with it. Anyone building on such a graph inherits the notice too, and treating that as a footer problem does not survive contact with a lawyer.
Staleness is a trust problem. A route across a washed-out bridge is worse than an error message, because the user has no way to know the line died last winter. Freshness in OSM is a function of editors, and editors are distributed about as unevenly as the trails themselves. The partial fix is a report path in the app, with corrections flowing back to OSM where an edit benefits every map that reads it; the honest statement is that this fix is partial.
What to take with you
Budget the weeks in the proportions the work actually has. Most of them go to filters, connectivity audits, elevation joins and report handling; a few go to the router, which was never going to be the hard part. Then spend one afternoon walking two loops you can verify in person, because the only benchmark that matters is whether the graph is right where somebody's boots are.
A router is only as honest as the ways you kept.
WayMaker is a free hiking route planner built by AngoraTek. The trail graph described here is the thing it runs on.Thanks for reading this far. If this sounds like the problem you're wrestling with, tell us about it. A principal reads every message.
Start a conversation