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):
Here's what I've tried so far. My issue with the solution is twofold:
- It takes a long time
- 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 {
i++
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)
_roads.push(
...splitRoads.features.map(f => {
const substringId = `${road.id}-${uniqueId()}`
const feature = turf.lineString(f.geometry.coordinates, {
...road.properties,
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;
});
}