How do I calculate a Rectangle around a Geographic Point? - geolocation

CLLocationManager will hand off to my delegate a new CLLocation whenever location has changed. The coordinates of that location are expressed as a CLLocationCoordinate2D object, which simply contains a latitude and a longitude. I'd like to take this location and determine the latitude and longitude 1000m south and 1000 west and the latitude and longitude 1000m north and 1000m east. This way I end up with one coordinate southwest of the location and one northeast of the location.
I have no clue how to do this, and my GoogleFoo seems quite poor tonight. What information I have found has offered up impenetrable mathematics. Anybody help a brotha hacker out? I'm fine to use an iOS API if there is one, but an equation that just operates on double values for the lat and long would be even better. It doesn't have to be accurate within centimeters, though within meters would be nice. Ideally, it would look something like this:
NSArray *rect = CalculateRectangleFromLocation(
clLocationCoordinate2D,
1000.0
);
And then *rect would have four values: the lat and long of the southwest corner and the lat and long of the northeast corner.

Here is the code to get the top/right/bottom/left coordinates of the bounding rectangle.
LatLon.h
#import <Foundation/Foundation.h>
#import <CoreLocation/CoreLocation.h>
extern double radians(double degrees);
extern double degrees(double radians);
extern CLLocationCoordinate2D LatLonDestPoint(CLLocationCoordinate2D origin, double brearing, CLLocationDistance distance);
LatLon.m
const CLLocationDegrees kLatLonEarthRadius = 6371.0;
double radians(double degrees) {
return degrees * M_PI / 180.0;
}
double degrees(double radians) {
return radians * 180.0 / M_PI;
}
CLLocationCoordinate2D LatLonDestPoint(CLLocationCoordinate2D origin, double bearing, CLLocationDistance distance) {
double brng = radians(bearing);
double lat1 = radians(origin.latitude);
double lon1 = radians(origin.longitude);
CLLocationDegrees lat2 = asin(sin(lat1) * cos(distance / kLatLonEarthRadius) +
cos(lat1) * sin(distance / kLatLonEarthRadius) * cos(brng));
CLLocationDegrees lon2 = lon1 + atan2(sin(brng) * sinf(distance / kLatLonEarthRadius) * cos(lat1),
cosf(distance / kLatLonEarthRadius) - sin(lat1) * sin(lat2));
lon2 = fmod(lon2 + M_PI, 2.0 * M_PI) - M_PI;
CLLocationCoordinate2D coordinate;
if (! (isnan(lat2) || isnan(lon2))) {
coordinate.latitude = degrees(lat2);
coordinate.longitude = degrees(lon2);
}
return coordinate;
}
Usage
CLLocationCoordinate2D location = ...;
double distance = ...;
CLLocationCoordinate2D right = LatLonDestPoint(location, 90.0, distance);
CLLocationDegrees rectRight = right.longitude;
CLLocationCoordinate2D top = LatLonDestPoint(location, 0.0, distance);
CLLocationDegrees rectTop = top.latitude;
CLLocationCoordinate2D left = LatLonDestPoint(location, 270.0, distance);
CLLocationDegrees rectLeft = left.longitude;
CLLocationCoordinate2D bottom = LatLonDestPoint(location, 180.0, distance);
CLLocationDegrees rectBottom = bottom.latitude;
Swift
extension CLLocationCoordinate2D {
fileprivate func radians(degrees: Double) -> Double { return degrees * .pi / 180.0 }
fileprivate func degrees(radians: Double) -> Double { return radians * 180.0 / .pi }
func coordinate(bearing: Double, distanceInMeter distance: CLLocationDistance) -> CLLocationCoordinate2D {
let kLatLonEarthRadius: CLLocationDegrees = 6371.0
let brng: Double = radians(degrees: bearing)
let lat1: Double = radians(degrees: self.latitude)
let lon1: Double = radians(degrees: self.longitude)
let lat2: CLLocationDegrees = asin(
sin(lat1) * cos(distance / kLatLonEarthRadius) +
cos(lat1) * sin(distance / kLatLonEarthRadius) * cos(brng)
)
var lon2: CLLocationDegrees = lon1 + atan2(
sin(brng) * sin(distance / kLatLonEarthRadius) * cos(lat1),
cos(distance / kLatLonEarthRadius) - sin(lat1) * sin(lat2)
)
lon2 = fmod(lon2 + .pi, 2.0 * .pi) - .pi
var coordinate = CLLocationCoordinate2D()
if !lat2.isNaN && !lon2.isNaN {
coordinate.latitude = degrees(radians: lat2)
coordinate.longitude = degrees(radians: lon2)
}
return coordinate
}
func rect(distanceInMeter meter: CLLocationDistance) -> (north: Double, west: Double, south: Double, east: Double) {
let north = coordinate(bearing: 0, distanceInMeter: meter).latitude
let south = coordinate(bearing: 180, distanceInMeter: meter).latitude
let east = coordinate(bearing: 90, distanceInMeter: meter).longitude
let west = coordinate(bearing: 270, distanceInMeter: meter).longitude
return (north: north, west: west, south: south, east: east)
}
}

I usually do this by using the PROJ4 library to convert latitude and longitude to a projection in meters that's useful for my region (UTM works well if you don't have more information, I'm in Northern California so surveyors in my region all work in EPSG:2226), adding the appropriate offset in meters to that, and then using PROJ4 to convert back.
Later edit: The answer given by Jayant below is fine, depending on how accurate your meters rectangle needs to be. The earth isn't a sphere, it isn't even an oblate spheroid, so the projection in which add your distance to your latitude and longitude may matter. Even using PROJ4 those meters are at sea level. Geography is harder than you'd think.

Related

Calculate bounding box of a position on map in iOS

How can I calculate South West and North East coordinates from a position. I do not have the MKMapView. I need to calculate solely on the basis of CLLocationCoordinate2D.
Found it:
#define DEGREE(RADIANS) (180/M_PI) * RADIANS
#define RADIANS(DEGREE) (M_PI/180) * DEGREE
double lat = [[_locManager location] coordinate].latitude;
double lon = [[_locManager location] coordinate].longitude;
double R = 6371; // earth radius in km
double radius = 0.5; // bounding box spacing from current location in kilo meters
double y2 = lon + DEGREE(radius/R/cos(RADIANS(lat)));
double y1 = lon - DEGREE(radius/R/cos(RADIANS(lat)));
double x2 = lat + DEGREE(radius/R);
double x1 = lat - DEGREE(radius/R);
x1, y1 is South West latitude and longitude and x2,y2 is North East latitude and longitude

Calculating MAX and MIN Latitude and Longitude with distance from Location - Objective C

I need to compute MAX and MIN Latitude and Longitude values from a location with certain distance.
I have thousands of locations stored in CoreData, and I want to show only the ones within 5km from users location.
How can I approach this problem?
Here's a possible solution:
macros to convert Degrees to Radians
#define deg2rad(degrees) ((degrees) / 180.0 M_PI)
macros to hold my searching distance
#define searchDistance 5.00 //float value in KM
set the minimum and maximum Latitude, Longitude values
float minLat = userLocation.coordinate.latitude - (searchDistance / 69);
float maxLat = userLocation.coordinate.latitude + (searchDistance / 69);
float minLon = userLocation.coordinate.latitude - searchDistance / fabs(cos(deg2rad(userLocation.coordinate.latitude))*69);
float maxLon = userLocation.coordinate.longitude + searchDistance / fabs(cos(deg2rad(userLocation.coordinate.latitude))*69);
create predicate as follows
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"latitude <= %f AND latitude >= %f AND longitude <= %f AND longitude >= %f", maxLat, minLat, maxLon, minLon];
This will create a square around userLocation and check if a given location falls into its coordinates.
Update: Swift 2.* implementation
First create a function to compute degrees to radians
func deg2rad(degrees:Double) -> Double{
return degrees * M_PI / 180
}
Compute and create minimum and maximum Latitude and Longitude values
let searchDistance:Double = 5.00 //float value in KM
let minLat = userLocation.coordinate.latitude - (searchDistance / 69)
let maxLat = userLocation.coordinate.latitude + (searchDistance / 69)
let minLon = userLocation.coordinate.longitude - searchDistance / fabs(cos(deg2rad(userLocation.coordinate.latitude))*69)
let maxLon = userLocation.coordinate.longitude + searchDistance / fabs(cos(deg2rad(userLocation.coordinate.latitude))*69)
Last create NSPredicate to query CoreData for locations. In my case I am querying for values latitude and longitude but you should change this to match your CoreData object
let predicate = NSPredicate(format: "latitude <= \(maxLat) AND latitude >= \(minLat) AND longitude <= \(maxLon) AND longitude >= \(minLon)")
Use the CoreLocation method distanceFromLocation: which returns the distance (in meters) between two points as such:
CLLocation* location = [[CLLocation alloc] initWithLatitude:lat longitude:lon];
if([userLocation distanceFromLocation:location) < searchDistance)
// do something with close point
A suitable predicate can be constructed as:
NSPredicate* predicate = [NSPredicate predicateWithBlock:(BOOL (^)(NSDictionary* target, NSDictionary *bindings)) {
CLLocation* location = [[CLLocation alloc] initWithLatitude:[target[#"lat"] doubleValue] longitude:[target[#"lon"] doubleValue]];
return [userLocation distanceFromLocation:location] < searchDistance;
}];
This has the advantage that it returns the items actually within range, as opposed to the items in a square approximating the range. It's also (probably, we don't know the details) using a more accurate approximation of the range itself. It has the disadvantage that the predicate requires loading every object since it can't be expressed as an sqlite query.

How to show heading direction from Current location to other location on map in ios?

I have a requirement where i have to show the heading direction towards any location on map from user current location. Let say if we have 4 location annotation on map apart from current location and i want to show heading towards any of the location after tapping on it.
How can we achieve it. I have gone trough Map API documentation & Locaiotn API, I found that we get the heading value in the delegate method which provided by API when we called the startupdatingheading method. I not getting idea how can we externally get the heading data between two locations.
Please help me out.
Thanks in advance.
This will calculate the initial bearing to get from lat1,lon1 to lat2,lon2 on a straight line.
double lat1=48.0; // source latitude, example data
double lon1=17.0; // source longitude
double lat2=49.0; // destination latitude
double lon2=18.0; // destination longitude
double lat1Rad = lat1 * M_PI / 180;
double lat2Rad = lat2 * M_PI / 180;
double dLon = (lon2 - lon1) * M_PI / 180;
double y = sin(dLon) * cos(lat2Rad);
double x = cos(lat1Rad) * sin(lat2Rad) - sin(lat1Rad) * cos(lat2Rad) * cos(dLon);
double bearingRad = atan2(y, x);
// this is the bearing from source to destination in degrees, normalized to 0..360
double bearing = fmod((bearingRad * 180 / M_PI + 360),360);
Swift version:
func getHeading(fromLoc: CLLocationCoordinate2D, toLoc: CLLocationCoordinate2D) -> Double {
let lat1Rad = fromLoc.latitude * Double.pi / 180
let lat2Rad = toLoc.latitude * Double.pi / 180
let dLon = (toLoc.longitude - fromLoc.longitude) * Double.pi / 180
let y = sin(dLon) * cos(lat2Rad)
let x = cos(lat1Rad) * sin(lat2Rad) - sin(lat1Rad) * cos(lat2Rad) * cos(dLon)
let bearingRad = atan2(y, x)
// this is the bearing from/to in degrees, normalized to 0..360
return fmod((bearingRad * 180 / Double.pi + 360),360);
}

Get distance between 2 latitude & longitude in iOS6+

I want to calculate the distance between 2 lat & long. I am able to calculate the distance as below
CLLocation *currentLoc = [[CLLocation alloc] initWithLatitude:-24.4132995 longitude:121.0790024];
CLLocation *restaurnatLoc = [[CLLocation alloc] initWithLatitude:-32.8310013 longitude:150.1390075];
CLLocationDistance meters = [restaurnatLoc distanceFromLocation:currentLoc];
NSLog(#"Distance between 2 geo cordinates: %.2f Meters",meters);
Now I want to get the direction from currentLocation to restaurnatLoc. For this I have below code
double DegreesToRadians(double degrees) {return degrees * M_PI / 180;};
double RadiansToDegrees(double radians) {return radians * 180/M_PI;};
-(double) bearingToLocationFromCoordinate:(CLLocation*)fromLoc toCoordinate:(CLLocation*)toLoc
{
double lat1 = DegreesToRadians(fromLoc.coordinate.latitude);
double lon1 = DegreesToRadians(fromLoc.coordinate.longitude);
double lat2 = DegreesToRadians(toLoc.coordinate.latitude);
double lon2 = DegreesToRadians(toLoc.coordinate.longitude);
double dLon = lon2 - lon1;
double y = sin(dLon) * cos(lat2);
double x = cos(lat1) * sin(lat2) - sin(lat1) * cos(lat2) * cos(dLon);
double radiansBearing = atan2(y, x);
return RadiansToDegrees(radiansBearing);
}
It returns bearing = 114.975752 Now how can I decide whether restaurant is in North,South,West,East,NW,NE,SW,SE from my current location ?
I get 1 solution from this link Direction based off of 2 Lat,Long points But if I consider this solution , then I have doubt on bearing 114 from my location(red circle) to restaurant (green circle) as shown below. Correct me if I am wrong.
As current location is "Western Australia" & restaurant location is "Sydney" as shown in Google Maps.
Can any body tell me whats going wrong here ? Thanks.
///////////////////////////// Update /////////////////////////////
My compass diagram is wrong. Here is the correct diagram all thanks to AlexWien
Now I am getting the correct output
your compass rose is totally wrong. have you ever looked at a compass? open the iphone compass app and look where 90Degrees is located. It is east, not west like in your graphic.
geographical direction is measured clockwise!
so 114 deg is east, which matches you expectation

Finding Bounding Box from CLLocationCoordinate2D

I am working with an API that allows me to specify a "bounding box" of coordinate which allows me to only return results within that box:
Returns a list of geocaches inside the specified bounding box sorted
by the distance from the center of the box.
The parameters south
latitude, west longitude, north latitude, east longitude define the
edges of a bounding box.
Coordinates should be in decimal degrees Use
positive numbers for north latitude and east longitude and negative
numbers of south latitude and west longitude.
The box cannot cross
the 180° longitude line or the 90° or -90° points.
The math for this is a little beyond me, but I found somewhat helpful calculations, but I am not sure it is what I need for the API:
MKCoordinateRegion region = MKCoordinateRegionMakeWithDistance(self.mapView.centerCoordinate, 2000.0, 2000.0);
CLLocationCoordinate2D northWestCorner, southEastCorner;
northWestCorner.latitude = center.latitude - (region.span.latitudeDelta / 2.0);
northWestCorner.longitude = center.longitude + (region.span.longitudeDelta / 2.0);
southEastCorner.latitude = center.latitude + (region.span.latitudeDelta / 2.0);
southEastCorner.longitude = center.longitude - (region.span.longitudeDelta / 2.0);
Does anyone know how I could do this? Are the calculations here not helpful in order to get the west longitude, north latitude, east longitude that define the edges of the bounding box?
EDIT:
The error I am getting:
Invalid value for parameter: bbox=south,west,north,east
Using the center value:
center=37.552821,-122.377413
Converted Box (after calculations from above):
bbox=37.561831,-122.388730,37.543811,-122.366096
Final Working code:
// Current distance
MKMapRect mRect = mapView.visibleMapRect;
MKMapPoint eastMapPoint = MKMapPointMake(MKMapRectGetMinX(mRect), MKMapRectGetMidY(mRect));
MKMapPoint westMapPoint = MKMapPointMake(MKMapRectGetMaxX(mRect), MKMapRectGetMidY(mRect));
CLLocationDistance distance = MKMetersBetweenMapPoints(eastMapPoint, westMapPoint);
// Region
MKCoordinateRegion region = MKCoordinateRegionMakeWithDistance(request.center, distance, distance);
CLLocationCoordinate2D northWestCorner, southEastCorner;
northWestCorner.latitude = request.center.latitude + (region.span.latitudeDelta / 2.0);
northWestCorner.longitude = request.center.longitude - (region.span.longitudeDelta / 2.0);
southEastCorner.latitude = request.center.latitude - (region.span.latitudeDelta / 2.0);
southEastCorner.longitude = request.center.longitude + (region.span.longitudeDelta / 2.0);
base = [base stringByAppendingFormat:#"bbox=%f,%f,%f,%f&", southEastCorner.latitude,northWestCorner.longitude,northWestCorner.latitude,southEastCorner.longitude];
You seem to have gotten your hemispheres reversed. North and east are positive. So if you start from the center latitude and you want to find the northern boundary you ADD half the delta, not subtract.

Resources