2

I'm trying to draw a Great Circle line between two lat/lon points on an MKMapView. This is a line that would appear rounded (a 'straight' line on a globe) and is best visualized here. In fact this very odd WordPress site seems to begin to describe exactly how to do this, but it ends abruptly after the first few steps.

Reading in Apple's documentation I see

In iOS 4.0 and later, you can also use projected map coordinates instead of regions to specify some values. When you project the curved surface of the globe onto a flat surface, you get a two-dimensional version of a map where longitude lines appear to be parallel. Locations and distances on this map are specified using the MKMapPoint, MKMapSize, and MKMapRect data types. You can use these data types to specify the map’s visible region and when specifying the location of overlays.

How I would apply this to a Great Circle overlay I'm not sure. Can anyone help?

Ryan
  • 159
  • 2
  • 14
  • As a follow-up, I haven't yet found a solution, and have moved away from MKMapView. I adopted some JavaScript for the Bing Maps AJAX interface (v7) and intend to display the map in a WebView. – Ryan May 29 '11 at 13:47

4 Answers4

9

I've implemented this for drawing a great circle route for aircraft going between two airports using MKPolyline.

+ (void)createGreatCircleMKPolylineFromPoint:(CLLocationCoordinate2D)point1 
                                     toPoint:(CLLocationCoordinate2D)point2
                                  forMapView:(MKMapView*)mapView
{
double lat1 = point1.latitude;
double lon1 = point1.longitude;
double lat2 = point2.latitude;
double lon2 = point2.longitude;
lat1 = lat1 * (PI/180);
lon1 = lon1 * (PI/180);
lat2 = lat2 * (PI/180);
lon2 = lon2 * (PI/180);
double d = 2 * asin( sqrt(pow(( sin( (lat1-lat2)/2) ), 2) + cos(lat1) * cos(lat2) * pow(( sin( (lon1-lon2)/2) ), 2)));
int numsegs = 100;
CLLocationCoordinate2D *coords = malloc(sizeof(CLLocationCoordinate2D) * numsegs);
double f = 0.0;
for(int i=1; i<=numsegs; i++)
{
    f += 1.0 / (float)numsegs;
    double A=sin((1-f)*d)/sin(d);
    double B=sin(f*d)/sin(d);
    double x = A*cos(lat1) * cos(lon1) +  B * cos(lat2) * cos(lon2);
    double y = A*cos(lat1) * sin(lon1) +  B * cos(lat2) * sin(lon2);
    double z = A*sin(lat1)           +  B*sin(lat2);
    double latr=atan2(z, sqrt(pow(x, 2) + pow(y, 2) ));
    double lonr=atan2(y, x);
    double lat = latr * (180/PI);
    double lon = lonr * (180/PI);
    //        NSLog(@"lat: %f lon: %f", lat, lon);
    CLLocationCoordinate2D loc = CLLocationCoordinate2DMake(lat, lon);
    coords[i - 1] = loc;
}

//check for circling west to east. If the plane is crossing 180, we need
//to draw two lines or else the polyline connects the dots and draws a straight
//line all the way across the map.
CLLocationCoordinate2D prevCoord;
BOOL twoLines = NO;
int numsegs2 = 0;
CLLocationCoordinate2D *coords2;

for(int i=0; i<numsegs; i++)
{
    CLLocationCoordinate2D coord = coords[i];
    if(prevCoord.longitude < -170 && prevCoord.longitude > -180  && prevCoord.longitude < 0 
       && coord.longitude > 170 && coord.longitude < 180 && coord.longitude > 0)
    {
        twoLines = YES;
        coords2 = malloc(sizeof(CLLocationCoordinate2D) * (numsegs - i));
        numsegs2 = numsegs - i;
        for(int j=0; j<numsegs2; j++)
        {
            coords2[j] = coords[i + j];
        }
        break;
    }
    prevCoord = coord;
}

//remove any previously added overlays
[mapView removeOverlays:mapView.overlays];

if(twoLines)
{
    MKPolyline *polyline = [MKPolyline polylineWithCoordinates:coords count:numsegs - numsegs2];
    free(coords);
    [mapView addOverlay:polyline];

    MKPolyline *polyline2 = [MKPolyline polylineWithCoordinates:coords2 count:numsegs2];
    free(coords2);
    [mapView addOverlay:polyline2];
}
else
{
    MKPolyline *polyline = [MKPolyline polylineWithCoordinates:coords count:numsegs];
    free(coords);
    [mapView addOverlay:polyline];
}

}

You've now created the overlay(s), now you just need to provide an MKOverlayView in mapView:viewForOverlay.

- (MKOverlayView*)mapView:(MKMapView *)mapView viewForOverlay:(id<MKOverlay>)overlay
{
    MKPolyline *polyline = (MKPolyline*)overlay;
    MKPolylineView *view = [[[MKPolylineView alloc] initWithPolyline:polyline] autorelease];
    //choose your line params here
    view.lineWidth = 2;
    view.fillColor = [UIColor blueColor];
    return view;
}

Hope this helps.

Screenshot http://s1-03.twitpicproxy.com/photos/large/489178500.png

cberkley
  • 311
  • 4
  • 5
3

It is late in the game, but worth mentioning MKGeodesicPolyline, new since iOS 7.0, which 'traces the shortest path along the surface of the Earth'.

With this it becomes simple to create and add an MKPolylineOverlay of type Geodesic Polyline.

points = [CLLocationCoordinate2DMake(27.123, 85.765),
                  CLLocationCoordinate2DMake(41.444, 106.987)]
geodesic = MKGeodesicPolyline(coordinates: points, count: 2)
mapView.add(geodesic)

remember to include the renderer and give the mapView a delegate:

//MARK: - MKMapView Delegate Method
func mapView(_ mapView: MKMapView, rendererFor overlay: MKOverlay) -> MKOverlayRenderer {
    if overlay is MKGeodesicPolyline {
        let polylineRenderer = MKPolylineRenderer(overlay: overlay)
        polylineRenderer.strokeColor = UIColor.white
        polylineRenderer.lineWidth = 2.5
        return polylineRenderer
    }
}
David
  • 3,285
  • 1
  • 37
  • 54
0

The numsegs parameter should be changeable according to the map zoom level and the distance of the two points. The lat/lon coordinate of the two points can be transformed into pixel coordinate. Thus the numsegs parameter can be viewed as a function of pixel differences.

Brian Lee
  • 11
  • 2
0

This can be accomplish creating a subclass of the MKOverlayPathView class. You need to override the (void)createPath method, and basically you could use a UIBezierPath to create the arc, or create an arc as path directly, which is possible but I haven't done it yet.

Once you define the path on the method, you need to set the path property of the class with the newly created path. That way, the rendering of the path is going to be done automatically.

Hope this helps.

lblasa
  • 6,284
  • 4
  • 27
  • 29