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 CGPoint
s, 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. 🤓