gluesql_core/data/
point.rs1use {
2 super::ValueError,
3 crate::result::Result,
4 regex::Regex,
5 serde::{Deserialize, Serialize},
6 std::{
7 fmt,
8 hash::{Hash, Hasher},
9 },
10};
11
12#[derive(Copy, Debug, Clone, Serialize, Deserialize)]
13pub struct Point {
14 pub x: f64,
15 pub y: f64,
16}
17
18impl Point {
19 pub fn new(x: f64, y: f64) -> Self {
20 Self { x, y }
21 }
22
23 pub fn from_wkt(v: &str) -> Result<Self> {
24 let re = Regex::new(r"POINT\s*\(\s*(-?\d*\.?\d+)\s+(-?\d*\.?\d+)\s*\)").unwrap();
25
26 if let Some(captures) = re.captures(v) {
27 let x = captures[1]
28 .parse::<f64>()
29 .map_err(|_| ValueError::FailedToParsePoint(v.to_owned()))?;
30 let y = captures[2]
31 .parse::<f64>()
32 .map_err(|_| ValueError::FailedToParsePoint(v.to_owned()))?;
33 Ok(Self { x, y })
34 } else {
35 Err(ValueError::FailedToParsePoint(v.to_owned()).into())
36 }
37 }
38
39 pub fn calc_distance(&self, other: &Point) -> f64 {
40 let dx = self.x - other.x;
41 let dy = self.y - other.y;
42 f64::sqrt(dx * dx + dy * dy)
43 }
44}
45
46impl PartialEq for Point {
47 fn eq(&self, other: &Self) -> bool {
48 let points_equal = |a: f64, b: f64| (a.is_nan() && b.is_nan()) || a == b;
49
50 points_equal(self.x, other.x) && points_equal(self.y, other.y)
51 }
52}
53
54impl Eq for Point {}
55
56impl Hash for Point {
57 fn hash<H: Hasher>(&self, state: &mut H) {
58 const CANONICAL_F64_NAN_BITS: u64 = 0x7ff8_0000_0000_0000;
59 const CANONICAL_F64_ZERO_BITS: u64 = 0;
60
61 #[inline]
62 fn normalize_nan_and_zero(x: f64) -> u64 {
63 if x.is_nan() {
64 CANONICAL_F64_NAN_BITS
65 } else if x == 0.0 {
66 CANONICAL_F64_ZERO_BITS
67 } else {
68 x.to_bits()
69 }
70 }
71
72 let x_bits = normalize_nan_and_zero(self.x);
73 let y_bits = normalize_nan_and_zero(self.y);
74
75 x_bits.hash(state);
76 y_bits.hash(state);
77 }
78}
79
80impl fmt::Display for Point {
81 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
82 write!(f, "POINT({} {})", self.x, self.y)
83 }
84}