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 std::ops::RangeInclusive;
|
||||||
|
|
||||||
use s2::{cellid::CellID, latlng::LatLng};
|
use s2::{cellid::CellID, latlng::LatLng};
|
||||||
@ -8,14 +9,16 @@ pub(crate) const THIRTEEN_BITS: u64 = 0b1_1111_1111_1111;
|
|||||||
use crate::Error;
|
use crate::Error;
|
||||||
|
|
||||||
pub fn lat_lon_to_cellid(lat: f64, lon: f64) -> Result<CellID, 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 !lat.is_finite() || !lon.is_finite() {
|
||||||
if latlng.normalized() == latlng {
|
return Err(Error::NaNLatLng);
|
||||||
Ok(Into::<CellID>::into(latlng))
|
|
||||||
} else {
|
|
||||||
Err(Error::InvalidLatLng)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Ok(CellID::from(LatLng::from_degrees(lat, lon)))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn extract_binary(number: u64, range: RangeInclusive<usize>) -> u64 {
|
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,
|
Empty,
|
||||||
/// The latitude given is not in the valid range
|
/// The latitude given is not in the valid range
|
||||||
///
|
///
|
||||||
/// This is the case when any of these are true:
|
/// This is the case when either lat or lon is f64::NAN
|
||||||
/// * `lat == f64::NAN`
|
|
||||||
/// * `lon == f64::NAN`
|
|
||||||
/// * `lat < -90`
|
|
||||||
/// * `lat > 90`
|
|
||||||
/// * `lon < -180`
|
|
||||||
/// * `lon > 180`
|
|
||||||
#[error("Invalid latitude or longitude")]
|
#[error("Invalid latitude or longitude")]
|
||||||
InvalidLatLng,
|
NaNLatLng,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(clippy::doc_markdown)]
|
#[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)
|
seen_coords.add(s)
|
||||||
f.write(s)
|
f.write(s)
|
||||||
|
|
||||||
# # Non-normalized
|
# Non-normalized
|
||||||
# for i in range(10000):
|
for i in range(10000):
|
||||||
# lat = (random() - .5) * 2 * 1000
|
lat = (random() - .5) * 2 * 1000
|
||||||
# lon = (random() - .5) * 2 * 1000
|
lon = (random() - .5) * 2 * 1000
|
||||||
# s = f"{lat},{lon}\n"
|
s = f"{lat},{lon}\n"
|
||||||
# if s not in seen_coords:
|
if s not in seen_coords:
|
||||||
# seen_coords.add(s)
|
seen_coords.add(s)
|
||||||
# f.write(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]
|
#[test]
|
||||||
fn test_invalid_lat_lon() {
|
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!(
|
assert_eq!(
|
||||||
Address::from_lat_lon(1.0_f64, 400.0_f64),
|
Address::from_lat_lon(f64::INFINITY, 400.0_f64),
|
||||||
Err(Error::InvalidLatLng)
|
Err(Error::NaNLatLng)
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
Address::from_lat_lon(1.0_f64, f64::NAN),
|
Address::from_lat_lon(1.0_f64, f64::NAN),
|
||||||
Err(Error::InvalidLatLng)
|
Err(Error::NaNLatLng)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user