I've got a query that pulls in a bbox of GeoJSON LineStrings representing roads from the Overpass Turbo API. I'm working in Typescript and have been using the TurfJS library extensively.

I want to split this array of LineStrings whenever each road is intersected by another road (e.g. a different street name).

By that I mean, I want to take an array (like the one on the left), and pull out individual intersections (in red on the right):

enter image description here

Here's what I've tried so far. My issue with the solution is twofold:

  1. It takes a long time
  2. When one road ends at a junction with another road (e.g. a T-junction), the 'top of the T' road doesn't always split.

Here's an example payload that can be passed into the below function's first argument roads: https://gist.github.com/janbaykara/1569fad668651f6171a9c10e942cfadb

export function cutRoadsByIntersection(
  roads: OverpassGeoJSONFeature<GeoJSON.LineString>[]
): OverpassGeoJSONFeature<GeoJSON.LineString>[] {
  let _roads = [...roads]

  let intersectionsDict = _roads.reduce((dict, road) => {
    dict[road.id] = true
    return dict
  }, {})

  let hasIntersections = Object.values(intersectionsDict).some(v => v)

  let i = 0
  let lastCount = _roads.length
  do {
    lastCount = _roads.length
    // console.log("Cutting roads", i, _roads.length, intersectionsDict)
    for (const road of _roads) {
      const intersectingOtherStreets = getRoadIntersections(road, _roads)
        .filter((r) => {
          if (!r.properties.name) return false;
          const roadStreet = makeStreetNameComparable(road.properties.name);
          const rStreet = makeStreetNameComparable(r.properties.name);
          return roadStreet !== rStreet;

      if (intersectingOtherStreets.length > 0) {
        // console.log("Found a road with intersections", road.id, intersectingOtherStreets.map(r => r.id))
        const firstIntersectingRoad = intersectingOtherStreets[0]

        // Add a meter to the beginning and end of the road
        // Doesn't always seem to actually affect a split when the road *ends* on another road, I guess because of the numbers not perfectly aligning.
        // const splitRoads = turf.lineSplit(road, firstIntersectingRoad)
        const firstIntersectingRoadPoint = turf.lineIntersect(road, firstIntersectingRoad)
        const splitRoads = turf.lineSplit(road, firstIntersectingRoadPoint.features[0])
        // const splitRoads = turf.lineSplit(road, turf.buffer(firstIntersectingRoadPoint.features[0], 1, { units: 'meters' }))

        delete intersectionsDict[road.id]

        if (splitRoads.features.length <= 1) {
          // console.log("Useless split", road, firstIntersectingRoad)
        } else {
          _roads = _roads.filter(r => r.id !== road.id)

            ...splitRoads.features.map(f => {
              const substringId = `${road.id}-${uniqueId()}`
              const feature = turf.lineString(f.geometry.coordinates, {
                id: substringId
              feature.id = substringId

              // Flag it as possibly having intersections for the next loop
              intersectionsDict[substringId] = true

              // console.log("--> created new split", substringId)

              return feature as OverpassGeoJSONFeature<GeoJSON.LineString>
      } else {
        delete intersectionsDict[road.id]
    hasIntersections = Object.values(intersectionsDict).some(v => !!v)
  } while (hasIntersections && lastCount !== _roads.length && i < 5)

  return _roads

export function getRoadIntersections(
  road: OverpassGeoJSONFeature<GeoJSON.LineString>,
  roads: OverpassGeoJSONFeature<GeoJSON.LineString>[]
) {
  return roads.filter((r) => {
    if (r.id === road.id) return false;
    const intersection = turf.lineIntersect(road, r);
    return intersection.features.length > 0;
  • 1
    I've added my first stab at this May 25 at 12:52
  • Can you be more specific about "it takes a long time"? What would be acceptable time for your example payload?
    – TomazicM
    May 25 at 13:44


Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.