1

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.

Hunter Monk
  • 1,967
  • 1
  • 14
  • 25

5 Answers5

1

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)
frankleonrose
  • 355
  • 2
  • 11
0

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

flovilmart
  • 1,759
  • 1
  • 11
  • 18
  • This isn't quite working for me yet. I'm assuming that your last line is supposed to be: 'let newNorth = CGPointApplyAffineTransform(ne**R**, translateBack)' Before rotation I am getting: newSouth:(0.0,568.0) newNorth:(320.0,0.0) which is correct. After rotating approximately 90 degrees I am getting: newSouth:(450.329289868992,432.205612053548) newNorth:(-130.329289868992,135.794387946452). Sorry, I accidentally hit enter before I had finished the comment. – Hunter Monk Mar 05 '15 at 16:34
  • To get the current heading, use mapCamera property of the mapView, https://developer.apple.com/library/ios/documentation/MapKit/Reference/MKMapCamera_class/index.html#//apple_ref/swift/cl/MKMapCamera – flovilmart Mar 05 '15 at 16:41
  • Ah! I was getting degrees rotated differently earlier which was producing negative values. Now by using heading for my degrees value, after rotating ~90 degrees I am returning: **swR:(-282.925430844159,-161.892558759343) neR:(282.925430844159,161.892558759343) newSouth:(-122.925430844159,122.107441240657) newNorth:(442.925430844159,445.892558759343)** – Hunter Monk Mar 05 '15 at 16:50
  • Replace the last lines with: let newSouth = CGPointMake(swR.x + center.x, swR.y + center.y) let newNorth = CGPointMake(neR.x + center.x, neR.y + center.y) – flovilmart Mar 05 '15 at 19:17
  • I am getting pretty similar results: newSouth:(-132.9152050359,140.973140079297) - newNorth:(452.9152050359,427.026859920703) – Hunter Monk Mar 05 '15 at 19:28
  • And that make sense, as those are the coordinates after the rotation. That algorithm gives you the current points for your rotated bounds. Now if you want to have the real coordintates: use that algo with degrees = -heading and convert the points back to coordinates with the mapView helper – flovilmart Mar 05 '15 at 21:21
  • In the above example I've rotated the map one quarter rotation clockwise. The newSouth should be approximately (320,568) and newNorth should be around (0,0). They are actually close to switched in the 90 degree example. However the closer I get rotating 180 degrees, the closer they get to the correct values. I'm able to get the real coordinates once we get the right points correct - I very much appreciate your help. – Hunter Monk Mar 05 '15 at 21:43
  • FWI, mapView converts automatically the coordintates according to the heading.... – flovilmart Mar 05 '15 at 23:28
0

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

flovilmart
  • 1,759
  • 1
  • 11
  • 18
  • Maybe I didn't explain my question well. I need to get the northeast most point and the southwest most point on the map. If I rotate the map 180 degrees, the northeast most point would be (0, 568) and the southwest point would be (320,0). – Hunter Monk Mar 07 '15 at 23:43
0

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)
Hunter Monk
  • 1,967
  • 1
  • 14
  • 25
  • And this is not right... You NE point HAS TO BE outside of the map as it's rotated around the center and you're dealing with a non square map. What you forgot there is that the points you extract now are not visible on the map at all so it pleases you but it's not true in the mathematical sense. – flovilmart Mar 09 '15 at 14:55
  • I stated, "I want to get the southwest-most point and northeast-most point displayed on the current MKMapView." which is exactly what I'm getting. I don't want any points outside of the visible map rect. I do appreciate the help, your help got me to this solution. – Hunter Monk Mar 09 '15 at 17:14
  • Which is provided right away from the MKMapView when you request convertPoint... Anyway, good luck. – flovilmart Mar 09 '15 at 17:57
0

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];
Can Aksoy
  • 1,322
  • 1
  • 12
  • 18