Convert MKCoordinateRegion to MKMapRect

2019-01-10 11:58发布

问题:

I have a square MKMapView in my app, and I wish to set a center point and the exact height/width of the view in meters.

Creating an MKCoordinateRegion and setting the map to it (as in this code...

MKCoordinateRegion region = MKCoordinateRegionMakeWithDistance(center_coord, 1000.0, 1000.0);
[self.mapView setRegion:region animated:YES];

..) doesn't work properly because using regions here just means that at least that region is displayed, typically more than the region is.


I'm planning on using setVisibleMapRect:animated: method instead, as I believe this will zoom to the actual MKMapRect passed.

So, is there a simple way to convert between an MKcoordinateRegion and an MKMapRect? Perhaps getting the top-left and bottom-right coordinates of the region, and using them to the make the MKMapRect?

I couldn't see anything handy in the Map Kit Functions Reference.

(Using iOS 5, Xcode 4.2)

回答1:

To add another implementation to the pile:

- (MKMapRect)MKMapRectForCoordinateRegion:(MKCoordinateRegion)region
{
    MKMapPoint a = MKMapPointForCoordinate(CLLocationCoordinate2DMake(
        region.center.latitude + region.span.latitudeDelta / 2, 
        region.center.longitude - region.span.longitudeDelta / 2));
    MKMapPoint b = MKMapPointForCoordinate(CLLocationCoordinate2DMake(
        region.center.latitude - region.span.latitudeDelta / 2, 
        region.center.longitude + region.span.longitudeDelta / 2));
    return MKMapRectMake(MIN(a.x,b.x), MIN(a.y,b.y), ABS(a.x-b.x), ABS(a.y-b.y));
}

NB: There are many ways to convert between MKMapRect and MKCoordinateRegion. This one certainly is not the exact inverse of MKCoordinateRegionMakeWithDistance(), but approximates it fairly well. So, be careful converting back and forth, because information can be lost.



回答2:

This is a Swift verion to Leo & Barnhart solution

func MKMapRectForCoordinateRegion(region:MKCoordinateRegion) -> MKMapRect {
    let topLeft = CLLocationCoordinate2D(latitude: region.center.latitude + (region.span.latitudeDelta/2), longitude: region.center.longitude - (region.span.longitudeDelta/2))
    let bottomRight = CLLocationCoordinate2D(latitude: region.center.latitude - (region.span.latitudeDelta/2), longitude: region.center.longitude + (region.span.longitudeDelta/2))

    let a = MKMapPointForCoordinate(topLeft)
    let b = MKMapPointForCoordinate(bottomRight)

    return MKMapRect(origin: MKMapPoint(x:min(a.x,b.x), y:min(a.y,b.y)), size: MKMapSize(width: abs(a.x-b.x), height: abs(a.y-b.y)))
}


回答3:

Use MKMapPointForCoordinate to convert the 2 point of the region (top/left and bottom/right), then create the MKMapRect using the 2 MKMapPoints

        CLLocationCoordinate2D coordinateOrigin = CLLocationCoordinate2DMake(latitude, longitude);
        CLLocationCoordinate2D coordinateMax = CLLocationCoordinate2DMake(latitude + cellSize, longitude + cellSize);

        MKMapPoint upperLeft = MKMapPointForCoordinate(coordinateOrigin);
        MKMapPoint lowerRight = MKMapPointForCoordinate(coordinateMax);

        MKMapRect mapRect = MKMapRectMake(upperLeft.x,
                                          upperLeft.y,
                                          lowerRight.x - upperLeft.x,
                                          lowerRight.y - upperLeft.y);


回答4:

you can use method to convert MKCoordinateRegion to CGRect

- (CGRect)convertRegion:(MKCoordinateRegion)region toRectToView:(UIView *)view

and use - (MKMapRect)mapRectForRect:(CGRect)rect

or use MKMapPointForCoordinate method to first convert coordinates to MKPoint and use that to form MKMapRect to eventually use setVisibleMapRect:animated:



回答5:

@Bogdan

I think it should be:

 CLLocationCoordinate2D topLeftCoordinate =
CLLocationCoordinate2DMake(coordinateRegion.center.latitude
                           + (coordinateRegion.span.latitudeDelta/2.0),
                           coordinateRegion.center.longitude
                           - (coordinateRegion.span.longitudeDelta/2.0));

MKMapPoint topLeftMapPoint = MKMapPointForCoordinate(topLeftCoordinate);

CLLocationCoordinate2D bottomRightCoordinate =
CLLocationCoordinate2DMake(coordinateRegion.center.latitude
                           - (coordinateRegion.span.latitudeDelta/2.0),
                           coordinateRegion.center.longitude
                           + (coordinateRegion.span.longitudeDelta/2.0));

MKMapPoint bottomRightMapPoint = MKMapPointForCoordinate(bottomRightCoordinate);

MKMapRect mapRect = MKMapRectMake(topLeftMapPoint.x,
                                  topLeftMapPoint.y,
                                  fabs(bottomRightMapPoint.x-topLeftMapPoint.x),
                                  fabs(bottomRightMapPoint.y-topLeftMapPoint.y));

According to apple api reference, MKCoordinateRegion.center represents the center point of the region; and MKCoordinateSpan.latitudeDelta represents the amount of north-to-south distance (measured in degrees) to display on the map; MKCoordinateSpan.longitudeDelta represents amount of east-to-west distance (measured in degrees) to display for the map region.



回答6:

@David's answer, in Swift 3

func mapRect(region: MKCoordinateRegion) -> MKMapRect {
  let topLeft = CLLocationCoordinate2D(
    latitude: region.center.latitude + (region.span.latitudeDelta/2.0),
    longitude: region.center.longitude - (region.span.longitudeDelta/2.0)
  )

  let bottomRight = CLLocationCoordinate2D(
    latitude: region.center.latitude - (region.span.latitudeDelta/2.0),
    longitude: region.center.longitude + (region.span.longitudeDelta/2.0)
  )

  let topLeftMapPoint = MKMapPointForCoordinate(topLeft)
  let bottomRightMapPoint = MKMapPointForCoordinate(bottomRight)

  let origin = MKMapPoint(x: topLeftMapPoint.x,
                          y: topLeftMapPoint.y)
  let size = MKMapSize(width: fabs(bottomRightMapPoint.x - topLeftMapPoint.x),
                       height: fabs(bottomRightMapPoint.y - topLeftMapPoint.y))

  return MKMapRect(origin: origin, size: size)
}


回答7:

The answer @David gave (and consequently the Swift 3 version by @onmyway133) has a significant error whenever the region crosses over the anti-meridian from the Eastern Hemisphere (longitude 0 degrees to 180 degrees) to the Western Hemisphere (longitude -180 degrees to 0 degrees). The width of the MKMapRect will be bigger than it should be (usually much bigger).

Here is the fix (for the Swift 3 code):

let topLeftMapPoint = MKMapPointForCoordinate(topLeft)
let bottomRightMapPoint = MKMapPointForCoordinate(bottomRight)
var width = bottomRightMapPoint.x - topLeftMapPoint.x
if width < 0.0 {
    // Rect crosses from the Eastern Hemisphere to the Western Hemisphere
    width += MKMapPointForCoordinate(CLLocationCoordinate2D(latitude: 0.0, longitude: 180.0)).x
}
let height = bottomRightMapPoint.y - topLeftMapPoint.y
let size = MKMapSize(width: width, height: height)
return MKMapRect(origin: topLeftMapPoint, size: size)

Taking an MKCoordinateRegion, converting it into an MKMapRect with the code above, and then turning it back into an MKCoordinateRegion using MKCoordinateRegionForMapRect() gives me very good agreement between the input region and the output region everywhere on the map.



回答8:

Still have to be a bit more careful about crossing the meridian (as well as wrapping around the poles) otherwise MKMapPointForCoordinate returns -1, -1:

public func MKMapRectForCoordinateRegion(region:MKCoordinateRegion) -> MKMapRect {
var topLeft = CLLocationCoordinate2D(
    latitude: min(region.center.latitude + (region.span.latitudeDelta/2.0), 90),
    longitude: region.center.longitude - (region.span.longitudeDelta/2.0)
)

if topLeft.longitude < -180 {
    // We wrapped around the meridian
    topLeft.longitude += 360
}

var bottomRight = CLLocationCoordinate2D(
    latitude: max(region.center.latitude - (region.span.latitudeDelta/2.0), -90),
    longitude: region.center.longitude + (region.span.longitudeDelta/2.0)
)

    if bottomRight.longitude > 180 {
        // We wrapped around the medridian
        bottomRight.longitude -= 360
    }

    let topLeftMapPoint = MKMapPointForCoordinate(topLeft)
    let bottomRightMapPoint = MKMapPointForCoordinate(bottomRight)

    var width = bottomRightMapPoint.x - topLeftMapPoint.x
    if width < 0.0 {
        // Rect crosses meridian
        width += MKMapPointForCoordinate(CLLocationCoordinate2D(latitude: 0.0, longitude: 180.0)).x
    }
    let height = bottomRightMapPoint.y - topLeftMapPoint.y
    let size = MKMapSize(width: width, height: height)

    return MKMapRect(origin: topLeftMapPoint, size: size)
}

Some test case code (using Nimble):

func testMKMapRectForCoordinateRegion() {
    let northWesternRegion = MKCoordinateRegionMake(CLLocationCoordinate2DMake(45.0, -90.0), MKCoordinateSpanMake(20.0, 20.0))
    let northWesternMapRect = MKMapRectForCoordinateRegion(region: northWesternRegion)
    let convertedNWRegion = MKCoordinateRegionForMapRect(northWesternMapRect)
    expect(self.equivalentRegions(northWesternRegion, convertedNWRegion)).to(beTrue())

    let northEasternRegion = MKCoordinateRegionMake(CLLocationCoordinate2DMake(45.0, 90.0), MKCoordinateSpanMake(20.0, 20.0))
    let northEasternMapRect = MKMapRectForCoordinateRegion(region: northEasternRegion)
    let convertedNERegion = MKCoordinateRegionForMapRect(northEasternMapRect)
    expect(self.equivalentRegions(northEasternRegion, convertedNERegion)).to(beTrue())

    let southWesternRegion = MKCoordinateRegionMake(CLLocationCoordinate2DMake(-45.0, -90.0), MKCoordinateSpanMake(20.0, 20.0))
    let southWesternMapRect = MKMapRectForCoordinateRegion(region: southWesternRegion)
    let convertedSWRegion = MKCoordinateRegionForMapRect(southWesternMapRect)
    expect(self.equivalentRegions(southWesternRegion, convertedSWRegion)).to(beTrue())

    let southEasternRegion = MKCoordinateRegionMake(CLLocationCoordinate2DMake(-45.0, 90.0), MKCoordinateSpanMake(20.0, 20.0))
    let southEasternMapRect = MKMapRectForCoordinateRegion(region: southEasternRegion)
    let convertedSERegion = MKCoordinateRegionForMapRect(southEasternMapRect)
    expect(self.equivalentRegions(southEasternRegion, convertedSERegion)).to(beTrue())

    let meridianSpanEastRegion = MKCoordinateRegionMake(CLLocationCoordinate2DMake(0.0, 170.0), MKCoordinateSpanMake(20.0, 20.0))
    let meridianSpanEastMapRect = MKMapRectForCoordinateRegion(region: meridianSpanEastRegion)
    let convertedMeridianSpanEastRegion = MKCoordinateRegionForMapRect(meridianSpanEastMapRect)
    expect(self.equivalentRegions(meridianSpanEastRegion, convertedMeridianSpanEastRegion)).to(beTrue())

    let meridianSpanWestRegion = MKCoordinateRegionMake(CLLocationCoordinate2DMake(0.0, -170.0), MKCoordinateSpanMake(20.0, 20.0))
    let meridianSpanWestMapRect = MKMapRectForCoordinateRegion(region: meridianSpanWestRegion)
    let convertedMeridianSpanWestRegion = MKCoordinateRegionForMapRect(meridianSpanWestMapRect)
    expect(self.equivalentRegions(meridianSpanWestRegion, convertedMeridianSpanWestRegion)).to(beTrue())
}

fileprivate func equivalentRegions(_ regionA: MKCoordinateRegion, _ regionB: MKCoordinateRegion) -> Bool {
    // Allow a small delta between values
    let deltaAllowed: Double = 1.0

    return (fabs(regionA.center.latitude - regionB.center.latitude) < deltaAllowed) &&
            (fabs(regionA.center.longitude - regionB.center.longitude) < deltaAllowed) &&
            (fabs(regionA.span.latitudeDelta - regionB.span.latitudeDelta) < deltaAllowed) &&
            (fabs(regionA.span.longitudeDelta - regionB.span.longitudeDelta) < deltaAllowed)
}


回答9:

Use the built in function MKCoordinateRegionForMapRect

MKCoordinateRegion region = MKCoordinateRegionForMapRect(rect);