OpenStop
OpenStop copied to clipboard
Improve stop area generation
What changed:
- internally converts stop areas from circles to bboxes
- elements are no longer filtered based on their centroid lying within the stop area, instead it is enough if their bbox intersects with the stop area. This helps to include platforms which centroid could previously lie outside but especially other elements that do not span the stop area like parking spaces or footways.
- stop area creation (merging of nearby stops) is completely done on the server side (via special Overpass query)
- the merging now works differently and should avoid the creation of super large stop areas (grouping more than 100 stops) as it could happen previously
- the merging should also be more accurate for geometries other than nodes. Previously it only looked around the centroid in a specific radius, now it looks around the entire geometry for ways.
- the stop are is now named after the lexicographically first stop name
Decisions
It was decided against additionally using public_transport=stop_area
inside the query.
While it has benefits like:
- performance improvement (as the elements do not have to be manually grouped by distance)
- the name (if existing) better describes the stop area than a name derived from the individual stops (as the stop name can contain things like "Platform B" or "West")
- correctly groups stops that are too far away from each other to be grouped by distance
The downside is that in reality stop areas overlap or are incomplete (wherefore a new stop area is created through grouping). This can lead to overlapping stop areas (bboxes) thus having multiple requests for the same region + overlapping loading indicators.
Ideal solution would be a merging/grouping by geometry of the existing stop areas wit the remaining platforms.
Query with stop areas (faster but overlapping bboxes):
[bbox:{{bbox}}];
// get all platforms
nwr[public_transport=platform]->.platforms;
// get all stop_areas
rel[public_transport=stop_area]->.stop_areas;
// get all stop_area members
nwr(r.stop_areas)->.stop_area_members;
// all platforms not part of a stop_area
(.platforms; - .stop_area_members;)->.remaining_platforms;
// for each stop area
foreach.stop_areas {
// recurse relation fully down (select all ancestors)
// required to construct the full geometry for relations that contain other relations
>>;
// convert all stop_areas to custom zone element
make zone
name=min(t["name"]),
// group geometries into one
::geom=gcat(geom());
out bb;
}
// return all bboxes of stop aras and platforms not part of stop areas
// loop over every single platform element
foreach.remaining_platforms {
// make intersection of current platform (default set ._) and .remaining_platforms
// therefore only if the current platform is in remaining_platforms count will be 1
// otherwise it has already been merged and therefore removed from remaining_platforms
nwr._.remaining_platforms;
if (count(nwr) > 0) {
// get any nearby platforms
// this loops for every newly found platform untill no new platforms are found
// 10 is the max number of iterations
complete(10) -> .grouped {
nwr.remaining_platforms(around:75);
}
// delete .grouped platforms from .remaining_platfroms set
(.remaining_platforms; - .grouped;) -> .remaining_platforms;
// write to default set because make always reads from default set
.grouped -> ._;
// build the target areas
make zone
name=min(t["name"]),
// uncomment the following line to get the source remaining_platforms
// source=set("{"+type()+" "+id()+"}"),
// group geometries into one
::geom=gcat(geom());
// output bbox only
out bb;
}
}
Query with stop areas, but merge stop areas with remaining platforms and other stop areas (sometimes slightly better bboxes, not faster, but for wrong stop areas we get large bboxes https://www.openstreetmap.org/relation/7646731/history/4)
[bbox:{{bbox}}];
// get all platforms
nwr[public_transport=platform]->.platforms;
// get all stop_areas
rel[public_transport=stop_area]->.stop_areas;
// get all stop_area members
nwr(r.stop_areas)->.stop_area_members;
// all platforms not part of a stop_area
(.platforms; - .stop_area_members;)->.remaining_platforms;
(.stop_areas; .remaining_platforms;)->.remaining_platforms;
// return all bboxes of stop aras and platforms not part of stop areas
// loop over every single platform element
foreach.remaining_platforms {
// make intersection of current platform (default set ._) and .remaining_platforms
// therefore only if the current platform is in remaining_platforms count will be 1
// otherwise it has already been merged and therefore removed from remaining_platforms
nwr._.remaining_platforms;
if (count(nwr) > 0) {
// get any nearby platforms
// this loops for every newly found platform untill no new platforms are found
// 20 is the max number of iterations
complete(20) -> .grouped {
nwr.remaining_platforms(around:100);
}
// delete .grouped platforms from .remaining_platfroms set
(.remaining_platforms; - .grouped;) -> .remaining_platforms;
// recurse relation fully down (select all ancestors)
// required to construct the full geometry for relations that contain other relations
// write to default set because make always reads from default set
(.grouped; .grouped >>;);
// build the target areas
make zone
name=min(t["name"]),
// uncomment the following line to get the source remaining_platforms
// source=set("{"+type()+" "+id()+"}"),
// group geometries into one
::geom=gcat(geom());
// output bbox only
out bb;
}
}