summaryrefslogtreecommitdiffhomepage
path: root/ios/MullvadREST/Relay/Midpoint.swift
blob: 1ff4e866de75e48d23a3a2473397e07334b13b34 (plain)
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
//
//  Midpoint.swift
//  RelaySelector
//
//  Created by Marco Nikic on 2023-07-11.
//  Copyright © 2025 Mullvad VPN AB. All rights reserved.
//

import CoreLocation
import Foundation
import MullvadTypes

public enum Midpoint {
    /// Computes the approximate midpoint of a set of locations.
    ///
    /// This works by calculating the mean Cartesian coordinates, and converting them
    /// back to spherical coordinates. This is approximate, because the semi-minor (polar)
    /// axis is assumed to equal the semi-major (equatorial) axis.
    ///
    /// https://en.wikipedia.org/wiki/Spherical_coordinate_system#Cartesian_coordinates
    static func location(in coordinates: [CLLocationCoordinate2D]) -> CLLocationCoordinate2D {
        var x = 0.0
        var y = 0.0
        var z = 0.0
        var count = 0

        coordinates.forEach { coordinate in
            let cos_lat = cos(coordinate.latitude.toRadians)
            let sin_lat = sin(coordinate.latitude.toRadians)
            let cos_lon = cos(coordinate.longitude.toRadians)
            let sin_lon = sin(coordinate.longitude.toRadians)

            x += cos_lat * cos_lon
            y += cos_lat * sin_lon
            z += sin_lat

            count += 1
        }

        let inv_total_weight = 1.0 / Double(count)
        x *= inv_total_weight
        y *= inv_total_weight
        z *= inv_total_weight

        let longitude = atan2(y, x)
        let hypotenuse = sqrt(x * x + y * y)
        let latitude = atan2(z, hypotenuse)

        return CLLocationCoordinate2D(latitude: latitude.toDegrees, longitude: longitude.toDegrees)
    }
}