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}