// // CompassViewController.m // Geopher OpenCaching // // Created by Jeremy on 12/11/10. // Copyright 2010 Stone Software. All rights reserved. // #import "CompassViewController.h" #import "LocatorModel.h" #import "Calculations.h" @implementation CompassViewController @synthesize arrow, compass, targetLatitudeLabel, targetLongitudeLabel, currentLatitudeLabel, currentLongitudeLabel, bearingLabel, accuracyLabel, distanceToTarget, GCCodeLabel; #warning GCCodeLabel does not currently get filled with anything! // The designated initializer. Override if you create the controller programmatically and want to perform customization that is not appropriate for viewDidLoad. - (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil { self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil]; if (self) { // Custom initialization. prevBearing = 0.0f; } return self; } // Implement viewDidLoad to do additional setup after loading the view, typically from a nib. - (void)viewDidLoad { // start updating every 7/10ths of a second. NSTimer* theTimer = [[NSTimer alloc] initWithFireDate:[NSDate date] interval:0.7 target:self selector:@selector(update) userInfo:nil repeats:YES]; if ([theTimer isValid] == NO) NSLog(@"Invalid Timer!"); [[NSRunLoop currentRunLoop] addTimer:theTimer forMode:NSDefaultRunLoopMode]; [theTimer release]; [super viewDidLoad]; } - (void) viewWillAppear:(BOOL)animated { [self update]; // update the view when we change to it. [[UIApplication sharedApplication] setIdleTimerDisabled:YES]; // default the target screen to not go to sleep by default } - (void)viewWillDisappear:(BOOL)animated { [[UIApplication sharedApplication] setIdleTimerDisabled:NO]; // default the target screen to not go to sleep by default } /* // Override to allow orientations other than the default portrait orientation. - (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation { // Return YES for supported orientations. return (interfaceOrientation == UIInterfaceOrientationPortrait); } */ - (void)didReceiveMemoryWarning { // Releases the view if it doesn't have a superview. [super didReceiveMemoryWarning]; // Release any cached data, images, etc. that aren't in use. } - (void)viewDidUnload { [super viewDidUnload]; // Release any retained subviews of the main view. // e.g. self.myOutlet = nil; } - (void)dealloc { [arrow release]; [compass release]; [currentLatitudeLabel release], currentLatitudeLabel = nil; [currentLongitudeLabel release], currentLongitudeLabel = nil; [targetLatitudeLabel release], targetLatitudeLabel = nil; [targetLongitudeLabel release], targetLongitudeLabel = nil; [bearingLabel release], bearingLabel = nil; [accuracyLabel release], accuracyLabel = nil; [distanceToTarget release], distanceToTarget = nil; [GCCodeLabel release], GCCodeLabel = nil; [super dealloc]; } #pragma mark - #pragma mark fields and compass // always return a specific orientation - (UIDeviceOrientation) orientation { UIDeviceOrientation orientation = [[UIDevice currentDevice] orientation]; if (orientation == UIDeviceOrientationFaceUp || orientation == UIDeviceOrientationFaceDown || orientation == UIDeviceOrientationUnknown) { orientation = [UIApplication sharedApplication].statusBarOrientation; if (orientation == UIDeviceOrientationUnknown) orientation = UIDeviceOrientationPortrait; } return orientation; } - (void) update { [self updateDisplay:YES updateCompass:YES]; } - (void) updateDisplay:(BOOL)newTarget updateCompass:(BOOL)compassUpdate { if (YES == [[LocatorModel sharedInstance] hasValidTarget]) { distanceToTarget.text = [Calculations distToDisplayInDecimal:[LocatorModel sharedInstance].distanceToTarget]; } // remove arrow graphic if we don't have a target. if (NO == [[LocatorModel sharedInstance] hasValidTarget]) { distanceToTarget.text = nil; } if ([[LocatorModel sharedInstance] hasValidTarget] == YES) { targetLatitudeLabel.text = [[LocatorModel sharedInstance] targetLatitudeText: NO]; targetLongitudeLabel.text = [[LocatorModel sharedInstance] targetLongitudeText: NO]; } else { targetLatitudeLabel.text = nil; targetLongitudeLabel.text = nil; } currentLatitudeLabel.text = [[LocatorModel sharedInstance] currentLatitudeText: NO]; currentLongitudeLabel.text = [[LocatorModel sharedInstance] currentLongitudeText: NO]; // update the view [self updateCompassAndArrowBearing:newTarget updateCompass:compassUpdate]; } // update the bearing. - (void) updateCompassAndArrowBearing:(BOOL)newTarget updateCompass:(BOOL)compassUpdate { LocatorModel* ins = [LocatorModel sharedInstance]; Calculations* calc = [[[Calculations alloc] init] autorelease]; [calc initSourceCoords:ins.currentLocation.coordinate.latitude longitude:ins.currentLocation.coordinate.longitude]; [calc initDestinationCoords:ins.targetLocation.coordinate.latitude longitude:ins.targetLocation.coordinate.longitude]; // calc the current bearing for the compass CLLocationDirection arrowBearing = [calc calcBearing]; CLLocationDirection compassHeading = 0.0; CLLocationAccuracy currentAccuracy = ins.currentLocation.horizontalAccuracy; CLLocationAccuracy previousAccuracy = ins.previousLocation.horizontalAccuracy; BOOL moveCompass = YES; BOOL animate = NO; BOOL newCheck = [ins currentHeading] > 0.0 && [CLLocationManager headingAvailable]; @synchronized(self) { if (newCheck) { compassHeading = [ins currentHeading]; // shift by 10 to keep 1 decimal place of accuracy. long int numToMod = ((arrowBearing + compassHeading) * 10.0) + 3600; arrowBearing = (numToMod % 3600) / 10.0; // move the arrow with the compass. animate = YES; } // else if we don't have a heading, try to calculate it manually. The old long way. else if (ins.previousLocation != nil && [ins getAccuracyGroup:currentAccuracy] == [ins getAccuracyGroup:previousAccuracy] && currentAccuracy > 0.0 && previousAccuracy > 0.0) { CLLocationDistance howFar = [ins.currentLocation distanceFromLocation:ins.previousLocation]; CLLocationAccuracy accuracyDistanceChange = kCompassAccuracy; // if the distance is < 100 meters update more frequently. if (ins.distanceToTarget < 100.0) { accuracyDistanceChange = 0.0; } // otherwise calculate actual accuracy change. else if (ins.currentLocation.horizontalAccuracy > 0.0 && ins.previousLocation.horizontalAccuracy > 0.0) { accuracyDistanceChange = fabsf(ins.currentLocation.horizontalAccuracy - ins.previousLocation.horizontalAccuracy); } BOOL oldCheck = (howFar > accuracyDistanceChange && howFar > kCompassAccuracy && (currentAccuracy <= previousAccuracy || currentAccuracy <= kCLLocationAccuracyHundredMeters)); if (oldCheck && YES == moveCompass && YES == compassUpdate) { // calc the bearing between the old and new points for the compass double tempBearing = [calc calcBearingWithLatitude:ins.previousTimerLocation.coordinate.latitude latitude:ins.currentLocation.coordinate.latitude longitude:ins.previousTimerLocation.coordinate.longitude longitude:ins.currentLocation.coordinate.longitude]; // take the current movement bearing and move the compass accordingly. double bearingOrientationModifier = 360.0; // default for portrait. // // alter the heading based on orientation. // UIDeviceOrientation orientation = [self orientation]; // if (orientation == UIDeviceOrientationPortraitUpsideDown) // bearingOrientationModifier = 180.0; // else if (orientation == UIDeviceOrientationLandscapeLeft) // bearingOrientationModifier = 90.0; // else if (orientation == UIDeviceOrientationLandscapeRight) // bearingOrientationModifier = 270.0; compassHeading = bearingOrientationModifier - tempBearing; if (compassHeading < 0) compassHeading = 360.0 + compassHeading; if (prevBearing > 0.0) { // only make course corrections every x degrees if (fabsf(prevBearing - compassHeading) < kCompassDegreeAccuracy && newCheck == NO) compassHeading = prevBearing; } arrowBearing = (int)(arrowBearing + compassHeading + 360) % 360; // move the arrow with the compass. animate = YES; prevBearing = compassHeading; } } CGAffineTransform transform; if (animate == YES) { if (![arrow isAnimating]) { // rotate the arrow [UIView beginAnimations:nil context:nil]; [UIView setAnimationDuration:ARROW_ANIMATION_DURATION_SECONDS]; [UIView setAnimationDelay:0.0]; transform = CGAffineTransformMakeRotation([calc toRadian:arrowBearing]); arrow.transform = transform; [UIView commitAnimations]; } if (![compass isAnimating]) { // rotate the compass [UIView beginAnimations:nil context:nil]; [UIView setAnimationDuration:ARROW_ANIMATION_DURATION_SECONDS]; [UIView setAnimationDelay:0.0]; transform = CGAffineTransformMakeRotation([calc toRadian:compassHeading]); compass.transform = transform; [UIView commitAnimations]; } // only update the bearing text when the compass and arrow rotate. bearingLabel.text = [NSString stringWithFormat:@"Bearing %.0f°", [calc calcBearing]]; } else if (moveCompass == NO || newTarget) // rotate the arrow without moving the compass { if (![arrow isAnimating]) { // rotate the arrow [UIView beginAnimations:nil context:nil]; [UIView setAnimationDuration:ARROW_ANIMATION_DURATION_SECONDS]; [UIView setAnimationDelay:0.0]; transform = CGAffineTransformMakeRotation([calc toRadian:arrowBearing]); arrow.transform = transform; [UIView commitAnimations]; } // only update the bearing text when the compass and arrow rotate. bearingLabel.text = [NSString stringWithFormat:@"Bearing %.0f°", [calc calcBearing]]; } // update the bearing and accuracy bearingLabel.font = [UIFont systemFontOfSize:13]; accuracyLabel.text = [NSString stringWithFormat:@"Accuracy ±%@", [ins accuracyString]]; accuracyLabel.font = [UIFont systemFontOfSize:13]; } } @end