rapidgeo_distance/
formats.rs

1use crate::{detection::detect_format, LngLat};
2
3/// Coordinate format hint indicating the ordering of coordinate pairs.
4///
5/// Used by format detection algorithms to determine whether coordinate pairs
6/// follow lng,lat or lat,lng ordering. This enables automatic correction of
7/// coordinate data from various sources.
8///
9/// # Examples
10///
11/// ```
12/// use rapidgeo_distance::formats::FormatHint;
13///
14/// // Check format equality
15/// assert_eq!(FormatHint::LngLat, FormatHint::LngLat);
16/// assert_ne!(FormatHint::LngLat, FormatHint::LatLng);
17///
18/// // Debug formatting
19/// println!("Detected format: {:?}", FormatHint::LngLat);
20/// ```
21#[derive(Debug, Clone, Copy, PartialEq)]
22pub enum FormatHint {
23    /// Longitude-first format: (longitude, latitude)
24    ///
25    /// This is the standard GIS format where the first value is longitude (-180 to +180)
26    /// and the second value is latitude (-90 to +90).
27    LngLat,
28
29    /// Latitude-first format: (latitude, longitude)
30    ///
31    /// This is common in some APIs and user interfaces where latitude comes first.
32    /// The first value is latitude (-90 to +90) and the second is longitude (-180 to +180).
33    LatLng,
34
35    /// Format cannot be determined with confidence
36    ///
37    /// Either the data is ambiguous (all values valid for both formats),
38    /// invalid (out of coordinate bounds), or empty.
39    Unknown,
40}
41
42/// Trait for types that can provide coordinate data in a uniform way.
43///
44/// This trait abstracts over different coordinate storage formats, allowing functions
45/// to accept coordinates from various sources (tuples, arrays, custom types) without
46/// needing separate implementations for each format.
47///
48/// The trait is thread-safe (`Send + Sync`) to support parallel processing.
49///
50/// # Performance Considerations
51///
52/// - `get_coords()` returns a boxed iterator for flexibility but adds allocation overhead
53/// - `hint_size()` helps pre-allocate output buffers for better performance  
54/// - `hint_format()` enables format-specific optimizations when possible
55///
56/// # Examples
57///
58/// ```
59/// use rapidgeo_distance::formats::{CoordSource, FormatHint};
60/// use rapidgeo_distance::LngLat;
61///
62/// let coords = vec![
63///     LngLat::new_deg(-122.4194, 37.7749),
64///     LngLat::new_deg(-74.0060, 40.7128),
65/// ];
66///
67/// // Access coordinate data uniformly
68/// let mut iter = coords.get_coords();
69/// assert_eq!(iter.next(), Some(LngLat::new_deg(-122.4194, 37.7749)));
70///
71/// // Get size hint for buffer allocation
72/// assert_eq!(coords.hint_size(), Some(2));
73///
74/// // Get format information
75/// assert_eq!(coords.hint_format(), FormatHint::LngLat);
76/// ```
77///
78/// # Thread Safety
79///
80/// All implementations must be `Send + Sync` to support parallel processing:
81///
82/// ```
83/// use rapidgeo_distance::formats::CoordSource;
84///
85/// fn process_coords_parallel<T: CoordSource>(coords: &T) {
86///     // Can safely use in parallel contexts
87/// }
88/// ```
89pub trait CoordSource: Send + Sync {
90    /// Returns an iterator over the coordinates as `LngLat` values.
91    ///
92    /// The iterator performs any necessary format conversion (e.g., lat,lng → lng,lat)
93    /// and coordinate validation. Invalid coordinates may be skipped or cause panics
94    /// depending on the implementation.
95    ///
96    /// # Performance
97    ///
98    /// Returns a boxed iterator for flexibility, but this adds heap allocation overhead.
99    /// For performance-critical code, consider using format-specific functions directly.
100    fn get_coords(&self) -> Box<dyn Iterator<Item = LngLat> + '_>;
101
102    /// Returns an estimate of the number of coordinates, if known.
103    ///
104    /// Used for buffer pre-allocation to improve performance. Returns `None` if the
105    /// size cannot be determined efficiently (e.g., for lazy iterators).
106    ///
107    /// # Examples
108    ///
109    /// ```
110    /// use rapidgeo_distance::formats::CoordSource;
111    ///
112    /// let coords = vec![(1.0, 2.0), (3.0, 4.0)];
113    /// assert_eq!(coords.hint_size(), Some(2));
114    ///
115    /// let empty: Vec<(f64, f64)> = vec![];
116    /// assert_eq!(empty.hint_size(), Some(0));
117    /// ```
118    fn hint_size(&self) -> Option<usize>;
119
120    /// Returns a hint about the coordinate format, if detectable.
121    ///
122    /// This enables format-specific optimizations and helps with automatic
123    /// format detection. Returns `FormatHint::Unknown` for ambiguous data.
124    ///
125    /// # Examples
126    ///
127    /// ```
128    /// use rapidgeo_distance::formats::{CoordSource, FormatHint};
129    /// use rapidgeo_distance::LngLat;
130    ///
131    /// // LngLat vectors have known format
132    /// let coords = vec![LngLat::new_deg(0.0, 0.0)];
133    /// assert_eq!(coords.hint_format(), FormatHint::LngLat);
134    ///
135    /// // Raw tuples require detection
136    /// let tuples = vec![(0.0, 0.0)];
137    /// assert_eq!(tuples.hint_format(), FormatHint::Unknown);
138    /// ```
139    fn hint_format(&self) -> FormatHint;
140}
141
142impl CoordSource for Vec<LngLat> {
143    fn get_coords(&self) -> Box<dyn Iterator<Item = LngLat> + '_> {
144        Box::new(self.iter().copied())
145    }
146
147    fn hint_size(&self) -> Option<usize> {
148        Some(self.len())
149    }
150
151    fn hint_format(&self) -> FormatHint {
152        FormatHint::LngLat
153    }
154}
155
156impl CoordSource for Vec<(f64, f64)> {
157    fn get_coords(&self) -> Box<dyn Iterator<Item = LngLat> + '_> {
158        Box::new(self.iter().map(|&(lng, lat)| LngLat::new_deg(lng, lat)))
159    }
160
161    fn hint_size(&self) -> Option<usize> {
162        Some(self.len())
163    }
164
165    fn hint_format(&self) -> FormatHint {
166        FormatHint::Unknown
167    }
168}
169
170impl CoordSource for [f64] {
171    fn get_coords(&self) -> Box<dyn Iterator<Item = LngLat> + '_> {
172        Box::new(
173            self.chunks_exact(2)
174                .map(|chunk| LngLat::new_deg(chunk[0], chunk[1])),
175        )
176    }
177
178    fn hint_size(&self) -> Option<usize> {
179        Some(self.len() / 2)
180    }
181
182    fn hint_format(&self) -> FormatHint {
183        FormatHint::Unknown
184    }
185}
186
187impl CoordSource for Vec<f64> {
188    fn get_coords(&self) -> Box<dyn Iterator<Item = LngLat> + '_> {
189        Box::new(
190            self.chunks_exact(2)
191                .map(|chunk| LngLat::new_deg(chunk[0], chunk[1])),
192        )
193    }
194
195    fn hint_size(&self) -> Option<usize> {
196        Some(self.len() / 2)
197    }
198
199    fn hint_format(&self) -> FormatHint {
200        FormatHint::Unknown
201    }
202}
203
204/// GeoJSON-style point geometry with [longitude, latitude] coordinates.
205///
206/// Represents a point in GeoJSON format where coordinates are stored as
207/// `[longitude, latitude]` in decimal degrees. This matches the GeoJSON
208/// specification and GIS conventions.
209///
210/// # GeoJSON Standard
211///
212/// According to [RFC 7946](https://tools.ietf.org/html/rfc7946), GeoJSON
213/// coordinates are always in `[longitude, latitude]` order, regardless of
214/// local conventions that might prefer latitude first.
215///
216/// # Examples
217///
218/// ```
219/// use rapidgeo_distance::formats::GeoPoint;
220///
221/// // San Francisco in GeoJSON format
222/// let sf = GeoPoint {
223///     coordinates: [-122.4194, 37.7749], // [lng, lat]
224/// };
225///
226/// assert_eq!(sf.coordinates[0], -122.4194); // longitude
227/// assert_eq!(sf.coordinates[1], 37.7749);   // latitude
228/// ```
229///
230/// # Coordinate Bounds
231///
232/// - `coordinates[0]`: Longitude in range [-180.0, +180.0]
233/// - `coordinates[1]`: Latitude in range [-90.0, +90.0]
234///
235/// # See Also
236///
237/// - [GeoJSON Specification](https://geojson.org/)
238/// - [`LngLat`](crate::LngLat) for the native coordinate type
239#[derive(Debug, Clone)]
240pub struct GeoPoint {
241    /// Coordinates array in [longitude, latitude] order (GeoJSON standard)
242    pub coordinates: [f64; 2],
243}
244
245impl CoordSource for Vec<GeoPoint> {
246    fn get_coords(&self) -> Box<dyn Iterator<Item = LngLat> + '_> {
247        Box::new(
248            self.iter()
249                .map(|point| LngLat::new_deg(point.coordinates[0], point.coordinates[1])),
250        )
251    }
252
253    fn hint_size(&self) -> Option<usize> {
254        Some(self.len())
255    }
256
257    fn hint_format(&self) -> FormatHint {
258        FormatHint::LngLat
259    }
260}
261
262/// Converts various coordinate formats to a standardized `Vec<LngLat>` with automatic format detection.
263///
264/// This is the main entry point for coordinate format conversion. It handles multiple
265/// input formats and automatically detects coordinate ordering (lng,lat vs lat,lng)
266/// when the format is ambiguous.
267///
268/// # Supported Input Formats
269///
270/// - **Tuples**: `Vec<(f64, f64)>` with automatic lng,lat vs lat,lng detection
271/// - **Flat arrays**: `Vec<f64>` chunked into coordinate pairs
272/// - **GeoJSON**: `Vec<GeoPoint>` following GeoJSON [lng, lat] specification
273/// - **Already converted**: `Vec<LngLat>` (returned as clone)
274///
275/// # Format Detection Logic
276///
277/// For ambiguous formats (tuples, flat arrays), the function:
278/// 1. Samples up to 100 coordinate pairs for performance
279/// 2. Tests which interpretation (lng,lat or lat,lng) has more valid coordinates
280/// 3. Applies 95% confidence threshold for early termination
281/// 4. Falls back to lng,lat ordering for truly ambiguous data
282///
283/// # Performance
284///
285/// - **Time complexity**: O(min(n, 100)) for format detection + O(n) for conversion
286/// - **Space complexity**: O(n) for output vector
287/// - **Early termination**: Typically processes 10-20 samples for clear datasets
288///
289/// # Examples
290///
291/// ```
292/// use rapidgeo_distance::formats::{coords_to_lnglat_vec, CoordinateInput};
293/// use rapidgeo_distance::LngLat;
294///
295/// // Automatic lng,lat detection (US cities)
296/// let us_cities = vec![
297///     (-122.4194, 37.7749), // San Francisco
298///     (-74.0060, 40.7128),  // New York
299/// ];
300/// let input = CoordinateInput::Tuples(us_cities);
301/// let coords = coords_to_lnglat_vec(&input);
302/// assert_eq!(coords[0], LngLat::new_deg(-122.4194, 37.7749));
303///
304/// // Automatic lat,lng detection (same cities, swapped)
305/// let swapped_cities = vec![
306///     (37.7749, -122.4194), // San Francisco (lat, lng)
307///     (40.7128, -74.0060),  // New York (lat, lng)
308/// ];
309/// let input = CoordinateInput::Tuples(swapped_cities);
310/// let coords = coords_to_lnglat_vec(&input);
311/// // Automatically corrected to lng,lat order
312/// assert_eq!(coords[0], LngLat::new_deg(-122.4194, 37.7749));
313///
314/// // Flat array format
315/// let flat = vec![-122.4194, 37.7749, -74.0060, 40.7128];
316/// let input = CoordinateInput::FlatArray(flat);
317/// let coords = coords_to_lnglat_vec(&input);
318/// assert_eq!(coords.len(), 2);
319/// ```
320///
321/// # Error Handling
322///
323/// - **Empty input**: Returns empty vector
324/// - **Odd-length arrays**: Incomplete pairs are ignored
325/// - **Invalid coordinates**: Out-of-bounds values affect format detection but are preserved
326/// - **Ambiguous format**: Defaults to lng,lat ordering
327///
328/// # See Also
329///
330/// - [`detect_format`](crate::detection::detect_format) for format detection details
331/// - [`CoordinateInput`] for input format options
332/// - [`tuples_to_lnglat_vec_auto`] for tuple-specific conversion
333pub fn coords_to_lnglat_vec(input: &CoordinateInput) -> Vec<LngLat> {
334    match input {
335        CoordinateInput::Tuples(tuples) => tuples_to_lnglat_vec_auto(tuples),
336        CoordinateInput::FlatArray(arr) => flat_array_to_lnglat_vec_auto(arr),
337        CoordinateInput::GeoJson(points) => points
338            .iter()
339            .map(|p| LngLat::new_deg(p.coordinates[0], p.coordinates[1]))
340            .collect(),
341        CoordinateInput::Already(coords) => coords.clone(),
342    }
343}
344
345/// Converts coordinate tuples to `Vec<LngLat>` with automatic format detection.
346///
347/// Detects whether the input tuples follow lng,lat or lat,lng ordering and converts
348/// them to the standard lng,lat format. Uses statistical analysis of coordinate
349/// bounds to determine the most likely format.
350///
351/// # Format Detection Process
352///
353/// 1. **Empty check**: Returns empty vector immediately
354/// 2. **Format detection**: Uses [`detect_format`](crate::detection::detect_format) to analyze coordinate bounds
355/// 3. **Conversion**: Applies appropriate coordinate swapping based on detected format
356/// 4. **Fallback**: Assumes lng,lat for ambiguous cases
357///
358/// # Performance
359///
360/// - **Time complexity**: O(min(n, 100)) for detection + O(n) for conversion
361/// - **Space complexity**: O(n) for output vector with pre-allocation
362/// - **Early termination**: Detection stops early when confidence threshold is reached
363///
364/// # Examples
365///
366/// ```
367/// use rapidgeo_distance::formats::tuples_to_lnglat_vec_auto;
368/// use rapidgeo_distance::LngLat;
369///
370/// // Clear lng,lat format (US cities with distinctive longitudes)
371/// let lng_lat_data = vec![
372///     (-122.4194, 37.7749), // San Francisco
373///     (-74.0060, 40.7128),  // New York
374/// ];
375/// let coords = tuples_to_lnglat_vec_auto(&lng_lat_data);
376/// assert_eq!(coords[0], LngLat::new_deg(-122.4194, 37.7749));
377///
378/// // Clear lat,lng format (same cities, swapped)
379/// let lat_lng_data = vec![
380///     (37.7749, -122.4194), // San Francisco (lat, lng)
381///     (40.7128, -74.0060),  // New York (lat, lng)
382/// ];
383/// let coords = tuples_to_lnglat_vec_auto(&lat_lng_data);
384/// // Automatically detects and swaps to lng,lat
385/// assert_eq!(coords[0], LngLat::new_deg(-122.4194, 37.7749));
386///
387/// // Empty input
388/// let empty: Vec<(f64, f64)> = vec![];
389/// let coords = tuples_to_lnglat_vec_auto(&empty);
390/// assert_eq!(coords.len(), 0);
391/// ```
392///
393/// # Detection Accuracy
394///
395/// The detection algorithm is highly accurate for:
396/// - **Clear cases**: Coordinates with values outside ±90° range
397/// - **Large datasets**: More samples improve confidence
398/// - **Geographically diverse data**: Mixed coordinate ranges
399///
400/// Less reliable for:
401/// - **Equatorial regions**: Values within ±90° for both coordinates
402/// - **Small datasets**: Less statistical significance
403/// - **Single coordinates**: No redundancy for validation
404///
405/// # See Also
406///
407/// - [`detect_format`](crate::detection::detect_format) for detection algorithm details
408/// - [`coords_to_lnglat_vec`] for multi-format conversion
409/// - [`flat_array_to_lnglat_vec_auto`] for flat array conversion
410pub fn tuples_to_lnglat_vec_auto(tuples: &[(f64, f64)]) -> Vec<LngLat> {
411    if tuples.is_empty() {
412        return Vec::new();
413    }
414
415    let format = detect_format(tuples);
416    let mut result = Vec::with_capacity(tuples.len());
417
418    match format {
419        FormatHint::LngLat => {
420            result.extend(tuples.iter().map(|&(lng, lat)| LngLat::new_deg(lng, lat)));
421        }
422        FormatHint::LatLng => {
423            result.extend(tuples.iter().map(|&(lat, lng)| LngLat::new_deg(lng, lat)));
424        }
425        FormatHint::Unknown => {
426            result.extend(
427                tuples
428                    .iter()
429                    .map(|&(first, second)| LngLat::new_deg(first, second)),
430            );
431        }
432    }
433
434    result
435}
436
437/// Converts flat coordinate arrays to `Vec<LngLat>` with automatic format detection.
438///
439/// Takes a flat array of coordinates in the format `[x1, y1, x2, y2, ...]` and converts
440/// them to `LngLat` coordinates. Automatically detects whether the pairs represent
441/// lng,lat or lat,lng ordering.
442///
443/// # Array Format
444///
445/// The input array is interpreted as consecutive coordinate pairs:
446/// - `[lng1, lat1, lng2, lat2, ...]` or
447/// - `[lat1, lng1, lat2, lng2, ...]` (detected automatically)
448///
449/// # Processing Steps
450///
451/// 1. **Length validation**: Arrays with less than 2 elements return empty vector
452/// 2. **Pair extraction**: Chunks array into `(f64, f64)` pairs using `chunks_exact(2)`
453/// 3. **Format detection**: Analyzes pairs to determine coordinate ordering
454/// 4. **Conversion**: Applies appropriate coordinate swapping
455///
456/// # Examples
457///
458/// ```
459/// use rapidgeo_distance::formats::flat_array_to_lnglat_vec_auto;
460/// use rapidgeo_distance::LngLat;
461///
462/// // Lng,lat format
463/// let lng_lat_array = vec![
464///     -122.4194, 37.7749,  // San Francisco
465///     -74.0060, 40.7128,   // New York
466/// ];
467/// let coords = flat_array_to_lnglat_vec_auto(&lng_lat_array);
468/// assert_eq!(coords.len(), 2);
469/// assert_eq!(coords[0], LngLat::new_deg(-122.4194, 37.7749));
470///
471/// // Lat,lng format (automatically detected and corrected)
472/// let lat_lng_array = vec![
473///     37.7749, -122.4194,  // San Francisco (lat, lng)
474///     40.7128, -74.0060,   // New York (lat, lng)
475/// ];
476/// let coords = flat_array_to_lnglat_vec_auto(&lat_lng_array);
477/// assert_eq!(coords[0], LngLat::new_deg(-122.4194, 37.7749));
478///
479/// // Odd-length array (last element ignored)
480/// let odd_array = vec![-122.4194, 37.7749, -74.0060];
481/// let coords = flat_array_to_lnglat_vec_auto(&odd_array);
482/// assert_eq!(coords.len(), 1); // Only complete pairs are processed
483/// ```
484///
485/// # Edge Cases
486///
487/// - **Empty arrays**: Return empty vector
488/// - **Single element**: Return empty vector (incomplete pair)
489/// - **Odd length**: Incomplete final pair is ignored
490/// - **Invalid coordinates**: Preserved in output, affect format detection
491///
492/// # Performance
493///
494/// - **Time complexity**: O(n) for chunking + O(min(n/2, 100)) for detection
495/// - **Space complexity**: O(n) for coordinate pairs + output vector
496/// - **Memory efficiency**: Single allocation for output vector
497///
498/// # See Also
499///
500/// - [`tuples_to_lnglat_vec_auto`] for tuple-based conversion
501/// - [`coords_to_lnglat_vec`] for multi-format conversion
502/// - [`detect_format`](crate::detection::detect_format) for detection details
503pub fn flat_array_to_lnglat_vec_auto(arr: &[f64]) -> Vec<LngLat> {
504    if arr.len() < 2 {
505        return Vec::new();
506    }
507
508    let pairs: Vec<(f64, f64)> = arr
509        .chunks_exact(2)
510        .map(|chunk| (chunk[0], chunk[1]))
511        .collect();
512
513    tuples_to_lnglat_vec_auto(&pairs)
514}
515
516/// Enumeration of supported coordinate input formats for batch conversion.
517///
518/// This enum unifies different coordinate representations to enable consistent
519/// processing through a single interface. Each variant handles a specific
520/// coordinate format with appropriate conversion logic.
521///
522/// # Design Rationale
523///
524/// Different systems and APIs represent coordinates in various formats:
525/// - **GIS systems**: Often use lng,lat ordering
526/// - **Web APIs**: May use lat,lng for user familiarity  
527/// - **Databases**: Might store as flat arrays for efficiency
528/// - **GeoJSON**: Standardized as [lng, lat] arrays
529///
530/// This enum allows handling all formats uniformly while preserving format-specific
531/// optimizations.
532///
533/// # Examples
534///
535/// ```
536/// use rapidgeo_distance::formats::{CoordinateInput, GeoPoint, coords_to_lnglat_vec};
537/// use rapidgeo_distance::LngLat;
538///
539/// // From tuple pairs
540/// let tuples = vec![(-122.4194, 37.7749), (-74.0060, 40.7128)];
541/// let input = CoordinateInput::Tuples(tuples);
542/// let coords = coords_to_lnglat_vec(&input);
543///
544/// // From flat array
545/// let flat = vec![-122.4194, 37.7749, -74.0060, 40.7128];
546/// let input = CoordinateInput::FlatArray(flat);
547/// let coords = coords_to_lnglat_vec(&input);
548///
549/// // From GeoJSON-style points
550/// let geojson = vec![
551///     GeoPoint { coordinates: [-122.4194, 37.7749] },
552///     GeoPoint { coordinates: [-74.0060, 40.7128] },
553/// ];
554/// let input = CoordinateInput::GeoJson(geojson);
555/// let coords = coords_to_lnglat_vec(&input);
556/// ```
557///
558/// # Conversion Trait Implementations
559///
560/// All variants implement `From<T>` for convenient conversion:
561///
562/// ```
563/// use rapidgeo_distance::formats::CoordinateInput;
564///
565/// let tuples = vec![(1.0, 2.0), (3.0, 4.0)];
566/// let input: CoordinateInput = tuples.into();
567/// ```
568///
569/// # See Also
570///
571/// - [`coords_to_lnglat_vec`] for format conversion
572/// - [`GeoPoint`] for GeoJSON-style coordinates
573/// - [`LngLat`](crate::LngLat) for the target coordinate type
574#[derive(Debug)]
575pub enum CoordinateInput {
576    /// Coordinate pairs as tuples with automatic format detection.
577    ///
578    /// Each tuple represents a coordinate pair that could be either (lng, lat)
579    /// or (lat, lng). The format is automatically detected by analyzing the
580    /// value ranges and determining which interpretation has more valid coordinates.
581    ///
582    /// Format detection considers:
583    /// - Longitude values must be in range [-180, +180]
584    /// - Latitude values must be in range [-90, +90]
585    /// - Statistical confidence based on sample size
586    Tuples(Vec<(f64, f64)>),
587
588    /// Flat array of coordinates as `[x1, y1, x2, y2, ...]`.
589    ///
590    /// The array is chunked into coordinate pairs using `chunks_exact(2)`,
591    /// then format detection is applied to determine if pairs represent
592    /// lng,lat or lat,lng ordering. Odd-length arrays ignore the final element.
593    ///
594    /// This format is common in:
595    /// - Database storage (efficient packing)
596    /// - Graphics APIs (vertex arrays)
597    /// - Scientific computing (NumPy arrays)
598    FlatArray(Vec<f64>),
599
600    /// GeoJSON-style point geometries with [longitude, latitude] coordinates.
601    ///
602    /// Following the GeoJSON specification (RFC 7946), coordinates are always
603    /// stored as [longitude, latitude] regardless of local conventions.
604    /// No format detection is needed as the format is standardized.
605    ///
606    /// This format is used by:
607    /// - GeoJSON files and APIs
608    /// - PostGIS and other spatial databases
609    /// - Web mapping libraries (Leaflet, OpenLayers)
610    GeoJson(Vec<GeoPoint>),
611
612    /// Coordinates already in the target `LngLat` format.
613    ///
614    /// No conversion is needed - the vector is simply cloned.
615    /// This variant exists for API consistency and to avoid
616    /// special-case handling in generic code.
617    Already(Vec<LngLat>),
618}
619
620impl From<Vec<(f64, f64)>> for CoordinateInput {
621    fn from(tuples: Vec<(f64, f64)>) -> Self {
622        CoordinateInput::Tuples(tuples)
623    }
624}
625
626impl From<Vec<f64>> for CoordinateInput {
627    fn from(arr: Vec<f64>) -> Self {
628        CoordinateInput::FlatArray(arr)
629    }
630}
631
632impl From<Vec<GeoPoint>> for CoordinateInput {
633    fn from(points: Vec<GeoPoint>) -> Self {
634        CoordinateInput::GeoJson(points)
635    }
636}
637
638impl From<Vec<LngLat>> for CoordinateInput {
639    fn from(coords: Vec<LngLat>) -> Self {
640        CoordinateInput::Already(coords)
641    }
642}
643
644#[cfg(test)]
645mod tests {
646    use super::*;
647
648    #[test]
649    fn test_format_hint_debug() {
650        // Verify FormatHint implements Debug and displays correctly
651        assert_eq!(format!("{:?}", FormatHint::LngLat), "LngLat");
652        assert_eq!(format!("{:?}", FormatHint::LatLng), "LatLng");
653        assert_eq!(format!("{:?}", FormatHint::Unknown), "Unknown");
654    }
655
656    #[test]
657    fn test_coord_source_vec_lnglat_iteration() {
658        let coords = vec![
659            LngLat::new_deg(-122.4194, 37.7749), // San Francisco
660            LngLat::new_deg(-74.0060, 40.7128),  // New York
661            LngLat::new_deg(-87.6298, 41.8781),  // Chicago
662        ];
663
664        let mut iter = coords.get_coords();
665
666        assert_eq!(iter.next(), Some(LngLat::new_deg(-122.4194, 37.7749)));
667        assert_eq!(iter.next(), Some(LngLat::new_deg(-74.0060, 40.7128)));
668        assert_eq!(iter.next(), Some(LngLat::new_deg(-87.6298, 41.8781)));
669        assert_eq!(iter.next(), None);
670        assert_eq!(iter.next(), None); // Should remain None
671    }
672
673    #[test]
674    fn test_coord_source_tuples_iteration() {
675        let tuples = vec![
676            (-122.4194, 37.7749),
677            (-74.0060, 40.7128),
678            (-87.6298, 41.8781),
679        ];
680
681        let mut iter = tuples.get_coords();
682
683        assert_eq!(iter.next(), Some(LngLat::new_deg(-122.4194, 37.7749)));
684        assert_eq!(iter.next(), Some(LngLat::new_deg(-74.0060, 40.7128)));
685        assert_eq!(iter.next(), Some(LngLat::new_deg(-87.6298, 41.8781)));
686        assert_eq!(iter.next(), None);
687    }
688
689    #[test]
690    fn test_coord_source_flat_vec_iteration() {
691        let flat_array = vec![
692            -122.4194, 37.7749, // San Francisco
693            -74.0060, 40.7128, // New York
694            -87.6298, 41.8781, // Chicago
695        ];
696
697        let mut iter = flat_array.get_coords();
698
699        assert_eq!(iter.next(), Some(LngLat::new_deg(-122.4194, 37.7749)));
700        assert_eq!(iter.next(), Some(LngLat::new_deg(-74.0060, 40.7128)));
701        assert_eq!(iter.next(), Some(LngLat::new_deg(-87.6298, 41.8781)));
702        assert_eq!(iter.next(), None);
703    }
704
705    #[test]
706    fn test_coord_source_flat_vec_odd_length() {
707        // Odd length should ignore the last element
708        let flat_array = vec![
709            -122.4194, 37.7749, // San Francisco
710            -74.0060, 40.7128,  // New York
711            -87.6298, // Incomplete coordinate
712        ];
713
714        let mut iter = flat_array.get_coords();
715
716        assert_eq!(iter.next(), Some(LngLat::new_deg(-122.4194, 37.7749)));
717        assert_eq!(iter.next(), Some(LngLat::new_deg(-74.0060, 40.7128)));
718        assert_eq!(iter.next(), None); // Incomplete coordinate is ignored
719    }
720
721    #[test]
722    fn test_coord_source_empty_arrays() {
723        // Test empty Vec<LngLat>
724        let empty_coords: Vec<LngLat> = vec![];
725        let mut iter = empty_coords.get_coords();
726        assert_eq!(iter.next(), None);
727
728        // Test empty tuples
729        let empty_tuples: Vec<(f64, f64)> = vec![];
730        let mut iter = empty_tuples.get_coords();
731        assert_eq!(iter.next(), None);
732
733        // Test empty flat array
734        let empty_flat: Vec<f64> = vec![];
735        let mut iter = empty_flat.get_coords();
736        assert_eq!(iter.next(), None);
737    }
738
739    #[test]
740    fn test_coord_source_vec_lnglat() {
741        let coords = vec![
742            LngLat::new_deg(-122.4194, 37.7749),
743            LngLat::new_deg(-74.0060, 40.7128),
744        ];
745
746        // Test trait methods
747        let hint_size = coords.hint_size();
748        assert_eq!(hint_size, Some(2));
749
750        let format_hint = coords.hint_format();
751        assert!(matches!(format_hint, FormatHint::LngLat));
752
753        // Test iterator
754        let collected: Vec<LngLat> = coords.get_coords().collect();
755        assert_eq!(collected.len(), 2);
756        assert_eq!(collected[0], LngLat::new_deg(-122.4194, 37.7749));
757        assert_eq!(collected[1], LngLat::new_deg(-74.0060, 40.7128));
758    }
759
760    #[test]
761    fn test_coord_source_vec_tuples() {
762        let tuples = vec![
763            (-122.4194, 37.7749),
764            (-74.0060, 40.7128),
765            (-87.6298, 41.8781),
766        ];
767
768        assert_eq!(tuples.hint_size(), Some(3));
769        assert!(matches!(tuples.hint_format(), FormatHint::Unknown));
770
771        let collected: Vec<LngLat> = tuples.get_coords().collect();
772        assert_eq!(collected.len(), 3);
773        assert_eq!(collected[0], LngLat::new_deg(-122.4194, 37.7749));
774        assert_eq!(collected[1], LngLat::new_deg(-74.0060, 40.7128));
775        assert_eq!(collected[2], LngLat::new_deg(-87.6298, 41.8781));
776    }
777
778    #[test]
779    fn test_coord_source_flat_array() {
780        let flat_array = vec![-122.4194, 37.7749, -74.0060, 40.7128];
781
782        assert_eq!(flat_array.hint_size(), Some(2));
783        assert!(matches!(flat_array.hint_format(), FormatHint::Unknown));
784
785        let collected: Vec<LngLat> = flat_array.get_coords().collect();
786        assert_eq!(collected.len(), 2);
787        assert_eq!(collected[0], LngLat::new_deg(-122.4194, 37.7749));
788        assert_eq!(collected[1], LngLat::new_deg(-74.0060, 40.7128));
789    }
790
791    #[test]
792    fn test_coord_source_empty_collections() {
793        // Empty Vec<LngLat>
794        let empty_coords: Vec<LngLat> = vec![];
795        assert_eq!(empty_coords.hint_size(), Some(0));
796        assert!(matches!(empty_coords.hint_format(), FormatHint::LngLat));
797        assert_eq!(empty_coords.get_coords().collect::<Vec<_>>().len(), 0);
798
799        // Empty Vec<(f64, f64)>
800        let empty_tuples: Vec<(f64, f64)> = vec![];
801        assert_eq!(empty_tuples.hint_size(), Some(0));
802        assert!(matches!(empty_tuples.hint_format(), FormatHint::Unknown));
803        assert_eq!(empty_tuples.get_coords().collect::<Vec<_>>().len(), 0);
804
805        // Empty flat array
806        let empty_flat: Vec<f64> = vec![];
807        assert_eq!(empty_flat.hint_size(), Some(0));
808        assert!(matches!(empty_flat.hint_format(), FormatHint::Unknown));
809        assert_eq!(empty_flat.get_coords().collect::<Vec<_>>().len(), 0);
810    }
811
812    #[test]
813    fn test_coord_source_single_element() {
814        // Single coordinate in Vec<LngLat>
815        let single_coord = vec![LngLat::new_deg(0.0, 0.0)];
816        assert_eq!(single_coord.hint_size(), Some(1));
817        let collected: Vec<LngLat> = single_coord.get_coords().collect();
818        assert_eq!(collected.len(), 1);
819        assert_eq!(collected[0], LngLat::new_deg(0.0, 0.0));
820
821        // Single tuple
822        let single_tuple = vec![(1.0, 2.0)];
823        assert_eq!(single_tuple.hint_size(), Some(1));
824        let collected: Vec<LngLat> = single_tuple.get_coords().collect();
825        assert_eq!(collected.len(), 1);
826        assert_eq!(collected[0], LngLat::new_deg(1.0, 2.0));
827
828        // Single coordinate in flat array
829        let single_flat = vec![3.0, 4.0];
830        assert_eq!(single_flat.hint_size(), Some(1));
831        let collected: Vec<LngLat> = single_flat.get_coords().collect();
832        assert_eq!(collected.len(), 1);
833        assert_eq!(collected[0], LngLat::new_deg(3.0, 4.0));
834    }
835
836    #[test]
837    fn test_coord_source_flat_array_size_calculation() {
838        // Test various flat array sizes
839        let sizes_and_expected = vec![
840            (0, 0),    // Empty
841            (1, 0),    // Single element (incomplete pair)
842            (2, 1),    // One coordinate pair
843            (3, 1),    // One pair + incomplete
844            (4, 2),    // Two coordinate pairs
845            (5, 2),    // Two pairs + incomplete
846            (100, 50), // Large array
847            (101, 50), // Large array + incomplete
848        ];
849
850        for (array_len, expected_coords) in sizes_and_expected {
851            let flat_array: Vec<f64> = (0..array_len).map(|i| i as f64).collect();
852            assert_eq!(
853                flat_array.hint_size(),
854                Some(expected_coords),
855                "Failed for array length {}",
856                array_len
857            );
858
859            let collected: Vec<LngLat> = flat_array.get_coords().collect();
860            assert_eq!(
861                collected.len(),
862                expected_coords,
863                "Iterator returned wrong count for array length {}",
864                array_len
865            );
866        }
867    }
868
869    #[test]
870    fn test_coord_source_consistency_across_formats() {
871        // Same data in different formats should produce identical results
872        let test_coords = vec![
873            (-122.4194, 37.7749),
874            (-74.0060, 40.7128),
875            (-87.6298, 41.8781),
876        ];
877
878        // Convert to different formats
879        let lnglat_vec: Vec<LngLat> = test_coords
880            .iter()
881            .map(|&(lng, lat)| LngLat::new_deg(lng, lat))
882            .collect();
883
884        let tuple_vec = test_coords.clone();
885
886        let flat_array: Vec<f64> = test_coords
887            .iter()
888            .flat_map(|&(lng, lat)| vec![lng, lat])
889            .collect();
890
891        // Collect results from all formats
892        let from_lnglat: Vec<LngLat> = lnglat_vec.get_coords().collect();
893        let from_tuples: Vec<LngLat> = tuple_vec.get_coords().collect();
894        let from_flat: Vec<LngLat> = flat_array.get_coords().collect();
895
896        // All should be identical
897        assert_eq!(from_lnglat.len(), 3);
898        assert_eq!(from_tuples.len(), 3);
899        assert_eq!(from_flat.len(), 3);
900
901        for i in 0..3 {
902            assert_eq!(from_lnglat[i], from_tuples[i]);
903            assert_eq!(from_lnglat[i], from_flat[i]);
904            assert_eq!(from_tuples[i], from_flat[i]);
905        }
906    }
907
908    #[test]
909    fn test_coord_source_extreme_coordinates() {
910        // Test with extreme valid coordinates
911        let extreme_coords = vec![
912            (-180.0, -90.0),          // Southwest corner
913            (180.0, 90.0),            // Northeast corner
914            (0.0, 0.0),               // Origin
915            (-179.999999, 89.999999), // Near limits
916        ];
917
918        let collected: Vec<LngLat> = extreme_coords.get_coords().collect();
919
920        assert_eq!(collected.len(), 4);
921        assert_eq!(collected[0], LngLat::new_deg(-180.0, -90.0));
922        assert_eq!(collected[1], LngLat::new_deg(180.0, 90.0));
923        assert_eq!(collected[2], LngLat::new_deg(0.0, 0.0));
924        assert_eq!(collected[3], LngLat::new_deg(-179.999999, 89.999999));
925    }
926
927    #[test]
928    fn test_coord_source_precision_preservation() {
929        // Test that high-precision coordinates are preserved exactly
930        let high_precision = vec![
931            (-122.419416123456, 37.774928987654),
932            (-74.006012345679, 40.712776543211),
933        ];
934
935        let flat_array = vec![
936            -122.419416123456,
937            37.774928987654,
938            -74.006012345679,
939            40.712776543211,
940        ];
941
942        let from_tuples: Vec<LngLat> = high_precision.get_coords().collect();
943        let from_flat: Vec<LngLat> = flat_array.get_coords().collect();
944
945        assert_eq!(from_tuples.len(), 2);
946        assert_eq!(from_flat.len(), 2);
947
948        // Verify exact precision preservation
949        assert_eq!(from_tuples[0].lng_deg, -122.419416123456);
950        assert_eq!(from_tuples[0].lat_deg, 37.774928987654);
951        assert_eq!(from_flat[0].lng_deg, -122.419416123456);
952        assert_eq!(from_flat[0].lat_deg, 37.774928987654);
953
954        assert_eq!(from_tuples[1].lng_deg, -74.006012345679);
955        assert_eq!(from_tuples[1].lat_deg, 40.712776543211);
956        assert_eq!(from_flat[1].lng_deg, -74.006012345679);
957        assert_eq!(from_flat[1].lat_deg, 40.712776543211);
958    }
959
960    #[test]
961    fn test_coord_source_large_dataset_consistency() {
962        // Test with a larger dataset to ensure iterator doesn't have off-by-one errors
963        let large_dataset: Vec<(f64, f64)> = (0..1000)
964            .map(|i| (i as f64 * 0.001, (i + 1) as f64 * 0.001))
965            .collect();
966
967        let collected: Vec<LngLat> = large_dataset.get_coords().collect();
968
969        assert_eq!(collected.len(), 1000);
970
971        // Spot check first, middle, and last elements
972        assert_eq!(collected[0], LngLat::new_deg(0.0, 0.001));
973        assert_eq!(collected[499], LngLat::new_deg(0.499, 0.5));
974        assert_eq!(collected[999], LngLat::new_deg(0.999, 1.0));
975
976        // Verify all elements are correct
977        for (i, coord) in collected.iter().enumerate() {
978            let expected_lng = i as f64 * 0.001;
979            let expected_lat = (i + 1) as f64 * 0.001;
980            assert_eq!(
981                coord.lng_deg, expected_lng,
982                "Longitude mismatch at index {}",
983                i
984            );
985            assert_eq!(
986                coord.lat_deg, expected_lat,
987                "Latitude mismatch at index {}",
988                i
989            );
990        }
991    }
992
993    #[test]
994    fn test_coords_to_lnglat_vec_tuples_lnglat() {
995        let tuples = vec![
996            (-122.4194, 37.7749), // San Francisco (lng, lat)
997            (-74.0060, 40.7128),  // New York
998        ];
999
1000        let input = CoordinateInput::Tuples(tuples);
1001        let result = coords_to_lnglat_vec(&input);
1002
1003        assert_eq!(result.len(), 2);
1004        assert_eq!(result[0], LngLat::new_deg(-122.4194, 37.7749));
1005        assert_eq!(result[1], LngLat::new_deg(-74.0060, 40.7128));
1006    }
1007
1008    #[test]
1009    fn test_coords_to_lnglat_vec_tuples_latlng() {
1010        let tuples = vec![
1011            (37.7749, -122.4194), // San Francisco (lat, lng)
1012            (40.7128, -74.0060),  // New York
1013        ];
1014
1015        let input = CoordinateInput::Tuples(tuples);
1016        let result = coords_to_lnglat_vec(&input);
1017
1018        assert_eq!(result.len(), 2);
1019        assert_eq!(result[0], LngLat::new_deg(-122.4194, 37.7749));
1020        assert_eq!(result[1], LngLat::new_deg(-74.0060, 40.7128));
1021    }
1022
1023    #[test]
1024    fn test_coords_to_lnglat_vec_flat_array() {
1025        let flat = vec![
1026            -122.4194, 37.7749, // San Francisco
1027            -74.0060, 40.7128, // New York
1028        ];
1029
1030        let input = CoordinateInput::FlatArray(flat);
1031        let result = coords_to_lnglat_vec(&input);
1032
1033        assert_eq!(result.len(), 2);
1034        assert_eq!(result[0], LngLat::new_deg(-122.4194, 37.7749));
1035        assert_eq!(result[1], LngLat::new_deg(-74.0060, 40.7128));
1036    }
1037
1038    #[test]
1039    fn test_coords_to_lnglat_vec_geojson() {
1040        let geojson = vec![
1041            GeoPoint {
1042                coordinates: [-122.4194, 37.7749],
1043            },
1044            GeoPoint {
1045                coordinates: [-74.0060, 40.7128],
1046            },
1047        ];
1048
1049        let input = CoordinateInput::GeoJson(geojson);
1050        let result = coords_to_lnglat_vec(&input);
1051
1052        assert_eq!(result.len(), 2);
1053        assert_eq!(result[0], LngLat::new_deg(-122.4194, 37.7749));
1054        assert_eq!(result[1], LngLat::new_deg(-74.0060, 40.7128));
1055    }
1056
1057    #[test]
1058    fn test_coords_to_lnglat_vec_already_lnglat() {
1059        let coords = vec![
1060            LngLat::new_deg(-122.4194, 37.7749),
1061            LngLat::new_deg(-74.0060, 40.7128),
1062        ];
1063
1064        let input = CoordinateInput::Already(coords.clone());
1065        let result = coords_to_lnglat_vec(&input);
1066
1067        assert_eq!(result, coords);
1068    }
1069
1070    #[test]
1071    fn test_tuples_to_lnglat_vec_auto_empty() {
1072        let empty: Vec<(f64, f64)> = vec![];
1073        let result = tuples_to_lnglat_vec_auto(&empty);
1074        assert_eq!(result.len(), 0);
1075    }
1076
1077    #[test]
1078    fn test_flat_array_to_lnglat_vec_auto_odd_length() {
1079        let odd_array = vec![-122.4194, 37.7749, -74.0060];
1080        let result = flat_array_to_lnglat_vec_auto(&odd_array);
1081
1082        assert_eq!(result.len(), 1);
1083        assert_eq!(result[0], LngLat::new_deg(-122.4194, 37.7749));
1084    }
1085
1086    #[test]
1087    fn test_coordinate_input_from_conversions() {
1088        // Test From implementations
1089        let tuples = vec![(-122.4194, 37.7749)];
1090        let _input: CoordinateInput = tuples.into();
1091
1092        let flat = vec![-122.4194, 37.7749];
1093        let _input: CoordinateInput = flat.into();
1094
1095        let geojson = vec![GeoPoint {
1096            coordinates: [-122.4194, 37.7749],
1097        }];
1098        let _input: CoordinateInput = geojson.into();
1099
1100        let coords = vec![LngLat::new_deg(-122.4194, 37.7749)];
1101        let _input: CoordinateInput = coords.into();
1102    }
1103
1104    #[test]
1105    fn test_geopoint_coord_source() {
1106        let points = vec![
1107            GeoPoint {
1108                coordinates: [-122.4194, 37.7749],
1109            },
1110            GeoPoint {
1111                coordinates: [-74.0060, 40.7128],
1112            },
1113        ];
1114
1115        assert_eq!(points.hint_size(), Some(2));
1116        assert_eq!(points.hint_format(), FormatHint::LngLat);
1117
1118        let collected: Vec<LngLat> = points.get_coords().collect();
1119        assert_eq!(collected.len(), 2);
1120        assert_eq!(collected[0], LngLat::new_deg(-122.4194, 37.7749));
1121        assert_eq!(collected[1], LngLat::new_deg(-74.0060, 40.7128));
1122    }
1123
1124    #[test]
1125    fn test_large_dataset_conversion_performance() {
1126        // Test with large dataset to ensure performance
1127        let large_tuples: Vec<(f64, f64)> = (0..10000)
1128            .map(|i| (i as f64 * 0.001, (i + 1) as f64 * 0.001))
1129            .collect();
1130
1131        let result = tuples_to_lnglat_vec_auto(&large_tuples);
1132
1133        assert_eq!(result.len(), 10000);
1134        assert_eq!(result[0], LngLat::new_deg(0.0, 0.001));
1135        assert_eq!(result[9999], LngLat::new_deg(9.999, 10.0));
1136    }
1137
1138    #[test]
1139    fn test_coord_source_f64_slice() {
1140        let flat_array = [
1141            -122.4194, 37.7749, // San Francisco
1142            -74.0060, 40.7128, // New York City
1143        ];
1144
1145        let coords: Vec<_> = flat_array.get_coords().collect();
1146        assert_eq!(coords.len(), 2);
1147        assert_eq!(coords[0], LngLat::new_deg(-122.4194, 37.7749));
1148        assert_eq!(coords[1], LngLat::new_deg(-74.0060, 40.7128));
1149
1150        assert_eq!(flat_array.hint_size(), Some(2));
1151        assert!(matches!(flat_array.hint_format(), FormatHint::Unknown));
1152    }
1153
1154    #[test]
1155    fn test_coord_source_empty_f64_slice() {
1156        let empty_slice: &[f64] = &[];
1157
1158        let coords: Vec<_> = empty_slice.get_coords().collect();
1159        assert_eq!(coords.len(), 0);
1160
1161        assert_eq!(empty_slice.hint_size(), Some(0));
1162        assert!(matches!(empty_slice.hint_format(), FormatHint::Unknown));
1163    }
1164
1165    #[test]
1166    fn test_coord_source_odd_length_f64_slice() {
1167        let odd_array = [-122.4194, 37.7749, -74.0060];
1168
1169        let coords: Vec<_> = odd_array.get_coords().collect();
1170        assert_eq!(coords.len(), 1);
1171        assert_eq!(coords[0], LngLat::new_deg(-122.4194, 37.7749));
1172
1173        assert_eq!(odd_array.hint_size(), Some(1)); // 3 / 2 = 1
1174        assert!(matches!(odd_array.hint_format(), FormatHint::Unknown));
1175    }
1176}