├── .gitignore ├── CLLocationUtils ├── CLLocation+Utils.h └── CLLocation+Utils.m ├── CLLocationUtilsProject.xcodeproj ├── project.pbxproj └── project.xcworkspace │ └── contents.xcworkspacedata ├── CLLocationUtilsProject ├── AppDelegate.h ├── AppDelegate.m ├── CLLocationUtilsProject-Info.plist ├── CLLocationUtilsProject-Prefix.pch ├── Default-568h@2x.png ├── Default.png ├── Default@2x.png ├── en.lproj │ └── InfoPlist.strings └── main.m ├── CLLocationUtilsTests ├── CLLocationUtilsTests-Info.plist ├── CLLocationUtilsTests-Prefix.pch ├── CLLocationUtilsTests.h ├── CLLocationUtilsTests.m └── en.lproj │ └── InfoPlist.strings ├── LICENSE └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | # Xcode 2 | .DS_Store 3 | */build/* 4 | *.pbxuser 5 | !default.pbxuser 6 | *.mode1v3 7 | !default.mode1v3 8 | *.mode2v3 9 | !default.mode2v3 10 | *.perspectivev3 11 | !default.perspectivev3 12 | xcuserdata 13 | profile 14 | *.moved-aside 15 | DerivedData 16 | .idea/ 17 | *.hmap 18 | 19 | #CocoaPods 20 | Pods 21 | -------------------------------------------------------------------------------- /CLLocationUtils/CLLocation+Utils.h: -------------------------------------------------------------------------------- 1 | // 2 | // CLLocation+Utils.h 3 | // CLLocationUtils 4 | // 5 | // Created by Fernando Sproviero on 10/07/13. 6 | // Source code based on http://www.movable-type.co.uk/scripts/latlong.html 7 | // Copyright (c) 2013 Fernando Sproviero. All rights reserved. 8 | // 9 | 10 | #import 11 | 12 | /* 13 | * CLLocationRadianCoordinate2D 14 | * 15 | * Discussion: 16 | * A structure that contains a geographical coordinate. 17 | * 18 | * Fields: 19 | * latitude: 20 | * The latitude in radians. 21 | * longitude: 22 | * The longitude in radians. 23 | */ 24 | typedef struct { 25 | double latitude; 26 | double longitude; 27 | } CLLocationRadianCoordinate2D; 28 | 29 | @interface CLLocation (Utils) 30 | /* 31 | * initWithCoordinate2D:coordinate 32 | * 33 | * Discussion: 34 | * Initialize with a CLLocationCoordinate2D struct only. 35 | */ 36 | - (instancetype)initWithCoordinate:(CLLocationCoordinate2D)coordinate; 37 | 38 | /* 39 | * initWithRadianLatitude:radianLongitude 40 | * 41 | * Discussion: 42 | * Initialize with the specified latitude and longitude in radians. 43 | */ 44 | - (id)initWithRadianLatitude:(double)latitude radianLongitude:(double)longitude; 45 | 46 | /* 47 | * initWithPrettyLatitude:prettyLongitude 48 | * 49 | * Discussion: 50 | * Initialize with the specified pretty latitude and pretty longitude. 51 | * e.g. latitude = @"34° 36' 12\" N" longitude = @"34° 36' 12\" W" 52 | */ 53 | - (id)initWithPrettyLatitude:(NSString *)latitude prettyLongitude:(NSString *)longitude; 54 | 55 | /* 56 | * prettyLatitude: 57 | * 58 | * Discussion: 59 | * Returns the latitude coordinate in a pretty format. 60 | * e.g. 34° 36' 12" S 61 | */ 62 | - (NSString *)prettyLatitude; 63 | 64 | /* 65 | * prettyLongitude: 66 | * 67 | * Discussion: 68 | * Returns the longitude coordinate in a pretty format. 69 | * e.g. 58° 22' 54" W 70 | */ 71 | - (NSString *)prettyLongitude; 72 | 73 | /* 74 | * radianCoordinate 75 | * 76 | * Discussion: 77 | * Returns the coordinate of the current location in radians. 78 | */ 79 | - (CLLocationRadianCoordinate2D)radianCoordinate; 80 | 81 | /* 82 | * haversineDistanceFromLocation: 83 | * 84 | * Discussion: 85 | * Returns the distance (in meters) between two locations using the Haversine formula. 86 | * 87 | * from: Haversine formula - R. W. Sinnott, "Virtues of the Haversine", 88 | * Sky and Telescope, vol 68, no 2, 1984 89 | */ 90 | - (CLLocationDistance)haversineDistanceFromLocation:(const CLLocation *)location; 91 | 92 | /* 93 | * sphericalLawOfCosDistanceFromLocation: 94 | * 95 | * Discussion: 96 | * Returns the distance (in meters) between two locations using the Spherical Law of cosines formula. 97 | */ 98 | - (CLLocationDistance)sphericalLawOfCosDistanceFromLocation:(const CLLocation *)location; 99 | 100 | /* 101 | * pythagorasDistanceFromLocation: 102 | * 103 | * Discussion: 104 | * Returns the distance (in coordinate units) between two locations using the Pythagoras formula. 105 | */ 106 | - (double)pythagorasDistanceFromLocation:(const CLLocation *)location; 107 | 108 | /* 109 | * pythagorasEquirectangularDistanceFromLocation: 110 | * 111 | * Discussion: 112 | * Returns the distance (in meters) between two locations using the Pythagoras formula (equirectangular projection) 113 | */ 114 | - (CLLocationDistance)pythagorasEquirectangularDistanceFromLocation:(const CLLocation *)location; 115 | 116 | /* 117 | * midpointWithLocation: 118 | * 119 | * Discussion: 120 | * Returns the half-way location along a great circle path between two locations. 121 | * 122 | * see http://mathforum.org/library/drmath/view/51822.html for derivation 123 | */ 124 | - (CLLocation *)midpointWithLocation:(const CLLocation *)location; 125 | 126 | /* 127 | * initialBearingToLocation: 128 | * 129 | * Discussion: 130 | * Returns the (initial) bearing from this location to the supplied location. 131 | * 132 | * see http://williams.best.vwh.net/avform.htm#Crs 133 | */ 134 | - (double)initialBearingToLocation:(const CLLocation *)location; 135 | 136 | /* 137 | * finalBearingToLocation: 138 | * 139 | * Discussion: 140 | * Returns final bearing arriving at supplied location from this location; the final bearing 141 | * will differ from the initial bearing by varying degrees according to distance and latitude. 142 | */ 143 | - (double)finalBearingToLocation:(const CLLocation *)location; 144 | 145 | /* 146 | * destinationLocationWithInitialBearing:distance 147 | * 148 | * Discussion: 149 | * Returns the destination location from this location having travelled the given distance (in meters) on the 150 | * given initial bearing in degrees (bearing may vary before destination is reached). 151 | * 152 | * see http://williams.best.vwh.net/avform.htm#LL 153 | */ 154 | - (CLLocation *)destinationLocationWithInitialBearing:(double)bearing distance:(CLLocationDistance)distance; 155 | 156 | /* 157 | * pythagorasDestinationLocationWithInitialBearing:pythagorasDistance 158 | * 159 | * Discussion: 160 | * Returns the destination location from this location having travelled the given distance (in coordinate units) on the 161 | * given initial bearing in degrees. 162 | */ 163 | - (CLLocation *)pythagorasDestinationLocationWithInitialBearing:(double)bearing pythagorasDistance:(double)distance; 164 | 165 | /* 166 | * intersectionWithSelfBearing:toLocation:bearing: 167 | * 168 | * Discussion: 169 | * Returns the point of intersection of two paths defined by point and bearing. 170 | * 171 | * see http://williams.best.vwh.net/avform.htm#Intersection 172 | */ 173 | - (CLLocation *)intersectionWithSelfBearing:(double)bearing1 toLocation:(const CLLocation *)location bearing:(double)bearing2; 174 | 175 | /* 176 | * rhumbDistanceFromLocation: 177 | * 178 | * Discussion: 179 | * Returns the distance (in meters) from this point to the supplied location, travelling along a rhumb line. 180 | * 181 | * see http://williams.best.vwh.net/avform.htm#Rhumb 182 | */ 183 | - (CLLocationDistance)rhumbDistanceFromLocation:(const CLLocation *)location; 184 | 185 | /* 186 | * rhumbBearingToLocation: 187 | * 188 | * Discussion: 189 | * Returns the bearing from this location to the supplied location along a rhumb line, in degrees. 190 | */ 191 | - (double)rhumbBearingToLocation:(const CLLocation *)location; 192 | 193 | /* 194 | * rhumbDestinationLocationWithBearing:distance: 195 | * 196 | * Discussion: 197 | * Returns the destination location from this location having travelled the given distance on the 198 | * given bearing along a rhumb line. 199 | */ 200 | - (CLLocation *)rhumbDestinationLocationWithBearing:(double)bearing distance:(CLLocationDistance)distance; 201 | 202 | /* 203 | * rhumbMidpointWithLocation: 204 | * 205 | * Discussion: 206 | * Returns the loxodromic midpoint (along a rhumb line) between this location and the supplied location. 207 | * 208 | * see http://mathforum.org/kb/message.jspa?messageID=148837 209 | */ 210 | - (CLLocation *)rhumbMidpointWithLocation:(const CLLocation *)location; 211 | 212 | /* 213 | * angleWithLocation: 214 | * 215 | * Discussion: 216 | * Returns the angle between two locations of a mercator projection (plain map) 217 | * North is reference (0 degrees) and angle is clockwise (between 0 and 360). 218 | */ 219 | - (double)angleWithLocation:(const CLLocation *)location; 220 | 221 | /* 222 | * angle:betweenAngle:andAngle 223 | * 224 | * Discussion: 225 | * Utility method that returns TRUE if the angle is between angle0 and angle1 226 | * or FALSE in other case. 227 | */ 228 | + (BOOL)angle:(double)angle betweenAngle:(double)angle0 andAngle:(double)angle1; 229 | 230 | @end 231 | -------------------------------------------------------------------------------- /CLLocationUtils/CLLocation+Utils.m: -------------------------------------------------------------------------------- 1 | // 2 | // CLLocation+Utils.m 3 | // CLLocationUtils 4 | // 5 | // Created by Fernando Sproviero on 10/07/13. 6 | // Source code based on http://www.movable-type.co.uk/scripts/latlong.html 7 | // Copyright (c) 2013 Fernando Sproviero. All rights reserved. 8 | // 9 | 10 | #import "CLLocation+Utils.h" 11 | #import 12 | 13 | static const NSInteger R = 6371000; 14 | 15 | @implementation CLLocation (Utils) 16 | 17 | - (instancetype)initWithCoordinate:(CLLocationCoordinate2D)coordinate { 18 | return [self initWithLatitude:coordinate.latitude longitude:coordinate.longitude]; 19 | } 20 | 21 | double degreesToRadians(double degrees) 22 | { 23 | return degrees / 180 * M_PI; 24 | } 25 | 26 | double radiansToDegrees(double radians) 27 | { 28 | return radians * 180 / M_PI; 29 | } 30 | 31 | - (CLLocationRadianCoordinate2D)radianCoordinate { 32 | CLLocationRadianCoordinate2D radCoord; 33 | radCoord.latitude = degreesToRadians(self.coordinate.latitude); 34 | radCoord.longitude = degreesToRadians(self.coordinate.longitude); 35 | return radCoord; 36 | } 37 | 38 | - (id)initWithRadianLatitude:(double)latitude radianLongitude:(double)longitude 39 | { 40 | return [self initWithLatitude:radiansToDegrees(latitude) longitude:radiansToDegrees(longitude)]; 41 | } 42 | 43 | - (double)extractCoordinateFromString:(NSString *)str { 44 | NSScanner *scanner = [NSScanner scannerWithString:str]; 45 | [scanner setCharactersToBeSkipped:[NSCharacterSet characterSetWithCharactersInString:@" °'\""]]; 46 | double operands[3]; 47 | char c = [str characterAtIndex:str.length - 1]; 48 | 49 | [scanner scanDouble:&operands[0]]; 50 | [scanner scanDouble:&operands[1]]; 51 | [scanner scanDouble:&operands[2]]; 52 | 53 | double result = operands[0] + operands[1] / 60 + operands[2] / 3600; 54 | if (c == 'N' || c == 'E') 55 | return result; 56 | 57 | return -result; 58 | } 59 | 60 | - (id)initWithPrettyLatitude:(NSString *)latitude prettyLongitude:(NSString *)longitude 61 | { 62 | NSPredicate *pLat = [NSPredicate predicateWithFormat:@" SELF MATCHES %@", @"\\d+° \\d+\' \\d+\" [NS]"]; 63 | NSPredicate *pLon = [NSPredicate predicateWithFormat:@" SELF MATCHES %@", @"\\d+° \\d+\' \\d+\" [EW]"]; 64 | 65 | if ( [pLat evaluateWithObject:latitude] && [pLon evaluateWithObject:longitude] ) { 66 | double lat = [self extractCoordinateFromString:latitude]; 67 | double lon = [self extractCoordinateFromString:longitude]; 68 | return [self initWithLatitude:lat longitude:lon]; 69 | } 70 | return nil; 71 | } 72 | 73 | - (NSString *)prettyLatitude 74 | { 75 | int latSeconds = (int)round(fabs(self.coordinate.latitude * 3600)); 76 | int latDegrees = latSeconds / 3600; 77 | latSeconds = latSeconds % 3600; 78 | int latMinutes = latSeconds / 60; 79 | latSeconds %= 60; 80 | 81 | char latDirection = (self.coordinate.latitude >= 0) ? 'N' : 'S'; 82 | 83 | return [NSString stringWithFormat:@"%02i° %02i' %02i\" %c", latDegrees, latMinutes, latSeconds, latDirection]; 84 | } 85 | 86 | - (NSString *)prettyLongitude 87 | { 88 | int longSeconds = (int)round(fabs(self.coordinate.longitude * 3600)); 89 | int longDegrees = longSeconds / 3600; 90 | longSeconds = longSeconds % 3600; 91 | int longMinutes = longSeconds / 60; 92 | longSeconds %= 60; 93 | 94 | char longDirection = (self.coordinate.longitude >= 0) ? 'E' : 'W'; 95 | 96 | return [NSString stringWithFormat:@"%02i° %02i' %02i\" %c", longDegrees, longMinutes, longSeconds, longDirection]; 97 | } 98 | 99 | - (CLLocationDistance)haversineDistanceFromLocation:(const CLLocation *)location 100 | { 101 | double dLat = degreesToRadians(location.coordinate.latitude - self.coordinate.latitude); 102 | double dLon = degreesToRadians(location.coordinate.longitude - self.coordinate.longitude); 103 | double lat1 = degreesToRadians(self.coordinate.latitude); 104 | double lat2 = degreesToRadians(location.coordinate.latitude); 105 | 106 | double a = sin(dLat/2) * sin(dLat/2) + sin(dLon/2) * sin(dLon/2) * cos(lat1) * cos(lat2); 107 | double c = 2 * atan2(sqrt(a), sqrt(1-a)); 108 | return R * c; 109 | } 110 | 111 | - (CLLocationDistance)sphericalLawOfCosDistanceFromLocation:(const CLLocation *)location 112 | { 113 | double dLon = degreesToRadians(location.coordinate.longitude - self.coordinate.longitude); 114 | double lat1 = degreesToRadians(self.coordinate.latitude); 115 | double lat2 = degreesToRadians(location.coordinate.latitude); 116 | return acos(sin(lat1) * sin(lat2) + cos(lat1) * cos(lat2) * cos(dLon)) * R; 117 | } 118 | 119 | - (CLLocationDistance)pythagorasEquirectangularDistanceFromLocation:(const CLLocation *)location 120 | { 121 | double dLat = degreesToRadians(location.coordinate.latitude - self.coordinate.latitude); 122 | double dLon = degreesToRadians(location.coordinate.longitude - self.coordinate.longitude); 123 | double sLat = degreesToRadians(location.coordinate.latitude + self.coordinate.latitude); 124 | double x = dLon * cos(sLat/2); 125 | return sqrt(x*x + dLat*dLat) * R; 126 | } 127 | 128 | - (double)pythagorasDistanceFromLocation:(const CLLocation *)location 129 | { 130 | double dLat = location.coordinate.latitude - self.coordinate.latitude; 131 | double dLon = location.coordinate.longitude - self.coordinate.longitude; 132 | return sqrt(dLon*dLon + dLat*dLat); 133 | } 134 | 135 | - (CLLocation *)midpointWithLocation:(const CLLocation *)location 136 | { 137 | double dLon = degreesToRadians(location.coordinate.longitude - self.coordinate.longitude); 138 | double lat1 = degreesToRadians(self.coordinate.latitude); 139 | double lat2 = degreesToRadians(location.coordinate.latitude); 140 | double lon1 = degreesToRadians(self.coordinate.longitude); 141 | 142 | double Bx = cos(lat2) * cos(dLon); 143 | double By = cos(lat2) * sin(dLon); 144 | double lat3 = atan2(sin(lat1) + sin(lat2), sqrt( (cos(lat1)+Bx) * (cos(lat1)+Bx) + By*By ) ); 145 | double lon3 = lon1 + atan2(By, cos(lat1) + Bx); 146 | 147 | return [[CLLocation alloc] initWithRadianLatitude:lat3 radianLongitude:lon3]; 148 | } 149 | 150 | - (double)initialBearingToLocation:(const CLLocation *)location 151 | { 152 | double dLon = degreesToRadians(location.coordinate.longitude - self.coordinate.longitude); 153 | double lat1 = degreesToRadians(self.coordinate.latitude); 154 | double lat2 = degreesToRadians(location.coordinate.latitude); 155 | 156 | double y = sin(dLon) * cos(lat2); 157 | double x = cos(lat1) * sin(lat2) - sin(lat1) * cos(lat2) * cos(dLon); 158 | double brng = radiansToDegrees( atan2(y, x) ); 159 | return fmod(brng + 360, 360); 160 | } 161 | 162 | - (double)finalBearingToLocation:(const CLLocation *)location 163 | { 164 | double dLon = degreesToRadians(self.coordinate.longitude - location.coordinate.longitude); 165 | double lat2 = degreesToRadians(self.coordinate.latitude); 166 | double lat1 = degreesToRadians(location.coordinate.latitude); 167 | 168 | double y = sin(dLon) * cos(lat2); 169 | double x = cos(lat1) * sin(lat2) - sin(lat1) * cos(lat2) * cos(dLon); 170 | double brng = radiansToDegrees( atan2(y, x) ); 171 | return fmod(brng + 180, 360); 172 | } 173 | 174 | - (CLLocation *)destinationLocationWithInitialBearing:(double)bearing distance:(CLLocationDistance)distance { 175 | double angularDistance = distance/R; 176 | double brng = degreesToRadians(bearing); 177 | double lat1 = degreesToRadians(self.coordinate.latitude); 178 | double lon1 = degreesToRadians(self.coordinate.longitude); 179 | 180 | double lat2 = asin( sin(lat1) * cos(angularDistance) + 181 | cos(lat1) * sin(angularDistance) * cos(brng) ); 182 | double lon2 = lon1 + atan2( sin(brng) * sin(angularDistance) * cos(lat1), 183 | cos(angularDistance) - sin(lat1) * sin(lat2) ); 184 | lon2 = fmod( lon2 + 3 * M_PI, 2 * M_PI ) - M_PI; // normalise to -180..+180º 185 | 186 | return [[CLLocation alloc] initWithRadianLatitude:lat2 radianLongitude:lon2]; 187 | } 188 | 189 | - (CLLocation *)pythagorasDestinationLocationWithInitialBearing:(double)bearing pythagorasDistance:(double)distance { 190 | double lon1 = distance * sin(degreesToRadians(bearing)) + self.coordinate.longitude; 191 | double lat1 = distance * cos(degreesToRadians(bearing)) + self.coordinate.latitude; 192 | return [[CLLocation alloc] initWithLatitude:lat1 longitude:lon1]; 193 | } 194 | 195 | - (double)angleWithLocation:(const CLLocation *)location 196 | { 197 | double lat1 = degreesToRadians(self.coordinate.latitude); 198 | double lat2 = degreesToRadians(location.coordinate.latitude); 199 | double lon1 = degreesToRadians(self.coordinate.longitude); 200 | double lon2 = degreesToRadians(location.coordinate.longitude); 201 | 202 | double theta = atan2(lon2 - lon1, lat2 - lat1); 203 | 204 | double angle = radiansToDegrees( theta ); 205 | 206 | // convert to positive range [0-360) 207 | // since we want to prevent negative angles, adjust them now. 208 | // we can assume that atan2 will not return a negative value 209 | // greater than one partial rotation 210 | if (angle < 0) { 211 | angle += 360; 212 | } 213 | return angle; 214 | } 215 | 216 | + (BOOL)angle:(double)angle betweenAngle:(double)angle0 andAngle:(double)angle1 217 | { 218 | double n = fmod(360 + (fmod(angle,360)),360); 219 | double a = fmod((3600000 + angle0), 360); 220 | double b = fmod((3600000 + angle1), 360); 221 | 222 | if (a < b) 223 | return a <= n && n <= b; 224 | 225 | return a <= n || n <= b; 226 | } 227 | 228 | - (CLLocation *)intersectionWithSelfBearing:(double)bearing1 toLocation:(const CLLocation *)location bearing:(double)bearing2 229 | { 230 | double lat1 = self.radianCoordinate.latitude; 231 | double lon1 = self.radianCoordinate.longitude; 232 | double lat2 = location.radianCoordinate.latitude; 233 | double lon2 = location.radianCoordinate.longitude; 234 | double brng13 = degreesToRadians(bearing1); 235 | double brng23 = degreesToRadians(bearing2); 236 | double dLat = lat2-lat1; 237 | double dLon = lon2-lon1; 238 | 239 | double dist12 = 2*asin( sqrt( sin(dLat/2)*sin(dLat/2) + 240 | cos(lat1)*cos(lat2)*sin(dLon/2)*sin(dLon/2) ) ); 241 | if (dist12 == 0) return nil; 242 | 243 | // initial/final bearings between points 244 | double brngA = acos( ( sin(lat2) - sin(lat1)*cos(dist12) ) / 245 | ( sin(dist12)*cos(lat1) ) ); 246 | if (isnan(brngA)) brngA = 0; // protect against rounding 247 | double brngB = acos( ( sin(lat1) - sin(lat2)*cos(dist12) ) / 248 | ( sin(dist12)*cos(lat2) ) ); 249 | 250 | double brng12, brng21; 251 | if (sin(lon2-lon1) > 0) { 252 | brng12 = brngA; 253 | brng21 = 2*M_PI - brngB; 254 | } else { 255 | brng12 = 2*M_PI - brngA; 256 | brng21 = brngB; 257 | } 258 | 259 | double alpha1 = fmod(brng13 - brng12 + M_PI, 2*M_PI) - M_PI; // angle 2-1-3 260 | double alpha2 = fmod(brng21 - brng23 + M_PI, 2*M_PI) - M_PI; // angle 1-2-3 261 | 262 | if (sin(alpha1)==0 && sin(alpha2)==0) return nil; // infinite intersections 263 | if (sin(alpha1)*sin(alpha2) < 0) return nil; // ambiguous intersection 264 | 265 | //alpha1 = fabs(alpha1); 266 | //alpha2 = fabs(alpha2); 267 | // ... Ed Williams takes abs of alpha1/alpha2, but seems to break calculation? 268 | 269 | double alpha3 = acos( -cos(alpha1)*cos(alpha2) + 270 | sin(alpha1)*sin(alpha2)*cos(dist12) ); 271 | double dist13 = atan2( sin(dist12)*sin(alpha1)*sin(alpha2), 272 | cos(alpha2)+cos(alpha1)*cos(alpha3) ); 273 | double lat3 = asin( sin(lat1)*cos(dist13) + 274 | cos(lat1)*sin(dist13)*cos(brng13) ); 275 | double dLon13 = atan2( sin(brng13)*sin(dist13)*cos(lat1), 276 | cos(dist13)-sin(lat1)*sin(lat3) ); 277 | double lon3 = lon1+dLon13; 278 | lon3 = fmod(lon3+3*M_PI, 2*M_PI) - M_PI; // normalise to -180..+180º 279 | 280 | return [[CLLocation alloc] initWithRadianLatitude:lat3 radianLongitude:lon3]; 281 | } 282 | 283 | - (CLLocationDistance)rhumbDistanceFromLocation:(const CLLocation *)location 284 | { 285 | double dLat = degreesToRadians(location.coordinate.latitude - self.coordinate.latitude); 286 | double dLon = degreesToRadians(fabs(location.coordinate.longitude - self.coordinate.longitude)); 287 | double lat1 = self.radianCoordinate.latitude; 288 | double lat2 = location.radianCoordinate.latitude; 289 | 290 | double dPhi = log(tan(lat2/2+M_PI_4)/tan(lat1/2+M_PI_4)); 291 | double q = (isfinite(dLat/dPhi)) ? dLat/dPhi : cos(lat1); // E-W line gives dPhi=0 292 | 293 | // if dLon over 180° take shorter rhumb across anti-meridian: 294 | if (fabs(dLon) > M_PI) { 295 | dLon = dLon>0 ? -(2*M_PI-dLon) : (2*M_PI+dLon); 296 | } 297 | 298 | return sqrt(dLat*dLat + q*q*dLon*dLon) * R; 299 | } 300 | 301 | - (double)rhumbBearingToLocation:(const CLLocation *)location 302 | { 303 | double dLon = degreesToRadians(location.coordinate.longitude - self.coordinate.longitude); 304 | double lat1 = self.radianCoordinate.latitude; 305 | double lat2 = location.radianCoordinate.latitude; 306 | 307 | double dPhi = log(tan(lat2/2+M_PI_4)/tan(lat1/2+M_PI_4)); 308 | if (fabs(dLon) > M_PI) dLon = dLon>0 ? -(2*M_PI-dLon) : (2*M_PI+dLon); 309 | double brng = atan2(dLon, dPhi); 310 | 311 | return fmod(radiansToDegrees(brng)+360, 360); 312 | } 313 | 314 | - (CLLocation *)rhumbDestinationLocationWithBearing:(double)bearing distance:(CLLocationDistance)distance { 315 | double d = distance/R; // d = angular distance covered on earth’s surface 316 | double lat1 = self.radianCoordinate.latitude; 317 | double lon1 = self.radianCoordinate.longitude; 318 | double brng = degreesToRadians(bearing); 319 | 320 | double dLat = d*cos(brng); 321 | // nasty kludge to overcome ill-conditioned results around parallels of latitude: 322 | if (fabs(dLat) < 1e-10) dLat = 0; // dLat < 1 mm 323 | 324 | double lat2 = lat1 + dLat; 325 | double dPhi = log(tan(lat2/2+M_PI_4)/tan(lat1/2+M_PI_4)); 326 | double q = (isfinite(dLat/dPhi)) ? dLat/dPhi : cos(lat1); // E-W line gives dPhi=0 327 | double dLon = d*sin(brng)/q; 328 | 329 | // check for some daft bugger going past the pole, normalise latitude if so 330 | if (fabs(lat2) > M_PI_2) lat2 = lat2>0 ? M_PI-lat2 : -M_PI-lat2; 331 | 332 | double lon2 = fmod(lon1+dLon+3*M_PI, 2*M_PI) - M_PI; 333 | 334 | return [[CLLocation alloc] initWithRadianLatitude:lat2 radianLongitude:lon2]; 335 | } 336 | 337 | - (CLLocation *)rhumbMidpointWithLocation:(const CLLocation *)location 338 | { 339 | double lat1 = self.radianCoordinate.latitude; 340 | double lon1 = self.radianCoordinate.longitude; 341 | double lat2 = location.radianCoordinate.latitude; 342 | double lon2 = location.radianCoordinate.longitude; 343 | 344 | if (fabs(lon2-lon1) > M_PI) lon1 += 2*M_PI; // crossing anti-meridian 345 | 346 | double lat3 = (lat1+lat2)/2; 347 | double f1 = tan(M_PI_4 + lat1/2); 348 | double f2 = tan(M_PI_4 + lat2/2); 349 | double f3 = tan(M_PI_4 + lat3/2); 350 | double lon3 = ( (lon2-lon1)*log(f3) + lon1*log(f2) - lon2*log(f1) ) / log(f2/f1); 351 | 352 | if (!isfinite(lon3)) lon3 = (lon1+lon2)/2; // parallel of latitude 353 | 354 | lon3 = fmod(lon3+3*M_PI, 2*M_PI) - M_PI; // normalise to -180..+180º 355 | 356 | return [[CLLocation alloc] initWithRadianLatitude:lat3 radianLongitude:lon3]; 357 | } 358 | 359 | @end 360 | -------------------------------------------------------------------------------- /CLLocationUtilsProject.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | CBB4A92217A584C400763209 /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = CBB4A92117A584C400763209 /* UIKit.framework */; }; 11 | CBB4A92417A584C400763209 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = CBB4A92317A584C400763209 /* Foundation.framework */; }; 12 | CBB4A92617A584C400763209 /* CoreGraphics.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = CBB4A92517A584C400763209 /* CoreGraphics.framework */; }; 13 | CBB4A92C17A584C400763209 /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = CBB4A92A17A584C400763209 /* InfoPlist.strings */; }; 14 | CBB4A92E17A584C400763209 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = CBB4A92D17A584C400763209 /* main.m */; }; 15 | CBB4A93217A584C400763209 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = CBB4A93117A584C400763209 /* AppDelegate.m */; }; 16 | CBB4A93417A584C400763209 /* Default.png in Resources */ = {isa = PBXBuildFile; fileRef = CBB4A93317A584C400763209 /* Default.png */; }; 17 | CBB4A93617A584C400763209 /* Default@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = CBB4A93517A584C400763209 /* Default@2x.png */; }; 18 | CBB4A93817A584C400763209 /* Default-568h@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = CBB4A93717A584C400763209 /* Default-568h@2x.png */; }; 19 | CBB4A94117A5855D00763209 /* CLLocation+Utils.m in Sources */ = {isa = PBXBuildFile; fileRef = CBB4A94017A5855D00763209 /* CLLocation+Utils.m */; }; 20 | CBB4A94317A5858A00763209 /* CoreLocation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = CBB4A94217A5858A00763209 /* CoreLocation.framework */; }; 21 | CBB4A94B17A5888F00763209 /* SenTestingKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = CBB4A94A17A5888F00763209 /* SenTestingKit.framework */; }; 22 | CBB4A94C17A5888F00763209 /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = CBB4A92117A584C400763209 /* UIKit.framework */; }; 23 | CBB4A94D17A5888F00763209 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = CBB4A92317A584C400763209 /* Foundation.framework */; }; 24 | CBB4A95317A5888F00763209 /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = CBB4A95117A5888F00763209 /* InfoPlist.strings */; }; 25 | CBB4A96117A58FCF00763209 /* CoreLocation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = CBB4A94217A5858A00763209 /* CoreLocation.framework */; }; 26 | CBB4A96717A590AF00763209 /* CLLocationUtilsTests.m in Sources */ = {isa = PBXBuildFile; fileRef = CBB4A96517A5907A00763209 /* CLLocationUtilsTests.m */; }; 27 | /* End PBXBuildFile section */ 28 | 29 | /* Begin PBXContainerItemProxy section */ 30 | CBB4A95E17A58D5D00763209 /* PBXContainerItemProxy */ = { 31 | isa = PBXContainerItemProxy; 32 | containerPortal = CBB4A91617A584C400763209 /* Project object */; 33 | proxyType = 1; 34 | remoteGlobalIDString = CBB4A91D17A584C400763209; 35 | remoteInfo = CLLocationUtilsProject; 36 | }; 37 | /* End PBXContainerItemProxy section */ 38 | 39 | /* Begin PBXFileReference section */ 40 | CBB4A91E17A584C400763209 /* CLLocationUtilsProject.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = CLLocationUtilsProject.app; sourceTree = BUILT_PRODUCTS_DIR; }; 41 | CBB4A92117A584C400763209 /* UIKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = UIKit.framework; path = System/Library/Frameworks/UIKit.framework; sourceTree = SDKROOT; }; 42 | CBB4A92317A584C400763209 /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = System/Library/Frameworks/Foundation.framework; sourceTree = SDKROOT; }; 43 | CBB4A92517A584C400763209 /* CoreGraphics.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreGraphics.framework; path = System/Library/Frameworks/CoreGraphics.framework; sourceTree = SDKROOT; }; 44 | CBB4A92917A584C400763209 /* CLLocationUtilsProject-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "CLLocationUtilsProject-Info.plist"; sourceTree = ""; }; 45 | CBB4A92B17A584C400763209 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/InfoPlist.strings; sourceTree = ""; }; 46 | CBB4A92D17A584C400763209 /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; 47 | CBB4A92F17A584C400763209 /* CLLocationUtilsProject-Prefix.pch */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "CLLocationUtilsProject-Prefix.pch"; sourceTree = ""; }; 48 | CBB4A93017A584C400763209 /* AppDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = ""; }; 49 | CBB4A93117A584C400763209 /* AppDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = ""; }; 50 | CBB4A93317A584C400763209 /* Default.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = Default.png; sourceTree = ""; }; 51 | CBB4A93517A584C400763209 /* Default@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "Default@2x.png"; sourceTree = ""; }; 52 | CBB4A93717A584C400763209 /* Default-568h@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "Default-568h@2x.png"; sourceTree = ""; }; 53 | CBB4A93F17A5855D00763209 /* CLLocation+Utils.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "CLLocation+Utils.h"; sourceTree = ""; }; 54 | CBB4A94017A5855D00763209 /* CLLocation+Utils.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "CLLocation+Utils.m"; sourceTree = ""; }; 55 | CBB4A94217A5858A00763209 /* CoreLocation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreLocation.framework; path = System/Library/Frameworks/CoreLocation.framework; sourceTree = SDKROOT; }; 56 | CBB4A94917A5888F00763209 /* CLLocationUtilsTests.octest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = CLLocationUtilsTests.octest; sourceTree = BUILT_PRODUCTS_DIR; }; 57 | CBB4A94A17A5888F00763209 /* SenTestingKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SenTestingKit.framework; path = Library/Frameworks/SenTestingKit.framework; sourceTree = DEVELOPER_DIR; }; 58 | CBB4A95017A5888F00763209 /* CLLocationUtilsTests-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "CLLocationUtilsTests-Info.plist"; sourceTree = ""; }; 59 | CBB4A95217A5888F00763209 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/InfoPlist.strings; sourceTree = ""; }; 60 | CBB4A95717A5888F00763209 /* CLLocationUtilsTests-Prefix.pch */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "CLLocationUtilsTests-Prefix.pch"; sourceTree = ""; }; 61 | CBB4A96417A5907A00763209 /* CLLocationUtilsTests.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CLLocationUtilsTests.h; sourceTree = ""; }; 62 | CBB4A96517A5907A00763209 /* CLLocationUtilsTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CLLocationUtilsTests.m; sourceTree = ""; }; 63 | /* End PBXFileReference section */ 64 | 65 | /* Begin PBXFrameworksBuildPhase section */ 66 | CBB4A91B17A584C400763209 /* Frameworks */ = { 67 | isa = PBXFrameworksBuildPhase; 68 | buildActionMask = 2147483647; 69 | files = ( 70 | CBB4A94317A5858A00763209 /* CoreLocation.framework in Frameworks */, 71 | CBB4A92217A584C400763209 /* UIKit.framework in Frameworks */, 72 | CBB4A92417A584C400763209 /* Foundation.framework in Frameworks */, 73 | CBB4A92617A584C400763209 /* CoreGraphics.framework in Frameworks */, 74 | ); 75 | runOnlyForDeploymentPostprocessing = 0; 76 | }; 77 | CBB4A94517A5888F00763209 /* Frameworks */ = { 78 | isa = PBXFrameworksBuildPhase; 79 | buildActionMask = 2147483647; 80 | files = ( 81 | CBB4A96117A58FCF00763209 /* CoreLocation.framework in Frameworks */, 82 | CBB4A94B17A5888F00763209 /* SenTestingKit.framework in Frameworks */, 83 | CBB4A94C17A5888F00763209 /* UIKit.framework in Frameworks */, 84 | CBB4A94D17A5888F00763209 /* Foundation.framework in Frameworks */, 85 | ); 86 | runOnlyForDeploymentPostprocessing = 0; 87 | }; 88 | /* End PBXFrameworksBuildPhase section */ 89 | 90 | /* Begin PBXGroup section */ 91 | CBB4A91517A584C400763209 = { 92 | isa = PBXGroup; 93 | children = ( 94 | CBB4A93E17A5855D00763209 /* CLLocationUtils */, 95 | CBB4A92717A584C400763209 /* CLLocationUtilsProject */, 96 | CBB4A94E17A5888F00763209 /* CLLocationUtilsTests */, 97 | CBB4A92017A584C400763209 /* Frameworks */, 98 | CBB4A91F17A584C400763209 /* Products */, 99 | ); 100 | sourceTree = ""; 101 | }; 102 | CBB4A91F17A584C400763209 /* Products */ = { 103 | isa = PBXGroup; 104 | children = ( 105 | CBB4A91E17A584C400763209 /* CLLocationUtilsProject.app */, 106 | CBB4A94917A5888F00763209 /* CLLocationUtilsTests.octest */, 107 | ); 108 | name = Products; 109 | sourceTree = ""; 110 | }; 111 | CBB4A92017A584C400763209 /* Frameworks */ = { 112 | isa = PBXGroup; 113 | children = ( 114 | CBB4A94217A5858A00763209 /* CoreLocation.framework */, 115 | CBB4A92117A584C400763209 /* UIKit.framework */, 116 | CBB4A92317A584C400763209 /* Foundation.framework */, 117 | CBB4A92517A584C400763209 /* CoreGraphics.framework */, 118 | CBB4A94A17A5888F00763209 /* SenTestingKit.framework */, 119 | ); 120 | name = Frameworks; 121 | sourceTree = ""; 122 | }; 123 | CBB4A92717A584C400763209 /* CLLocationUtilsProject */ = { 124 | isa = PBXGroup; 125 | children = ( 126 | CBB4A93017A584C400763209 /* AppDelegate.h */, 127 | CBB4A93117A584C400763209 /* AppDelegate.m */, 128 | CBB4A92817A584C400763209 /* Supporting Files */, 129 | ); 130 | path = CLLocationUtilsProject; 131 | sourceTree = ""; 132 | }; 133 | CBB4A92817A584C400763209 /* Supporting Files */ = { 134 | isa = PBXGroup; 135 | children = ( 136 | CBB4A92917A584C400763209 /* CLLocationUtilsProject-Info.plist */, 137 | CBB4A92A17A584C400763209 /* InfoPlist.strings */, 138 | CBB4A92D17A584C400763209 /* main.m */, 139 | CBB4A92F17A584C400763209 /* CLLocationUtilsProject-Prefix.pch */, 140 | CBB4A93317A584C400763209 /* Default.png */, 141 | CBB4A93517A584C400763209 /* Default@2x.png */, 142 | CBB4A93717A584C400763209 /* Default-568h@2x.png */, 143 | ); 144 | name = "Supporting Files"; 145 | sourceTree = ""; 146 | }; 147 | CBB4A93E17A5855D00763209 /* CLLocationUtils */ = { 148 | isa = PBXGroup; 149 | children = ( 150 | CBB4A93F17A5855D00763209 /* CLLocation+Utils.h */, 151 | CBB4A94017A5855D00763209 /* CLLocation+Utils.m */, 152 | ); 153 | path = CLLocationUtils; 154 | sourceTree = ""; 155 | }; 156 | CBB4A94E17A5888F00763209 /* CLLocationUtilsTests */ = { 157 | isa = PBXGroup; 158 | children = ( 159 | CBB4A96417A5907A00763209 /* CLLocationUtilsTests.h */, 160 | CBB4A96517A5907A00763209 /* CLLocationUtilsTests.m */, 161 | CBB4A94F17A5888F00763209 /* Supporting Files */, 162 | ); 163 | path = CLLocationUtilsTests; 164 | sourceTree = ""; 165 | }; 166 | CBB4A94F17A5888F00763209 /* Supporting Files */ = { 167 | isa = PBXGroup; 168 | children = ( 169 | CBB4A95017A5888F00763209 /* CLLocationUtilsTests-Info.plist */, 170 | CBB4A95117A5888F00763209 /* InfoPlist.strings */, 171 | CBB4A95717A5888F00763209 /* CLLocationUtilsTests-Prefix.pch */, 172 | ); 173 | name = "Supporting Files"; 174 | sourceTree = ""; 175 | }; 176 | /* End PBXGroup section */ 177 | 178 | /* Begin PBXNativeTarget section */ 179 | CBB4A91D17A584C400763209 /* CLLocationUtilsProject */ = { 180 | isa = PBXNativeTarget; 181 | buildConfigurationList = CBB4A93B17A584C400763209 /* Build configuration list for PBXNativeTarget "CLLocationUtilsProject" */; 182 | buildPhases = ( 183 | CBB4A91A17A584C400763209 /* Sources */, 184 | CBB4A91B17A584C400763209 /* Frameworks */, 185 | CBB4A91C17A584C400763209 /* Resources */, 186 | ); 187 | buildRules = ( 188 | ); 189 | dependencies = ( 190 | ); 191 | name = CLLocationUtilsProject; 192 | productName = CLLocationUtilsProject; 193 | productReference = CBB4A91E17A584C400763209 /* CLLocationUtilsProject.app */; 194 | productType = "com.apple.product-type.application"; 195 | }; 196 | CBB4A94817A5888F00763209 /* CLLocationUtilsTests */ = { 197 | isa = PBXNativeTarget; 198 | buildConfigurationList = CBB4A95817A5888F00763209 /* Build configuration list for PBXNativeTarget "CLLocationUtilsTests" */; 199 | buildPhases = ( 200 | CBB4A94417A5888F00763209 /* Sources */, 201 | CBB4A94517A5888F00763209 /* Frameworks */, 202 | CBB4A94617A5888F00763209 /* Resources */, 203 | CBB4A94717A5888F00763209 /* ShellScript */, 204 | ); 205 | buildRules = ( 206 | ); 207 | dependencies = ( 208 | CBB4A95F17A58D5D00763209 /* PBXTargetDependency */, 209 | ); 210 | name = CLLocationUtilsTests; 211 | productName = CLLocationUtilsTests; 212 | productReference = CBB4A94917A5888F00763209 /* CLLocationUtilsTests.octest */; 213 | productType = "com.apple.product-type.bundle"; 214 | }; 215 | /* End PBXNativeTarget section */ 216 | 217 | /* Begin PBXProject section */ 218 | CBB4A91617A584C400763209 /* Project object */ = { 219 | isa = PBXProject; 220 | attributes = { 221 | LastUpgradeCheck = 0460; 222 | ORGANIZATIONNAME = "Fernando Sproviero"; 223 | }; 224 | buildConfigurationList = CBB4A91917A584C400763209 /* Build configuration list for PBXProject "CLLocationUtilsProject" */; 225 | compatibilityVersion = "Xcode 3.2"; 226 | developmentRegion = English; 227 | hasScannedForEncodings = 0; 228 | knownRegions = ( 229 | en, 230 | ); 231 | mainGroup = CBB4A91517A584C400763209; 232 | productRefGroup = CBB4A91F17A584C400763209 /* Products */; 233 | projectDirPath = ""; 234 | projectRoot = ""; 235 | targets = ( 236 | CBB4A91D17A584C400763209 /* CLLocationUtilsProject */, 237 | CBB4A94817A5888F00763209 /* CLLocationUtilsTests */, 238 | ); 239 | }; 240 | /* End PBXProject section */ 241 | 242 | /* Begin PBXResourcesBuildPhase section */ 243 | CBB4A91C17A584C400763209 /* Resources */ = { 244 | isa = PBXResourcesBuildPhase; 245 | buildActionMask = 2147483647; 246 | files = ( 247 | CBB4A92C17A584C400763209 /* InfoPlist.strings in Resources */, 248 | CBB4A93417A584C400763209 /* Default.png in Resources */, 249 | CBB4A93617A584C400763209 /* Default@2x.png in Resources */, 250 | CBB4A93817A584C400763209 /* Default-568h@2x.png in Resources */, 251 | ); 252 | runOnlyForDeploymentPostprocessing = 0; 253 | }; 254 | CBB4A94617A5888F00763209 /* Resources */ = { 255 | isa = PBXResourcesBuildPhase; 256 | buildActionMask = 2147483647; 257 | files = ( 258 | CBB4A95317A5888F00763209 /* InfoPlist.strings in Resources */, 259 | ); 260 | runOnlyForDeploymentPostprocessing = 0; 261 | }; 262 | /* End PBXResourcesBuildPhase section */ 263 | 264 | /* Begin PBXShellScriptBuildPhase section */ 265 | CBB4A94717A5888F00763209 /* ShellScript */ = { 266 | isa = PBXShellScriptBuildPhase; 267 | buildActionMask = 2147483647; 268 | files = ( 269 | ); 270 | inputPaths = ( 271 | ); 272 | outputPaths = ( 273 | ); 274 | runOnlyForDeploymentPostprocessing = 0; 275 | shellPath = /bin/sh; 276 | shellScript = "# Run the unit tests in this test bundle.\n\"${SYSTEM_DEVELOPER_DIR}/Tools/RunUnitTests\"\n"; 277 | }; 278 | /* End PBXShellScriptBuildPhase section */ 279 | 280 | /* Begin PBXSourcesBuildPhase section */ 281 | CBB4A91A17A584C400763209 /* Sources */ = { 282 | isa = PBXSourcesBuildPhase; 283 | buildActionMask = 2147483647; 284 | files = ( 285 | CBB4A92E17A584C400763209 /* main.m in Sources */, 286 | CBB4A93217A584C400763209 /* AppDelegate.m in Sources */, 287 | CBB4A94117A5855D00763209 /* CLLocation+Utils.m in Sources */, 288 | ); 289 | runOnlyForDeploymentPostprocessing = 0; 290 | }; 291 | CBB4A94417A5888F00763209 /* Sources */ = { 292 | isa = PBXSourcesBuildPhase; 293 | buildActionMask = 2147483647; 294 | files = ( 295 | CBB4A96717A590AF00763209 /* CLLocationUtilsTests.m in Sources */, 296 | ); 297 | runOnlyForDeploymentPostprocessing = 0; 298 | }; 299 | /* End PBXSourcesBuildPhase section */ 300 | 301 | /* Begin PBXTargetDependency section */ 302 | CBB4A95F17A58D5D00763209 /* PBXTargetDependency */ = { 303 | isa = PBXTargetDependency; 304 | target = CBB4A91D17A584C400763209 /* CLLocationUtilsProject */; 305 | targetProxy = CBB4A95E17A58D5D00763209 /* PBXContainerItemProxy */; 306 | }; 307 | /* End PBXTargetDependency section */ 308 | 309 | /* Begin PBXVariantGroup section */ 310 | CBB4A92A17A584C400763209 /* InfoPlist.strings */ = { 311 | isa = PBXVariantGroup; 312 | children = ( 313 | CBB4A92B17A584C400763209 /* en */, 314 | ); 315 | name = InfoPlist.strings; 316 | sourceTree = ""; 317 | }; 318 | CBB4A95117A5888F00763209 /* InfoPlist.strings */ = { 319 | isa = PBXVariantGroup; 320 | children = ( 321 | CBB4A95217A5888F00763209 /* en */, 322 | ); 323 | name = InfoPlist.strings; 324 | sourceTree = ""; 325 | }; 326 | /* End PBXVariantGroup section */ 327 | 328 | /* Begin XCBuildConfiguration section */ 329 | CBB4A93917A584C400763209 /* Debug */ = { 330 | isa = XCBuildConfiguration; 331 | buildSettings = { 332 | ALWAYS_SEARCH_USER_PATHS = NO; 333 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 334 | CLANG_CXX_LIBRARY = "libc++"; 335 | CLANG_ENABLE_OBJC_ARC = YES; 336 | CLANG_WARN_CONSTANT_CONVERSION = YES; 337 | CLANG_WARN_EMPTY_BODY = YES; 338 | CLANG_WARN_ENUM_CONVERSION = YES; 339 | CLANG_WARN_INT_CONVERSION = YES; 340 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 341 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 342 | COPY_PHASE_STRIP = NO; 343 | GCC_C_LANGUAGE_STANDARD = gnu99; 344 | GCC_DYNAMIC_NO_PIC = NO; 345 | GCC_OPTIMIZATION_LEVEL = 0; 346 | GCC_PREPROCESSOR_DEFINITIONS = ( 347 | "DEBUG=1", 348 | "$(inherited)", 349 | ); 350 | GCC_SYMBOLS_PRIVATE_EXTERN = NO; 351 | GCC_WARN_ABOUT_RETURN_TYPE = YES; 352 | GCC_WARN_UNINITIALIZED_AUTOS = YES; 353 | GCC_WARN_UNUSED_VARIABLE = YES; 354 | IPHONEOS_DEPLOYMENT_TARGET = 6.1; 355 | ONLY_ACTIVE_ARCH = YES; 356 | SDKROOT = iphoneos; 357 | TARGETED_DEVICE_FAMILY = "1,2"; 358 | }; 359 | name = Debug; 360 | }; 361 | CBB4A93A17A584C400763209 /* Release */ = { 362 | isa = XCBuildConfiguration; 363 | buildSettings = { 364 | ALWAYS_SEARCH_USER_PATHS = NO; 365 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 366 | CLANG_CXX_LIBRARY = "libc++"; 367 | CLANG_ENABLE_OBJC_ARC = YES; 368 | CLANG_WARN_CONSTANT_CONVERSION = YES; 369 | CLANG_WARN_EMPTY_BODY = YES; 370 | CLANG_WARN_ENUM_CONVERSION = YES; 371 | CLANG_WARN_INT_CONVERSION = YES; 372 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 373 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 374 | COPY_PHASE_STRIP = YES; 375 | GCC_C_LANGUAGE_STANDARD = gnu99; 376 | GCC_WARN_ABOUT_RETURN_TYPE = YES; 377 | GCC_WARN_UNINITIALIZED_AUTOS = YES; 378 | GCC_WARN_UNUSED_VARIABLE = YES; 379 | IPHONEOS_DEPLOYMENT_TARGET = 6.1; 380 | OTHER_CFLAGS = "-DNS_BLOCK_ASSERTIONS=1"; 381 | SDKROOT = iphoneos; 382 | TARGETED_DEVICE_FAMILY = "1,2"; 383 | VALIDATE_PRODUCT = YES; 384 | }; 385 | name = Release; 386 | }; 387 | CBB4A93C17A584C400763209 /* Debug */ = { 388 | isa = XCBuildConfiguration; 389 | buildSettings = { 390 | GCC_PRECOMPILE_PREFIX_HEADER = YES; 391 | GCC_PREFIX_HEADER = "CLLocationUtilsProject/CLLocationUtilsProject-Prefix.pch"; 392 | GCC_SYMBOLS_PRIVATE_EXTERN = NO; 393 | INFOPLIST_FILE = "CLLocationUtilsProject/CLLocationUtilsProject-Info.plist"; 394 | PRODUCT_NAME = "$(TARGET_NAME)"; 395 | WRAPPER_EXTENSION = app; 396 | }; 397 | name = Debug; 398 | }; 399 | CBB4A93D17A584C400763209 /* Release */ = { 400 | isa = XCBuildConfiguration; 401 | buildSettings = { 402 | GCC_PRECOMPILE_PREFIX_HEADER = YES; 403 | GCC_PREFIX_HEADER = "CLLocationUtilsProject/CLLocationUtilsProject-Prefix.pch"; 404 | GCC_SYMBOLS_PRIVATE_EXTERN = NO; 405 | INFOPLIST_FILE = "CLLocationUtilsProject/CLLocationUtilsProject-Info.plist"; 406 | PRODUCT_NAME = "$(TARGET_NAME)"; 407 | WRAPPER_EXTENSION = app; 408 | }; 409 | name = Release; 410 | }; 411 | CBB4A95917A5888F00763209 /* Debug */ = { 412 | isa = XCBuildConfiguration; 413 | buildSettings = { 414 | BUNDLE_LOADER = "$(BUILT_PRODUCTS_DIR)/CLLocationUtilsProject.app/CLLocationUtilsProject"; 415 | FRAMEWORK_SEARCH_PATHS = ( 416 | "\"$(SDKROOT)/Developer/Library/Frameworks\"", 417 | "\"$(DEVELOPER_LIBRARY_DIR)/Frameworks\"", 418 | ); 419 | GCC_PRECOMPILE_PREFIX_HEADER = YES; 420 | GCC_PREFIX_HEADER = "CLLocationUtilsTests/CLLocationUtilsTests-Prefix.pch"; 421 | INFOPLIST_FILE = "CLLocationUtilsTests/CLLocationUtilsTests-Info.plist"; 422 | PRODUCT_NAME = "$(TARGET_NAME)"; 423 | TEST_HOST = "$(BUNDLE_LOADER)"; 424 | WRAPPER_EXTENSION = octest; 425 | }; 426 | name = Debug; 427 | }; 428 | CBB4A95A17A5888F00763209 /* Release */ = { 429 | isa = XCBuildConfiguration; 430 | buildSettings = { 431 | BUNDLE_LOADER = "$(BUILT_PRODUCTS_DIR)/CLLocationUtilsProject.app/CLLocationUtilsProject"; 432 | FRAMEWORK_SEARCH_PATHS = ( 433 | "\"$(SDKROOT)/Developer/Library/Frameworks\"", 434 | "\"$(DEVELOPER_LIBRARY_DIR)/Frameworks\"", 435 | ); 436 | GCC_PRECOMPILE_PREFIX_HEADER = YES; 437 | GCC_PREFIX_HEADER = "CLLocationUtilsTests/CLLocationUtilsTests-Prefix.pch"; 438 | INFOPLIST_FILE = "CLLocationUtilsTests/CLLocationUtilsTests-Info.plist"; 439 | PRODUCT_NAME = "$(TARGET_NAME)"; 440 | TEST_HOST = "$(BUNDLE_LOADER)"; 441 | WRAPPER_EXTENSION = octest; 442 | }; 443 | name = Release; 444 | }; 445 | /* End XCBuildConfiguration section */ 446 | 447 | /* Begin XCConfigurationList section */ 448 | CBB4A91917A584C400763209 /* Build configuration list for PBXProject "CLLocationUtilsProject" */ = { 449 | isa = XCConfigurationList; 450 | buildConfigurations = ( 451 | CBB4A93917A584C400763209 /* Debug */, 452 | CBB4A93A17A584C400763209 /* Release */, 453 | ); 454 | defaultConfigurationIsVisible = 0; 455 | defaultConfigurationName = Release; 456 | }; 457 | CBB4A93B17A584C400763209 /* Build configuration list for PBXNativeTarget "CLLocationUtilsProject" */ = { 458 | isa = XCConfigurationList; 459 | buildConfigurations = ( 460 | CBB4A93C17A584C400763209 /* Debug */, 461 | CBB4A93D17A584C400763209 /* Release */, 462 | ); 463 | defaultConfigurationIsVisible = 0; 464 | defaultConfigurationName = Release; 465 | }; 466 | CBB4A95817A5888F00763209 /* Build configuration list for PBXNativeTarget "CLLocationUtilsTests" */ = { 467 | isa = XCConfigurationList; 468 | buildConfigurations = ( 469 | CBB4A95917A5888F00763209 /* Debug */, 470 | CBB4A95A17A5888F00763209 /* Release */, 471 | ); 472 | defaultConfigurationIsVisible = 0; 473 | defaultConfigurationName = Release; 474 | }; 475 | /* End XCConfigurationList section */ 476 | }; 477 | rootObject = CBB4A91617A584C400763209 /* Project object */; 478 | } 479 | -------------------------------------------------------------------------------- /CLLocationUtilsProject.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /CLLocationUtilsProject/AppDelegate.h: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.h 3 | // CLLocationUtilsProject 4 | // 5 | // Created by Fernando Sproviero on 28/07/13. 6 | // Copyright (c) 2013 Fernando Sproviero. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface AppDelegate : UIResponder 12 | 13 | @property (strong, nonatomic) UIWindow *window; 14 | 15 | @end 16 | -------------------------------------------------------------------------------- /CLLocationUtilsProject/AppDelegate.m: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.m 3 | // CLLocationUtilsProject 4 | // 5 | // Created by Fernando Sproviero on 28/07/13. 6 | // Copyright (c) 2013 Fernando Sproviero. All rights reserved. 7 | // 8 | 9 | #import "AppDelegate.h" 10 | 11 | @implementation AppDelegate 12 | 13 | - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions 14 | { 15 | self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]]; 16 | // Override point for customization after application launch. 17 | self.window.backgroundColor = [UIColor whiteColor]; 18 | [self.window makeKeyAndVisible]; 19 | return YES; 20 | } 21 | 22 | - (void)applicationWillResignActive:(UIApplication *)application 23 | { 24 | // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state. 25 | // Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game. 26 | } 27 | 28 | - (void)applicationDidEnterBackground:(UIApplication *)application 29 | { 30 | // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later. 31 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. 32 | } 33 | 34 | - (void)applicationWillEnterForeground:(UIApplication *)application 35 | { 36 | // Called as part of the transition from the background to the inactive state; here you can undo many of the changes made on entering the background. 37 | } 38 | 39 | - (void)applicationDidBecomeActive:(UIApplication *)application 40 | { 41 | // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface. 42 | } 43 | 44 | - (void)applicationWillTerminate:(UIApplication *)application 45 | { 46 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. 47 | } 48 | 49 | @end 50 | -------------------------------------------------------------------------------- /CLLocationUtilsProject/CLLocationUtilsProject-Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleDisplayName 8 | ${PRODUCT_NAME} 9 | CFBundleExecutable 10 | ${EXECUTABLE_NAME} 11 | CFBundleIdentifier 12 | com.fls.${PRODUCT_NAME:rfc1034identifier} 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | ${PRODUCT_NAME} 17 | CFBundlePackageType 18 | APPL 19 | CFBundleShortVersionString 20 | 1.0 21 | CFBundleSignature 22 | ???? 23 | CFBundleVersion 24 | 1.0 25 | LSRequiresIPhoneOS 26 | 27 | UIRequiredDeviceCapabilities 28 | 29 | armv7 30 | 31 | UISupportedInterfaceOrientations 32 | 33 | UIInterfaceOrientationPortrait 34 | UIInterfaceOrientationLandscapeLeft 35 | UIInterfaceOrientationLandscapeRight 36 | 37 | UISupportedInterfaceOrientations~ipad 38 | 39 | UIInterfaceOrientationPortrait 40 | UIInterfaceOrientationPortraitUpsideDown 41 | UIInterfaceOrientationLandscapeLeft 42 | UIInterfaceOrientationLandscapeRight 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /CLLocationUtilsProject/CLLocationUtilsProject-Prefix.pch: -------------------------------------------------------------------------------- 1 | // 2 | // Prefix header for all source files of the 'CLLocationUtilsProject' target in the 'CLLocationUtilsProject' project 3 | // 4 | 5 | #import 6 | 7 | #ifndef __IPHONE_3_0 8 | #warning "This project uses features only available in iOS SDK 3.0 and later." 9 | #endif 10 | 11 | #ifdef __OBJC__ 12 | #import 13 | #import 14 | #endif 15 | -------------------------------------------------------------------------------- /CLLocationUtilsProject/Default-568h@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fernandospr/CoreLocationUtils/21468d9bb72ae62d6ddd995144800dd4cac15772/CLLocationUtilsProject/Default-568h@2x.png -------------------------------------------------------------------------------- /CLLocationUtilsProject/Default.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fernandospr/CoreLocationUtils/21468d9bb72ae62d6ddd995144800dd4cac15772/CLLocationUtilsProject/Default.png -------------------------------------------------------------------------------- /CLLocationUtilsProject/Default@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fernandospr/CoreLocationUtils/21468d9bb72ae62d6ddd995144800dd4cac15772/CLLocationUtilsProject/Default@2x.png -------------------------------------------------------------------------------- /CLLocationUtilsProject/en.lproj/InfoPlist.strings: -------------------------------------------------------------------------------- 1 | /* Localized versions of Info.plist keys */ 2 | 3 | -------------------------------------------------------------------------------- /CLLocationUtilsProject/main.m: -------------------------------------------------------------------------------- 1 | // 2 | // main.m 3 | // CLLocationUtilsProject 4 | // 5 | // Created by Fernando Sproviero on 28/07/13. 6 | // Copyright (c) 2013 Fernando Sproviero. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | #import "AppDelegate.h" 12 | 13 | int main(int argc, char *argv[]) 14 | { 15 | @autoreleasepool { 16 | return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /CLLocationUtilsTests/CLLocationUtilsTests-Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | ${EXECUTABLE_NAME} 9 | CFBundleIdentifier 10 | com.fls.${PRODUCT_NAME:rfc1034identifier} 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundlePackageType 14 | BNDL 15 | CFBundleShortVersionString 16 | 1.0 17 | CFBundleSignature 18 | ???? 19 | CFBundleVersion 20 | 1 21 | 22 | 23 | -------------------------------------------------------------------------------- /CLLocationUtilsTests/CLLocationUtilsTests-Prefix.pch: -------------------------------------------------------------------------------- 1 | // 2 | // Prefix header for all source files of the 'CLLocationUtilsTests' target in the 'CLLocationUtilsTests' project 3 | // 4 | 5 | #ifdef __OBJC__ 6 | #import 7 | #import 8 | #endif 9 | -------------------------------------------------------------------------------- /CLLocationUtilsTests/CLLocationUtilsTests.h: -------------------------------------------------------------------------------- 1 | // 2 | // CLLocationUtilsTests.h 3 | // CLLocationUtilsTests 4 | // 5 | // Created by Fernando Sproviero on 10/07/13. 6 | // Copyright (c) 2013 Fernando Sproviero. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface CLLocationUtilsTests : SenTestCase 12 | 13 | @end 14 | -------------------------------------------------------------------------------- /CLLocationUtilsTests/CLLocationUtilsTests.m: -------------------------------------------------------------------------------- 1 | // 2 | // CLLocationUtilsTests.m 3 | // CLLocationUtilsTests 4 | // 5 | // Created by Fernando Sproviero on 10/07/13. 6 | // Copyright (c) 2013 Fernando Sproviero. All rights reserved. 7 | // 8 | 9 | #import "CLLocationUtilsTests.h" 10 | #import "CLLocation+Utils.h" 11 | 12 | @implementation CLLocationUtilsTests 13 | 14 | - (void)setUp 15 | { 16 | [super setUp]; 17 | 18 | // Set-up code here. 19 | } 20 | 21 | - (void)tearDown 22 | { 23 | // Tear-down code here. 24 | 25 | [super tearDown]; 26 | } 27 | 28 | - (void)testPrettyCoordinates 29 | { 30 | CLLocation *location1 = [[CLLocation alloc] initWithLatitude:-34.603611 longitude:-58.381667]; 31 | NSString *prettyLat = [location1 prettyLatitude]; 32 | NSString *prettyLon = [location1 prettyLongitude]; 33 | STAssertTrue([prettyLat isEqualToString:@"34° 36' 13\" S"], @"Pretty coordinates do not match"); 34 | STAssertTrue([prettyLon isEqualToString:@"58° 22' 54\" W"], @"Pretty coordinates do not match"); 35 | } 36 | 37 | - (void)testHaversineZeroDistance 38 | { 39 | CLLocation *location1 = [[CLLocation alloc] initWithLatitude:40.0 longitude:-73.0]; 40 | CLLocationDistance distance = [location1 haversineDistanceFromLocation:location1]; 41 | STAssertEquals(0.0, distance, @"Distance should be zero"); 42 | } 43 | 44 | - (void)testHaversineHugeDistance 45 | { 46 | CLLocation *location1 = [[CLLocation alloc] initWithLatitude:40.0 longitude:-73.0]; 47 | CLLocation *location2 = [[CLLocation alloc] initWithLatitude:-10.0 longitude:20.0]; 48 | CLLocationDistance distance = ceil([location1 haversineDistanceFromLocation:location2] / 1000); 49 | STAssertEquals(10974.0, distance, @"Distance do not match"); 50 | } 51 | 52 | - (void)testSphericalLawOfCosZeroDistance 53 | { 54 | CLLocation *location1 = [[CLLocation alloc] initWithLatitude:40.0 longitude:-73.0]; 55 | CLLocationDistance distance = [location1 sphericalLawOfCosDistanceFromLocation:location1]; 56 | STAssertEquals(0.0, distance, @"Distance should be zero"); 57 | } 58 | 59 | - (void)testSphericalLawOfCosHugeDistance 60 | { 61 | CLLocation *location1 = [[CLLocation alloc] initWithLatitude:40.0 longitude:-73.0]; 62 | CLLocation *location2 = [[CLLocation alloc] initWithLatitude:-10.0 longitude:20.0]; 63 | CLLocationDistance distance = ceil([location1 sphericalLawOfCosDistanceFromLocation:location2] / 1000); 64 | STAssertEquals(10974.0, distance, @"Distance do not match"); 65 | } 66 | 67 | - (void)testPythagorasEquirectangularZeroDistance 68 | { 69 | CLLocation *location1 = [[CLLocation alloc] initWithLatitude:40.0 longitude:-73.0]; 70 | CLLocationDistance distance = [location1 pythagorasEquirectangularDistanceFromLocation:location1]; 71 | STAssertEquals(0.0, distance, @"Distance should be zero"); 72 | } 73 | 74 | - (void)testPythagorasEquirectangularSmallDistance 75 | { 76 | CLLocation *location1 = [[CLLocation alloc] initWithLatitude:51.0 longitude:0.0]; 77 | CLLocation *location2 = [[CLLocation alloc] initWithLatitude:51.01 longitude:0.0]; 78 | CLLocationDistance distance = ceil([location1 pythagorasEquirectangularDistanceFromLocation:location2]); 79 | STAssertEquals(1112.0, distance, @"Distance do not match"); 80 | } 81 | 82 | - (void)testPythagorasSmallDistance 83 | { 84 | CLLocation *location1 = [[CLLocation alloc] initWithLatitude:-10.0 longitude:-10.0]; 85 | CLLocation *location2 = [[CLLocation alloc] initWithLatitude:40.0 longitude:40.0]; 86 | double distance = [location1 pythagorasDistanceFromLocation:location2]; 87 | STAssertEqualsWithAccuracy(70.71068, distance, 0.1, @"Distance do not match"); 88 | } 89 | 90 | - (void)testMidPoint 91 | { 92 | CLLocation *location1 = [[CLLocation alloc] initWithLatitude:50.0 longitude:0.0]; 93 | CLLocation *location2 = [[CLLocation alloc] initWithLatitude:-50.0 longitude:0.0]; 94 | CLLocation *location3 = [location1 midpointWithLocation:location2]; 95 | STAssertEquals(0.0, location3.coordinate.latitude, @"Latitude should be zero"); 96 | STAssertEquals(0.0, location3.coordinate.longitude, @"Longitude should be zero"); 97 | } 98 | 99 | - (void)testAngleZero 100 | { 101 | CLLocation *location1 = [[CLLocation alloc] initWithLatitude:50.0 longitude:0.0]; 102 | CLLocation *location2 = [[CLLocation alloc] initWithLatitude:-50.0 longitude:0.0]; 103 | CLLocationDegrees angle = [location2 angleWithLocation:location1]; 104 | STAssertEquals(0.0, angle, @"Angle should be zero"); 105 | } 106 | 107 | - (void)testAngle45 108 | { 109 | CLLocation *location1 = [[CLLocation alloc] initWithLatitude:0.0 longitude:-50.0]; 110 | CLLocation *location2 = [[CLLocation alloc] initWithLatitude:50.0 longitude:0.0]; 111 | CLLocationDegrees angle = [location1 angleWithLocation:location2]; 112 | STAssertEquals(45.0, angle, @"Angle should be 45"); 113 | } 114 | 115 | - (void)testAngle90 116 | { 117 | CLLocation *location1 = [[CLLocation alloc] initWithLatitude:0.0 longitude:-50.0]; 118 | CLLocation *location2 = [[CLLocation alloc] initWithLatitude:0.0 longitude:50.0]; 119 | CLLocationDegrees angle = [location1 angleWithLocation:location2]; 120 | STAssertEquals(90.0, angle, @"Angle should be 90"); 121 | } 122 | 123 | - (void)testAngle135 124 | { 125 | CLLocation *location1 = [[CLLocation alloc] initWithLatitude:0.0 longitude:0.0]; 126 | CLLocation *location2 = [[CLLocation alloc] initWithLatitude:-50.0 longitude:50.0]; 127 | CLLocationDegrees angle = [location1 angleWithLocation:location2]; 128 | STAssertEquals(135.0, angle, @"Angle should be 135"); 129 | } 130 | 131 | - (void)testAngle180 132 | { 133 | CLLocation *location1 = [[CLLocation alloc] initWithLatitude:50.0 longitude:0.0]; 134 | CLLocation *location2 = [[CLLocation alloc] initWithLatitude:-50.0 longitude:0.0]; 135 | CLLocationDegrees angle = [location1 angleWithLocation:location2]; 136 | STAssertEquals(180.0, angle, @"Angle should be 180"); 137 | } 138 | 139 | - (void)testAngle225 140 | { 141 | CLLocation *location1 = [[CLLocation alloc] initWithLatitude:0.0 longitude:-50.0]; 142 | CLLocation *location2 = [[CLLocation alloc] initWithLatitude:50.0 longitude:0.0]; 143 | CLLocationDegrees angle = [location2 angleWithLocation:location1]; 144 | STAssertEquals(225.0, angle, @"Angle should be 225"); 145 | } 146 | 147 | - (void)testAngle270 148 | { 149 | CLLocation *location1 = [[CLLocation alloc] initWithLatitude:0.0 longitude:0.0]; 150 | CLLocation *location2 = [[CLLocation alloc] initWithLatitude:0.0 longitude:-50.0]; 151 | CLLocationDegrees angle = [location1 angleWithLocation:location2]; 152 | STAssertEquals(270.0, angle, @"Angle should be 270"); 153 | } 154 | 155 | - (void)testAngle315 156 | { 157 | CLLocation *location1 = [[CLLocation alloc] initWithLatitude:0.0 longitude:0.0]; 158 | CLLocation *location2 = [[CLLocation alloc] initWithLatitude:50.0 longitude:-50.0]; 159 | CLLocationDegrees angle = [location1 angleWithLocation:location2]; 160 | STAssertEquals(315.0, angle, @"Angle should be 315"); 161 | } 162 | 163 | - (void)testAngleBetweenNegativeAndPositive 164 | { 165 | STAssertTrue([CLLocation angle:0.0 betweenAngle:-45.0 andAngle:45.0], @"Should return true"); 166 | STAssertTrue([CLLocation angle:0.0 betweenAngle:315.0 andAngle:45.0], @"Should return true"); 167 | } 168 | 169 | - (void)testAngleBetweenZeroAndPositive 170 | { 171 | STAssertTrue([CLLocation angle:45.0 betweenAngle:0.0 andAngle:90.0], @"Should return true"); 172 | } 173 | 174 | - (void)testAngleBetweenPositiveAndPositive 175 | { 176 | STAssertTrue([CLLocation angle:90.0 betweenAngle:45.0 andAngle:135.0], @"Should return true"); 177 | } 178 | 179 | - (void)testAngleBetweenPositiveAndNegative 180 | { 181 | STAssertTrue([CLLocation angle:180.0 betweenAngle:90.0 andAngle:-90.0], @"Should return true"); 182 | STAssertTrue([CLLocation angle:180.0 betweenAngle:90.0 andAngle:270.0], @"Should return true"); 183 | } 184 | 185 | - (void)testAngleBetweenNegativeAndNegative 186 | { 187 | STAssertTrue([CLLocation angle:225.0 betweenAngle:180.0 andAngle:-90.0], @"Should return true"); 188 | STAssertTrue([CLLocation angle:225.0 betweenAngle:180.0 andAngle:270.0], @"Should return true"); 189 | STAssertTrue([CLLocation angle:-135.0 betweenAngle:180.0 andAngle:-90.0], @"Should return true"); 190 | STAssertTrue([CLLocation angle:-135.0 betweenAngle:180.0 andAngle:270.0], @"Should return true"); 191 | } 192 | 193 | - (void)testAngleWithDecimalsBetweenZeroAndPositive 194 | { 195 | STAssertTrue([CLLocation angle:40.2 betweenAngle:40.1 andAngle:40.3], @"Should return true"); 196 | } 197 | 198 | - (void)testAngleNotBetweenZeroAndPositive 199 | { 200 | STAssertFalse([CLLocation angle:90.1 betweenAngle:0.0 andAngle:90.0], @"Should return true"); 201 | } 202 | 203 | - (void)testInitialBearing 204 | { 205 | CLLocation *location1 = [[CLLocation alloc] initWithLatitude:35.0 longitude:45.0]; 206 | CLLocation *location2 = [[CLLocation alloc] initWithLatitude:35.0 longitude:135.0]; 207 | CLLocationDegrees angle = [location1 initialBearingToLocation:location2]; 208 | STAssertEqualsWithAccuracy(60.0, angle, 1.0, @"Angle should be 60"); 209 | } 210 | 211 | - (void)testFinalBearing 212 | { 213 | CLLocation *location1 = [[CLLocation alloc] initWithLatitude:35.0 longitude:45.0]; 214 | CLLocation *location2 = [[CLLocation alloc] initWithLatitude:35.0 longitude:135.0]; 215 | CLLocationDegrees angle = [location1 finalBearingToLocation:location2]; 216 | STAssertEqualsWithAccuracy(120.0, angle, 1.0, @"Angle should be 60"); 217 | } 218 | 219 | - (void)testDestinationLocation 220 | { 221 | CLLocation *location1 = [[CLLocation alloc] initWithLatitude:50.0 longitude:50.0]; 222 | CLLocation *destinationLocation = [location1 destinationLocationWithInitialBearing:45.0 distance:100000]; 223 | STAssertTrue([destinationLocation.prettyLatitude isEqualToString:@"50° 37' 54\" N"], @"Wrong latitude"); 224 | STAssertTrue([destinationLocation.prettyLongitude isEqualToString:@"51° 00' 09\" E"], @"Wrong longitude"); 225 | } 226 | 227 | - (void)testPythagorasDestinationLocation 228 | { 229 | CLLocation *location1 = [[CLLocation alloc] initWithLatitude:0.0 longitude:0.0]; 230 | CLLocation *destinationLocation = [location1 pythagorasDestinationLocationWithInitialBearing:63.435 pythagorasDistance:201.246118]; 231 | STAssertEqualsWithAccuracy(destinationLocation.coordinate.latitude, 90.0, 1.0, @"Wrong latitude"); 232 | STAssertEqualsWithAccuracy(destinationLocation.coordinate.longitude, 180.0, 1.0, @"Wrong longitude"); 233 | } 234 | 235 | - (void)testPythagorasDestinationLocation45 236 | { 237 | CLLocation *location1 = [[CLLocation alloc] initWithLatitude:0.0 longitude:0.0]; 238 | CLLocation *destinationLocation = [location1 pythagorasDestinationLocationWithInitialBearing:45.0 pythagorasDistance:100.0]; 239 | STAssertEqualsWithAccuracy(destinationLocation.coordinate.latitude, 70.710678, 1.0, @"Wrong latitude"); 240 | STAssertEqualsWithAccuracy(destinationLocation.coordinate.longitude, 70.710678, 1.0, @"Wrong longitude"); 241 | } 242 | 243 | - (void)testPythagorasDestinationLocation90 244 | { 245 | CLLocation *location1 = [[CLLocation alloc] initWithLatitude:0.0 longitude:0.0]; 246 | CLLocation *destinationLocation = [location1 pythagorasDestinationLocationWithInitialBearing:90.0 pythagorasDistance:100.0]; 247 | STAssertEqualsWithAccuracy(destinationLocation.coordinate.latitude, 0.0, 1.0, @"Wrong latitude"); 248 | STAssertEqualsWithAccuracy(destinationLocation.coordinate.longitude, 100.0, 1.0, @"Wrong longitude"); 249 | } 250 | 251 | - (void)testPythagorasDestinationLocation0Negative 252 | { 253 | CLLocation *location1 = [[CLLocation alloc] initWithLatitude:-10.0 longitude:0.0]; 254 | CLLocation *destinationLocation = [location1 pythagorasDestinationLocationWithInitialBearing:0.0 pythagorasDistance:100.0]; 255 | STAssertEqualsWithAccuracy(destinationLocation.coordinate.latitude, 90.0, 1.0, @"Wrong latitude"); 256 | STAssertEqualsWithAccuracy(destinationLocation.coordinate.longitude, 0.0, 1.0, @"Wrong longitude"); 257 | } 258 | 259 | - (void)testPythagorasDestinationLocation45Negative 260 | { 261 | CLLocation *location1 = [[CLLocation alloc] initWithLatitude:-20.0 longitude:-10.0]; 262 | CLLocation *destinationLocation = [location1 pythagorasDestinationLocationWithInitialBearing:45.0 pythagorasDistance:28.284271]; 263 | STAssertEqualsWithAccuracy(destinationLocation.coordinate.latitude, 0.0, 1.0, @"Wrong latitude"); 264 | STAssertEqualsWithAccuracy(destinationLocation.coordinate.longitude, 10.0, 1.0, @"Wrong longitude"); 265 | } 266 | 267 | - (void)testConstructorWithPrettyCoordinates 268 | { 269 | NSString *lat = @"38° 53' 23\" N"; 270 | NSString *lon = @"77° 00' 32\" W"; 271 | CLLocation *location1 = [[CLLocation alloc] initWithPrettyLatitude:lat prettyLongitude:lon]; 272 | STAssertEqualsWithAccuracy(location1.coordinate.latitude, 38.889722, 0.000001, @"Coordinates do not match"); 273 | STAssertEqualsWithAccuracy(location1.coordinate.longitude, -77.008889, 0.000001, @"Coordinates do not match"); 274 | } 275 | 276 | - (void)testIntersection 277 | { 278 | CLLocation *location1 = [[CLLocation alloc] initWithLatitude:50.0 longitude:50.0]; 279 | CLLocation *location2 = [[CLLocation alloc] initWithLatitude:50.0 longitude:0.0]; 280 | double brng1 = 45.0; 281 | double brng2 = 10.0; 282 | 283 | CLLocation *intersection = [location1 intersectionWithSelfBearing:brng1 toLocation:location2 bearing:brng2]; 284 | STAssertTrue([intersection.prettyLatitude isEqualToString:@"40° 22' 51\" N"], @"Coordinates do not match"); 285 | STAssertTrue([intersection.prettyLongitude isEqualToString:@"166° 49' 32\" E"], @"Coordinates do not match"); 286 | } 287 | 288 | - (void)testRhumbDistance 289 | { 290 | CLLocation *location1 = [[CLLocation alloc] initWithPrettyLatitude:@"50° 21' 50\" N" prettyLongitude:@"04° 09' 25\" W"]; 291 | CLLocation *location2 = [[CLLocation alloc] initWithPrettyLatitude:@"42° 21' 04\" N" prettyLongitude:@"71° 02' 27\" W"]; 292 | int distance = round([location1 rhumbDistanceFromLocation:location2] / 1000); 293 | STAssertEquals(distance, 5196, @"Wrong rhumb distance"); 294 | } 295 | 296 | - (void)testRhumbBearing 297 | { 298 | CLLocation *location1 = [[CLLocation alloc] initWithPrettyLatitude:@"50° 21' 50\" N" prettyLongitude:@"04° 09' 25\" W"]; 299 | CLLocation *location2 = [[CLLocation alloc] initWithPrettyLatitude:@"42° 21' 04\" N" prettyLongitude:@"71° 02' 27\" W"]; 300 | int brng = round([location1 rhumbBearingToLocation:location2]); 301 | STAssertEquals(brng, 260, @"Wrong bearing distance"); 302 | } 303 | 304 | - (void)testRhumbMidpoint 305 | { 306 | CLLocation *location1 = [[CLLocation alloc] initWithPrettyLatitude:@"50° 21' 50\" N" prettyLongitude:@"04° 09' 25\" W"]; 307 | CLLocation *location2 = [[CLLocation alloc] initWithPrettyLatitude:@"42° 21' 04\" N" prettyLongitude:@"71° 02' 27\" W"]; 308 | CLLocation *destinationLocation = [location1 rhumbMidpointWithLocation:location2]; 309 | STAssertTrue([destinationLocation.prettyLatitude isEqualToString:@"46° 21' 27\" N"], @"Coordinates do not match"); 310 | STAssertTrue([destinationLocation.prettyLongitude isEqualToString:@"38° 49' 39\" W"], @"Coordinates do not match"); 311 | 312 | } 313 | 314 | - (void)testRhumbDestinationLocation 315 | { 316 | CLLocation *location1 = [[CLLocation alloc] initWithLatitude:50.0 longitude:10.0]; 317 | CLLocation *destinationLocation = [location1 rhumbDestinationLocationWithBearing:116.0 distance:40000]; 318 | STAssertTrue([destinationLocation.prettyLatitude isEqualToString:@"49° 50' 32\" N"], @"Coordinates do not match"); 319 | STAssertTrue([destinationLocation.prettyLongitude isEqualToString:@"10° 30' 08\" E"], @"Coordinates do not match"); 320 | } 321 | 322 | @end 323 | -------------------------------------------------------------------------------- /CLLocationUtilsTests/en.lproj/InfoPlist.strings: -------------------------------------------------------------------------------- 1 | /* Localized versions of Info.plist keys */ 2 | 3 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2013 fernandospr 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Click here to lend your support to: Fernando's Open Source Projects and make a donation at pledgie.com ! 2 | 3 | 4 | CoreLocationUtils 5 | ================= 6 | 7 | A category with convenient methods for CLLocation, based on **http://www.movable-type.co.uk/scripts/latlong.html** 8 | 9 | ## Features 10 | 11 | * Convenient CLLocation initializers: 12 | * initialize with latitude and longitude in radians 13 | * initialize with a pretty string format (e.g. latitude = @"34° 36' 12\" N" longitude = @"35° 12' 24\" W") 14 | * Obtain coordinates in a pretty format 15 | * Distance between coordinates using: 16 | * Haversine formula 17 | * Spherical Law of cosines formula 18 | * Pythagoras formula 19 | * Rhumb line formula 20 | * Midpoint along a great circle path between coordinates 21 | * Initial/final bearings to target location from source location 22 | * Calculate target location with a given distance and bearing from source location 23 | 24 | ## Usage 25 | 26 | Copy CLLocationUtils to your project, include CLLocation+Utils.h and start using! 27 | 28 | Please check CLLocationUtilsTests for examples. 29 | --------------------------------------------------------------------------------