// // Calculations.m // Compass Screen // // Created by Jeremy on 7/4/08. // Copyright 2008 Stone Software. All rights reserved. // #import "Calculations.h" #import "LocatorModel.h" @implementation Calculations // Convert our passed value (in degrees) to radians // + (double) toRadian: (double) val { return val * (M_PI / 180); } - (double) toRadian: (double) val { return [Calculations toRadian:val]; } // Convert to degrees from radians + (double) toDegrees: (double) val { return val * 180 / M_PI; } - (double) toDegrees: (double) val { return [Calculations toDegrees:val]; } // convert from a radian to a 360 degree format. + (double) toBearing: (double) val { return ( (int)([self toDegrees: val]) + 360 ) % 360; // use mod to get the degrees } - (double) toBearing: (double) val { return [Calculations toBearing:val]; } // Do the math! // For haversine calculations see http://www.movable-type.co.uk/scripts/latlong.html and http://jaimerios.com/?p=39 // // calc the haversine formula // --------------------------------------------- // double sourceLat = locationSourceLatitude; // double sourceLon = locationSourceLongitude; // double destLat = locationDestinationLatitude; // double destLon = locationDestinationLongitude; // // // Get the difference between our two points then convert the difference into radians // double differenceLat = [self toRadian: (destLat - sourceLat)]; // double differenceLon = [self toRadian: (destLon - sourceLon)]; // // sourceLat = [self toRadian: sourceLat]; // destLat = [self toRadian: destLat]; // // double firstCalc = pow ( sin(differenceLat / 2), 2 ) + cos(sourceLat) * cos(destLat) * pow ( sin(differenceLon / 2), 2 ); // // double nextCalc = 2 * atan2( sqrt(firstCalc), sqrt( 1 - firstCalc )); // distanceBetweenLocations = kEarthRadius_km * nextCalc; // + (CLLocationDistance) calcDistanceFromLat:(CLLocationDegrees)firstLat lon:(CLLocationDegrees)firstLon toLat:(CLLocationDegrees)secondLat lon:(CLLocationDegrees)secondLon { CLLocation* source = [[CLLocation alloc] initWithLatitude:firstLat longitude:firstLon]; CLLocation* dest = [[CLLocation alloc] initWithLatitude:secondLat longitude:secondLon]; double retVal = [source distanceFromLocation:dest]; [source release]; [dest release]; return retVal; } - (void) doDistanceCalculations { distanceBetweenLocations = [[self class] calcDistanceFromLat:locationSourceLatitude lon:locationSourceLongitude toLat:locationDestinationLatitude lon:locationDestinationLongitude]; } - (id) init { self = [super init]; if (self != nil) { distanceBetweenLocations = 0.0; locationSourceLatitude = 0.0; locationSourceLongitude = 0.0; locationDestinationLatitude = 0.0; locationDestinationLongitude = 0.0; } return self; } - (CLLocationDistance) distanceBetweenPoints { return distanceBetweenLocations; } - (void) initSourceCoords:(CLLocationDegrees)lat longitude:(CLLocationDegrees)lon { locationSourceLatitude = lat; locationSourceLongitude = lon; if (locationDestinationLatitude != 0) [self doDistanceCalculations]; } - (void) initDestinationCoords:(CLLocationDegrees)lat longitude:(CLLocationDegrees)lon { locationDestinationLatitude = lat; locationDestinationLongitude = lon; if (locationSourceLatitude != 0) [self doDistanceCalculations]; } // Calculate the bearing between both points. Returns the new bearing in degrees. // - (double) calcBearing { return [self calcBearingWithLatitude:locationSourceLatitude latitude:locationDestinationLatitude longitude:locationSourceLongitude longitude:locationDestinationLongitude]; } // Calculate the bearing based off of the passed coordinates and destination. Does not update // the bearing calculation. Used for current bearing information. // + (double) calcBearingWithLatitude:(CLLocationDegrees)latSource latitude:(CLLocationDegrees)latDest longitude:(CLLocationDegrees)lonSrc longitude:(CLLocationDegrees)lonDest { double lat1 = [self toRadian:latSource]; double lat2 = [self toRadian:latDest]; double dLon = [self toRadian:(lonDest - lonSrc)]; double y = sin(dLon) * cos(lat2); double x = cos(lat1) * sin(lat2) - sin(lat1) * cos(lat2) * cos(dLon); return [self toBearing:atan2(y, x)]; } - (double) calcBearingWithLatitude:(CLLocationDegrees)latSource latitude:(CLLocationDegrees)latDest longitude:(CLLocationDegrees)lonSrc longitude:(CLLocationDegrees)lonDest { return [Calculations calcBearingWithLatitude:latSource latitude:latDest longitude:lonSrc longitude:lonDest]; } + (NSString*) bearingDirectionFromLatitude:(CLLocationDegrees)latSource latitude:(CLLocationDegrees)latDest longitude:(CLLocationDegrees)lonSrc longitude:(CLLocationDegrees)lonDest { double bearing = [self calcBearingWithLatitude:latSource latitude:latDest longitude:lonSrc longitude:lonDest]; bearing -= 45/2; // shift by half of a spectrum to make me think less. if (bearing < 0) bearing = 0; NSString* retVal = @""; // boundaries fall every 45 degrees: 0, 45, 90, 135, 180, 225, 270, 315 if (bearing < 45) retVal = @"N"; else if (bearing < 90) retVal = @"NE"; else if (bearing < 135) retVal = @"E"; else if (bearing < 180) retVal = @"SE"; else if (bearing < 225) retVal = @"S"; else if (bearing < 270) retVal = @"SW"; else if (bearing < 315) retVal = @"W"; else retVal = @"NW"; return retVal; } // got this code from some forums and modified it, thanks for posting it coullis! Mostly here for reference on how to do this without sin and cos. - (CLLocationDegrees) altCalcBearingWithLatitude:(CLLocationDegrees)latSource latitude:(CLLocationDegrees)latDest longitude:(CLLocationDegrees)lonSrc longitude:(CLLocationDegrees)lonDest { CLLocationDegrees result; // First You calculate Delta distances. float dx = lonSrc - latSource; float dy = lonDest - latDest; // If x part is 0 we could get into division by zero problems, but in that case result can only be 90 or 270: if (dx==0) { if (dy > 0) result = 90; else result = 270; } else { result = [Calculations toDegrees: atan(dy/dx)]; } // This is only valid for two quadrants (for right side of the coordinate system) so modify result if necessary... if (dx < 0) result = result + 180; // looks better if all numbers are positive (0 to 360 range) if (result < 0) result = result + 360; // return our result. return result; } // Create an autoreleased NSString from a double. // + (NSString*) distToDisplayInDecimal:(double) distance { NSString* retVal = nil; NSInteger num = [[NSUserDefaults standardUserDefaults] integerForKey:kMeasurementKey]; if (num == 1) { if (distance > kMetersInKilometers) { // convert to km retVal = [NSString stringWithFormat:@"%.2f %@", distance / kMetersInKilometers, kKilometerLabel]; } else { // display in meters retVal = [NSString stringWithFormat:@"%.1f %@", distance, kMeterLabel]; } } else { CLLocationAccuracy distInFeet = distance * kFeetToMetersRatio; if (distInFeet > kFeetInMile) { retVal = [NSString stringWithFormat:@"%.2f %@", distInFeet / kFeetInMile, kMileLabel]; } else { retVal = [NSString stringWithFormat:@"%.1f %@", distInFeet, kFeetLabel]; } } return retVal; } + (NSString*) degreeToDirection: (double) deg latitude:(BOOL) lat { NSString* retVal = @"N"; if (lat && deg < 0.0) { retVal = @"S"; } else if (lat == NO) { if (deg < 0) retVal = @"W"; else retVal = @"E"; } return retVal; } // returns NO if S or W is passed, returns YES otherwise. + (BOOL) directionIsPositiveDegree:(NSString*) val { // hax! Or at least, C stuff instead of obj-C. char temp[512]; if (NO == [val getCString:temp maxLength:512 encoding:NSMacOSRomanStringEncoding]) { return YES; } toupper(temp[0]); if ('S' == temp[0] || 'W' == temp[0]) return NO; else return YES; } // isLatitude determines if the string is formatted with N/S or E/W if it's... latitude or longitude respecively. + (NSString*) degreesToHourMinuteDecimal: (double) val isLatitude:(BOOL) lat { // decimal = (decimal of degrees only) * 60; double decm = fabs((val - trunc(val)) * 60); return [NSString stringWithFormat:@"%@ %d %.3f", [Calculations degreeToDirection:val latitude:lat], abs((int)val), decm]; } @end