I am developing iOS app, in which i want to find all locations within a some radius.
Is there any way in objective-c that will allow me to specify a fixed radius and a location, and that will tell me which locations are within that radius ?
I have done some researching and i got this code snippet,
- (void)locationManager:(CLLocationManager *)manager didUpdateToLocation:(CLLocation *)newLocation fromLocation:(CLLocation *)oldLocation
{
CLGeocoder *geocoder = [[CLGeocoder alloc] init];
[geocoder reverseGeocodeLocation:locationManager.location
completionHandler:^(NSArray *placemarks, NSError *error)
{
NSLog(#"reverseGeocodeLocation:completionHandler: Completion Handler called!");
if (error)
{
NSLog(#"Geocode failed with error: %#", error);
return;
}
CLLocationDistance radius = 30;
CLLocation* target = [[CLLocation alloc] initWithLatitude:51.5028 longitude:0.0031];
NSArray *locationsWithinRadius = [placemarks objectsAtIndexes:
[placemarks indexesOfObjectsPassingTest:
^BOOL(id obj, NSUInteger idx, BOOL *stop) {
return [(CLLocation*)obj distanceFromLocation:target] < radius;
}]];
NSLog(#"locationsWithinRadius=%#",locationsWithinRadius);
}];
but it gets crashes and shows error:
Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[CLPlacemark distanceFromLocation:]:
Am i going on right way ? is this a way, to find all locations from my specified locations ?
Thanks in advance.
EDIT:
NSArray *testLocations = #[[[CLLocation alloc] initWithLatitude:19.0759 longitude:72.8776]];
CLLocationDistance maxRadius = 3000; // in meters
CLLocation *targetLocation = [[CLLocation alloc] initWithLatitude:newLocation.coordinate.latitude longitude:newLocation.coordinate.longitude]; //Current location coordinate..
NSPredicate *predicate = [NSPredicate predicateWithBlock:^BOOL(CLLocation *testLocation, NSDictionary *bindings) {
return ([testLocation distanceFromLocation:targetLocation] <= maxRadius);
}];
NSArray *closeLocations = [testLocations filteredArrayUsingPredicate:predicate];
NSLog(#"closeLocations=%#",closeLocations);
When i log my closeLocations array it shows null(Empty) value. Coordinate which i provided in testLocations is near to my current location.
What you're trying to do in your code is geocoding, this is the process of translating coordinates to addresses and isn't what you want to do. Instead you need to more basic coordinate bounding. You could use the distanceFromLocation: method in your code above and just iterate through your coordinates, converting them into CLLocation objects (if they aren't already) and then checking the distance to your center point.
Rather than using indexesOfObjectsPassingTest, I'd probably use filteredArrayUsingPredicate and a predicate created with predicateWithBlock to do your distance check (unless you actually want the indices for some reason).
NSArray *testLocations = #[ [[CLLocation alloc] initWithLatitude:11.2233 longitude:13.2244], ... ];
CLLocationDistance maxRadius = 30; // in meters
CLLocation *targetLocation = [[CLLocation alloc] initWithLatitude:51.5028 longitude:0.0031];
NSPredicate *predicate = [NSPredicate predicateWithBlock:^BOOL(CLLocation *testLocation, NSDictionary *bindings) {
return ([testLocation distanceFromLocation:targetLocation] <= maxRadius);
}];
NSArray *closeLocations = [testLocations filteredArrayUsingPredicate:predicate];
Related
I am working on this walking/running app which calculates distance in meters and records lat-long for further use. Now when I calculate distance I get incorrect distance every time. I have compared it with other running apps and they generally show different distance than my distance.
Here is the code that I am using:
#define kDesiredAccuracy 5.0f
self.locationManager = [[CLLocationManager alloc] init];
self.locationManager.desiredAccuracy = kCLLocationAccuracyBestForNavigation;
self.locationManager.distanceFilter = kDesiredAccuracy;
_routes = [[NSMutableArray alloc] init];
lastKnownLocation = nil;
-(void)locationManager:(CLLocationManager *)manager didUpdateToLocation:(CLLocation *)newLocation fromLocation:(CLLocation *)oldLocation
{
// return if accuracy is less than 0 or is greater than desired accuracy.
if (newLocation.horizontalAccuracy < 0)
{
return;
}
if(newLocation.horizontalAccuracy > kDesiredAccuracy)
{
return;
}
NSTimeInterval locationAge = -[newLocation.timestamp timeIntervalSinceNow];
CLLocationSpeed speed = [newLocation speed];
// return if stale data or user is not moving
if (locationAge > 5.0 || speed <= 0) return;
//return if first location is found
if(lastKnownLocation == nil)
{
lastKnownLocation = newLocation;
return;
}
CLLocationDistance distance = [newLocation distanceFromLocation:(self.pramDistance > 0)?lastKnownLocation:oldLocation];
if(distance > 0)
{
// save distance for future use
NSMutableDictionary *dict=[[NSMutableDictionary alloc] init];
[dict setObject:[NSString stringWithFormat:#"%g", newLocation.coordinate.latitude] forKey:#"latitude"];
[dict setObject:[NSString stringWithFormat:#"%g", newLocation.coordinate.longitude] forKey:#"longtitude"];
[dict setObject:[NSString stringWithFormat:#"%f",distance] forKey:#"distance"];
[_routes addObject:dict];
// add distance to total distance.
self.pramDistance += distance;
}
}
Once user finishes walking/running I draw rout of walk/run on map view. For this purpose I simply draw a ploy line over MKMapView using all the recorded locations.
The map view shows zig-zag line for route and distance is always incorrect. Please suggest me where I am doing wrong and what should I amend to make it work proper?
Here is the comparison (left one is other's app and right one is mine):
Try this,
Import CLLocationManagerDelegate,
CLLocation *currentLocation = self.currentLocation;
float distanceMile = [currentLocation distanceFromLocation:[[CLLocation alloc] initWithLatitude:latitude longitude:longitude]]/1609.34;
-(void)postCurrentLocationOfDevice
{
self.locationManager = [[CLLocationManager alloc] init];
self.locationManager.distanceFilter = kCLDistanceFilterNone;
self.locationManager.desiredAccuracy = kCLLocationAccuracyBest;
[self.locationManager startUpdatingLocation];
self.locationManager.delegate = self;
}
-(void)locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray *)locations
{
self.currentLocation = [locations objectAtIndex:0];
[self.locationManager stopUpdatingLocation];
}
-(void)locationManager:(CLLocationManager *)manager didUpdateToLocation:(CLLocation *)newLocation fromLocation:(CLLocation *)oldLocation
{
self.currentLocation = newLocation;
[self.locationManager stopUpdatingLocation];
}
Though I didn't get much of the help here, I found some help and idea in this question: Corelocation incorrect distances
What I did to fix the problem is:
I stored recent 5 locations in an array.
Once 5 items are stored in the array, I checked for the nearest location.
After getting nearest location I calculated distance between last best location and stored new location in last best location.
P.S.: My code is messy and naming conventions are not that generic, that's why I am not posting my code here.
I am to reverse Geo Code Latitude and Longitude of some location and want to get address of that location. I have done it through google web service but it takes time.
I want to know if there is some other good and efficient approach.
Currently calling this service,
NSString * getAddress = [NSString stringWithFormat:#"http://maps.googleapis.com/maps/api/geocode/json?latlng=%#,%#&sensor=true",Lattitude,Longitude];
You can use CLGeocoder:
[self.geocoder reverseGeocodeLocation:location completionHandler:^(NSArray *placemarks, NSError *error){
CLPlacemark *placemark = placemarks[0];
NSLog(#"Found %#", placemark.name);
}];
This will still take time though, since both methods use web services to convert lat / long into a place
Try This code .
geoCoder = [[CLGeocoder alloc]init];
[self.geoCoder reverseGeocodeLocation: locationManager.location completionHandler:
//Getting Human readable Address from Lat long,,,
^(NSArray *placemarks, NSError *error) {
//Get nearby address
CLPlacemark *placemark = [placemarks objectAtIndex:0];
//String to hold address
NSString *locatedAt = [[placemark.addressDictionary valueForKey:#"FormattedAddressLines"] componentsJoinedByString:#", "];
//Print the location to console
NSLog(#"I am currently at %#",locatedAt);
}];
Have a look at GLGeocoder. Specifically reverseGeocodeLocation:completionHandler:
You can use Apple's CLGeocoder (part of CoreLocation). Specifically, the – reverseGeocodeLocation:completionHandler: method which will return a dictionary of address data for the given coordinates.
Have a look at this tutorial, or, if just want something to copy quickly: NSArray *addressOutput;
CLLocation *currentLocation;
//assumes these instance variables
// Reverse Geocoding
CLGeocoder *geocoder = [[CLGeocoder alloc] init];
[geocoder reverseGeocodeLocation:currentLocation completionHandler:^(NSArray *placemarks, NSError *error) {
NSLog(#"Found placemarks: %#, error: %#", placemarks, error);
if (error == nil && [placemarks count] > 0) {
NSMutableArray *tempArray = [[NSMutableArray alloc] initWithCapacity:[placemarks count]];
for (CLPlacemark *placemark in placemarks) {
[tempArray addObject:[NSString stringWithFormat:#"%# %#\n%# %#\n%#\n%#",
placemark.subThoroughfare, placemark.thoroughfare,
placemark.postalCode, placemark.locality,
placemark.administrativeArea,
placemark.country]];
}
addressOutput = [tempArray copy];
}
else {
addressOutput = nil;
NSLog(#"%#", error.debugDescription);
}
}];
Based off the code in the tutorial.
If you to not want to use google API, try this code - basically transforms latitude and longitude inputs into ZIPs (can be adjusted to adresses).
pip install uszipcode
# Import packages
from uszipcode import SearchEngine
search = SearchEngine(simple_zipcode=True)
from uszipcode import Zipcode
import numpy as np
#define zipcode search function
def get_zipcode(lat, lon):
result = search.by_coordinates(lat = lat, lng = lon, returns = 1)
return result[0].zipcode
#load columns from dataframe
lat = df_shooting['Latitude']
lon = df_shooting['Longitude']
#define latitude/longitude for function
df = pd.DataFrame({'lat':lat, 'lon':lon})
#add new column with generated zip-code
df['zipcode'] = df.apply(lambda x: get_zipcode(x.lat,x.lon), axis=1)
#print result
print(df)
#(optional) save as csv
#df.to_csv(r'zip_codes.csv')
I am trying to find distance from selectedAnnotation to userLocation. I added the following code in the annotation NSObject:
-(void) setDistanceFromCurrentLocation:(CLLocation *)currentLocation{
CLLocation *location2 = [[CLLocation alloc] initWithLatitude:self.latitude longitude:self.longitude];
[self setDistance:[currentLocation distanceFromLocation:location2]];
}
- (NSString *)subtitle
{
NSString *myDistance = [NSString stringWithFormat:#"%1.1f from current location", distance];
return myDistance;
}
Now in the didUpdatedidUpdateToLocation I tried using the following logic from this question: https://stackoverflow.com/a/10881683/984248
Still getting 0.0 back. What am I doing wrong?
EDIT:
So I am calculating the distance from current location correctly now. But how do I pass this on to set it as the subtitle to a pin?
Here is how I am finding distance:
- (void)locationManager:(CLLocationManager *)manager didUpdateToLocation:(CLLocation *)newLocation fromLocation:(CLLocation *)oldLocation {
[self setCurrentLocation:newLocation];
// if not Current Location then update the currently displayed Dealer Annotation
for (int i=0; i<self.dataArray.count; i++){
NSDictionary *dataDictionary = [self.dataArray objectAtIndex:i];
NSArray *array = [dataDictionary objectForKey:#"Locations"];
for (int i=0; i<array.count; i++){
NSMutableDictionary *dictionary = [array objectAtIndex:i];
CLLocation *pinLocation = [[CLLocation alloc] initWithLatitude:[[dictionary objectForKey:#"Latitude"] doubleValue] longitude:[[dictionary objectForKey:#"Longitude"] doubleValue]];
[?????? setDistance:[self.currentLocation distanceFromLocation:pinLocation]];
}
}
}
Here is how I am adding pins to the map:
for (int i=0; i<self.dataArray.count; i++){
NSDictionary *dataDictionary = [self.dataArray objectAtIndex:i];
NSArray *array = [dataDictionary objectForKey:#"Locations"];
for (int i=0; i<array.count; i++){
NSMutableDictionary *dictionary = [array objectAtIndex:i];
MapAnnotation *annotation = [[MapAnnotation alloc] init];
annotation.latitude = [[dictionary objectForKey:#"Latitude"] doubleValue];
annotation.longitude = [[dictionary objectForKey:#"Longitude"] doubleValue];
CLLocationCoordinate2D coord = {.latitude =
annotation.latitude, .longitude = annotation.longitude};
MKCoordinateRegion region = {coord};
annotation.title = [dictionary objectForKey:#"Name"];
annotation.subtitle = ?????;
annotation.coordinate = region.center;
//Saving the dictionary of the pin to show contact info later
annotation.sourceDictionary = dictionary;
[mapView addAnnotation:annotation];
}
}
Just wondering, is currentLocation set to self.latitude,self.longitude?
in which case you would be trying to find the distance from yourself. Try logging the latitude and longitude values of the currentLocation parameter and the self.latitude and self.longitude variables, and make sure they are different.
If they are, then try logging [currentLocation distanceFromLocation:location2] to see if it is non zero, if it is, then your setDistance method is the problem.
Other than that all I can think of is your distance variable is getting set to 0 somewhere else, or it is a variable that does not format with %1.1f so the formatter is setting it to 0.0
I have a MKMapView, and I would like to know how I can find the nearest 5 annotations to the user, and only display them on the MKMapView.
My code currently is:
- (void)loadFiveAnnotations {
NSString *string = [[NSString alloc] initWithContentsOfURL:url];
string = [string stringByReplacingOccurrencesOfString:#"\n" withString:#""];
NSArray *chunks = [string componentsSeparatedByString:#";"];
NSArray *keys = [NSArray arrayWithObjects:#"type", #"name", #"street", #"address1", #"address2", #"town", #"county", #"postcode", #"number", #"coffeeclub", #"latlong", nil];
// max should be a multiple of 12 (number of elements in keys array)
NSUInteger max = [chunks count] - ([chunks count] % [keys count]);
NSUInteger i = 0;
while (i < max)
{
NSArray *subarray = [chunks subarrayWithRange:NSMakeRange(i, [keys count])];
NSDictionary *dict = [[NSDictionary alloc] initWithObjects:subarray forKeys:keys];
// do something with dict
NSArray *latlong = [[dict objectForKey:#"latlong"] componentsSeparatedByString:#","];
NSString *latitude = [[latlong objectAtIndex:0] stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
NSString *longitude = [[latlong objectAtIndex:1] stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
CLLocationDegrees lat = [latitude floatValue];
CLLocationDegrees longi = [longitude floatValue];
Annotation *annotation = [[Annotation alloc] initWithCoordinate:CLLocationCoordinate2DMake(lat, longi)];
annotation.title = [dict objectForKey:#"name"];
annotation.subtitle = [NSString stringWithFormat:#"%#, %#, %#",[dict objectForKey:#"street"],[dict objectForKey:#"county"], [dict objectForKey:#"postcode"]];
[mapView addAnnotation:annotation];
[dict release];
i += [keys count];
}
}
A long answer, already mostly written when Stephen Poletto posted and containing example code on how to use the built-in methods for sorting an array, so I though it was still worth posting though the essential answer is the same (ie, "pick the five closest for yourself, pass only those on"):
You're going to need to sort your annotations by distance for yourself, and submit only the closest five to the MKMapView. If you have two CLLocations then you can get the distance between them using the distanceFromLocation: method (which was getDistanceFrom: prior to iOS 3.2; that name is now deprecated).
So, for example, supposing your Annotation class had a method 'setReferenceLocation:' to which you pass a CLLocation and a getter 'distanceFromReferenceLocation' which returns the distance between the two, you could do:
// create and populate an array containing all potential annotations
NSMutableArray *allPotentialAnnotations = [NSMutableArray array];
for(all potential annotations)
{
Annotation *annotation = [[Annotation alloc]
initWithCoordinate:...whatever...];
[allPotentialAnnotations addObject:annotation];
[annotation release];
}
// set the user's current location as the reference location
[allPotentialAnnotations
makeObjectsPerformSelector:#selector(setReferenceLocation:)
withObject:mapView.userLocation.location];
// sort the array based on distance from the reference location, by
// utilising the getter for 'distanceFromReferenceLocation' defined
// on each annotation (note that the factory method on NSSortDescriptor
// was introduced in iOS 4.0; use an explicit alloc, init, autorelease
// if you're aiming earlier)
NSSortDescriptor *sortDescriptor =
[NSSortDescriptor
sortDescriptorWithKey:#"distanceFromReferenceLocation"
ascending:YES];
[allPotentialAnnotations sortUsingDescriptors:
[NSArray arrayWithObject:sortDescriptor]];
// remove extra annotations if there are more than five
if([allPotentialAnnotations count] > 5)
{
[allPotentialAnnotations
removeObjectsInRange:NSMakeRange(5,
[allPotentialAnnotations count] - 5)];
}
// and, finally, pass on to the MKMapView
[mapView addAnnotations:allPotentialAnnotations];
Depending on where you're loading from, you made need to create a local store (in memory or on disk) for annotations and select the five nearest whenever the user moves. Either register yourself as a CLLocationManager delegate or key-value observe on the map view's userLocation property. If you have quite a lot of potential annotations then sorting all of them is a bit wasteful and you'd be better advised to use a quadtree or a kd-tree.
First you'll need to grab the user's current location. You can build a CLLocationManager and register yourself as the delegate for location updates as follows:
locationManager = [[[CLLocationManager alloc] init] autorelease];
[locationManager setDelegate:self];
[locationManager startUpdatingLocation];
After setting yourself as the delegate, you'll receive the following callback:
- (void)locationManager:(CLLocationManager *)manager didUpdateToLocation:(CLLocation *)newLocation fromLocation:(CLLocation *)oldLocation
Now that you have the user's location (newLocation), you can find the five closest annotations. There is a handy method in CoreLocation:
- (CLLocationDistance)distanceFromLocation:(const CLLocation *)location
As you're iterating through your annotations, just store the five nearest locations. You can build a CLLocation out of the 'lat' and 'longi' variables you have using:
- (id)initWithLatitude:(CLLocationDegrees)latitude longitude:(CLLocationDegrees)longitude
Hope this helps!
I use this code to set a label with a location string
locationString = [[NSString alloc] initWithFormat:#"%#%# - %# %#%#",
thoroughfare,subThoroughfare,postalCode,
locality,countryCode];
locationLabel.text = locationString;
where thoroughfare, subThoroughfare, postalCode, locality,countryCode are obtained from a placemark.
Now, I'd like to visualize this string according the current locale. Have I specify a string format for each locale in which I'm interested or there is a simpler way to obtain this?
Thanks,
Fran
you can use following function
-(void) setLocation:(NSString *)latitude withLongitude:(NSString *)longitude {
CLGeocoder *geocoder = [[CLGeocoder alloc] init];
CLLocation *location = [[CLLocation alloc] initWithLatitude:[latitude doubleValue] longitude:
longitude doubleValue]];
CLGeocodeCompletionHandler completionHandler = ^ (NSArray *placemarks, NSError *error){
if (error){
NSLog(#"error in fetching location <%#>",error);
return ;
}
if ( placemarks && placemarks.count >0){
CLPlacemark *mark = [placemarks objectAtIndex:0];
NSString *addresstring = [[mark addressDictionary] objectForKey:#"FormattedAddressLines"] componentsJoinedByString:#","];
*//fetched addressDictionary for key FormattedAddressLines*
}
The addressDictionary property of the placemark object should resolve in part the problem with its FormattedAddressLines array.