use geo_types::{LineString, MultiLineString};
use log::warn;
use osmpbfreader::OsmObj::*;
use std::collections::btree_set::BTreeSet;
use std::collections::BTreeMap;
use std::path::Path;
pub type OsmPbfReader = osmpbfreader::OsmPbfReader<std::fs::File>;
pub trait Id<T> {
fn id(&self) -> &str;
}
pub trait Shape {
fn get_shape(&self) -> &Vec<Vec<Coord>>;
}
pub fn shape_to_multi_line_string<T>(container: &T) -> MultiLineString<f64>
where
T: Shape,
{
container
.get_shape()
.iter()
.map(|way_coords| {
way_coords
.iter()
.map(|coord| (coord.lon, coord.lat))
.collect::<LineString<f64>>()
})
.collect::<MultiLineString<f64>>()
}
#[derive(Debug, Clone)]
pub struct Coord {
pub lat: f64,
pub lon: f64,
}
impl Coord {
fn new(lat_param: f64, lon_param: f64) -> Coord {
Coord {
lat: lat_param,
lon: lon_param,
}
}
}
#[derive(Debug, Clone)]
pub struct StopPoint {
pub id: String,
pub coord: Coord,
pub name: String,
pub all_osm_tags: osmpbfreader::objects::Tags,
}
#[derive(Debug, Clone)]
pub struct StopArea {
pub id: String,
pub coord: Coord,
pub name: String,
pub all_osm_tags: osmpbfreader::objects::Tags,
pub stop_point_ids: Vec<String>,
}
#[derive(Debug, Clone)]
pub struct Route {
pub id: String,
pub name: String,
pub code: String,
pub destination: String,
pub origin: String,
pub colour: String,
pub operator: String,
pub network: String,
pub mode: String,
pub frequency: String,
pub opening_hours: String,
pub frequency_exceptions: String,
pub travel_time: String,
pub all_osm_tags: osmpbfreader::objects::Tags,
pub ordered_stops_id: Vec<String>,
pub shape: Vec<Vec<Coord>>,
}
impl Id<Route> for Route {
fn id(&self) -> &str {
self.id.as_str()
}
}
impl Shape for Route {
fn get_shape(&self) -> &Vec<Vec<Coord>> {
&self.shape
}
}
#[derive(Debug, Clone)]
pub struct Line {
pub id: String,
pub name: String,
pub code: String,
pub colour: String,
pub operator: String,
pub network: String,
pub mode: String,
pub frequency: String,
pub opening_hours: String,
pub frequency_exceptions: String,
pub all_osm_tags: osmpbfreader::objects::Tags,
pub shape: Vec<Vec<Coord>>,
pub routes_id: Vec<String>,
}
impl Id<Line> for Line {
fn id(&self) -> &str {
self.id.as_str()
}
}
impl Shape for Line {
fn get_shape(&self) -> &Vec<Vec<Coord>> {
&self.shape
}
}
#[allow(dead_code)]
impl Route {
fn to_multilinestring(&self) -> wkt::types::MultiLineString {
let wkt_linestrings = self
.shape
.iter()
.map(|vec_coord| {
vec_coord
.iter()
.map(|coord| wkt::types::Coord {
x: coord.lon,
y: coord.lat,
z: None,
m: None,
})
.collect()
})
.map(|wkt_coords| wkt::types::LineString(wkt_coords))
.collect();
wkt::types::MultiLineString(wkt_linestrings)
}
}
fn shape_to_wkt(shape: &Vec<Vec<Coord>>) -> String {
if shape.len() == 0 {
"".to_string()
} else {
let linestring: String = shape
.iter()
.map(|vec_coord| {
vec_coord
.iter()
.map(|coord| format!("{} {}", coord.lon.to_string(), coord.lat.to_string()))
.collect::<Vec<String>>()
.join(", ")
})
.collect::<Vec<String>>()
.join("), (");
format!("MULTILINESTRING(({}))", linestring)
}
}
pub struct OsmTcResponse {
pub stop_points: Vec<StopPoint>,
pub stop_areas: Vec<StopArea>,
pub routes: Option<Vec<Route>>,
pub lines: Option<Vec<Line>>,
}
pub fn parse_osm_pbf(path: &str) -> OsmPbfReader {
let path = std::path::Path::new(&path);
osmpbfreader::OsmPbfReader::new(std::fs::File::open(&path).unwrap())
}
fn is_stop_point(obj: &osmpbfreader::OsmObj) -> bool {
obj.tags().contains("public_transport", "plateform")
|| (obj.is_node() && obj.tags().contains("highway", "bus_stop"))
}
fn is_stop_area(obj: &osmpbfreader::OsmObj) -> bool {
obj.is_relation() && obj.tags().contains("public_transport", "stop_area")
}
fn is_pt_route_type(
osm_id: osmpbfreader::objects::RelationId,
tag_name: &str,
route_type: Option<&String>,
) -> bool {
let non_pt_route_type = vec![
"bicycle",
"canoe",
"detour",
"fitness_trail",
"foot",
"hiking",
"horse",
"inline_skates",
"mtb",
"nordic_walking",
"pipeline",
"piste",
"power",
"proposed",
"road",
"running",
"ski",
"historic",
"path",
"junction",
"tracks",
];
let pt_route_type = vec![
"trolleybus",
"bus",
"train",
"subway",
"light_rail",
"monorail",
"tram",
"railway",
"ferry",
"coach",
"aerialway",
"funicular",
"rail",
"share_taxi",
];
match route_type {
Some(r) => {
let is_in_white_list = pt_route_type.contains(&r.as_str());
let is_in_black_list = non_pt_route_type.contains(&r.as_str());
if !is_in_white_list && !is_in_black_list {
warn!("tag {} is unknown : relation {} is extracted. Update mode list in crate to remove this message.", tag_name, osm_id.0);
}
!is_in_black_list
}
None => {
warn!(
"tag {} is empty : relation {} is ignored",
tag_name, osm_id.0
);
false
}
}
}
fn is_line(obj: &osmpbfreader::OsmObj) -> bool {
let route_type = "route_master";
obj.is_relation()
&& obj.tags().contains("type", route_type)
&& is_pt_route_type(
obj.id().relation().unwrap(),
route_type,
obj.tags().get(route_type),
)
}
fn is_route(obj: &osmpbfreader::OsmObj) -> bool {
let route_type = "route";
obj.is_relation()
&& obj.tags().contains("type", route_type)
&& is_pt_route_type(
obj.id().relation().unwrap(),
route_type,
obj.tags().get(route_type),
)
}
const STOP_ROLES: [&'static str; 7] = [
"stop",
"platform",
"stop_exit_only",
"stop_entry_only",
"platform_exit_only",
"platform_entry_only",
"fixme",
];
fn is_stop(refe: &osmpbfreader::Ref) -> bool {
STOP_ROLES.contains(&refe.role.as_str())
}
fn get_one_coord_from_way(
obj_map: &BTreeMap<osmpbfreader::OsmId, osmpbfreader::OsmObj>,
way: &osmpbfreader::objects::Way,
) -> Coord {
way.nodes
.iter()
.filter_map(|node_id| {
obj_map
.get(&(*node_id).into())
.and_then(|obj| obj.node())
.map(|node| Coord::new(node.lat(), node.lon()))
})
.next()
.unwrap_or(Coord::new(0., 0.))
}
fn get_one_coord_from_rel(
obj_map: &BTreeMap<osmpbfreader::OsmId, osmpbfreader::OsmObj>,
rel: &osmpbfreader::objects::Relation,
) -> Coord {
rel.refs
.iter()
.filter_map(|refe| obj_map.get(&refe.member))
.filter_map(|osm_obj| match *osm_obj {
Way(ref way) => Some(get_one_coord_from_way(obj_map, way)),
Node(ref node) => Some(Coord::new(node.lat(), node.lon())),
Relation(..) => None,
})
.next()
.unwrap_or(Coord::new(0., 0.))
}
fn osm_way_to_vec(
obj_map: &BTreeMap<osmpbfreader::OsmId, osmpbfreader::OsmObj>,
osm_way: &osmpbfreader::Way,
) -> Vec<Coord> {
osm_way
.nodes
.iter()
.filter_map(|id| obj_map.get(&osmpbfreader::OsmId::Node(*id)))
.filter_map(|osm_obj| osmpbfreader::OsmObj::node(osm_obj))
.map(|node| Coord::new(node.lat(), node.lon()))
.collect()
}
fn osm_route_to_shape(
obj_map: &BTreeMap<osmpbfreader::OsmId, osmpbfreader::OsmObj>,
osm_relation: &osmpbfreader::Relation,
) -> Vec<Vec<Coord>> {
osm_relation
.refs
.iter()
.filter(|refe| !is_stop(*refe))
.filter_map(|refe| obj_map.get(&refe.member))
.filter_map(|osm_obj| osmpbfreader::OsmObj::way(osm_obj))
.filter_map(|osm_way| {
let coord_vec = osm_way_to_vec(obj_map, osm_way);
match coord_vec.len() {
0 | 1 => None,
_ => Some(coord_vec),
}
})
.collect()
}
fn osm_line_to_shape(
obj_map: &BTreeMap<osmpbfreader::OsmId, osmpbfreader::OsmObj>,
osm_relations_ref: &Vec<osmpbfreader::Ref>,
) -> Vec<Vec<Coord>> {
osm_relations_ref
.iter()
.filter_map(|refe| obj_map.get(&refe.member))
.filter_map(|osm_obj| osmpbfreader::OsmObj::relation(osm_obj))
.flat_map(|relation| osm_route_to_shape(obj_map, relation))
.collect()
}
fn osm_route_to_stop_list(osm_relation: &osmpbfreader::Relation) -> Vec<String> {
osm_relation
.refs
.iter()
.filter(|refe| is_stop(*refe))
.map(|refe| match refe.member {
osmpbfreader::OsmId::Node(obj_id) => format!("node:{}", obj_id.0),
osmpbfreader::OsmId::Way(obj_id) => format!("way:{}", obj_id.0),
osmpbfreader::OsmId::Relation(obj_id) => format!("relation:{}", obj_id.0),
})
.collect()
}
fn osm_line_to_routes_list(route_master: &osmpbfreader::Relation) -> Vec<String> {
route_master
.refs
.iter()
.filter_map(|refe| match refe.member {
osmpbfreader::OsmId::Relation(rel_id) => Some(format!("relation:{}", rel_id.0)),
_ => None,
})
.collect()
}
fn osm_obj_to_route(
obj_map: &BTreeMap<osmpbfreader::OsmId, osmpbfreader::OsmObj>,
obj: &osmpbfreader::OsmObj,
) -> Option<Route> {
let osm_tags = obj.tags().clone();
obj.relation().map(|rel| Route {
id: format!("relation:{}", rel.id.0),
name: rel.tags.get("name").cloned().unwrap_or_default(),
code: rel.tags.get("ref").cloned().unwrap_or_default(),
destination: rel.tags.get("to").cloned().unwrap_or_default(),
origin: rel.tags.get("from").cloned().unwrap_or_default(),
mode: rel.tags.get("route").cloned().unwrap_or_default(),
colour: rel.tags.get("colour").cloned().unwrap_or_default(),
operator: rel.tags.get("operator").cloned().unwrap_or_default(),
network: rel.tags.get("network").cloned().unwrap_or_default(),
frequency: rel.tags.get("interval").cloned().unwrap_or_default(),
opening_hours: rel.tags.get("opening_hours").cloned().unwrap_or_default(),
frequency_exceptions: rel
.tags
.get("interval:conditional")
.cloned()
.unwrap_or_default(),
travel_time: rel.tags.get("duration").cloned().unwrap_or_default(),
all_osm_tags: osm_tags,
ordered_stops_id: osm_route_to_stop_list(rel),
shape: osm_route_to_shape(obj_map, rel),
})
}
fn osm_obj_to_line(
obj_map: &BTreeMap<osmpbfreader::OsmId, osmpbfreader::OsmObj>,
obj: &osmpbfreader::OsmObj,
) -> Option<Line> {
let osm_tags = obj.tags().clone();
obj.relation().map(|rel| Line {
id: format!("relation:{}", rel.id.0),
name: rel.tags.get("name").cloned().unwrap_or_default(),
code: rel.tags.get("ref").cloned().unwrap_or_default(),
colour: rel.tags.get("colour").cloned().unwrap_or_default(),
mode: rel.tags.get("route_master").cloned().unwrap_or_default(),
operator: rel.tags.get("operator").cloned().unwrap_or_default(),
network: rel.tags.get("network").cloned().unwrap_or_default(),
frequency: rel.tags.get("interval").cloned().unwrap_or_default(),
opening_hours: rel.tags.get("opening_hours").cloned().unwrap_or_default(),
frequency_exceptions: rel
.tags
.get("interval:conditional")
.cloned()
.unwrap_or_default(),
all_osm_tags: osm_tags,
shape: osm_line_to_shape(obj_map, &rel.refs),
routes_id: osm_line_to_routes_list(rel),
})
}
fn osm_obj_to_stop_point(
obj_map: &BTreeMap<osmpbfreader::OsmId, osmpbfreader::OsmObj>,
obj: &osmpbfreader::OsmObj,
) -> StopPoint {
let (obj_type, obj_id, coord) = match *obj {
Relation(ref rel) => ("relation", rel.id.0, get_one_coord_from_rel(obj_map, rel)),
Way(ref way) => ("way", way.id.0, get_one_coord_from_way(obj_map, way)),
Node(ref node) => (
"node",
node.id.0,
Coord {
lat: node.lat(),
lon: node.lon(),
},
),
};
let name = obj.tags().get("name").cloned().unwrap_or_default();
let id = format!("{}:{}", obj_type, obj_id);
let osm_tags = obj.tags().clone();
StopPoint {
id,
name,
coord,
all_osm_tags: osm_tags,
}
}
fn osm_obj_to_stop_area(
obj_map: &BTreeMap<osmpbfreader::OsmId, osmpbfreader::OsmObj>,
obj: &osmpbfreader::OsmObj,
) -> StopArea {
let rel = &*obj.relation().unwrap();
let (obj_type, obj_id, coord) = ("relation", rel.id.0, get_one_coord_from_rel(obj_map, &rel));
let name = obj.tags().get("name").cloned().unwrap_or_default();
let osm_tags = obj.tags().clone();
StopArea {
id: format!("{}:{}", obj_type, obj_id),
name,
coord,
all_osm_tags: osm_tags,
stop_point_ids: osm_stop_area_to_stop_point_list(rel),
}
}
fn osm_stop_area_to_stop_point_list(osm_relation: &osmpbfreader::Relation) -> Vec<String> {
osm_relation
.refs
.iter()
.filter(|refe| refe.role.as_str() == "platform")
.map(|refe| match refe.member {
osmpbfreader::OsmId::Node(obj_id) => format!("node:{}", obj_id.0),
osmpbfreader::OsmId::Way(obj_id) => format!("way:{}", obj_id.0),
osmpbfreader::OsmId::Relation(obj_id) => format!("relation:{}", obj_id.0),
})
.collect()
}
pub fn get_stop_points_from_osm(pbf: &mut OsmPbfReader) -> Vec<StopPoint> {
let objects = pbf.get_objs_and_deps(is_stop_point).unwrap();
objects
.values()
.filter(|x| is_stop_point(*x))
.map(|obj| osm_obj_to_stop_point(&objects, obj))
.collect()
}
pub fn get_stop_areas_from_osm(pbf: &mut OsmPbfReader) -> Vec<StopArea> {
let objects = pbf.get_objs_and_deps(is_stop_area).unwrap();
objects
.values()
.filter(|x| is_stop_area(*x))
.map(|obj| osm_obj_to_stop_area(&objects, obj))
.collect()
}
pub fn get_routes_from_osm(pbf: &mut OsmPbfReader) -> Vec<Route> {
let objects = pbf.get_objs_and_deps(is_route).unwrap();
objects
.values()
.filter(|x| is_route(*x))
.filter_map(|obj| osm_obj_to_route(&objects, obj))
.collect()
}
pub fn get_lines_from_osm(pbf: &mut OsmPbfReader) -> Vec<Line> {
let objects = pbf.get_objs_and_deps(is_line).unwrap();
objects
.values()
.filter(|x| is_line(*x))
.filter_map(|obj| osm_obj_to_line(&objects, obj))
.collect()
}
pub fn get_osm_tcobjects(parsed_pbf: &mut OsmPbfReader, stops_only: bool) -> OsmTcResponse {
let stop_points = get_stop_points_from_osm(parsed_pbf);
let stop_areas = get_stop_areas_from_osm(parsed_pbf);
if stops_only {
OsmTcResponse {
stop_points,
stop_areas,
routes: None,
lines: None,
}
} else {
let routes = get_routes_from_osm(parsed_pbf);
let lines = get_lines_from_osm(parsed_pbf);
OsmTcResponse {
stop_points,
stop_areas,
routes: Some(routes),
lines: Some(lines),
}
}
}
fn get_stop_area_ids_for_stop_point(
stop_point_id: &String,
stop_areas: &Vec<StopArea>,
) -> Vec<String> {
stop_areas
.iter()
.filter(|sa| sa.stop_point_ids.contains(stop_point_id))
.map(|sa| format!("StopArea:{}", sa.id))
.collect()
}
pub fn write_stop_points_to_csv<P: AsRef<Path>>(
stop_points: &Vec<StopPoint>,
stop_areas: &Vec<StopArea>,
output_dir: P,
all_tags: bool,
) {
let output_dir = output_dir.as_ref();
let csv_file = output_dir.join("osm-transit-extractor_stop_points.csv");
let mut wtr = csv::Writer::from_path(csv_file).unwrap();
let default_header = ["stop_point_id", "lat", "lon", "name", "first_stop_area_id"];
let osm_tag_list: BTreeSet<String> = stop_points
.iter()
.flat_map(|s| s.all_osm_tags.keys().map(|s| s.to_string()))
.collect();
if all_tags {
let osm_header = osm_tag_list.iter().map(|s| format!("osm:{}", s));
let v: Vec<_> = default_header
.iter()
.map(|s| s.to_string())
.chain(osm_header)
.collect();
wtr.serialize(v).unwrap();
} else {
wtr.serialize(default_header).unwrap();
}
for sp in stop_points {
let stop_area_ids = get_stop_area_ids_for_stop_point(&sp.id, &stop_areas);
let mut csv_row = vec![
format!("StopPoint:{}", sp.id),
sp.coord.lat.to_string(),
sp.coord.lon.to_string(),
sp.name.to_string(),
stop_area_ids
.iter()
.next()
.unwrap_or(&"".to_string())
.to_string(),
];
if all_tags {
csv_row = csv_row
.into_iter()
.chain(osm_tag_list.iter().map(|k| {
sp.all_osm_tags
.get(k)
.map_or("", |s| s.as_str())
.to_string()
}))
.collect();
}
wtr.serialize(csv_row).unwrap();
}
}
pub fn write_stop_areas_to_csv<P: AsRef<Path>>(
stop_areas: &Vec<StopArea>,
output_dir: P,
all_tags: bool,
) {
let output_dir = output_dir.as_ref();
let csv_file = output_dir.join("osm-transit-extractor_stop_areas.csv");
let mut wtr = csv::Writer::from_path(csv_file).unwrap();
let osm_tag_list: BTreeSet<String> = stop_areas
.iter()
.flat_map(|s| s.all_osm_tags.keys().map(|s| s.to_string()))
.collect();
let default_header = ["stop_area_id", "lat", "lon", "name"];
if all_tags {
let osm_header = osm_tag_list.iter().map(|s| format!("osm:{}", s));
let v: Vec<_> = default_header
.iter()
.map(|s| s.to_string())
.chain(osm_header)
.collect();
wtr.serialize(v).unwrap();
} else {
wtr.serialize(default_header).unwrap();
}
for sa in stop_areas {
let mut csv_row = vec![
format!("StopArea:{}", sa.id),
sa.coord.lat.to_string(),
sa.coord.lon.to_string(),
sa.name.to_string(),
];
if all_tags {
csv_row = csv_row
.into_iter()
.chain(osm_tag_list.iter().map(|k| {
sa.all_osm_tags
.get(k)
.map_or("", |s| s.as_str())
.to_string()
}))
.collect();
}
wtr.serialize(csv_row).unwrap();
}
}
pub fn write_routes_to_csv<P: AsRef<Path>>(routes: Vec<Route>, output_dir: P, all_tags: bool) {
let output_dir = output_dir.as_ref();
let csv_route_file = output_dir.join("osm-transit-extractor_routes.csv");
let csv_route_stops_file = output_dir.join("osm-transit-extractor_route_stops.csv");
let mut wtr_route = csv::Writer::from_path(csv_route_file).unwrap();
let mut wtr_stops = csv::Writer::from_path(csv_route_stops_file).unwrap();
let osm_tag_list: BTreeSet<String> = routes
.iter()
.flat_map(|r| r.all_osm_tags.keys().map(|s| s.to_string()))
.collect();
let osm_header = osm_tag_list.iter().map(|s| format!("osm:{}", s));
wtr_stops.serialize(("route_id", "stop_id")).unwrap();
let default_header = [
"route_id",
"name",
"code",
"destination",
"origin",
"colour",
"operator",
"network",
"mode",
"frequency",
"opening_hours",
"frequency_exceptions",
"travel_time",
"shape",
];
if all_tags {
let v: Vec<_> = default_header
.iter()
.map(|s| s.to_string())
.chain(osm_header)
.collect();
wtr_route.serialize(v).unwrap();
} else {
wtr_route.serialize(default_header).unwrap();
}
for r in &routes {
let mut csv_row = vec![
format!("Route:{}", r.id),
r.name.to_string(),
r.code.to_string(),
r.destination.to_string(),
r.origin.to_string(),
r.colour.to_string(),
r.operator.to_string(),
r.network.to_string(),
r.mode.to_string(),
r.frequency.to_string(),
r.opening_hours.to_string(),
r.frequency_exceptions.to_string(),
r.travel_time.to_string(),
shape_to_wkt(&r.shape),
];
if all_tags {
csv_row = csv_row
.into_iter()
.chain(
osm_tag_list
.iter()
.map(|k| r.all_osm_tags.get(k).map_or("", |s| s.as_str()).to_string()),
)
.collect();
}
wtr_route.serialize(csv_row).unwrap();
for s in &r.ordered_stops_id {
let row = vec![format!("Route:{}", r.id), format!("StopPoint:{}", s)];
wtr_stops.write_record(row.into_iter()).unwrap();
}
}
}
pub fn write_lines_to_csv<P: AsRef<Path>>(lines: Vec<Line>, output_dir: P, all_tags: bool) {
let output_dir = output_dir.as_ref();
let lines_csv_file = output_dir.join("osm-transit-extractor_lines.csv");
let mut lines_wtr = csv::Writer::from_path(lines_csv_file).unwrap();
let osm_tag_list: BTreeSet<String> = lines
.iter()
.flat_map(|r| r.all_osm_tags.keys().map(|s| s.to_string()))
.collect();
let default_header = [
"line_id",
"name",
"code",
"colour",
"operator",
"network",
"mode",
"frequency",
"opening_hours",
"frequency_exceptions",
"shape",
];
if all_tags {
let osm_header = osm_tag_list.iter().map(|s| format!("osm:{}", s));
let v: Vec<_> = default_header
.iter()
.map(|s| s.to_string())
.chain(osm_header)
.collect();
lines_wtr.serialize(v).unwrap();
} else {
lines_wtr.serialize(default_header).unwrap();
}
let csv_file = output_dir.join("osm-transit-extractor_line_routes.csv");
let mut wtr = csv::Writer::from_path(csv_file).unwrap();
wtr.serialize(("line_id", "route_id")).unwrap();
for l in &lines {
let mut csv_row = vec![
format!("Line:{}", l.id),
l.name.to_string(),
l.code.to_string(),
l.colour.to_string(),
l.operator.to_string(),
l.network.to_string(),
l.mode.to_string(),
l.frequency.to_string(),
l.opening_hours.to_string(),
l.frequency_exceptions.to_string(),
shape_to_wkt(&l.shape),
];
if all_tags {
csv_row = csv_row
.into_iter()
.chain(
osm_tag_list
.iter()
.map(|k| l.all_osm_tags.get(k).map_or("", |s| s.as_str()).to_string()),
)
.collect();
}
lines_wtr.serialize(csv_row).unwrap();
for r in &l.routes_id {
wtr.serialize((format!("Line:{}", &l.id), format!("Route:{}", &r)))
.unwrap();
}
}
}