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}