use super::flight_plan::*;
use super::schedule::*;
use super::vehicle::*;
use super::{best_path, BestPathError, BestPathRequest};
use crate::grpc::client::GrpcClients;
use chrono::Duration;
use std::cmp::{max, min};
use std::collections::HashMap;
use std::str::FromStr;
use svc_gis_client_grpc::prelude::gis::*;
use svc_storage_client_grpc::prelude::*;
const MAX_DURATION_TIMESLOT_MINUTES: i64 = 30;
#[derive(Debug, Copy, Clone)]
pub enum VertiportError {
ClientError,
InvalidData,
NoVertipads,
NoSchedule,
InvalidSchedule,
Internal,
}
impl std::fmt::Display for VertiportError {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
match self {
VertiportError::ClientError => write!(f, "Client error"),
VertiportError::InvalidData => write!(f, "Invalid data"),
VertiportError::NoVertipads => write!(f, "No vertipads"),
VertiportError::NoSchedule => write!(f, "No schedule"),
VertiportError::InvalidSchedule => write!(f, "Invalid schedule"),
VertiportError::Internal => write!(f, "Internal error"),
}
}
}
pub enum GetVertipadsArg {
VertiportId(String),
VertipadIds(Vec<String>),
}
pub async fn get_vertipads(
clients: &GrpcClients,
arg: GetVertipadsArg,
) -> Result<Vec<String>, VertiportError> {
let mut filter = AdvancedSearchFilter::search_is_null("deleted_at".to_owned())
.and_equals("enabled".to_string(), "1".to_string());
match arg {
GetVertipadsArg::VertiportId(vertiport_id) => {
filter = filter.and_equals("vertiport_id".to_string(), vertiport_id);
}
GetVertipadsArg::VertipadIds(ids) => {
filter = filter.and_in("vertipad_id".to_string(), ids);
}
}
router_info!("(get_vertipads) proposed filter: {:?}", filter.clone());
let filter = AdvancedSearchFilter::default();
let Ok(response) = clients.storage.vertipad.search(filter).await else {
router_error!("(get_vertipads) Failed to get vertipads.");
return Err(VertiportError::NoVertipads);
};
router_info!("(get_vertipads) response: {:?}", response);
Ok(response
.into_inner()
.list
.into_iter()
.filter_map(|vp| {
let Some(data) = vp.data else {
return None;
};
if !data.enabled {
return None;
}
Some(vp.id)
})
.collect::<Vec<String>>())
}
#[allow(clippy::too_many_arguments)]
pub async fn get_timeslot_pairs(
origin_vertiport_id: &str,
origin_vertipad_id: Option<&str>,
target_vertiport_id: &str,
target_vertipad_id: Option<&str>,
origin_time_block: &Duration,
target_time_block: &Duration,
timeslot: &Timeslot,
existing_flight_plans: &[FlightPlanSchedule],
clients: &GrpcClients,
) -> Result<Vec<TimeslotPair>, VertiportError> {
let origin_timeslots = get_available_timeslots(
origin_vertiport_id,
origin_vertipad_id,
existing_flight_plans,
timeslot,
origin_time_block,
clients,
)
.await?;
let target_timeslots = get_available_timeslots(
target_vertiport_id,
target_vertipad_id,
existing_flight_plans,
timeslot,
target_time_block,
clients,
)
.await?;
get_vertipad_timeslot_pairs(
origin_vertiport_id,
target_vertiport_id,
origin_timeslots,
target_timeslots,
clients,
)
.await
}
async fn get_available_timeslots(
vertiport_id: &str,
vertipad_id: Option<&str>,
existing_flight_plans: &[FlightPlanSchedule],
timeslot: &Timeslot,
minimum_duration: &Duration,
clients: &GrpcClients,
) -> Result<HashMap<String, Vec<Timeslot>>, VertiportError> {
let calendar = get_vertiport_calendar(vertiport_id, clients).await?;
let base_timeslots = calendar
.to_timeslots(×lot.time_start, ×lot.time_end)
.map_err(|e| {
router_error!("(get_available_timeslots) Could not convert calendar to timeslots: {e}");
VertiportError::Internal
})?;
router_debug!(
"(get_available_timeslots) base_timeslots: {:?}",
base_timeslots
);
let max_duration = Duration::try_minutes(MAX_DURATION_TIMESLOT_MINUTES).ok_or_else(|| {
router_error!("(get_available_timeslots) error creating time delta.");
VertiportError::Internal
})?;
let filter = match vertipad_id {
Some(id) => GetVertipadsArg::VertipadIds(vec![id.to_string()]),
None => GetVertipadsArg::VertiportId(vertiport_id.to_string()),
};
let mut timeslots = get_vertipads(clients, filter)
.await?
.into_iter()
.map(|id| (id, base_timeslots.clone()))
.collect::<HashMap<String, Vec<Timeslot>>>();
let occupied_slots = build_timeslots_from_flight_plans(vertiport_id, existing_flight_plans)?;
router_debug!("(get_available_timeslots): vertiport: {:?}", vertiport_id);
router_debug!("(get_available_timeslots): vertipads {:?}", timeslots);
router_debug!(
"(get_available_timeslots): occupied {:?}",
occupied_slots
.iter()
.map(|(id, _)| id)
.collect::<Vec<&String>>()
);
for (vertipad_id, occupied_slot) in occupied_slots.iter() {
let Some(vertipad_slots) = timeslots.get_mut(vertipad_id) else {
router_error!("(get_available_timeslots) Vertipad {} (from a flight plan) not found in list of vertipads from storage.", vertipad_id);
continue;
};
*vertipad_slots = vertipad_slots
.iter_mut()
.flat_map(|slot| *slot - *occupied_slot)
.flat_map(|slot| slot.split(minimum_duration, &max_duration))
.collect::<Vec<Timeslot>>();
}
Ok(timeslots)
}
async fn get_vertiport_calendar(
vertiport_id: &str,
clients: &GrpcClients,
) -> Result<Calendar, VertiportError> {
let vertiport_object = match clients
.storage
.vertiport
.get_by_id(Id {
id: vertiport_id.to_string(),
})
.await
{
Ok(response) => response.into_inner(),
Err(e) => {
let error_str = format!("Could not retrieve data for vertiport {vertiport_id}.");
router_error!("(get_vertiport_calendar) {}: {e}", error_str);
return Err(VertiportError::ClientError);
}
};
let vertiport_data = match vertiport_object.data {
Some(d) => d,
None => {
let error_str = format!("Date invalid for vertiport {}.", vertiport_id);
router_error!("(get_vertiport_calendar) {}", error_str);
return Err(VertiportError::InvalidData);
}
};
let Some(vertiport_schedule) = vertiport_data.schedule else {
let error_str = format!("No schedule for vertiport {}.", vertiport_id);
router_error!("(get_vertiport_calendar) {}", error_str);
return Err(VertiportError::NoSchedule);
};
match Calendar::from_str(&vertiport_schedule) {
Ok(calendar) => Ok(calendar),
Err(e) => {
let error_str = format!("Schedule invalid for vertiport {}.", vertiport_id);
router_error!("(get_vertiport_calendar) {}: {}", error_str, e);
Err(VertiportError::InvalidSchedule)
}
}
}
fn build_timeslots_from_flight_plans(
vertiport_id: &str,
flight_plans: &[FlightPlanSchedule],
) -> Result<Vec<(String, Timeslot)>, VertiportError> {
let required_loading_time =
Duration::try_seconds(crate::grpc::api::query_flight::LOADING_AND_TAKEOFF_TIME_SECONDS)
.ok_or_else(|| {
router_error!("(build_timeslots_from_flight_plans) error creating time delta.");
VertiportError::Internal
})?;
let required_unloading_time =
Duration::try_seconds(crate::grpc::api::query_flight::LANDING_AND_UNLOADING_TIME_SECONDS)
.ok_or_else(|| {
router_error!("(build_timeslots_from_flight_plans) error creating time delta.");
VertiportError::Internal
})?;
let results = flight_plans
.iter()
.filter_map(|fp| {
if *vertiport_id == fp.origin_vertiport_id {
let timeslot = Timeslot {
time_start: fp.origin_timeslot_start,
time_end: fp.origin_timeslot_start + required_loading_time,
};
Some((fp.origin_vertipad_id.clone(), timeslot))
} else if *vertiport_id == fp.target_vertiport_id {
let timeslot = Timeslot {
time_start: fp.target_timeslot_start,
time_end: fp.target_timeslot_start + required_unloading_time,
};
Some((fp.target_vertipad_id.clone(), timeslot))
} else {
None
}
})
.collect::<Vec<(String, Timeslot)>>();
Ok(results)
}
#[derive(Debug, Clone)]
pub struct TimeslotPair {
pub origin_vertiport_id: String,
pub origin_vertipad_id: String,
pub origin_timeslot: Timeslot,
pub target_vertiport_id: String,
pub target_vertipad_id: String,
pub target_timeslot: Timeslot,
pub path: Vec<PointZ>,
pub distance_meters: f32,
}
impl From<TimeslotPair> for flight_plan::Data {
fn from(val: TimeslotPair) -> Self {
let points = val
.path
.iter()
.map(|p| GeoPoint {
latitude: p.latitude,
longitude: p.longitude,
})
.collect();
let path = Some(GeoLineString { points });
flight_plan::Data {
origin_vertiport_id: Some(val.origin_vertiport_id),
origin_vertipad_id: val.origin_vertipad_id,
origin_timeslot_start: Some(val.origin_timeslot.time_start.into()),
origin_timeslot_end: Some(val.origin_timeslot.time_end.into()),
target_vertiport_id: Some(val.target_vertiport_id),
target_vertipad_id: val.target_vertipad_id,
target_timeslot_start: Some(val.target_timeslot.time_start.into()),
target_timeslot_end: Some(val.target_timeslot.time_end.into()),
path,
..Default::default()
}
}
}
pub async fn get_vertipad_timeslot_pairs(
origin_vertiport_id: &str,
target_vertiport_id: &str,
mut origin_vertipads: HashMap<String, Vec<Timeslot>>,
mut target_vertipads: HashMap<String, Vec<Timeslot>>,
clients: &GrpcClients,
) -> Result<Vec<TimeslotPair>, VertiportError> {
let mut pairs = vec![];
let mut best_path_request = BestPathRequest {
origin_identifier: origin_vertiport_id.to_string(),
target_identifier: target_vertiport_id.to_string(),
origin_type: NodeType::Vertiport as i32,
target_type: NodeType::Vertiport as i32,
time_start: None,
time_end: None,
limit: 5,
};
for (origin_vertipad_id, origin_schedule) in origin_vertipads.iter_mut() {
origin_schedule.sort_by(|a, b| a.time_end.cmp(&b.time_end));
'origin_timeslots: for dts in origin_schedule.iter() {
for (target_vertipad_id, target_schedule) in target_vertipads.iter_mut() {
target_schedule.sort_by(|a, b| a.time_start.cmp(&b.time_start));
for ats in target_schedule.iter() {
if dts.time_start >= ats.time_end {
continue;
}
best_path_request.time_start = Some(dts.time_start.into());
best_path_request.time_end = Some(ats.time_end.into());
let paths = match best_path(&best_path_request, clients).await {
Ok(paths) => paths,
Err(BestPathError::NoPathFound) => {
router_debug!(
"(get_vertipad_timeslot_pairs) No path found from vertiport {}
to vertiport {} (from {} to {}).",
origin_vertiport_id,
target_vertiport_id,
dts.time_start,
ats.time_end
);
break 'origin_timeslots;
}
Err(BestPathError::ClientError) => {
router_error!(
"(get_vertipad_timeslot_pairs) Could not determine path."
);
return Err(VertiportError::ClientError);
}
};
let Some(path) = paths.first() else {
router_debug!(
"(get_vertipad_timeslot_pairs) No path found from vertiport {}
to vertiport {} (from {} to {}).",
origin_vertiport_id,
target_vertiport_id,
dts.time_start,
ats.time_end
);
break 'origin_timeslots;
};
let distance_meters = path.1 as f32;
let path = path.0.clone();
let estimated_duration_s = estimate_flight_time_seconds(&distance_meters)
.map_err(|e| {
router_error!(
"(get_vertipad_timeslot_pairs) Could not estimate flight time: {e}"
);
VertiportError::Internal
})?;
if dts.time_end + estimated_duration_s < ats.time_start {
break;
}
let origin_timeslot = Timeslot {
time_start: max(dts.time_start, ats.time_start - estimated_duration_s),
time_end: min(dts.time_end, ats.time_end - estimated_duration_s),
};
let target_timeslot = Timeslot {
time_start: max(
ats.time_start,
origin_timeslot.time_start + estimated_duration_s,
),
time_end: min(
ats.time_end,
origin_timeslot.time_end + estimated_duration_s,
),
};
pairs.push(TimeslotPair {
origin_vertiport_id: origin_vertiport_id.to_string(),
origin_vertipad_id: origin_vertipad_id.clone(),
origin_timeslot,
target_vertiport_id: target_vertiport_id.to_string(),
target_vertipad_id: target_vertipad_id.clone(),
target_timeslot,
path,
distance_meters,
});
}
}
}
}
pairs.sort_by(
|a, b| match a.distance_meters.partial_cmp(&b.distance_meters) {
Some(ord) => ord,
None => {
router_error!(
"(get_vertipad_timeslot_pairs) Could not compare distances: {}, {}",
a.distance_meters,
b.distance_meters
);
std::cmp::Ordering::Equal
}
},
);
Ok(pairs)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::grpc::client::get_clients;
use crate::router::vehicle::estimate_flight_time_seconds;
use chrono::DateTime;
use uuid::Uuid;
#[tokio::test]
#[cfg(feature = "stub_backends")]
async fn ut_get_vertipad_pairs_no_overlap() {
let origin_vertiport_id: String = Uuid::new_v4().to_string();
let target_vertiport_id: String = Uuid::new_v4().to_string();
let origin_vertipad_id: String = Uuid::new_v4().to_string();
let target_vertipad_id: String = Uuid::new_v4().to_string();
let clients = get_clients().await;
let origin_start = DateTime::from_str("2021-01-01T03:00:00Z").unwrap();
let origin_end = DateTime::from_str("2021-01-01T06:00:00Z").unwrap();
let origin_vertipads = HashMap::from([(
origin_vertipad_id.clone(),
vec![Timeslot {
time_start: origin_start,
time_end: origin_end,
}],
)]);
let target_vertipads = HashMap::from([(
target_vertipad_id.clone(),
vec![Timeslot {
time_start: DateTime::from_str("2021-01-01T10:00:00Z").unwrap(),
time_end: DateTime::from_str("2021-01-01T13:00:00Z").unwrap(),
}],
)]);
let pairs = get_vertipad_timeslot_pairs(
&origin_vertiport_id,
&target_vertiport_id,
origin_vertipads,
target_vertipads,
&clients,
)
.await
.unwrap();
assert!(pairs.is_empty());
}
#[tokio::test]
#[cfg(feature = "stub_backends")]
async fn ut_get_vertipad_pairs_no_overlap_target_lead() {
let origin_vertiport_id: String = Uuid::new_v4().to_string();
let target_vertiport_id: String = Uuid::new_v4().to_string();
let origin_vertipad_id: String = Uuid::new_v4().to_string();
let target_vertipad_id: String = Uuid::new_v4().to_string();
let clients = get_clients().await;
let origin_start = DateTime::from_str("2021-01-01T06:00:00Z").unwrap();
let origin_end = DateTime::from_str("2021-01-01T10:00:00Z").unwrap();
let origin_vertipads = HashMap::from([(
origin_vertipad_id.clone(),
vec![Timeslot {
time_start: origin_start,
time_end: origin_end,
}],
)]);
let target_vertipads = HashMap::from([(
target_vertipad_id.clone(),
vec![Timeslot {
time_start: DateTime::from_str("2021-01-01T03:00:00Z").unwrap(),
time_end: DateTime::from_str("2021-01-01T06:00:00Z").unwrap(),
}],
)]);
let pairs = get_vertipad_timeslot_pairs(
&origin_vertiport_id,
&target_vertiport_id,
origin_vertipads,
target_vertipads,
&clients,
)
.await
.unwrap();
println!("{:?}", pairs);
assert!(pairs.is_empty());
}
#[tokio::test]
#[cfg(feature = "stub_backends")]
async fn ut_get_vertipad_pairs_some_overlap() {
let origin_vertiport_id: String = Uuid::new_v4().to_string();
let target_vertiport_id: String = Uuid::new_v4().to_string();
let origin_vertipad_id: String = Uuid::new_v4().to_string();
let target_vertipad_id: String = Uuid::new_v4().to_string();
let clients = get_clients().await;
let origin_start = DateTime::from_str("2021-01-01T03:00:00Z").unwrap();
let origin_end = DateTime::from_str("2021-01-01T06:00:00Z").unwrap();
let origin_vertipads = HashMap::from([(
origin_vertipad_id.clone(),
vec![Timeslot {
time_start: origin_start,
time_end: origin_end,
}],
)]);
let target_start = DateTime::from_str("2021-01-01T06:00:00Z").unwrap();
let target_end = DateTime::from_str("2021-01-01T09:00:00Z").unwrap();
let target_vertipads = HashMap::from([(
target_vertipad_id.clone(),
vec![Timeslot {
time_start: target_start,
time_end: target_end,
}],
)]);
let pairs = get_vertipad_timeslot_pairs(
&origin_vertiport_id,
&target_vertiport_id,
origin_vertipads,
target_vertipads,
&clients,
)
.await
.unwrap();
assert_eq!(pairs.len(), 1);
let pair = pairs.last().unwrap();
let flight_duration = estimate_flight_time_seconds(&pair.distance_meters).unwrap();
assert_eq!(pair.origin_vertipad_id, origin_vertipad_id);
assert_eq!(pair.target_vertipad_id, target_vertipad_id);
assert_eq!(
pair.origin_timeslot.time_start,
target_start - flight_duration
);
assert_eq!(pair.origin_timeslot.time_end, origin_end);
assert_eq!(pair.target_timeslot.time_start, target_start);
assert_eq!(pair.target_timeslot.time_end, origin_end + flight_duration);
}
#[tokio::test]
#[cfg(feature = "stub_backends")]
async fn ut_get_vertipad_pairs_overlap_nested() {
let origin_vertiport_id: String = Uuid::new_v4().to_string();
let target_vertiport_id: String = Uuid::new_v4().to_string();
let origin_vertipad_id: String = Uuid::new_v4().to_string();
let target_vertipad_id: String = Uuid::new_v4().to_string();
let clients = get_clients().await;
let origin_start = DateTime::from_str("2021-01-01T03:00:00Z").unwrap();
let origin_end = DateTime::from_str("2021-01-01T09:00:00Z").unwrap();
let origin_vertipads = HashMap::from([(
origin_vertipad_id.clone(),
vec![Timeslot {
time_start: origin_start,
time_end: origin_end,
}],
)]);
let target_start = DateTime::from_str("2021-01-01T05:00:00Z").unwrap();
let target_end = DateTime::from_str("2021-01-01T07:00:00Z").unwrap();
let target_vertipads = HashMap::from([(
target_vertipad_id.clone(),
vec![Timeslot {
time_start: target_start,
time_end: target_end,
}],
)]);
let pairs = get_vertipad_timeslot_pairs(
&origin_vertiport_id,
&target_vertiport_id,
origin_vertipads,
target_vertipads,
&clients,
)
.await
.unwrap();
assert_eq!(pairs.len(), 1);
let pair = pairs.last().unwrap();
let flight_duration = estimate_flight_time_seconds(&pair.distance_meters).unwrap();
assert_eq!(pair.origin_vertipad_id, origin_vertipad_id);
assert_eq!(pair.target_vertipad_id, target_vertipad_id);
assert_eq!(
pair.origin_timeslot.time_start,
target_start - flight_duration
);
assert_eq!(pair.origin_timeslot.time_end, target_end - flight_duration);
assert_eq!(pair.target_timeslot.time_start, target_start);
assert_eq!(pair.target_timeslot.time_end, target_end);
}
#[tokio::test]
#[cfg(feature = "stub_backends")]
async fn ut_get_vertipad_pairs_overlap_target_window_lead() {
let origin_vertiport_id: String = Uuid::new_v4().to_string();
let target_vertiport_id: String = Uuid::new_v4().to_string();
let origin_vertipad_id: String = Uuid::new_v4().to_string();
let target_vertipad_id: String = Uuid::new_v4().to_string();
let clients = get_clients().await;
let origin_start = DateTime::from_str("2021-01-01T06:00:00Z").unwrap();
let origin_end = DateTime::from_str("2021-01-01T09:00:00Z").unwrap();
let origin_vertipads = HashMap::from([(
origin_vertipad_id.clone(),
vec![Timeslot {
time_start: origin_start,
time_end: origin_end,
}],
)]);
let target_start = DateTime::from_str("2021-01-01T03:00:00Z").unwrap();
let target_end = DateTime::from_str("2021-01-01T07:00:00Z").unwrap();
let target_vertipads = HashMap::from([(
target_vertipad_id.clone(),
vec![Timeslot {
time_start: target_start,
time_end: target_end,
}],
)]);
let pairs = get_vertipad_timeslot_pairs(
&origin_vertiport_id,
&target_vertiport_id,
origin_vertipads,
target_vertipads,
&clients,
)
.await
.unwrap();
assert_eq!(pairs.len(), 1);
let pair = pairs.last().unwrap();
let flight_duration = estimate_flight_time_seconds(&pair.distance_meters).unwrap();
assert_eq!(pair.origin_vertipad_id, origin_vertipad_id);
assert_eq!(pair.target_vertipad_id, target_vertipad_id);
assert_eq!(pair.origin_timeslot.time_start, origin_start);
assert_eq!(pair.origin_timeslot.time_end, target_end - flight_duration);
assert_eq!(
pair.target_timeslot.time_start,
origin_start + flight_duration
);
assert_eq!(pair.target_timeslot.time_end, target_end);
}
#[tokio::test]
#[cfg(feature = "stub_backends")]
async fn ut_get_vertipad_pairs_overlap_multiple() {
let origin_vertiport_id: String = Uuid::new_v4().to_string();
let target_vertiport_id: String = Uuid::new_v4().to_string();
let origin_vertipad_id: String = Uuid::new_v4().to_string();
let target_vertipad_id: String = Uuid::new_v4().to_string();
let clients = get_clients().await;
let origin_start = DateTime::from_str("2021-01-01T03:00:00Z").unwrap();
let origin_end = DateTime::from_str("2021-01-01T09:00:00Z").unwrap();
let origin_vertipads = HashMap::from([(
origin_vertipad_id.clone(),
vec![Timeslot {
time_start: origin_start,
time_end: origin_end,
}],
)]);
let target_timeslot_1 = Timeslot {
time_start: DateTime::from_str("2021-01-01T05:00:00Z").unwrap(),
time_end: DateTime::from_str("2021-01-01T07:00:00Z").unwrap(),
};
let target_timeslot_2 = Timeslot {
time_start: DateTime::from_str("2021-01-01T09:00:00Z").unwrap(),
time_end: DateTime::from_str("2021-01-01T10:00:00Z").unwrap(),
};
let target_vertipads = HashMap::from([(
target_vertipad_id.clone(),
vec![target_timeslot_1, target_timeslot_2],
)]);
let pairs = get_vertipad_timeslot_pairs(
&origin_vertiport_id,
&target_vertiport_id,
origin_vertipads,
target_vertipads,
&clients,
)
.await
.unwrap();
assert_eq!(pairs.len(), 2);
{
let pair = pairs[0].clone();
let target_timeslot = target_timeslot_1;
assert_eq!(pair.origin_vertipad_id, origin_vertipad_id);
assert_eq!(pair.target_vertipad_id, target_vertipad_id);
let flight_duration = estimate_flight_time_seconds(&pair.distance_meters).unwrap();
assert_eq!(
pair.origin_timeslot.time_start,
target_timeslot.time_start - flight_duration
);
assert_eq!(
pair.origin_timeslot.time_end,
target_timeslot.time_end - flight_duration
);
assert_eq!(pair.target_timeslot.time_start, target_timeslot.time_start);
assert_eq!(pair.target_timeslot.time_end, target_timeslot.time_end);
}
{
let pair = pairs[1].clone();
let target_timeslot = target_timeslot_2;
assert_eq!(pair.origin_vertipad_id, origin_vertipad_id);
assert_eq!(pair.target_vertipad_id, target_vertipad_id);
let flight_duration = estimate_flight_time_seconds(&pair.distance_meters).unwrap();
assert_eq!(
pair.origin_timeslot.time_start,
target_timeslot.time_start - flight_duration
);
assert_eq!(pair.origin_timeslot.time_end, origin_end);
assert_eq!(pair.target_timeslot.time_start, target_timeslot.time_start);
assert_eq!(pair.target_timeslot.time_end, origin_end + flight_duration);
}
}
}