2021-05-14 18:32:56 +03:00
|
|
|
#![allow(clippy::new_without_default)]
|
|
|
|
|
2021-01-05 22:35:03 +03:00
|
|
|
#[macro_use]
|
|
|
|
extern crate anyhow;
|
|
|
|
|
2021-06-28 19:32:05 +03:00
|
|
|
use serde::{Deserialize, Deserializer, Serialize, Serializer};
|
2021-05-17 20:34:13 +03:00
|
|
|
|
2018-12-06 21:18:20 +03:00
|
|
|
pub use crate::angle::Angle;
|
2019-02-22 20:36:44 +03:00
|
|
|
pub use crate::bounds::{Bounds, GPSBounds};
|
2018-12-06 21:18:20 +03:00
|
|
|
pub use crate::circle::Circle;
|
2019-02-27 03:17:37 +03:00
|
|
|
pub use crate::distance::Distance;
|
2019-12-03 01:19:50 +03:00
|
|
|
pub use crate::duration::Duration;
|
2019-02-12 01:40:55 +03:00
|
|
|
pub use crate::find_closest::FindClosest;
|
2019-02-22 20:36:44 +03:00
|
|
|
pub use crate::gps::LonLat;
|
2019-01-29 05:09:16 +03:00
|
|
|
pub use crate::line::{InfiniteLine, Line};
|
2020-08-20 21:01:59 +03:00
|
|
|
pub use crate::percent::Percent;
|
2018-12-06 21:18:20 +03:00
|
|
|
pub use crate::polygon::{Polygon, Triangle};
|
2020-05-14 01:31:29 +03:00
|
|
|
pub use crate::polyline::{ArrowCap, PolyLine};
|
2019-02-22 20:36:44 +03:00
|
|
|
pub use crate::pt::{HashablePt2D, Pt2D};
|
2019-10-25 23:14:05 +03:00
|
|
|
pub use crate::ring::Ring;
|
2019-02-27 03:17:37 +03:00
|
|
|
pub use crate::speed::Speed;
|
2020-04-01 06:57:50 +03:00
|
|
|
pub use crate::stats::{HgramValue, Histogram, Statistic};
|
2019-11-27 21:33:58 +03:00
|
|
|
pub use crate::time::Time;
|
2018-08-07 23:22:24 +03:00
|
|
|
|
2020-10-05 02:09:20 +03:00
|
|
|
mod angle;
|
|
|
|
mod bounds;
|
|
|
|
mod circle;
|
|
|
|
mod distance;
|
|
|
|
mod duration;
|
|
|
|
mod find_closest;
|
|
|
|
mod gps;
|
|
|
|
mod line;
|
|
|
|
mod percent;
|
|
|
|
mod polygon;
|
|
|
|
mod polyline;
|
|
|
|
mod pt;
|
|
|
|
mod ring;
|
|
|
|
mod speed;
|
|
|
|
mod stats;
|
|
|
|
mod time;
|
|
|
|
|
2018-10-18 01:18:35 +03:00
|
|
|
// About 0.4 inches... which is quite tiny on the scale of things. :)
|
2019-01-30 23:13:31 +03:00
|
|
|
pub const EPSILON_DIST: Distance = Distance::const_meters(0.01);
|
2019-01-31 09:24:47 +03:00
|
|
|
|
2020-10-11 20:45:13 +03:00
|
|
|
/// Reduce the precision of an f64. This helps ensure serialization is idempotent (everything is
|
|
|
|
/// exacly the same before and after saving/loading). Ideally we'd use some kind of proper
|
|
|
|
/// fixed-precision type instead of f64.
|
2019-12-12 20:13:25 +03:00
|
|
|
pub fn trim_f64(x: f64) -> f64 {
|
2019-01-31 09:24:47 +03:00
|
|
|
(x * 10_000.0).round() / 10_000.0
|
|
|
|
}
|
2020-10-11 20:45:13 +03:00
|
|
|
|
2021-06-28 19:32:05 +03:00
|
|
|
/// Serializes a trimmed `f64` as an `i32` to save space.
|
|
|
|
fn serialize_f64<S: Serializer>(x: &f64, s: S) -> Result<S::Ok, S::Error> {
|
|
|
|
// So a trimmed f64's range becomes 2**31 / 10,000 =~ 214,000, which is plenty
|
|
|
|
// We don't need to round() here; trim_f64 already handles that.
|
|
|
|
let int = (x * 10_000.0) as i32;
|
|
|
|
int.serialize(s)
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Deserializes a trimmed `f64` from an `i32`.
|
|
|
|
fn deserialize_f64<'de, D: Deserializer<'de>>(d: D) -> Result<f64, D::Error> {
|
|
|
|
let x = <i32>::deserialize(d)?;
|
|
|
|
Ok(x as f64 / 10_000.0)
|
|
|
|
}
|
|
|
|
|
2020-10-11 20:45:13 +03:00
|
|
|
/// Specifies how to stringify different geom objects.
|
2021-05-17 20:34:13 +03:00
|
|
|
#[derive(Clone, Serialize, Deserialize)]
|
2020-10-11 20:45:13 +03:00
|
|
|
pub struct UnitFmt {
|
|
|
|
/// Round `Duration`s to a whole number of seconds.
|
|
|
|
pub round_durations: bool,
|
|
|
|
/// Display in metric; US imperial otherwise.
|
|
|
|
pub metric: bool,
|
|
|
|
}
|
2021-01-25 02:32:28 +03:00
|
|
|
|
|
|
|
#[derive(Clone, Copy, Debug)]
|
|
|
|
pub struct CornerRadii {
|
|
|
|
pub top_left: f64,
|
|
|
|
pub top_right: f64,
|
|
|
|
pub bottom_right: f64,
|
|
|
|
pub bottom_left: f64,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl CornerRadii {
|
|
|
|
pub fn uniform(radius: f64) -> Self {
|
|
|
|
Self {
|
|
|
|
top_left: radius,
|
|
|
|
top_right: radius,
|
|
|
|
bottom_right: radius,
|
|
|
|
bottom_left: radius,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn zero() -> Self {
|
|
|
|
Self::uniform(0.0)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl std::convert::From<f64> for CornerRadii {
|
|
|
|
fn from(uniform: f64) -> Self {
|
|
|
|
Self::uniform(uniform)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl std::default::Default for CornerRadii {
|
|
|
|
fn default() -> Self {
|
|
|
|
Self::zero()
|
|
|
|
}
|
|
|
|
}
|
2021-06-28 19:32:05 +03:00
|
|
|
|
|
|
|
#[cfg(test)]
|
|
|
|
mod tests {
|
|
|
|
use super::*;
|
|
|
|
use rand::{Rng, SeedableRng};
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn f64_trimming() {
|
|
|
|
// Roundtrip a bunch of random f64's
|
|
|
|
let mut rng = rand_xorshift::XorShiftRng::seed_from_u64(42);
|
|
|
|
for _ in 0..1_000 {
|
|
|
|
let input = rng.gen_range(-214_000.00..214_000.0);
|
|
|
|
let trimmed = trim_f64(input);
|
|
|
|
let json_roundtrip: f64 =
|
|
|
|
abstutil::from_json(abstutil::to_json(&trimmed).as_bytes()).unwrap();
|
|
|
|
let bincode_roundtrip: f64 =
|
|
|
|
abstutil::from_binary(&abstutil::to_binary(&trimmed)).unwrap();
|
|
|
|
assert_eq!(json_roundtrip, trimmed);
|
|
|
|
assert_eq!(bincode_roundtrip, trimmed);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Hardcode a particular case, where we can hand-verify that it trims to 4 decimal places
|
|
|
|
let input = 1.2345678;
|
|
|
|
let trimmed = trim_f64(input);
|
|
|
|
let json_roundtrip: f64 =
|
|
|
|
abstutil::from_json(abstutil::to_json(&trimmed).as_bytes()).unwrap();
|
|
|
|
let bincode_roundtrip: f64 = abstutil::from_binary(&abstutil::to_binary(&trimmed)).unwrap();
|
|
|
|
assert_eq!(json_roundtrip, 1.2346);
|
|
|
|
assert_eq!(bincode_roundtrip, 1.2346);
|
|
|
|
}
|
|
|
|
}
|