use crate::{
configs::parser::{self, EdgeCategory, NodeCategory},
defaults::{self, capacity::DimVec},
helpers,
network::{EdgeBuilder, EdgeIdx, MetricIdx, NodeBuilder, ProtoEdge, ProtoNode, ProtoShortcut},
units::{distance::Meters, geo, speed::KilometersPerHour, time::Seconds},
};
use log::info;
use std::{
io::{BufRead, BufReader},
ops::Range,
};
pub struct Parser {
node_lines: Range<usize>,
edge_lines: Range<usize>,
}
impl Parser {
pub fn new() -> Parser {
Parser {
node_lines: 1..0,
edge_lines: 1..0,
}
}
fn is_line_functional(line: &String) -> bool {
line.len() > 0 && line.chars().next() != Some('#')
}
}
impl super::Parsing for Parser {
fn preprocess(&mut self, cfg: &parser::Config) -> Result<(), String> {
info!("START Start preprocessing fmi-parser.");
super::check_parser_config(cfg)?;
let mut line_number = 0;
let mut is_taking_counts = false;
let mut counts = vec![];
let file = helpers::open_file(&cfg.map_file)?;
for line in BufReader::new(file)
.lines()
.map(Result::unwrap)
.filter(Self::is_line_functional)
{
let params: Vec<&str> = line.split_whitespace().collect();
if params.len() == 1 {
is_taking_counts = true;
if let Ok(count) = params[0].parse::<usize>() {
counts.push(count);
}
} else if is_taking_counts {
break;
}
line_number += 1;
}
if counts.len() < 2 {
return Err(format!(
"The provided fmi-map-file doesn't have enough (edge-, node-) counts."
));
}
let edge_count = counts.pop().unwrap();
let node_count = counts.pop().unwrap();
let start = line_number;
let end = start + node_count;
self.node_lines = start..end;
let start = end;
let end = start + edge_count;
self.edge_lines = start..end;
info!("FINISHED");
Ok(())
}
fn parse_ways(&self, builder: &mut EdgeBuilder) -> Result<(), String> {
info!("START Create edges from input-file.");
let mut line_number = 0;
let file = helpers::open_file(&builder.cfg().map_file)?;
for line in BufReader::new(file)
.lines()
.map(Result::unwrap)
.filter(Self::is_line_functional)
{
if !self.edge_lines.contains(&line_number) {
line_number += 1;
continue;
}
line_number += 1;
let proto_edge = ProtoShortcut::from_str(&line, &builder.cfg().edges)?;
builder.insert(proto_edge);
}
info!("FINISHED");
Ok(())
}
fn parse_nodes(&self, builder: &mut NodeBuilder) -> Result<(), String> {
info!("START Create nodes from input-file.");
let mut line_number = 0;
let file = helpers::open_file(&builder.cfg().map_file)?;
for line in BufReader::new(file)
.lines()
.map(Result::unwrap)
.filter(Self::is_line_functional)
{
if !self.node_lines.contains(&line_number) {
line_number += 1;
continue;
}
line_number += 1;
let proto_node = ProtoNode::from_str(&line, &builder.cfg().nodes)?;
builder.insert(proto_node);
}
info!("FINISHED");
Ok(())
}
}
impl ProtoShortcut {
pub fn from_str(line: &str, cfg: &parser::edges::Config) -> Result<ProtoShortcut, String> {
let mut metric_values = DimVec::<_>::with_capacity(cfg.dim());
let mut src_id = None;
let mut dst_id = None;
let mut sc_edge_0 = None;
let mut sc_edge_1 = None;
let params: Vec<&str> = line.split_whitespace().collect();
let mut param_idx = 0;
for category in cfg.edge_categories().iter() {
let param = *params.get(param_idx).ok_or(&format!(
"The fmi-map-file is expected to have more edge-params (> {}) \
than actually has ({}).",
param_idx,
params.len()
))?;
match category {
EdgeCategory::SrcId => {
if src_id.is_none() {
src_id = Some(param.parse::<i64>().ok().ok_or(format!(
"Parsing {} (for edge-src) '{:?}' from fmi-file, which is not i64.",
category, param
))?);
param_idx += 1;
} else {
return Err(format!(
"Src-id is already set, but another src-id {} should be parsed.",
param
));
}
}
EdgeCategory::DstId => {
if dst_id.is_none() {
dst_id = Some(param.parse::<i64>().ok().ok_or(format!(
"Parsing {} (for edge-src) '{:?}' from fmi-file, which is not i64.",
category, param
))?);
param_idx += 1;
} else {
return Err(format!(
"Dst-id is already set, but another dst-id {} should be parsed.",
param
));
}
}
EdgeCategory::Meters => {
let metric_idx = MetricIdx(metric_values.len());
if cfg.is_metric_provided(metric_idx) {
if let Ok(raw_m) = param.parse::<f64>() {
let distance = defaults::distance::TYPE::from(Meters(raw_m));
metric_values.push(Some(*distance));
} else {
return Err(format!(
"Parsing {} '{}' of edge-param #{} didn't work.",
category, param, param_idx
));
};
param_idx += 1;
} else {
metric_values.push(None);
}
}
EdgeCategory::Seconds => {
let metric_idx = MetricIdx(metric_values.len());
if cfg.is_metric_provided(metric_idx) {
if let Ok(raw_s) = param.parse::<f64>() {
let duration = defaults::time::TYPE::from(Seconds(raw_s));
metric_values.push(Some(*duration));
} else {
return Err(format!(
"Parsing {} '{}' of edge-param #{} didn't work.",
category, param, param_idx
));
};
param_idx += 1;
} else {
metric_values.push(None);
}
}
EdgeCategory::KilometersPerHour => {
let metric_idx = MetricIdx(metric_values.len());
if cfg.is_metric_provided(metric_idx) {
if let Ok(raw_kmph) = param.parse::<f64>() {
let maxspeed = defaults::speed::TYPE::from(KilometersPerHour(raw_kmph));
metric_values.push(Some(*maxspeed));
} else {
return Err(format!(
"Parsing {} '{}' of edge-param #{} didn't work.",
category, param, param_idx
));
};
param_idx += 1;
} else {
metric_values.push(None);
}
}
EdgeCategory::LaneCount | EdgeCategory::Custom => {
let metric_idx = MetricIdx(metric_values.len());
if cfg.is_metric_provided(metric_idx) {
if let Ok(value) = param.parse::<f64>() {
metric_values.push(Some(value));
} else {
return Err(format!(
"Parsing {} '{}' of edge-param #{} didn't work.",
category, param, param_idx
));
};
param_idx += 1;
} else {
metric_values.push(None);
}
}
EdgeCategory::ShortcutEdgeIdx => {
if param != defaults::parser::NO_SHORTCUT_IDX {
let sc_edge_idx = {
param.parse::<usize>().ok().ok_or(format!(
"Parsing {} '{}' of edge-param #{} didn't work.",
category, param, param_idx
))?
};
if sc_edge_0.is_none() {
sc_edge_0 = Some(sc_edge_idx);
} else if sc_edge_1.is_none() {
sc_edge_1 = Some(sc_edge_idx);
} else {
return Err(format!(
"Too many {}: parsing '{}' of edge-param #{}",
EdgeCategory::ShortcutEdgeIdx,
param,
param_idx
));
}
}
param_idx += 1;
}
EdgeCategory::IgnoredSrcIdx
| EdgeCategory::IgnoredDstIdx
| EdgeCategory::Ignore => param_idx += 1,
}
}
debug_assert_eq!(
cfg.dim(),
metric_values.len(),
"Metric-vec of proto-edge has {} elements, but should have {}.",
metric_values.len(),
cfg.dim()
);
let sc_edges = {
if sc_edge_0.is_none() && sc_edge_1.is_none() {
None
} else {
Some([EdgeIdx(sc_edge_0.unwrap()), EdgeIdx(sc_edge_1.unwrap())])
}
};
Ok(ProtoShortcut {
proto_edge: ProtoEdge {
src_id: src_id.ok_or("Proto-edge should have a src-id, but doesn't.".to_owned())?,
dst_id: dst_id.ok_or("Proto-edge should have a dst-id, but doesn't.".to_owned())?,
metrics: metric_values,
},
sc_edges,
})
}
}
impl ProtoNode {
pub fn from_str(line: &str, cfg: &parser::nodes::Config) -> Result<ProtoNode, String> {
let mut node_id = None;
let mut lat = None;
let mut lon = None;
let mut level = None;
let params: Vec<&str> = line.split_whitespace().collect();
for (param_idx, category) in cfg.categories().iter().enumerate() {
let param = *params.get(param_idx).ok_or(
"The fmi-map-file is expected to have more node-params \
than actually has.",
)?;
match category {
NodeCategory::NodeId => {
node_id = match param.parse::<i64>() {
Ok(id) => Some(id),
Err(_) => {
return Err(format!(
"Parsing id '{:?}' from fmi-file, which is not i64.",
param
))
}
};
}
NodeCategory::Latitude => {
lat = match param.parse::<f64>() {
Ok(lat) => Some(lat),
Err(_) => {
return Err(format!(
"Parsing lat '{:?}' from fmi-file, which is not f64.",
params[2]
))
}
};
}
NodeCategory::Longitude => {
lon = match param.parse::<f64>() {
Ok(lon) => Some(lon),
Err(_) => {
return Err(format!(
"Parsing lon '{:?}' from fmi-file, which is not f64.",
params[3]
))
}
};
}
NodeCategory::Level => {
level = match param.parse::<usize>() {
Ok(level) => Some(level),
Err(_) => {
return Err(format!(
"Parsing level '{:?}' from fmi-file, which is not usize.",
param
))
}
};
}
NodeCategory::Ignore => (),
}
}
let node_id = node_id.ok_or("Proto-node should have an id, but doesn't.".to_owned())?;
let lat = lat.ok_or("Proto-node should have a coordinate, but latitude is misisng.")?;
let lon = lon.ok_or("Proto-node should have a coordinate, but longitude is misisng.")?;
Ok(ProtoNode {
id: node_id,
coord: geo::Coordinate { lat, lon },
level,
})
}
}