Compass navigation

Discussion in 'iOS Programming' started by DennisBlah, Apr 29, 2014.

  1. DennisBlah macrumors 6502

    DennisBlah

    Joined:
    Dec 5, 2013
    Location:
    The Netherlands
    #1
    Hi there,

    I'm trying to make an image aka compass point to a certain coordinate.

    With this code I get my compass to point to the North:
    Code:
    - (void)viewDidLoad
    {
        locationManager = [[CLLocationManager alloc] init];
        locationManager.distanceFilter = kCLDistanceFilterNone;
        locationManager.desiredAccuracy = kCLLocationAccuracyBest;
        locationManager.delegate = self;
        [locationManager startUpdatingLocation];
        [locationManager startUpdatingHeading];
    }
    
    -(BOOL) shouldAutorotate {
        return NO;
    }
    
    - (UIInterfaceOrientation)preferredInterfaceOrientationForPresentation {
        return UIInterfaceOrientationPortrait;
    }
    
    #pragma mark -
    #pragma mark CLLocationManagerDelegate
    
    - (void)locationManager:(CLLocationManager *)manager didUpdateHeading:(CLHeading *)newHeading{
        // Convert Degree to Radian and move the needle
        float oldRad =  -manager.heading.trueHeading * M_PI / 180.0f;
        float newRad =  -newHeading.trueHeading * M_PI / 180.0f;
        CABasicAnimation *theAnimation;
        theAnimation=[CABasicAnimation animationWithKeyPath:@"transform.rotation"];
        theAnimation.fromValue = [NSNumber numberWithFloat:oldRad];
        theAnimation.toValue=[NSNumber numberWithFloat:newRad];
        theAnimation.duration = 0.1f;
        [compassImage.layer addAnimation:theAnimation forKey:@"animateMyRotation"];
        compassImage.transform = CGAffineTransformMakeRotation(newRad);
    }
    
    Now what I'm trying to is the following:
    Code:
    -(IBAction)setLocation:(id)sender {
        tLat = locationManager.location.coordinate.latitude;
        tLon = locationManager.location.coordinate.longitude;
    }
    
    //Using 
    #define RadiansToDegrees(radians)(radians * 180.0/M_PI)
    #define DegreesToRadians(degrees)(degrees * M_PI / 180.0)
    
    -(float)setLatLonForDistanceAndAngle:(CLLocation *)userlocation
    {
        float lat1 = DegreesToRadians(userlocation.coordinate.latitude);
        float lon1 = DegreesToRadians(userlocation.coordinate.longitude);
        
        float lat2 = DegreesToRadians(tLat);
        float lon2 = DegreesToRadians(tLon);
        
        float dLon = lon2 - lon1;
        
        float y = sin(dLon) * cos(lat2);
        float x = cos(lat1) * sin(lat2) - sin(lat1) * cos(lat2) * cos(dLon);
        float radiansBearing = atan2(y, x);
        if(radiansBearing < 0.0)
        {
            radiansBearing += 2*M_PI;
        }
        
        return radiansBearing;
    }
    
    -(void)locationManager:(CLLocationManager *)manager didUpdateToLocation:(CLLocation *)newLocation fromLocation:(CLLocation *)oldLocation
    {
        GeoAngle = [self setLatLonForDistanceAndAngle:newLocation];
    }
    
    - (void)locationManager:(CLLocationManager *)manager didUpdateHeading:(CLHeading *)newHeading
    {
        float direction = -newHeading.trueHeading;
        theCompass.transform=CGAffineTransformMakeRotation((direction* M_PI / 180)+ GeoAngle);
    }
    
    Somehow it's not working quite right... I took this function 'setLatLonForDistanceAndAngle' one of all other pages I been searching around about bearing with coordinates.

    The big problems I'm facing:
    - The locationManager stops getting current location after ... x amount of seconds.
    - The needle is not rotating correctly.

    Is there anybody that has made something like this before, and is willing to help me out?

    Kind regards!
     
  2. TheWatchfulOne macrumors 6502

    Joined:
    Jun 19, 2009
    #2
    Are you using the latest version of the SDK?

    The delegate method you are trying to use:
    Code:
    -(void)locationManager:(CLLocationManager *)manager didUpdateToLocation:(CLLocation *)newLocation fromLocation:(CLLocation *)oldLocation
    has been deprecated.

    Try using the method that replaced it:
    Code:
    - (void)locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray *)locations
    Every location object you receive will not be accurate enough so you should test them against certain criteria before you use them. If you Google for a sample implementation of this method you'll see what I'm talking about.

    I don't know if that alone will fix your problem but that's where I'd start.
     
  3. DennisBlah thread starter macrumors 6502

    DennisBlah

    Joined:
    Dec 5, 2013
    Location:
    The Netherlands
    #3
    Thanks I will take a look at that tomorrow when I'm back at work.
     
  4. DennisBlah thread starter macrumors 6502

    DennisBlah

    Joined:
    Dec 5, 2013
    Location:
    The Netherlands
    #4
    Code:
    - (void)locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray *)locations
    Thanks for pointing me to the deprecated function. I didn't received any warning in xCode however my xCode is up to date.

    Anyways, this new function brings me to an big workaround to get current longitude and latitude. (I don't understand why they make it an NSArray as it only contains 1 object with a big string of location, proberly height, speed and course with an timestamp and timezone).

    So after the strip I take the one and only array object on the ' ' space.
    Then replace characters like <, >, +, - to nothing. And explode again on the ',' to get the longitude and latitude.

    However, I don't have to do it all the time, the device does it so no problem at all. But still it has an huge delay of almost 45 seconds. And my compass is not rotating to the right direction.

    Anyone has any experience with bearing with coordinates ?
    I want to be able to set current location. And after that I wanna walk away and have my compass pointing to the direction where I setted the location.
     
  5. waterskier2007 macrumors 68000

    waterskier2007

    Joined:
    Jun 19, 2007
    Location:
    White Lake, MI
    #5
    Dude, if you read the documentation, you would know it is an array of CLLocation objects. The locations will always be at least 1 item, and it could be more if there was an issue where the location manager had to store multiple locations and deliver them all at once. The most recent location is always the last object in the array

    Just do

    Code:
    CLLocation * loc = [locations lastObject];
    
    From there you can access all the properties of that object

    see here
     
  6. dejo Moderator

    dejo

    Staff Member

    Joined:
    Sep 2, 2004
    Location:
    The Centennial State
    #6
    No, it's an array of CLLocation objects, not some "big string".
     
  7. chown33 macrumors 604

    Joined:
    Aug 9, 2009
    #7
    Please explain how you came to that conclusion.

    My guess is that you used NSLog to print the returned object. What you failed to realize is that NSLog calls the -description method of the object in order to produce an NSString description. An NSArray (and many other Foundation objects) will produce an NSString that contains some delimiters, followed by a recursive call to -description of every object it contains.

    If you assume this means that the object is simply a string that must be parsed, you're making a huge mistake. The string doesn't exist until NSLog asks for it, or the %@" format-specifier in a formatting string.
     
  8. DennisBlah thread starter macrumors 6502

    DennisBlah

    Joined:
    Dec 5, 2013
    Location:
    The Netherlands
    #8
    Well ehm, when I try to init firstObject or lastObject I get the same stuff.

    And now I see it.. when i init it with CLLocation it looks pretty well. :)
    firstObject is oldLocation and lastObject newLocation :)

    And I found some other topic with an 'equal' problem and I'm going through that now :)
     
  9. DennisBlah, May 3, 2014
    Last edited: May 4, 2014

    DennisBlah thread starter macrumors 6502

    DennisBlah

    Joined:
    Dec 5, 2013
    Location:
    The Netherlands
    #9
    Ok after following another example and some readings about bearing I wrote the code down here.
    It seems to be working good (with manually setted coordinates from googlemaps).

    Once doing it through the setTarget call it's not so great anymore. It will not change from 'direction'. So when I set my target, and walking to the north (away from the target) the needle should go and point to the south.
    And I mean.. on target difference smaller than 10 meters. Haven't walked outside yet. I will in a few moments while walking our dog :)

    ---------------
    For some reason the needle keeps pointing to the North East <-> East nomatter what direction and how far I walk away from target.
    ---------------

    Does anyone has any comments or idea's to make this app be better ? Preciser, faster ?

    Code:
    #pragma mark -
    #pragma mark CLLocationManagerDelegate
    
    - (void)viewDidLoad
    {
        [super viewDidLoad];
        locationManager = [[CLLocationManager alloc] init];
        locationManager.distanceFilter = kCLDistanceFilterNone;
        locationManager.desiredAccuracy = kCLLocationAccuracyBest;
        locationManager.delegate = self;
        locationManager.pausesLocationUpdatesAutomatically = NO;
        locationManager.headingFilter = 1;
        [locationManager startUpdatingLocation];
        [locationManager startUpdatingHeading];
    }
    
    -(IBAction)setTarget:(id)sender {
        setLocation = curLocation;
        distance.hidden = NO;
        theNeedle.hidden = NO;
    }
    
    #define RadiansToDegrees(radians)((radians * 180.0) / M_PI)
    #define DegreesToRadians(degrees)((degrees * M_PI) / 180.0)
    
    -(float)calculateAngle:(CLLocation *)userlocation
    {
        float userLocationLatitude = DegreesToRadians(curLocation.coordinate.latitude);
        float userLocationLongitude = DegreesToRadians(curLocation.coordinate.longitude);
        
        float targetedPointLatitude = DegreesToRadians(setLocation.coordinate.latitude);
        float targetedPointLongitude = DegreesToRadians(setLocation.coordinate.latitude);
        
        float longitudeDifference = targetedPointLongitude - userLocationLongitude;
        
        float y = sin(longitudeDifference) * cos(targetedPointLatitude);
        float x = cos(userLocationLatitude) * sin(targetedPointLatitude) - sin(userLocationLatitude) * cos(targetedPointLatitude) * cos(longitudeDifference);
        float radiansValue = atan2(y, x);
        if(radiansValue < 0.0)
        {
            radiansValue += 2*M_PI;
        }
        
        return radiansValue;
    }
    
    
    
    -(void)locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray *)locations {
        curLocation = [locations lastObject];
        if(setLocation != nil) {
            angle = [self calculateAngle:[locations lastObject]];
            CLLocationDistance distanceLocation = [setLocation distanceFromLocation: curLocation];
            tDistance = distanceLocation;
            [distance setText: [NSString stringWithFormat: @"%.2f m. to go", tDistance]];
            if(distanceLocation < 5) {
                theNeedle.image = [UIImage imageNamed: @"near.png"];
            } else {
                theNeedle.image = [UIImage imageNamed: @"direction.png"];
            }
        }
    }
    
    -(void)locationManager:(CLLocationManager *)manager didUpdateHeading:(CLHeading *)newHeading {
        if (newHeading.headingAccuracy > 0) {
            currentHeading = newHeading;
        
            //Rotate the compass
            float oldRad =  -manager.heading.trueHeading * M_PI / 180.0f;
            float newRad =  -newHeading.trueHeading * M_PI / 180.0f;
            CABasicAnimation *theAnimation;
            theAnimation=[CABasicAnimation animationWithKeyPath:@"transform.rotation"];
            theAnimation.fromValue = [NSNumber numberWithFloat:oldRad];
            theAnimation.toValue=[NSNumber numberWithFloat:newRad];
            theAnimation.duration = 0.1f;
            [theCompass.layer addAnimation:theAnimation forKey:@"animateMyRotation"];
            theCompass.transform = CGAffineTransformMakeRotation(newRad);
        
            //Rotate the needle if target is set
            if(setLocation != nil) {
                float direction = newHeading.magneticHeading;
            
                if (direction > 180) {
                    direction = 360 - direction;
                } else {
                    direction = 0 - direction;
                }
            
                // Rotate the arrow image
                [UIView animateWithDuration:3.0f animations:^{ theNeedle.transform = CGAffineTransformMakeRotation(DegreesToRadians(direction) + angle); }];
            }
        }
    }
    
    
    -(BOOL)locationManagerShouldDisplayHeadingCalibration:(CLLocationManager *)manager {
        if(!currentHeading) return YES; // Got nothing, We can assume we got to calibrate.
        else if( currentHeading.headingAccuracy < 0 ) return YES; // 0 means invalid heading, need to calibrate
        else if( currentHeading.headingAccuracy > 5 )return YES; // 5 degrees is a small value correct for my needs, too.
        else return NO; // All is good. Compass is precise enough.
    }
    
     

Share This Page