collisions/lib.rs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141
//! A simple data format to list collisions that've occurred in the real world. The data is
//! serializable in a binary format or as JSON.
#[macro_use]
extern crate log;
use geom::{Duration, LonLat};
use kml::ExtraShapes;
use serde::{Deserialize, Serialize};
/// A single dataset describing some collisions that happened.
#[derive(Serialize, Deserialize)]
pub struct CollisionDataset {
/// A URL pointing to the original data source.
pub source_url: String,
/// The collisions imported from the data source.
pub collisions: Vec<Collision>,
}
/// A single collision that occurred in the real world.
#[derive(Serialize, Deserialize)]
pub struct Collision {
/// A single point describing where the collision occurred.
pub location: LonLat,
/// The local time the collision occurred.
// TODO Wait, why isn't this Time?
pub time: Duration,
/// The severity reported in the original data source.
pub severity: Severity,
/* TODO Many more interesting and common things: the date, the number of
* people/vehicles/bikes/casualties, road/weather/alcohol/speeding conditions possibly
* influencing the event, etc. */
}
/// A simple ranking for how severe the collision was. Different agencies use different
/// classification systems, each of which likely has their own nuance and bias. This is
/// deliberately simplified.
#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub enum Severity {
Slight,
Serious,
Fatal,
}
/// Import data from the UK STATS19 dataset. See https://github.com/ropensci/stats19. Any parsing
/// errors will skip the row and log a warning.
pub fn import_stats19(input: ExtraShapes, source_url: &str) -> CollisionDataset {
let mut data = CollisionDataset {
source_url: source_url.to_string(),
collisions: Vec::new(),
};
for shape in input.shapes {
if shape.points.len() != 1 {
warn!("One row had >1 point: {:?}", shape);
continue;
}
let time = match Duration::parse(&format!("{}:00", shape.attributes["Time"])) {
Ok(time) => time,
Err(err) => {
warn!("Couldn't parse time: {}", err);
continue;
}
};
let severity = match shape.attributes["Accident_Severity"].as_ref() {
// TODO Is this backwards?
"1" => Severity::Slight,
"2" => Severity::Serious,
"3" => Severity::Fatal,
x => {
warn!("Unknown severity {}", x);
continue;
}
};
data.collisions.push(Collision {
location: shape.points[0],
time,
severity,
});
}
data
}
/// Import data from Seattle GeoData
/// (https://data-seattlecitygis.opendata.arcgis.com/datasets/5b5c745e0f1f48e7a53acec63a0022ab_0).
/// Any parsing errors will skip the row and log a warning.
pub fn import_seattle(input: ExtraShapes, source_url: &str) -> CollisionDataset {
let mut data = CollisionDataset {
source_url: source_url.to_string(),
collisions: Vec::new(),
};
for shape in input.shapes {
if shape.points.len() != 1 {
warn!("One row had >1 point: {:?}", shape);
continue;
}
let time = match parse_incdttm(&shape.attributes["INCDTTM"]) {
Some(time) => time,
None => {
warn!("Couldn't parse time {}", shape.attributes["INCDTTM"]);
continue;
}
};
let severity = match shape
.attributes
.get("SEVERITYCODE")
.cloned()
.unwrap_or_else(String::new)
.as_ref()
{
"1" | "0" => Severity::Slight,
"2b" | "2" => Severity::Serious,
"3" => Severity::Fatal,
x => {
warn!("Unknown severity {}", x);
continue;
}
};
data.collisions.push(Collision {
location: shape.points[0],
time,
severity,
});
}
data
}
// INCDTTM is something like "11/12/2019 7:30:00 AM"
fn parse_incdttm(x: &str) -> Option<Duration> {
let parts = x.split(' ').collect::<Vec<_>>();
if parts.len() != 3 {
return None;
}
let time = Duration::parse(parts[1]).ok()?;
if parts[2] == "AM" {
Some(time)
} else if parts[2] == "PM" {
Some(time + Duration::hours(12))
} else {
None
}
}