harry lakins harry lakins - 3 months ago 16
Swift Question

Why is the my overlay line not resulting how I expect in the map based on a polar formulae (swift, ios)?

My aim

have a line drawn based on three bits of data: given coordinates, a distance, a bearing.

I want a line to be drawn from my current location in the direction of the bearing, at and end point of the distance.

Bare in mind

I do get a line on the map, updated every time current location is, so there isn't a problem in that sense (the code i've shown may not include all definitions - but assume there is no compile error, etc)

The problem

My line appears, but only always in the same direction, at a distance across the whole world (instead of 1km!). I tried changing the heading input but still get the same line.

The code

func drawHeadingLine(currentLocation: [Double], heading: Double){

//to radians
let headingR = heading * 0.0174533;

if viewLine != nil{
mapView.removeOverlay(viewLine);
}
// setup data for polar formulae
let pi = M_PI;
let currentLONG = currentLocation[0];
let currentLAT = currentLocation[1];
let currentLongRadian = currentLocation[0] * (pi * 180);
let currentLatRadian = currentLocation[1] * (pi * 180);
let lineDistance: Double = 1; //km
let earthRadius: Double = 6378.1; //km
let angularDistance: Double = lineDistance / earthRadius;

//use polar formulae (given point, distance, bearing) for line end point

let latitude2 = asin( sin(currentLatRadian) * cos(angularDistance)
+ cos(currentLatRadian) * sin(angularDistance) * cos(headingR) );

let longitude2 = currentLongRadian + atan2( cos(angularDistance) - sin(currentLatRadian) * sin(latitude2),
sin(headingR) * sin(angularDistance) * cos(currentLatRadian));


var coordinates1 = CLLocationCoordinate2D();
coordinates1.longitude = currentLONG;
coordinates1.latitude = currentLAT;

var coordinates2 = CLLocationCoordinate2D();
coordinates2.longitude = longitude2;
coordinates2.latitude = latitude2;

var lineCoords = [ coordinates1,coordinates2];

viewLine = MKPolyline(coordinates: &lineCoords, count: lineCoords.count);
self.mapView.addOverlay(viewLine);

}


func mapView(mapView: MKMapView!, viewForOverlay overlay: MKOverlay!) -> MKOverlayRenderer! {

if (overlay is MKPolyline) {
viewLine = overlay;
let line = MKPolylineRenderer(overlay: viewLine);
line.strokeColor = UIColor.redColor().colorWithAlphaComponent(0.5);
line.lineWidth = 5;
return line;
}

return nil
}


enter image description here
Please say if you need more information. Thanks for any help.

Answer

Your math was off. Try this:

func drawHeadingLine(currentLocation: [Double], heading: Double){
    if viewLine != nil {
        mapView.removeOverlay(viewLine!)
    }

    let (lat, long) = (currentLocation[0], currentLocation[1])
    let headingR = heading * M_PI / 180.0
    let lineDistance = 1.0 //km

    let latDelta = cos(headingR) * lineDistance / EarthMeasurement.lengthOfLatitude(at: lat)
    let longDelta = sin(headingR) * lineDistance / EarthMeasurement.lengthOfLongitude(at: lat)

    let pointA = CLLocationCoordinate2D(latitude: lat, longitude: long)
    let pointB = CLLocationCoordinate2D(latitude: lat + latDelta, longitude: long + longDelta)

    // For debugging, let's calculate a geodesic distance between these 2 points
    // Results are in meters
    let locationA = CLLocation(latitude: lat, longitude: long)
    let locationB = CLLocation(latitude: lat + latDelta, longitude: long + longDelta)
    print("heading = \(heading), distance = \(locationA.distanceFromLocation(locationB)")

    var coordinates = [pointA, pointB]
    let polyline = MKPolyline(coordinates: &coordinates, count: 2)
    self.mapView.addOverlay(polyline)
}

And the EarthMeasurement class:

final class EarthMeasurement {
    static func lengthOfLatitude(at latitude: Double) -> Double {
        return 110.574
    }

    static func lengthOfLongitude(at latitude: Double) -> Double {
        let latitudeR = latitude * M_PI / 180.0
        return cos(latitudeR) * 111.320
    }
}

The idea is that Map Kit deals in latitudes and longitude, not in kilometer, so we need to know how many kilometer are there in a latitude / longitude. This is far from simple as the Earth is not a perfect sphere. but we can make approximations:

  • The length of 1 degree of latitude does become longer as you approach the poles, but the difference is small: about 1km (or 1%) between the equator and the poles. We can ignore that and use the length at the equator, which according to Wikipedia is 111.574km.
  • The formula for the length of 1 degree of latitude is taken from this question.

This does not give a point that is exactly 1km away due to the approximations we used. The error is smaller at latitudes near the equator and progressively increases as you approach the poles (~10m). If you want more accurate result, adjust the EarthMeasurement class accordingly.