37

I have a custom class that extends NSObject and implements the MKOverlay protocol. As a result, I need to implement the protocol's boundingMapRect property which is an MKMapRect. To create an MKMapRect I can of course use MKMapRectMake to make one. However, I don't know how to create an MKMapRect using that data I have which is two points, each specified by a latitude and longitude. MKMapRectMake's docs state:

MKMapRect MKMapRectMake(
    double x,
    double y,
    double width,
    double height
);

Parameters
x
    The point along the east-west axis of the map projection to use for the origin.
y
    The point along the north-south axis of the map projection to use for the origin.
width
    The width of the rectangle (measured using map points).
height
    The height of the rectangle (measured using map points).
Return Value
    A map rectangle with the specified values.

The latitude and longitude values I have to spec out the MKMapRect are:

24.7433195, -124.7844079
49.3457868, -66.9513812

The target MKMapRect would therefore need to spec out an area that looks about like this: The Target MKMapRect

So, to reiterate, how do I use my lat/lon values to create an MKMapRect that I can set as MKOverlay protocol's @property (nonatomic, readonly) MKMapRect boundingMapRect property?

ROMANIA_engineer
  • 54,432
  • 29
  • 203
  • 199
John Erck
  • 9,478
  • 8
  • 61
  • 71

4 Answers4

45

This should do it:

// these are your two lat/long coordinates
CLLocationCoordinate2D coordinate1 = CLLocationCoordinate2DMake(lat1,long1);
CLLocationCoordinate2D coordinate2 = CLLocationCoordinate2DMake(lat2,long2);

// convert them to MKMapPoint
MKMapPoint p1 = MKMapPointForCoordinate (coordinate1);
MKMapPoint p2 = MKMapPointForCoordinate (coordinate2);

// and make a MKMapRect using mins and spans
MKMapRect mapRect = MKMapRectMake(fmin(p1.x,p2.x), fmin(p1.y,p2.y), fabs(p1.x-p2.x), fabs(p1.y-p2.y));

this uses the lesser of the two x and y coordinates for your start point, and calculates the x/y spans between the two points for the width and height.

CSmith
  • 13,318
  • 3
  • 39
  • 42
  • You, my friend, are a `legend` and a `boss`! Thank you!! – John Erck Jun 03 '14 at 20:39
  • 3
    This does not work near 180th meridian. Verified with Source : CLLocationCoordinate2D(latitude: -33.8392932, longitude: 151.21519799999999) Destination: CLLocationCoordinate2D(latitude: 39.645516999999998, longitude: -104.598724) – Abin Baby May 24 '18 at 15:00
34

For any number of coordinates, in Swift (4.2):

// Assuming `coordinates` is of type `[CLLocationCoordinate2D]`
let rects = coordinates.lazy.map { MKMapRect(origin: MKMapPoint($0), size: MKMapSize()) }
let fittingRect = rects.reduce(MKMapRect.null) { $0.union($1) }

As noted by @Abin Baby, this will not take wrap around into account (at +/-180 longitude & +/-90 latitude). The result will still be correct, but it will not be the smallest possible rectangle.

Patrick Pijnappel
  • 7,317
  • 3
  • 39
  • 39
  • 1
    Very short and precise answer! Works for all coordinate inputs. Great! – blackjacx Dec 15 '16 at 13:32
  • 1
    This does not work near 180th meridian. Test values Source : CLLocationCoordinate2D(latitude: -33.8392932, longitude: 151.21519799999999) Destination: CLLocationCoordinate2D(latitude: 39.645516999999998, longitude: -104.598724) – Abin Baby May 24 '18 at 15:01
  • This solution works perfectly, and is very simple! I'm new to coding so I though the only way to do this would be with a `for` loop. Is there any way you could explain how the mapping works, and what the `$` signs mean? – yambo Feb 03 '19 at 20:48
  • $0.union($1) has been renamed to MKMapRectUnion($0,$1) – Zeezer Mar 07 '19 at 10:47
  • 1
    @Zeezer the other way around. – Patrick Pijnappel Mar 08 '19 at 21:42
6

Based on Patrick's answer an extension on MKMapRect:

extension MKMapRect {
    init(coordinates: [CLLocationCoordinate2D]) {
        self = coordinates.map({ MKMapPointForCoordinate($0) }).map({ MKMapRect(origin: $0, size: MKMapSize(width: 0, height: 0)) }).reduce(MKMapRectNull, combine: MKMapRectUnion)
    }
}
boliva
  • 5,604
  • 6
  • 37
  • 39
5

This is what worked for me.

No trouble even when crossing between +/-180 longitude and +/-90 latitude.

Swift 4.2

func makeRect(coordinates:[CLLocationCoordinate2D]) -> MKMapRect {
    var rect = MKMapRect()
    var coordinates = coordinates
    if !coordinates.isEmpty {
        let first = coordinates.removeFirst()
        var top = first.latitude
        var bottom = first.latitude
        var left = first.longitude
        var right = first.longitude
        coordinates.forEach { coordinate in
            top = max(top, coordinate.latitude)
            bottom = min(bottom, coordinate.latitude)
            left = min(left, coordinate.longitude)
            right = max(right, coordinate.longitude)
        }
        let topLeft = MKMapPoint(CLLocationCoordinate2D(latitude:top, longitude:left))
        let bottomRight = MKMapPoint(CLLocationCoordinate2D(latitude:bottom, longitude:right))
        rect = MKMapRect(x:topLeft.x, y:topLeft.y,
                         width:bottomRight.x - topLeft.x, height:bottomRight.y - topLeft.y)
    }
    return rect
}
vicegax
  • 4,709
  • 28
  • 37