use crate::formats::FormatHint;
const LAT_MIN: f64 = -90.0;
const LAT_MAX: f64 = 90.0;
const LNG_MIN: f64 = -180.0;
const LNG_MAX: f64 = 180.0;
pub fn detect_format(coords: &[(f64, f64)]) -> FormatHint {
detect_format_with_early_termination(coords, 100, 0.95)
}
pub fn detect_format_with_early_termination(
coords: &[(f64, f64)],
max_samples: usize,
confidence_threshold: f64,
) -> FormatHint {
if coords.is_empty() {
return FormatHint::Unknown;
}
let mut lng_lat_valid = 0;
let mut lat_lng_valid = 0;
let sample_size = coords.len().min(max_samples);
let min_samples_for_confidence = 10.min(sample_size);
for (i, &(first, second)) in coords.iter().take(sample_size).enumerate() {
if is_valid_lng(first) && is_valid_lat(second) {
lng_lat_valid += 1;
}
if is_valid_lat(first) && is_valid_lng(second) {
lat_lng_valid += 1;
}
if i + 1 >= min_samples_for_confidence && (i + 1) % 5 == 0 {
let _samples_so_far = i + 1;
let total_valid = lng_lat_valid + lat_lng_valid;
if total_valid > 0 {
let lng_lat_ratio = lng_lat_valid as f64 / total_valid as f64;
let lat_lng_ratio = lat_lng_valid as f64 / total_valid as f64;
if lng_lat_ratio >= confidence_threshold {
return FormatHint::LngLat;
} else if lat_lng_ratio >= confidence_threshold {
return FormatHint::LatLng;
}
}
}
}
match (lng_lat_valid, lat_lng_valid) {
(0, 0) => FormatHint::Unknown,
(a, b) if a > b => FormatHint::LngLat,
(a, b) if b > a => FormatHint::LatLng,
_ => FormatHint::Unknown,
}
}
fn is_valid_lng(value: f64) -> bool {
(LNG_MIN..=LNG_MAX).contains(&value) && value.is_finite()
}
fn is_valid_lat(value: f64) -> bool {
(LAT_MIN..=LAT_MAX).contains(&value) && value.is_finite()
}
pub fn confidence_score(coords: &[(f64, f64)], hint: FormatHint) -> f64 {
confidence_score_with_early_termination(coords, hint, 100, 1.0)
}
pub fn confidence_score_with_early_termination(
coords: &[(f64, f64)],
hint: FormatHint,
max_samples: usize,
target_confidence: f64,
) -> f64 {
if coords.is_empty() {
return 0.0;
}
let sample_size = coords.len().min(max_samples);
let mut valid_count = 0;
let min_samples_for_confidence = 10.min(sample_size);
for (i, &(first, second)) in coords.iter().take(sample_size).enumerate() {
let is_valid = match hint {
FormatHint::LngLat => is_valid_lng(first) && is_valid_lat(second),
FormatHint::LatLng => is_valid_lat(first) && is_valid_lng(second),
FormatHint::Unknown => false,
};
if is_valid {
valid_count += 1;
}
if i + 1 >= min_samples_for_confidence && (i + 1) % 5 == 0 {
let current_confidence = valid_count as f64 / (i + 1) as f64;
if current_confidence >= target_confidence {
return current_confidence;
}
let remaining_samples = sample_size - (i + 1);
let max_possible_confidence =
(valid_count + remaining_samples) as f64 / sample_size as f64;
if max_possible_confidence < target_confidence {
return valid_count as f64 / (i + 1) as f64;
}
}
}
valid_count as f64 / sample_size as f64
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_detect_format_lng_lat_order() {
let coords = vec![
(-122.4194, 37.7749), (-74.0060, 40.7128), (-87.6298, 41.8781), ];
let detected = detect_format(&coords);
assert!(matches!(detected, FormatHint::LngLat));
let confidence = confidence_score(&coords, FormatHint::LngLat);
assert_eq!(confidence, 1.0); }
#[test]
fn test_detect_format_lat_lng_order() {
let coords = vec![
(37.7749, -122.4194), (40.7128, -74.0060), (41.8781, -87.6298), ];
let detected = detect_format(&coords);
assert!(matches!(detected, FormatHint::LatLng));
let confidence = confidence_score(&coords, FormatHint::LatLng);
assert_eq!(confidence, 1.0); }
#[test]
fn test_detect_format_empty_coords() {
let empty_coords: Vec<(f64, f64)> = vec![];
let detected = detect_format(&empty_coords);
assert!(matches!(detected, FormatHint::Unknown));
let confidence = confidence_score(&empty_coords, FormatHint::LngLat);
assert_eq!(confidence, 0.0);
}
#[test]
fn test_detect_format_ambiguous_coordinates() {
let coords = vec![
(45.0, 60.0), (30.0, -80.0), (-45.0, 70.0), ];
let detected = detect_format(&coords);
assert!(matches!(detected, FormatHint::Unknown));
}
#[test]
fn test_detect_format_invalid_coordinates() {
let coords = vec![
(200.0, 95.0), (-200.0, -95.0), (250.0, 100.0), ];
let detected = detect_format(&coords);
assert!(matches!(detected, FormatHint::Unknown));
let confidence_lng_lat = confidence_score(&coords, FormatHint::LngLat);
let confidence_lat_lng = confidence_score(&coords, FormatHint::LatLng);
assert_eq!(confidence_lng_lat, 0.0);
assert_eq!(confidence_lat_lng, 0.0);
}
#[test]
fn test_detect_format_mixed_validity() {
let coords = vec![
(-122.4194, 37.7749), (200.0, 40.7128), (-87.6298, 41.8781), ];
let detected = detect_format(&coords);
assert!(matches!(detected, FormatHint::LngLat));
let confidence_lng_lat = confidence_score(&coords, FormatHint::LngLat);
assert_eq!(confidence_lng_lat, 2.0 / 3.0);
let confidence_lat_lng = confidence_score(&coords, FormatHint::LatLng);
assert_eq!(confidence_lat_lng, 1.0 / 3.0); }
#[test]
fn test_is_valid_lng() {
assert!(is_valid_lng(-180.0));
assert!(is_valid_lng(180.0));
assert!(is_valid_lng(0.0));
assert!(is_valid_lng(-122.4194));
assert!(is_valid_lng(151.2093));
assert!(!is_valid_lng(-180.1));
assert!(!is_valid_lng(180.1));
assert!(!is_valid_lng(-200.0));
assert!(!is_valid_lng(200.0));
assert!(!is_valid_lng(f64::NAN));
assert!(!is_valid_lng(f64::INFINITY));
assert!(!is_valid_lng(f64::NEG_INFINITY));
}
#[test]
fn test_is_valid_lat() {
assert!(is_valid_lat(-90.0));
assert!(is_valid_lat(90.0));
assert!(is_valid_lat(0.0));
assert!(is_valid_lat(37.7749));
assert!(is_valid_lat(-33.8688));
assert!(!is_valid_lat(-90.1));
assert!(!is_valid_lat(90.1));
assert!(!is_valid_lat(-100.0));
assert!(!is_valid_lat(100.0));
assert!(!is_valid_lat(f64::NAN));
assert!(!is_valid_lat(f64::INFINITY));
assert!(!is_valid_lat(f64::NEG_INFINITY));
}
#[test]
fn test_detect_format_large_sample() {
let mut coords = Vec::new();
for i in 0..150 {
let lng = -180.0 + (i as f64 * 2.4); let lat = -90.0 + (i as f64 * 1.2); coords.push((lng, lat));
}
let detected = detect_format(&coords);
assert!(matches!(detected, FormatHint::LngLat));
let confidence = confidence_score(&coords, FormatHint::LngLat);
assert_eq!(confidence, 1.0); }
#[test]
fn test_detect_format_edge_coordinate_values() {
let coords = vec![
(-180.0, -90.0), (180.0, 90.0), (0.0, 0.0), (-179.999999, 89.999999), ];
let detected = detect_format(&coords);
assert!(matches!(detected, FormatHint::LngLat));
let confidence = confidence_score(&coords, FormatHint::LngLat);
assert_eq!(confidence, 1.0);
let confidence_reversed = confidence_score(&coords, FormatHint::LatLng);
assert_eq!(confidence_reversed, 0.25); }
#[test]
fn test_detect_format_single_coordinate() {
let coords = vec![(-122.4194, 37.7749)];
let detected = detect_format(&coords);
assert!(matches!(detected, FormatHint::LngLat));
let confidence = confidence_score(&coords, FormatHint::LngLat);
assert_eq!(confidence, 1.0);
}
#[test]
fn test_confidence_score_unknown_format() {
let coords = vec![(-122.4194, 37.7749), (-74.0060, 40.7128)];
let confidence = confidence_score(&coords, FormatHint::Unknown);
assert_eq!(confidence, 0.0);
}
#[test]
fn test_confidence_score_partial_validity() {
let coords = vec![
(-122.4194, 37.7749), (200.0, 40.7128), (-87.6298, 41.8781), (-200.0, 42.3601), ];
let confidence = confidence_score(&coords, FormatHint::LngLat);
assert_eq!(confidence, 0.5);
let confidence_reversed = confidence_score(&coords, FormatHint::LatLng);
assert_eq!(confidence_reversed, 0.25); }
#[test]
fn test_detect_format_precision_coordinates() {
let coords = vec![
(-122.419416123456, 37.774928987654),
(-74.006012345679, 40.712776543211),
(-87.629798765432, 41.878113456789),
];
let detected = detect_format(&coords);
assert!(matches!(detected, FormatHint::LngLat));
let confidence = confidence_score(&coords, FormatHint::LngLat);
assert_eq!(confidence, 1.0);
}
#[test]
fn test_detect_format_extreme_longitude_values() {
let coords = vec![
(-179.999999, 45.0), (179.999999, 45.0), (-170.0, 60.0), (170.0, -60.0), ];
let detected = detect_format(&coords);
assert!(matches!(detected, FormatHint::LngLat));
let confidence = confidence_score(&coords, FormatHint::LngLat);
assert_eq!(confidence, 1.0);
let confidence_reversed = confidence_score(&coords, FormatHint::LatLng);
assert_eq!(confidence_reversed, 0.0);
}
#[test]
fn test_detect_format_polar_regions() {
let coords = vec![
(-120.0, 89.5), (45.0, -89.5), (0.0, 89.999), (180.0, -89.999), ];
let detected = detect_format(&coords);
assert!(matches!(detected, FormatHint::LngLat));
let confidence = confidence_score(&coords, FormatHint::LngLat);
assert_eq!(confidence, 1.0);
}
#[test]
fn test_detect_format_equatorial_region() {
let coords = vec![
(0.0, 0.0), (-1.0, 1.0), (1.0, -1.0), (-0.5, 0.5), ];
let detected = detect_format(&coords);
assert!(matches!(
detected,
FormatHint::LngLat | FormatHint::LatLng | FormatHint::Unknown
));
}
#[test]
fn test_confidence_score_mathematical_properties() {
let coords = vec![
(-122.4194, 37.7749),
(-74.0060, 40.7128),
(-87.6298, 41.8781),
];
let conf_lng_lat = confidence_score(&coords, FormatHint::LngLat);
let conf_lat_lng = confidence_score(&coords, FormatHint::LatLng);
let conf_unknown = confidence_score(&coords, FormatHint::Unknown);
assert!((0.0..=1.0).contains(&conf_lng_lat));
assert!((0.0..=1.0).contains(&conf_lat_lng));
assert!((0.0..=1.0).contains(&conf_unknown));
assert!(conf_lng_lat > conf_lat_lng);
assert_eq!(conf_unknown, 0.0);
}
#[test]
fn test_detect_format_sampling_consistency() {
let mut coords = Vec::new();
for i in 0..100 {
coords.push((-(180.0 - i as f64), i as f64 - 50.0));
}
for _i in 100..150 {
coords.push((200.0, 95.0)); }
let detected = detect_format(&coords);
assert!(matches!(detected, FormatHint::LngLat));
let confidence = confidence_score(&coords, FormatHint::LngLat);
assert_eq!(confidence, 1.0);
}
#[test]
fn test_early_termination_clear_format() {
let coords: Vec<(f64, f64)> = (0..1000)
.map(|i| (-(180.0 - i as f64 * 0.1), i as f64 * 0.1 - 50.0))
.collect();
let detected = detect_format_with_early_termination(&coords, 1000, 0.95);
assert!(matches!(detected, FormatHint::LngLat));
let detected_conservative = detect_format_with_early_termination(&coords, 50, 0.9);
assert!(matches!(detected_conservative, FormatHint::LngLat));
}
#[test]
fn test_early_termination_ambiguous_format() {
let coords = vec![
(45.0, 60.0), (30.0, -80.0), (-45.0, 70.0), (50.0, 45.0), ];
let detected = detect_format_with_early_termination(&coords, 100, 0.95);
assert!(matches!(detected, FormatHint::Unknown));
}
#[test]
fn test_early_termination_mixed_data() {
let mut coords = Vec::new();
for i in 0..20 {
coords.push((-(180.0 - i as f64), i as f64 - 10.0));
}
for i in 20..100 {
coords.push((i as f64 * 0.5, (i + 10) as f64 * 0.3));
}
let detected = detect_format_with_early_termination(&coords, 100, 0.8);
assert!(matches!(detected, FormatHint::LngLat));
}
#[test]
fn test_confidence_score_early_termination() {
let coords: Vec<(f64, f64)> = (0..1000)
.map(|i| (-(180.0 - i as f64 * 0.1), i as f64 * 0.1 - 50.0))
.collect();
let confidence =
confidence_score_with_early_termination(&coords, FormatHint::LngLat, 1000, 0.95);
assert!(confidence >= 0.95);
let confidence_impossible =
confidence_score_with_early_termination(&coords, FormatHint::LatLng, 100, 0.95);
assert!(confidence_impossible < 0.95);
}
#[test]
fn test_confidence_score_mixed_validity() {
let coords = vec![
(-122.4194, 37.7749), (200.0, 40.7128), (-87.6298, 41.8781), (-200.0, 42.3601), (-74.0060, 40.7128), ];
let confidence =
confidence_score_with_early_termination(&coords, FormatHint::LngLat, 100, 1.0);
assert_eq!(confidence, 0.6);
let confidence_low_target =
confidence_score_with_early_termination(&coords, FormatHint::LngLat, 100, 0.5);
assert!(confidence_low_target >= 0.5);
}
#[test]
fn test_early_termination_performance_characteristics() {
let mut coords = Vec::new();
for i in 0..15 {
coords.push((-(180.0 - i as f64), i as f64 - 10.0));
}
for _i in 15..10000 {
coords.push((200.0, 95.0)); }
let detected = detect_format_with_early_termination(&coords, 10000, 0.9);
assert!(matches!(detected, FormatHint::LngLat));
}
#[test]
fn test_early_termination_minimum_sample_requirement() {
let coords = vec![
(-122.4194, 37.7749),
(-74.0060, 40.7128),
(-87.6298, 41.8781),
];
let detected = detect_format_with_early_termination(&coords, 100, 0.8);
assert!(matches!(detected, FormatHint::LngLat));
let confidence =
confidence_score_with_early_termination(&coords, FormatHint::LngLat, 100, 0.8);
assert_eq!(confidence, 1.0);
}
}