Blog

Swift Tip: Vector Algebra with Protocols

In last week’s Swift Talk we discussed the vector algebra required to build a routing graph for our running trail app. To work out where the tracks overlap, we needed to calculate the closest distance from a point to a line segment.

We won’t repeat the math here, but we’ll highlight a detail of the implementation.

For testing purposes we wanted to calculate with CGPoints, which makes it easy to draw the points in a view. In the final app we want to calculate with Core Location coordinates. It’s important to note that geographic coordinates are coordinates on a sphere, but for our use case, we’ll pretend that the earth is flat β€” it’s a good enough approximation for the small region we’re doing our routing in.

Instead of implementing the vector operations first on CGPoint, and then re-writing them on CLLocationCoordinate2D, we decided to introduce a simple protocol:

protocol Vector2 {
    associatedtype Component: Numeric
    var x: Component { get }
    var y: Component { get }
    init(x: Component, y: Component)
}

This protocol represents a vector with two numeric components, x and y. We’re using an associated type to specify the type of the components: in our case they can be of type CGFLoat (for CGPoint) or CLLocationDegrees (for CLLocationCoordinate2D). Since CGPoint already has all the protocol requirements, conforming it to Vector2 is a one-liner:

extension CGPoint: Vector2 {}

Now we can start to define vector operations on the Vector2 protocol, for example the dot product and vector addition:

extension Vector2 {
    func dot(_ other: Self) -> Component {
        return x * other.x + y * other.y
    }

    static func +(l: Self, r: Self) -> Self {
        return Self(x: l.x + r.x, y: l.y + r.y)
    }
}

Since the operations are defined on the protocol, we get all of them for free when we conform a new type to the protocol; for instance, with CLLocationCoordinate2D:

extension CLLocationCoordinate2D: Vector2 {
    var x: CLLocationDegrees { return longitude }
    var y: CLLocationDegrees { return latitude }
    init(x: CLLocationDegrees, y: CLLocationDegrees) {
        self.init(latitude: y, longitude: x)
    }
}

If you’d like to learn more, the first episode in this Collection is public. To follow our progress, you can Subscribe. πŸ€“

Stay up-to-date with our newsletter or follow us on Twitter.

Back to the Blog

recent posts