diff --git a/Cargo.toml b/Cargo.toml index d6039b9..c043016 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -138,6 +138,7 @@ compact_str = { version = "0.8.0", features = ["serde", "diesel"] } urlencoding = "2.1.3" url = "2.5.2" slippy-map-tiles = "0.16.0" +geo-buffer = "0.2.0" [[bin]] name = "maple" path = "src/maple/main.rs" diff --git a/src/birch/nearby_departures.rs b/src/birch/nearby_departures.rs index 5e2ebc1..3a0845a 100644 --- a/src/birch/nearby_departures.rs +++ b/src/birch/nearby_departures.rs @@ -66,7 +66,7 @@ pub struct ItineraryPatternRowMerge { pub trip_headsign: Option, pub trip_headsign_translations: Option, pub timezone: String, - pub route_id: CompactString + pub route_id: CompactString, } #[derive(Deserialize, Clone, Debug)] @@ -85,12 +85,12 @@ struct DeparturesFromStop { #[derive(Deserialize, Serialize, Clone, Debug)] pub struct DeparturesDebug { - pub stop_lookup_ms: u128, - pub directions_ms: u128, - pub itinerary_meta_ms: u128, - pub itinerary_row_ms: u128, - pub trips_ms: u128, - pub total_time_ms: u128, + pub stop_lookup_ms: u128, + pub directions_ms: u128, + pub itinerary_meta_ms: u128, + pub itinerary_row_ms: u128, + pub trips_ms: u128, + pub total_time_ms: u128, } #[derive(Clone, Debug, Deserialize, Serialize)] @@ -275,7 +275,10 @@ pub async fn nearby_from_coords( let mut bus_distance_limit = 3500; - let spatial_resolution_in_degs = make_degree_length_as_distance_from_point(&input_point, rail_and_other_distance_limit as f64); + let spatial_resolution_in_degs = make_degree_length_as_distance_from_point( + &input_point, + rail_and_other_distance_limit as f64, + ); let start_stops_query = Instant::now(); @@ -291,7 +294,7 @@ pub async fn nearby_from_coords( let end_stops_duration = start_stops_query.elapsed(); let stops = stops.unwrap(); - + if stops.len() > 400 { bus_distance_limit = 1500; rail_and_other_distance_limit = 2000; @@ -417,7 +420,7 @@ pub async fn nearby_from_coords( .map(|(k, v)| (k.0.clone(), k.1.to_string(), v.1)) .collect::>(); - //chateau -> (direction_id, stop_sequence) + //chateau -> (direction_id, stop_sequence) let mut hashmap_of_directions_lookup: AHashMap> = AHashMap::new(); @@ -481,7 +484,7 @@ pub async fn nearby_from_coords( //chateau -> itinerary_pattern_id -> ItineraryPatternMeta let mut itin_meta_table: HashMap> = HashMap::new(); - //chateau -> direction_id -> Vec + //chateau -> direction_id -> Vec let mut chateau_direction_to_itin_meta_id: HashMap>> = HashMap::new(); @@ -496,10 +499,13 @@ pub async fn nearby_from_coords( Entry::Occupied(mut oe) => { match oe.get_mut().entry(direction_pattern_id.clone()) { Entry::Occupied(mut oe) => { - oe.get_mut().push(itinerary_meta.itinerary_pattern_id.clone()); + oe.get_mut() + .push(itinerary_meta.itinerary_pattern_id.clone()); } Entry::Vacant(mut ve) => { - ve.insert(vec![itinerary_meta.itinerary_pattern_id.clone()]); + ve.insert(vec![itinerary_meta + .itinerary_pattern_id + .clone()]); } } } @@ -544,8 +550,12 @@ pub async fn nearby_from_coords( let mut vec_to_insert: Vec<(String, u32)> = vec![]; for (direction_id, stop_sequence) in set_of_directions.iter() { - if let Some(itinerary_pattern_ids_in_chateau) = chateau_direction_to_itin_meta_id.get(chateau) { - if let Some(itinerary_pattern_ids) = itinerary_pattern_ids_in_chateau.get(direction_id) { + if let Some(itinerary_pattern_ids_in_chateau) = + chateau_direction_to_itin_meta_id.get(chateau) + { + if let Some(itinerary_pattern_ids) = + itinerary_pattern_ids_in_chateau.get(direction_id) + { for itinerary_pattern_id in itinerary_pattern_ids.iter() { vec_to_insert.push((itinerary_pattern_id.clone(), *stop_sequence)); } @@ -575,17 +585,28 @@ pub async fn nearby_from_coords( let itineraries_timer = Instant::now(); - let new_seek_itinerary_list = futures::stream::iter(itineraries_and_seq_to_lookup_join.into_iter() - .map( - |(chateau, itin_id, stop_seq)| { - catenary::schema::gtfs::itinerary_pattern::dsl::itinerary_pattern - .filter(catenary::schema::gtfs::itinerary_pattern::dsl::chateau.eq(chateau.clone())) - .filter(catenary::schema::gtfs::itinerary_pattern::dsl::itinerary_pattern_id.eq(itin_id.clone())) - .filter(catenary::schema::gtfs::itinerary_pattern::dsl::stop_sequence.eq(stop_seq as i32)) - .select(catenary::models::ItineraryPatternRow::as_select()) - .first::(conn) - } - )).buffer_unordered(64).collect::>>().await; + let new_seek_itinerary_list = + futures::stream::iter(itineraries_and_seq_to_lookup_join.into_iter().map( + |(chateau, itin_id, stop_seq)| { + catenary::schema::gtfs::itinerary_pattern::dsl::itinerary_pattern + .filter( + catenary::schema::gtfs::itinerary_pattern::dsl::chateau.eq(chateau.clone()), + ) + .filter( + catenary::schema::gtfs::itinerary_pattern::dsl::itinerary_pattern_id + .eq(itin_id.clone()), + ) + .filter( + catenary::schema::gtfs::itinerary_pattern::dsl::stop_sequence + .eq(stop_seq as i32), + ) + .select(catenary::models::ItineraryPatternRow::as_select()) + .first::(conn) + }, + )) + .buffer_unordered(64) + .collect::>>() + .await; println!( "Finished getting itineraries in {:?}", @@ -621,36 +642,37 @@ pub async fn nearby_from_coords( .unwrap(); if let Some(itinerary_direction_id) = &itin_meta_ref.direction_pattern_id { - - let new_itinerary = ItineraryPatternRowMerge { - onestop_feed_id: itinerary.onestop_feed_id.clone(), - attempt_id: itinerary.attempt_id.clone(), - itinerary_pattern_id: itinerary.itinerary_pattern_id.clone(), - arrival_time_since_start: itinerary.arrival_time_since_start, - departure_time_since_start: itinerary.departure_time_since_start, - interpolated_time_since_start: itinerary.interpolated_time_since_start, - stop_id: itinerary.stop_id.clone(), - chateau: itinerary.chateau.clone().into(), - gtfs_stop_sequence: itinerary.gtfs_stop_sequence, - direction_pattern_id: itinerary_direction_id.clone(), - trip_headsign: itin_meta_ref.trip_headsign.clone(), - trip_headsign_translations: itin_meta_ref.trip_headsign_translations.clone(), - timezone: itin_meta_ref.timezone.clone(), - route_id: itin_meta_ref.route_id.clone() - }; + let new_itinerary = ItineraryPatternRowMerge { + onestop_feed_id: itinerary.onestop_feed_id.clone(), + attempt_id: itinerary.attempt_id.clone(), + itinerary_pattern_id: itinerary.itinerary_pattern_id.clone(), + arrival_time_since_start: itinerary.arrival_time_since_start, + departure_time_since_start: itinerary.departure_time_since_start, + interpolated_time_since_start: itinerary.interpolated_time_since_start, + stop_id: itinerary.stop_id.clone(), + chateau: itinerary.chateau.clone().into(), + gtfs_stop_sequence: itinerary.gtfs_stop_sequence, + direction_pattern_id: itinerary_direction_id.clone(), + trip_headsign: itin_meta_ref.trip_headsign.clone(), + trip_headsign_translations: itin_meta_ref + .trip_headsign_translations + .clone(), + timezone: itin_meta_ref.timezone.clone(), + route_id: itin_meta_ref.route_id.clone(), + }; - match itinerary_table.entry(( - itinerary.chateau.clone(), - itinerary.itinerary_pattern_id.clone(), - )) { - Entry::Occupied(mut oe) => { - oe.get_mut().push(new_itinerary); - } - Entry::Vacant(mut ve) => { - ve.insert(vec![new_itinerary]); + match itinerary_table.entry(( + itinerary.chateau.clone(), + itinerary.itinerary_pattern_id.clone(), + )) { + Entry::Occupied(mut oe) => { + oe.get_mut().push(new_itinerary); + } + Entry::Vacant(mut ve) => { + ve.insert(vec![new_itinerary]); + } } } - } } Err(err) => { return HttpResponse::InternalServerError().body(format!("{:#?}", err)); diff --git a/src/maple/gtfs_handlers/hull_from_gtfs.rs b/src/maple/gtfs_handlers/hull_from_gtfs.rs index 6bec882..b4ab9ba 100644 --- a/src/maple/gtfs_handlers/hull_from_gtfs.rs +++ b/src/maple/gtfs_handlers/hull_from_gtfs.rs @@ -1,44 +1,188 @@ +use actix_web::Route; use catenary::is_null_island; use geo::algorithm::concave_hull::ConcaveHull; -use geo::{MultiPoint, Point, Polygon}; +use geo::algorithm::convex_hull::ConvexHull; +use geo::Centroid; +use gtfs_structures::RouteType; +use geo::HaversineDistance; +use geo::{convex_hull, Coord, MultiPoint, Point, Polygon}; +use geo::Distance; +use geo::RhumbBearing; +use geo::RhumbDestination; pub fn hull_from_gtfs(gtfs: >fs_structures::Gtfs) -> Option { - match gtfs.shapes.len() > 3 { - // hull shapes with parameter of 50 - // it's still better than convex hull - true => { - let points: MultiPoint = gtfs - .shapes + let bus_only = gtfs + .routes + .iter() + .all(|(_, route)| route.route_type == RouteType::Bus); + + let contains_metro_and_bus_only = gtfs + .routes + .iter() + .any(|(_, route)| route.route_type == RouteType::Subway || route.route_type == RouteType::Bus); + + let list_of_coordinates_to_use_from_shapes = gtfs + .shapes + .iter() + .flat_map(|(id, points)| { + points .iter() - .flat_map(|(id, points)| { - points - .iter() - .filter(|point| !is_null_island(point.longitude, point.latitude)) - .map(|point| Point::new(point.longitude, point.latitude)) - }) - .collect::(); - Some(points.concave_hull(1.5)) + .filter(|point| !is_null_island(point.longitude, point.latitude)) + .map(|point| Point::new(point.longitude, point.latitude)) + }) + .collect::>(); + + let stop_points = gtfs + .stops + .iter() + .filter(|(_, stop)| stop.longitude.is_some() && stop.latitude.is_some()) + .filter(|(_, stop)| !is_null_island(stop.latitude.unwrap(), stop.longitude.unwrap())) + .map(|(_, stop)| Point::new(stop.longitude.unwrap(), stop.latitude.unwrap())) + .collect::>(); + + //join vecs together + + let new_point_collection = list_of_coordinates_to_use_from_shapes + .into_iter() + .chain(stop_points.into_iter()) + .collect::>(); + + if new_point_collection.len() < 4 { + return None; + } + + let multi_point = MultiPoint(new_point_collection); + + let concave_hull = multi_point.concave_hull(1.0); + let convex_hull = multi_point.convex_hull(); + + //buffer the convex hull by 5km if bus only, 10km for metros, but 50km if contains rail or other modes + + let buffer_distance = match bus_only { + true => 5000.0, + false => match contains_metro_and_bus_only { + true => 10000.0, + false => 50000.0, + }, + }; + + let mut buffered_convex_hull = buffer_geo_polygon(convex_hull, buffer_distance).unwrap(); + + //convert concave hull back into multipoint + + //let concave_hull_points = concave_hull.exterior().points().collect::>(); + + //let mut longest_side = longest_side_length_metres(&buffered_convex_hull); + + //while the convex hull is still long or too simple, try to shrink it + + /*while !(longest_side.length < 80000.0 || concave_hull_points.0.len() > 30) { + + //find the midpoint of the longest side + + let midpoint = buffered_convex_hull.exterior().points_iter().nth(longest_side.starting_index).unwrap().midpoint( + &buffered_convex_hull.exterior().points_iter().nth(longest_side.ending_index).unwrap() + ); + + //find the closest point on the concave hull to the midpoint + + let closest_point = concave_hull_points.0.iter().min_by_key(|point| point.haversine_distance(&midpoint)).unwrap(); + + //if the vector of convex hull coordinates contains the closest point coordinate , then we can't shrink it any further + + let closest_point_is_on_convex_hull = buffered_convex_hull.exterior().points_iter().any(|point| point == closest_point); + + if closest_point_is_on_convex_hull { + break; + } + + //if the closest point is within 5km of the midpoint, + + //recompute longest side of the polygon + longest_side = longest_side_length_metres(&buffered_convex_hull); + }*/ + + let buffer_concave_hull = buffer_geo_polygon(concave_hull, buffer_distance); + + match buffer_concave_hull { + Some(buffer_concave_hull) => Some(buffer_concave_hull), + None => Some(buffered_convex_hull) + } +} + +struct PolygonSide { + starting_index: usize, + ending_index: usize, + length: f64, +} + +pub fn longest_side_length_metres( + polygon: &geo::Polygon, +) -> PolygonSide { + let exterior = polygon.exterior(); + + let points = exterior.points_iter().collect::>(); + + let mut longest_side = PolygonSide { + starting_index: 0, + ending_index: 1, + length: 0.0, + }; + + for (index, point) in points.iter().enumerate() { + //skip the last one + if index == points.len() - 1 { + break; } - false => { - match gtfs.stops.len() > 3 { - true => { - //hull stops with parameter of 10 - - let points: MultiPoint = gtfs - .stops - .iter() - .filter(|(_, stop)| stop.longitude.is_some() && stop.latitude.is_some()) - .filter(|(_, stop)| { - !is_null_island(stop.latitude.unwrap(), stop.longitude.unwrap()) - }) - .map(|(_, stop)| { - Point::new(stop.longitude.unwrap(), stop.latitude.unwrap()) - }) - .collect::(); - Some(points.concave_hull(1.5)) - } - false => None, + + let next_point = points[index + 1]; + + let distance = point.haversine_distance(&next_point); + + if distance > longest_side.length { + longest_side = PolygonSide { + starting_index: index, + ending_index: index + 1, + length: distance, + }; + } + } + + longest_side +} + +pub fn buffer_geo_polygon( + polygon: geo::Polygon, + distance_metres: f64, +) -> Option> { + let centre = polygon.centroid(); + + match centre { + Some(centre) => { + let mut points = Vec::new(); + + let points_of_polygon = polygon.exterior().points_iter().collect::>(); + + for original_point in points_of_polygon { + //calculate bearing between the centre and the point + + let bearing = centre.rhumb_bearing(original_point); + + // calculate the distance_metres between the centre and the point + + let distance = centre.haversine_distance(&original_point); + + // calculate the new point + + let new_point = centre.rhumb_destination(distance + distance_metres, bearing); + + points.push(new_point); } + + let new_polygon = geo::Polygon::new(geo::LineString::new(points.into_iter().map(|x| Coord::from(x)).collect()), vec![]); + + Some(new_polygon) } + None => None, } }