Get directions from one location to another based on vehicle course - ios

I am developing an iOS app where I have to get directions from one location to another. I am getting the directions using the MKDirectionsRequest API. This api gives direction between two locations. But the problem is that the route returned by this api do not consider the direction (course) in which the vehicle is moving. Is there any way to get route between user current location to the destination in iOS which also takes the course of the moving vehicle into consideration. Currently I am using the following function:
-(void)displayDirections{
CLLocation *currentLocation = [[CLLocation alloc] initWithLatitude:currentLocationLatitude longitude:currentLocationLongitude];
MKPlacemark *placemarker0 = [[MKPlacemark alloc]initWithCoordinate:currentLocation.coordinate];
MKMapItem *myMapItem0 = [[MKMapItem alloc] initWithPlacemark:placemarker0];
CLLocation *location = [[CLLocation alloc] initWithLatitude:destinationLocationLatitude longitude:destinationLocationLongitude]
MKPlacemark *placemarker1 = [[MKPlacemark alloc]initWithCoordinate:location.coordinate];
MKMapItem *myMapItem1 = [[MKMapItem alloc] initWithPlacemark:placemarker1];
MKDirectionsRequest *request = [[MKDirectionsRequest alloc] init];
[request setSource:myMapItem0];
[request setDestination:myMapItem1];
[request setTransportType:MKDirectionsTransportTypeAny];
[request setRequestsAlternateRoutes:YES]; // Gives you several route options.
MKDirections *directions = [[MKDirections alloc] initWithRequest:request];
__weak COMSiteDetailViewController *weakSelf = self;
[directions calculateDirectionsWithCompletionHandler:^(MKDirectionsResponse *response, NSError *error) {
if (!error) {
[weakSelf.mkMapView removeOverlays:weakSelf.mkMapView.overlays];
NSArray *routes = [response routes];
NSArray *sortedArray;
sortedArray = [routes sortedArrayUsingComparator:^NSComparisonResult(MKRoute *a, MKRoute *b) {
CLLocationDistance first = a.distance;
CLLocationDistance second = b.distance;
return first < second;
}];
int count = 1;
for (MKRoute *route in sortedArray) {
if(count < sortedArray.count){
route.polyline.title = #"longer";
}else{
route.polyline.title = #"shortest";
}
count++;
[self.mkMapView addOverlay:[route polyline] level:MKOverlayLevelAboveRoads];
}
}
}];
}
Thanks in advance!

After searching the web I have found that neither Google API or Apple MapKit Directions API consider the heading or movement of the vehicle while giving the route information. It just consider the coordinates of the start/end locations and give the shortest route(s).
So there is no way, at the moment, to display route considering vehicle heading using Apple/Google Apis.
Although, there are other paid Apis such as https://developer.here.com which I used in my app. This api is very reliable and the pricing too is very reasonable.
Thanks!

Related

iOS Swift - Draw route using multiple coordinates in MKMapView [duplicate]

I'm using this code to connect pins(points on map) with polyline:
CLLocationCoordinate2D coordinates[allLocations.count];
int i = 0;
for (locHolder *location in allLocations) {
coordinates[i] = CLLocationCoordinate2DMake([location.lat floatValue], [location floatValue]);
i++;
}
MKPolyline *polyline = [MKPolyline polylineWithCoordinates:coordinates count:[allLocations count]];
self->polyline = polyline;
[self.mapView addOverlay:self->polyline level:MKOverlayLevelAboveRoads];
But this code connects them over-the-air(ignoring roads), is it possible to connect multiple CLLocationCoordinate2D locations with polyline that follows roads?
**
And a shor sub question,
Is there any difference in performance between [allLocations count] and allLocations.count.
Thanks.
In these days I've came across with the same problem, and looking for answers here and there, I found this answer from Rob very useful for this case.
First, supose you have in your MapViewController, an array of objects containing an origin an a destination, each one being of type CLLocationCoordinate2D
In your MapViewController.h
#property (nonatomic, strong) NSArray *locations;
Then, in the implementation, populate that array with some data that goes like this:
_locations = [NSArray arrayWithObjects:
[RouteObject routeWithOrigin:CLLocationCoordinate2DMake(-23.595571, -46.684408) destination:CLLocationCoordinate2DMake(-23.597886, -46.673950)],
[RouteObject routeWithOrigin:CLLocationCoordinate2DMake(-23.597886, -46.673950) destination:CLLocationCoordinate2DMake(-23.597591, -46.666805)],
[RouteObject routeWithOrigin:CLLocationCoordinate2DMake(-23.597591, -46.666805) destination:CLLocationCoordinate2DMake(-23.604061, -46.662728)], nil];
Now that you have your array of origin and destinations, you can start drawing a route between them (from A to B, from B to C and from C to D).
Add a button and then connect an IBAction so in that method you are going to do de magic.
- (IBAction)btnDirectionsPressed:(id)sender {
[self enableUI:NO]; // This method enables or disables all the UI elements that interact with the user
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0); // Create the semaphore
__block NSMutableArray *routes = [[NSMutableArray alloc] init]; // Arrays to store MKRoute objects and MKAnnotationPoint objects, so then we can draw them on the map
__block NSMutableArray *annotations = [[NSMutableArray alloc] init];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{
for (RouteObject *routeObject in _locations) {
MKDirectionsRequest *directionsRequest = [MKDirectionsRequest new];
[directionsRequest setTransportType:MKDirectionsTransportTypeAutomobile];
[directionsRequest setSource:[routeObject originMapItem]];
[directionsRequest setDestination:[routeObject destinationMapItem]];
[directionsRequest setRequestsAlternateRoutes:NO];
MKDirections *direction = [[MKDirections alloc] initWithRequest:directionsRequest];
// For each object in the locations array, we request that route from its origin and its destination
[direction calculateDirectionsWithCompletionHandler: ^(MKDirectionsResponse *response, NSError *error) {
if (error) {
NSLog(#"There was an error getting your directions");
return;
}
MKRoute *route = [response.routes firstObject];
[routes addObject:route];
[annotations addObject:[routeObject destinationAnnotation]];
dispatch_semaphore_signal(semaphore); // Send the signal that one semaphore is ready to consume
}];
}
});
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{
for (int i = 0; i < _locations.count; i++) {
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER); // Wait until one semaphore is ready to consume
dispatch_async(dispatch_get_main_queue(), ^{ // For each element, dispatch to the main queue to draw route and annotation corresponding to that location
MKRoute *route = routes[i];
[self.mapView addOverlay:route.polyline];
[self.mapView addAnnotation:annotations[i]];
});
}
dispatch_async(dispatch_get_main_queue(), ^{ // Finally, dispatch to the main queue enabling elements and resizing map to show all the annotations
[self enableUI:YES];
[self fitRegionToRoutes];
});
});
}
Hope this help!
Joel.

MKLocalSearch returning results from outside region

I'm searching for places (would like to only show businesses if possible) like this:
CLLocationCoordinate2D currentCenter = CLLocationCoordinate2DMake(self.locationManager.location.coordinate.latitude, self.locationManager.location.coordinate.longitude);
MKCoordinateRegion currentRegion = MKCoordinateRegionMakeWithDistance(currentCenter, 15000, 15000);
MKLocalSearchRequest *localSearchRequest = [[MKLocalSearchRequest alloc] init];
localSearchRequest.naturalLanguageQuery = identifier;
[localSearchRequest setRegion:currentRegion];
MKLocalSearch *localSearch = [[MKLocalSearch alloc] initWithRequest:localSearchRequest];
[localSearch startWithCompletionHandler:^(MKLocalSearchResponse *respone, NSError *error) {
if (!error)
{
// Result handling
}
}];
but the results include results from other countries, why is this? I only call this method if the CLLocationManager coordinate passes this check:
CLLocationCoordinate2DIsValid
Thanks!

How to get directions to a MKPointAnnotation

I have created a map that has an MKPointAnnotation which is the single point on the map (besides the users location). I am trying to work out how to amend some existing code I have to get the Driving directions to this point.
This is the code that I use early in the application. At this earlier point in the application I have the following which gives me a CLPlaceMark.
[geocoder geocodeAddressString:location
completionHandler:^(NSArray* placemarks, NSError* error){
if (placemarks && placemarks.count > 0) {
CLPlacemark *topResult = [placemarks objectAtIndex:0];
Collecting directions:
MKDirectionsRequest *request = [[MKDirectionsRequest alloc] init];
[request setSource:[MKMapItem mapItemForCurrentLocation]];
MKPlacemark *mkDest = [[MKPlacemark alloc] initWithPlacemark:topResult];
[request setDestination:[[MKMapItem alloc] initWithPlacemark:mkDest]];
[request setTransportType:MKDirectionsTransportTypeWalking]; // This can be limited to automobile and walking directions.
[request setRequestsAlternateRoutes:NO];
MKDirections *directions = [[MKDirections alloc] initWithRequest:request];
[directions calculateDirectionsWithCompletionHandler:^(MKDirectionsResponse *response, NSError *error) {
if (!error) {
for (MKRoute *route in [response routes]) {
[self.mapView addOverlay:[route polyline] level:MKOverlayLevelAboveRoads]; // Draws the route above roads, but below labels.
// You can also get turn-by-turn steps, distance, advisory notices, ETA, etc by accessing various route properties.
}
}
}];
Issue
The issue is that later on I seem to only be able to access the self.mapView.annotations. So I have access to the MKPointAnnotation, but I need access to a CLPlacemark for the setDestination on the MKDirectionsRequest.
So the question is how do I get a CLPacemark from a MKPointAnnotation, or is there a different approach to getting directions to a single point without that requirement? Thanks
For the directions request, the MKMapItem needs an MKPlacemark (not a CLPlacemark).
You can create an MKPlacemark directly from coordinates using its initWithCoordinate:addressDictionary: method.
For example:
MKPlacemark *mkDest = [[MKPlacemark alloc]
initWithCoordinate:pointAnnotation.coordinate
addressDictionary:nil];
[request setDestination:[[MKMapItem alloc] initWithPlacemark:mkDest]];
MKPointAnnotation will give you the coordinate, which you can put into CLPlacemark's location.coordinate. I don't think there will be any other readily-available information within an MKPointAnnotation that would be usable in a CLPlacemark.
MKPointAnnotation *annotation = ...;
CLPlacemark *placemark = ...;
placemark.location.coordinate = annotation.coordinate;
Edit: Apologies, I didn't realize that CLPlacemarks are largely read-only. That being said, you can use a reverse-geocode on the coordinate of your MKPointAnnotation in order to get a CLPlacemark. This link has information on how to reverse-geocode to get your CLPlacemark from a CLLocation (populate the location.coordinate with your annotation.coordinate) to look up directions.
You need to use the coordinate property of the MKPointAnnotation and then get the CLPlacemark via CLGeocoder using reverse geocode.
EDIT: Some sample code.
CLLocation *location = [[CLLocation alloc] initWithLatitude:annotation.coordinate.latitude longitude:annotation.coordinate.longitude];
CLGeocoder *geocoder = [CLGeocoder new];
[geocoder reverseGeocodeLocation:location completionHandler:^(NSArray *placemark, NSError *error){
// Grab the placemark
}];
Otherwise you need to cache the CLPlacemark which you can do on your annotation datasource if you like (remember MKAnnotation is a protocol, there is nothing saying you can't add a property to the backing model).

Checking Proximity of two Locations in IOS

I have an array of locations and when I add another I want to be able to check if the other locations in the array are within a block of the new one. This is the Code I have to find the current location:
//Geocoding Block
[self.geoCoder reverseGeocodeLocation: locationManager.location completionHandler:
^(NSArray *placemarks, NSError *error) {
//Get nearby address
CLPlacemark *placemark = [placemarks objectAtIndex:0];
//String to hold address
locatedAt = [[placemark.addressDictionary valueForKey:#"FormattedAddressLines"] componentsJoinedByString:#", "];
The array has yet to be created because I want to figure this out first, I dont know what should be held in the array (string...). I know how to do a search I just need to know how to compare the locations.
You can get the distance between two locations using the distanceFromLocation: method on CLLocation. (You can get a CLLocation out of a CLPlacemark with myPlacemark.location.) So if you have an array of CLLocation objects, and you want to find the ones that are within one block (1/20 mile, or about 80 meters), you can do this:
NSMutableArray *locationsWithinOneBlock = [NSMutableArray new];
for (CLLocation *location in myLocations) {
if ([location distanceFromLocation:targetLocation] <= 80.0)
[locationsWithinOneBlock addObject:location];
}
This assumes you have an array myLocations of CLLocation objects that you want to filter against a single CLLocation called targetLocation.

Use MKLocalSearch to search for locations on a map

I want to use MKLocalSearch for searching in a Map. This functionality is available in iOS 6.1+. Does anybody know how to use this or can anybody give an example of how to use an MKLocalSearch?
MKLocalSearchResponse documentation
The API for MKLocalSearch is fairly easy to understand. At its most basic, you
alloc-init an MKLocalSearchRequest
Set its naturalLanguageQuery to some search term
Use the search request to initialize an MKLocalSearch object
Tell the local search to start, passing it a completion handler
Do something with the array of MKMapItem objects in the response
Search for Cafes:
// Create a search request with a string
MKLocalSearchRequest *searchRequest = [[MKLocalSearchRequest alloc] init];
[searchRequest setNaturalLanguageQuery:#"Cafe"];
// Create the local search to perform the search
MKLocalSearch *localSearch = [[MKLocalSearch alloc] initWithRequest:searchRequest];
[localSearch startWithCompletionHandler:^(MKLocalSearchResponse *response, NSError *error) {
if (!error) {
for (MKMapItem *mapItem in [response mapItems]) {
NSLog(#"Name: %#, Placemark title: %#", [mapItem name], [[mapItem placemark] title]);
}
} else {
NSLog(#"Search Request Error: %#", [error localizedDescription]);
}
}];
You can specify a region for the search like this:
// Search for Cafes in Paris
MKLocalSearchRequest *searchRequest = [[MKLocalSearchRequest alloc] init];
[searchRequest setNaturalLanguageQuery:#"Cafe"];
CLLocationCoordinate2D parisCenter = CLLocationCoordinate2DMake(48.8566667, 2.3509871);
MKCoordinateRegion parisRegion = MKCoordinateRegionMakeWithDistance(parisCenter, 15000, 15000);
[searchRequest setRegion:parisRegion];
You can also take the region from an MKMapView that the user has zoomed into. This will give better results:
[searchRequest setRegion:self.mapView.region];
The response object, an MKLocalSearchResponse, contains an array of MKMapItem objects (mapItems) and an MKCoordinateRegion called boundingRegion, which is a region that contains all the results. You can use it to set a map view to show all results:
[self.mapView setRegion:response.boundingRegion];
The array of MKMapItem objects can't be placed on a map (they're used for sending to the Maps app) but each contains a placemark property which can be added to a map:
[localSearch startWithCompletionHandler:^(MKLocalSearchResponse *response, NSError *error) {
if (!error) {
for (MKMapItem *mapItem in [response mapItems]) {
NSLog(#"Name: %#, MKAnnotation title: %#", [mapItem name], [[mapItem placemark] title]);
NSLog(#"Coordinate: %f %f", [[mapItem placemark] coordinate].latitude, [[mapItem placemark] coordinate].longitude);
// Should use a weak copy of self
[self.mapView addAnnotation:[mapItem placemark]];
}
} else {
NSLog(#"Search Request Error: %#", [error localizedDescription]);
}
}];
Search for Dublin places a pin on the map view and logs:
Name: Dublin, Co. Dublin, MKAnnotation title: Dublin, Co. Dublin, Ireland
Coordinate: 53.344104 -6.267494
There are a load of extra details in the returned objects, especially if you search for businesses. Here are a few:
[localSearch startWithCompletionHandler:^(MKLocalSearchResponse *response, NSError *error) {
if (!error) {
NSLog(#"Results: %#", [response mapItems]);
MKMapItem *mapItem = [[response mapItems] objectAtIndex:0];
NSLog(#"Name:%# Phone:%# URL:%#", [mapItem name], [mapItem phoneNumber], [mapItem url]);
NSLog(#"Placemark: %#", [mapItem placemark]);
MKPlacemark *placemark = [mapItem placemark];
NSLog(#"Placemark Address: %#", [placemark addressDictionary]);
MKCoordinateRegion boundingRegion = [response boundingRegion];
NSLog(#"Bounds: %f %f", boundingRegion.span.latitudeDelta, boundingRegion.span.longitudeDelta);
}
Here is an example that search for cafes in a radius of 1 km around a given location :
MKLocalSearchRequest *request = [[MKLocalSearchRequest alloc] init];
CLLocationCoordinate2D location = CLLocationCoordinate2DMake(11.567898, 104.894430);
request.naturalLanguageQuery = #"cafe";
request.region = MKCoordinateRegionMakeWithDistance(location, 1000, 1000);
MKLocalSearch *search = [[MKLocalSearch alloc] initWithRequest:request];
[search startWithCompletionHandler:^(MKLocalSearchResponse *response, NSError *error){
for (MKMapItem *item in response.mapItems) {
NSLog(#"%#", item.name);
}
}];
Please note than when the search is unsuccessful, it does not return an empty list, but an error with domain MKErrorDomain and code 4.
Here is a tutorial for Localsearch
http://jeffreysambells.com/2013/01/28/mklocalsearch-example

Resources