iOS MapKit show nearest annotations within certain distance - ios5

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!

Related

Adding an unknown number of annotations to MKMapView

So I am using the map to add points for each person that has an address in the contacts, and I am having a bit of a hard time figuring out how to set it up for the unknown number of contacts. right now the way I have it set up, it is only adding a pin for the last contact in the list. here is some code: address is a string that is set from addressProperty.
if(addressProperty != nil)
{
[location geocodeAddressString:address completionHandler:^(NSArray *placemarks, NSError *error) {
NSMutableArray *array = [[NSMutableArray alloc]init];
for(int a = 0; a < [placemarks count]; a++)
{
self.placeMarkData = [placemarks objectAtIndex:a];
[point setCoordinate: self.placeMarkData.location.coordinate];
[array addObject:point];
pin.animatesDrop = YES;
point.title = address;
[map addAnnotations:array];
}
}];
}
So when I run the app I can see the pin being set in each location and moving to the next location until it ends on the last locations in the list. How can I add a point for each? I am sure it is an easy solution, but it is eluding me right now.
There must be a loop around all of this to be going through all the contacts. Is location a CLGeocoder? You shouldn't set two geocodeAddressString functions going on the same geocoder, you'll need to initialize a new one for each geocode your doing (unless you know for sure the previous geocode, which is asynchronous, has finished).
Four other problems:
The point variable is the same one each time. As you change the coordinate you'll that point appear in different places on the map, but it's the same one so you'll never see more than on on the map at any time.
Because it is running asynchronously you should be writing and reading a variable outside the block. Right now two geocodes could get responses at the same time and one sets self.placeMarkData just as the other one is reading the location.coordinate from it.
What is pin used for?
Don't bother adding array to the map until you've finished filling it up. If you want the points on screen asap then add them individually to the map. Adding the entire array, then adding another thing to the array and adding the entire array again is a waste.
Seems like a simple bug in your code. Can you try moving the [map addAnnotations:array]; line outside of the for loop?

How to display and connect multiple locations with a route with annotations in an MKMapView inside an iPhone App?

I need to display a MKMapView with more than 4 locations with different Annotations and a route connecting the locations. I have tried to display the multiple locations inside a MKMapView but i still not able to find out on how to connect the locations with a route. I am also trying to get this checked if i have implemented it in a right way. I have created a "CLLocationCoordinate2D" and then added a lat and long similarly for 4 points. I have created a custom object which implements MKAnnotation and returning a location .
CLLocationCoordinate2D coordinate1 = CLLocationCoordinate2DMake(40.7180583 ,-74.007109);
CLLocationCoordinate2D coordinate2 = CLLocationCoordinate2DMake(40.716355 ,-74.006816);
CLLocationCoordinate2D coordinate3 = CLLocationCoordinate2DMake(40.715281 ,-74.005485);
CLLocationCoordinate2D coordinate4 = CLLocationCoordinate2DMake(40.71559 ,-74.003114);
AnnotationPoints *location1 = [[AnnotationPoints alloc] initWithCoordinate:coordinate1];
AnnotationPoints *location2 = [[AnnotationPoints alloc] initWithCoordinate:coordinate2];
AnnotationPoints *location3 = [[AnnotationPoints alloc] initWithCoordinate:coordinate3];
AnnotationPoints *location4 = [[AnnotationPoints alloc] initWithCoordinate:coordinate4];
NSArray *poiArray = [[NSArray alloc] initWithObjects:location1,location2,location3,location4,nil];
[mapView addAnnotations:poiArray];
//Inside the Annotation Class initWithCoordinate Method is implemented this way:-
-(id)initWithCoordinate:(CLLocationCoordinate2D) c{
coordinate=c;
NSLog(#"%f,%f",c.latitude,c.longitude);
return self;
}
My concern here is i need to create a Annotation Point for every Location. Is there any alternative that i can load all the points at a single place. And another difficulty here is the route connecting all the multiple points. Any help on this? Thanks a lot
The way you are adding the annotations is fine.
Not sure what your concern is and what you mean by "all the points at a single place".
If you want pins/annotations at several places, you have to create a separate annotation object for each place.
Drawing a route connecting those locations requires creating an overlay (not an "annotation").
You want to add an MKPolyline to the map for which you will specify the list of coordinates.
To draw the polyline, you don't need to also add annotations at each coordinate (but you could if you want to).
Creating and adding an MKPolyline and its corresponding MKPolylineView is very similar to MKPolygon and MKPolygonView. See this question for an example:
iPhone MKMapView - MKPolygon Issues

read latitude and longitude from sqlite iphone

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.

Detecting when a user scrolls MKMapView a certain distance?

I want to determine if a user has scrolled more than a certain percentage of the map then disable centering of the map from the user location (similar to how the Maps app works).
I'm not sure which methods to make use of.
I think it would be straightforward to create a rectangle and see if the rectangle contains the current center point, however I have to target IOS 3, so I can't make use of many of the newer Mapkit apis.
I've tried futzing with CLLocation, and using distanceFrom, between the current mapcenter, and the users location, but I'm trying to figure out if that distance is a certain percentage.
I personally find it more helpful when someone can post a snippet of code versus general prose about how one might go about this. Here's what I came up with- roughly hacked out to simply better answer this question:
In a header file I have:
#define SCROLL_UPDATE_DISTANCE 80.00
and in my view (that is both a delegate for CLLocationManagerDelegate, MKMapViewDelegate):
// this method is called when the map region changes as a delegate of MKMapViewDelegate
- (void)mapView:(MKMapView *)mapView regionDidChangeAnimated:(BOOL)animated
{
NSLog(#"regionDidChangeAnimated");
MKCoordinateRegion mapRegion;
// set the center of the map region to the now updated map view center
mapRegion.center = mapView.centerCoordinate;
mapRegion.span.latitudeDelta = 0.3; // you likely don't need these... just kinda hacked this out
mapRegion.span.longitudeDelta = 0.3;
// get the lat & lng of the map region
double lat = mapRegion.center.latitude;
double lng = mapRegion.center.longitude;
// note: I have a variable I have saved called lastLocationCoordinate. It is of type
// CLLocationCoordinate2D and I initially set it in the didUpdateUserLocation
// delegate method. I also update it again when this function is called
// so I always have the last mapRegion center point to compare the present one with
CLLocation *before = [[CLLocation alloc] initWithLatitude:lastLocationCoordinate.latitude longitude:lastLocationCoordinate.longitude];
CLLocation *now = [[CLLocation alloc] initWithLatitude:lat longitude:lng];
CLLocationDistance distance = ([before distanceFromLocation:now]) * 0.000621371192;
[before release];
[now release];
NSLog(#"Scrolled distance: %#", [NSString stringWithFormat:#"%.02f", distance]);
if( distance > SCROLL_UPDATE_DISTANCE )
{
// do something awesome
}
// resave the last location center for the next map move event
lastLocationCoordinate.latitude = mapRegion.center.latitude;
lastLocationCoordinate.longitude = mapRegion.center.longitude;
}
Hope that sends you in the right direction.
distanceFromLocation is iOS 3.2 and later.
initWithLatitude is iOS 2.0 and later.
MKCoordinateRegion is iOS 3.0 and later.
MKMapView centerCoordinate is iOS 3.0 and later.
Also- please feel free to jump in and set me straight where I've erred. I'm figuring all of this out myself- but this is working fairly well for me so far.
Hope this helps someone.
First lesson: Don't ask questions late night on SO.
Second lesson: you can achieve this simply by construction a CGPoint from the user's current location, and a CGPoint from the MapView center.
With two points, just calculate the distance, and see if it's past a certain threshold.
You can also construct a CGRect around the map center, and check CGRectContainsPoint if that's easier.
- (BOOL) isUserPointInsideMapCenterRegion
{
CLLocation * ul = _mapView.userLocation.location;
CGPoint userPoint = [_mapView convertCoordinate: ul.coordinate toPointToView: _mapView];
CGPoint mapPoint = [_mapView convertCoordinate: _mapView.centerCoordinate toPointToView: _mapView];
if (fabs(userPoint.x - mapPoint.x) > MAP_CENTER_RECTANGLE_SIZE || fabs(userPoint.y - mapPoint.y) > MAP_CENTER_RECTANGLE_SIZE)
{
return NO;
}
return YES;
}
I realise this question is a bit old now, but I feel that the answer described in this other question is more robust because the delegate method could be fired for any reason. Using a UIPanGestureRecognizer to detect the scroll means that the user manually scrolled the map, and it can check if the map has scrolled X pixels, instead of relying on meters, which means the user has scrolled more or less depending on the zoom level.
https://stackoverflow.com/a/11675587/159758

Using distanceFromLocation: on none CLLocation objects?

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.