kasane_logic/spatial_id/range_id/mod.rs
1pub mod constructor;
2pub mod convert;
3pub mod impls;
4pub mod random;
5
6use crate::{
7 SpatialId, SpatialIdError, TemporalId,
8 error::Error,
9 spatial_id::{
10 constants::{F_MAX, F_MIN, MAX_ZOOM_LEVEL, XY_MAX},
11 helpers,
12 },
13};
14
15/// RangeIdは空間IDの範囲表現を表す型です。
16///
17/// 各インデックスを範囲で指定することができます。各次元の範囲を表す配列の順序には意味を持ちません。内部的には下記のような構造体で構成されており、各フィールドをプライベートにすることで、ズームレベルに依存するインデックス範囲やその他のバリデーションを適切に適用することができます。
18///
19/// この型は `PartialOrd` / `Ord` を実装していますが、これは主に`BTreeSet` や `BTreeMap` などの順序付きコレクションでの格納・探索用です。実際の空間的な「大小」を意味するものではありません。
20///
21/// ```
22/// pub struct RangeId {
23/// z: u8,
24/// f: [i32; 2],
25/// x: [u32; 2],
26/// y: [u32; 2],
27/// }
28/// ```
29#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
30#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
31#[derive(Debug, PartialEq, Eq, Hash, Clone, PartialOrd, Ord)]
32pub struct RangeId {
33 z: u8,
34 f: [i32; 2],
35 x: [u32; 2],
36 y: [u32; 2],
37 temporal_id: TemporalId,
38}
39
40impl RangeId {
41 /// この `RangeId` が保持しているズームレベル `z` を返します。
42 ///
43 /// ```
44 /// # use kasane_logic::RangeId;
45 /// # use kasane_logic::Error;
46 /// let id = RangeId::new(5, [-3,29], [8,9], [5,10]).unwrap();
47 /// assert_eq!(id.z(), 5u8);
48 /// ```
49 pub fn z(&self) -> u8 {
50 self.z
51 }
52
53 /// この `RangeId` が保持しているズームレベル `[f1,f2]` を返します。
54 ///
55 /// ```
56 /// # use kasane_logic::RangeId;
57 /// # use kasane_logic::Error;
58 /// let id = RangeId::new(5, [-3,29], [8,9], [5,10]).unwrap();
59 /// assert_eq!(id.f(), [-3i32,29i32]);
60 /// ```
61 pub fn f(&self) -> [i32; 2] {
62 self.f
63 }
64
65 /// この `RangeId` が保持しているズームレベル `[x1,x2]` を返します。
66 ///
67 /// ```
68 /// # use kasane_logic::RangeId;
69 /// # use kasane_logic::Error;
70 /// let id = RangeId::new(5, [-3,29], [8,9], [5,10]).unwrap();
71 /// assert_eq!(id.x(), [8u32,9u32]);
72 /// ```
73 pub fn x(&self) -> [u32; 2] {
74 self.x
75 }
76
77 /// この `RangeId` が保持しているズームレベル `[y1,y2]` を返します。
78 ///
79 /// ```
80 /// # use kasane_logic::RangeId;
81 /// # use kasane_logic::Error;
82 /// let id = RangeId::new(5, [-3,29], [8,9], [5,10]).unwrap();
83 /// assert_eq!(id.y(), [5u32,10u32]);
84 /// ```
85 pub fn y(&self) -> [u32; 2] {
86 self.y
87 }
88
89 pub fn set_f(&mut self, value: [i32; 2]) -> Result<(), Error> {
90 let z = self.z;
91 let mut value = value;
92 let f_min = F_MIN[z as usize];
93 let f_max = F_MAX[z as usize];
94
95 for i in 0..2 {
96 if value[i] < f_min || value[i] > f_max {
97 return Err(SpatialIdError::FOutOfRange { f: value[i], z }.into());
98 }
99 }
100
101 if value[0] > value[1] {
102 value.swap(0, 1);
103 }
104
105 self.f = value;
106 Ok(())
107 }
108
109 pub fn set_x(&mut self, value: [u32; 2]) -> Result<(), Error> {
110 let z = self.z;
111 let xy_max = XY_MAX[z as usize];
112
113 for i in 0..2 {
114 if value[i] > xy_max {
115 return Err(SpatialIdError::XOutOfRange { x: value[i], z }.into());
116 }
117 }
118
119 self.x = value;
120 Ok(())
121 }
122
123 pub fn set_y(&mut self, value: [u32; 2]) -> Result<(), Error> {
124 let z = self.z;
125 let mut value = value;
126 let xy_max = XY_MAX[z as usize];
127
128 for i in 0..2 {
129 if value[i] > xy_max {
130 return Err(SpatialIdError::YOutOfRange { y: value[i], z }.into());
131 }
132 }
133
134 if value[0] > value[1] {
135 value.swap(0, 1);
136 }
137
138 self.y = value;
139 Ok(())
140 }
141
142 /// 指定したズームレベル `target_z` に細分化した、この `RangeId` を含むすべての子 `RangeId` を生成します。
143 ///
144 /// # パラメータ
145 /// * `target_z` — 生成したい子 `RangeId` のズームレベル
146 ///
147 /// # バリデーション
148 /// - `target_z` が現在のズームレベルより浅い場合は、[`SpatialIdError::ZoomLevelTransitionOutOfRange`] を返します。
149 /// - `target_z` が本クレートで扱える最大ズームレベルを超える場合は、[`SpatialIdError::ZOutOfRange`] を返します。
150 ///
151 /// 1段深いズームへの細分化
152 /// ```
153 /// # use kasane_logic::RangeId;
154 /// # use kasane_logic::Error;
155 /// let id = RangeId::new(5, [-3,29], [8,9], [5,10]).unwrap();
156 /// let result = id.spatial_children_at_zoom(6).unwrap();
157 /// assert_eq!(result, RangeId::new(6, [-6, 59], [16, 19], [10, 21] ).unwrap());
158 ///
159 /// ```
160 ///
161 /// 現在より浅いズームを指定した場合
162 /// ```
163 /// # use kasane_logic::{Error, RangeId, SpatialIdError};
164 /// let id = RangeId::new(5, [-3,29], [8,9], [5,10]).unwrap();
165 /// let result = id.spatial_children_at_zoom(4);
166 /// assert!(matches!(result, Err(Error::SpatialId(SpatialIdError::ZoomLevelTransitionOutOfRange { current_z: 5, target_z: 4 }))));
167 /// ```
168 pub fn spatial_children_at_zoom(&self, target_z: u8) -> Result<RangeId, Error> {
169 if target_z < self.z {
170 return Err(SpatialIdError::ZoomLevelTransitionOutOfRange {
171 current_z: self.z,
172 target_z,
173 }
174 .into());
175 }
176
177 if target_z as usize > MAX_ZOOM_LEVEL {
178 return Err(SpatialIdError::ZOutOfRange { z: target_z }.into());
179 }
180
181 let difference = target_z - self.z;
182 let scale_f = 2_i32.pow(difference as u32);
183 let scale_xy = 2_u32.pow(difference as u32);
184
185 let f = helpers::scale_range_i32(self.f[0], self.f[1], scale_f);
186 let x = helpers::scale_range_u32(self.x[0], self.x[1], scale_xy);
187 let y = helpers::scale_range_u32(self.y[0], self.y[1], scale_xy);
188
189 Ok(RangeId {
190 z: target_z,
191 f,
192 x,
193 y,
194
195 temporal_id: self.temporal().clone(),
196 })
197 }
198
199 /// 指定したズームレベル `target_z` に縮約した、この `RangeId` の親 `RangeId` を返します。
200 ///
201 /// # パラメータ
202 /// * `target_z` — 取得したい親 `RangeId` のズームレベル
203 ///
204 /// # バリデーション
205 /// - `target_z` が現在のズームレベルより深い場合は、[`SpatialIdError::ZoomLevelTransitionOutOfRange`] を返します。
206 /// - `target_z` が本クレートで扱える最大ズームレベルを超える場合は、[`SpatialIdError::ZOutOfRange`] を返します。
207 ///
208 /// 1段浅いズームへの縮約
209 /// ```
210 /// # use kasane_logic::RangeId;
211 /// # use kasane_logic::Error;
212 /// let id = RangeId::new(5, [1,29], [8,9], [5,10]).unwrap();
213 /// let parent = id.spatial_parent_at_zoom(4).unwrap();
214 ///
215 /// assert_eq!(parent.z(), 4);
216 /// assert_eq!(parent.f(), [0,14]);
217 /// assert_eq!(parent.x(), [4,4]);
218 /// assert_eq!(parent.y(), [2,5]);
219 /// ```
220 ///
221 /// Fが負の場合の挙動:
222 /// ```
223 /// # use kasane_logic::RangeId;
224 /// # use kasane_logic::Error;
225 /// let id = RangeId::new(5, [-10,-5], [8,9], [5,10]).unwrap();
226 ///
227 /// let parent = id.spatial_parent_at_zoom(4).unwrap();
228 ///
229 /// assert_eq!(parent.z(), 4);
230 /// assert_eq!(parent.f(), [-5,-3]);
231 /// assert_eq!(parent.x(), [4,4]);
232 /// assert_eq!(parent.y(), [2,5]);
233 /// ```
234 ///
235 /// 現在より深いズームを指定した場合:
236 /// ```
237 /// # use kasane_logic::{Error, RangeId, SpatialIdError};
238 /// let id = RangeId::new(5, [-10,-5], [8,9], [5,10]).unwrap();
239 /// let result = id.spatial_parent_at_zoom(6);
240 /// assert!(matches!(result, Err(Error::SpatialId(SpatialIdError::ZoomLevelTransitionOutOfRange { current_z: 5, target_z: 6 }))));
241 /// ```
242 pub fn spatial_parent_at_zoom(&self, target_z: u8) -> Result<RangeId, Error> {
243 if target_z > self.z {
244 return Err(SpatialIdError::ZoomLevelTransitionOutOfRange {
245 current_z: self.z,
246 target_z,
247 }
248 .into());
249 }
250
251 if target_z as usize > MAX_ZOOM_LEVEL {
252 return Err(SpatialIdError::ZOutOfRange { z: target_z }.into());
253 }
254
255 let shift = (self.z - target_z) as u32;
256
257 let f = [
258 if self.f[0] == -1 {
259 -1
260 } else {
261 self.f[0] >> shift
262 },
263 if self.f[1] == -1 {
264 -1
265 } else {
266 self.f[1] >> shift
267 },
268 ];
269
270 let x = [self.x[0] >> shift, self.x[1] >> shift];
271 let y = [self.y[0] >> shift, self.y[1] >> shift];
272
273 Ok(RangeId {
274 z: target_z,
275 f,
276 x,
277 y,
278
279 temporal_id: self.temporal().clone(),
280 })
281 }
282}