I am building an application that should show an arrow to a certain location. Forinstance, if I am at position X and the target is set to position Y, it should show an arrow pointing directly to that position.
After searching and searching I found that there are some fancy formulas of calculating this, however I seem to get it wrong over and over.
This is my problem. Although it seems to find the correct initial position, as soon as I turn my device the "arrow" turns in the opposite direction or what it should. So, if I turn my device clockwise, the arrow turns also clockwise... and visa versa.
This is some of my code:
#implementation CompassViewController
BOOL firstPositionFound = NO;
float lastPosition = 0;
float currentHeading = 0;
[...]
#pragma mark -
#pragma mark Core Location Methods
- (void)locationManager:(CLLocationManager *)manager didUpdateHeading:(CLHeading *)newHeading
{
if (newHeading.headingAccuracy > 0) {
CLLocationDirection theHeading = newHeading.magneticHeading;
currentHeading = theHeading;
[self fixPointer:theHeading];
}
}
- (void)locationManager:(CLLocationManager *)manager didUpdateToLocation:(CLLocation *)newLocation fromLocation:(CLLocation *)oldLocation{
self.currentLocation = newLocation;
[self fixPointer:currentHeading];
}
- (void)fixPointer:(float)heading
{
float degree = [self calculateDegree:self.currentLocation];
degree = heading - degree;
NSLog(#"heading: %f, Degree: %f", heading, degree);
NSLog(#"Degree 2: %f", degree);
self.arrowView.transform = CGAffineTransformMakeRotation(degreesToRadians(degree));
}
#pragma mark -
#pragma mark Delegate methods
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
{
return (interfaceOrientation == UIInterfaceOrientationPortrait);
}
#pragma mark -
#pragma mark Other methods
- (float)calculateDegree:(CLLocation *)newLocation
{
CLLocationCoordinate2D from = newLocation.coordinate;
CLLocationCoordinate2D to;
to.latitude = APP_DESTINATION_LAT;
to.longitude = APP_DESTINATION_LON;
float res = atan2(sin((to.longitude*M_PI/180)-(from.longitude*M_PI/180))*cos(to.latitude*M_PI/180),
cos(from.latitude*M_PI/180)*sin(to.latitude*M_PI/180)-sin(from.latitude*M_PI/180)*cos(to.latitude*M_PI/180)*cos((to.longitude*M_PI/180)-(from.longitude*M_PI/180)));
res = res/M_PI*180;
if (res < 0)
res = 360.0f + res;
return res;
}
#pragma mark -
I am just lost, could someone please point out to me where I went wrong? I guess its some simple thing that I currently am to blind for to see.
Have a look at the answer in this Stack Overflow question:
CLLocation Category for Calculating Bearing w/ Haversine function
specifically, this part:
If you are getting negative bearings, add 2*M_PI to the final result in radiansBearing (or 360 if you do it after converting to degrees). atan2 returns the result in the range -M_PI to M_PI (-180 to 180 degrees), so you might want to convert it to compass bearings, using something like the following code
if(radiansBearing < 0.0)
radiansBearing += 2*M_PI;**
Also, heading info needs to be adjusted for device orientation and angle, unless you are always holding your device in portrait. Turn you device slowly to landscape mode and you will see your heading value change by 90°.
Check this out
Wikipedia - On rotation matrix
From I understand from the code you have the angle between the sin element and the cos element of the given (x,y) coordinate. [Basically given by (tan(x,0)/(0,y))]
lets call this angle theta.
What you should do is take this coordinate and multiply it by
lets call the new coordinate (x',y')
Hope this helps.
Related
I m using Google map For my IOS application.
I m able to calculate Route and draw polyline based on it from directions API. The present direction api and my code is working well. But if user is not going to drawn route or going to wrong direction. I would like to recalculate route from the direction user is travelling not from the direction user is coming on. I m recalculating route for every 100 meters if not going to routhpath which is drawn.
It's calculating route correctly but in the opposite direction i m travelling on. So it should detect the direction i'm going on a road and not the backwards direction from which i m coming.
Here is code snippet. I written in delegate didUpdateToLocations:
- (void)locationManager:(CLLocationManager *)manager didUpdateToLocation:(CLLocation *)newLocation fromLocation:(CLLocation *)oldLocation
if (aLocationDistanceLoc == Nil) {
aLocationDistanceLoc=[[CLLocation alloc]init];
aLocationDistanceLoc=newLocation;
}
else
{
CLLocationDistance distance1 = [aLocationDistanceLoc distanceFromLocation:newLocation];
if (distance1 > 100) {
aLocationDistanceLoc=newLocation;
if (GMSGeometryIsLocationOnPathTolerance(newLocation.coordinate, routesPath, YES, 10)) {
}
else
{
flagForRouteRecalculateActive=YES;
[self callApiForNavigation:[NSString stringWithFormat:#"%f,%f",newLocation.coordinate.latitude,newLocation.coordinate.longitude] destinationPoint:[NSString stringWithFormat:#"%#,%#",currentLatDropoff ,currentLongDropoff ]];
}
}
}
Here is my Directions API call:
[NSString stringWithFormat:#"https://maps.googleapis.com/maps/api/directions/json?origin=%f,%f&destination=%f,%f&sensor=true&mode=driving",[[arrayStart objectAtIndex:0] floatValue],[[arrayStart objectAtIndex:1] floatValue],[[arrayDestination objectAtIndex:0] floatValue],[[arrayDestination objectAtIndex:1] floatValue]]
Here is the Video demonstration of problem. You'll see as i moving in direction the route is recalculating but its recalculate from backward direction. I need to recalculate from front in the direction i m going.
video of actual Issue
I am building a compass into an IOS app that I'm developing. Currently I am building on iPad but am facing a huge issue when switching orientations whilst on the view
IF I open the view while the app is already in landscape/portrait mode, the compass appears perfectly fine (like 1st image) however IF i go in Portrait mode for example and then turn to landscape, the whole UI of the compass goes crazy (see second image)
My code in ViewDidLoad looks something like this:
//start updating compass
locationManager=[[CLLocationManager alloc] init];
locationManager.desiredAccuracy = kCLLocationAccuracyBest;
locationManager.headingFilter = 1;
locationManager.delegate=self;
[locationManager startUpdatingHeading];
//get coords of current location
CLLocation *location = [locationManager location];
CLLocationCoordinate2D fromLoc = [location coordinate];
//mecca:
CLLocationCoordinate2D toLoc = [location coordinate];
toLoc = CLLocationCoordinate2DMake(21.4167, 39.8167);
//calculate the bearing between current location and Mecca
float fLat = degreesToRadians(fromLoc.latitude);
float fLng = degreesToRadians(fromLoc.longitude);
float tLat = degreesToRadians(toLoc.latitude);
float tLng = degreesToRadians(toLoc.longitude);
float degree = radiandsToDegrees(atan2(sin(tLng-fLng)*cos(tLat), cos(fLat)*sin(tLat)-sin(fLat)*cos(tLat)*cos(tLng-fLng)));
if (degree >= 0) {
bearing = degree;
} else {
bearing = degree+360;
}
NSLog(#"bearing: %f", bearing);
//rotate the needle from true north
float MnewRad = degreesToRadians(bearing);
needleImage.transform = CGAffineTransformMakeRotation(MnewRad);//rotate the number of degrees from north
}
- (void)locationManager:(CLLocationManager *)manager didUpdateHeading:(CLHeading *)newHeading {
//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.3f;
[compassView.layer addAnimation:theAnimation forKey:#"animateMyRotation"];
compassView.transform = CGAffineTransformMakeRotation(newRad);
}
I'd really appreciate some help with this because its driving me crazy! Thank you
I am making a Qibla functionality for another app, and have compass issues, but I have to say your issue is completely unrelated to the compass functionality.
You said, when you rotate the device, the views get messed up. That seems like an obvious view layout problem. Depending on whether you are using auto layout or not, I would at least suggest that you add all the compass views into a single view container, first. That view should have a fixed size and centered in the view. (aka UIAutoResizingMaskFlexibleTopMargin, BottomMargin, RightMargin, LeftMargin).
Then, I am not sure if the rotation is working well for you, but I use transform.rotation.z, because the rotation happens on the z axis.
Finally, I suggest you would make the animation duration equal to the location update period, i.e duration = 0.1 or something
btw, I switched from using CoreLocation and moved to CoreMotion. It has a simpler compass API, and is much less resource demanding.
If you need to see code, here is my old code that uses CoreLocation:
- (int)fixOrientation {
switch ([[UIApplication sharedApplication] statusBarOrientation]) {
case UIInterfaceOrientationPortrait:
return 0;
case UIInterfaceOrientationLandscapeLeft:
return 90;
case UIInterfaceOrientationLandscapeRight:
return 270;
case UIInterfaceOrientationPortraitUpsideDown:
return 180;
}
}
CLHeading* newHeading = [MCLocationTracker sharedTracker].currentHeading;
[self dismissAlert];
float headingFloat = - newHeading.magneticHeading;
orientationCorrection = [self fixOrientation];
double angle = [[MCLocationTracker sharedTracker] qiblaBearing];
[UIView animateWithDuration:0.05f
delay:0.f
options:(UIViewAnimationOptionCurveEaseOut | UIViewAnimationOptionBeginFromCurrentState)
animations:^{
self.compassImage.transform = CGAffineTransformMakeRotation(toRadian((headingFloat + orientationCorrection)));
if (fabs(kMCLocTrackerUndefinedAngle - angle) > 0.1f) {
self.arrow.transform = CGAffineTransformMakeRotation(toRadian((headingFloat+angle) + orientationCorrection));
}
}
completion:NULL];
didUpdateHeading is called multiple times per second, so you don't need any animation here. I suspect you aren't seeing anything dues to the 0.3f second animation lag. Set your angle rotation to the new value directly and it will still look smooth.
i am using MKMapView in my custom app and would like to show a map scale (tape measure) during zooming like Apple's Maps.app. Is this possible?
If not, and i would implement my own map scale, how can i get continious update information while the zoom ov the MKMapView is changed?
- (void)mapView:(MKMapView *)mapView regionWillChangeAnimated:(BOOL)animated
seems to be called only once at the begining of a zoom while
- (void)mapView:(MKMapView *)mapView regionDidChangeAnimated:(BOOL)animated
is called only once at the end of the zoom.
Maps.app map scale is shown and updated in realtime continiously during a zoom.
thanks in advance.
I had a similar problem, getting the camera.altitude based on user zooming, to display in a label.
Since there is no method such as "regionISChangingAnimated", but only WillChange and DidChange, I start a timer at WillChange and invalidate it at DidChange. The timer calls a method (updateElevationLabel) that calculates the altitude of the camera above the map.
However, since camera.altitude is not calculated until regionDidChange is called, use the zoomscale and the starting height of the map (zoomscale = 1.0 does not always equal altitude = 0m, it depends on where you are in the world) to calculate the current height. Starting height is a float in the method below, set it once on load and for each region change.
Finally you can change the format of the altitude, e.g. from km from m beyond a certain altitude (10'000 metres below).
For the oldschool: 1m = 3.2808399 ft.
-(void)mapView:(MKMapView *)mapView regionWillChangeAnimated:(BOOL)animated {
if (showsElevation) {
//update starting height for the region
MKMapCamera *camera = map.camera;
CLLocationDistance altitude = camera.altitude;
MKZoomScale currentZoomScale = map.bounds.size.width / map.visibleMapRect.size.width;
float factor = 1.0/currentZoomScale;
startingHeight = altitude/factor;
elevationTimer = [NSTimer scheduledTimerWithTimeInterval:0.1
target:self
selector:#selector(updateElevationLabel)
userInfo:Nil
repeats:YES];
[elevationTimer fire];
}
}
- (void)mapView:(MKMapView *)mapView regionDidChangeAnimated:(BOOL)animated {
[elevationTimer invalidate];
}
-(void)updateElevationLabel {
//1. create the label
if (!elevationLabel) {
elevationLabel = [UILabel new];
[elevationLabel setFrame:CGRectMake(0, 18, 200, 44)];
[elevationLabel setBackgroundColor:[UIColor redColor]];
[self addSubview:elevationLabel];
}
//2. grab the initial starting height (further updated on region changes)
if (startingHeight == 0) {
MKMapCamera *camera = map.camera;
CLLocationDistance altitude = camera.altitude;
MKZoomScale currentZoomScale = map.bounds.size.width / map.visibleMapRect.size.width;
float factor = 1.0/currentZoomScale;
startingHeight = altitude/factor;
}
//3. get current zoom scale and altitude, format changes
MKZoomScale currentZoomScale = map.bounds.size.width / map.visibleMapRect.size.width;
float altitude = startingHeight * (1/currentZoomScale);
if (altitude>10000) {
altitude = altitude/1000;
[elevationLabel setText:[NSString stringWithFormat:#"%.1fkm", altitude]];
} else {
[elevationLabel setText:[NSString stringWithFormat:#"%.0fm", altitude]];
}
}
From Apple's docs on MKMapView's regionWillChangeAnimated: method (emphasis mine):
This method is called whenever the currently displayed map region
changes. During scrolling, this method may be called many times to
report updates to the map position. Therefore, your implementation of
this method should be as lightweight as possible to avoid affecting
scrolling performance.
Sounds like you should be able to use this method continuously as the map view scrolls, which solves part of your problem - so when that method is called, take a look at (I'm guessing here) the mapview's region attribute and derive your map scale from that.
I am trying to check whether my SKSpriteNode will remain in bounds of the screen during a drag gesture. I've gotten to the point where I am pretty sure my logic toward approaching the problem is right, but my implementation is wrong. Basically, before the player moves from the translation, the program checks to see whether its in bounds. Here is my code:
-(CGPoint)checkBounds:(CGPoint)newLocation{
CGSize screenSize = self.size;
CGPoint returnValue = newLocation;
if (newLocation.x <= self.player.position.x){
returnValue.x = MIN(returnValue.x,0);
} else {
returnValue.x = MAX(returnValue.x, screenSize.width);
}
if (newLocation.y <= self.player.position.x){
returnValue.y = MIN(-returnValue.y, 0);
} else {
returnValue.y = MAX(returnValue.y, screenSize.height);
}
NSLog(#"%#", NSStringFromCGPoint(returnValue));
return returnValue;
}
-(void)dragPlayer: (UIPanGestureRecognizer *)gesture {
CGPoint translation = [gesture translationInView:self.view];
CGPoint newLocation = CGPointMake(self.player.position.x + translation.x, self.player.position.y - translation.y);
self.player.position = [self checkBounds:newLocation];
}
For some reason, my player is going off screen. I think my use of the MIN & MAX macros may be wrong, but I am not sure.
Exactly, you mixed up MIN/MAX. The line MIN(x, 0) will return the lower value of x or 0, meaning the result will be 0 or less.
At one line you're using -returnValue.y which makes no sense.
You can (and should for readability) omit the if/else because MIN/MAX, if used correctly, make if/else unnecessary here.
I'm trying to develop an App with an "Around Me"-like feature of a location list with small directional arrows on the side.
Bearing and offset to the different locations hadn't been a problem thanks to Stackoverflow and compensating the compass-lag did well with following tutorial:
http://www.sundh.com/blog/2011/09/stabalize-compass-of-iphone-with-gyroscope/
All the stuff works fine with only one location in that UITableView.
But when there are more than one location, the arrows won't turn smooth and it feels like my iPhone isn't fast enough for calculating the stuff and turning these multiple arrows but I don't know how to do that better.
At the moment I'm trying this (without the locations specific directional offset):
I'm saving all the UIImageViews of all the cells in an array
when getting a new yaw value I loop through the array an actualize all the Images Rotation
if(motionManager.isDeviceMotionAvailable) {
// Listen to events from the motionManager
motionHandler = ^ (CMDeviceMotion *motion, NSError *error) {
CMAttitude *currentAttitude = motion.attitude;
float yawValue = currentAttitude.yaw; // Use the yaw value
// Yaw values are in radians (-180 - 180), here we convert to degrees
float yawDegrees = CC_RADIANS_TO_DEGREES(yawValue);
currentYaw = yawDegrees;
// We add new compass value together with new yaw value
yawDegrees = newCompassTarget + (yawDegrees - offsetG);
// Degrees should always be positive
if(yawDegrees < 0) {
yawDegrees = yawDegrees + 360;
}
compassDif.text = [NSString stringWithFormat:#"Gyro: %f",yawDegrees]; // Debug
float gyroDegrees = (yawDegrees*radianConst);
// If there is a new compass value the gyro graphic animates to this position
if(updateCompass) {
[self setRotateArrow:gyroDegrees animated:YES];
[self commitAnimations];
updateCompass = 0;
} else {
[self setRotateArrow:gyroDegrees animated:NO];
[UIView commitAnimations];
}
};
and the setRotateArrow:animated method:
- (void) setRotateArrow:(float)degrees animated:(BOOL)animated{
UIImage *arrowImage = [UIImage imageNamed:#"DirectionArrow.png"];
for (int i = 0; i<arrowImageViews.count; i++) {
[(UIImageView *)[arrowImageViews objectAtIndex:i] setImage:arrowImage];
CGFloat arrowTransform = degrees;
//Rotate the Arrow
CGAffineTransform rotate = CGAffineTransformMakeRotation(arrowTransform);
[(UIImageView *)[arrowImageViews objectAtIndex:i] setTransform:rotate];
}
}
If anyone got an idea how to get the arrows rotation following smoothly the device rotation I would be very thankful.