Getting the bounds of an MKMapView

34,032

Solution 1

Okay I officially answered my own question but since I didn't find it anywhere before I'll post the answer here:

//To calculate the search bounds...
//First we need to calculate the corners of the map so we get the points
CGPoint nePoint = CGPointMake(self.mapView.bounds.origin.x + mapView.bounds.size.width, mapView.bounds.origin.y);
CGPoint swPoint = CGPointMake((self.mapView.bounds.origin.x), (mapView.bounds.origin.y + mapView.bounds.size.height));

//Then transform those point into lat,lng values
CLLocationCoordinate2D neCoord;
neCoord = [mapView convertPoint:nePoint toCoordinateFromView:mapView];

CLLocationCoordinate2D swCoord;
swCoord = [mapView convertPoint:swPoint toCoordinateFromView:mapView];

SWIFT 5

public extension MKMapView {

  var newBounds: MapBounds {
    let originPoint = CGPoint(x: bounds.origin.x + bounds.size.width, y: bounds.origin.y)
    let rightBottomPoint = CGPoint(x: bounds.origin.x, y: bounds.origin.y + bounds.size.height)

    let originCoordinates = convert(originPoint, toCoordinateFrom: self)
    let rightBottomCoordinates = convert(rightBottomPoint, toCoordinateFrom: self)

    return MapBounds(
      firstBound: CLLocation(latitude: originCoordinates.latitude, longitude: originCoordinates.longitude),
      secondBound: CLLocation(latitude: rightBottomCoordinates.latitude, longitude: rightBottomCoordinates.longitude)
    )
  }
}

public struct MapBounds {
  let firstBound: CLLocation
  let secondBound: CLLocation
}

Usage

self.mapView.newBounds

Solution 2

Another option is to use the visibleMapRect property on your MKMapView instance and use MKCoordinateForMapPoint() to convert to the lat/lon.

MKMapRect mRect = self.mapView.visibleMapRect;
MKMapPoint neMapPoint = MKMapPointMake(MKMapRectGetMaxX(mRect), mRect.origin.y);
MKMapPoint swMapPoint = MKMapPointMake(mRect.origin.x, MKMapRectGetMaxY(mRect));
CLLocationCoordinate2D neCoord = MKCoordinateForMapPoint(neMapPoint);
CLLocationCoordinate2D swCoord = MKCoordinateForMapPoint(swMapPoint);

Swift 5

let rect = visibleMapRect
let neMapPoint = MKMapPoint(x: rect.maxX, y: rect.origin.y)
let swMapPoint = MKMapPoint(x: rect.origin.x, y: rect.maxY)

let neCoordinate = neMapPoint.coordinate
let swCoordinate = swMapPoint.coordinate

Solution 3

Swift away... (Based on @deadroxy's answer...)

typealias Edges = (ne: CLLocationCoordinate2D, sw: CLLocationCoordinate2D)

extension MKMapView {
    func edgePoints() -> Edges {
          let nePoint = CGPoint(x: self.bounds.maxX, y: self.bounds.origin.y)
    let swPoint = CGPoint(x: self.bounds.minX, y: self.bounds.maxY)
    
    let neCoord = self.convert(nePoint, toCoordinateFrom: self)
    let swCoord = self.convert(swPoint, toCoordinateFrom: self)
    
    return (ne: neCoord, sw: swCoord)
    }
}

Solution 4

This extension solves this problem and maintain the centerCoordinate syntax in Swift 5

extension MKMapView {
    var northWestCoordinate: CLLocationCoordinate2D {
        return MKMapPoint(x: visibleMapRect.minX, y: visibleMapRect.minY).coordinate
    }

    var northEastCoordinate: CLLocationCoordinate2D {
        return MKMapPoint(x: visibleMapRect.maxX, y: visibleMapRect.minY).coordinate
    }

    var southEastCoordinate: CLLocationCoordinate2D {
        return MKMapPoint(x: visibleMapRect.maxX, y: visibleMapRect.maxY).coordinate
    }

    var southWestCoordinate: CLLocationCoordinate2D {
        return MKMapPoint(x: visibleMapRect.minX, y: visibleMapRect.maxY).coordinate
    }
}

Solution 5

This http://wiki.openstreetmap.org/wiki/Bounding_Box is a document for bounding box

bbox = left,bottom,right,top
bbox = min Longitude , min Latitude , max Longitude , max Latitude

enter image description here

You can have a BoundingBox struct that represents this

struct BoundingBox {
  let min: CLLocationCoordinate2D
  let max: CLLocationCoordinate2D

  init(rect: MKMapRect) {
    let bottomLeft = MKMapPointMake(rect.origin.x, MKMapRectGetMaxY(rect))
    let topRight = MKMapPointMake(MKMapRectGetMaxX(rect), rect.origin.y)

    min = MKCoordinateForMapPoint(bottomLeft)
    max = MKCoordinateForMapPoint(topRight)
  }

  var points: [CLLocationDegrees] {
    return [
      min.latitude,
      min.longitude,
      max.latitude
      max.longitude,
    ]
  }
}

The visibleMapRect is the same as region.span

let mapView = MKMapView(frame: CGRect(x: 0, y: 0, width: 320, height: 640))
XCTAssertEqual(mapView.userLocation.coordinate.latitude, 0)
XCTAssertEqual(mapView.userLocation.coordinate.longitude, 0)

let boundingBox = BoundingBox(rect: mapView.visibleMapRect)
XCTAssertEqual(boundingBox.max.longitude-boundingBox.min.longitude, mapView.region.span.longitudeDelta)
XCTAssertEqual(boundingBox.max.latitude-boundingBox.min.latitude, mapView.region.span.latitudeDelta)
Share:
34,032
deadroxy
Author by

deadroxy

Computer Whisperer (PhD). Formerly CEO & Co-Founder of frestyl.

Updated on December 05, 2021

Comments

  • deadroxy
    deadroxy over 2 years

    In order to setup a query to an external server I want to get the bounds of the current Map View in an iPhone app I'm building. UIView should respond to bounds but it seems MKMapView doesn't. After setting a region and zooming in the map I try to get the bounds. I'm stuck on the first step which is to try to get the CGPoints that represent the SE and NW corners of the map. After that I was going to use:

    - (CLLocationCoordinate2D)convertPoint:(CGPoint)point toCoordinateFromView:(UIView *)view
    

    To transform the points into map coordinates. But I can't even get that far...

    //Recenter and zoom map in on search location
    MKCoordinateRegion region =  {{0.0f, 0.0f}, {0.0f, 0.0f}};
    region.center = mySearchLocation.searchLocation.coordinate;
    region.span.longitudeDelta = 0.01f;
    region.span.latitudeDelta = 0.01f;
    [self.mapView setRegion:region animated:YES];
    
    
    //After the new search location has been added to the map, and the map zoomed, we need to update the search bounds
    //First we need to calculate the corners of the map
    CGPoint se = CGPointMake(self.mapView.bounds.origin.x, mapView.bounds.origin.y);
    CGPoint nw = CGPointMake((self.mapView.bounds.origin.x + mapView.bounds.size.width), (mapView.bounds.origin.y + mapView.bounds.size.height));
    NSLog(@"points are: se %@, nw %@", se, nw);
    

    The code compiles without warnings however se and nw are both null. Looking at self.mapView.bounds.origin.x the variable is 0. Trying to NSLog directly self.mapView.bounds.size.width gives me a "Program received signal: “EXC_BAD_ACCESS”." which seems to come from NSLog.

    Anyone know the proper way to get the south east corner and northwest corner (in map coordinates) from the visible area of a MKMapView?

    EDIT: It seems whenever you asked something here the answer comes to you right after. I was using %@ instead of @f to print each variable in NSLog which was throwing errors there. I also discovered the annotationVisibleRect property of MKMapview. It seems though that the annotationVisibleRect is based on the parent view coordinates.

  • neilkimmett
    neilkimmett about 12 years
    Also, instead of calculating the x and y coordinates yourself you can use the MKMapRectGetMaxX(mRect) (and similar) convenience functions.
  • joneswah
    joneswah about 10 years
    It is worth noting that visibleMapRect is true to its name and only returns the visible part of the map. In our app we have a slide out UIView which comes over the MapView when an item is selected. When this is out the visibleMapRect only returns the part of the map which is still visible. In our instance we needed the whole bounds so @deadroxy solution worked better for us.
  • Owen Godfrey
    Owen Godfrey over 9 years
    There are a few limitations associated with this solution. It was alright back in 2010, but now the nature of the maps have changed. The following are important differences; a) the coordinates don't make sense if the user rotates the view, and b) if the map enters 3D mode, then the visible region will likely be a trapezoid instead of a rectangle.
  • Qadir Hussain
    Qadir Hussain almost 9 years
    how can I use this in my view controller?
  • Aviel Gross
    Aviel Gross almost 9 years
    @QadirHussain once you add this into your project you can call self.myMapView.edgePoints() which will return you the tuple with ne and sw parameters (as I defined in the typealias)
  • Mihado
    Mihado about 8 years
    But even if I use visibleMapRect, how can I go about effectively querying my backend for locations that would exist in the viewable portion of the map. I'm not entirely sure on how to implement. Any help would be super appreciated! Thank you!
  • webjunkie
    webjunkie about 8 years
    var edgePoints = mapView.edgePoints() print(edgePoints.ne.latitude) print(edgePoints.ne.longitude) print(edgePoints.sw.longitude) print(edgePoints.sw.latitude)
  • AamirR
    AamirR about 6 years
    Very well done, thanks, just missed the Note and got me troubled, I saw the bbox definition on the first line which is obviously correct, BTW I dont see a tuple, what tuple is this Note about, can we fix the var points to match bbox