Skip to main content

kasane_logic/geometry/point/coordinate/
mod.rs

1pub mod impls;
2
3use std::borrow::Borrow;
4
5use crate::{
6    Ecef, SingleId,
7    error::{Error, GeometryError, SpatialIdError},
8    spatial_id::constants::MAX_ZOOM_LEVEL,
9};
10
11/// 緯度・経度・高度を表す型。
12///
13/// 内部的には下記のような構造体として定義されており、空間 ID 上で扱える座標に対する制約が常に満たされる。
14///
15/// この型は `PartialOrd` を実装していますが、これは主に `BTreeSet` や `BTreeMap`
16/// といった順序付きコレクションにおける格納および探索を目的としたものであり、。空間的な位置関係における「大小」を意味するものではない。
17/// ```
18/// pub struct Coordinate {
19///     latitude: f64,
20///     longitude: f64,
21///     altitude: f64,
22/// }
23/// ```
24#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
25#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
26#[derive(Clone, Copy, PartialEq, PartialOrd)]
27pub struct Coordinate {
28    latitude: f64,
29    longitude: f64,
30    altitude: f64,
31}
32
33impl Coordinate {
34    /// 指定された緯度・経度・高度から [Coordinate] を生成する。
35    ///
36    /// 各引数は、空間 ID 上で扱える座標として有効な範囲に収まっている必要がある。
37    /// 範囲外の値が指定された場合、この関数は対応するエラーを返す。
38    ///
39    /// # 引数
40    /// * `latitude` - 緯度(-85.0511 〜 85.0511)
41    /// * `longitude` - 経度(-180.0 〜 180.0)
42    /// * `altitude` - 高度(-33,554,432.0 〜 33,554,432.0)
43    ///
44    /// # 戻り値
45    /// * 有効な値が指定された場合は `Ok(Coordinate)` を返す。
46    /// * いずれかの値が範囲外の場合は、対応する `Error` を返す。
47    ///
48    /// # Example
49    /// ```no_run
50    /// # use kasane_logic::Coordinate;
51    /// let coord = Coordinate::new(35.0, 139.0, 10.0).unwrap();
52    ///
53    /// assert_eq!(coord.latitude(), 35.0);
54    /// assert_eq!(coord.longitude(), 139.0);
55    /// assert_eq!(coord.altitude(), 10.0);
56    /// ```
57    pub fn new(latitude: f64, longitude: f64, altitude: f64) -> Result<Self, Error> {
58        if !(-85.0511..=85.0511).contains(&latitude) {
59            return Err(GeometryError::LatitudeOutOfRange { latitude }.into());
60        }
61
62        if !(-180.0..=180.0).contains(&longitude) {
63            return Err(GeometryError::LongitudeOutOfRange { longitude }.into());
64        }
65
66        if !(-33_554_432.0..=33_554_432.0).contains(&altitude) {
67            return Err(GeometryError::AltitudeOutOfRange { altitude }.into());
68        }
69
70        Ok(Self {
71            latitude,
72            longitude,
73            altitude,
74        })
75    }
76
77    /// 値の妥当性検証を行わずに `Coordinate` を生成する。
78    ///
79    /// この関数は緯度・経度・高度に対する範囲チェックを一切行わない。
80    /// 呼び出し側は、渡す値が空間 ID 上で扱える有効な範囲に収まっていることを
81    /// 保証する責任を負う。
82    ///
83    /// # Safety
84    /// この関数は `unsafe` である。
85    /// 不正な値を指定した場合、`Coordinate` が前提としている不変条件が破られ、
86    /// 以降の処理で未定義な振る舞いまたは論理的な不整合を引き起こす可能性があるため、入力値の正当性が外部で十分に検証されている場合にのみ使用せよ。
87    pub unsafe fn new_unchecked(latitude: f64, longitude: f64, altitude: f64) -> Coordinate {
88        Coordinate {
89            latitude,
90            longitude,
91            altitude,
92        }
93    }
94
95    /// 緯度を返す。
96    ///
97    /// 返される値は度数法(degree)で表され、
98    /// 常にWEBメルカトル上で扱える有効な範囲(-85.0511 〜 85.0511)内に収まる。
99    ///
100    /// # Example
101    /// ```
102    /// # use kasane_logic::Coordinate;
103    /// let coord = Coordinate::new(43.068564, 41.3507138, 30.0).unwrap();
104    /// assert_eq!(coord.latitude(), 43.068564);
105    /// ```
106    pub fn latitude(&self) -> f64 {
107        self.latitude
108    }
109
110    /// 経度を返す。
111    ///
112    /// 返される値は度数法(degree)で表され、
113    /// 常に -180.0 〜 180.0 の範囲内に収まる。
114    ///
115    /// # Example
116    /// ```
117    /// # use kasane_logic::Coordinate;
118    /// let coord = Coordinate::new(35.4095198,136.7566027, 0.0).unwrap();
119    /// assert_eq!(coord.longitude(), 136.7566027);
120    /// ```
121    pub fn longitude(&self) -> f64 {
122        self.longitude
123    }
124
125    /// 高度を返す。
126    ///
127    /// される値はメートル(m)で表され、
128    /// 常に`-33,554,432.0 ..= 33,554,432.0`の範囲内に収まる。
129    ///
130    /// # Example
131    /// ```
132    /// # use kasane_logic::Coordinate;
133    /// let coord = Coordinate::new(34.9851603, 135.7584294, 20.0).unwrap();
134    /// assert_eq!(coord.altitude(), 20.0);
135    /// ```
136    pub fn altitude(&self) -> f64 {
137        self.altitude
138    }
139
140    /// 緯度を設定する。
141    ///
142    /// 指定された値が有効な範囲外の場合、この関数はエラーを返し、
143    /// 内部の値は変更されない。
144    ///
145    /// # Example
146    /// ```
147    /// # use kasane_logic::Coordinate;
148    /// let mut coord = Coordinate::new(35.0, 41.3507138, 30.0).unwrap();
149    /// coord.set_latitude(43.068564);
150    /// assert_eq!(coord.latitude(), 43.068564);
151    /// ```
152    pub fn set_latitude(&mut self, latitude: f64) -> Result<(), Error> {
153        if !(-85.0511..=85.0511).contains(&latitude) {
154            return Err(GeometryError::LatitudeOutOfRange { latitude }.into());
155        }
156        self.latitude = latitude;
157        Ok(())
158    }
159
160    /// 経度を設定する。
161    ///
162    /// 指定された値が有効な範囲外の場合、この関数はエラーを返し、
163    /// 内部の値は変更されない。
164    ///
165    /// # Example
166    /// ```
167    /// # use kasane_logic::Coordinate;
168    /// let mut coord = Coordinate::new(35.4095198,130.0, 0.0).unwrap();
169    /// coord.set_longitude(136.7566027);
170    /// assert_eq!(coord.longitude(), 136.7566027);
171    /// ```
172    pub fn set_longitude(&mut self, longitude: f64) -> Result<(), Error> {
173        if !(-180.0..=180.0).contains(&longitude) {
174            return Err(GeometryError::LongitudeOutOfRange { longitude }.into());
175        }
176        self.longitude = longitude;
177        Ok(())
178    }
179
180    /// 高度を設定する。
181    ///
182    /// 単位はメートル(m)である。
183    /// 指定された値が有効な範囲外の場合、この関数はエラーを返し、
184    /// 内部の値は変更されない。
185    ///
186    /// # Example
187    /// ```
188    /// # use kasane_logic::Coordinate;
189    /// let mut coord = Coordinate::new(34.9851603, 135.7584294, -10.0).unwrap();
190    /// coord.set_altitude(20.0);
191    /// assert_eq!(coord.altitude(), 20.0);
192    /// ```
193    pub fn set_altitude(&mut self, altitude: f64) -> Result<(), Error> {
194        if !(-33_554_432.0..=33_554_432.0).contains(&altitude) {
195            return Err(GeometryError::AltitudeOutOfRange { altitude }.into());
196        }
197        self.altitude = altitude;
198        Ok(())
199    }
200
201    /// この座標を、指定されたズームレベルに対応する [SingleId] に変換する。
202    ///
203    /// # 引数
204    /// * `z` - 空間 ID のズームレベル
205    ///
206    /// # 戻り値
207    /// * 指定されたズームレベルに対応する [SingleId]
208    ///
209    /// # Example
210    /// ```
211    /// # use kasane_logic::{Coordinate,SingleId};
212    /// let mut coord = Coordinate::new(34.9851603, 135.7584294, 20.0).unwrap();
213    /// assert_eq!(
214    ///     &coord.single_id(24),
215    ///     &SingleId::new(24, 10, 14715409, 6646263)
216    /// )
217    /// ```
218    pub fn single_id(&self, z: u8) -> Result<SingleId, Error> {
219        if z > MAX_ZOOM_LEVEL as u8 {
220            return Err(SpatialIdError::ZOutOfRange { z }.into());
221        }
222
223        let lat = self.latitude;
224        let lon = self.longitude;
225        let alt = self.altitude;
226
227        //Z=25のとき高さはちょうど1m
228        let factor = 2_f64.powi(z as i32 - 25);
229        let f = (factor * alt).floor() as i32;
230
231        let n = 2u64.pow(z as u32) as f64;
232        let x = ((lon + 180.0) / 360.0 * n).floor() as u32;
233
234        let lat_rad = lat.to_radians();
235        let y = ((1.0 - (lat_rad.tan() + 1.0 / lat_rad.cos()).ln() / std::f64::consts::PI) / 2.0
236            * n)
237            .floor() as u32;
238
239        Ok(unsafe { SingleId::new_unchecked(z, f, x, y) })
240    }
241
242    /// 他の [`Coordinate`] との距離をメートル単位で返す。
243    ///
244    /// * 単位はメートル(m)です
245    /// * WGS84 楕円体を前提とした近似距離。
246    ///
247    /// # 引数
248    /// * `other` - 距離を計算する対象の座標
249    ///
250    /// # 戻り値
251    /// * 2 点間の距離(メートル)
252    ///
253    ///# Example
254    /// ```
255    /// # use kasane_logic::Coordinate;
256    /// let coord_tokyo = Coordinate::new(35.681382, 139.76608399999998, 0.0).unwrap();
257    /// let coord_sinagawa = Coordinate::new(35.630152, 139.74044000000004, 10.0).unwrap();
258    /// let d = coord_tokyo.distance(&coord_sinagawa);
259    /// assert_eq!(&d, &6140.165513581259);
260    /// ```
261    pub fn distance(&self, other: &Coordinate) -> f64 {
262        let e1: Ecef = (*self).into();
263        let e2: Ecef = (*other).into();
264        e1.distance(&e2)
265    }
266
267    /// [Coordinate]が同じ位置にあるかを判定します
268    /// 2点間の直線距離が epsilon 以内にあるかを判定します
269    pub fn eq_epsilon(&self, other: &Coordinate, epsilon: f64) -> bool {
270        let distance_squared = self.distance(other);
271        distance_squared < epsilon * epsilon
272    }
273
274    /// 与えられた座標群の重心を計算する。
275    ///
276    /// この関数は `IntoIterator` を受け入れるため、スライス `&[Coordinate]`、
277    /// `Vec<Coordinate>`、または `iter_coords()` などのイテレータを直接渡すことができる。
278    /// 内部で `Borrow` トレイトを利用しているため、要素が実体か参照かを問わず動作する。
279    ///
280    /// 座標群が空の場合は、`Coordinate::default()` を返す。
281    ///
282    /// # パラメーター
283    /// * `coordinates` — 重心を計算する対象となる座標の集合(イテレータ、スライス、Vecなど)
284    ///
285    /// # 動作例
286    ///
287    /// スライスからの計算:
288    /// ```
289    /// # use kasane_logic::Coordinate;
290    /// let points = [
291    ///     Coordinate::new(0.0, 0.0, 10.0).unwrap(),
292    ///     Coordinate::new(10.0, 10.0, 20.0).unwrap(),
293    /// ];
294    /// let center = Coordinate::center_gravity(&points);
295    /// assert_eq!(center.latitude(), 5.0);
296    /// ```
297    pub fn center_gravity<I>(coordinates: I) -> Coordinate
298    where
299        I: IntoIterator,
300        I::Item: Borrow<Coordinate>,
301    {
302        let mut sum_lat = 0.0;
303        let mut sum_lon = 0.0;
304        let mut sum_alt = 0.0;
305        let mut count = 0usize;
306
307        for coord in coordinates {
308            let c = coord.borrow();
309            sum_lat += c.latitude;
310            sum_lon += c.longitude;
311            sum_alt += c.altitude;
312            count += 1;
313        }
314
315        if count == 0 {
316            return Self::default();
317        }
318
319        let n = count as f64;
320        // 重心は元の座標の凸包内に必ず収まるため、バリデーションをスキップ
321        unsafe { Self::new_unchecked(sum_lat / n, sum_lon / n, sum_alt / n) }
322    }
323}