Hunter Monk
Hunter Monk

Reputation: 1997

Get SW/NE point on MKMapView after rotation

I'm writing an application in Swift, and using Parse as the back-end.

In order to setup a query to Parse, I want to get the southwest-most point and northeast-most point displayed on the current MKMapView.

I currently get these values as displayed here:

//___ To calculate the search bounds, first we need to calculate the corners of the map
    let nePoint = CGPointMake(self.myMap.bounds.origin.x + myMap.bounds.size.width, myMap.bounds.origin.y);
    let swPoint = CGPointMake((self.myMap.bounds.origin.x), (myMap.bounds.origin.y + myMap.bounds.size.height));

    //___ Then transform those point into lat, lng values
    let neCoord = myMap.convertPoint(nePoint, toCoordinateFromView: myMap)
    let swCoord = myMap.convertPoint(swPoint, toCoordinateFromView: myMap)

    let neGP = PFGeoPoint(latitude: neCoord.latitude, longitude: neCoord.longitude)
    let swGP = PFGeoPoint(latitude: swCoord.latitude, longitude: swCoord.longitude)

    var query = PFQuery(className: "locations")
    //___ Limit what could be a lot of points.
    query.limit = 25
    query.whereKey("location", withinGeoBoxFromSouthwest: swGP, toNortheast: neGP)

This works perfectly until the map is rotated.

However, once the user rotates the map, the top-right and bottom-left points no longer represent the northeast-most and southwest-most points.

How can I always calculate the true southwestern-most and northeastern-most points displayed on the map?

Thanks.

Upvotes: 1

Views: 1372

Answers (5)

Hunter Monk
Hunter Monk

Reputation: 1997

I came up with a (pretty clunky) solution.

This displays the actual northeast most and southwest most points that are visible on the map view rather than just rotating the points around the center of the map, which causes the points to be moved outside of the view due to the screen's rectangular shape.

    let heading = myMap.camera.heading

    let mapWidth = Double(myMap.frame.width)
    let mapHeight = Double(myMap.frame.height)

    var neX = mapWidth
    var neY = 0.0

    var swX = 0.0
    var swY = mapHeight


    if heading >= 0 && heading <= 90 {

        let ratio = heading / 90

        neX = (1 - ratio) * mapWidth
        swX = mapWidth * ratio
    } else if heading >= 90 && heading <= 180 {

        let ratio = (heading - 90) / 90
        neX = 0
        neY = mapHeight * ratio
        swY = (1 - ratio) * mapHeight
        swX = mapWidth

    } else if heading >= 180 && heading <= 270 {

        let ratio = (heading - 180) / 90
        neX = mapWidth * ratio
        neY = mapHeight
        swX = (1 - ratio) * mapWidth
        swY = 0

    } else if heading >= 270 && heading <= 360 {

        let ratio = (heading - 270) / 90
        neX = mapWidth
        neY = (1 - ratio) * mapHeight
        swY = ratio * mapHeight

    }

    let swPoint = CGPointMake(CGFloat(swX), CGFloat(swY))
    let nePoint = CGPointMake(CGFloat(neX), CGFloat(neY))

    let swCoord = myMap.convertPoint(swPoint, toCoordinateFromView: myMap)
    let neCoord = myMap.convertPoint(nePoint, toCoordinateFromView: myMap)


    //___ Then transform those point into lat,lng values
    let swGP = PFGeoPoint(latitude: swCoord.latitude, longitude: swCoord.longitude)
    let neGP = PFGeoPoint(latitude: neCoord.latitude, longitude: neCoord.longitude)

Upvotes: 0

frankleonrose
frankleonrose

Reputation: 365

For my application I had to get the NE/SW corners of a box that totally enclosed the visible map region.

The important thing to realize is that you cannot figure out the bounding coordinates of a rotated map by sampling just two corners.

If you picked just the upper right and lower left corners of this map and convert to lat/lon, you'll get the red rectangle, which is probably not what you want.

Getting bounds of rotated map

As you can see from the picture, the NE and SW bounding points don't lie anywhere on the map view.

Instead, you need to sample all four corners and then convert them to lat/lon before comparing them, because the map may be upside down rotated, meaning you have no idea which corner is more Northerly/Southerly, etc.

// Using http://stackoverflow.com/a/28683812/1207583 code extension name
typealias Edges = (ne: CLLocationCoordinate2D, sw: CLLocationCoordinate2D)

extension MKMapView {
    func edgePoints() -> Edges {
        let corners = [
            CGPoint(x: self.bounds.minX, y: self.bounds.minY),
            CGPoint(x: self.bounds.minX, y: self.bounds.maxY),
            CGPoint(x: self.bounds.maxX, y: self.bounds.maxY),
            CGPoint(x: self.bounds.maxX, y: self.bounds.minY)
        ]
        let coords = corners.map { corner in
            self.convertPoint(corner, toCoordinateFromView: self)
        }
        let startBounds = (
            n: coords[0].latitude, s: coords[0].latitude,
            e: coords[0].longitude, w: coords[0].longitude)
        let bounds = coords.reduce(startBounds) { b, c in
            let n = max(b.n, c.latitude)
            let s = min(b.s, c.latitude)
            let e = max(b.e, c.longitude)
            let w = min(b.w, c.longitude)
            return (n: n, s: s, e: e, w: w)
        }
        return (ne: CLLocationCoordinate2D(latitude: bounds.n, longitude: bounds.e),
                sw: CLLocationCoordinate2D(latitude: bounds.s, longitude: bounds.w))
    }
}

Now you should be happy when you run

let edges = mapView.edgePoints()
debugPrint("Edges: ", edges)

Upvotes: 1

Can Aksoy
Can Aksoy

Reputation: 1322

Objective-C version:

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

Upvotes: 0

flovilmart
flovilmart

Reputation: 1769

MKMapView converts automatically according to the current heading:

self.mapView.centerCoordinate = CLLocationCoordinate2D(latitude: 45.5249442, longitude: -73.59655650000002)
let pt0 = self.mapView.convertPoint(CGPointZero, toCoordinateFromView: self.mapView)
// pt0: latitude:80.777327154362311
//      longitude:-163.59656124778414
self.mapView.camera.heading = 45.0
let pt1 = self.mapView.convertPoint(CGPointZero, toCoordinateFromView: self.mapView)
 // pt1: latitude: -84.995143020120821
 //      longitude: -71.475233216395111

So you don't have anything to do actually...

Upvotes: 0

flovilmart
flovilmart

Reputation: 1769

You need to rotate your points around the center of your bounds

let degrees = 15 // rotation in degrees
// determine the center
let sw = swPoint
let ne = nePoint
let center = CGPoint(x: (ne.x-sw.x)/2+sw.x, y: (ne.y-sw.y)/2+sw.y)
let translate = CGAffineTransformMakeTranslation(-center.x, -center.y)

// Norm the points around the center
let swN = CGPointApplyAffineTransform(sw, translate)
let neN = CGPointApplyAffineTransform(ne, translate)

// Rotate your points around the center
let rotate = CGAffineTransformMakeRotation(CGFloat(degrees/180.0*M_PI))
let swR = CGPointApplyAffineTransform(swN, rotate)
let neR = CGPointApplyAffineTransform(neN, rotate)

// offset from the center back
let translateBack = CGAffineTransformMakeTranslation(center.x, center.y)

// Final coordinates
let newSouth = CGPointApplyAffineTransform(swR, translateBack)
let newNorth = CGPointApplyAffineTransform(neR, translateBack)

Does that help?

To get the coordinates back from the map apply the inverse transformation

Edit: Fix the typo on the last line

Upvotes: 0

Related Questions