I'm still learning objective C and iOS and I'm running into a problem. I am creating an array from CoreData that contains latitudes and longitudes. I want to take this array and sort it by the closest location.
This is what I have so far:
NSError *error = nil;
NSFetchRequest *getProjects = [[NSFetchRequest alloc] init];
NSEntityDescription *projectsEntity = [NSEntityDescription entityForName:#"TimeProjects" inManagedObjectContext:context];
[getProjects setEntity:projectsEntity];
projectArray = [[context executeFetchRequest:getProjects error:&error] mutableCopy];
for (NSObject *project in projectArray) {
// Get location of house
NSNumber *lat = [project valueForKey:#"houseLat"];
NSNumber *lng = [project valueForKey:#"HouseLng"];
CLLocationCoordinate2D coord;
coord.latitude = (CLLocationDegrees)[lat doubleValue];
coord.longitude = (CLLocationDegrees)[lng doubleValue];
houseLocation = [[CLLocation alloc] initWithLatitude:coord.latitude longitude:coord.longitude];
//NSLog(#"House location: %#", houseLocation);
CLLocationDistance meters = [houseLocation distanceFromLocation:currentLocation];
}
I also have this sorting code but I'm not sure how to put the two together.
[projectArray sortUsingComparator:^NSComparisonResult(id o1, id o2) {
CLLocation *l1 = o1, *l2 = o2;
CLLocationDistance d1 = [l1 distanceFromLocation:currentLocation];
CLLocationDistance d2 = [l2 distanceFromLocation:currentLocation];
return d1 < d2 ? NSOrderedAscending : d1 > d2 ? NSOrderedDescending : NSOrderedSame;
}];
Can some one help me out with making these two things work together?
Your sortUsingComparator block expects CLLocation objects, not instances of your
Core Data class. That would be easy to fix, but what I would recommend is:
Add a transient property currentDistance to your entity. (Transient properties are not stored in the persistent store file.) The type should be "Double".
After fetching the objects, compute the currentDistance for all objects in projectArray.
Finally sort the projectArray array, using a sort descriptor on the currentDistance key.
The advantage is that the distance to the current location is calculated only once for each object, and not calculated repeatedly in the comparator method.
The code would look like this (not compiler checked!):
NSMutableArray *projectArray = ... // your mutable copy of the fetched objects
for (TimeProjects *project in projectArray) {
CLLocationDegrees lat = [project.houseLat doubleValue];
CLLocationDegrees lng = [project.houseLng doubleValue];
CLLocation *houseLocation = [[CLLocation alloc] initWithLatitude:lat longitude:lng];
CLLocationDistance meters = [houseLocation distanceFromLocation:currentLocation];
project.currentDistance = #(meters);
}
NSSortDescriptor *sort = [NSSortDescriptor sortDescriptorWithKey:#"currentDistance" ascending:YES]
[projectArray sortUsingDescriptors:#[sort]];
Alternatively, you can make currentDistance a persistent property of the entity and calculate it when the object is created or modified. The advantage is that you could add
a sort descriptor based on currentDistance to the fetch request instead of fetching
first and sorting afterwards. The disadvantage is of course that you have to re-calculate
all values when the current location changes.
Related
I am working on a project in which I have to show the distance of multiple locations from one location. locations are based on latitude and longitude.
I am using the following code to get the distance between two locations is shows nearly same distance
CLLocation *locationA = [[CLLocation alloc] initWithLatitude:28.6379 longitude: 77.2432];CLLocation *locationB = [[CLLocation alloc] initWithLatitude:28.6562 longitude:77.2410];CLLocationDistance distance = [locationA distanceFromLocation:locationB];NSLog(#"Distance is %f",distance);float i = distance/1000;NSLog(#"distance between two places is %f KM", i);
but now i am struct to get the distance of multiple locations from my location: locationA.
for example I take NSArray for latitude and longitude as
NSArray * latitudeArray = [[NSArray alloc]initWithObjects:#"28.6129",#"28.6020",#"28.5244", nil];NSArray * longitudeArray = [[NSArray alloc]initWithObjects:#"77.2295",#"77.2478",#"77.1855", nil];
Please help me to resolve it..
Take locationA as one location..
Please help me to sort the Array by nearest Distance..
First of all, don't create two array for latitude and longitude, It should be one array of CLLocations.
NSMutableArray locationsArray = [[NSMutableArray alloc] init];
//This is just for example, You should add locations to this array according to format of data you have available.
[locationsArray addObject:[[CLLocation alloc] initWithLatitude:28.6379 longitude:77.2432]];
[locationsArray addObject:[[CLLocation alloc] initWithLatitude:28.6020 longitude:77.2478]];
[locationsArray addObject:[[CLLocation alloc] initWithLatitude:28.5244 longitude:77.1855]];
Now, Take some reference location,
CLLocation *yourLocationA ; //set whatever value you have..
You can sort array of location with following.
[locationsArray sortUsingComparator:^NSComparisonResult(CLLocation *obj1Location,CLLocation *obj2Location) {
CLLocationDistance obj1Distance = [obj1Location distanceFromLocation: yourLocationA];
CLLocationDistance obj2Distance = [obj2Location distanceFromLocation: yourLocationA];
return (obj1Distance > obj2Distance);
}];
I have a map app that allows the User to save annotations as a favorite to a mutable array. All favorite annotations can then be displayed when the User chooses to.
Annotations added to the mutable array are of the MKPointAnnotation class. I can correctly add annotations to the mutable array, but I haven't come up with a working solution that correctly removes a specific annotation from the mutable array. How can a specific annotation be removed from the mutable array that contains multiple annotations that were saved as favorites? Several non-working solutions are found in my sample code.
//** Correctly adds a favorite annotation to the mutable array favoriteAnnotationsArray **
-(void)addToFavoriteAnnotationsArray{
MKPointAnnotation *annotation = [[MKPointAnnotation alloc] init];
NSArray *components = [favoritesString componentsSeparatedByString:#","];
annotation.coordinate = CLLocationCoordinate2DMake([components[1] doubleValue], [components[0] doubleValue]);
annotation.title = [components[2] stringByTrimmingCharactersInSet:NSCharacterSet.whitespaceAndNewlineCharacterSet];
[self.favoriteAnnotationsArray addObject:annotation];
}
//** Need to remove a favorite annotation from the mutable array favoriteAnnotationsArray **
-(void)removeObjectFromFavoriteAnnotationsArray{
MKPointAnnotation *annotation = [[MKPointAnnotation alloc] init];
NSArray *components = [favoritesString componentsSeparatedByString:#","];
annotation.coordinate = CLLocationCoordinate2DMake([components[1] doubleValue], [components[0] doubleValue]);
annotation.title = [components[2] stringByTrimmingCharactersInSet:NSCharacterSet.whitespaceAndNewlineCharacterSet];
//** Used in first non-working solution below **
//NSMutableArray *objectToRemoveArray = [[NSMutableArray alloc] init];
//[objectToRemoveArray addObject:annotation];
//** The following three lines didn't remove any annotations from array **
//[self.favoriteAnnotationsArray removeObjectsInArray:objectToRemoveArray];
//[self.favoriteAnnotationsArray removeObject:annotation];
//[self.favoriteAnnotationsArray removeObjectIdenticalTo:annotation];
//** This only removes the last object in array and not necessarily the correct annotation to remove **
[self.favoriteAnnotationsArray removeLastObject];
}
You will need to specify an unique annotation from the favoriteAnnotationsArray in order for it o be removed successfully.
Maybe you can try something as follows:
-(void)removeAnnotationFromFavoriteAnnotationsArrayWithTitle: (NSString *) titleString {
for(int i=0; i<self.favoriteAnnotationsArray.count; i++) {
MKPointAnnotation *annotation = (MKPointAnnotation *)[self.favoriteAnnotationsArray objectAtIndex:i];
NSString * annotationTitle = annotation.title;
if([annotationTitle isEqualToString:titleString]) {
[self.favoriteAnnotationsArray removeObject:annotation];
break;
}
}
}
If title is not unique enough for you to differentiate between annotations, then you might consider subclassing MKAnnotation and add an unique property and pass it to the above function instead of the title.
One approach is to iterate through your annotation array and find the one to remove. For example:
- (void)addToFavoriteAnnotationsArray:(NSString *)string {
MKPointAnnotation *annotation = [[MKPointAnnotation alloc] init];
NSArray *components = [string componentsSeparatedByString:#","];
annotation.coordinate = CLLocationCoordinate2DMake([components[1] doubleValue], [components[0] doubleValue]);
annotation.title = [components[2] stringByTrimmingCharactersInSet:NSCharacterSet.whitespaceAndNewlineCharacterSet];
[self.favoriteAnnotationsArray addObject:annotation];
}
- (void)removeObjectFromFavoriteAnnotationsArray:(NSString *)string {
NSArray *components = [string componentsSeparatedByString:#","];
NSString *titleOfAnnotationToRemove = [components[2] stringByTrimmingCharactersInSet:NSCharacterSet.whitespaceAndNewlineCharacterSet];
CLLocationCoordinate2D coordinateOfAnnotationToRemove = CLLocationCoordinate2DMake([components[1] doubleValue], [components[0] doubleValue]);
for (NSInteger i = 0; i < self.favoriteAnnotationsArray.count; i++) {
id<MKAnnotation>annotation = self.favoriteAnnotationsArray[i];
if ([annotation.title isEqualToString:titleOfAnnotationToRemove] && coordinateOfAnnotationToRemove.latitude == annotation.coordinate.latitude && coordinateOfAnnotationToRemove.longitude == annotation.coordinate.longitude) {
[self.favoriteAnnotationsArray removeObjectAtIndex:i];
break;
}
}
}
Or if you wanted to match on title, alone, then remove the coordinates from the if statement above. Also note that I've added the string as a parameter to these methods. It's always better to pass a parameter to a method rather than relying upon some property.
Frankly, though, when the user is selecting one to remove, you probably don't want them to have to manually enter the name and coordinates again. You probably want them to pick one from the list. So you might show them a table view of the annotations and when they say they want to remove the third one, you'd do just pass the index to a method like this:
- (void)removeObjectFromFavoriteAnnotationsArray:(NSInteger)index {
[self.favoriteAnnotationsArray removeObjectAtIndex:index];
}
By the way, all of the above routines remove the annotation from your array. If you've also added this annotation to the map view, remember to remove it from there, too.
I've an array from core data and I'm trying to think how can I sort the array by the nearest distance:
for (int i=0; i<allTasks.count; i++) {
id singleTask = allTasks[i];
double latitude = [singleTask[#"latitude"] doubleValue];
double longitude = [singleTask[#"longitude"] doubleValue];
}
EDIT:
The distance between current location and all the locations in the array.
I know how to calculate the distance, I don't know how to sort them.
So do you want to sort your allTasks array?
The best thing to do would be to add a distance key/value pair to each singleTask object, holding a double NSNumber.
In a first pass, loop through your allTasks array, fetch each lat/long, use it to create a CLLocation, and use the CLLocation method distanceFromLocation: to calculate the distance between each location and your target (current?) location. Save the result into each singleTask object in your array.
Once your allTasks array contains a distance property, simply use one of the sort methods like sortUsingComparator to sort the array based on the distance value. (In the sortUsingComparator family of methods, you provide a comparator block that the system uses to compare pairs of objets. It then runs a sort algorithm on your array, using your comparator to decide on the sort order.
get the CLLocation for your currentPosition (this is done via CLLocationManager)
calculate the distances for each item and store distance+item as a Pair in a Dictionary
Sort Dictionary allKeys array with compare: selector
so
CLLocation *current = ...;
NSMutableDictionary *distsAndTasks [NSMutableDictionary dictionary];
for(id task in allTasks) {
CLLocation *taskLoc = [[CLLocation alloc] initWithLatitude:task.lat longitude:task.long];//!
CLLocationDistance dist = [taskLoc distanceFrom:current];
if(distsAndTasks[#(dist)]) {
NSMutableArray *equidstants = [distsAndTasks[#(dist)] mutableCopy];
[equidstants addObject:task];
distsAndTasks[#(dist)] = equidstants;
}
else {
distsAndTasks[#(dist)] = #[task];
}
}
NSArray *sortedDists = [distsAndTasks.allKeys sortedArrayUsingSelector:#selector(compare:)];
//the tasks can now be access in a sorted way
for(NSNumber *dist in sortedDists) {
NSArray *tasksAtDistance = distsAndTasks[dist];
NSLog(#"%#", tasksAtDistance);
}
You can calculate distance between two points like this
You can also try this https://stackoverflow.com/a/9104926/3151066 and define some way of calculating distance that will satisfy you as the comparison operator
This is my method
- (void)populateLocationsToSort {
//1. Get UserLocation based on mapview
self.userLocation = [[CLLocation alloc] initWithLatitude:self._mapView.userLocation.coordinate.latitude longitude:self._mapView.userLocation.coordinate.longitude];
//Set self.annotationsToSort so any new values get written onto a clean array
self.myLocationsToSort = nil;
// Loop thru dictionary-->Create allocations --> But dont plot
for (Holiday * holidayObject in self.farSiman) {
// 3. Unload objects values into locals
NSString * latitude = holidayObject.latitude;
NSString * longitude = holidayObject.longitude;
NSString * storeDescription = holidayObject.name;
NSString * address = holidayObject.address;
// 4. Create MyLocation object based on locals gotten from Custom Object
CLLocationCoordinate2D coordinate;
coordinate.latitude = latitude.doubleValue;
coordinate.longitude = longitude.doubleValue;
MyLocation *annotation = [[MyLocation alloc] initWithName:storeDescription address:address coordinate:coordinate distance:0];
// 5. Calculate distance between locations & uL
CLLocation *pinLocation = [[CLLocation alloc] initWithLatitude:annotation.coordinate.latitude longitude:annotation.coordinate.longitude];
CLLocationDistance calculatedDistance = [pinLocation distanceFromLocation:self.userLocation];
annotation.distance = calculatedDistance/1000;
//Add annotation to local NSMArray
[self.myLocationsToSort addObject:annotation];
**NSLog(#"self.myLocationsToSort in someEarlyMethod is %#",self.myLocationsToSort);**
}
//2. Set appDelegate userLocation
AppDelegate *myDelegate = [[UIApplication sharedApplication] delegate];
myDelegate.userLocation = self.userLocation;
//3. Set appDelegate mylocations
myDelegate.annotationsToSort = self.myLocationsToSort;
}
In the bold line, self.myLocationsToSort is already null. I thought setting a value to nil was cleaning it out basically, ready to be re-used? I need to do so because this method is called once on launch and a second time after an NSNotification is received when data is gotten from the web. If I call this method again from the NSNotification selector, the new web data gets written on top of the old data and it spits out an inconsistent mess of values :)
Setting it to nil removes the reference to that object. If you are using ARC and it is the last strong reference to that object, the system autoreleases the object and frees its memory. In either case, it does not "clean it out and be ready for re-use", you need to re-allocate and initialize your object. If you would rather just remove all of the objects, and assuming myLocationsToSort is an NSMutableArray you can just call
[self.myLocationsToSort removeAllObjects];
Otherwise you need to do
self.myLocationsToSort = nil;
self.myLocationsToSort = [[NSMutableArray alloc] init];
I'm building a fitness application which tracks user's movement using Core Location framework.
I'm saving the data using Core Data framework. Currently I have two entities; Workout and Location. Workout consists of these Location objects, which have latitude and longitude as their main attributes.
When I'm trying to create MKPolyLine from these Location objects it takes an awful lot of time on device.
- (void)createRouteLineAndAddOverLay
{
CLLocationCoordinate2D coordinateArray[[self.workout.route count]];
for (int i = 0; i < [self.workout.route count]; i++) {
CLLocationCoordinate2D coordinate;
coordinate.latitude = [[[self.workout.route objectAtIndex:i] latitude] doubleValue];
coordinate.longitude = [[[self.workout.route objectAtIndex:i] longitude] doubleValue];
coordinateArray[i] = coordinate;
}
self.routeLine = [MKPolyline polylineWithCoordinates:coordinateArray count:[self.workout.route count]];
[self.mapView addOverlay:self.routeLine];
[self setVisibleMapRect];
}
Could using of scalars make any performance improvement? Or should I try to filter out some of these locations points with some kind of algorithm when saving them?
Here are some suggestion for optimization:
First, you have count+1 calls to count (>2000). Store the count in a variable.
Second, in your loop you are repeatedly retrieving the data from the workout object. Try storing the route array before starting the loop.
Besides, if route is a to-many relationship from Workout to Location, it should result in a NSSet, not an array. I suspect that you are using NSOrderdSet, which could also affect your performance. It would perhaps be better to keep track of the order with a simple integer attribute.
The Trick here is to do the sorting on the database side.
- (void)createRouteLineAndAddOverLay
{
NSFetchRequest *request = [NSFetchRequest fetchRequestWithEntityName:#"Location"];
NSSortDescriptor *fromStartToEnd = [NSSortDescriptor sortDescriptorWithKey:#"distance" ascending:YES];
request.sortDescriptors = [NSArray arrayWithObject:fromStartToEnd];
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"workout = %#", self.workout];
request.predicate = predicate;
NSArray *locations = [self.workout.managedObjectContext executeFetchRequest:request error:NULL];
int routeSize = [locations count];
CLLocationCoordinate2D coordinateArray[routeSize];
for (int i = 0; i < routeSize; i++) {
CLLocationCoordinate2D coordinate;
coordinate.latitude = [[[locations objectAtIndex:i] latitude] doubleValue];
coordinate.longitude = [[[locations objectAtIndex:i] longitude] doubleValue];
coordinateArray[i] = coordinate;
}
self.routeLine = [MKPolyline polylineWithCoordinates:coordinateArray count:routeSize];
[self.mapView addOverlay:self.routeLine];
[self setVisibleMapRect];
}
This method took only 0.275954 seconds when workout had 1321 locations