use std::sync::Arc;
use crate::services::reviewable_exact_evidence;
use crate::walkability::VerticalConnector;
use axioval_ir::{Evidence, ObjectId};
use thiserror::Error;
#[derive(Clone, Debug, Error, PartialEq, Eq)]
pub enum MetricRoutingError {
#[error("metric point coordinates must be finite")]
InvalidCoordinate,
#[error("metric length interval is invalid")]
InvalidLengthInterval,
#[error("mobility profile contains an invalid dimension")]
InvalidMobilityProfile,
#[error("metric route evidence is empty")]
EmptyRouteEvidence,
#[error("metric route provenance is not exact and reviewable")]
InexactRouteEvidence,
#[error("metric evidence is incomplete")]
IncompleteMetricEvidence,
#[error("metric routing backend returned mismatched endpoints")]
ResponseEndpointMismatch,
#[error("metric geometry is unavailable for `{0}`")]
MissingGeometry(Box<ObjectId>),
#[error("metric routing query unavailable: {0}")]
Unavailable(String),
#[error("a metric routing query needs at least one target")]
NoTargets,
#[error("metric routing tolerance must be finite and non-negative")]
InvalidTolerance,
#[error("metric routing backend answered inconsistently with the request")]
InconsistentResponse,
#[error("a climb's vertical factor must be finite and non-negative")]
InvalidClimb,
#[error("a vertical connector is given twice with different kinds")]
ConflictingConnector,
#[error("a travel cost factor must be finite and at least one")]
InvalidCostFactor,
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum ThresholdVerdict {
Satisfied,
Violated,
Indeterminate,
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct LengthInterval {
lower_metres: f64,
upper_metres: f64,
}
impl LengthInterval {
pub fn try_new(lower_metres: f64, upper_metres: f64) -> Result<Self, MetricRoutingError> {
if !valid_non_negative(lower_metres)
|| !valid_non_negative(upper_metres)
|| lower_metres > upper_metres
{
return Err(MetricRoutingError::InvalidLengthInterval);
}
Ok(Self {
lower_metres,
upper_metres,
})
}
pub fn exact(metres: f64) -> Result<Self, MetricRoutingError> {
Self::try_new(metres, metres)
}
pub fn lower_metres(&self) -> f64 {
self.lower_metres
}
pub fn upper_metres(&self) -> f64 {
self.upper_metres
}
#[allow(clippy::float_cmp)]
pub fn is_exact(&self) -> bool {
self.lower_metres == self.upper_metres
}
pub fn compare_maximum(
&self,
maximum_metres: f64,
) -> Result<ThresholdVerdict, MetricRoutingError> {
if !valid_non_negative(maximum_metres) {
return Err(MetricRoutingError::InvalidLengthInterval);
}
if self.upper_metres <= maximum_metres {
Ok(ThresholdVerdict::Satisfied)
} else if self.lower_metres > maximum_metres {
Ok(ThresholdVerdict::Violated)
} else {
Ok(ThresholdVerdict::Indeterminate)
}
}
}
#[derive(Clone, Debug, PartialEq)]
pub struct MetricPoint {
subject: ObjectId,
coordinates_metres: [f64; 3],
}
impl MetricPoint {
pub fn try_new(
subject: ObjectId,
coordinates_metres: [f64; 3],
) -> Result<Self, MetricRoutingError> {
if !coordinates_metres.iter().all(|value| value.is_finite()) {
return Err(MetricRoutingError::InvalidCoordinate);
}
Ok(Self {
subject,
coordinates_metres,
})
}
pub fn subject(&self) -> &ObjectId {
&self.subject
}
pub fn coordinates_metres(&self) -> [f64; 3] {
self.coordinates_metres
}
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct MobilityProfile {
radius_metres: f64,
height_metres: f64,
maximum_step_metres: f64,
maximum_slope: f64,
}
impl MobilityProfile {
pub fn try_new(
radius_metres: f64,
height_metres: f64,
maximum_step_metres: f64,
maximum_slope: f64,
) -> Result<Self, MetricRoutingError> {
if ![
radius_metres,
height_metres,
maximum_step_metres,
maximum_slope,
]
.into_iter()
.all(valid_non_negative)
{
return Err(MetricRoutingError::InvalidMobilityProfile);
}
Ok(Self {
radius_metres,
height_metres,
maximum_step_metres,
maximum_slope,
})
}
pub fn radius_metres(&self) -> f64 {
self.radius_metres
}
pub fn height_metres(&self) -> f64 {
self.height_metres
}
pub fn maximum_step_metres(&self) -> f64 {
self.maximum_step_metres
}
pub fn maximum_slope(&self) -> f64 {
self.maximum_slope
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum StairLength {
Slope,
HorizontalPlusVertical,
}
impl StairLength {
#[must_use]
pub fn as_str(self) -> &'static str {
match self {
Self::Slope => "slope",
Self::HorizontalPlusVertical => "horizontal-plus-vertical",
}
}
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct ClimbLength {
measure: StairLength,
vertical_factor: f64,
}
impl ClimbLength {
pub fn try_new(measure: StairLength, vertical_factor: f64) -> Result<Self, MetricRoutingError> {
if !valid_non_negative(vertical_factor) {
return Err(MetricRoutingError::InvalidClimb);
}
Ok(Self {
measure,
vertical_factor,
})
}
#[must_use]
pub fn slope() -> Self {
Self {
measure: StairLength::Slope,
vertical_factor: 1.0,
}
}
#[must_use]
pub fn measure(&self) -> StairLength {
self.measure
}
#[must_use]
pub fn vertical_factor(&self) -> f64 {
self.vertical_factor
}
#[must_use]
pub fn length(&self, horizontal: LengthInterval, rise: LengthInterval) -> LengthInterval {
let at = |h: f64, v: f64| match self.measure {
StairLength::Slope => h.hypot(self.vertical_factor * v),
StairLength::HorizontalPlusVertical => self.vertical_factor.mul_add(v, h),
};
let lower = at(horizontal.lower_metres(), rise.lower_metres());
let upper = at(horizontal.upper_metres(), rise.upper_metres());
LengthInterval {
lower_metres: (lower * (1.0 - CLIMB_ROUNDING)).max(0.0),
upper_metres: upper * (1.0 + CLIMB_ROUNDING),
}
}
}
const CLIMB_ROUNDING: f64 = 4.0 * f64::EPSILON;
#[derive(Clone, Debug, PartialEq)]
pub struct ConnectorRouting {
connectors: Vec<VerticalConnector>,
climb: ClimbLength,
}
impl ConnectorRouting {
pub fn try_new(
mut connectors: Vec<VerticalConnector>,
climb: ClimbLength,
) -> Result<Self, MetricRoutingError> {
connectors.sort();
connectors.dedup();
if connectors
.windows(2)
.any(|pair| pair[0].object() == pair[1].object())
{
return Err(MetricRoutingError::ConflictingConnector);
}
Ok(Self { connectors, climb })
}
#[must_use]
pub fn connectors(&self) -> &[VerticalConnector] {
&self.connectors
}
#[must_use]
pub fn climb(&self) -> ClimbLength {
self.climb
}
}
#[derive(Clone, Debug, PartialEq)]
pub struct TravelCost {
object: ObjectId,
factor: f64,
}
impl TravelCost {
pub fn try_new(object: ObjectId, factor: f64) -> Result<Self, MetricRoutingError> {
if !(factor.is_finite() && factor >= 1.0) {
return Err(MetricRoutingError::InvalidCostFactor);
}
Ok(Self { object, factor })
}
#[must_use]
pub fn object(&self) -> &ObjectId {
&self.object
}
#[must_use]
pub fn factor(&self) -> f64 {
self.factor
}
}
fn settled(mut costs: Vec<TravelCost>) -> Vec<TravelCost> {
costs.retain(|cost| cost.factor > 1.0);
costs.sort_by(|a, b| a.object.cmp(&b.object).then(b.factor.total_cmp(&a.factor)));
costs.dedup_by(|later, earlier| later.object == earlier.object);
costs
}
#[derive(Clone, Debug, PartialEq)]
pub struct MetricRouteRequest {
origin: MetricPoint,
destination: MetricPoint,
profile: MobilityProfile,
connectors: Option<ConnectorRouting>,
}
impl MetricRouteRequest {
pub fn new(origin: MetricPoint, destination: MetricPoint, profile: MobilityProfile) -> Self {
Self {
origin,
destination,
profile,
connectors: None,
}
}
#[must_use]
pub fn with_connectors(mut self, connectors: ConnectorRouting) -> Self {
self.connectors = Some(connectors);
self
}
pub fn connectors(&self) -> Option<&ConnectorRouting> {
self.connectors.as_ref()
}
pub fn origin(&self) -> &MetricPoint {
&self.origin
}
pub fn destination(&self) -> &MetricPoint {
&self.destination
}
pub fn profile(&self) -> MobilityProfile {
self.profile
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct CompleteMetricEvidence(Evidence);
impl CompleteMetricEvidence {
pub fn try_new(evidence: Evidence) -> Result<Self, MetricRoutingError> {
if !reviewable_exact_evidence(&evidence) {
return Err(MetricRoutingError::IncompleteMetricEvidence);
}
Ok(Self(evidence))
}
pub fn evidence(&self) -> &Evidence {
&self.0
}
}
#[derive(Clone, Debug, PartialEq)]
pub struct BlockedMetricRouteEvidence {
request: MetricRouteRequest,
completeness: CompleteMetricEvidence,
}
impl BlockedMetricRouteEvidence {
pub fn new(request: MetricRouteRequest, completeness: CompleteMetricEvidence) -> Self {
Self {
request,
completeness,
}
}
pub fn request(&self) -> &MetricRouteRequest {
&self.request
}
pub fn completeness(&self) -> &CompleteMetricEvidence {
&self.completeness
}
}
#[derive(Clone, Debug, PartialEq)]
pub struct MetricRouteEvidence {
shortest_distance: LengthInterval,
waypoints: Vec<MetricPoint>,
traversed_objects: Vec<ObjectId>,
evidence: Evidence,
}
impl MetricRouteEvidence {
pub fn try_new(
shortest_distance: LengthInterval,
waypoints: Vec<MetricPoint>,
traversed_objects: Vec<ObjectId>,
evidence: Evidence,
) -> Result<Self, MetricRoutingError> {
if waypoints.is_empty() || traversed_objects.is_empty() {
return Err(MetricRoutingError::EmptyRouteEvidence);
}
if !reviewable_exact_evidence(&evidence) {
return Err(MetricRoutingError::InexactRouteEvidence);
}
Ok(Self {
shortest_distance,
waypoints,
traversed_objects,
evidence,
})
}
pub fn shortest_distance(&self) -> &LengthInterval {
&self.shortest_distance
}
pub fn waypoints(&self) -> &[MetricPoint] {
&self.waypoints
}
pub fn traversed_objects(&self) -> &[ObjectId] {
&self.traversed_objects
}
pub fn evidence(&self) -> &Evidence {
&self.evidence
}
}
#[derive(Clone, Debug, PartialEq)]
pub enum MetricRouteOutcome {
Reachable(MetricRouteEvidence),
Blocked(BlockedMetricRouteEvidence),
}
#[derive(Clone, Debug, PartialEq)]
pub struct NearestTargetRequest {
origin: MetricPoint,
targets: Vec<MetricPoint>,
profile: MobilityProfile,
avoided: Vec<ObjectId>,
connectors: Option<ConnectorRouting>,
costs: Vec<TravelCost>,
}
impl NearestTargetRequest {
pub fn try_new(
origin: MetricPoint,
targets: Vec<MetricPoint>,
profile: MobilityProfile,
) -> Result<Self, MetricRoutingError> {
if targets.is_empty() {
return Err(MetricRoutingError::NoTargets);
}
Ok(Self {
origin,
targets,
profile,
avoided: Vec::new(),
connectors: None,
costs: Vec::new(),
})
}
#[must_use]
pub fn with_costs(mut self, costs: Vec<TravelCost>) -> Self {
self.costs = settled(costs);
self
}
pub fn costs(&self) -> &[TravelCost] {
&self.costs
}
#[must_use]
pub fn with_avoided(mut self, mut avoided: Vec<ObjectId>) -> Self {
avoided.sort();
avoided.dedup();
self.avoided = avoided;
self
}
#[must_use]
pub fn with_connectors(mut self, connectors: ConnectorRouting) -> Self {
self.connectors = Some(connectors);
self
}
pub fn connectors(&self) -> Option<&ConnectorRouting> {
self.connectors.as_ref()
}
pub fn origin(&self) -> &MetricPoint {
&self.origin
}
pub fn targets(&self) -> &[MetricPoint] {
&self.targets
}
pub fn profile(&self) -> MobilityProfile {
self.profile
}
pub fn avoided(&self) -> &[ObjectId] {
&self.avoided
}
}
#[derive(Clone, Debug, PartialEq)]
pub struct NearestTargetEvidence {
target: usize,
shortest_distance: LengthInterval,
waypoints: Vec<MetricPoint>,
evidence: Evidence,
}
impl NearestTargetEvidence {
pub fn try_new(
target: usize,
shortest_distance: LengthInterval,
waypoints: Vec<MetricPoint>,
evidence: Evidence,
) -> Result<Self, MetricRoutingError> {
if waypoints.is_empty() {
return Err(MetricRoutingError::EmptyRouteEvidence);
}
if !reviewable_exact_evidence(&evidence) {
return Err(MetricRoutingError::InexactRouteEvidence);
}
Ok(Self {
target,
shortest_distance,
waypoints,
evidence,
})
}
pub fn target(&self) -> usize {
self.target
}
pub fn shortest_distance(&self) -> &LengthInterval {
&self.shortest_distance
}
pub fn waypoints(&self) -> &[MetricPoint] {
&self.waypoints
}
pub fn evidence(&self) -> &Evidence {
&self.evidence
}
}
#[derive(Clone, Debug, PartialEq)]
pub struct UnreachableTargetsEvidence {
request: NearestTargetRequest,
completeness: CompleteMetricEvidence,
}
impl UnreachableTargetsEvidence {
pub fn new(request: NearestTargetRequest, completeness: CompleteMetricEvidence) -> Self {
Self {
request,
completeness,
}
}
pub fn request(&self) -> &NearestTargetRequest {
&self.request
}
pub fn completeness(&self) -> &CompleteMetricEvidence {
&self.completeness
}
}
#[derive(Clone, Debug, PartialEq)]
pub enum NearestTargetOutcome {
Reached(NearestTargetEvidence),
Unreachable(UnreachableTargetsEvidence),
}
#[derive(Clone, Debug, PartialEq)]
pub struct FarthestPointRequest {
region: ObjectId,
targets: Vec<MetricPoint>,
profile: MobilityProfile,
tolerance_metres: f64,
connectors: Option<ConnectorRouting>,
costs: Vec<TravelCost>,
}
impl FarthestPointRequest {
pub fn try_new(
region: ObjectId,
targets: Vec<MetricPoint>,
profile: MobilityProfile,
tolerance_metres: f64,
) -> Result<Self, MetricRoutingError> {
if targets.is_empty() {
return Err(MetricRoutingError::NoTargets);
}
if !valid_non_negative(tolerance_metres) {
return Err(MetricRoutingError::InvalidTolerance);
}
Ok(Self {
region,
targets,
profile,
tolerance_metres,
connectors: None,
costs: Vec::new(),
})
}
#[must_use]
pub fn with_costs(mut self, costs: Vec<TravelCost>) -> Self {
self.costs = settled(costs);
self
}
pub fn costs(&self) -> &[TravelCost] {
&self.costs
}
#[must_use]
pub fn with_connectors(mut self, connectors: ConnectorRouting) -> Self {
self.connectors = Some(connectors);
self
}
pub fn connectors(&self) -> Option<&ConnectorRouting> {
self.connectors.as_ref()
}
pub fn region(&self) -> &ObjectId {
&self.region
}
pub fn targets(&self) -> &[MetricPoint] {
&self.targets
}
pub fn profile(&self) -> MobilityProfile {
self.profile
}
pub fn tolerance_metres(&self) -> f64 {
self.tolerance_metres
}
}
#[derive(Clone, Debug, PartialEq)]
pub struct FarthestPointEvidence {
distance: LengthInterval,
witness: MetricPoint,
converged: bool,
evidence: Evidence,
}
impl FarthestPointEvidence {
pub fn try_new(
distance: LengthInterval,
witness: MetricPoint,
converged: bool,
evidence: Evidence,
) -> Result<Self, MetricRoutingError> {
if !reviewable_exact_evidence(&evidence) {
return Err(MetricRoutingError::InexactRouteEvidence);
}
Ok(Self {
distance,
witness,
converged,
evidence,
})
}
pub fn distance(&self) -> &LengthInterval {
&self.distance
}
pub fn witness(&self) -> &MetricPoint {
&self.witness
}
pub fn converged(&self) -> bool {
self.converged
}
pub fn evidence(&self) -> &Evidence {
&self.evidence
}
}
#[derive(Clone, Debug, PartialEq)]
pub struct UnreachableRegionEvidence {
request: FarthestPointRequest,
witness: MetricPoint,
completeness: CompleteMetricEvidence,
}
impl UnreachableRegionEvidence {
pub fn new(
request: FarthestPointRequest,
witness: MetricPoint,
completeness: CompleteMetricEvidence,
) -> Self {
Self {
request,
witness,
completeness,
}
}
pub fn request(&self) -> &FarthestPointRequest {
&self.request
}
pub fn witness(&self) -> &MetricPoint {
&self.witness
}
pub fn completeness(&self) -> &CompleteMetricEvidence {
&self.completeness
}
}
#[derive(Clone, Debug, PartialEq)]
pub enum FarthestPointOutcome {
Bounded(FarthestPointEvidence),
Unreachable(UnreachableRegionEvidence),
}
#[derive(Clone, Debug, PartialEq)]
pub struct PathTraceRequest {
waypoints: Vec<MetricPoint>,
objects: Vec<ObjectId>,
}
impl PathTraceRequest {
pub fn try_new(
waypoints: Vec<MetricPoint>,
mut objects: Vec<ObjectId>,
) -> Result<Self, MetricRoutingError> {
if waypoints.is_empty() {
return Err(MetricRoutingError::EmptyRouteEvidence);
}
objects.sort();
objects.dedup();
Ok(Self { waypoints, objects })
}
pub fn waypoints(&self) -> &[MetricPoint] {
&self.waypoints
}
pub fn objects(&self) -> &[ObjectId] {
&self.objects
}
pub fn plan_length_metres(&self) -> f64 {
self.waypoints
.windows(2)
.map(|pair| {
let ([ax, ay, _], [bx, by, _]) =
(pair[0].coordinates_metres(), pair[1].coordinates_metres());
(bx - ax).hypot(by - ay)
})
.sum()
}
}
#[derive(Clone, Debug, PartialEq)]
pub struct PathTrace {
lengths: Vec<Result<LengthInterval, String>>,
evidence: Evidence,
}
impl PathTrace {
pub fn try_new(
lengths: Vec<Result<LengthInterval, String>>,
evidence: Evidence,
) -> Result<Self, MetricRoutingError> {
if !reviewable_exact_evidence(&evidence) {
return Err(MetricRoutingError::InexactRouteEvidence);
}
Ok(Self { lengths, evidence })
}
pub fn lengths(&self) -> &[Result<LengthInterval, String>] {
&self.lengths
}
pub fn evidence(&self) -> &Evidence {
&self.evidence
}
}
#[derive(Clone, Debug, PartialEq)]
pub struct ForcedWalkRequest {
origin: MetricPoint,
targets: Vec<MetricPoint>,
through: ObjectId,
profile: MobilityProfile,
tolerance_metres: f64,
avoided: Vec<ObjectId>,
connectors: Option<ConnectorRouting>,
}
impl ForcedWalkRequest {
pub fn try_new(
origin: MetricPoint,
targets: Vec<MetricPoint>,
through: ObjectId,
profile: MobilityProfile,
tolerance_metres: f64,
) -> Result<Self, MetricRoutingError> {
if targets.is_empty() {
return Err(MetricRoutingError::NoTargets);
}
if !valid_non_negative(tolerance_metres) {
return Err(MetricRoutingError::InvalidTolerance);
}
Ok(Self {
origin,
targets,
through,
profile,
tolerance_metres,
avoided: Vec::new(),
connectors: None,
})
}
#[must_use]
pub fn with_avoided(mut self, mut avoided: Vec<ObjectId>) -> Self {
avoided.sort();
avoided.dedup();
self.avoided = avoided;
self
}
#[must_use]
pub fn with_connectors(mut self, connectors: ConnectorRouting) -> Self {
self.connectors = Some(connectors);
self
}
pub fn origin(&self) -> &MetricPoint {
&self.origin
}
pub fn targets(&self) -> &[MetricPoint] {
&self.targets
}
pub fn through(&self) -> &ObjectId {
&self.through
}
pub fn profile(&self) -> MobilityProfile {
self.profile
}
pub fn tolerance_metres(&self) -> f64 {
self.tolerance_metres
}
pub fn avoided(&self) -> &[ObjectId] {
&self.avoided
}
pub fn connectors(&self) -> Option<&ConnectorRouting> {
self.connectors.as_ref()
}
}
#[derive(Clone, Debug, PartialEq)]
pub struct ForcedWalkEvidence {
lower_metres: f64,
upper_metres: f64,
converged: bool,
evidence: Evidence,
}
impl ForcedWalkEvidence {
pub fn try_new(
lower_metres: f64,
upper_metres: f64,
converged: bool,
evidence: Evidence,
) -> Result<Self, MetricRoutingError> {
if !valid_non_negative(lower_metres) || upper_metres.is_nan() || upper_metres < lower_metres
{
return Err(MetricRoutingError::InvalidLengthInterval);
}
if !reviewable_exact_evidence(&evidence) {
return Err(MetricRoutingError::InexactRouteEvidence);
}
Ok(Self {
lower_metres,
upper_metres,
converged,
evidence,
})
}
pub fn lower_metres(&self) -> f64 {
self.lower_metres
}
pub fn upper_metres(&self) -> f64 {
self.upper_metres
}
pub fn converged(&self) -> bool {
self.converged
}
pub fn evidence(&self) -> &Evidence {
&self.evidence
}
}
#[derive(Clone, Debug, PartialEq)]
pub struct NeverEnteredEvidence {
request: ForcedWalkRequest,
completeness: CompleteMetricEvidence,
}
impl NeverEnteredEvidence {
pub fn new(request: ForcedWalkRequest, completeness: CompleteMetricEvidence) -> Self {
Self {
request,
completeness,
}
}
pub fn request(&self) -> &ForcedWalkRequest {
&self.request
}
pub fn completeness(&self) -> &CompleteMetricEvidence {
&self.completeness
}
}
#[derive(Clone, Debug, PartialEq)]
pub enum ForcedWalkOutcome {
Bounded(ForcedWalkEvidence),
NeverEntered(Box<NeverEnteredEvidence>),
}
const TRACE_ROUNDING: f64 = 1e-9;
pub trait MetricRoutingService: Send + Sync + 'static {
fn route(&self, request: &MetricRouteRequest)
-> Result<MetricRouteOutcome, MetricRoutingError>;
fn nearest_target(
&self,
request: &NearestTargetRequest,
) -> Result<NearestTargetOutcome, MetricRoutingError> {
let _ = request;
Err(MetricRoutingError::Unavailable(
"this backend does not measure nearest targets".into(),
))
}
fn farthest_point(
&self,
request: &FarthestPointRequest,
) -> Result<FarthestPointOutcome, MetricRoutingError> {
let _ = request;
Err(MetricRoutingError::Unavailable(
"this backend does not measure farthest points".into(),
))
}
fn avoids_objects(&self) -> bool {
false
}
fn weighs_travel(&self) -> bool {
false
}
fn forced_walk(
&self,
request: &ForcedWalkRequest,
) -> Result<ForcedWalkOutcome, MetricRoutingError> {
let _ = request;
Err(MetricRoutingError::Unavailable(
"this backend does not measure walks forced through objects".into(),
))
}
fn climbs_connectors(&self) -> bool {
false
}
fn trace_path(&self, request: &PathTraceRequest) -> Result<PathTrace, MetricRoutingError> {
let _ = request;
Err(MetricRoutingError::Unavailable(
"this backend does not trace paths over objects".into(),
))
}
}
#[derive(Clone)]
pub struct MetricRoutingServiceHandle(Arc<dyn MetricRoutingService>);
impl MetricRoutingServiceHandle {
pub fn new(service: Arc<dyn MetricRoutingService>) -> Self {
Self(service)
}
pub fn route(
&self,
request: &MetricRouteRequest,
) -> Result<MetricRouteOutcome, MetricRoutingError> {
self.climbing(request.connectors())?;
let outcome = self.0.route(request)?;
if let MetricRouteOutcome::Reachable(route) = &outcome {
let (Some(first), Some(last)) = (route.waypoints.first(), route.waypoints.last())
else {
return Err(MetricRoutingError::EmptyRouteEvidence);
};
if first != request.origin() || last != request.destination() {
return Err(MetricRoutingError::ResponseEndpointMismatch);
}
} else if let MetricRouteOutcome::Blocked(blocked) = &outcome
&& blocked.request() != request
{
return Err(MetricRoutingError::ResponseEndpointMismatch);
}
Ok(outcome)
}
pub fn nearest_target(
&self,
request: &NearestTargetRequest,
) -> Result<NearestTargetOutcome, MetricRoutingError> {
if !request.avoided().is_empty() && !self.0.avoids_objects() {
return Err(MetricRoutingError::Unavailable(
"this backend does not walk around objects".into(),
));
}
self.weighing(request.costs())?;
self.climbing(request.connectors())?;
let outcome = self.0.nearest_target(request)?;
match &outcome {
NearestTargetOutcome::Reached(reached) => {
let target = request
.targets()
.get(reached.target())
.ok_or(MetricRoutingError::InconsistentResponse)?;
let (Some(first), Some(last)) =
(reached.waypoints().first(), reached.waypoints().last())
else {
return Err(MetricRoutingError::EmptyRouteEvidence);
};
if first != request.origin() || last != target {
return Err(MetricRoutingError::ResponseEndpointMismatch);
}
}
NearestTargetOutcome::Unreachable(unreachable) => {
if unreachable.request() != request {
return Err(MetricRoutingError::ResponseEndpointMismatch);
}
}
}
Ok(outcome)
}
pub fn farthest_point(
&self,
request: &FarthestPointRequest,
) -> Result<FarthestPointOutcome, MetricRoutingError> {
self.weighing(request.costs())?;
self.climbing(request.connectors())?;
let outcome = self.0.farthest_point(request)?;
match &outcome {
FarthestPointOutcome::Bounded(bounded) => {
if bounded.witness().subject() != request.region() {
return Err(MetricRoutingError::ResponseEndpointMismatch);
}
let width = bounded.distance().upper_metres() - bounded.distance().lower_metres();
if bounded.converged() && width > request.tolerance_metres() {
return Err(MetricRoutingError::InconsistentResponse);
}
}
FarthestPointOutcome::Unreachable(unreachable) => {
if unreachable.request() != request
|| unreachable.witness().subject() != request.region()
{
return Err(MetricRoutingError::ResponseEndpointMismatch);
}
}
}
Ok(outcome)
}
}
impl MetricRoutingServiceHandle {
fn weighing(&self, costs: &[TravelCost]) -> Result<(), MetricRoutingError> {
if !costs.is_empty() && !self.0.weighs_travel() {
return Err(MetricRoutingError::Unavailable(
"this backend does not weigh travel over objects".into(),
));
}
Ok(())
}
pub fn forced_walk(
&self,
request: &ForcedWalkRequest,
) -> Result<ForcedWalkOutcome, MetricRoutingError> {
if !request.avoided().is_empty() && !self.0.avoids_objects() {
return Err(MetricRoutingError::Unavailable(
"this backend does not walk around objects".into(),
));
}
self.climbing(request.connectors())?;
let outcome = self.0.forced_walk(request)?;
match &outcome {
ForcedWalkOutcome::Bounded(bounded) => {
if bounded.converged()
&& bounded.upper_metres() - bounded.lower_metres() > request.tolerance_metres()
{
return Err(MetricRoutingError::InconsistentResponse);
}
}
ForcedWalkOutcome::NeverEntered(never) => {
if never.request() != request {
return Err(MetricRoutingError::ResponseEndpointMismatch);
}
}
}
Ok(outcome)
}
fn climbing(&self, connectors: Option<&ConnectorRouting>) -> Result<(), MetricRoutingError> {
if connectors.is_some() && !self.0.climbs_connectors() {
return Err(MetricRoutingError::Unavailable(
"this backend does not route through vertical connectors".into(),
));
}
Ok(())
}
pub fn trace_path(&self, request: &PathTraceRequest) -> Result<PathTrace, MetricRoutingError> {
let trace = self.0.trace_path(request)?;
if trace.lengths().len() != request.objects().len() {
return Err(MetricRoutingError::InconsistentResponse);
}
let most = request.plan_length_metres() * (1.0 + TRACE_ROUNDING) + TRACE_ROUNDING;
if trace
.lengths()
.iter()
.flatten()
.any(|length| length.lower_metres() > most)
{
return Err(MetricRoutingError::InconsistentResponse);
}
Ok(trace)
}
}
fn valid_non_negative(value: f64) -> bool {
value.is_finite() && value >= 0.0
}