// // LocatorModel.m // Compass Screen // // Created by Jeremy on 7/3/08. // Copyright 2008 Stone Software. All rights reserved. // #import "LocatorModel.h" #import "Calculations.h" //#import "RootViewController.h" // This is a singleton class, see below static LocatorModel *sharedCLDelegate = nil; #define kLatitudeRememberedKey @"latitude" #define kLongitudeRememberedKey @"longitude" @implementation LocatorModel //@synthesize previousLocationList; @synthesize currentLocation, distanceMoved, previousLocation, distanceToTarget, locationManager, delegateView, hasValidTarget, previousTimerLocation, curHeading; - (CLLocation*) targetLocation { return targetLocation; } - (void) setTargetLocation:(CLLocation*)targetLoc { [targetLocation release]; targetLocation = [targetLoc retain]; hasValidTarget = YES; // save the target to make it persistent. NSArray *paths = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES); if (paths) { NSString *finalPath = [[paths lastObject] stringByAppendingPathComponent:@"LocationTargetPersist"]; if (finalPath) { NSMutableDictionary* dict = [NSMutableDictionary dictionary]; [dict setObject:[NSString stringWithFormat:@"%f", [targetLoc coordinate].latitude] forKey:kLatitudeRememberedKey]; [dict setObject:[NSString stringWithFormat:@"%f", [targetLoc coordinate].longitude] forKey:kLongitudeRememberedKey]; [dict writeToFile:finalPath atomically:YES]; } } } - (id)init { if (![super init]) return nil; self.locationManager = [[CLLocationManager alloc] init]; self.locationManager.delegate = self; // Tells the location manager to send updates to this object self.locationManager.desiredAccuracy = kCLLocationAccuracyBestForNavigation; self.locationManager.distanceFilter = kCLDistanceFilterNone; currentLocation = nil; previousLocation = nil; previousTimerLocation = nil; lastHeadingReading = 0.0; // previousLocationList = [[NSMutableArray alloc] init]; [self.locationManager startUpdatingLocation]; [self.locationManager startUpdatingHeading]; // remember the previous target, if it exists. NSArray *paths = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES); if (paths) { NSString *finalPath = [[paths lastObject] stringByAppendingPathComponent:@"LocationTargetPersist"]; if (finalPath) { NSMutableDictionary* dict = [[[NSMutableDictionary alloc] initWithContentsOfFile:finalPath] autorelease]; if (dict) { CLLocationDegrees lat = [[dict objectForKey:kLatitudeRememberedKey] doubleValue]; CLLocationDegrees lon = [[dict objectForKey:kLongitudeRememberedKey] doubleValue]; if (lat && lon) // grab the target coords and set them. { CLLocation* loc = [[CLLocation alloc] initWithLatitude:lat longitude:lon]; [self setTargetLocation:loc]; [loc release]; } } } } return self; } // get which kind of accuracy we are, generally speaking. - (CLLocationAccuracy) getAccuracyGroup:(CLLocationAccuracy)test { if (test < kCLLocationAccuracyNearestTenMeters) return kCLLocationAccuracyNearestTenMeters; else if (test < kCLLocationAccuracyHundredMeters) return kCLLocationAccuracyHundredMeters; else if (test < kCLLocationAccuracyKilometer) return kCLLocationAccuracyKilometer; else if (test < kCLLocationAccuracyThreeKilometers) return kCLLocationAccuracyThreeKilometers; else return -1; } - (CLLocationAccuracy) toFeet:(CLLocationAccuracy) meters { return meters * kFeetToMetersRatio; } - (CLLocationAccuracy) toMeters:(CLLocationAccuracy) feet { return feet / kFeetToMetersRatio; } - (NSString*) accuracyString { NSInteger num = [[NSUserDefaults standardUserDefaults] integerForKey:kMeasurementKey]; if (num == 1) // 1 is metric { return [NSString stringWithFormat:@"%.0f%@", currentLocation.horizontalAccuracy / 2, kMeterLabel]; } else { CLLocationAccuracy accuracy = [self toFeet:currentLocation.horizontalAccuracy]; return [NSString stringWithFormat:@"%.0f%@", accuracy / 2, kFeetLabel]; } } // return the current latitude in degrees - (NSString*) currentLatitudeText:(BOOL) wantsInDecimal { NSString* direction = [Calculations degreeToDirection:currentLocation.coordinate.latitude latitude:YES]; if (wantsInDecimal) { return [NSString stringWithFormat:@"%@ %.3f", direction, fabs(currentLocation.coordinate.latitude)]; } else { // do HH° MM.MMM' conversion. return [Calculations degreesToHourMinuteDecimal:currentLocation.coordinate.latitude isLatitude:YES]; } } // return the current longitude in degrees - (NSString*) currentLongitudeText:(BOOL)wantsInDecimal { if (wantsInDecimal) { return [NSString stringWithFormat:@"%@ %.3f", [Calculations degreeToDirection:currentLocation.coordinate.longitude latitude:NO], fabs(currentLocation.coordinate.longitude)]; } else { // do Mour minute decimal conversion. // do HH° MM.MMM' conversion. return [Calculations degreesToHourMinuteDecimal:currentLocation.coordinate.longitude isLatitude:NO]; } } // return the current latitude in degrees - (NSString*) targetLatitudeText:(BOOL) wantsInDecimal { NSString* direction = [Calculations degreeToDirection:targetLocation.coordinate.latitude latitude:YES]; if (wantsInDecimal) { return [NSString stringWithFormat:@"%@ %.3f", direction, fabs(targetLocation.coordinate.latitude)]; } else { // do HH° MM.MMM' conversion. return [Calculations degreesToHourMinuteDecimal:targetLocation.coordinate.latitude isLatitude:YES]; } } // return the current longitude in degrees - (NSString*) targetLongitudeText:(BOOL)wantsInDecimal { if (wantsInDecimal) { return [NSString stringWithFormat:@"%@ %.3f", [Calculations degreeToDirection:targetLocation.coordinate.longitude latitude:NO], fabs(currentLocation.coordinate.longitude)]; } else { // do Mour minute decimal conversion. // do HH° MM.MMM' conversion. return [Calculations degreesToHourMinuteDecimal:targetLocation.coordinate.longitude isLatitude:NO]; } } // Update the directions variables (do the math) // - (void) updateDirections { if (currentLocation != nil && previousLocation != nil) distanceMoved = [currentLocation distanceFromLocation:previousLocation]; if (currentLocation != nil) distanceToTarget = [currentLocation distanceFromLocation:targetLocation]; } - (void)locationManager:(CLLocationManager *)manager didUpdateHeading:(CLHeading *)newHeading { // update the compass graphics if (curHeading != nil) [curHeading release]; curHeading = newHeading; [curHeading retain]; } - (CLLocationDirection) currentHeading { CLLocationDirection retVal; if (curHeading != nil && curHeading.headingAccuracy > 0) { BOOL magnetic = (2 == [[NSUserDefaults standardUserDefaults] integerForKey:kMagneticCompassKey]); if (magnetic) { retVal = curHeading.magneticHeading; } else { retVal = curHeading.trueHeading; } lastHeadingReading = retVal; return 360.0 - retVal; } else { return 0.0; } } // Invoked when a new location is available. oldLocation may be nil if there is no previous location available. // - (void)locationManager:(CLLocationManager *)manager didUpdateToLocation:(CLLocation *)newLocation fromLocation:(CLLocation *)oldLocation { // this needs smoothed out by timestamp in case of out-of-order updates from location services. // remember the prev location in our history list // [previousLocationList addObject: newLocation]; // release the prev obj if it exists and get the "new" old location if (previousLocation) [previousLocation release]; if (oldLocation) previousLocation = oldLocation; else previousLocation = currentLocation; [previousLocation retain]; if (previousTimerLocation == nil) // should initialize this here only once, the timer will update this from there on out. { previousTimerLocation = oldLocation; [previousTimerLocation retain]; } // get the new object and place it in current. if (currentLocation) [currentLocation release]; currentLocation = newLocation; [currentLocation retain]; if (!targetLocation) targetLocation = [[CLLocation alloc] initWithLatitude:kStartingLatitude longitude:kStartingLongitude]; // update the math based stuff. [self update]; } // do the updating work. - (void) update { static BOOL hasUpdated = NO; [self updateDirections]; // if (hasUpdated == NO) // [delegateView updateDisplay:YES updateCompass:YES]; // else // [delegateView updateDisplay:NO updateCompass:NO]; hasUpdated = YES; } // Invoked when an error has occurred. // - (void)locationManager:(CLLocationManager *)manager didFailWithError:(NSError *)error { NSLog(@"%@", [NSString stringWithFormat:@"location Manager error: %@", [error localizedDescription]]); } - (void) dealloc { [self.locationManager stopUpdatingLocation]; if ([locationManager respondsToSelector:@selector(stopUpdatingHeading)]) [self.locationManager stopUpdatingHeading]; [self.locationManager release]; if (currentLocation) [currentLocation release]; if (previousLocation) [previousLocation release]; [super dealloc]; } #pragma mark ---- singleton object methods ---- // See "Creating a Singleton Instance" in the Cocoa Fundamentals Guide for more info + (LocatorModel *)sharedInstance { @synchronized(self) { if (sharedCLDelegate == nil) { [[self alloc] init]; // assignment not done here } } return sharedCLDelegate; } + (id)allocWithZone:(NSZone *)zone { @synchronized(self) { if (sharedCLDelegate == nil) { sharedCLDelegate = [super allocWithZone:zone]; return sharedCLDelegate; // assignment and return on first allocation } } return nil; // on subsequent allocation attempts return nil } - (id)copyWithZone:(NSZone *)zone { return self; } - (id)retain { return self; } - (unsigned)retainCount { return UINT_MAX; // denotes an object that cannot be released } - (void)release { //do nothing } - (id)autorelease { return self; } @end