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}