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.
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
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
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)
}
}
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
}
}
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
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)
Updated @onmyway133's excellent answer for my purposes, I needed the coordinates of all four corners:
struct BoundingBox {
let topRight: CLLocationCoordinate2D
let topLeft: CLLocationCoordinate2D
let bottomRight: CLLocationCoordinate2D
let bottomLeft: CLLocationCoordinate2D
init(rect: MKMapRect) {
topRight = MKMapPoint(x: rect.maxX, y: rect.origin.y).coordinate
topLeft = MKMapPoint(x: rect.origin.x, y: rect.origin.y).coordinate
bottomRight = MKMapPoint(x: rect.maxX, y: rect.maxY).coordinate
bottomLeft = MKMapPoint(x: rect.origin.x, y: rect.maxY).coordinate
}
var items: [String: CLLocationCoordinate2D] {
return [
"topRight": topRight,
"topLeft": topLeft,
"bottomRight": bottomRight,
"bottomLeft": bottomLeft,
]
}
var points: [CLLocationDegrees] {
return [
topRight.latitude,
topRight.longitude,
topLeft.latitude,
topLeft.longitude,
bottomRight.latitude,
bottomRight.longitude,
bottomLeft.latitude,
bottomLeft.longitude,
]
}
}
And an example of how I used this data:
let boundingBox = BoundingBox(rect: mapView.visibleMapRect)
var annotations = Array<MKPointAnnotation>()
for point in boundingBox.items {
let newPoint = MKPointAnnotation()
newPoint.coordinate = point.value
annotations.append(newPoint)
}
mapView.addAnnotations(annotations)
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With