Skip to content

Segmenting the road network

OpenStreetMap stores roads as long, continuous lines that can run through many intersections without a break. Before any routing can happen, bikescore-bna has to cut those lines into the individual pieces a route is built from. That is the job of the segment stage.

It turns the ways read from OSM into segments — each a single stretch of road between two intersections — and, along the way, identifies the recreational trails that scoring later rewards. The stress and routing stages that follow all operate on these segments.

From ways to segments

OSM stores roads as continuous linestrings (called ways) that may span multiple intersections. A single way might run for a kilometre and pass through four traffic lights without any break in the underlying data. Each way is split at every intersection node into individual segments — the atomic units of the routing network.

This process is called topology splitting. A node is an intersection if it appears in more than one way. For each way, bikescore-bna walks the node sequence and splits whenever it reaches an intersection node, carrying that node forward as the start of the next segment.

The result: each segment connects exactly two nodes (start and end), has a unique segment ID, and inherits the road attributes of its parent way — speed limit, lane counts, bike infrastructure, and so on. Level of Traffic Stress is assigned to each segment afterwards, by the stress stage.

Segment identifiers

Every segment is identified by its end node (road_id = end_node_id); each vertex corresponds to the target end of a road segment. This identifier is what the routing network later uses to wire segments together.

Using only the end node as the identifier is intentional. Using both start and end nodes would create ~5% extra connectivity pairs, inflating scores.

Recreational trails

Some path segments form recreational trails — long, spread-out networks of cycling paths that are cycling destinations in their own right, not just short connectors. The segment stage identifies them so the scoring stage can reward access to them.

After splitting, bikescore-bna groups connected path segments into clusters using union-find on shared node IDs. Each cluster that is long enough and geometrically spread-out enough qualifies as a trail for recreational scoring.

Two thresholds filter the clusters:

  • min_path_length = 4800 m — the total length of all segments in the cluster. Very short clusters (parking-lot bike racks, short connecting paths) are excluded.
  • min_bbox_length = 3300 m — the diagonal of the bounding box of the cluster geometry. This filters paths that loop back on themselves: a circular loop of 5 km still has a small bounding box if it doesn't go anywhere new, so it fails this check.

Together these two thresholds select linear trail networks (rail trails, greenways, riverside paths) while excluding short connectors and circular loops.

Relationship to brokenspoke-analyzer

The SQL scripts this stage replaces — and any points where the output intentionally differs — are catalogued in the Differences from brokenspoke-analyzer section.