How to check if MKCoordinateRegion contains CLLocationCoordinate2D without using MKMapView? - iphone

I need to check if user location belongs to the MKCoordinateRegion.
I was surprised not to find simple function for this, something like: CGRectContainsCGPoint(rect, point).
I found following piece of code:
CLLocationCoordinate2D topLeftCoordinate =
CLLocationCoordinate2DMake(region.center.latitude
+ (region.span.latitudeDelta/2.0),
region.center.longitude
- (region.span.longitudeDelta/2.0));
CLLocationCoordinate2D bottomRightCoordinate =
CLLocationCoordinate2DMake(region.center.latitude
- (region.span.latitudeDelta/2.0),
region.center.longitude
+ (region.span.longitudeDelta/2.0));
if (location.latitude < topLeftCoordinate.latitude || location.latitude > bottomRightCoordinate.latitude || location.longitude < bottomRightCoordinate.longitude || location.longitude > bottomRightCoordinate.longitude) {
// Coordinate fits into the region
}
But, I am not sure if it is accurate as documentation does not specify exactly how the region rectangle is calculated.
There must be simpler way to do it. Have I overlooked some function in the MapKit framework documentation?

I'm posting this answer as the accepted solution is not valid in my opinion. This answer is also not perfect but it handles the case when coordinates wrap around 360 degrees boundaries, which is enough to be suitable in my situation.
+ (BOOL)coordinate:(CLLocationCoordinate2D)coord inRegion:(MKCoordinateRegion)region
{
CLLocationCoordinate2D center = region.center;
MKCoordinateSpan span = region.span;
BOOL result = YES;
result &= cos((center.latitude - coord.latitude)*M_PI/180.0) > cos(span.latitudeDelta/2.0*M_PI/180.0);
result &= cos((center.longitude - coord.longitude)*M_PI/180.0) > cos(span.longitudeDelta/2.0*M_PI/180.0);
return result;
}

You can convert your location to a point with MKMapPointForCoordinate, then use MKMapRectContainsPoint on the mapview's visibleMapRect. This is completely off the top of my head. Let me know if it works.

In case there is anybody else confused with latitudes and longitues, here is tested, working solution:
MKCoordinateRegion region = self.mapView.region;
CLLocationCoordinate2D location = user.gpsposition.coordinate;
CLLocationCoordinate2D center = region.center;
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);
if (
location.latitude >= northWestCorner.latitude &&
location.latitude <= southEastCorner.latitude &&
location.longitude >= northWestCorner.longitude &&
location.longitude <= southEastCorner.longitude
)
{
// User location (location) in the region - OK :-)
NSLog(#"Center (%f, %f) span (%f, %f) user: (%f, %f)| IN!", region.center.latitude, region.center.longitude, region.span.latitudeDelta, region.span.longitudeDelta, location.latitude, location.longitude);
}else {
// User location (location) out of the region - NOT ok :-(
NSLog(#"Center (%f, %f) span (%f, %f) user: (%f, %f)| OUT!", region.center.latitude, region.center.longitude, region.span.latitudeDelta, region.span.longitudeDelta, location.latitude, location.longitude);
}

The other answers all have faults. The accepted answer is a little verbose, and fails near the international dateline. The cosine answer is workable, but fails for very small regions (because delta cosine is sine which tends towards zero near zero, meaning for smaller angular differences we expect zero change) This answer should work correctly for all situations, and is simpler.
Swift:
/* Standardises and angle to [-180 to 180] degrees */
class func standardAngle(var angle: CLLocationDegrees) -> CLLocationDegrees {
angle %= 360
return angle < -180 ? -360 - angle : angle > 180 ? 360 - 180 : angle
}
/* confirms that a region contains a location */
class func regionContains(region: MKCoordinateRegion, location: CLLocation) -> Bool {
let deltaLat = abs(standardAngle(region.center.latitude - location.coordinate.latitude))
let deltalong = abs(standardAngle(region.center.longitude - location.coordinate.longitude))
return region.span.latitudeDelta >= deltaLat && region.span.longitudeDelta >= deltalong
}
Objective C:
/* Standardises and angle to [-180 to 180] degrees */
+ (CLLocationDegrees)standardAngle:(CLLocationDegrees)angle {
angle %= 360
return angle < -180 ? -360 - angle : angle > 180 ? 360 - 180 : angle
}
/* confirms that a region contains a location */
+ (BOOL)region:(MKCoordinateRegion*)region containsLocation:(CLLocation*)location {
CLLocationDegrees deltaLat = fabs(standardAngle(region.center.latitude - location.coordinate.latitude))
CLLocationDegrees deltalong = fabs(standardAngle(region.center.longitude - location.coordinate.longitude))
return region.span.latitudeDelta >= deltaLat && region.span.longitudeDelta >= deltalong
}
This method fails for regions that include either pole though, but then the coordinate system itself fails at the poles. For most applications, this solution should suffice. (Note, not tested on Objective C)

I've used this code to determine if a coordinate is within a circular region (a coordinate with a radius around it).
- (BOOL)location:(CLLocation *)location isNearCoordinate:(CLLocationCoordinate2D)coordinate withRadius:(CLLocationDistance)radius
{
CLCircularRegion *circularRegion = [[CLCircularRegion alloc] initWithCenter:location.coordinate radius:radius identifier:#"radiusCheck"];
return [circularRegion containsCoordinate:coordinate];
}

Works for me like a charm (Swift 5)
func check(
location: CLLocationCoordinate2D,
contains childLocation: CLLocationCoordinate2D,
with radius: Double)
-> Bool
{
let region = CLCircularRegion(center: location, radius: radius, identifier: "SearchId")
return region.contains(childLocation)
}

Owen Godfrey, the objective-C code doesnΒ΄t work, this is the good code:
Fails on Objective-C, this is the good code:
/* Standardises and angle to [-180 to 180] degrees */
- (CLLocationDegrees)standardAngle:(CLLocationDegrees)angle {
angle=fmod(angle,360);
return angle < -180 ? -360 - angle : angle > 180 ? 360 - 180 : angle;
}
-(BOOL)thisRegion:(MKCoordinateRegion)region containsLocation:(CLLocation *)location{
CLLocationDegrees deltaLat =fabs([self standardAngle:(region.center.latitude-location.coordinate.latitude)]);
CLLocationDegrees deltaLong =fabs([self standardAngle:(region.center.longitude-location.coordinate.longitude)]);
return region.span.latitudeDelta >= deltaLat && region.span.longitudeDelta >=deltaLong;
}
CLLocationDegrees deltalong = fabs(standardAngle(region.center.longitude - location.coordinate.longitude));
return region.span.latitudeDelta >= deltaLat && region.span.longitudeDelta >= deltalong;
}
Thanks!

I had problem with same calculations. I like conception proposed by Owen Godfrey here, bun even Fernando here missed the fact that latitude is wraped diferently than longitude and has diferent range. To clarify my proposal I post it with tests so you can check it out by your self.
import XCTest
import MapKit
// MARK - The Solution
extension CLLocationDegrees {
enum WrapingDimension: Double {
case latitude = 180
case longitude = 360
}
/// Standardises and angle to [-180 to 180] or [-90 to 90] degrees
func wrapped(diemension: WrapingDimension) -> CLLocationDegrees {
let length = diemension.rawValue
let halfLenght = length/2.0
let angle = self.truncatingRemainder(dividingBy: length)
switch diemension {
case .longitude:
// return angle < -180.0 ? 360.0 + angle : angle > 180.0 ? -360.0 + angle : angle
return angle < -halfLenght ? length + angle : angle > halfLenght ? -length + angle : angle
case .latitude:
// return angle < -90.0 ? -180.0 - angle : angle > 90.0 ? 180.0 - angle : angle
return angle < -halfLenght ? -length - angle : angle > halfLenght ? length - angle : angle
}
}
}
extension MKCoordinateRegion {
/// confirms that a region contains a location
func contains(_ coordinate: CLLocationCoordinate2D) -> Bool {
let deltaLat = abs((self.center.latitude - coordinate.latitude).wrapped(diemension: .latitude))
let deltalong = abs((self.center.longitude - coordinate.longitude).wrapped(diemension: .longitude))
return self.span.latitudeDelta/2.0 >= deltaLat && self.span.longitudeDelta/2.0 >= deltalong
}
}
// MARK - Unit tests
class MKCoordinateRegionContaingTests: XCTestCase {
func testRegionContains() {
var region = MKCoordinateRegionMake(CLLocationCoordinate2DMake(0, 0), MKCoordinateSpan(latitudeDelta: 1, longitudeDelta: 1))
var coords = CLLocationCoordinate2DMake(0, 0)
XCTAssert(region.contains(coords))
coords = CLLocationCoordinate2DMake(0.5, 0.5)
XCTAssert(region.contains(coords))
coords = CLLocationCoordinate2DMake(-0.5, 0.5)
XCTAssert(region.contains(coords))
coords = CLLocationCoordinate2DMake(0.5, 0.5000001)
XCTAssert(!region.contains(coords)) // NOT Contains
coords = CLLocationCoordinate2DMake(0.5, -0.5000001)
XCTAssert(!region.contains(coords)) // NOT Contains
coords = CLLocationCoordinate2DMake(1, 1)
XCTAssert(!region.contains(coords)) // NOT Contains
region = MKCoordinateRegionMake(CLLocationCoordinate2DMake(0, 180), MKCoordinateSpan(latitudeDelta: 1, longitudeDelta: 1))
coords = CLLocationCoordinate2DMake(0, 180.5)
XCTAssert(region.contains(coords))
coords.longitude = 179.5
XCTAssert(region.contains(coords))
coords.longitude = 180.5000001
XCTAssert(!region.contains(coords)) // NOT Contains
coords.longitude = 179.5000001
XCTAssert(region.contains(coords))
coords.longitude = 179.4999999
XCTAssert(!region.contains(coords)) // NOT Contains
region = MKCoordinateRegionMake(CLLocationCoordinate2DMake(90, -180), MKCoordinateSpan(latitudeDelta: 1, longitudeDelta: 1))
coords = CLLocationCoordinate2DMake(90.5, -180.5)
XCTAssert(region.contains(coords))
coords = CLLocationCoordinate2DMake(89.5, -180.5)
XCTAssert(region.contains(coords))
coords = CLLocationCoordinate2DMake(90.50000001, -180.5)
XCTAssert(!region.contains(coords)) // NOT Contains
coords = CLLocationCoordinate2DMake(89.50000001, -180.5)
XCTAssert(region.contains(coords))
coords = CLLocationCoordinate2DMake(89.49999999, -180.5)
XCTAssert(!region.contains(coords)) // NOT Contains
}
func testStandardAngle() {
var angle = 180.5.wrapped(diemension: .longitude)
var required = -179.5
XCTAssert(self.areAngleEqual(angle, required))
angle = 360.5.wrapped(diemension: .longitude)
required = 0.5
XCTAssert(self.areAngleEqual(angle, required))
angle = 359.5.wrapped(diemension: .longitude)
required = -0.5
XCTAssert(self.areAngleEqual(angle, required))
angle = 179.5.wrapped(diemension: .longitude)
required = 179.5
XCTAssert(self.areAngleEqual(angle, required))
angle = 90.5.wrapped(diemension: .latitude)
required = 89.5
XCTAssert(self.areAngleEqual(angle, required))
angle = 90.5000001.wrapped(diemension: .latitude)
required = 89.4999999
XCTAssert(self.areAngleEqual(angle, required))
angle = -90.5.wrapped(diemension: .latitude)
required = -89.5
XCTAssert(self.areAngleEqual(angle, required))
angle = -90.5000001.wrapped(diemension: .latitude)
required = -89.4999999
XCTAssert(self.areAngleEqual(angle, required))
}
/// compare doubles with presition to 8 digits after the decimal point
func areAngleEqual(_ a:Double, _ b:Double) -> Bool {
let presition = 0.00000001
let equal = Int(a / presition) == Int(b / presition)
print(String(format:"%14.9f %# %14.9f", a, equal ? "==" : "!=", b) )
return equal
}
}

Based on Lukasz solution, but in Swift, in case anybody can make use of Swift:
func isInRegion (region : MKCoordinateRegion, coordinate : CLLocationCoordinate2D) -> Bool {
let center = region.center;
let northWestCorner = CLLocationCoordinate2D(latitude: center.latitude - (region.span.latitudeDelta / 2.0), longitude: center.longitude - (region.span.longitudeDelta / 2.0))
let southEastCorner = CLLocationCoordinate2D(latitude: center.latitude + (region.span.latitudeDelta / 2.0), longitude: center.longitude + (region.span.longitudeDelta / 2.0))
return (
coordinate.latitude >= northWestCorner.latitude &&
coordinate.latitude <= southEastCorner.latitude &&
coordinate.longitude >= northWestCorner.longitude &&
coordinate.longitude <= southEastCorner.longitude
)
}

MarekR's answer works for me. This is the extension I've put it in:
extension MKCoordinateRegion {
func contains(coordinate:CLLocationCoordinate2D) -> Bool {
cos((center.latitude - coordinate.latitude) * Double.pi/180) > cos(span.latitudeDelta / 2.0*Double.pi/180) &&
cos((center.longitude - coordinate.longitude) * Double.pi/180) > cos(span.longitudeDelta / 2.0*Double.pi/180)
}
}

Related

How do I calculate the altitude needed to display the full MKCoordinateRegion in a MKMapView?

So I want to set a specific position with a specific region shown at a specific rotation.
I know there is a setRegion that calculates the altitude automatically, but it doesn't let you set a rotation. To set a rotation I need to use setRotation that takes a center, altitude and heading.
I have no idea how to calculate the altitude needed to file the full region on the screen, regardless of screen side.
I'm trying:
func updateUIView(_ view: ZooMap, context: Context) {
let coordinate = CLLocationCoordinate2D(
latitude: 21.270381425220606,
longitude: -157.81908260613335
)
let span = MKCoordinateSpan(latitudeDelta: 0.007075784913642025, longitudeDelta: 0.010196763428524491)
let region = MKCoordinateRegion(center: coordinate, span: span)
let camera = MKMapCamera()
camera.heading = 71
camera.centerCoordinate = coordinate;
camera.altitude = determineAltitudeForRegion(region: region, heading: camera.heading, viewport: UIScreen.main.bounds.size)
view.setCamera(camera, animated: false)
}
func determineAltitudeForRegion(region: MKCoordinateRegion, heading: Double, viewport: CGSize) -> Double {
// Calculate a new bounding rectangle that is corrected for the aspect ratio
// of the viewport/camera -- this will be needed to ensure the resulting
// altitude actually fits the polygon in view for the observer.
let upperLeftCoord = CLLocationCoordinate2DMake(region.center.latitude + region.span.latitudeDelta / 2, region.center.longitude - region.span.longitudeDelta / 2)
let upperRightCoord = CLLocationCoordinate2DMake(region.center.latitude + region.span.latitudeDelta / 2, region.center.longitude + region.span.longitudeDelta / 2)
let lowerLeftCoord = CLLocationCoordinate2DMake(region.center.latitude - region.span.latitudeDelta / 2, region.center.longitude - region.span.longitudeDelta / 2)
let hDist = MKMapPoint(upperLeftCoord).distance(to: MKMapPoint(upperRightCoord));
let vDist = MKMapPoint(upperLeftCoord).distance(to: MKMapPoint(lowerLeftCoord));
var adjacent: Double = 0;
var newHDist: Double = 0;
var newVDist: Double = 0;
let rect = RectForRegion(region: region)
if (rect.size.height > rect.size.width) {
newVDist = (Double(viewport.height) / Double(viewport.width)) * hDist;
adjacent = newVDist;
} else {
newHDist = (Double(viewport.width) / Double(viewport.height)) * vDist;
adjacent = newHDist;
}
return adjacent / tan(heading.degreesToRadians) / 2
}
func RectForRegion(region: MKCoordinateRegion) -> MKMapRect {
let a: MKMapPoint = MKMapPoint(CLLocationCoordinate2DMake(region.center.latitude + region.span.latitudeDelta / 2, region.center.longitude - region.span.longitudeDelta / 2))
let b: MKMapPoint = MKMapPoint(CLLocationCoordinate2DMake(region.center.latitude - region.span.latitudeDelta / 2, region.center.longitude + region.span.longitudeDelta / 2))
return MKMapRect(x: min(a.x, b.x), y: min(a.y, b.y), width: abs(a.x - b.x), height: abs(a.y - b.y))
}
but its not working properly. How do I do this??

Swift - Let Constant Value Disappears Stepping To Next Line

I am attempting to overlay a weather radar image onto a map. I get the image and its world data file which tells me where to overlay it on the map. I have to calculate the mapRect of the image from the data associated with the image. My mapRect did not seem to be coming through correctly, so I broke out the code following my let constants of deltaX and deltaY. There is a breakpoint at deltaX so I can see what is happening. When I step over deltaX, it properly assigns the value. When I step over deltaY, deltaX's data disappears, and then deltaY's.
The function is as follows:
func parseWorldFileData(worldFileStringToParse: String) -> (worldFileDataStruct) {
worldFileDataToParse = worldFileStringToParse.components(separatedBy: "\n") //componentsSeparatedByString("\n")
for i in 0...5 {
worldFileDataToParse[i] = String(worldFileDataToParse[i].characters.dropLast(2))
}
if self.worldFileDataToParse.count >= 6 {
worldFileData.xDimension = Double(self.worldFileDataToParse[0])!
worldFileData.rotationParam1 = Double(self.worldFileDataToParse[1])!
worldFileData.rotationParam2 = Double(self.worldFileDataToParse[2])!
worldFileData.yDimension = Double(self.worldFileDataToParse[3])!
worldFileData.xCoordinate = (Double(self.worldFileDataToParse[4])! as CLLocationDegrees)
worldFileData.yCoordinate = (Double(self.worldFileDataToParse[5])! as CLLocationDegrees)
worldFileData.easternLongitude = (self.worldFileData.xCoordinate + ((Double(self.worldFileData.imageWidth)) * self.worldFileData.xDimension) as CLLocationDegrees)
worldFileData.southernLatitude = (self.worldFileData.yCoordinate + ((Double(self.worldFileData.imageHeight)) * self.worldFileData.yDimension) as CLLocationDegrees)
let deltaX = fabs(self.worldFileData.xCoordinate - self.worldFileData.easternLongitude)
let deltaY = fabs(self.worldFileData.yCoordinate - self.worldFileData.southernLatitude)
worldFileData.boundingMapRect = MKMapRectMake(self.worldFileData.xCoordinate, //topLeft.x
self.worldFileData.yCoordinate, //topLeft.y
deltaX, //fabs(topLeft.x-topRight.x)
deltaY) //fabs(topLeft.y - bottomLeft.y)
worldFileData.midCoordinate = CLLocationCoordinate2D(latitude:((self.worldFileData.southernLatitude + (self.worldFileData.yCoordinate - self.worldFileData.southernLatitude)/2) as CLLocationDegrees), longitude: ((self.worldFileData.xCoordinate + (self.worldFileData.easternLongitude - self.worldFileData.xCoordinate)/2) as CLLocationDegrees))
} else { print("Error parsing WorldData file.") }
return worldFileData
}
Just prior to stepping over deltaX, the data is:
worldFileStringToParse String "0.00925265225497161\r\n0.000000\r\n0.000000\r\n-0.00925265225497161\r\n-80.2491661765359\r\n41.5148515181108\r\n"
self WeatherFun.WorldFileDataModel 0x000060000033b260
worldFileDataToParse [String] 7 values
worldFileData WeatherFun.WorldFileDataModel.worldFileDataStruct
xDimension Double 0.0092526522549715995
rotationParam1 Double 0
rotationParam2 Double 0
yDimension Double -0.0092526522549715995
xCoordinate CLLocationDegrees -80.249166176534999
yCoordinate CLLocationDegrees 41.51485151811
imageHeight Double 183.33333333333334
imageWidth Double 200
easternLongitude CLLocationDegrees -78.398635725540686
southernLatitude CLLocationDegrees 39.818531938031875
boundingMapRect MKMapRect
midCoordinate CLLocationCoordinate2D
deltaX CLLocationDegrees
deltaY CLLocationDegrees
The data after the step at deltaX is as follows:
worldFileStringToParse String "0.00925265225497161\r\n0.000000\r\n0.000000\r\n-0.00925265225497161\r\n-80.2491661765359\r\n41.5148515181108\r\n"
self WeatherFun.WorldFileDataModel 0x000060000033b260
worldFileDataToParse [String] 7 values
worldFileData WeatherFun.WorldFileDataModel.worldFileDataStruct
xDimension Double 0.0092526522549715995
rotationParam1 Double 0
rotationParam2 Double 0
yDimension Double -0.0092526522549715995
xCoordinate CLLocationDegrees -80.249166176534999
yCoordinate CLLocationDegrees 41.51485151811
imageHeight Double 183.33333333333334
imageWidth Double 200
easternLongitude CLLocationDegrees -78.398635725540686
southernLatitude CLLocationDegrees 39.818531938031875
boundingMapRect MKMapRect
midCoordinate CLLocationCoordinate2D
deltaX CLLocationDegrees 1.850530450994313
deltaY CLLocationDegrees
When stepping over deltaY, the data is:
worldFileStringToParse String "0.00925265225497161\r\n0.000000\r\n0.000000\r\n-0.00925265225497161\r\n-80.2491661765359\r\n41.5148515181108\r\n"
self WeatherFun.WorldFileDataModel 0x000060000033b260
worldFileDataToParse [String] 7 values
worldFileData WeatherFun.WorldFileDataModel.worldFileDataStruct
xDimension Double 0.0092526522549715995
rotationParam1 Double 0
rotationParam2 Double 0
yDimension Double -0.0092526522549715995
xCoordinate CLLocationDegrees -80.249166176534999
yCoordinate CLLocationDegrees 41.51485151811
imageHeight Double 183.33333333333334
imageWidth Double 200
easternLongitude CLLocationDegrees -78.398635725540686
southernLatitude CLLocationDegrees 39.818531938031875
boundingMapRect MKMapRect
midCoordinate CLLocationCoordinate2D
deltaX CLLocationDegrees
deltaY CLLocationDegrees 1.696319580078125
When I step into the boundingMapRect calculation, this is my data:
worldFileStringToParse String "0.00925265225497161\r\n0.000000\r\n0.000000\r\n-0.00925265225497161\r\n-80.2491661765359\r\n41.5148515181108\r\n"
self WeatherFun.WorldFileDataModel 0x000060000033b260
worldFileDataToParse [String] 7 values
worldFileData WeatherFun.WorldFileDataModel.worldFileDataStruct
xDimension Double 0.0092526522549715995
rotationParam1 Double 0
rotationParam2 Double 0
yDimension Double -0.0092526522549715995
xCoordinate CLLocationDegrees -80.249166176534999
yCoordinate CLLocationDegrees 41.51485151811
imageHeight Double 183.33333333333334
imageWidth Double 200
easternLongitude CLLocationDegrees -78.398635725540686
southernLatitude CLLocationDegrees 39.818531938031875
boundingMapRect MKMapRect
midCoordinate CLLocationCoordinate2D
deltaX CLLocationDegrees
deltaY CLLocationDegrees
Does anyone have any clue of what is happening? The calculations appear to be correct in that the overlay mapRect should span that distance on the map. However, when entering the calculation for boundingMapRect, my deltaX seems to vanish. HELP!!!
After following through further, the data ended up appearing again. I am not sure why this happened, and it is a bit disconcerting, but my advice into the future is to follow it all through.

Calculation that calculates the new latitude and longitude based on a starting point, bearing and distance?

Does anyone have a calculation that calculates the new
latitude and longitude based on a starting point, bearing and
distance?
I would greatly appreciate any help people might have.
I've used the code from Calculate new coordinate x meters and y degree away from one coordinate:
- (CLLocationCoordinate2D)coordinateFromCoord:(CLLocationCoordinate2D)fromCoord
atDistanceKm:(double)distanceKm
atBearingDegrees:(double)bearingDegrees
{
double distanceRadians = distanceKm / 6371.0;
//6,371 = Earth's radius in km
double bearingRadians = [self radiansFromDegrees:bearingDegrees];
double fromLatRadians = [self radiansFromDegrees:fromCoord.latitude];
double fromLonRadians = [self radiansFromDegrees:fromCoord.longitude];
double toLatRadians = asin(sin(fromLatRadians) * cos(distanceRadians)
+ cos(fromLatRadians) * sin(distanceRadians) * cos(bearingRadians) );
double toLonRadians = fromLonRadians + atan2(sin(bearingRadians)
* sin(distanceRadians) * cos(fromLatRadians), cos(distanceRadians)
- sin(fromLatRadians) * sin(toLatRadians));
// adjust toLonRadians to be in the range -180 to +180...
toLonRadians = fmod((toLonRadians + 3*M_PI), (2*M_PI)) - M_PI;
CLLocationCoordinate2D result;
result.latitude = [self degreesFromRadians:toLatRadians];
result.longitude = [self degreesFromRadians:toLonRadians];
return result;
}
- (double)radiansFromDegrees:(double)degrees
{
return degrees * (M_PI/180.0);
}
- (double)degreesFromRadians:(double)radians
{
return radians * (180.0/M_PI);
}
Or in Swift:
extension CLLocationCoordinate2D {
func adjusted(distance: Double, degrees: Double) -> CLLocationCoordinate2D {
let distanceRadians = distance / 6_371 // 6,371 == Earth's radius in km
let bearingRadians = degrees.radians
let fromLatRadians = latitude.radians
let fromLonRadians = longitude.radians
let toLatRadians = asin(sin(fromLatRadians) * cos(distanceRadians) + cos(fromLatRadians) * sin(distanceRadians) * cos(bearingRadians))
var toLonRadians = fromLonRadians + atan2(sin(bearingRadians)
* sin(distanceRadians) * cos(fromLatRadians), cos(distanceRadians)
- sin(fromLatRadians) * sin(toLatRadians))
// adjust toLonRadians to be in the range -180 to +180...
toLonRadians = fmod((toLonRadians + 3 * .pi), (2 * .pi)) - .pi
return CLLocationCoordinate2D(latitude: toLatRadians.degrees, longitude: toLonRadians.degrees)
}
}
extension CLLocationDegrees {
var radians: Double { self * .pi / 180 }
}
extension Double {
var degrees: CLLocationDegrees { self * 180 / .pi }
}
You will find all the calculations you could possibly want (including explanations etc) at http://www.movable-type.co.uk/scripts/latlong.html
The code you need is (in JavaScript) under the heading "Destination point given distance and bearing from start point". Excerpting:
var lat2 = Math.asin( Math.sin(lat1)*Math.cos(d/R) +
Math.cos(lat1)*Math.sin(d/R)*Math.cos(brng) );
var lon2 = lon1 + Math.atan2(Math.sin(brng)*Math.sin(d/R)*Math.cos(lat1),
Math.cos(d/R)-Math.sin(lat1)*Math.sin(lat2));
Where R = radius of the earth, d = distance (in same units), and lat/long are in radians (since that's what the sin function expects). You go from degrees to radians with
radians = pi * degrees / 180;
You should be able to take it from here. Do look at the link I gave for more info.

Finding closest object to CGPoint

I have four UIViews on a UIScrollView (screen divided into quartiles)
On the quartiles, I have a few objects (UIImageViews), on each quartile.
When the user taps the screen, I want to find the closest object to the given CGPoint?
Any ideas?
I have the CGPoint and frame (CGRect) of the objects within each quartile.
UPDATE:
(source: skitch.com)Red Pins are UIImageViews.
// UIScrollView
NSLog(#" UIScrollView: %#", self);
// Here's the tap on the Window in UIScrollView's coordinates
NSLog(#"TapPoint: %3.2f, %3.2f", tapLocation.x, tapLocation.y);
// Find Distance between tap and objects
NSArray *arrayOfCGRrectObjects = [self subviews];
NSEnumerator *enumerator = [arrayOfCGRrectObjects objectEnumerator];
for (UIView *tilesOnScrollView in enumerator) {
// each tile may have 0 or more images
for ( UIView *subview in tilesOnScrollView.subviews ) {
// Is this an UIImageView?
if ( [NSStringFromClass([subview class]) isEqualToString:#"UIImageView"]) {
// Yes, here are the UIImageView details (subView)
NSLog(#"%#", subview);
// Convert CGPoint of UIImageView to CGPoint of UIScrollView for comparison...
// First, Convert CGPoint from UIScrollView to UIImageView's coordinate system for reference
CGPoint found = [subview convertPoint:tapLocation fromView:self];
NSLog(#"Converted Point from ScrollView: %3.2f, %3.2f", found.x, found.y);
// Second, Convert CGPoint from UIScrollView to Window's coordinate system for reference
found = [subview convertPoint:subview.frame.origin toView:nil];
NSLog(#"Converted Point in Window: %3.2f, %3.2f", found.x, found.y);
// Finally, use the object's CGPoint in UIScrollView's coordinates for comparison
found = [subview convertPoint:subview.frame.origin toView:self]; // self is UIScrollView (see above)
NSLog(#"Converted Point: %3.2f, %3.2f", found.x, found.y);
// Determine tap CGPoint in UIImageView's coordinate system
CGPoint localPoint = [touch locationInView:subview];
NSLog(#"LocateInView: %3.2f, %3.2f",localPoint.x, localPoint.y );
//Kalle's code
CGRect newRect = CGRectMake(found.x, found.y, 32, 39);
NSLog(#"Kalle's Distance: %3.2f",[self distanceBetweenRect:newRect andPoint:tapLocation]);
}
Debug Console
Here's the problem. Each Tile is 256x256. The first UIImageView's CGPoint converted to the
UIScrollView's coordinate system (53.25, 399.36) should be dead on with the tapPoint (30,331). Why the difference?? The other point to the right of the tapped point is calculating closer (distance wise)??
<CALayer: 0x706a690>>
[207] TapPoint: 30.00, 331.00
[207] <UIImageView: 0x7073db0; frame = (26.624 71.68; 32 39); opaque = NO; userInteractionEnabled = NO; tag = 55; layer = <CALayer: 0x70747d0>>
[207] Converted Point from ScrollView: 3.38, 3.32
[207] Converted Point in Window: 53.25, 463.36
[207] Converted Point: 53.25, 399.36 *** Looks way off!
[207] LocateInView: 3.38, 3.32
[207] Kalle's Distance: 72.20 **** THIS IS THE TAPPED POINT
[207] <UIImageView: 0x7074fb0; frame = (41.984 43.008; 32 39); opaque = NO; userInteractionEnabled = NO; tag = 55; layer = <CALayer: 0x7074fe0>>
[207] Converted Point from ScrollView: -11.98, 31.99
[207] Converted Point in Window: 83.97, 406.02
[207] Converted Point: 83.97, 342.02
[207] LocateInView: -11.98, 31.99
207] Kalle's Distance: 55.08 ***** BUT THIS ONE's CLOSER??????
The following method should do the trick. If you spot anything weird in it feel free to point it out.
- (CGFloat)distanceBetweenRect:(CGRect)rect andPoint:(CGPoint)point
{
// first of all, we check if point is inside rect. If it is, distance is zero
if (CGRectContainsPoint(rect, point)) return 0.f;
// next we see which point in rect is closest to point
CGPoint closest = rect.origin;
if (rect.origin.x + rect.size.width < point.x)
closest.x += rect.size.width; // point is far right of us
else if (point.x > rect.origin.x)
closest.x = point.x; // point above or below us
if (rect.origin.y + rect.size.height < point.y)
closest.y += rect.size.height; // point is far below us
else if (point.y > rect.origin.y)
closest.y = point.y; // point is straight left or right
// we've got a closest point; now pythagorean theorem
// distance^2 = [closest.x,y - closest.x,point.y]^2 + [closest.x,point.y - point.x,y]^2
// i.e. [closest.y-point.y]^2 + [closest.x-point.x]^2
CGFloat a = powf(closest.y-point.y, 2.f);
CGFloat b = powf(closest.x-point.x, 2.f);
return sqrtf(a + b);
}
Example output:
CGPoint p = CGPointMake(12,12);
CGRect a = CGRectMake(5,5,10,10);
CGRect b = CGRectMake(13,11,10,10);
CGRect c = CGRectMake(50,1,10,10);
NSLog(#"distance p->a: %f", [self distanceBetweenRect:a andPoint:p]);
// 2010-08-24 13:36:39.506 app[4388:207] distance p->a: 0.000000
NSLog(#"distance p->b: %f", [self distanceBetweenRect:b andPoint:p]);
// 2010-08-24 13:38:03.149 app[4388:207] distance p->b: 1.000000
NSLog(#"distance p->c: %f", [self distanceBetweenRect:c andPoint:p]);
// 2010-08-24 13:39:52.148 app[4388:207] distance p->c: 38.013157
There might be more optimized versions out there, so might be worth digging more.
The following method determines the distance between two CGPoints.
- (CGFloat)distanceBetweenPoint:(CGPoint)a andPoint:(CGPoint)b
{
CGFloat a2 = powf(a.x-b.x, 2.f);
CGFloat b2 = powf(a.y-b.y, 2.f);
return sqrtf(a2 + b2)
}
Update: removed fabsf(); -x^2 is the same as x^2, so it's unnecessary.
Update 2: added distanceBetweenPoint:andPoint: method too, for completeness.
If you're using Swift, here's how you can calculate the distance between a CGPoint and a CGRect (e.g. an UIView's frame)
private func distanceToRect(rect: CGRect, fromPoint point: CGPoint) -> CGFloat {
// if it's on the left then (rect.minX - point.x) > 0 and (point.x - rect.maxX) < 0
// if it's on the right then (rect.minX - point.x) < 0 and (point.x - rect.maxX) > 0
// if it's inside the rect then both of them < 0.
let dx = max(rect.minX - point.x, point.x - rect.maxX, 0)
// same as dx
let dy = max(rect.minY - point.y, point.y - rect.maxY, 0)
// if one of them == 0 then the distance is the other one.
if dx * dy == 0 {
return max(dx, dy)
} else {
// both are > 0 then the distance is the hypotenuse
return hypot(dx, dy)
}
}
Thanks #cristian,
Here's Objective-C version of your answer
- (CGFloat)distanceToRect:(CGRect)rect fromPoint:(CGPoint)point
{
CGFloat dx = MAX(0, MAX(CGRectGetMinX(rect) - point.x, point.x - CGRectGetMaxX(rect)));
CGFloat dy = MAX(0, MAX(CGRectGetMinY(rect) - point.y, point.y - CGRectGetMaxY(rect)));
if (dx * dy == 0)
{
return MAX(dx, dy);
}
else
{
return hypot(dx, dy);
}
}
Shorter #cristian answer:
func distance(from rect: CGRect, to point: CGPoint) -> CGFloat {
let dx = max(rect.minX - point.x, point.x - rect.maxX, 0)
let dy = max(rect.minY - point.y, point.y - rect.maxY, 0)
return dx * dy == 0 ? max(dx, dy) : hypot(dx, dy)
}
Personally, I would implement this as a CGPoint extension:
extension CGPoint {
func distance(from rect: CGRect) -> CGFloat {
let dx = max(rect.minX - x, x - rect.maxX, 0)
let dy = max(rect.minY - y, y - rect.maxY, 0)
return dx * dy == 0 ? max(dx, dy) : hypot(dx, dy)
}
}
Alternatively, you can also implement it as a CGRect extension:
extension CGRect {
func distance(from point: CGPoint) -> CGFloat {
let dx = max(minX - point.x, point.x - maxX, 0)
let dy = max(minY - point.y, point.y - maxY, 0)
return dx * dy == 0 ? max(dx, dy) : hypot(dx, dy)
}
}

CLLocation Category for Calculating Bearing w/ Haversine function

I'm trying to write a category for CLLocation to return the bearing to another CLLocation.
I believe I'm doing something wrong with the formula (calculous is not my strong suit). The returned bearing is always off.
I've been looking at this question and tried applying the changes that were accepted as a correct answer and the webpage it references:
Calculating bearing between two CLLocationCoordinate2Ds
http://www.movable-type.co.uk/scripts/latlong.html
Thanks for any pointers. I've tried incorporating the feedback from that other question and I'm still just not getting something.
Thanks
Here's my category -
----- CLLocation+Bearing.h
#import <Foundation/Foundation.h>
#import <CoreLocation/CoreLocation.h>
#interface CLLocation (Bearing)
-(double) bearingToLocation:(CLLocation *) destinationLocation;
-(NSString *) compassOrdinalToLocation:(CLLocation *) nwEndPoint;
#end
---------CLLocation+Bearing.m
#import "CLLocation+Bearing.h"
double DegreesToRadians(double degrees) {return degrees * M_PI / 180;};
double RadiansToDegrees(double radians) {return radians * 180/M_PI;};
#implementation CLLocation (Bearing)
-(double) bearingToLocation:(CLLocation *) destinationLocation {
double lat1 = DegreesToRadians(self.coordinate.latitude);
double lon1 = DegreesToRadians(self.coordinate.longitude);
double lat2 = DegreesToRadians(destinationLocation.coordinate.latitude);
double lon2 = DegreesToRadians(destinationLocation.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);
}
Your code seems fine to me. Nothing wrong with the calculous. You don't specify how far off your results are, but you might try tweaking your radian/degrees converters to this:
double DegreesToRadians(double degrees) {return degrees * M_PI / 180.0;};
double RadiansToDegrees(double radians) {return radians * 180.0/M_PI;};
If you are getting negative bearings, add 2*M_PI to the final result in radiansBearing (or 360 if you do it after converting to degrees). atan2 returns the result in the range -M_PI to M_PI (-180 to 180 degrees), so you might want to convert it to compass bearings, using something like the following code
if(radiansBearing < 0.0)
radiansBearing += 2*M_PI;
This is a porting in Swift of the Category at the beginning:
import Foundation
import CoreLocation
public extension CLLocation{
func DegreesToRadians(_ degrees: Double ) -> Double {
return degrees * M_PI / 180
}
func RadiansToDegrees(_ radians: Double) -> Double {
return radians * 180 / M_PI
}
func bearingToLocationRadian(_ destinationLocation:CLLocation) -> Double {
let lat1 = DegreesToRadians(self.coordinate.latitude)
let lon1 = DegreesToRadians(self.coordinate.longitude)
let lat2 = DegreesToRadians(destinationLocation.coordinate.latitude);
let lon2 = DegreesToRadians(destinationLocation.coordinate.longitude);
let dLon = lon2 - lon1
let y = sin(dLon) * cos(lat2);
let x = cos(lat1) * sin(lat2) - sin(lat1) * cos(lat2) * cos(dLon);
let radiansBearing = atan2(y, x)
return radiansBearing
}
func bearingToLocationDegrees(destinationLocation:CLLocation) -> Double{
return RadiansToDegrees(bearingToLocationRadian(destinationLocation))
}
}
Here is another implementation
public func bearingBetweenTwoPoints(#lat1 : Double, #lon1 : Double, #lat2 : Double, #lon2: Double) -> Double {
func DegreesToRadians (value:Double) -> Double {
return value * M_PI / 180.0
}
func RadiansToDegrees (value:Double) -> Double {
return value * 180.0 / M_PI
}
let y = sin(lon2-lon1) * cos(lat2)
let x = (cos(lat1) * sin(lat2)) - (sin(lat1) * cos(lat2) * cos(lat2-lon1))
let degrees = RadiansToDegrees(atan2(y,x))
let ret = (degrees + 360) % 360
return ret;
}
Working Swift 3 and 4
Tried so many versions and this one finally gives correct values!
extension CLLocation {
func getRadiansFrom(degrees: Double ) -> Double {
return degrees * .pi / 180
}
func getDegreesFrom(radians: Double) -> Double {
return radians * 180 / .pi
}
func bearingRadianTo(location: CLLocation) -> Double {
let lat1 = self.getRadiansFrom(degrees: self.coordinate.latitude)
let lon1 = self.getRadiansFrom(degrees: self.coordinate.longitude)
let lat2 = self.getRadiansFrom(degrees: location.coordinate.latitude)
let lon2 = self.getRadiansFrom(degrees: location.coordinate.longitude)
let dLon = lon2 - lon1
let y = sin(dLon) * cos(lat2)
let x = cos(lat1) * sin(lat2) - sin(lat1) * cos(lat2) * cos(dLon)
var radiansBearing = atan2(y, x)
if radiansBearing < 0.0 {
radiansBearing += 2 * .pi
}
return radiansBearing
}
func bearingDegreesTo(location: CLLocation) -> Double {
return self.getDegreesFrom(radians: self.bearingRadianTo(location: location))
}
}
Usage:
let degrees = location1.bearingDegreesTo(location: location2)
This is an another CLLocation extension can be used in Swift 3 and Swift 4
public extension CLLocation {
func degreesToRadians(degrees: Double) -> Double {
return degrees * .pi / 180.0
}
func radiansToDegrees(radians: Double) -> Double {
return radians * 180.0 / .pi
}
func getBearingBetweenTwoPoints(point1: CLLocation, point2: CLLocation) -> Double {
let lat1 = degreesToRadians(degrees: point1.coordinate.latitude)
let lon1 = degreesToRadians(degrees: point1.coordinate.longitude)
let lat2 = degreesToRadians(degrees: point2.coordinate.latitude)
let lon2 = degreesToRadians(degrees: point2.coordinate.longitude)
let dLon = lon2 - lon1
let y = sin(dLon) * cos(lat2)
let x = cos(lat1) * sin(lat2) - sin(lat1) * cos(lat2) * cos(dLon)
let radiansBearing = atan2(y, x)
return radiansToDegrees(radians: radiansBearing)
}
}
I use the Law of Cosines in Swift. It runs faster than Haversine and its result is extremely similar. Variation of 1 metre on huge distances.
Why do I use the Law of Cosines:
Run fast (because there is no sqrt functions)
Precise enough unless you do some astronomy
Perfect for a background task
func calculateDistance(from: CLLocationCoordinate2D, to: CLLocationCoordinate2D) -> Double {
let Ο€ = M_PI
let degToRad: Double = Ο€/180
let earthRadius: Double = 6372797.560856
// Law of Cosines formula
// d = r . arc cos (sin πœ‘A sin πœ‘B + cos πœ‘A cos πœ‘B cos(πœ†B - πœ†A) )
let πœ‘A = from.latitude * degToRad
let πœ‘B = to.latitude * degToRad
let πœ†A = from.longitude * degToRad
let πœ†B = to.longitude * degToRad
let angularDistance = acos(sin(πœ‘A) * sin(πœ‘B) + cos(πœ‘A) * cos(πœ‘B) * cos(πœ†B - πœ†A) )
let distance = earthRadius * angularDistance
return distance
}
Worth mentioning that if you are using Google map GMSMapView, there's an out-of-the-box solution using the GMSGeometryHeading method:
GMSGeometryHeading(from: CLLocationCoordinate2D, to: CLLocationCoordinate2D)
Returns the initial heading (degrees clockwise of North) at from of
the shortest path to to.
Implemented this in Swift 5. Focus is on accuracy, not speed, but it runs in real time np.
let earthRadius: Double = 6372456.7
let degToRad: Double = .pi / 180.0
let radToDeg: Double = 180.0 / .pi
func calcOffset(_ coord0: CLLocationCoordinate2D,
_ coord1: CLLocationCoordinate2D) -> (Double, Double) {
let lat0: Double = coord0.latitude * degToRad
let lat1: Double = coord1.latitude * degToRad
let lon0: Double = coord0.longitude * degToRad
let lon1: Double = coord1.longitude * degToRad
let dLat: Double = lat1 - lat0
let dLon: Double = lon1 - lon0
let y: Double = cos(lat1) * sin(dLon)
let x: Double = cos(lat0) * sin(lat1) - sin(lat0) * cos(lat1) * cos(dLon)
let t: Double = atan2(y, x)
let bearing: Double = t * radToDeg
let a: Double = pow(sin(dLat * 0.5), 2.0) + cos(lat0) * cos(lat1) * pow(sin(dLon * 0.5), 2.0)
let c: Double = 2.0 * atan2(sqrt(a), sqrt(1.0 - a));
let distance: Double = c * earthRadius
return (distance, bearing)
}
func translateCoord(_ coord: CLLocationCoordinate2D,
_ distance: Double,
_ bearing: Double) -> CLLocationCoordinate2D {
let d: Double = distance / earthRadius
let t: Double = bearing * degToRad
let lat0: Double = coord.latitude * degToRad
let lon0: Double = coord.longitude * degToRad
let lat1: Double = asin(sin(lat0) * cos(d) + cos(lat0) * sin(d) * cos(t))
let lon1: Double = lon0 + atan2(sin(t) * sin(d) * cos(lat0), cos(d) - sin(lat0) * sin(lat1))
let lat: Double = lat1 * radToDeg
let lon: Double = lon1 * radToDeg
let c: CLLocationCoordinate2D = CLLocationCoordinate2D(latitude: lat,
longitude: lon)
return c
}
I found that Haversine nailed the distance versus CLLocation's distance method, but didn't provide a bearing ready-to-use with CL. So I'm not using it for the bearing. This gives the most accurate measurement I've encountered from all the math I've tried. The translateCoord method will also precisely plot a new point given an origin, distance in meters, and a bearing in degrees.