supercluster_rs/
cluster.rs

1use crate::util::{latitude_to_y, longitude_to_x, x_to_longitude, y_to_latitude};
2
3// encode both zoom and point index on which the cluster originated -- offset by total length of
4// features
5#[repr(transparent)]
6#[derive(Debug, Clone, Copy, PartialEq, Eq)]
7pub struct ClusterId(usize);
8
9// Note: I think like many of mourner's projects, here the ID can have multiple meanings depending
10// on whether it's a leaf or an internal node of the tree
11//
12// Thus we have multiple constructors
13impl ClusterId {
14    pub fn new(i: usize, zoom: usize, length: usize) -> Self {
15        let id = (i << 5) + (zoom + 1) + length;
16        Self(id)
17    }
18
19    pub fn new_source_id(id: usize) -> Self {
20        Self(id)
21    }
22
23    pub fn as_usize(self) -> usize {
24        self.0
25    }
26
27    /// get index of the point from which the cluster originated
28    // Note: I _think_ this doesn't return a ClusterId
29    pub(crate) fn get_origin_idx(&self, length: usize) -> usize {
30        (self.0 - length) >> 5
31    }
32
33    /// get zoom of the point from which the cluster originated
34    pub(crate) fn get_origin_zoom(&self, length: usize) -> usize {
35        (self.0 - length) % 32
36    }
37}
38
39impl From<ClusterId> for usize {
40    fn from(value: ClusterId) -> Self {
41        value.0
42    }
43}
44
45#[derive(Debug, Clone)]
46pub struct ClusterData {
47    /// projected point x
48    pub(crate) x: f64,
49
50    /// projected point y
51    pub(crate) y: f64,
52
53    /// The last zoom the point was processed at
54    pub(crate) zoom: Option<usize>,
55
56    // index of the source feature in the original input array
57    pub(crate) source_id: ClusterId,
58
59    // parent cluster id
60    pub(crate) parent_id: Option<ClusterId>,
61
62    // number of points in a cluster
63    pub(crate) num_points: usize,
64}
65
66impl ClusterData {
67    /// Create a new object from longitude-latitude x and y values
68    pub fn new_geographic(lon: f64, lat: f64, source_id: ClusterId) -> Self {
69        let x = longitude_to_x(lon);
70        let y = latitude_to_y(lat);
71        Self::new_projected(x, y, source_id)
72    }
73
74    /// Create a new object from spherical mercator x and y values
75    pub fn new_projected(x: f64, y: f64, source_id: ClusterId) -> Self {
76        Self {
77            x,
78            y,
79            zoom: None,
80            source_id,
81            parent_id: None,
82            num_points: 1,
83        }
84    }
85
86    /// The x value of this point in Spherical Mercator projection
87    pub fn x(&self) -> f64 {
88        self.x
89    }
90
91    /// The y value of this point in Spherical Mercator projection
92    pub fn y(&self) -> f64 {
93        self.y
94    }
95}
96
97/// Information describing a cluster of points.
98#[derive(Clone, Debug)]
99pub struct ClusterInfo {
100    /// If this is a cluster,
101    ///
102    /// If this is not a cluster, this references the positional index of data added to
103    /// Supercluster.
104    id: ClusterId,
105
106    /// The longitude of the cluster
107    x: f64,
108
109    /// The latitude of the cluster
110    y: f64,
111
112    /// If true, references a cluster with containing data. Otherwise, is an original point that
113    /// was added to the index.
114    cluster: bool,
115
116    /// Note: this will always be 1 if `is_cluster` is false
117    point_count: usize,
118}
119
120impl From<ClusterInfo> for ClusterId {
121    fn from(value: ClusterInfo) -> Self {
122        value.id()
123    }
124}
125
126impl From<&ClusterInfo> for ClusterId {
127    fn from(value: &ClusterInfo) -> Self {
128        value.id()
129    }
130}
131
132impl ClusterInfo {
133    pub(crate) fn new_cluster(id: ClusterId, x: f64, y: f64, count: usize) -> Self {
134        Self {
135            id,
136            x: x_to_longitude(x),
137            y: y_to_latitude(y),
138            cluster: true,
139            point_count: count,
140        }
141    }
142
143    /// NOTE: here the x and y are already in the user's own coordinate system (usually lon-lat),
144    /// so no need to reproject back.
145    pub(crate) fn new_leaf(id: ClusterId, x: f64, y: f64) -> Self {
146        Self {
147            id,
148            x,
149            y,
150            cluster: false,
151            point_count: 1,
152        }
153    }
154
155    /// If this is a cluster (i.e. [`cluster()`][Self::cluster] is `true`)
156    ///
157    /// If this is not a cluster (i.e. [`cluster()`][Self::cluster] is `false`), this references
158    /// the positional index of data originall added via SuperclusterBuilder.
159    pub fn id(&self) -> ClusterId {
160        self.id
161    }
162
163    /// The longitude of the cluster
164    pub fn x(&self) -> f64 {
165        self.x
166    }
167
168    /// The latitude of the cluster
169    pub fn y(&self) -> f64 {
170        self.y
171    }
172
173    /// Whether this object represents a cluster of containing points or a single input point.
174    ///
175    /// If true, references a cluster with containing data. Otherwise, is an original point that
176    /// was added to the index.
177    pub fn is_cluster(&self) -> bool {
178        self.cluster
179    }
180
181    /// The number of points contained in this cluster
182    ///
183    /// This will always be 1 if [`is_cluster`][Self::is_cluster] is `false`.
184    pub fn count(&self) -> usize {
185        self.point_count
186    }
187}
188
189#[cfg(test)]
190mod test {
191    use super::*;
192
193    #[test]
194    fn test_get_origin_idx() {
195        let id = ClusterId::new_source_id(100);
196        let x = id.get_origin_idx(0);
197        dbg!(&x);
198    }
199}