OPENER-next / OpenStop

App for collecting OpenStreetMap-compliant accessibility data in public transport
https://openstop.app
GNU General Public License v3.0
63 stars 13 forks source link

Improve stop area generation #262

Closed Robbendebiene closed 1 month ago

Robbendebiene commented 1 month ago

What changed:

Decisions

It was decided against additionally using public_transport=stop_area inside the query. While it has benefits like:

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;
  }
}