Skip to main content

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}