screen: Show lane types
This commit is contained in:
parent
2b9e50c16c
commit
7bbdae9243
8 changed files with 172 additions and 40 deletions
11
Cargo.lock
generated
11
Cargo.lock
generated
|
|
@ -1216,6 +1216,7 @@ dependencies = [
|
|||
"prost",
|
||||
"prost-build",
|
||||
"static_cell",
|
||||
"tinytga",
|
||||
"trouble-host",
|
||||
"ublox",
|
||||
]
|
||||
|
|
@ -2529,6 +2530,16 @@ dependencies = [
|
|||
"cfg-if",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tinytga"
|
||||
version = "0.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "477839bd612acb4d0551915eaf6eef8cc1b3a9dd58e18e9c2b746c78614a25d5"
|
||||
dependencies = [
|
||||
"embedded-graphics",
|
||||
"nom",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "toml_datetime"
|
||||
version = "1.1.1+spec-1.1.0"
|
||||
|
|
|
|||
|
|
@ -53,7 +53,7 @@ spat = ["c-its-parser/spatem", "c-its-parser/mapem"]
|
|||
# enable some debug printing (without enabling debug logging of the ESP RTOS)
|
||||
spat_debug = []
|
||||
# enable ST7789 based display
|
||||
screen = ["dep:mipidsi", "dep:embedded-graphics", "dep:embedded-hal-bus", "dep:profont"]
|
||||
screen = ["dep:mipidsi", "dep:embedded-graphics", "dep:embedded-hal-bus", "dep:profont", "dep:tinytga"]
|
||||
# enable I/O via UART
|
||||
uart = ["_uart", "_gnss"]
|
||||
# enable I/O via BLE
|
||||
|
|
@ -123,6 +123,7 @@ mipidsi = { version = "0.10.0", optional = true }
|
|||
num-traits = { version = "0.2.19", default-features = false, features = ["libm"] }
|
||||
profont = { version = "0.7.0", optional = true }
|
||||
prost = { version = "0.14", default-features = false, features = ["derive"] }
|
||||
tinytga = { version = "0.5.0", optional = true }
|
||||
ublox = { version = "0.10", optional = true, default-features = false, features = ["alloc", "ubx_proto27"] }
|
||||
|
||||
[dev-dependencies]
|
||||
|
|
|
|||
13
Readme.md
13
Readme.md
|
|
@ -119,3 +119,16 @@ The unit tests in this project are meant to be run on the host:
|
|||
```
|
||||
cargo test --target host-tuple --bin test --no-default-features -F std,spat
|
||||
```
|
||||
|
||||
## Tips and Tricks
|
||||
|
||||
### Create TGA Assets
|
||||
|
||||
Images are loaded as TGA assets, but shall not have an alpha channel.
|
||||
You can convert them using [ImageMagick](https://imagemagick.org) like this:
|
||||
```shell
|
||||
# resize to 25px height, adding a black background to the white symbol and vice versa
|
||||
magick Sinnbild_Radfahrer_StVO_1992-200px-white.png -resize x25 -background black -alpha background -flatten -alpha off -verbose Sinnbild_Radfahrer_StVO_1992-25px-white.tga
|
||||
magick Sinnbild_Radfahrer_StVO_1992-200px-black.png -resize x25 -background white -alpha background -flatten -alpha off -verbose Sinnbild_Radfahrer_StVO_1992-25px-black.tga
|
||||
```
|
||||
|
||||
|
|
|
|||
BIN
graphics/Sinnbild_Radfahrer_StVO_1992-25px-black.tga
Normal file
BIN
graphics/Sinnbild_Radfahrer_StVO_1992-25px-black.tga
Normal file
Binary file not shown.
BIN
graphics/Sinnbild_Radfahrer_StVO_1992-25px-white.tga
Normal file
BIN
graphics/Sinnbild_Radfahrer_StVO_1992-25px-white.tga
Normal file
Binary file not shown.
|
|
@ -106,6 +106,7 @@ pub struct GlosaSignalInfo {
|
|||
pub struct SignalGroupData {
|
||||
pub phase: SignalPhase,
|
||||
pub maneuver: map::Maneuvers,
|
||||
pub usages: Vec<map::UsageType>,
|
||||
pub timing: Option<TimingData>,
|
||||
}
|
||||
|
||||
|
|
@ -383,35 +384,29 @@ impl State {
|
|||
}),
|
||||
)
|
||||
} else {
|
||||
// get signal groups which are in our approach
|
||||
// get signal groups which are in our approach and have a maneuver
|
||||
let mut approach_sig_grps = intersection
|
||||
.signal_groups()
|
||||
.iter()
|
||||
.filter(|i| {
|
||||
info.signal_groups
|
||||
.iter()
|
||||
.find(|sg_id| **sg_id == i.id())
|
||||
.is_some()
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
// sort signal groups by maneuver
|
||||
approach_sig_grps.sort_by_key(|v| v.maneuvers());
|
||||
|
||||
// display signal phases
|
||||
let sig_grps = approach_sig_grps
|
||||
.iter()
|
||||
.filter_map(|sig| {
|
||||
if let Some(maneuver) = sig.maneuvers() {
|
||||
let phase = sig
|
||||
.filter_map(|i| {
|
||||
if let Some(maneuver) = i.maneuvers()
|
||||
&& info
|
||||
.signal_groups
|
||||
.iter()
|
||||
.find(|sg_id| **sg_id == i.id())
|
||||
.is_some()
|
||||
{
|
||||
let phase = i
|
||||
.phase()
|
||||
.map(core::convert::Into::into)
|
||||
.unwrap_or_default();
|
||||
let timing = TimingData::new(sig, self.time.and_utc());
|
||||
let timing = TimingData::new(i, self.time.and_utc());
|
||||
let usages = i.source_lane_usages().to_vec();
|
||||
|
||||
Some(SignalGroupData {
|
||||
phase,
|
||||
maneuver,
|
||||
usages,
|
||||
timing,
|
||||
})
|
||||
} else {
|
||||
|
|
@ -420,11 +415,39 @@ impl State {
|
|||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
// sort signal groups by maneuver
|
||||
approach_sig_grps.sort_by_key(|v| v.maneuver);
|
||||
|
||||
// // display signal phases
|
||||
// let sig_grps = approach_sig_grps
|
||||
// .iter()
|
||||
// .filter_map(|sig| {
|
||||
// if let Some(maneuver) = sig.sig_group.maneuvers() {
|
||||
// let phase = sig
|
||||
// .sig_group
|
||||
// .phase()
|
||||
// .map(core::convert::Into::into)
|
||||
// .unwrap_or_default();
|
||||
// let timing =
|
||||
// TimingData::new(&sig.sig_group, self.time.and_utc());
|
||||
|
||||
// Some(SignalGroupData {
|
||||
// phase,
|
||||
// maneuver,
|
||||
// timing,
|
||||
// usages: sig.lane_types.clone(),
|
||||
// })
|
||||
// } else {
|
||||
// None
|
||||
// }
|
||||
// })
|
||||
// .collect::<Vec<_>>();
|
||||
|
||||
(
|
||||
None,
|
||||
GlosaOutput::Locked(GlosaSignalInfo {
|
||||
intersection_id: intersection.id(),
|
||||
signal_groups: sig_grps,
|
||||
signal_groups: approach_sig_grps,
|
||||
}),
|
||||
) // keep state, return signal groups
|
||||
}
|
||||
|
|
|
|||
|
|
@ -191,27 +191,35 @@ impl Intersection {
|
|||
fn collect_signal_groups(lanes: &[Lane]) -> Vec<SignalGroup> {
|
||||
let mut connections = lanes
|
||||
.iter()
|
||||
.flat_map(|i| i.connections.clone())
|
||||
.filter(|i| i.signal_group.is_some())
|
||||
.flat_map(ConnectionExt::from_lane)
|
||||
.filter(|i| i.conn.signal_group.is_some())
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
connections.sort_by_key(|i| i.signal_group.unwrap_or_default());
|
||||
connections.sort_by_key(|i| i.conn.signal_group.unwrap_or_default());
|
||||
|
||||
let chunks = connections.chunk_by(|a, b| a.signal_group == b.signal_group);
|
||||
let chunks = connections.chunk_by(|a, b| a.conn.signal_group == b.conn.signal_group);
|
||||
chunks
|
||||
.map(|chunk| {
|
||||
let (sig_grp_id, maneuvers, mut target_lanes) = chunk.iter().fold(
|
||||
(None, Maneuvers::default(), vec![]),
|
||||
|(sig_grp_id, maneuvers, mut lanes), i| {
|
||||
let new_maneuvers = i
|
||||
.maneuvers
|
||||
.map(|v| core::ops::Add::add(maneuvers, v))
|
||||
.unwrap_or_default();
|
||||
lanes.push(i.target_lane_id);
|
||||
let (sig_grp_id, maneuvers, mut target_lanes, mut source_lane_usages) =
|
||||
chunk.iter().fold(
|
||||
(None, Maneuvers::default(), vec![], vec![]),
|
||||
|(sig_grp_id, maneuvers, mut lanes, mut usages), i| {
|
||||
let new_maneuvers = i
|
||||
.conn
|
||||
.maneuvers
|
||||
.map(|v| core::ops::Add::add(maneuvers, v))
|
||||
.unwrap_or_default();
|
||||
lanes.push(i.conn.target_lane_id);
|
||||
|
||||
(i.signal_group, new_maneuvers, lanes)
|
||||
},
|
||||
);
|
||||
let lane_usages = i.source_lane_usages.clone();
|
||||
usages.extend_from_slice(&lane_usages);
|
||||
|
||||
usages.sort_unstable();
|
||||
usages.dedup(); // TODO: only do this after all lanes were added?
|
||||
|
||||
(i.conn.signal_group, new_maneuvers, lanes, usages)
|
||||
},
|
||||
);
|
||||
|
||||
// unwrap is fine since we removed all connections without signal group beforehand
|
||||
let id = sig_grp_id.unwrap();
|
||||
|
|
@ -226,6 +234,7 @@ impl Intersection {
|
|||
SignalGroup {
|
||||
id,
|
||||
maneuvers,
|
||||
source_lane_usages,
|
||||
target_lanes,
|
||||
phase: None,
|
||||
min_end_time: None,
|
||||
|
|
@ -402,6 +411,28 @@ impl From<&etsi_its_dsrc::Connection> for Connection {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
#[allow(unused)]
|
||||
struct ConnectionExt {
|
||||
source_lane_id: u8,
|
||||
source_lane_usages: Vec<UsageType>,
|
||||
conn: Connection,
|
||||
}
|
||||
|
||||
impl ConnectionExt {
|
||||
fn from_lane(value: &Lane) -> Vec<Self> {
|
||||
value
|
||||
.connections
|
||||
.iter()
|
||||
.map(|conn| Self {
|
||||
source_lane_id: value.id,
|
||||
source_lane_usages: value.usages.clone(),
|
||||
conn: *conn,
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
}
|
||||
|
||||
/// Different view on a lane connection
|
||||
///
|
||||
/// Technically this data is redundant with the `Connection` data of a `Lane`,
|
||||
|
|
@ -411,7 +442,8 @@ impl From<&etsi_its_dsrc::Connection> for Connection {
|
|||
pub struct SignalGroup {
|
||||
id: u8,
|
||||
maneuvers: Option<Maneuvers>,
|
||||
target_lanes: Vec<u8>, // mainly for debugging
|
||||
source_lane_usages: Vec<UsageType>, // for easier signal group evaluation
|
||||
target_lanes: Vec<u8>, // mainly for debugging
|
||||
|
||||
// SPAT data
|
||||
/// current signal phase
|
||||
|
|
@ -441,6 +473,9 @@ impl SignalGroup {
|
|||
pub fn maneuvers(&self) -> Option<Maneuvers> {
|
||||
self.maneuvers
|
||||
}
|
||||
pub fn source_lane_usages(&self) -> &[UsageType] {
|
||||
&self.source_lane_usages
|
||||
}
|
||||
pub fn phase(&self) -> Option<etsi_its_dsrc::MovementPhaseState> {
|
||||
self.phase
|
||||
}
|
||||
|
|
|
|||
|
|
@ -23,6 +23,10 @@ where
|
|||
prev_state: ScreenState,
|
||||
hb_state: bool,
|
||||
gnss_hb_state: bool,
|
||||
|
||||
// assets
|
||||
#[cfg(feature = "spat")]
|
||||
bike_image: tinytga::Tga<'static, pixelcolor::Rgb565>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, PartialEq, Eq)]
|
||||
|
|
@ -94,12 +98,19 @@ where
|
|||
const BIG_HEIGHT_THLD: u16 = 172;
|
||||
|
||||
pub fn new(target: D, display_size: DisplaySize) -> Self {
|
||||
#[cfg(feature = "spat")]
|
||||
let data = include_bytes!("../graphics/Sinnbild_Radfahrer_StVO_1992-25px-white.tga");
|
||||
#[cfg(feature = "spat")]
|
||||
let bike_image = tinytga::Tga::from_slice(data).expect("Failed to parse TGA image");
|
||||
|
||||
Self {
|
||||
target,
|
||||
size: display_size,
|
||||
prev_state: ScreenState::Uninitialized,
|
||||
hb_state: false,
|
||||
gnss_hb_state: false,
|
||||
#[cfg(feature = "spat")]
|
||||
bike_image,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -307,7 +318,7 @@ where
|
|||
// clear everything if number of signals has changed
|
||||
self.clear();
|
||||
} else {
|
||||
// only clear maneuver and timing area otherwise
|
||||
// only clear maneuver and timing area otherwise (lane type will clear itself)
|
||||
let sig_total_height = 67 + if is_big_height { 20 } else { 0 };
|
||||
primitives::Rectangle::new(
|
||||
geometry::Point::new(0, 105 + self.size.offset_top),
|
||||
|
|
@ -355,7 +366,38 @@ where
|
|||
.into_styled(style)
|
||||
.draw(&mut self.target)?;
|
||||
|
||||
// TODO: draw lane type?
|
||||
// mark bike lanes which are not a vehicle lane, too
|
||||
let lane_symbol_y_pos = 75;
|
||||
if sig.usages.contains(&applogic::v2x::map::UsageType::Vehicle) {
|
||||
// clear area
|
||||
primitives::Rectangle::new(
|
||||
geometry::Point::new(
|
||||
centerline - (45 / 2),
|
||||
lane_symbol_y_pos + self.size.offset_top,
|
||||
),
|
||||
geometry::Size::new(45, 25),
|
||||
)
|
||||
.into_styled(Self::BK_STYLE)
|
||||
.draw(&mut self.target)?;
|
||||
} else if sig.usages.contains(&applogic::v2x::map::UsageType::Bike) {
|
||||
// bike symbol (45x25px)
|
||||
embedded_graphics::image::Image::new(
|
||||
&self.bike_image,
|
||||
geometry::Point::new(
|
||||
centerline - (45 / 2) + 1,
|
||||
lane_symbol_y_pos + self.size.offset_top,
|
||||
),
|
||||
)
|
||||
.draw(&mut self.target)?;
|
||||
} else {
|
||||
text::Text::with_alignment(
|
||||
"?",
|
||||
geometry::Point::new(centerline, lane_symbol_y_pos + 24 + self.size.offset_top),
|
||||
Self::DEFAULT_TEXT_STYLE,
|
||||
text::Alignment::Center,
|
||||
)
|
||||
.draw(&mut self.target)?;
|
||||
};
|
||||
|
||||
// draw maneuver
|
||||
text::Text::with_alignment(
|
||||
|
|
@ -434,6 +476,13 @@ where
|
|||
|
||||
// draw welcome text
|
||||
let centerline = i32::from(self.size.width) / 2;
|
||||
|
||||
embedded_graphics::image::Image::new(
|
||||
&self.bike_image,
|
||||
geometry::Point::new(centerline - (45 / 2) + 1, 75 + self.size.offset_top),
|
||||
)
|
||||
.draw(&mut self.target)?;
|
||||
|
||||
text::Text::with_alignment(
|
||||
"C-ITS Signal Phase\nWaiting for GNSS...",
|
||||
geometry::Point::new(centerline, 130 + self.size.offset_top),
|
||||
|
|
|
|||
Loading…
Reference in a new issue