opendrive/signal/
mod.rs

1use crate::core::additional_data::AdditionalData;
2use crate::object::lane_validity::LaneValidity;
3use crate::object::orientation::Orientation;
4use crate::road::country_code::CountryCode;
5use crate::road::unit::Unit;
6use crate::signal::dependency::Dependency;
7use crate::signal::position::inertial::PositionInertial;
8use crate::signal::position::road::PositionRoad;
9use crate::signal::position::Position;
10use crate::signal::reference::Reference;
11use std::borrow::Cow;
12use uom::si::angle::radian;
13use uom::si::f64::{Angle, Length};
14use uom::si::length::meter;
15
16pub mod control;
17pub mod controller;
18pub mod dependency;
19pub mod position;
20pub mod reference;
21pub mod signal_reference;
22pub mod signals;
23
24/// Used to provide information about signals along a road. Consists of a main element and an
25/// optional lane validity element. The element for a signal is `<signal>`.
26#[derive(Debug, Clone, PartialEq)]
27pub struct Signal {
28    pub validity: Vec<LaneValidity>,
29    pub dependency: Vec<Dependency>,
30    pub reference: Vec<Reference>,
31    pub choice: Option<Position>,
32    /// Country code of the road, see ISO 3166-1, alpha-2 codes.
33    pub country: Option<CountryCode>,
34    /// Defines the year of the applied traffic rules
35    pub country_revision: Option<String>,
36    /// Indicates whether the signal is dynamic or static. Example: traffic light is dynamic
37    pub dynamic: bool,
38    /// Height of the signal, measured from bottom edge of the signal
39    pub height: Option<Length>,
40    /// Heading offset of the signal (relative to @orientation, if orientation is equal to “+” or “-“)
41    /// Heading offset of the signal (relative to reference line, if orientation is equal to “none” )
42    pub h_offset: Option<Length>,
43    /// Unique ID of the signal within the OpenDRIVE file
44    pub id: String,
45    /// Name of the signal. May be chosen freely.
46    pub name: Option<String>,
47    /// - "+" = valid in positive s- direction
48    /// - "-" = valid in negative s- direction
49    /// - "none" = valid in both directions
50    pub orientation: Orientation,
51    /// Pitch angle of the signal, relative to the inertial system (xy-plane)
52    pub pitch: Option<Angle>,
53    /// Roll angle of the signal after applying pitch, relative to the inertial system
54    /// (x’’y’’-plane)
55    pub roll: Option<Angle>,
56    /// s-coordinate
57    pub s: Length,
58    /// Subtype identifier according to country code or "-1" / "none"
59    pub subtype: String,
60    /// t-coordinate
61    pub t: Length,
62    /// Additional text associated with the signal, for example, text on city limit
63    /// "City\nBadAibling"
64    pub text: Option<String>,
65    /// Type identifier according to country code or "-1" / "none". See extra document.
66    pub r#type: String,
67    /// Unit of @value
68    pub unit: Option<Unit>,
69    /// Value of the signal, if value is given, unit is mandatory
70    pub value: Option<f64>,
71    /// Width of the signal
72    pub width: Option<Length>,
73    /// z offset from the road to bottom edge of the signal. This represents the vertical clearance
74    /// of the object. Relative to the reference line.
75    pub z_offset: Length,
76    pub additional_data: AdditionalData,
77}
78
79impl Signal {
80    pub fn visit_attributes(
81        &self,
82        visitor: impl for<'b> FnOnce(
83            Cow<'b, [xml::attribute::Attribute<'b>]>,
84        ) -> xml::writer::Result<()>,
85    ) -> xml::writer::Result<()> {
86        visit_attributes_flatten!(
87            visitor,
88            "country" => self.country.as_ref().map(CountryCode::as_str),
89            "countryRevision" => self.country_revision.as_deref(),
90            "dynamic" => Some(if self.dynamic { "yes" } else { "no" }),
91            "height" => self.height.map(|v| v.value.to_scientific_string()).as_deref(),
92            "hOffset" => self.h_offset.map(|v| v.value.to_scientific_string()).as_deref(),
93            "id" => Some(self.id.as_str()),
94            "name" => self.name.as_deref(),
95            "orientation" => Some(self.orientation.as_str()),
96            "pitch" => self.pitch.map(|v| v.value.to_scientific_string()).as_deref(),
97            "roll" => self.roll.map(|v| v.value.to_scientific_string()).as_deref(),
98            "s" => Some(self.s.value.to_scientific_string()).as_deref(),
99            "subtype" => Some(self.subtype.as_str()),
100            "t" => Some(self.t.value.to_scientific_string()).as_deref(),
101            "text" => self.text.as_deref(),
102            "type" => Some(self.r#type.as_str()),
103            "unit" => self.unit.as_ref().map(Unit::as_str),
104            "value" => self.value.map(|v| v.to_scientific_string()).as_deref(),
105            "width" => self.width.map(|v| v.value.to_scientific_string()).as_deref(),
106            "zOffset" => Some(self.z_offset.value.to_scientific_string()).as_deref(),
107        )
108    }
109
110    pub fn visit_children(
111        &self,
112        mut visitor: impl FnMut(xml::writer::XmlEvent) -> xml::writer::Result<()>,
113    ) -> xml::writer::Result<()> {
114        for validity in &self.validity {
115            visit_children!(visitor, "validity" => validity);
116        }
117
118        for dependency in &self.dependency {
119            visit_children!(visitor, "dependency" => dependency);
120        }
121
122        for reference in &self.reference {
123            visit_children!(visitor, "reference" => reference);
124        }
125
126        match &self.choice {
127            Some(Position::Inertial(v)) => visit_children!(visitor, "positionInertial" => v),
128            Some(Position::Road(v)) => visit_children!(visitor, "positionRoad" => v),
129            None => {}
130        }
131
132        self.additional_data.append_children(visitor)
133    }
134}
135
136impl<'a, I> TryFrom<crate::parser::ReadContext<'a, I>> for Signal
137where
138    I: Iterator<Item = xml::reader::Result<xml::reader::XmlEvent>>,
139{
140    type Error = Box<crate::parser::Error>;
141
142    fn try_from(mut read: crate::parser::ReadContext<'a, I>) -> Result<Self, Self::Error> {
143        let mut validity = Vec::new();
144        let mut dependency = Vec::new();
145        let mut reference = Vec::new();
146        let mut choice = None;
147        let mut additional_data = AdditionalData::default();
148
149        match_child_eq_ignore_ascii_case!(
150            read,
151            "validity" => LaneValidity => |v| validity.push(v),
152            "dependency" => Dependency => |v| dependency.push(v),
153            "reference" => Reference => |v| reference.push(v),
154            "positionInertial" => PositionInertial => |v| choice = Some(Position::Inertial(v)),
155            "positionRoad" => PositionRoad => |v| choice = Some(Position::Road(v)),
156            _ => |_name, context| additional_data.fill(context),
157        );
158
159        Ok(Self {
160            validity,
161            dependency,
162            reference,
163            choice,
164            country: read.attribute_opt("country")?,
165            country_revision: read.attribute_opt("countryRevision")?,
166            dynamic: read
167                .attribute::<String>("dynamic")
168                .map(|v| v.eq_ignore_ascii_case("yes"))?,
169            height: read.attribute_opt("height")?.map(Length::new::<meter>),
170            h_offset: read.attribute_opt("hOffset")?.map(Length::new::<meter>),
171            id: read.attribute("id")?,
172            name: read.attribute_opt("name")?,
173            orientation: read.attribute("orientation")?,
174            pitch: read.attribute_opt("pitch")?.map(Angle::new::<radian>),
175            roll: read.attribute_opt("roll")?.map(Angle::new::<radian>),
176            s: read.attribute("s").map(Length::new::<meter>)?,
177            subtype: read.attribute("subtype")?,
178            t: read.attribute("t").map(Length::new::<meter>)?,
179            text: read.attribute_opt("text")?,
180            r#type: read.attribute("type")?,
181            unit: read.attribute_opt("unit")?,
182            value: read.attribute_opt("value")?,
183            width: read.attribute_opt("width")?.map(Length::new::<meter>),
184            z_offset: read.attribute("zOffset").map(Length::new::<meter>)?,
185            additional_data,
186        })
187    }
188}
189
190#[cfg(feature = "fuzzing")]
191impl arbitrary::Arbitrary<'_> for Signal {
192    fn arbitrary(u: &mut arbitrary::Unstructured) -> arbitrary::Result<Self> {
193        use crate::fuzzing::NotNan;
194        Ok(Self {
195            validity: u.arbitrary()?,
196            dependency: u.arbitrary()?,
197            reference: u.arbitrary()?,
198            choice: u.arbitrary()?,
199            country: u.arbitrary()?,
200            country_revision: u.arbitrary()?,
201            dynamic: u.arbitrary()?,
202            height: u
203                .arbitrary::<Option<()>>()?
204                .map(|_| u.not_nan_f64().map(Length::new::<meter>))
205                .transpose()?,
206            h_offset: u
207                .arbitrary::<Option<()>>()?
208                .map(|_| u.not_nan_f64().map(Length::new::<meter>))
209                .transpose()?,
210            id: u.arbitrary()?,
211            name: u.arbitrary()?,
212            orientation: u.arbitrary()?,
213            pitch: u
214                .arbitrary::<Option<()>>()?
215                .map(|_| u.not_nan_f64().map(Angle::new::<radian>))
216                .transpose()?,
217            roll: u
218                .arbitrary::<Option<()>>()?
219                .map(|_| u.not_nan_f64().map(Angle::new::<radian>))
220                .transpose()?,
221            s: Length::new::<meter>(u.not_nan_f64()?),
222            subtype: u.arbitrary()?,
223            t: Length::new::<meter>(u.not_nan_f64()?),
224            text: u.arbitrary()?,
225            r#type: u.arbitrary()?,
226            unit: u.arbitrary()?,
227            value: u
228                .arbitrary::<Option<()>>()?
229                .map(|_| u.not_nan_f64())
230                .transpose()?,
231            width: u
232                .arbitrary::<Option<()>>()?
233                .map(|_| u.not_nan_f64().map(Length::new::<meter>))
234                .transpose()?,
235            z_offset: Length::new::<meter>(u.not_nan_f64()?),
236            additional_data: u.arbitrary()?,
237        })
238    }
239}