iPhone: Sorting based on location - iphone

I have been working on an iPhone app, where-in i have list of users in a NSMutableArray like below.
myMutableArray: (
{
FirstName = Getsy;
LastName = marie;
Latitude = "30.237314";
Longitude = "-92.461008";
},
{
FirstName = Angel;
LastName = openza;
Latitude = "30.260329";
Longitude = "-92.450414";
},
{
FirstName = Sara;
LastName = Hetzel;
Latitude = "30.2584499";
Longitude = "-92.4135357";
}
)
I need to sort users based on the location who is nearby to my location by calculating latitude and longitude. I am not able to achieve this till now. Could someone help me on giving some samples?
UPDATED: I am trying like below as per Mr.sch suggested. Please check my updated code. Is it fine?.
NSArray *orderedUsers = [myMutableArray sortedArrayUsingComparator:^(id a,id b) {
NSArray *userA = (NSArray *)a;
NSArray *userB = (NSArray *)b;
CGFloat aLatitude = [[userA valueForKey:#"Latitude"] floatValue];
CGFloat aLongitude = [[userA valueForKey:#"Longitude"] floatValue];
CLLocation *participantALocation = [[CLLocation alloc] initWithLatitude:aLatitude longitude:aLongitude];
CGFloat bLatitude = [[userA valueForKey:#"Latitude"] floatValue];
CGFloat bLongitude = [[userA valueForKey:#"Longitude"] floatValue];
CLLocation *participantBLocation = [[CLLocation alloc] initWithLatitude:bLatitude longitude:bLongitude];
CLLocation *myLocation = [[CLLocation alloc] initWithLatitude:locationCoordinates.latitude longitude:locationCoordinates.longitude];
CLLocationDistance distanceA = [participantALocation distanceFromLocation:myLocation];
CLLocationDistance distanceB = [participantBLocation distanceFromLocation:myLocation];
if (distanceA < distanceB) {
return NSOrderedAscending;
} else if (distanceA > distanceB) {
return NSOrderedDescending;
} else {
return NSOrderedSame;
}
}];
Thank you!

NSArray *orderedUsers = [users sortedArrayUsingComparator:^(id a,id b) {
User *userA = (User *)a;
User *userB = (User *)b;
CLLocationDistance distanceA = [userA.location getDistanceFromLocation:myLocation];
CLLocationDistance distanceB = [userB.location getDistanceFromLocation:myLocation];
if (distanceA < distanceB) {
return NSOrderedAscending
} else if (distanceA > distanceB) {
return NSOrderedDescending;
} else {
return NSOrderedSame;
}
}];

First thing, you will need to calculate the distance between your current location and the location of each other user.
Talking mathematically, here is a Wolfram|Alpha example
Now "programmatic-ally", you can use CLLocation class, here is an example:
(CLLocationDistance)getDistanceFrom:(const CLLocation *)location
But first you will need to create the location object from your Latitude and Longitude. You can use:
(id)initWithLatitude:(CLLocationDegrees)latitude longitude:(CLLocationDegrees)longitude

You can calculate the distance (geographical, not flat plane!) between your position and each of these items' positions and order by that value.

- (void)viewDidLoad {
[super viewDidLoad];
// This Array Taken Globally
List_of_locationsArray =[[NSMutableArray alloc]initWithObjects:
#{#"latitude" : #"17.415045",#"logitude":#"78.421424"} ,#{#"latitude" : #"17.415045",#"logitude":#"78.421424"},#{#"latitude" : #"17.415045",#"logitude":#"78.421424"},#{#"latitude" : #"17.415045",#"logitude":#"78.421424"},#{#"latitude" : #"17.415045",#"logitude":#"78.421424"}
,nil];
}
-(void)sortingLocationsArray{
// CLLocation* currentLocation =[[CLLocation alloc]initWithLatitude:[currentLatitude doubleValue] longitude:[currentLogitude doubleValue]];
CLLocation* currentLocation =[[CLLocation alloc]initWithLatitude: currentLatitudeHere longitude:CurrentLogHere];
NSMutableArray* tempLocationsArr = [[NSMutableArray alloc]initWithCapacity:[locationsArray count]];
for (int i=0; i<[locationsArray count]; i++) {
CLLocationDegrees latValue = [[locationsArray[i] objectForKey:#"latitude"] doubleValue];
CLLocationDegrees longValue = [[locationsArray[i] objectForKey:#"logitude"] doubleValue];
CLLocation* location = [[CLLocation alloc]initWithLatitude:latValue longitude:longValue];
[tempLocationsArr addObject:location];
NSArray* sortLocationArry = [tempLocationsArr sortedArrayUsingComparator:^NSComparisonResult(CLLocation* location1, CLLocation* location2) {
CLLocationDistance distA = [location1 distanceFromLocation:currentLocation];
CLLocationDistance distB = [location2 distanceFromLocation:currentLocation];
if (distA < distB) {
return NSOrderedAscending;
} else if ( distA > distB) {
return NSOrderedDescending;
} else {
return NSOrderedSame;
}
}];
//ArrayAfterSorting is another mutable Array to Store Sorting Data
[ArrayAfterSorting removeAllObjects];
[sortLocationArry enumerateObjectsUsingBlock:^(CLLocation* location, NSUInteger idx, BOOL *stop) {
NSMutableDictionary *tempDict = [[NSMutableDictionary alloc]init];
[tempDict setObject:[NSString stringWithFormat:#"%f",location.coordinate.latitude] forKey:#"latitude"];
[tempDict setObject:[NSString stringWithFormat:#"%f",location.coordinate.longitude] forKey:#"logitude"];
[ArrayAfterSorting addObject:tempDict];
}];
NSLog(#"sortedArray : %#", ArrayAfterSorting);
}
}

You may solve your problem in following way
1)Firstly store all above values in separate array Like latArray ,longArray,nameArray
2)Now get the distance between location(longArray,latArray) from your current location.
Then store these distances in separate Array(distanceArray).
//getDistance Between currentLocation and desired Location
-(void *)getDistanceFromCurrentLocation{
for(int val=0;val<[latArray count];val++){
NSString *dis_From_Current_Location;
dis_From_Current_Location =nil;
CGFloat str_Longitude=[[defaults objectForKey:#"long"]floatValue];
CGFloat str_Latitude=[[defaults objectForKey:#"lati"]floatValue];
//Suppose this is your current location
CGFloat lat1= [[latArray objectAtIndex:val]floatValue];
//these array for lat
CGFloat long1=[[longArray objectAtIndex:val]floatValue];
//these array for longArray
CLLocation *location1 = [[CLLocation alloc] initWithLatitude:lat1 longitude:long1];
CLLocation *location2 = [[CLLocation alloc] initWithLatitude:str_Latitude longitude:str_Longitude];
CLLocationDistance dist=[location1 distanceFromLocation:location2];
NSLog(#"Distance i meters: %f", [location1 distanceFromLocation:location2]);
long long v = llabs(dist/1000);
dis_From_Current_Location=[NSString stringWithFormat:#"%lld km",v];
[location1 release];
[location2 release];
[distanceArray addObject: dis_From_Current_Location];
//distanceArray is Global NsMutableArray.
}
}
Now You should Apply sorting method(selection, bubble) fro sorting the distances.
One thing need to care is that when you sort the distanceArray
please adjust values of nameArray as according to the distanceArray
See Below code for sorting the distanceArray and adjust the nameArray's value.
-(void)getSoretdArray{
NSString * tempStr,*tempStr2;
for(int i=0;i<[distanceArray count]; i++){
for(int j=i+1;j<[distanceArray count]; j++){
if([distanceArray objectAtIndex:j]>[distanceArray objectAtIndex:j+1]){
tempStr=[distanceArray objectAtIndex:j];
NSString* str= [distanceArray objectAtIndex:j+1];
[ distanceArray insertObject:str atIndex:j];
[distanceArray insertObject:tempStr atIndex:j+1] ;
//also change the name of corresponding location.
//you have to adjust the stored names in namArray for storing names of Corresponding Distances
tempStr2=[nameArray objectAtIndex:j];
NSString* str1= [nameArray objectAtIndex:j+1];
[ nameArray insertObject:str1 atIndex:j];
[nameArray insertObject:tempStr2 atIndex:j+1] ;
}
}
}
}
This will definitely work just try to use carefully

Related

How to add elements to an array created by a 'for' loop ?

I am using for loop for get distance from current location to destination location. I would like an array that contains all distances from current to destination location.
for (i = 0; i < [Arr_Lat count]; i++)
{
NSString *latst1 = [[NSString alloc]initWithFormat:[Arr_Lat objectAtIndex:i]];
NSString *longst1 = [[NSString alloc]initWithFormat:[Arr_Long objectAtIndex:i]];
NSLog(#"First lat : %#",latst1);
NSLog(#"First ong : %#",longst1);
double Doblat1 = [latst1 doubleValue];
double Doblong1 = [longst1 doubleValue];
CLLocationCoordinate2D coord1 = CLLocationCoordinate2DMake(coord1.latitude = Doblat1, coord1.longitude = Doblat1);
NSLog(#" Coordinat ==== : %f -- %f",coord1.latitude,coord1.longitude) ;
CLLocation *currLoc = [[CLLocation alloc] initWithLatitude:appDel.curr_lat longitude:appDel.curr_long];
CLLocation *destLoc1 = [[CLLocation alloc] initWithLatitude:Doblat1 longitude:Doblong1];
NSLog(#" Currennt Location : %#", currLoc);
NSLog(#" Destination Location : %#" , destLoc1);
distance = [destLoc1 distanceFromLocation:currLoc];
NSLog(#" Distance : ------- %0.3f", distance);
DistStr = [[NSString alloc]initWithFormat:#" %f",distance];
[currLoc release];
[destLoc1 release];
[Arr_title retain];
[tbl_nearplace reloadData];
}
If you want to store the distance you need an NSMutableArray.
Declare a NSMutableArray *distanceArray; in class scope.
Initialize it: distanceArray = [[NSMutableArray alloc] init];
In the for loop after DistStr =[[NSString alloc]initWithFormat:#" %f",distance];
write the following code:
[distanceArray addObject:DistStr];
Say we have this array with stuff we want to iterate over and add those items to another array
NSArray *someArrayWithStuff = #[#"Hello",
#"Its",
#"Very",
#"Cold",
#"Outside",
#"Today"];
Say we want the content of someArrayWithStuff to be added to this other array so we create an NSMutableArray
NSMutableArray *theNewArrayWithOurStuff = [NSMutableArray array];
We loop through the someArrayWithStuff
for (int i = 0; i < someArrayWithStuff.count; i++) {
// Add object to the new array
[theNewArrayWithOurStuff addObject:[someArrayWithStuff objectAtIndex:i]];
}
NSMutableArray *Distance=[[NSMutableArray alloc]Init];
for (i = 0; i < [Arr_Lat count]; i++)
{
NSString *latst1 = [[NSString alloc]initWithFormat:[Arr_Lat objectAtIndex:i]];
NSString *longst1 = [[NSString alloc]initWithFormat:[Arr_Long objectAtIndex:i]];
NSLog(#"First lat : %#",latst1);
NSLog(#"First ong : %#",longst1);
double Doblat1 = [latst1 doubleValue];
double Doblong1 = [longst1 doubleValue];
CLLocationCoordinate2D coord1 = CLLocationCoordinate2DMake(coord1.latitude = Doblat1, coord1.longitude = Doblat1);
NSLog(#" Coordinat ==== : %f -- %f",coord1.latitude,coord1.longitude) ;
CLLocation *currLoc = [[CLLocation alloc] initWithLatitude:appDel.curr_lat longitude:appDel.curr_long];
CLLocation *destLoc1 = [[CLLocation alloc] initWithLatitude:Doblat1 longitude:Doblong1];
NSLog(#" Currennt Location : %#", currLoc);
NSLog(#" Destination Location : %#" , destLoc1);
distance = [destLoc1 distanceFromLocation:currLoc];
NSLog(#" Distance : ------- %0.3f", distance);
NSString *DistStr = [[NSString alloc]initWithFormat:#" %f",distance];
[Distance addObject:DistStr];
[DistStr release];
[currLoc release];
[destLoc1 release];
[Arr_title retain];
[tbl_nearplace reloadData];
}

Sort a NSMutableArray of location with my GPS position

I want to sort a NSMutableArray, where each row is a NSMutableDictionary, with my GPS position from CoreLocation framework.
This is an example of my array of POI
arrayCampi = (
{
cap = 28100;
"cell_phone" = "";
championship = "IBL 1D";
citta = Novara;
division = "";
email = "";
fax = 0321457933;
indirizzo = "Via Patti, 14";
latitude = "45.437174";
league = "";
longitude = "8.596029";
name = "Comunale M. Provini";
naz = Italy;
prov = NO;
reg = Piemonte;
sport = B;
surname = "Elettra Energia Novara 2000";
telefono = 03211816389;
webaddress = "http://www.novarabaseball.it/";
})
I need to sort this array with my location (lat and long) with field 'latitude' and 'longitude' of each row in ascending mode (first row is POI nearest to me).
I have tried this solution without success:
+ (NSMutableArray *)sortBallparkList:(NSMutableArray *)arrayCampi location:(CLLocation *)myLocation {
if ([arrayCampi count] == 0) {
return arrayCampi;
}
if (myLocation.coordinate.latitude == 0.00 &&
myLocation.coordinate.longitude == 0.00) {
return arrayCampi;
}
NSMutableArray *sortedArray = [NSMutableArray arrayWithArray:arrayCampi];
BOOL finito = FALSE;
NSDictionary *riga1, *riga2;
while (!finito) {
for (int i = 0; i < [sortedArray count] - 1; i++) {
finito = TRUE;
riga1 = [sortedArray objectAtIndex: i];
riga2 = [sortedArray objectAtIndex: i+1];
CLLocationDistance distanceA = [myLocation distanceFromLocation:
[[CLLocation alloc]initWithLatitude:[[riga1 valueForKey:#"latitude"] doubleValue]
longitude:[[riga1 valueForKey:#"longitude"] doubleValue]]];
CLLocationDistance distanceB = [myLocation distanceFromLocation:
[[CLLocation alloc]initWithLatitude:[[riga2 valueForKey:#"latitude"] doubleValue]
longitude:[[riga2 valueForKey:#"longitude"] doubleValue]]];
if (distanceA > distanceB) {
[riga1 retain];
[riga2 retain];
[sortedArray replaceObjectAtIndex:i+1 withObject:riga2];
[sortedArray replaceObjectAtIndex:i withObject:riga1];
[riga1 release];
[riga2 release];
finito = FALSE;
}
}
}
return sortedArray;
}
Can anyone help me, also with other solution?
Alex.
Sorting by lat and long will not give you the nearest location from any given coordinates. As an approximation*) you could use Pythagoras (you learned that in high school, remember?):
float distance = sqrtf(powf((origLat-destLat),2)+powf((origLon-destLon), 2));
Simply add that to your dictionary with key #"distance" and sort with
NSArray *sorted = [arrayOfDictionaries sortedArrayUsingDescriptors:
#[[NSSortDescriptor sortDescriptorWithKey:#"distance" ascending:YES]]];
*) It's an approximation because theoretically distance between two points is a curved line on the surface of an ellipsoid.
[arrayCampi sortedArrayUsingSelector:#selector(compare:)];
- (NSComparisonResult)compare:(NSDictionary *)otherObject {
if ([[self objectForKey:#"key"] isEqual:[otherObject objectForKey:#"key"]]) {
return NSOrderedSame;
}
else if (//condition) {
return NSOrderedAscending;
}
else {
return NSOrderedDescending;
}
}
Take a look at How to sort an NSMutableArray with custom objects in it?
I think there's no need to implement your own sorting algorithm. There are the ready ones out there :-) I would suggest to look at NSSortDescriptor.
And since you keep your geo coordinates in NSString format, and not the NSNumber, you probably would need to write your own NSPredicate for NSString objects comparison in your class. (I don't remember if #"123" is greater than #"1.23", I mean special symbol '.')

iPhone distance from current location as subtitle

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

CLLocation causing EXC_BAD_ACCESS

This code works some of the time, but then sometimes crashes with a EXC_BAD_ACCESS error.
NSInteger compareDistance(id num1, id num2, void *context)
{
int rv;
//cast parameters from general type
CLLocation* location = (__bridge_transfer CLLocation *)context;
Attraction* attr1 = num1;
Attraction* attr2 = num2;
//convert to CLLocation objects and calculate distance from user current
CLLocation* locationAttr1 =
[[CLLocation alloc] initWithLatitude:attr1.latitude
longitude:attr1.longitude];
CLLocation* locationAttr2 =
[[CLLocation alloc] initWithLatitude:attr2.latitude
longitude:attr2.longitude];
CLLocationDistance distance1 = [locationAttr1 distanceFromLocation:location];
CLLocationDistance distance2 = [locationAttr2 distanceFromLocation:location];
//compare and rate
if (distance1 < distance2)
rv = NSOrderedAscending;
else if (distance1 > distance2)
rv = NSOrderedDescending;
else
rv = NSOrderedSame;
return rv;
}
This is a compare function for ordering an NSMutableArray:
-(NSMutableArray *)getAttractionsByDistanceInCategory:(int)catID
{
[self confirmAttractions];
//set up array and context to prepare for sort
NSMutableArray *attractionsToSort = [[NSMutableArray alloc] init];
for (Attraction *a in attractions)
{
if ((catID < 0) || (catID >= 0 && a.category == catID))
[attractionsToSort addObject:a];
}
CLLocation* currentLocation =
[[CLLocation alloc] initWithLatitude:usersLat longitude:usersLng];
//conduct sort
[attractionsToSort sortUsingFunction:compareDistance
context:(__bridge_retained void *)currentLocation];
return attractionsToSort;
}
Hmm. Do you think the problem could be ARC? I'm side-eyeing that __bridge_retained void * business.
Thanks in advance for any advice!
__bridge_transfer transfers ownership to the destination object, which is probably not what you want to do here, you don't want ownership of the context, because ARC will try and release it when it thinks you are done with it, and you don't want it to do that.
Basically, you don't want to transfer ownership anywhere, so just use __bridge in both cases, and it should work fine.

how to use sortUsingFunction:context

appdata.items is NSMutableArray.
I connot compile This code.
Error code is "prop.173 has an incomplete type".
NSInteger compareInfo(id aInfo1, id aInfo2, void *context){
NSDate* info1 = [aInfo1 objectAtIndex:2];
NSDate* info2 = [aInfo2 objectAtIndex:2];
return [info1 compare:info2];
}
-(void)saveData{
NSData* data = [[NSMutableData alloc] init];
appdata.items = [appdata.items sortUsingFunction:compareInfo context:NULL];
NSKeyedArchiver* archiver = [[NSKeyedArchiver alloc] initForWritingWithMutableData:data];
[archiver encodeObject:appdata forKey:DATAKEY];
[archiver finishEncoding];
[data writeToFile:[self dataFilePath] atomically:YES];
[archiver release];
[data release];
}
Here is some code that might help you, (I spend pretty much time making it working, doc is not really helpfull)
It computes the distance between POI object and a currentLocation and order POI Object from the closest to the fartest from this location
NSInteger compareDistance(id num1, id num2, void *context)
{
int retour;
// fist we need to cast all the parameters
CLLocation* location = context;
POI* param1 = num1;
POI* param2 = num2;
// then we can use them as standard ObjC objects
CLLocation* locationCoordinates = [[CLLocation alloc] initWithLatitude:location.coordinate.latitude longitude:location.coordinate.longitude];
CLLocation* locationPOI1 = [[CLLocation alloc] initWithLatitude:param1.coords.latitude longitude:param1.coords.longitude];
CLLocation* locationPOI2 = [[CLLocation alloc] initWithLatitude:param2.coords.latitude longitude:param2.coords.longitude];
CLLocationDistance distance1 = [locationPOI1 distanceFromLocation:locationCoordinates];
CLLocationDistance distance2 = [locationPOI2 distanceFromLocation:locationCoordinates];
//make the comparaison
if (distance1 < distance2)
retour = NSOrderedAscending;
else if (distance1 > distance2)
retour = NSOrderedDescending;
else
retour = NSOrderedSame;
[locationCoordinates release];
[locationPOI1 release];
[locationPOI2 release];
return retour;
}
-(void) orderByProximityFromLocation:(CLLocationCoordinate2D) coordinates
{
CLLocation* currentLocation = [[CLLocation alloc] initWithLatitude:coordinates.latitude longitude:coordinates.longitude];
[listOfPOI sortUsingFunction:compareDistance context:currentLocation];
[currentLocation release];
}
The method sortUsingFunction:context: is for NSMutableArray, not NSArray, which sorts the contents of the mutable array. The method you want is sortedArrayUsingFunction:context: which will return a sorted array which you can assign, as you're currently trying to do.
Unless, of course, items is an NSMutableArray, in which case you can call sortUsingFunction:context: but as it doesn't return anything, so you don't assign it to items.