Become a MacRumors Supporter for $50/year with no ads, ability to filter front page stories, and private forums.

DennisBlah

macrumors 6502
Original poster
Dec 5, 2013
485
2
The Netherlands
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!
 
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.
 
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.
 
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
 
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).

No, it's an array of CLLocation objects, not some "big string".
 
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).

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.
 
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 :)
 
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.
}
 
Last edited:
Register on MacRumors! This sidebar will go away, and you'll see fewer ads.