I have defined a class called FGLocation that conforms to the MKAnnotation protocol, what I am trying to do is measure the distance between two of these objects. I noticed that I could use the method distanceFromLocation: that is defined as a method for the CLLocation class. As you can see below I am creating two temp CLLocation objects to do the calculation, but I can't help thinking that I am maybe missing a better / easier way. Does anyone have any comments on what I am doing or how I might do it better?
// IS THERE A BETTER WAY TO DO THIS?
FGLocation *root = [annotations objectAtIndex:counter-1];
FGLocation *leaf = [annotations objectAtIndex:counter];
CLLocation *rootLocation = [[CLLocation alloc] initWithLatitude:[root coordinate].latitude longitude:[root coordinate].longitude];
CLLocation *leafLocation = [[CLLocation alloc] initWithLatitude:[leaf coordinate].latitude longitude:[leaf coordinate].longitude];
CLLocationDistance thisDistance = [rootLocation distanceFromLocation:leafLocation];
[rootLocation release];
[leafLocation release];
NB: my FGLocation object is defined as (see below) I noticed in the docs that I should not be subclassing CLLocation.
#interface FGLocation : NSObject <MKAnnotation> {
I think that what you are doing is a fair approach, with the possible exception that you might want to implement some sort of caching mechanism/threshold for recalculation if you are doing this often or on a lot of points. Calculating distances accurately involves non-Euclidean geometry and is computationally expensive.
http://en.wikipedia.org/wiki/Haversine_formula
Depending on where the points are located on the globe and whether or not precision is highly important, you can also fudge things by pretending to be working in a Euclidean space and leveraging the Pythagorean theorem. I have used this kind of scheme to create a gross filter over a large dataset and then resorted to more precise calculations on points that pass the filter.
Related
Ive got an app that plots mkannotations (i hope i get my terminology right...its kinda confusing) on a mapview.
I have already included the subtitle for when you tap on them.
I have been looking online for a way to include the distance in those callouts but im not quite there yet. I ran across two partial solutions and Im wondering if they should be combined.
First, I didnt have CoreLocation added to my project, I need it right? To be constantly updating my user location and be able to calculate the distances to each point? Or does Mapkit somehow include a user location data that I can use?
Partial Solution A uses this code:
`-(void)locationManager:(CLLocationManager *)manager
didUpdateToLocation:(CLLocation *)newLocation
fromLocation:(CLLocation *)oldLocation {
if(!newLocation) return;
if ((oldLocation.coordinate.latitude != newLocation.coordinate.latitude) &&
(oldLocation.coordinate.longitude != newLocation.coordinate.longitude)){
CLLocation *loc1 = [[CLLocation alloc] initWithLatitude:oldLocation.coordinate.latitude longitude:oldLocation.coordinate.longitude];
CLLocation *loc2 = [[CLLocation alloc] initWithLatitude:newLocation.coordinate.latitude longitude:newLocation.coordinate.longitude];
CLLocationDistance distance = ([loc2 distanceFromLocation:loc1]) * 0.000621371192;
//distance = distance;
NSLog(#"Total Distance %f in miles",distance);
}
}
I understand this method calculates the distance between 2 points. I would somehow need to cycle thru my annotations and create the distance. It seems this would be the more useful one since it constantly recalculates the distances based on the current userLocation. Although, I do wonder about the effectiveness of that. Once you know how far away something is, you rarely wish to be constantly reminded as to how far away it is.
Partial Solution B uses this code:
`- (void)mapView:(MKMapView *)mapView annotationView:(MKAnnotationView *)view calloutAccessoryControlTapped:(UIControl *)control{
CLLocation *pinLocation = [[[CLLocation alloc] initWithLatitude:[(MyAnnotation*)[view annotation] coordinate].latitude longitude:[(MyAnnotation*)[view annotation] coordinate].longitude]];
CLLocation *userLocation = [[CLLocation alloc] initWithLatitude:self._mapView.userLocation.coordinate.latitude longitude:self._mapView.userLocation.coordinate.longitude];
CLLocationDistance distance = [pinLocation distanceFromLocation:userLocation];
NSLog(#"Distance to pin %4.0f", distance);
}
`
In this case, whenever the pin is tapped, the distance is calculated. But Im unclear as to the code for MyAnnotation [view annotation], Im guessing the original poster had his locations based off of a MyAnnotation Class so I changed it to MyLocation and all but 1 error went away. I get an Expected Identifier error at the pinLocation line at the last square bracket for some reason.
I feel the solution is in the tip of my tongue. Just need that little extra push :)
Thanks guys
Just move the code inside the calloutAccessoryControlTapped method right after the line, wherever you have it, that creates the MKAnnotation. Give MKAnnotation subclass a float distance property and set it as the subtitle.
Currently i am working on a Location based application for iPhone/iPad . I have several annotations in my MapKit , what i want to do is to track the location of the user and shows the annotations that are within the 3km . Can somebody give me a start ?
Sorry for the delayed response... the question just fell off my radar.
I'm going to suppose that you have a method that returns a set of NSValue-wrapped CLLocationCoordinate2D structs (the basic approach is the same regardless of what your internal data representations are). You can then filter the list using a method something akin to the following (warning: typed in browser):
NSSet *locations = ...;
CLLocation centerLocation = ...; // Reference location for comparison, maybe from CLLocationManager
CLLocationDistance radius = 3000.; // Radius in meters
NSSet *nearbyLocations = [locations objectsPassingTest:^(id obj, BOOL *stop) {
CLLocationCoordinate2D testCoordinate;
[obj getValue:&testCoordinate];
CLLocation *testLocation = [[CLLocation alloc] initWithLatitude:testCoordinate.latitude
longitude:testCoordinate.longitude];
BOOL returnValue = ([centerLocation distanceFromLocation:testLocation] <= radius);
[testLocation release];
return returnValue;
}
];
With the filtered set of coordinates in hand, you can create MKAnnotation instances and add them to the map in the usual manner, as described in Apple's documentation.
If you have many thousands of test locations then I suppose this approach could start to incur performance issues. You would then want to switch your point storage approach to use, e.g., quadtrees, to reduce the number of points that need to be precision-filtered. But don't optimize prematurely!
Hope that helps!
I need to calculate the distance between two points in iOS. I can guarantee at least an iPhone 4 so the picture quality on the camera should be good. The idea is to calculate the distance to a point using the picture. There's an app called easyMeasure which does exactly what I need to do.
I'm ok with Pythagoras but this boggles my mind. How would I do something like this?
Ok, so you were correct in that you need to use sine and such. First though, you'll need to find the lens angle of the iPhones camera. Do do this, put the camera a known distance away from the wall and measure how far it is from the edge of the field of vision to the other side and divide by two. To find θ in the picture below, use tanθ = opposite/adjacent, so inverse tan(opposite/adjacent) = θ.
Once you know that, you just have the user take a picture, and give a measurement for how big something on the screen really is. Then just use tanθ = opposite/adjacent, and since you now know θ and the opposite distance, adjacent = opposite/tanθ.
Hope that helps!
New update in ios7
#import CoreLocation;
#import MapKit;
CLLocation *sanFrancisco = [[CLLocation alloc] initWithLatitude:37.775 longitude:-122.4183333];
CLLocation *portland = [[CLLocation alloc] initWithLatitude:45.5236111 longitude:-122.675];
CLLocationDistance distance = [portland distanceFromLocation:sanFrancisco];
MKDistanceFormatter *formatter = [[MKDistanceFormatter alloc] init];
formatter.units = MKDistanceFormatterUnitsImperial;
NSLog(#"%#", [formatter stringFromDistance:distance]); // 535 miles
I have been struggling with this for long. I read many resources but still not able to find a clear way around it.
I have a Sqlite table with rows of latitudes and longitudes. My task is to fetch those latitudes and longitudes and put them in an array and then use later for displaying on map with all the pins. I do these "reading from database" in my AppDelegate (is this advisable or is it better to do in view controller which has the map?)
I fetch the lat and long as double values as shown below
while (sqlite3_step(selectStmt)==SQLITE_ROW){
double latitude= sqlite3_column_double(selectStmt, 1);
double longitude= sqlite3_column_double(selectStmt, 2);
CLLocationCoordinate2D coord=CLLocationCoordinate2DMake(latitude,longitude);
// I want to add this to an array, so that i can use later for annotations
}
However when i try to add "Incompatible type for argument 1 off addObject".
Is this the right way of fetching multiple coordinates from sqlite to display on maps?
Help would be appreciated
You need to use CLLocation class to store your location data.
http://developer.apple.com/library/ios/#DOCUMENTATION/CoreLocation/Reference/CLLocation_Class/CLLocation/CLLocation.html#//apple_ref/doc/uid/TP40007126
CLLocation *location = [[CLLocation alloc] initWithLatitude:latitude longitude:longitude];
[array addObject:location];
[location release];
CLLocationCoordinate2D is a struct not an object and you need objects for addObject methods.
You can use the CLLocation as Evgeniy suggest or create your own object to store/retrieve those values.
I'm trying to create then retrieve an array of CLLocationCoordinate2D objects, but for some reason the array is always empty.
I have:
NSMutableArray *currentlyDisplayedTowers;
CLLocationCoordinate2D new_coordinate = { currentTowerLocation.latitude, currentTowerLocation.longitude };
[currentlyDisplayedTowers addObject:[NSData dataWithBytes:&new_coordinate length:sizeof(new_coordinate)] ];
I've also tried this for adding the data:
[currentlyDisplayedTowers addObject:[NSValue value:&new_coordinate withObjCType:#encode(struct CLLocationCoordinate2D)] ];
And either way, the [currentlyDisplayedTowers count] always returns zero. Any ideas what might be going wrong?
Thanks!
To stay in object land, you could create instances of CLLocation and add those to the mutable array.
CLLocation *towerLocation = [[CLLocation alloc] initWithLatitude:lat longitude:lon];
[currentDisplayedTowers addObject:towerLocation];
To get the CLLocationCoordinate struct back from CLLocation, call coordinate on the object.
CLLocationCoordinate2D coord = [[currentDisplayedTowers lastObject] coordinate];
As SB said, make sure your array is allocated and initialized.
You’ll also probably want to use NSValue wrapping as in your second code snippet. Then decoding is as simple as:
NSValue *wrappedCoordinates = [currentlyDisplayedTowers lastObject]; // or whatever object you wish to grab
CLLocationCoordinate2D coordinates;
[wrappedCoordinates getValue:&coordinates];
You need to allocate your array.
NSMutableArray* currentlyDisplayedTowers = [[NSMutableArray alloc] init];
Then you can use it. Be sure to call release when you are done with it or use another factory method.
I had currentlyDisplayedTowers = nil which was causing all the problems. Also, the previous advice to init and alloc were necessary. Thanks everyone for the help!
For anyone else with this issue, there's another solution if you are planning on using MapKit.
(The reason I say IF, of course, is because importing a module such as MapKit purely for a convenient wrapper method is probably not the best move.. but nonetheless here you go.)
#import MapKit;
Then just use MapKit's coordinate value wrapper whenever you need to:
[coordinateArray addObject:[NSValue valueWithMKCoordinate:coordinateToAdd]];
In your example..
[currentlyDisplayedTowers addObject:[NSValue valueWithMKCoordinate:new_coordinate]];