diff --git a/rust/bambam-omf/src/collection/filter/mod.rs b/rust/bambam-omf/src/collection/filter/mod.rs index d55c5ae..0ed7d66 100644 --- a/rust/bambam-omf/src/collection/filter/mod.rs +++ b/rust/bambam-omf/src/collection/filter/mod.rs @@ -10,4 +10,4 @@ mod travel_mode_filter; pub use bbox::Bbox; pub use row_filter::RowFilter; pub use row_filter_config::RowFilterConfig; -pub use travel_mode_filter::TravelModeFilter; +pub use travel_mode_filter::{MatchBehavior, TravelModeFilter}; diff --git a/rust/bambam-omf/src/collection/record/mod.rs b/rust/bambam-omf/src/collection/record/mod.rs index 6b896f1..25a671d 100644 --- a/rust/bambam-omf/src/collection/record/mod.rs +++ b/rust/bambam-omf/src/collection/record/mod.rs @@ -15,10 +15,11 @@ pub use record_type::OvertureRecordType; pub use transportation_collection::TransportationCollection; pub use transportation_connector::TransportationConnectorRecord; pub use transportation_segment::{ - SegmentAccessRestriction, SegmentAccessRestrictionWhen, SegmentAccessType, SegmentClass, - SegmentDestination, SegmentFullType, SegmentHeading, SegmentMode, SegmentRecognized, - SegmentSpeedLimit, SegmentSpeedUnit, SegmentSubclass, SegmentSubtype, SegmentUsing, - TransportationSegmentRecord, + SegmentAccessRestriction, SegmentAccessRestrictionWhen, SegmentAccessRestrictionWhenVehicle, + SegmentAccessType, SegmentClass, SegmentDestination, SegmentFullType, SegmentHeading, + SegmentLengthUnit, SegmentMode, SegmentRecognized, SegmentSpeedLimit, SegmentSpeedUnit, + SegmentSubclass, SegmentSubtype, SegmentUnit, SegmentUsing, SegmentVehicleComparator, + SegmentVehicleDimension, TransportationSegmentRecord, }; // Common structs and functions for many record types diff --git a/rust/bambam-omf/src/collection/record/transportation_segment.rs b/rust/bambam-omf/src/collection/record/transportation_segment.rs index 196e97f..06a40c6 100644 --- a/rust/bambam-omf/src/collection/record/transportation_segment.rs +++ b/rust/bambam-omf/src/collection/record/transportation_segment.rs @@ -421,6 +421,18 @@ pub enum SegmentVehicleComparator { LessThanEqual, } +impl SegmentVehicleComparator { + pub fn apply(&self, value: f64, restriction: f64) -> bool { + match self { + SegmentVehicleComparator::GreaterThan => value > restriction, + SegmentVehicleComparator::GreaterThanEqual => value >= restriction, + SegmentVehicleComparator::Equal => value == restriction, + SegmentVehicleComparator::LessThan => value < restriction, + SegmentVehicleComparator::LessThanEqual => value <= restriction, + } + } +} + /// units in vehicle restrictions which may be length or weight units. #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, Hash)] #[serde(untagged)] @@ -447,6 +459,24 @@ pub enum SegmentLengthUnit { Kilometer, } +impl SegmentLengthUnit { + pub fn to_uom(&self, value: f64) -> uom::si::f64::Length { + match self { + SegmentLengthUnit::Inches => uom::si::f64::Length::new::(value), + SegmentLengthUnit::Feet => uom::si::f64::Length::new::(value), + SegmentLengthUnit::Yard => uom::si::f64::Length::new::(value), + SegmentLengthUnit::Mile => uom::si::f64::Length::new::(value), + SegmentLengthUnit::Centimeter => { + uom::si::f64::Length::new::(value) + } + SegmentLengthUnit::Meter => uom::si::f64::Length::new::(value), + SegmentLengthUnit::Kilometer => { + uom::si::f64::Length::new::(value) + } + } + } +} + #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, Hash)] #[serde(untagged)] pub enum SegmentWeightUnit { @@ -454,6 +484,27 @@ pub enum SegmentWeightUnit { Metric(SegmentMetricWeightUnit), } +impl SegmentWeightUnit { + pub fn to_uom(&self, value: f64) -> uom::si::f64::Mass { + use SegmentImperialWeightUnit as I; + use SegmentMetricWeightUnit as M; + use SegmentWeightUnit as SWU; + + match self { + SWU::Imperial(I::Ounce) => uom::si::f64::Mass::new::(value), + SWU::Imperial(I::Pound) => uom::si::f64::Mass::new::(value), + // Couldn't find "Stone" so we use the transformation to Kg + SWU::Imperial(I::Stone) => { + uom::si::f64::Mass::new::(value * 6.350288) + } + SWU::Imperial(I::LongTon) => uom::si::f64::Mass::new::(value), + SWU::Metric(M::Kilogram) => uom::si::f64::Mass::new::(value), + SWU::Metric(M::Gram) => uom::si::f64::Mass::new::(value), + SWU::Metric(M::MetricTon) => uom::si::f64::Mass::new::(value), + } + } +} + #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, Hash)] #[serde(rename_all = "snake_case")] pub enum SegmentImperialWeightUnit { @@ -625,11 +676,50 @@ impl SegmentAccessRestrictionWhen { #[derive(Debug, Serialize, Deserialize, Clone)] pub struct SegmentAccessRestrictionWhenVehicle { - dimension: SegmentVehicleDimension, - comparison: SegmentVehicleComparator, - value: f64, + pub dimension: SegmentVehicleDimension, + pub comparison: SegmentVehicleComparator, + pub value: f64, #[serde(skip_serializing_if = "Option::is_none", default)] - unit: Option, + pub unit: Option, +} + +impl SegmentAccessRestrictionWhenVehicle { + /// returns true if the when provided would pass the restriction + /// based on the comparison logic + pub fn is_valid(&self, when: &SegmentAccessRestrictionWhenVehicle) -> bool { + use SegmentUnit as SU; + + if when.dimension != self.dimension { + return false; + } + + match (&self.unit, &when.unit) { + (Some(SU::Length(this_unit)), Some(SU::Length(other_unit))) => { + let this_value_f64 = this_unit.to_uom(self.value).get::(); + let other_value_f64 = other_unit + .to_uom(when.value) + .get::(); + self.comparison.apply(other_value_f64, this_value_f64) + } + (Some(SU::Weight(this_unit)), Some(SU::Weight(other_unit))) => { + let this_value_f64 = this_unit + .to_uom(self.value) + .get::(); + let other_value_f64 = other_unit + .to_uom(when.value) + .get::(); + self.comparison.apply(other_value_f64, this_value_f64) + } + + // Should be handled by the if statement checking the dimension but + // just to be sure + (Some(SU::Weight(_)), Some(SU::Length(_))) => false, + (Some(SU::Length(_)), Some(SU::Weight(_))) => false, + + // If we miss any unit, check the raw values + _ => self.comparison.apply(when.value, self.value), + } + } } /// Describes objects that can be reached by following a transportation diff --git a/rust/bambam-omf/src/graph/segment_ops.rs b/rust/bambam-omf/src/graph/segment_ops.rs index 74b2bbd..daa659a 100644 --- a/rust/bambam-omf/src/graph/segment_ops.rs +++ b/rust/bambam-omf/src/graph/segment_ops.rs @@ -219,6 +219,23 @@ fn when_is_compatible( } } + // Check vehicle dimension filters + if let Some(restrictions_vehicle) = &restrictions.vehicle { + let all_restrictions_apply_to_all_vehicles = restrictions_vehicle.iter().all(|r_vehicle| { + if let Some(when_vehicles) = &when.vehicle { + when_vehicles + .iter() + .all(|w_vehicle| r_vehicle.is_valid(w_vehicle)) + } else { + false + } + }); + + if !all_restrictions_apply_to_all_vehicles { + return false; + } + } + // If we got here, all specified fields in when are compatible true } @@ -226,8 +243,14 @@ fn when_is_compatible( #[cfg(test)] mod tests { use super::*; - use crate::collection::record::{ - OvertureMapsBbox, SegmentAccessType, SegmentMode, SegmentRecognized, SegmentUsing, + use crate::collection::{ + filter::{MatchBehavior, TravelModeFilter}, + record::{ + OvertureMapsBbox, SegmentAccessRestrictionWhenVehicle, SegmentAccessType, + SegmentLengthUnit, SegmentMode, SegmentRecognized, SegmentUnit, SegmentUsing, + SegmentVehicleComparator, SegmentVehicleDimension, + }, + SegmentClass, }; #[test] @@ -576,6 +599,98 @@ mod tests { assert_eq!(result2.len(), 0); } + #[test] + fn test_from_data_3() { + let access_restrictions = vec![ + create_restriction_mode(SegmentAccessType::Designated, vec![SegmentMode::Hgv]), + create_restriction_when_vehicle( + SegmentAccessType::Denied, + SegmentAccessRestrictionWhenVehicle { + dimension: SegmentVehicleDimension::Height, + comparison: SegmentVehicleComparator::GreaterThan, + value: 13.0, + unit: Some(SegmentUnit::Length(SegmentLengthUnit::Feet)), + }, + ), + ]; + + let segment = create_test_segment(Some(access_restrictions)); + + // Should pass simple drive filter + let filter1 = create_simple_drive_filter(); + assert!(filter1.matches_filter(&segment)); + + // Should be denied when vehicle height is greater than 13 ft + let when1 = SegmentAccessRestrictionWhen { + during: None, + heading: Some(SegmentHeading::Forward), + using: None, + recognized: None, + mode: None, + vehicle: Some(vec![SegmentAccessRestrictionWhenVehicle { + dimension: SegmentVehicleDimension::Height, + comparison: SegmentVehicleComparator::GreaterThan, + value: 100.0, + unit: Some(SegmentUnit::Length(SegmentLengthUnit::Feet)), + }]), + }; + let result1 = get_headings(&segment, Some(&when1)).unwrap(); + assert_eq!(result1.len(), 0); + + // But should be allowed in any other case + let when2 = SegmentAccessRestrictionWhen { + during: None, + heading: Some(SegmentHeading::Forward), + using: None, + recognized: None, + mode: None, + vehicle: Some(vec![SegmentAccessRestrictionWhenVehicle { + dimension: SegmentVehicleDimension::Height, + comparison: SegmentVehicleComparator::GreaterThan, + value: 10., + unit: Some(SegmentUnit::Length(SegmentLengthUnit::Feet)), + }]), + }; + let result2 = get_headings(&segment, Some(&when2)).unwrap(); + assert_eq!(result2, vec![SegmentHeading::Forward]); + + // also when no vehicle is specified + let when3 = SegmentAccessRestrictionWhen { + during: None, + heading: Some(SegmentHeading::Forward), + using: None, + recognized: None, + mode: None, + vehicle: None, + }; + let result3 = get_headings(&segment, Some(&when3)).unwrap(); + assert_eq!(result3, vec![SegmentHeading::Forward]); + } + + // Helper to represent a typical drive mode filter + fn create_simple_drive_filter() -> TravelModeFilter { + let classes = vec![ + SegmentClass::Motorway, + SegmentClass::Trunk, + SegmentClass::Primary, + SegmentClass::Secondary, + SegmentClass::Tertiary, + SegmentClass::Residential, + SegmentClass::LivingStreet, + SegmentClass::Unclassified, + SegmentClass::Service, + SegmentClass::Unknown, + ] + .into_iter() + .collect(); + + TravelModeFilter::MatchesClasses { + classes, + behavior: MatchBehavior::Include, + allow_unset: true, + } + } + /// Helper to create a minimal segment for testing fn create_test_segment( access_restrictions: Option>, @@ -648,6 +763,25 @@ mod tests { } } + /// Helper to create a simple access restriction with vehicle detail + fn create_restriction_when_vehicle( + access_type: SegmentAccessType, + vehicle: SegmentAccessRestrictionWhenVehicle, + ) -> SegmentAccessRestriction { + SegmentAccessRestriction { + access_type, + when: Some(SegmentAccessRestrictionWhen { + during: None, + heading: None, + using: None, + recognized: None, + mode: None, + vehicle: Some(vec![vehicle]), + }), + vehicle: None, + } + } + /// Helper to create "Denied all + Allowed specific" pattern for a heading fn create_denied_all_allowed_specific( heading: SegmentHeading, diff --git a/rust/bambam-omf/src/graph/segment_split.rs b/rust/bambam-omf/src/graph/segment_split.rs index 486520a..7895085 100644 --- a/rust/bambam-omf/src/graph/segment_split.rs +++ b/rust/bambam-omf/src/graph/segment_split.rs @@ -222,7 +222,7 @@ impl SegmentSplit { Some(h) if h == heading => true, _ => false, }, - None => false, + None => true, }) .collect_vec();