Getting the bounds of an MKMapView

2020-01-25 13:33发布

问题:

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.

回答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];


回答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);


回答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.convertPoint(nePoint, toCoordinateFromView: self)
        let swCoord = self.convertPoint(swPoint, toCoordinateFromView: self)
        return (ne: neCoord, sw: swCoord)
    }
}


回答4:

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)


回答5:

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
    }
}


回答6:

I was able to get this to work with the Parse GeoBox query:

//Calculate the corners of the map to get the points
CGPoint nePoint = CGPointMake(self.mapView.bounds.origin.x + self.mapView.bounds.size.width, self.mapView.bounds.origin.y);
CGPoint swPoint = CGPointMake((self.mapView.bounds.origin.x),(self.mapView.bounds.origin.y+ self.mapView.bounds.size.height));

//Transform points into lat/long values
CLLocationCoordinate2D NECoordinate = [self.mapView convertPoint:nePoint toCoordinateFromView:self.mapView];
CLLocationCoordinate2D SWCoordinate = [self.mapView convertPoint:swPoint toCoordinateFromView:self.mapView];

//Convert to Parse GeoPoints
PFGeoPoint *Southwest = [PFGeoPoint geoPointWithLatitude:SWCoordinate.latitude longitude:SWCoordinate.longitude];
PFGeoPoint *Northeast = [PFGeoPoint geoPointWithLatitude:NECoordinate.latitude longitude:NECoordinate.longitude];


回答7:

this website solve the problem. http://www.softwarepassion.com/how-to-get-geographic-coordinates-of-the-visible-mkmapview-area-in-ios/

MKMapRect mRect = self.mapView.visibleMapRect;

-(CLLocationCoordinate2D)getNECoordinate:(MKMapRect)mRect{
    return [self getCoordinateFromMapRectanglePoint:MKMapRectGetMaxX(mRect) y:mRect.origin.y];
}
-(CLLocationCoordinate2D)getNWCoordinate:(MKMapRect)mRect{
    return [self getCoordinateFromMapRectanglePoint:MKMapRectGetMinX(mRect) y:mRect.origin.y];
}
-(CLLocationCoordinate2D)getSECoordinate:(MKMapRect)mRect{
    return [self getCoordinateFromMapRectanglePoint:MKMapRectGetMaxX(mRect) y:MKMapRectGetMaxY(mRect)];
}
-(CLLocationCoordinate2D)getSWCoordinate:(MKMapRect)mRect{
    return [self getCoordinateFromMapRectanglePoint:mRect.origin.x y:MKMapRectGetMaxY(mRect)];
}

-(CLLocationCoordinate2D)getCoordinateFromMapRectanglePoint:(double)x y:(double)y{
    MKMapPoint swMapPoint = MKMapPointMake(x, y);
    return MKCoordinateForMapPoint(swMapPoint);
}

-(NSArray *)getBoundingBox:(MKMapRect)mRect{
    CLLocationCoordinate2D bottomLeft = [self getSWCoordinate:mRect];
    CLLocationCoordinate2D topRight = [self getNECoordinate:mRect];
    return @[[NSNumber numberWithDouble:bottomLeft.latitude ],
             [NSNumber numberWithDouble:bottomLeft.longitude],
             [NSNumber numberWithDouble:topRight.latitude],
             [NSNumber numberWithDouble:topRight.longitude]];
}


回答8:

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)


回答9:

This code works with map rotating like 90/180 degrees. set mapView.pitchEnabled = NO; for less bugs.

CLLocationDirection heading = mapView.camera.heading;

float mapWidth = mapView.frame.size.width;
float mapHeight = mapView.frame.size.height;

float neX = mapWidth;
float neY = 0.0;

float swX = 0.0;
float swY = mapHeight;


if (heading >= 0 && heading <= 90) {
    //println("Q1")
    float ratio = heading / 90;

    neX = (1-ratio) * mapWidth;
    swX = (mapWidth*ratio);
} else if (heading >= 90 && heading <= 180) {
    //println("Q2")
    float ratio = (heading - 90) / 90;
    neX = 0;
    neY = (mapHeight*ratio);
    swY = (1-ratio) * mapHeight;
    swX = mapWidth;

} else if (heading >= 180 && heading <= 270) {
    //println("Q3")
    float ratio = (heading - 180) / 90;
    neX = mapWidth*ratio;
    neY = mapHeight;
    swX = (1-ratio) * mapWidth;
    swY = 0;

} else if (heading >= 270 && heading <= 360) {
    //println("Q4");
    float ratio = (heading - 270) / 90;
    neX = mapWidth;
    neY = (1-ratio) * mapHeight;
    swY = ratio * mapHeight;

}

CGPoint swPoint = CGPointMake(swX, swY);
CGPoint nePoint = CGPointMake(neX, neY);

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


回答10:

I was having some trouble with some of the other answers for maps that had been 2 finger rotated. This code worked for me:

MKMapRect rect = self.mapView.visibleMapRect;
CLLocationCoordinate2D northeast = MKCoordinateForMapPoint(MKMapPointMake(MKMapRectGetMaxX(rect),rect.origin.y));
CLLocationCoordinate2D southwest = MKCoordinateForMapPoint(MKMapPointMake(rect.origin.x         ,MKMapRectGetMaxY(rect)));

My answer is derived from 陈保状's answer and the associated website enter link description here. Its simplified down too 3 lines for south west and north east corners.