Allow wrapping and update test data to include previously out-of-bounds lat/lon
This commit is contained in:
parent
4c8a73029b
commit
e31bd12a3b
@ -1,3 +1,4 @@
|
||||
mod wrap;
|
||||
use std::ops::RangeInclusive;
|
||||
|
||||
use s2::{cellid::CellID, latlng::LatLng};
|
||||
@ -8,14 +9,16 @@ pub(crate) const THIRTEEN_BITS: u64 = 0b1_1111_1111_1111;
|
||||
use crate::Error;
|
||||
|
||||
pub fn lat_lon_to_cellid(lat: f64, lon: f64) -> Result<CellID, Error> {
|
||||
let latlng = LatLng::from_degrees(lat, lon);
|
||||
// Wrap the latitudes and longitudes
|
||||
eprintln!("Pre-lat: {lat},{lon}");
|
||||
let (lat, lon) = wrap::wrap_latlon(lat, lon);
|
||||
eprintln!("Post-lat: {lat},{lon}");
|
||||
|
||||
// We don't want to entertain any invalid latitudes or longitudes
|
||||
if latlng.normalized() == latlng {
|
||||
Ok(Into::<CellID>::into(latlng))
|
||||
} else {
|
||||
Err(Error::InvalidLatLng)
|
||||
if !lat.is_finite() || !lon.is_finite() {
|
||||
return Err(Error::NaNLatLng);
|
||||
}
|
||||
|
||||
Ok(CellID::from(LatLng::from_degrees(lat, lon)))
|
||||
}
|
||||
|
||||
pub fn extract_binary(number: u64, range: RangeInclusive<usize>) -> u64 {
|
||||
|
139
src/conversions/wrap.rs
Normal file
139
src/conversions/wrap.rs
Normal file
@ -0,0 +1,139 @@
|
||||
/// Normalize co-ordinates that lie outside of the normal ranges.
|
||||
/// Longitude wrapping simply requires adding +- 360 to the value until it comes into range.
|
||||
///
|
||||
/// For the latitude values, we need to flip the longitude whenever the latitude crosses a pole.
|
||||
///
|
||||
/// Description and original source was ported from https://gist.github.com/missinglink/d0a085188a8eab2ca66db385bb7c023a to rust
|
||||
pub fn wrap_latlon(mut lat: f64, mut lon: f64) -> (f64, f64) {
|
||||
let quadrant = ((lat.abs() / 90_f64).floor() % 4_f64) as i8;
|
||||
let pole = if lat > 0_f64 { 90_f64 } else { -90_f64 };
|
||||
let offset = lat % 90_f64;
|
||||
|
||||
match quadrant {
|
||||
0 => {
|
||||
lat = offset;
|
||||
}
|
||||
1 => {
|
||||
lat = pole - offset;
|
||||
lon += 180_f64
|
||||
}
|
||||
2 => {
|
||||
lat = -offset;
|
||||
lon += 180_f64;
|
||||
}
|
||||
3 => {
|
||||
lat = -pole + offset;
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
if lon > 180_f64 || lon < -180_f64 {
|
||||
lon -= ((lon + 180_f64) / 360_f64).floor() * 360_f64;
|
||||
}
|
||||
|
||||
(lat, lon)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_control() {
|
||||
// Control - no wrapping required
|
||||
assert_eq!(
|
||||
wrap_latlon(55.555_f64, 22.222_f64),
|
||||
(55.555_f64, 22.222_f64)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_latitude_positive() {
|
||||
// Positive latitude wrapping - 1 degree
|
||||
assert_eq!(wrap_latlon(1_f64, 0_f64), (1_f64, 0_f64));
|
||||
|
||||
// Positive latitude wrapping - 91 degrees
|
||||
assert_eq!(wrap_latlon(91_f64, 0_f64), (89_f64, 180_f64));
|
||||
|
||||
// Positive latitude wrapping - 181 degrees
|
||||
assert_eq!(wrap_latlon(181_f64, 0_f64), (-1_f64, 180_f64));
|
||||
|
||||
// Positive latitude wrapping - 271 degrees
|
||||
assert_eq!(wrap_latlon(271_f64, 0_f64), (-89_f64, 0_f64));
|
||||
|
||||
// Positive latitude wrapping - 361 degrees
|
||||
assert_eq!(wrap_latlon(361_f64, 0_f64), (1_f64, 0_f64));
|
||||
|
||||
// Positive latitude wrapping - 631 degrees
|
||||
assert_eq!(wrap_latlon(631_f64, 0_f64), (-89_f64, 0_f64));
|
||||
|
||||
// Positive latitude wrapping - 721 degrees
|
||||
assert_eq!(wrap_latlon(721_f64, 0_f64), (1_f64, 0_f64));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_latitude_negative() {
|
||||
// Negative latitude wrapping - 1 degree
|
||||
assert_eq!(wrap_latlon(-1_f64, 0_f64), (-1_f64, 0_f64));
|
||||
|
||||
// Negative latitude wrapping - 91 degrees
|
||||
assert_eq!(wrap_latlon(-91_f64, 0_f64), (-89_f64, 180_f64));
|
||||
|
||||
// Negative latitude wrapping - 181 degrees
|
||||
assert_eq!(wrap_latlon(-181_f64, 0_f64), (1_f64, 180_f64));
|
||||
|
||||
// Negative latitude wrapping - 271 degrees
|
||||
assert_eq!(wrap_latlon(-271_f64, 0_f64), (89_f64, 0_f64));
|
||||
|
||||
// Negative latitude wrapping - 361 degrees
|
||||
assert_eq!(wrap_latlon(-361_f64, 0_f64), (-1_f64, 0_f64));
|
||||
|
||||
// Negative latitude wrapping - 631 degrees
|
||||
assert_eq!(wrap_latlon(-631_f64, 0_f64), (89_f64, 0_f64));
|
||||
|
||||
// Positive latitude wrapping - 721 degrees
|
||||
assert_eq!(wrap_latlon(721_f64, 0_f64), (1_f64, 0_f64));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_longitude_positive() {
|
||||
// Positive longitude wrapping - 1 degree
|
||||
assert_eq!(wrap_latlon(0_f64, 1_f64), (0_f64, 1_f64));
|
||||
|
||||
// Positive longitude wrapping - 181 degrees
|
||||
assert_eq!(wrap_latlon(0_f64, 181_f64), (0_f64, -179_f64));
|
||||
|
||||
// Positive longitude wrapping - 271 degrees
|
||||
assert_eq!(wrap_latlon(0_f64, 271_f64), (0_f64, -89_f64));
|
||||
|
||||
// Positive longitude wrapping - 361 degrees
|
||||
assert_eq!(wrap_latlon(0_f64, 361_f64), (0_f64, 1_f64));
|
||||
|
||||
// Positive longitude wrapping - 631 degrees
|
||||
assert_eq!(wrap_latlon(0_f64, 631_f64), (0_f64, -89_f64));
|
||||
|
||||
// Positive longitude wrapping - 721 degrees
|
||||
assert_eq!(wrap_latlon(0_f64, 721_f64), (0_f64, 1_f64));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_longitude_negative() {
|
||||
// Negative longitude wrapping - 1 degree
|
||||
assert_eq!(wrap_latlon(0_f64, -1_f64), (0_f64, -1_f64));
|
||||
|
||||
// Negative longitude wrapping - 181 degrees
|
||||
assert_eq!(wrap_latlon(0_f64, -181_f64), (0_f64, 179_f64));
|
||||
|
||||
// Negative longitude wrapping - 271 degrees
|
||||
assert_eq!(wrap_latlon(0_f64, -271_f64), (0_f64, 89_f64));
|
||||
|
||||
// Negative longitude wrapping - 361 degrees
|
||||
assert_eq!(wrap_latlon(0_f64, -361_f64), (0_f64, -1_f64));
|
||||
|
||||
// Negative longitude wrapping - 631 degrees
|
||||
assert_eq!(wrap_latlon(0_f64, -631_f64), (0_f64, 89_f64));
|
||||
|
||||
// Negative longitude wrapping - 721 degrees
|
||||
assert_eq!(wrap_latlon(0_f64, -721_f64), (0_f64, -1_f64));
|
||||
}
|
||||
}
|
10
src/lib.rs
10
src/lib.rs
@ -55,15 +55,9 @@ pub enum Error {
|
||||
Empty,
|
||||
/// The latitude given is not in the valid range
|
||||
///
|
||||
/// This is the case when any of these are true:
|
||||
/// * `lat == f64::NAN`
|
||||
/// * `lon == f64::NAN`
|
||||
/// * `lat < -90`
|
||||
/// * `lat > 90`
|
||||
/// * `lon < -180`
|
||||
/// * `lon > 180`
|
||||
/// This is the case when either lat or lon is f64::NAN
|
||||
#[error("Invalid latitude or longitude")]
|
||||
InvalidLatLng,
|
||||
NaNLatLng,
|
||||
}
|
||||
|
||||
#[allow(clippy::doc_markdown)]
|
||||
|
93
src/wrap.rs
Normal file
93
src/wrap.rs
Normal file
@ -0,0 +1,93 @@
|
||||
// From: https://gist.github.com/missinglink/d0a085188a8eab2ca66db385bb7c023a
|
||||
unction wrap( lat, lon ){
|
||||
|
||||
var point = { lat: lat, lon: lon };
|
||||
var quadrant = Math.floor( Math.abs( lat ) / 90) % 4;
|
||||
var pole = ( lat > 0 ) ? 90 : -90;
|
||||
var offset = lat % 90;
|
||||
|
||||
switch( quadrant ){
|
||||
case 0:
|
||||
point.lat = offset;
|
||||
break;
|
||||
case 1:
|
||||
point.lat = pole - offset;
|
||||
point.lon += 180;
|
||||
break;
|
||||
case 2:
|
||||
point.lat = -offset
|
||||
point.lon += 180;
|
||||
break;
|
||||
case 3:
|
||||
point.lat = -pole + offset;
|
||||
break;
|
||||
}
|
||||
|
||||
if( point.lon > 180 || point.lon < -180 ){
|
||||
point.lon -= Math.floor(( point.lon + 180 ) / 360) * 360;
|
||||
}
|
||||
|
||||
return point;
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_extract_binary() {
|
||||
assert_eq!(extract_binary(0b0, 0..=0), 0b0);
|
||||
assert_eq!(extract_binary(0b11_1111_11, 2..=5), 0b1111);
|
||||
assert_eq!(extract_binary(0b11_0101_11, 2..=5), 0b0101);
|
||||
assert_eq!(extract_binary(0b11_1100_11, 2..=5), 0b1100);
|
||||
assert_eq!(extract_binary(0b111_10011_, 0..=4), 0b10011);
|
||||
assert_eq!(extract_binary(0b111100_1_1, 1..=1), 0b1);
|
||||
assert_eq!(extract_binary(0b111100_0_1, 1..=1), 0b0);
|
||||
|
||||
// A full test from the docs
|
||||
let cellid = 0b0100101110101000_1011100010010011_1001001100100100_1100000000000000_u64;
|
||||
assert_eq!(extract_binary(cellid, 15..=24), 0b10_01001001);
|
||||
assert_eq!(extract_binary(cellid, 25..=37), 0b01001_11001001);
|
||||
assert_eq!(extract_binary(cellid, 38..=50), 0b00010_11100010);
|
||||
assert_eq!(extract_binary(cellid, 51..=63), 0b01001_01110101);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_normalize_latlon() {
|
||||
let mut num_failed = 0;
|
||||
for (idx, (input_lat, input_lon, expected_lat, expected_lon)) in [
|
||||
// Lat
|
||||
(0_f64, 0_f64, 0_f64, 0_f64),
|
||||
(-89_f64, 0_f64, -89_f64, 0_f64),
|
||||
(-90_f64, 0_f64, -90_f64, 0_f64),
|
||||
(-91_f64, 0_f64, -89_f64, 0_f64),
|
||||
(89_f64, 0_f64, 89_f64, 0_f64),
|
||||
(90_f64, 0_f64, 90_f64, 0_f64),
|
||||
(91_f64, 0_f64, 89_f64, 0_f64),
|
||||
(180_f64, 0_f64, 0_f64, 0_f64),
|
||||
// Lon
|
||||
(0_f64, -179_f64, 0_f64, -179_f64),
|
||||
(0_f64, -180_f64, 0_f64, -180_f64),
|
||||
(0_f64, -181_f64, 0_f64, -179_f64),
|
||||
(0_f64, 179_f64, 0_f64, 179_f64),
|
||||
(0_f64, 180_f64, 0_f64, 180_f64),
|
||||
(0_f64, 181_f64, 0_f64, 179_f64),
|
||||
(0_f64, 360_f64, 0_f64, 0_f64),
|
||||
]
|
||||
.into_iter()
|
||||
.enumerate()
|
||||
{
|
||||
let (lat, lon) = normalize_latlon(input_lat, input_lon).unwrap();
|
||||
|
||||
eprint!("Testing #{idx}:\t{input_lat},{input_lon}\t=> {expected_lat},{expected_lon} \t(=> {lat},{lon})");
|
||||
|
||||
if (lat, lon) != (expected_lat, expected_lon) {
|
||||
eprint!("\tERROR!",);
|
||||
num_failed += 1;
|
||||
}
|
||||
eprintln!("");
|
||||
}
|
||||
|
||||
assert_eq!(num_failed, 0);
|
||||
}
|
||||
}
|
@ -30,11 +30,11 @@ with open("./00-sample-latlon.csv", "w") as f:
|
||||
seen_coords.add(s)
|
||||
f.write(s)
|
||||
|
||||
# # Non-normalized
|
||||
# for i in range(10000):
|
||||
# lat = (random() - .5) * 2 * 1000
|
||||
# lon = (random() - .5) * 2 * 1000
|
||||
# s = f"{lat},{lon}\n"
|
||||
# if s not in seen_coords:
|
||||
# seen_coords.add(s)
|
||||
# f.write(s)
|
||||
# Non-normalized
|
||||
for i in range(10000):
|
||||
lat = (random() - .5) * 2 * 1000
|
||||
lon = (random() - .5) * 2 * 1000
|
||||
s = f"{lat},{lon}\n"
|
||||
if s not in seen_coords:
|
||||
seen_coords.add(s)
|
||||
f.write(s)
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -11,13 +11,15 @@ use common::CELLID_LEVEL_23_END_BIT;
|
||||
|
||||
#[test]
|
||||
fn test_invalid_lat_lon() {
|
||||
// Latitudes/longitudes are wrapped, so this is okay
|
||||
assert!(Address::from_lat_lon(1.0_f64, 400.0_f64).is_ok());
|
||||
assert_eq!(
|
||||
Address::from_lat_lon(1.0_f64, 400.0_f64),
|
||||
Err(Error::InvalidLatLng)
|
||||
Address::from_lat_lon(f64::INFINITY, 400.0_f64),
|
||||
Err(Error::NaNLatLng)
|
||||
);
|
||||
assert_eq!(
|
||||
Address::from_lat_lon(1.0_f64, f64::NAN),
|
||||
Err(Error::InvalidLatLng)
|
||||
Err(Error::NaNLatLng)
|
||||
);
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user