How can I compare CLLocationCoordinate2D
Asked Answered
E

13

52

I need a way of comparing two CLLocationCoordinate2D's however when I tried using == it wouldn't work. Please can someone help me out with the best way of comparing them?

Entopic answered 17/4, 2012 at 21:4 Comment(0)
O
60

Either the generic approach for comparing two instances of any given struct type:

memcmp(&cllc2d1, &second_cllc2d, sizeof(CLLocationCoordinate2D))

or

cllc2d1.latitude == cllc2d2.latitude && cllc2d1.longitude == cllc2d2.longitude

should work, if you really want to be sure they're exactly equal. However, given that latitude and longitude are defined as doubles, you might really want to do a "close enough" comparison:

fabs(cllc2d1.latitude - cllc2d2.latitude) <= epsilon && fabs(cllc2d1.longitude - cllc2d2.longitude) <= epsilon

where epsilon is whatever level of error you want to accept.

Ocarina answered 17/4, 2012 at 21:10 Comment(7)
The first might be considered sketchy, since you're making the assumption that the struct is packed without any bytes between the members.Snivel
Good point. If you zero the entire struct when it's created/allocated then the memcmp approach is safe even for non-packed structs, but otherwise, use with caution.Ocarina
@Ocarina can you please fix abs to fabs and < to <=.Burgeon
what do you mean by zero the entire struct? And what do you mean that struct is packed without any bytes between the members? you mean it may not even be the case>?Thach
I'm having trouble getting this to work for exactly equal locations and can't figure out what I'm doing wrong. See my question at #18201109Declaratory
The first approach is a completely unreliable approach. The second is difficult to use correctly. Why not just calculate the distance and compare the result with a minimum value where two locations are considered "equal"?Allegory
fyi, sizeof is now undefinedHerrera
K
35

As a small addition to all these answers, it's quite handy to have the comparison defined as a preprocessor define:

#define CLCOORDINATES_EQUAL( coord1, coord2 ) (coord1.latitude == coord2.latitude && coord1.longitude == coord2.longitude)

or with epsilon:

#define CLCOORDINATE_EPSILON 0.005f
#define CLCOORDINATES_EQUAL2( coord1, coord2 ) (fabs(coord1.latitude - coord2.latitude) < CLCOORDINATE_EPSILON && fabs(coord1.longitude - coord2.longitude) < CLCOORDINATE_EPSILON)

This allows you to do a comparison as follows:

CLLocationCoordinate2D resultingCoordinate = ... a method call ...;
CLLocationCoordinate2D expectedCoordinate = CLLocationCoordinate2DMake(48.11, 11.12);

if(CLCOORDINATES_EQUAL( resultingCoordinate, expectedCoordinate)) {
    NSLog(@"equal");
} else {
    NSLog(@"not equal");
}

Another alternative is to use an inline method, if you don't like the preprocessor.

Kongo answered 16/8, 2012 at 22:2 Comment(7)
Never compare floating point numbers with ==. See answers where people use epsilon.Cloe
@Cloe See my edited answer. Sometimes you have a use case where you want to compare exact coordinates. E.g. if the source for the coordinates is a place database and you only want to compare those. Then there is no need for a range for the equal comparison. So you might think about your down vote again.Kongo
You didn't get my point. Imagine the case when 1 != 0.99999999999 ... It's general rule for any floating point comparison. Read about it. This rule is old as a computer.Cloe
I still don't get your point. If the source of your coordinates is e.g. a database and you don't do any computations with the coordinates it is just fine to use a regular == comparison.Kongo
But some one computed that coordinates. You mean that all that float coordinates were set manually by some person? Not a computer?Cloe
Im my case I let the user choose a location and save the coordinates into my database. From that point on the coordinates are fixed and will never change. To to syncing between multiple devices I need the exact coordinates, because the user could create two locations that are very close to each other.Kongo
Just a late clarification on the earlier comments here: the issue is how the coordinates are stored. Latitude and longitude are Double values by default. Double values are rarely exact in a computer. If the user enters a latitude, say, of 38.627003, there's no guarantee that the computer will store exactly 38.627003. A recent test printing a computer's interpretation to 15 decimal places returned a value of 38.627002716064453! Now, if you retrieve and store the user's response as a String, then the result would be exact - but remember the default is Double.Scarf
S
26

A Swift extension:

import MapKit

extension CLLocationCoordinate2D: Equatable {}

public func ==(lhs: CLLocationCoordinate2D, rhs: CLLocationCoordinate2D) -> Bool {
    return (lhs.latitude == rhs.latitude && lhs.longitude == rhs.longitude)
}

[tested as of Xcode 7.3.1, Swift 2.2] [and, of course, still has the intrinsic danger of comparing floating point values, so you might want to consider using epsilon as mentioned in earlier answers]

Scarf answered 25/7, 2016 at 22:4 Comment(0)
P
15

You can use the CLLocation class' distanceFromLocation: method. The return value is a CLLocationDistance, which is really just a double.

- (CLLocationDistance)distanceFromLocation:(const CLLocation *)location
Painkiller answered 26/10, 2013 at 19:10 Comment(0)
J
12

In Swift 3, DBL_EPSILON is deprecated. Use Double.ulpOfOne.

extension CLLocationCoordinate2D {
    func isEqual(_ coord: CLLocationCoordinate2D) -> Bool {
        return (fabs(self.latitude - coord.latitude) < .ulpOfOne) && (fabs(self.longitude - coord.longitude) < .ulpOfOne)
    }
}
Jackshaft answered 31/7, 2017 at 16:54 Comment(1)
This should be <= .ulpOfOne instead of < .ulpOfOne because anything less than ulpOfOne is in effect zero by definition.Blind
D
9

You could define a function which feels pretty like coming from CoreLocation:

BOOL CLLocationCoordinateEqual(CLLocationCoordinate2D coordinate1, CLLocationCoordinate2D coordinate2) { return (fabs(coordinate1.latitude - coordinate2.latitude) <= DBL_EPSILON && fabs(coordinate1.longitude - coordinate2.longitude) <= DBL_EPSILON); }

Danyelldanyelle answered 23/2, 2015 at 14:30 Comment(3)
upvoted as this is the most maintainable solution IMOInfidelity
Good solution. Here is the complete method just for new bee. -(BOOL)compairLastSavedCoordinates:(CLLocationCoordinate2D )coordinate1 withNewCoordinates:(CLLocationCoordinate2D)coordinate2 { return (coordinate1.latitude == coordinate2.latitude) && (coordinate1.longitude == coordinate2.longitude); }Blend
Never compare floating point numbers! If you do, it's unreliable. In practice, most comparisons from real locations generated by different sources will return false - even if they are pretty close to each other and can be considered "equal".Allegory
P
6

Updated for Swift 5 based on leann's answer.

import CoreLocation

extension CLLocationCoordinate2D: Equatable {
    static public func ==(lhs: Self, rhs: Self) -> Bool {
        lhs.latitude == rhs.latitude && lhs.longitude == rhs.longitude
    }
}
Pyemia answered 20/10, 2019 at 2:29 Comment(0)
S
2
CLLocationCoordinate2D c1, c2;
if (c1.latitude == c2.latitude && c1.longitude == c2.longitude)
{
    // ...
}

I'm not kidding. CLLocationCoordinate2D is a C struct, and there's no easy way to compare C structs, apart from comparing the individual members.

Snivel answered 17/4, 2012 at 21:9 Comment(0)
E
1

CLLocationCoordinate2D is a C struct, thus, you need to compare its fields:

CLLocationCoordinate2D coord1, coord2;
if (coord1.latitude == coord2.latitude && coord1.longitude == coord2.longitude) {
    // coordinates are equal
}

Note, CLLocationCoordinate2D fields are double, thus, you may get the same issues like with any other floating point comparison. Thus, I suggest to round a little bit values, e.g.:

CLLocationCoordinate2D coord1, coord2;
if (round(coord1.latitude * 1000.0) == round(coord2.latitude * 1000.0)
    && round(coord1.longitude * 1000.0) == round(coord2.longitude * 1000.0)) {
    // coordinates are equal
}

Yes, this, code is slower, but this may help you to avoid problems caused by not so perfect precision of floating point numbers.

Enounce answered 17/4, 2012 at 21:17 Comment(0)
V
1

You can wrap CLLocationCoordinate into an NSValue using this function + (NSValue *)valueWithMKCoordinate:(CLLocationCoordinate2D)coordinate

And then use isEqualToValue to compare.

Quote from Apple doc of isEqualToValue function:

The NSValue class compares the type and contents of each value object to determine equality.

Ref: Apple doc of NSValue

Vitek answered 23/9, 2015 at 5:9 Comment(0)
T
0

We can convert latitude and longitude to NSString and do a string compare.

+ (BOOL)isEqualWithCoordinate:(CLLocationCoordinate2D)location1 withAnotherCoordinate:(CLLocationCoordinate2D)location2{
NSString *locationString1 = [NSString stringWithFormat:@"%g, %g", location1.latitude, location1.longitude];
NSString *locationString2 = [NSString stringWithFormat:@"%g, %g", location2.latitude, location2.longitude];

if ([locationString1 isEqualToString:locationString2]) {
    return YES;
}else{
    return NO;
}

}

Since %g will convert truncate decimal number to 4 digit.

Tristantristas answered 16/3, 2016 at 10:23 Comment(0)
A
0

You have to use some tolerance value to handle Double comparison, with trial and error I found that 0.01 is an acceptable value.

import MapKit

extension CLLocationCoordinate2D: Equatable {
  public static func == (lhs: CLLocationCoordinate2D, rhs: CLLocationCoordinate2D) -> Bool {
    let tolerance = 0.01
    return abs(lhs.latitude - rhs.latitude) < tolerance &&
    abs(lhs.longitude - rhs.longitude) < tolerance
  }
}
Artificial answered 25/8, 2023 at 12:1 Comment(0)
M
-1

Swift 5.3 way

I've created a gist

extension CLLocationCoordinate2D: Equatable {
    public static func == (lhs: CLLocationCoordinate2D, rhs: CLLocationCoordinate2D) -> Bool {
        let numbersAfterCommaAccuracy: Double = 4
        let ratio = numbersAfterCommaAccuracy * 10
        let isLatitudeEqual = ((lhs.latitude - rhs.latitude) * ratio).rounded(.down) == 0
        let isLongitudeEqual = ((lhs.latitude - rhs.latitude) * ratio).rounded(.down) == 0
        return isLatitudeEqual && isLongitudeEqual
    }
}
Montmartre answered 2/4, 2021 at 15:17 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.