Order results of CLGeoCoder geocodeAddressString by nearest location - ios

I'm sure that I can figure this out based on code that I have implemented in PHP in the past, but I was hoping that someone has either done this and can provide an example or knows of a way to use the iOS SDK to accomplish it.
Here's my relevant code:
CLGeocoder *geocoder = [[CLGeocoder alloc] init];
CLLocationDistance dist = _searchDistance;
CLLocationCoordinate2D point = self.locationManager.location.coordinate;
CLRegion *region = [[CLRegion alloc] initCircularRegionWithCenter:point radius:dist identifier:#"Hint Region"];
[geocoder geocodeAddressString:address inRegion:region completionHandler:^(NSArray *placemarks, NSError *error)
{
if( error )
{
// Push errors to the user
}
else
{
NSLog( [NSString stringWithFormat:#"Got results: %#", placemarks] );
}
}];
This pushes to the log something like the following:
(
"300 Oak St, Phoenix, OR 97535-5722, United States # <+42.26953820,-122.81554259> +/- 100.00m, region (identifier <+42.26953820,-122.81554106> radius 27.00) <+42.26953820,-122.81554106> radius 27.00m",
"300 Oak St, Ashland, OR 97520, United States # <+42.19955633,-122.71289484> +/- 100.00m, region (identifier <+42.19955633,-122.71289484> radius 138.42) <+42.19955633,-122.71289484> radius 138.42m",
"300 Oak St, Jacksonville, OR 97530, United States # <+42.31236366,-122.97179130> +/- 100.00m, region (identifier <+42.31236366,-122.97179130> radius 138.33) <+42.31236366,-122.97179130> radius 138.33m",
"300 Oak St, Central Point, OR 97502, United States # <+42.37422514,-122.91427182> +/- 100.00m, region (identifier <+42.37422514,-122.91427182> radius 138.29) <+42.37422514,-122.91427182> radius 138.29m",
"300 Oak St, Rogue River, OR 97537, United States # <+42.43621216,-123.16864522> +/- 100.00m, region (identifier <+42.43621216,-123.16864522> radius 138.24) <+42.43621216,-123.16864522> radius 138.24m"
)
In this case, the second result is the closest to my current location. I would like to have this list ordered by matches that are closest to my current location. I'm reasonably confident that I know how to do the math to figure out which location is closest, but I was hoping for a shortcut. Iterating through this array and sorting the results is going to be somewhat heavy, so I'd like to avoid that if possible.
EDIT (complete answer):
#Akash gave me the bit of information I was seeking, so I marked that answer as the correct one. If anyone is interested in sorting the results (rather than just getting the closest location), I put the unsorted results into an NSDictionary using the distance as a key, then used compare to sort the keys and then put the original placemark array elements into an output array ordered by the key to which they were associated in the NSDictionary. Here's the complete code (and the complete answer to my question):
-(NSMutableArray *)distanceSortPlacemarks:(NSArray *)inputArray fromLocation:(CLLocation *)originLocation
{
NSMutableArray *returnValue = [[NSMutableArray alloc] initWithCapacity:[inputArray count]];
NSMutableDictionary *sortDictionary = [[NSMutableDictionary alloc] initWithCapacity:[inputArray count]];
for( int i = 0; i < [inputArray count]; i++ )
{
CLPlacemark *currentPlacemark = [inputArray objectAtIndex:i];
[sortDictionary
setObject:currentPlacemark
forKey:[NSNumber numberWithDouble:[currentPlacemark.location distanceFromLocation:originLocation]]
];
}
NSArray *sortedKeys = [[sortDictionary allKeys] sortedArrayUsingSelector:#selector(compare:)];
for( int i = 0; i < [sortedKeys count]; i++ )
{
CLPlacemark *currentPlacemark = [sortDictionary objectForKey:[sortedKeys objectAtIndex:i]];
[returnValue insertObject:currentPlacemark atIndex:i];
}
return returnValue;
}
I know this could be a little tighter (some of the variables aren't absolutely necessary), but I tend to err on the side of making code easier to understand at quick glance.

Try getting distance of your current location from CLLocation in each CLPlacemark as follows :
for(int i = 0; i < placemarks.count; i++)
{
double distance = [placemarks[i].location distanceFromLocation:yourCurrentLocation];
if(i == 0)
{
indexOfClosestLocation = i;
distanceFromClosestLocation = distance;
}
else
{
if(distance < distanceFromClosestLocation)
{
distanceFromClosestLocation = distance;
indexOfClosestLocation = i;
}
}
}
After executing this code you have index of closest CLLocation in placemarks array and distance to it in meters.

Related

iOS : App crashes when zooming out a map

I have this situation where my app crashes when I zoom out the map.
The problem arises because of the large number of annotations that I'm adding. Please have a look at my code below :
- (void) plotUsersInMap
{
for (id<MKAnnotation> annotation in self.mapView.annotations) {
[self.mapView removeAnnotation:annotation];
}
NSUInteger count = //get total count
NSLog(#"count * %d", count);
for (int i = 0; i < count; i++)
{
NSNumber *latitude = //get latitude from json
NSNumber *longitude = //get longitude from json
CLLocationCoordinate2D coordinate;
coordinate.latitude = latitude.doubleValue;
coordinate.longitude = longitude.doubleValue;
#autoreleasepool {
MyLocation *annotation = [[MyLocation alloc] initWithName:#"test" coordinate:coordinate QuestionId:nil];
//annotations are added
[self.mapView addAnnotation:annotation];
}
}
}
Here I'm trying to add more than 400 pins which I think is the cause of crash [probably a memory leak!]. I would like to know if there is any way to add the pins one by one as I zoom out?
Map in initial stage, without any problem :
And when I zoom out :
Try clustering. Basically you group together annotations.
The code repo from the article I linked to: https://github.com/applidium/ADClusterMapView

CLLocation distanceFromLocation result is differ when I calculate

I have used CLLocation's distanceFromLocation: method to calculate distance from some location.
But it's result is slightly different when I calculate by using "Haversine formula".
- (CLLocationDistance)distanceFromCoordinate:(CLLocationCoordinate2D)fromCoord {
double earthRadius = 6371.0; // Earth's radius in Kilometers
// Get the difference between our two points then convert the difference into radians
double nDLat = RADIANS((fromCoord.latitude - self.coordinate.latitude));
double nDLon = RADIANS((fromCoord.longitude - self.coordinate.longitude));
double fromLat = RADIANS(self.coordinate.latitude);
double toLat = RADIANS(fromCoord.latitude);
double nA = pow ( sin(nDLat/2.0), 2 ) + cos(fromLat) * cos(toLat) * pow ( sin(nDLon/2.0), 2 );
double nC = 2.0 * atan2( sqrt(nA), sqrt( 1 - nA ));
double nD = earthRadius * nC;
return nD * 1000.0;
}
CLLocation * loc = [[CLLocation alloc] initWithLatitude:[location.latitude doubleValue]
longitude:[location.longitude doubleValue]];
CLLocationDistance dist = [userLocation distanceFromLocation:loc];
CLLocationDistance dist2 = [userLocation distanceFromCoordinate:loc.coordinate];
Why two values are different?
Should I init location object with horizontalAccuracy and verticalAccuracy?
Your results are different because you are using different code.
You don't say how different.
Totally different? There's a bug in your code.
Big differences for places close together? Maybe your formula has problems with rounding errors.
Differences that grow as places are further apart? Maybe your definition of distance is different. Should be the closest distance on a path along earth surface.
In general, Earth is not a sphere but a spheroid. Taking that into account is more difficult but gives more precise results.
It is slightly different because Earth's Radius is not exact 6371 km. Use the correct Earth's radius, may be you can get better results.
Use the official WGS84 earth radius:
6 378 137 meter
I remember that ios delivers exactly the same result.
Should I init location object with horizontalAccuracy and
verticalAccuracy?
No, for sure not. That attributes are hints how acurate the position might be.
Distance is calculated by latitude and longitude only.
There are not much formulas:
- haversine formula (a bit slower than law of cosines, otherwise fine)
- law of cosines (problematic on small distances if not using 64 bit precision)
- vicenties which i smore acurate, it uses an elipsoidal earth model.
I also got the same problem... so i used google webservice to calculate distance. use this method, you will get accurate distance
-(void)calculateDistance()
{
//http://maps.googleapis.com/maps/api/directions/json?origin=41.742964,-87.995971& destination=41.811511,-87.967923&mode=driving&sensor=false
NSString *LocationUrl = [NSString stringWithFormat:#"http://maps.googleapis.com/maps/api/directions/json?origin=%#,%#&destination=%#,%#&mode=driving&sensor=false",origin.latitude,origin.longitude,destination.latitude,destination.latitude];
NSLog(#"Location URL:%#",LocationUrl);
NSURL *finalurl = [NSURL URLWithString: LocationUrl];
NSLog(#"Final URL = %#",finalurl);
NSData *data = [NSData dataWithContentsOfURL:finalurl];
NSLog(#"Data:-%#",data);
NSError *error;
NSDictionary *json = [NSJSONSerialization JSONObjectWithData:data
options:kNilOptions error:&error];
// NSLog(#"josn data of location:%#",[json description]);
NSMutableDictionary *routes = [json objectForKey:#"routes"];
NSMutableArray *legs = [routes valueForKey:#"legs"];
NSMutableDictionary *newDistance =[legs valueForKey:#"distance"];
NSMutableArray distanceList =[[newDistance valueForKey:#"text"]objectAtIndex:0];
distance = [[distanceList objectAtIndex:0]floatValue];
NSLog(#"%.1f",distance);
}
Hope it will help you

CLGeocoder returns null results for city and state when using current location data

Hi I am trying to get the name of city and state using CLGeocoder. However, placemark.addressDictionary is returning me :
{
FormattedAddressLines = (
"South Atlantic Ocean"
);
Name = "South Atlantic Ocean";
Ocean = "South Atlantic Ocean";
}
and placemark is:
South Atlantic Ocean, South Atlantic Ocean # <-42.60533670,-21.93128480> +/- 100.00m, region CLCircularRegion (identifier:'<-41.51023865,-31.60774370> radius 4958095.65', center:<-41.51023865,-31.60774370>, radius:4958095.65m)
Also, NSLog of [placemark locality] and [placemark administrativeArea] shows both nil.
Also tried adding ABAdressBookUI , ABAdressBook framework and get the data as:
NSString *addressDict = ABCreateStringWithAddressDictionary(placemark.addressDictionary, NO); which returns an object with empty description.
or tried:
NSString *state = (NSString *)[placemark.addressDictionary objectForKey:kABPersonAddressStateKey]; which throws warning of incompatible pointer types.
If there is any possible solution or if I am missing something please let me know.
My code when current location button is tapped is as follows:
CLLocation *loc;
loc = [[CLLocation alloc] initWithLatitude:[KLocationManager sharedManager]._locationManager.location.coordinate.latitude longitude:[KLocationManager sharedManager]._locationManager.location.coordinate.longitude];
CLGeocoder* geocoder = [[CLGeocoder alloc] init];
[geocoder reverseGeocodeLocation:loc completionHandler:^(NSArray *placemarks, NSError *error) {
CLPlacemark* placemark = [placemarks objectAtIndex:0];
if (placemark) {
NSDictionary* dic = placemark.addressDictionary;
NSString* locName = [NSString stringWithFormat:#"%#, %#", [dic objectForKey:#"City"],[dic objectForKey:#"State"]];
CLLocation* loc = placemark.location;
self.returnLocationDict = #{#"name":locName, #"location":loc};
}
}];
What am I missing here?
One way of replicating what you're experiencing is to use a CLLocation with latitude and longitude of 0,0
I ran your code by using latitude:37.3318 longitude:-122.0312 (Infinite Loop..) and it worked as expected.
Your location manager is probably not returning what you expect.

Using Block sortedArrayUsingComparator:^(id a, id b)

In my project, I try to compare a known location to an inputted location by using block sortedArrayUsingComparator:^(id a, id b). I have a dictionary array called locationArray containing a lat long and a station number that corresponds to that lat long point. I try to compare each locationArray station to the inputted station. I do this by taking the absolute value of the difference between the two which gives me a distance. Then I try to sort locationArray based on the distance from the inputted station from closest to furthest away.
//locationArray
#define kStation #"station"
#define kLatitude #"latitude"
#define kLongitude #"longitude"
NSString *filePath = [[NSBundle mainBundle] pathForResource:#"499CSV" ofType:#"csv"];
NSString *csvString = [NSString stringWithContentsOfFile:filePath encoding:NSUTF8StringEncoding error:nil];
NSArray *locations = [csvString componentsSeparatedByCharactersInSet:[NSCharacterSet newlineCharacterSet]];
NSMutableArray *CSVArray = [NSMutableArray array];
NSCharacterSet *whiteSPNewLine = [NSCharacterSet whitespaceAndNewlineCharacterSet];
for (NSString * location in locations)
{
NSArray *components = [location componentsSeparatedByString:#","];
double latitude = [[components[0] stringByTrimmingCharactersInSet:whiteSPNewLine] doubleValue];
double longitude = [[components[1] stringByTrimmingCharactersInSet:whiteSPNewLine] doubleValue];
NSString *station = [components[2] stringByTrimmingCharactersInSet:whiteSPNewLine];
NSDictionary *dict = #{kLatitude: #(latitude),
kLongitude: #(longitude),
kStation: station};
[CSVArray addObject:dict];
}
NSLog(#"The contents of CSVArray = %#",[CSVArray description]);
{
latitude = "41.674364";
longitude = "-81.23700700000001";
station = 40150;
},
{
latitude = "41.67517";
longitude = "-81.235038";
station = 40763;
},
{
latitude = "41.673106";
longitude = "-81.24017499999999";
station = 39175;
}, ...
My block code that directly follows locationArray.
NSArray *orderedPlaces = [CSVArray sortedArrayUsingComparator:^(id a, id b) {
NSDictionary *dictA;
NSDictionary *dictB;
NSString *locA;
NSString *locB;
int distanceA;
int distanceB;
dictA = (NSDictionary *)a;
dictB = (NSDictionary *)b;
NSLog(#"dictA = %#", dictA);
NSLog(#"dictB = %#", dictB);
locA = [dictA objectForKey:kStation];
locB = [dictB objectForKey:kStation];
NSLog(#"locA = %#", locA);
NSLog(#"locB = %#", locB);
distanceA = abs(stationNumber-[locA intValue]);
distanceB = abs(stationNumber-[locB intValue]);
NSLog(#"distanceA = %d", distanceA);
NSLog(#"distanceB = %d", distanceB);
if (distanceA < distanceB) {
return NSOrderedAscending;
} else if (distanceA > distanceB) {
return NSOrderedDescending;
} else {
return NSOrderedSame;
}
}];
NSLog(#"The contents of array = %#",[orderedPlaces description]);
The block runs but it's not sorting locationsArray as intended. orderedPlaces returns an unsorted locationsArray. By running NSLOG on the block components, I see that it successfully recognizes locationsArray and creates the distance object. I must be missing something because I use the same code in a different part of my project where I compare locationsArray to the lat long of the user's location and it works well. Please help me identify the issue keeping it from working as intended.
*If you need any more information or clarification, just ask.
I don't understand your sort block. You're calculating distance A as abs(stationNumber-[locA intValue]).
What is stationNumber? Is that some fixed integer index? How can the difference between a fixed station number and the station numbers in your array have anything to do with distances?
It seems to me that your distance values should be
(target.lat - this_station.lat)^2 + (target.long - this_station.long)^2
That will give you the square of the pythagorean distance between your target point and one of the stations you are comparing. Then in your comparison you select the item who's distance squared is less.
You can skip the square root, because you're just comparing them. That will make your distance calculations much faster.
Try this:
NSArray *orderedPlaces = [CSVArray sortedArrayUsingComparator:^NSComparisonResult(id a, id b) {
^(id a, id b) means ^void (id a, id b) and your return value is ignored.

I need to get the penultimate result from a for loop

I have an array of waypoints stored which have latitude and longitude values. I have created a for loop to loop through the array and compare my current CLLocation positions latitude and longitude to find which of the waypoints i am closest to. I also need to get the second closest waypoint and store this ac s a CLLocation object as well but cannot get it working.
The logic would be something like this
am I closest
yes
move closest location to second closest
set as second closest
loop again to get the closest point
My code:
-(void)locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray *)locations{
//set latestlocation as the last object from the locations array
CLLocation *currentLocation = [locations lastObject];
//declare a closestpoint object
CLLocation *closestWayPointToCurrentLocation;
//declare a second closest point object
CLLocation *secondClosestWayPointToCurrentLocation;
//set the distance to a high number
float distance = 10000000;
float secondClosestWaypointDistance = 10000000;
//load in plist
NSString *plistName = [self.mapsInfo objectForKey:#"plistName"];
NSString *path = [[NSBundle mainBundle] pathForResource:#"Chester" ofType:#"plist"];
NSString *path = [[NSBundle mainBundle] pathForResource:plistName ofType:#"plist"];
//store in array called waypoints
NSArray *waypoints= [NSArray arrayWithContentsOfFile:path];
//declare a variable for locationNum (the waypoints)
int locationNum = 0;
for (NSDictionary *point in waypoints) {
CLLocation *waypointLocation = [[CLLocation alloc]initWithLatitude:[(NSNumber *)[point objectForKey:#"Lat"]floatValue] longitude:[(NSNumber *)[point objectForKey:#"Long"]floatValue]];
float waypointDistanceFromCurrentLocation = [currentLocation distanceFromLocation:waypointLocation];
//secondClosestWayPointToCurrentLocation = waypointLocation;
if(waypointDistanceFromCurrentLocation < distance) {
//todo: move the current closestWayPointToCurrentLocation into second postion
//update the second closest waypoint distance variable also with distance
distance = waypointDistanceFromCurrentLocation;
closestWayPointToCurrentLocation = waypointLocation;
if(closestWayPointToCurrentLocation == waypointLocation) {
}
}
else
{
//check against the second position
//if closer than second position, replace it with new waypoint with code similar to above
}
If you are sure the point you is always the penultimate then you can retrive it like this
int totalNumberOfWaypoints = [waypoints count];
//Get penultimate waypoints
NSDictionary *penultimateWaypoint = [waypoints objectAtIndex:(totalNumberOfWaypoints - 2)];
Why dont you just make a sorted array of points, sorted by distance in ascending order?
NSMutableArray *waypoints= [NSArray arrayWithContentsOfFile:path];
for (NSDictionary *point in waypoints) {
[waypoints sortUsingComparator: (NSComparisonResult) ^ (id obj1, id obj2) {
CLLocation *waypointLocation1 = [[CLLocation alloc]initWithLatitude:[(NSNumber *)[obj1 objectForKey:#"Lat"]floatValue] longitude:[(NSNumber *)[obj1 objectForKey:#"Long"]floatValue]];
CLLocation *waypointLocation2 = [[CLLocation alloc]initWithLatitude:[(NSNumber *)[obj2 objectForKey:#"Lat"]floatValue] longitude:[(NSNumber *)[obj2 objectForKey:#"Long"]floatValue]];
float distance1 = [currentLocation distanceFromLocation:waypointLocation1];
float distance2 = [currentLocation distanceFromLocation:waypointLocation2];
if (distance1 < distance2) {
return NSOrderedAscending;
}
else if (distance1 > distance2) {
return NSOrderedDescending
}
else
return NSOrderedSame;
}];
This way you can always have the closest point at [waypoints objectAtIndex:0] and second closest point at [waypoints objectAtIndex:1] and so on.
less work, better result IMHO

Resources