cheap_ruler/lib.rs
1//! # cheap-ruler
2//!
3//! A collection of very fast approximations to common geodesic measurements.
4//! Useful for performance-sensitive code that measures things on a city scale.
5//!
6//! This is a port of the cheap-ruler JS library and cheap-ruler-cpp C++ library
7//! into safe Rust.
8//!
9//! Note: WGS84 ellipsoid is used instead of the Clarke 1866 parameters used by
10//! the FCC formulas. See cheap-ruler-cpp#13 for more information.
11
12// Temporarily permit geo_types::Coordinate until geo-types 0.8
13#![allow(deprecated)]
14
15#[macro_use]
16extern crate geo_types;
17
18use geo_types::{Coordinate, LineString, Point, Polygon};
19use num_traits::Float;
20use std::f64;
21use std::fmt;
22use std::iter;
23use std::mem;
24
25mod distance_unit;
26mod point_on_line;
27mod rect;
28
29pub use distance_unit::DistanceUnit;
30pub use point_on_line::PointOnLine;
31pub use rect::Rect;
32
33const RE: f64 = 6378.137; // equatorial radius in km
34const FE: f64 = 1.0 / 298.257223563; // flattening
35const E2: f64 = FE * (2.0 - FE);
36
37/// A collection of very fast approximations to common geodesic measurements.
38/// Useful for performance-sensitive code that measures things on a city scale.
39/// Point coordinates are in the [x = longitude, y = latitude] form.
40#[allow(clippy::derive_partial_eq_without_eq)]
41#[derive(Debug, PartialEq, Clone)]
42pub struct CheapRuler<T>
43where
44 T: Float + fmt::Debug,
45{
46 kx: T,
47 ky: T,
48}
49
50impl<T> CheapRuler<T>
51where
52 T: Float + fmt::Debug,
53{
54 pub fn new(latitude: T, distance_unit: DistanceUnit) -> Self {
55 let one = T::one();
56 let e2 = T::from(E2).unwrap();
57
58 // Curvature formulas from https://en.wikipedia.org/wiki/Earth_radius#Meridional
59 let coslat = latitude.to_radians().cos();
60 let w2 = one / (one - e2 * (one - coslat * coslat));
61 let w = w2.sqrt();
62
63 // multipliers for converting longitude and latitude degrees into distance
64 let dkx = w * coslat; // based on normal radius of curvature
65 let dky = w * w2 * (one - e2); // based on meridonal radius of curvature
66
67 let (kx, ky) = calculate_multipliers(distance_unit, dkx, dky);
68
69 Self { kx, ky }
70 }
71
72 /// Creates a ruler object from tile coordinates (y and z). Convenient in
73 /// tile-reduce scripts
74 ///
75 /// # Arguments
76 ///
77 /// * `y` - y
78 /// * `z` - z
79 /// * `distance_unit` - Unit to express distances in
80 ///
81 /// # Examples
82 ///
83 /// ```
84 /// use cheap_ruler::{CheapRuler, DistanceUnit};
85 /// let cr = CheapRuler::<f64>::from_tile(1567, 12, DistanceUnit::Meters);
86 /// ```
87 pub fn from_tile(y: u32, z: u32, distance_unit: DistanceUnit) -> Self {
88 assert!(z < 32);
89
90 let n = T::from(f64::consts::PI).unwrap()
91 * (T::one()
92 - T::from(2.0).unwrap()
93 * (T::from(y).unwrap() + T::from(0.5).unwrap())
94 / T::from(1u32 << z).unwrap());
95 let latitude = n.sinh().atan().to_degrees();
96
97 Self::new(latitude, distance_unit)
98 }
99
100 /// Calculates the square of the approximate distance between two
101 /// geographical points
102 ///
103 /// # Arguments
104 ///
105 /// * `a` - First point
106 /// * `b` - Second point
107 pub fn square_distance(&self, a: &Point<T>, b: &Point<T>) -> T {
108 let dx = long_diff(a.x(), b.x()) * self.kx;
109 let dy = (a.y() - b.y()) * self.ky;
110 dx.powi(2) + dy.powi(2)
111 }
112
113 /// Calculates the approximate distance between two geographical points
114 ///
115 /// # Arguments
116 ///
117 /// * `a` - First point
118 /// * `b` - Second point
119 ///
120 /// # Examples
121 ///
122 /// ```
123 /// use cheap_ruler::{CheapRuler, DistanceUnit};
124 /// let cr = CheapRuler::new(44.7192003, DistanceUnit::Meters);
125 /// let dist = cr.distance(
126 /// &(14.8901816, 44.7209699).into(),
127 /// &(14.8905188, 44.7209699).into()
128 /// );
129 /// assert!(dist < 38.0);
130 /// ```
131 pub fn distance(&self, a: &Point<T>, b: &Point<T>) -> T {
132 self.square_distance(a, b).sqrt()
133 }
134
135 /// Returns the bearing between two points in angles
136 ///
137 /// # Arguments
138 ///
139 /// * `a` - First point
140 /// * `b` - Second point
141 ///
142 /// # Examples
143 ///
144 /// ```
145 /// use cheap_ruler::{CheapRuler, DistanceUnit};
146 /// let cr = CheapRuler::new(44.7192003, DistanceUnit::Meters);
147 /// let bearing = cr.bearing(
148 /// &(14.8901816, 44.7209699).into(),
149 /// &(14.8905188, 44.7209699).into()
150 /// );
151 /// assert_eq!(bearing, 90.0);
152 /// ```
153 pub fn bearing(&self, a: &Point<T>, b: &Point<T>) -> T {
154 let dx = long_diff(b.x(), a.x()) * self.kx;
155 let dy = (b.y() - a.y()) * self.ky;
156
157 dx.atan2(dy).to_degrees()
158 }
159
160 /// Returns a new point given distance and bearing from the starting point
161 ///
162 /// # Arguments
163 ///
164 /// * `origin` - origin point
165 /// * `dist` - distance
166 /// * `bearing` - bearing
167 ///
168 /// # Examples
169 ///
170 /// ```
171 /// use cheap_ruler::{CheapRuler, DistanceUnit};
172 /// let cr = CheapRuler::new(44.7192003, DistanceUnit::Meters);
173 /// let p1 = (14.8901816, 44.7209699).into();
174 /// let p2 = (14.8905188, 44.7209699).into();
175 /// let dist = cr.distance(&p1, &p2);
176 /// let bearing = cr.bearing(&p1, &p2);
177 /// let destination = cr.destination(&p1, dist, bearing);
178 ///
179 /// assert_eq!(destination.x(), p2.x());
180 /// assert_eq!(destination.y(), p2.y());
181 /// ```
182 pub fn destination(
183 &self,
184 origin: &Point<T>,
185 dist: T,
186 bearing: T,
187 ) -> Point<T> {
188 let a = bearing.to_radians();
189 self.offset(origin, a.sin() * dist, a.cos() * dist)
190 }
191
192 /// Returns a new point given easting and northing offsets (in ruler units)
193 /// from the starting point
194 ///
195 /// # Arguments
196 ///
197 /// * `origin` - point
198 /// * `dx` - easting
199 /// * `dy` - northing
200 pub fn offset(&self, origin: &Point<T>, dx: T, dy: T) -> Point<T> {
201 (origin.x() + dx / self.kx, origin.y() + dy / self.ky).into()
202 }
203
204 /// Given a line (an array of points), returns the total line distance.
205 ///
206 /// # Arguments
207 ///
208 /// * `points` - line of points
209 ///
210 /// # Example
211 ///
212 /// ```
213 /// use cheap_ruler::{CheapRuler, DistanceUnit};
214 /// use geo_types::LineString;
215 /// let cr = CheapRuler::new(50.458, DistanceUnit::Meters);
216 /// let line_string: LineString<f64> = vec![
217 /// (-67.031, 50.458),
218 /// (-67.031, 50.534),
219 /// (-66.929, 50.534),
220 /// (-66.929, 50.458)
221 /// ].into();
222 /// let length = cr.line_distance(&line_string);
223 /// ```
224 pub fn line_distance(&self, points: &LineString<T>) -> T {
225 let line_iter = points.0.iter().copied();
226
227 let left = iter::once(None).chain(line_iter.clone().map(Some));
228 left.zip(line_iter)
229 .map(|(a, b)| match a {
230 Some(a) => self.distance(&a.into(), &b.into()),
231 None => T::zero(),
232 })
233 // avoided using Iterator's sum() so that we don't have to require T
234 // to implement std::iter::Sum.
235 .fold(T::zero(), |acc, x| acc + x)
236 }
237
238 /// Given a polygon returns the area
239 ///
240 /// * `polygon` - Polygon
241 pub fn area(&self, polygon: &Polygon<T>) -> T {
242 let exterior_sum =
243 sum_area(&polygon.exterior().points().collect::<Vec<Point<T>>>());
244 let interiors_sum = polygon
245 .interiors()
246 .iter()
247 .map(|interior| {
248 sum_area(&interior.points().collect::<Vec<Point<T>>>())
249 })
250 .fold(T::zero(), |acc, x| acc + x);
251 let sum = exterior_sum - interiors_sum;
252 (sum.abs() / T::from(2.0).unwrap()) * self.kx * self.ky
253 }
254
255 /// Returns the point at a specified distance along the line
256 ///
257 /// # Arguments
258 ///
259 /// * `line` - Line
260 /// * `dist` - Distance along the line
261 pub fn along(&self, line: &LineString<T>, dist: T) -> Option<Point<T>> {
262 let line_len = line.0.len();
263 if line_len == 0 {
264 return None;
265 }
266
267 if dist <= T::zero() {
268 return Some(line[0].into());
269 }
270
271 let last_index = line_len - 1;
272 let mut sum = T::zero();
273 for i in 0..last_index {
274 let p0 = &line[i].into();
275 let p1 = &line[i + 1].into();
276 let d = self.distance(p0, p1);
277 sum = sum + d;
278 if sum > dist {
279 return Some(interpolate(p0, p1, (dist - (sum - d)) / d));
280 }
281 }
282 Some(line[last_index].into())
283 }
284
285 /// Returns the shortest distance between a point and a line segment given
286 /// with two points.
287 ///
288 /// # Arguments
289 ///
290 /// * `p` - Point to calculate the distance from
291 /// * `start` - Start point of line segment
292 /// * `end` - End point of line segment
293 pub fn point_to_segment_distance(
294 &self,
295 p: &Point<T>,
296 start: &Point<T>,
297 end: &Point<T>,
298 ) -> T {
299 let zero = T::zero();
300 let mut x = start.x();
301 let mut y = start.y();
302 let dx = long_diff(end.x(), x) * self.kx;
303 let dy = (end.y() - y) * self.ky;
304
305 if dx != zero || dy != zero {
306 let t = (long_diff(p.x(), x) * self.kx * dx
307 + (p.y() - y) * self.ky * dy)
308 / (dx * dx + dy * dy);
309 if t > T::one() {
310 x = end.x();
311 y = end.y();
312 } else if t > zero {
313 x = x + (dx / self.kx) * t;
314 y = y + (dy / self.ky) * t;
315 }
316 }
317 self.distance(p, &point!(x: x, y: y))
318 }
319
320 /// Returns a tuple of the form (point, index, t) where point is closest
321 /// point on the line from the given point, index is the start index of the
322 /// segment with the closest point, and t is a parameter from 0 to 1 that
323 /// indicates where the closest point is on that segment
324 ///
325 /// # Arguments
326 ///
327 /// * `line` - Line to compare with point
328 /// * `point` - Point to calculate the closest point on the line
329 pub fn point_on_line(
330 &self,
331 line: &LineString<T>,
332 point: &Point<T>,
333 ) -> Option<PointOnLine<T>> {
334 let zero = T::zero();
335 let mut min_dist = T::infinity();
336 let mut min_x = zero;
337 let mut min_y = zero;
338 let mut min_i = 0;
339 let mut min_t = zero;
340
341 let line_len = line.0.len();
342 if line_len == 0 {
343 return None;
344 }
345
346 for i in 0..line_len - 1 {
347 let mut t = zero;
348 let mut x = line[i].x;
349 let mut y = line[i].y;
350 let dx = long_diff(line[i + 1].x, x) * self.kx;
351 let dy = (line[i + 1].y - y) * self.ky;
352
353 if dx != zero || dy != zero {
354 t = (long_diff(point.x(), x) * self.kx * dx
355 + (point.y() - y) * self.ky * dy)
356 / (dx * dx + dy * dy);
357
358 if t > T::one() {
359 x = line[i + 1].x;
360 y = line[i + 1].y;
361 } else if t > zero {
362 x = x + (dx / self.kx) * t;
363 y = y + (dy / self.ky) * t;
364 }
365 }
366
367 let d2 = self.square_distance(point, &point!(x: x, y: y));
368
369 if d2 < min_dist {
370 min_dist = d2;
371 min_x = x;
372 min_y = y;
373 min_i = i;
374 min_t = t;
375 }
376 }
377
378 Some(PointOnLine::new(
379 point!(x: min_x, y: min_y),
380 min_i,
381 T::zero().max(T::one().min(min_t)),
382 ))
383 }
384
385 /// Returns a part of the given line between the start and the stop points
386 /// (or their closest points on the line)
387 ///
388 /// # Arguments
389 ///
390 /// * `start` - Start point
391 /// * `stop` - Stop point
392 /// * `line` - Line string
393 pub fn line_slice(
394 &self,
395 start: &Point<T>,
396 stop: &Point<T>,
397 line: &LineString<T>,
398 ) -> LineString<T> {
399 let pol1 = self.point_on_line(line, start);
400 let pol2 = self.point_on_line(line, stop);
401
402 if pol1.is_none() || pol2.is_none() {
403 return line_string![];
404 }
405 let mut pol1 = pol1.unwrap();
406 let mut pol2 = pol2.unwrap();
407
408 if pol1.index() > pol2.index()
409 || pol1.index() == pol2.index() && pol1.t() > pol2.t()
410 {
411 mem::swap(&mut pol1, &mut pol2);
412 }
413
414 let mut slice = vec![pol1.point()];
415
416 let l = pol1.index() + 1;
417 let r = pol2.index();
418
419 if line[l] != slice[0].into() && l <= r {
420 slice.push(line[l].into());
421 }
422
423 let mut i = l + 1;
424 while i <= r {
425 slice.push(line[i].into());
426 i += 1;
427 }
428
429 if line[r] != pol2.point().into() {
430 slice.push(pol2.point());
431 }
432
433 slice.into()
434 }
435
436 /// Returns a part of the given line between the start and the stop points
437 /// indicated by distance along the line
438 ///
439 /// * `start` - Start distance
440 /// * `stop` - Stop distance
441 /// * `line` - Line string
442 pub fn line_slice_along(
443 &self,
444 start: T,
445 stop: T,
446 line: &LineString<T>,
447 ) -> LineString<T> {
448 let mut sum = T::zero();
449 let mut slice = vec![];
450
451 let line_len = line.0.len();
452 if line_len == 0 {
453 return slice.into();
454 }
455
456 for i in 0..line_len - 1 {
457 let p0 = line[i].into();
458 let p1 = line[i + 1].into();
459 let d = self.distance(&p0, &p1);
460
461 sum = sum + d;
462
463 if sum > start && slice.is_empty() {
464 slice.push(interpolate(&p0, &p1, (start - (sum - d)) / d));
465 }
466
467 if sum >= stop {
468 slice.push(interpolate(&p0, &p1, (stop - (sum - d)) / d));
469 return slice.into();
470 }
471
472 if sum > start {
473 slice.push(p1);
474 }
475 }
476
477 slice.into()
478 }
479
480 /// Given a point, returns a bounding rectangle created from the given point
481 /// buffered by a given distance
482 ///
483 /// # Arguments
484 ///
485 /// * `p` - Point
486 /// * `buffer` - Buffer distance
487 pub fn buffer_point(&self, p: &Point<T>, buffer: T) -> Rect<T> {
488 let v = buffer / self.ky;
489 let h = buffer / self.kx;
490
491 Rect::new(
492 Coordinate {
493 x: p.x() - h,
494 y: p.y() - v,
495 },
496 Coordinate {
497 x: p.x() + h,
498 y: p.y() + v,
499 },
500 )
501 }
502
503 /// Given a bounding box, returns the box buffered by a given distance
504 ///
505 /// # Arguments
506 ///
507 /// * `bbox` - Bounding box
508 /// * `buffer` - Buffer distance
509 pub fn buffer_bbox(&self, bbox: &Rect<T>, buffer: T) -> Rect<T> {
510 let v = buffer / self.ky;
511 let h = buffer / self.kx;
512
513 Rect::new(
514 Coordinate {
515 x: bbox.min().x - h,
516 y: bbox.min().y - v,
517 },
518 Coordinate {
519 x: bbox.max().x + h,
520 y: bbox.max().y + v,
521 },
522 )
523 }
524
525 /// Returns true if the given point is inside in the given bounding box,
526 /// otherwise false.
527 ///
528 /// # Arguments
529 ///
530 /// * `p` - Point
531 /// * `bbox` - Bounding box
532 pub fn inside_bbox(&self, p: &Point<T>, bbox: &Rect<T>) -> bool {
533 p.y() >= bbox.min().y
534 && p.y() <= bbox.max().y
535 && long_diff(p.x(), bbox.min().x) >= T::zero()
536 && long_diff(p.x(), bbox.max().x) <= T::zero()
537 }
538}
539
540pub fn interpolate<T: Float + fmt::Debug>(
541 a: &Point<T>,
542 b: &Point<T>,
543 t: T,
544) -> Point<T> {
545 let dx = long_diff(b.x(), a.x());
546 let dy = b.y() - a.y();
547 Point::new(a.x() + dx * t, a.y() + dy * t)
548}
549
550fn calculate_multipliers<T: Float>(
551 distance_unit: DistanceUnit,
552 dkx: T,
553 dky: T,
554) -> (T, T) {
555 let re = T::from(RE).unwrap();
556 let mul = distance_unit
557 .conversion_factor_kilometers::<T>()
558 .to_radians()
559 * re;
560 let kx = mul * dkx;
561 let ky = mul * dky;
562 (kx, ky)
563}
564
565fn long_diff<T: Float>(a: T, b: T) -> T {
566 let threesixty = T::from(360).unwrap();
567 let diff = a - b;
568 diff - ((diff / threesixty).round() * threesixty)
569}
570
571fn sum_area<T: Float + fmt::Debug>(line: &[Point<T>]) -> T {
572 let line_len = line.len();
573 let mut sum = T::zero();
574 let mut k = line_len - 1;
575 for j in 0..line_len {
576 sum = sum + (line[j].x() - line[k].x()) * (line[j].y() + line[k].y());
577 k = j;
578 }
579 sum
580}