scirs2-spatial 0.4.2

Spatial algorithms module for SciRS2 (scirs2-spatial)
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
//! Geospatial Functionality Example
//!
//! This example demonstrates various geospatial operations including:
//! - Distance calculations (Haversine and Vincenty)
//! - Bearing and navigation calculations
//! - Coordinate system transformations
//! - Spherical geometry operations

use scirs2_spatial::geospatial::*;
use std::f64::consts::PI;

#[allow(dead_code)]
fn main() -> Result<(), Box<dyn std::error::Error>> {
    println!("=== Geospatial Functionality Example ===\n");

    // Example 1: Distance calculations between cities
    println!("1. Distance Calculations Between Major Cities");
    distance_calculations_example()?;
    println!();

    // Example 2: Navigation and bearing calculations
    println!("2. Navigation and Bearing Calculations");
    navigation_example()?;
    println!();

    // Example 3: Coordinate system transformations
    println!("3. Coordinate System Transformations");
    coordinate_transformations_example()?;
    println!();

    // Example 4: Flight path analysis
    println!("4. Flight Path Analysis");
    flight_path_example()?;
    println!();

    // Example 5: Spherical geometry
    println!("5. Spherical Geometry Operations");
    spherical_geometry_example()?;
    println!();

    // Example 6: GPS tracking simulation
    println!("6. GPS Tracking Simulation");
    gps_tracking_example()?;
    println!();

    // Example 7: Geospatial analysis
    println!("7. Geospatial Analysis");
    geospatial_analysis_example()?;

    Ok(())
}

#[allow(dead_code)]
fn distance_calculations_example() -> Result<(), Box<dyn std::error::Error>> {
    // Define major world cities
    let cities = [
        ("London", (51.5074, -0.1278)),
        ("Paris", (48.8566, 2.3522)),
        ("New York", (40.7128, -74.0060)),
        ("Tokyo", (35.6762, 139.6503)),
        ("Sydney", (-33.8688, 151.2093)),
        ("São Paulo", (-23.5505, -46.6333)),
        ("Cairo", (30.0444, 31.2357)),
        ("Mumbai", (19.0760, 72.8777)),
    ];

    println!("Distance matrix between major cities (km):");
    println!(
        "{:>12} {:>8} {:>8} {:>8} {:>8} {:>8} {:>8} {:>8} {:>8}",
        "City", "London", "Paris", "New York", "Tokyo", "Sydney", "São Paulo", "Cairo", "Mumbai"
    );

    for (i, (city1, coord1)) in cities.iter().enumerate() {
        print!("{city1:>12}");
        for (j, (_, coord2)) in cities.iter().enumerate() {
            if i <= j {
                let distance_m = haversine_distance(*coord1, *coord2);
                let distance_km = distance_m / 1000.0;
                print!(" {distance_km:>8.0}");
            } else {
                print!(" {:>8}", "-");
            }
        }
        println!();
    }

    // Compare Haversine vs Vincenty for long distances
    println!("\nHaversine vs Vincenty comparison for long distances:");
    let long_distance_pairs = vec![
        ("London", "Sydney", cities[0].1, cities[4].1),
        ("New York", "Tokyo", cities[2].1, cities[3].1),
        ("Cairo", "São Paulo", cities[6].1, cities[5].1),
    ];

    for (city1, city2, coord1, coord2) in long_distance_pairs {
        let haversine_km = haversine_distance(coord1, coord2) / 1000.0;
        let vincenty_km = vincenty_distance(coord1, coord2)? / 1000.0;
        let difference = (vincenty_km - haversine_km).abs();

        println!("{city1} to {city2}:");
        println!("  Haversine: {haversine_km:.1} km");
        println!("  Vincenty:  {vincenty_km:.1} km");
        println!(
            "  Difference: {:.1} km ({:.2}%)",
            difference,
            difference / vincenty_km * 100.0
        );
    }

    Ok(())
}

#[allow(dead_code)]
fn navigation_example() -> Result<(), Box<dyn std::error::Error>> {
    let departure = (40.6413, -73.7781); // JFK Airport, New York
    let destination = (51.4700, -0.4543); // Heathrow Airport, London

    println!("Flight from JFK (New York) to Heathrow (London):");
    println!("Departure: ({:.4}°, {:.4}°)", departure.0, departure.1);
    println!(
        "Destination: ({:.4}°, {:.4}°)",
        destination.0, destination.1
    );

    // Calculate distance and bearings
    let distance_m = haversine_distance(departure, destination);
    let distance_km = distance_m / 1000.0;
    let distance_nm = distance_km * 0.539957; // Convert to nautical miles

    let initial_bearing_rad = initial_bearing(departure, destination);
    let initial_bearing_deg = initial_bearing_rad * 180.0 / PI;

    let final_bearing_rad = final_bearing(departure, destination);
    let final_bearing_deg = final_bearing_rad * 180.0 / PI;

    println!("Distance: {distance_km:.1} km ({distance_nm:.1} nautical miles)");
    println!(
        "Initial bearing: {:.1}°",
        normalize_bearing(initial_bearing_deg)
    );
    println!(
        "Final bearing: {:.1}°",
        normalize_bearing(final_bearing_deg)
    );

    // Calculate waypoints along the great circle route
    println!("\nWaypoints along the great circle route:");
    let num_waypoints = 5;
    for i in 0..=num_waypoints {
        let fraction = i as f64 / num_waypoints as f64;
        let waypoint_distance = distance_m * fraction;
        let waypoint = destination_point(departure, waypoint_distance, initial_bearing_rad);

        println!(
            "  {:.0}%: ({:.4}°, {:.4}°) - {:.0} km from departure",
            fraction * 100.0,
            waypoint.0,
            waypoint.1,
            waypoint_distance / 1000.0
        );
    }

    // Calculate midpoint
    let mid = midpoint(departure, destination);
    let mid_distance_from_departure = haversine_distance(departure, mid) / 1000.0;
    let mid_distance_from_destination = haversine_distance(destination, mid) / 1000.0;

    println!("\nMidpoint: ({:.4}°, {:.4}°)", mid.0, mid.1);
    println!("Distance from departure: {mid_distance_from_departure:.1} km");
    println!("Distance from destination: {mid_distance_from_destination:.1} km");

    Ok(())
}

#[allow(dead_code)]
fn coordinate_transformations_example() -> Result<(), Box<dyn std::error::Error>> {
    let locations = vec![
        ("Greenwich Observatory", (51.4769, 0.0)),
        ("Equator/Prime Meridian", (0.0, 0.0)),
        ("North Pole", (90.0, 0.0)),
        ("Sydney Opera House", (-33.8568, 151.2153)),
        ("Statue of Liberty", (40.6892, -74.0445)),
        ("Eiffel Tower", (48.8584, 2.2945)),
    ];

    println!("Coordinate transformations for various locations:");
    println!(
        "{:>20} {:>12} {:>12} {:>15} {:>15} {:>6} {:>3}",
        "Location", "Latitude", "Longitude", "UTM Easting", "UTM Northing", "Zone", "Let"
    );

    for (name, (lat, lon)) in &locations {
        // UTM conversion (skip poles)
        let lat_val: f64 = *lat;
        if lat_val.abs() < 84.0 {
            let utm_result = geographic_to_utm(*lat, *lon);
            match utm_result {
                Ok((easting, northing, zone, letter)) => {
                    println!(
                        "{name:>20} {lat:>12.4} {lon:>12.4} {easting:>15.0} {northing:>15.0} {zone:>6} {letter:>3}"
                    );
                }
                Err(_) => {
                    println!(
                        "{:>20} {:>12.4} {:>12.4} {:>15} {:>15} {:>6} {:>3}",
                        name, lat, lon, "Error", "Error", "-", "-"
                    );
                }
            }
        } else {
            println!(
                "{:>20} {:>12.4} {:>12.4} {:>15} {:>15} {:>6} {:>3}",
                name, lat, lon, "N/A (Polar)", "N/A (Polar)", "-", "-"
            );
        }
    }

    println!("\nWeb Mercator transformations:");
    println!(
        "{:>20} {:>15} {:>15}",
        "Location", "Web Merc X", "Web Merc Y"
    );

    for (name, (lat, lon)) in &locations {
        let lat_val: f64 = *lat;
        if lat_val.abs() < 85.05 {
            // Web Mercator limit
            let web_merc_result = geographic_to_web_mercator(*lat, *lon);
            match web_merc_result {
                Ok((x, y)) => {
                    println!("{name:>20} {x:>15.0} {y:>15.0}");

                    // Test round trip
                    let (back_lat, back_lon) = web_mercator_to_geographic(x, y);
                    let lat_diff = (back_lat - lat).abs();
                    let lon_diff = (back_lon - lon).abs();

                    if lat_diff > 1e-6 || lon_diff > 1e-6 {
                        println!("    Round trip error: Δlat={lat_diff:.8}, Δlon={lon_diff:.8}");
                    }
                }
                Err(_) => {
                    println!("{:>20} {:>15} {:>15}", name, "Error", "Error");
                }
            }
        } else {
            println!("{:>20} {:>15} {:>15}", name, "Out of range", "Out of range");
        }
    }

    Ok(())
}

#[allow(dead_code)]
fn flight_path_example() -> Result<(), Box<dyn std::error::Error>> {
    // Simulate an aircraft flying from Los Angeles to London
    let departure = (34.0522, -118.2437); // Los Angeles
    let destination = (51.5074, -0.1278); // London

    // Define a planned route with waypoints
    let planned_route = [
        departure,
        (40.0, -100.0), // Over central US
        (50.0, -60.0),  // Over Atlantic
        (55.0, -20.0),  // Approaching Europe
        destination,
    ];

    println!("Flight path analysis: Los Angeles to London");
    println!("Planned route waypoints:");

    let mut total_distance = 0.0;
    for (i, waypoint) in planned_route.iter().enumerate() {
        if i > 0 {
            let segment_distance = haversine_distance(planned_route[i - 1], *waypoint);
            total_distance += segment_distance;
            let bearing = initial_bearing(planned_route[i - 1], *waypoint);

            println!(
                "  Waypoint {}: ({:.2}°, {:.2}°) - {:.0} km, bearing {:.0}°",
                i,
                waypoint.0,
                waypoint.1,
                segment_distance / 1000.0,
                normalize_bearing(bearing * 180.0 / PI)
            );
        } else {
            println!(
                "  Waypoint {}: ({:.2}°, {:.2}°) - Departure",
                i, waypoint.0, waypoint.1
            );
        }
    }

    println!("Total planned distance: {:.0} km", total_distance / 1000.0);

    // Compare with great circle route
    let great_circle_distance = haversine_distance(departure, destination);
    let route_efficiency = great_circle_distance / total_distance * 100.0;

    println!(
        "Great circle distance: {:.0} km",
        great_circle_distance / 1000.0
    );
    println!("Route efficiency: {route_efficiency:.1}%");

    // Simulate aircraft deviating from planned route
    println!("\nSimulating aircraft tracking:");
    let current_position = (45.0, -75.0); // Somewhere over the Atlantic

    // Calculate cross-track and along-track distances
    let cross_track = cross_track_distance(current_position, departure, destination);
    let along_track = along_track_distance(current_position, departure, destination);

    println!(
        "Current position: ({:.2}°, {:.2}°)",
        current_position.0, current_position.1
    );
    println!("Cross-track distance: {:.1} km", cross_track / 1000.0);
    println!(
        "Along-track distance: {:.0} km from departure",
        along_track / 1000.0
    );

    let remaining_distance = great_circle_distance - along_track;
    println!(
        "Estimated remaining distance: {:.0} km",
        remaining_distance / 1000.0
    );

    Ok(())
}

#[allow(dead_code)]
fn spherical_geometry_example() -> Result<(), Box<dyn std::error::Error>> {
    // Create a polygon representing the Bermuda Triangle
    let bermuda_triangle = vec![
        (25.7617, -80.1918), // Miami
        (32.3078, -64.7505), // Bermuda
        (18.2208, -66.5901), // San Juan, Puerto Rico
    ];

    println!("Bermuda Triangle analysis:");
    println!("Vertices:");
    for (i, (lat, lon)) in bermuda_triangle.iter().enumerate() {
        let name = match i {
            0 => "Miami",
            1 => "Bermuda",
            2 => "San Juan",
            _ => "Unknown",
        };
        println!("  {name}: ({lat:.4}°, {lon:.4}°)");
    }

    // Calculate area
    let area_sq_m = spherical_polygon_area(&bermuda_triangle)?;
    let area_sq_km = area_sq_m / 1_000_000.0;

    println!("Area: {area_sq_km:.0} km²");

    // Calculate perimeter
    let mut perimeter = 0.0;
    for i in 0..bermuda_triangle.len() {
        let j = (i + 1) % bermuda_triangle.len();
        perimeter += haversine_distance(bermuda_triangle[i], bermuda_triangle[j]);
    }

    println!("Perimeter: {:.0} km", perimeter / 1000.0);

    // Test points inside and outside the triangle
    let test_points = vec![
        (25.0, -75.0, "Center of triangle"),
        (30.0, -70.0, "Inside triangle"),
        (20.0, -60.0, "Outside triangle (east)"),
        (35.0, -80.0, "Outside triangle (north)"),
        (15.0, -75.0, "Outside triangle (south)"),
    ];

    println!("\nPoint-in-polygon tests:");
    for (lat, lon, description) in test_points {
        let inside = point_in_spherical_polygon((lat, lon), &bermuda_triangle);
        println!(
            "  ({:.1}°, {:.1}°) - {}: {}",
            lat,
            lon,
            description,
            if inside { "Inside" } else { "Outside" }
        );
    }

    // Create a larger polygon (approximate Caribbean Sea boundary)
    let caribbean = vec![
        (10.0, -85.0), // Costa Rica
        (20.0, -88.0), // Yucatan
        (25.0, -85.0), // Florida
        (25.0, -75.0), // Bahamas
        (18.0, -65.0), // Puerto Rico
        (12.0, -62.0), // Trinidad
        (10.0, -65.0), // Venezuela
        (8.0, -75.0),  // Colombia
        (8.0, -82.0),  // Panama
    ];

    let caribbean_area = spherical_polygon_area(&caribbean)? / 1_000_000.0;
    println!("\nCaribbean Sea (approximate area): {caribbean_area:.0} km²");

    Ok(())
}

#[allow(dead_code)]
fn gps_tracking_example() -> Result<(), Box<dyn std::error::Error>> {
    println!("GPS tracking simulation: Hiking trail");

    // Simulate GPS coordinates along a hiking trail
    let trail_points = [
        (37.8651, -119.5383), // Yosemite Valley start
        (37.8701, -119.5234), // Moving northeast
        (37.8781, -119.5145), // Continuing
        (37.8834, -119.5089), // Elevation gain
        (37.8889, -119.5012), // Near peak
        (37.8923, -119.4967), // Peak
        (37.8889, -119.5012), // Return
        (37.8834, -119.5089), // Descent
        (37.8781, -119.5145), // Continuing descent
        (37.8651, -119.5383), // Back to start
    ];

    println!("Trail GPS coordinates:");
    let mut total_distance = 0.0;
    let elevations = [
        1200.0, 1250.0, 1320.0, 1400.0, 1520.0, 1650.0, 1520.0, 1400.0, 1320.0, 1200.0,
    ];

    for (i, ((lat, lon), elevation)) in trail_points.iter().zip(elevations.iter()).enumerate() {
        let time = format!("{}:{:02}", 9 + i / 2, (i % 2) * 30); // Simulate time every 30 minutes

        if i > 0 {
            let segment_distance = haversine_distance(trail_points[i - 1], trail_points[i]);
            let elevation_change = elevation - elevations[i - 1];
            total_distance += segment_distance;

            // Calculate speed (assuming 30 minutes between points)
            let speed_kmh = (segment_distance / 1000.0) / 0.5; // 0.5 hours

            println!("  {time}: ({lat:.4}°, {lon:.4}°) - {elevation:.0}m - {segment_distance:.1}m segment, {elevation_change:.1}m elevation change, {speed_kmh:.1} km/h");
        } else {
            println!("  {time}: ({lat:.4}°, {lon:.4}°) - {elevation:.0}m - Start");
        }
    }

    let total_km = total_distance / 1000.0;
    let total_elevation_gain = elevations
        .iter()
        .zip(elevations.iter().skip(1))
        .map(|(prev, curr)| if curr > prev { curr - prev } else { 0.0 })
        .sum::<f64>();

    println!("\nHike summary:");
    println!("  Total distance: {total_km:.2} km");
    println!("  Total elevation gain: {total_elevation_gain:.0} m");
    println!("  Average speed: {:.1} km/h", total_km / 4.5); // 4.5 hours total

    // Calculate some statistics
    let max_elevation = elevations.iter().fold(0.0f64, |acc, &x| acc.max(x));
    let min_elevation = elevations.iter().fold(f64::INFINITY, |acc, &x| acc.min(x));

    println!("  Elevation range: {min_elevation:.0}m - {max_elevation:.0}m");

    Ok(())
}

#[allow(dead_code)]
fn geospatial_analysis_example() -> Result<(), Box<dyn std::error::Error>> {
    println!("Geospatial analysis: Earthquake monitoring network");

    // Simulate seismic monitoring stations around the San Andreas Fault
    let monitoring_stations = vec![
        ("Berkeley", (37.8719, -122.2585)),
        ("Stanford", (37.4419, -122.1430)),
        ("Parkfield", (35.8997, -120.4339)),
        ("Paso Robles", (35.6266, -120.6909)),
        ("Cholame", (35.7214, -120.3085)),
        ("San Juan Bautista", (36.8455, -121.5374)),
    ];

    // Simulate earthquake epicenter
    let earthquake = (35.7000, -120.4000); // Near Parkfield
    let magnitude = 5.2;

    println!("Monitoring stations:");
    for (name, (lat, lon)) in &monitoring_stations {
        println!("  {name}: ({lat:.4}°, {lon:.4}°)");
    }

    println!("\nEarthquake event:");
    println!("  Epicenter: ({:.4}°, {:.4}°)", earthquake.0, earthquake.1);
    println!("  Magnitude: M{magnitude:.1}");

    // Calculate distances from each station to earthquake
    println!("\nStation distances from epicenter:");
    let mut station_distances: Vec<_> = monitoring_stations
        .iter()
        .map(|(name, coord)| {
            let distance_km = haversine_distance(*coord, earthquake) / 1000.0;
            (name, distance_km)
        })
        .collect();

    // Sort by distance
    station_distances.sort_by(|a, b| a.1.partial_cmp(&b.1).expect("Operation failed"));

    for (name, distance) in &station_distances {
        // Estimate P-wave arrival time (approximately 6 km/s)
        let p_wave_time = distance / 6.0; // seconds

        // Estimate S-wave arrival time (approximately 3.5 km/s)
        let s_wave_time = distance / 3.5; // seconds

        println!(
            "  {name}: {distance:.1} km - P-wave: {p_wave_time:.1}s, S-wave: {s_wave_time:.1}s"
        );
    }

    // Find the centroid of the monitoring network
    let centroid_lat = monitoring_stations
        .iter()
        .map(|(_, (lat, _))| lat)
        .sum::<f64>()
        / monitoring_stations.len() as f64;
    let centroid_lon = monitoring_stations
        .iter()
        .map(|(_, (_, lon))| lon)
        .sum::<f64>()
        / monitoring_stations.len() as f64;
    let centroid = (centroid_lat, centroid_lon);

    println!("\nNetwork analysis:");
    println!(
        "  Network centroid: ({:.4}°, {:.4}°)",
        centroid.0, centroid.1
    );

    let distance_to_centroid = haversine_distance(earthquake, centroid) / 1000.0;
    println!("  Distance from earthquake to network centroid: {distance_to_centroid:.1} km");

    // Calculate network coverage
    let mut max_station_distance = 0.0f64;
    for i in 0..monitoring_stations.len() {
        for j in (i + 1)..monitoring_stations.len() {
            let distance =
                haversine_distance(monitoring_stations[i].1, monitoring_stations[j].1) / 1000.0;
            max_station_distance = max_station_distance.max(distance);
        }
    }

    println!("  Maximum inter-station distance: {max_station_distance:.1} km");

    // Estimate detection threshold
    let closest_station_distance = station_distances[0].1;
    let estimated_magnitude_threshold = 1.0 + 2.0 * (closest_station_distance / 100.0).log10();

    println!(
        "  Estimated detection threshold at closest station: M{estimated_magnitude_threshold:.1}"
    );

    Ok(())
}

/// Helper function to format coordinates in degrees, minutes, seconds
#[allow(dead_code)]
fn format_dms(decimal_degrees: f64, is_latitude: bool) -> String {
    let abs_degrees = decimal_degrees.abs();
    let _degrees = abs_degrees.floor() as i32;
    let minutes = ((abs_degrees - _degrees as f64) * 60.0).floor() as i32;
    let seconds = ((abs_degrees - _degrees as f64) * 60.0 - minutes as f64) * 60.0;

    let direction = if is_latitude {
        if decimal_degrees >= 0.0 {
            "N"
        } else {
            "S"
        }
    } else if decimal_degrees >= 0.0 {
        "E"
    } else {
        "W"
    };

    format!("{_degrees}°{minutes}'{seconds:.1}\"{direction}")
}

/// Calculate the centroid of a set of geographic points
#[allow(dead_code)]
fn geographic_centroid(points: &[(f64, f64)]) -> (f64, f64) {
    if points.is_empty() {
        return (0.0, 0.0);
    }

    let mut x_sum = 0.0;
    let mut y_sum = 0.0;
    let mut z_sum = 0.0;

    for (lat, lon) in points {
        let lat_rad = lat * PI / 180.0;
        let lon_rad = lon * PI / 180.0;

        x_sum += lat_rad.cos() * lon_rad.cos();
        y_sum += lat_rad.cos() * lon_rad.sin();
        z_sum += lat_rad.sin();
    }

    let n = points.len() as f64;
    let x_avg = x_sum / n;
    let y_avg = y_sum / n;
    let z_avg = z_sum / n;

    let lon_avg = y_avg.atan2(x_avg);
    let hyp = (x_avg.powi(2) + y_avg.powi(2)).sqrt();
    let lat_avg = z_avg.atan2(hyp);

    (lat_avg * 180.0 / PI, lon_avg * 180.0 / PI)
}