Smooth M.K. Poliline follows the road
I know this was asked a few years ago, but all the answers were related to using the Google Maps API to solve this problem. I wonder if there is a correct way to solve this now that iOS8 is disabled and has brought many improvements to its own MapKit.
Basically, I draw a polyline on the road, consisting of many intermediate points.
var locations = [CLLocation]()
for item in list {
locations.append(CLLocation(latitude: CLLocationDegrees(item["location"]["coordinate"]["x"].doubleValue), longitude: CLLocationDegrees(item["location"]["coordinate"]["y"].doubleValue)))
}
var coordinates = locations.map({(location: CLLocation!) -> CLLocationCoordinate2D in return location.coordinate})
var polyline = MKPolyline(coordinates: &coordinates, count: locations.count)
mapView.addOverlay(polyline)
In due time on the map from 5 to 30 points. When the polyline connects them, I get a more or less fair view of the ride. The problem is, it does NOT stick to the road.
This way I get "rough" edges from time to time. Even if I use the Google Direction API, it is limited to 8 ways and basically decides how to get from point A to point B by drawing a smooth polyline along the way. Additionally, API Directions is capped at 2,500 custom per 24 hours. All I need to do is adjust my current polyline to the nearest road
Many thanks
After some digging, I was able to find the answer to this question, although I'm not entirely sure about its impact on the overall performance of whether Apple is happy as it ships a lot of little MKDirectionsRequest
's. For me, 30+ points is just fine.
var myRoute : MKRoute?
var directionsRequest = MKDirectionsRequest()
var placemarks = [MKMapItem]()
for item in list {
var placemark = MKPlacemark(coordinate: CLLocationCoordinate2D(latitude: CLLocationDegrees(item["location"]["coordinate"]["x"].doubleValue), longitude: CLLocationDegrees(item["location"]["coordinate"]["y"].doubleValue)), addressDictionary: nil )
placemarks.append(MKMapItem(placemark: placemark))
}
directionsRequest.transportType = MKDirectionsTransportType.Automobile
for (k, item) in enumerate(placemarks) {
if k < (placemarks.count - 1) {
directionsRequest.setSource(item)
directionsRequest.setDestination(placemarks[k+1])
var directions = MKDirections(request: directionsRequest)
directions.calculateDirectionsWithCompletionHandler { (response:MKDirectionsResponse!, error: NSError!) -> Void in
if error == nil {
self.myRoute = response.routes[0] as? MKRoute
self.mapView.addOverlay(self.myRoute?.polyline)
}
}
}
}
Special thanks to Anna for pointing me in the right direction.
I used a recursive block to get a 72 way route. always it works up to 50 requests, then the api throws me an error. I think 50 is the limit for a minute. after 50 I have to wait a while to get the api working.
I used google map to draw polyline. so the following code takes an array lat, long and gets the direction and transformation of the path to a set of points and rendering on a google map.
// Pathstr is | detached // Let me know if it needs improvement. // I use recursive blocks because the rendering can cause an error when calculating the direction, so if it's like a sequential block using recursive.
NSArray *arr = [pathStr componentsSeparatedByString:@"|"];
int pointsCount = (int)[arr count];
NSMutableArray* pointsToUse = [[NSMutableArray alloc] init];
for(NSString* locationStr in arr)
{
NSArray *locArr = [locationStr componentsSeparatedByString:@","];
CLLocation *trackLocation = [[CLLocation alloc] initWithLatitude:[[locArr objectAtIndex:0] doubleValue] longitude:[[locArr objectAtIndex:1] doubleValue]];
[pointsToUse addObject:trackLocation];
}
// __block declaration of the block makes it possible to call the block from within itself
__block void (^urlFetchBlock)();
__block int urlIndex = 0;
// the 'recursive' block
urlFetchBlock = [^void () {
MKDirectionsRequest *request = [[MKDirectionsRequest alloc] init];
NSLog(@"Index Value:::%d", urlIndex);
if(urlIndex < (pointsCount - 5))
{
MKPlacemark *sourcePlacemark = [[MKPlacemark alloc] initWithCoordinate:((CLLocation*)pointsToUse[urlIndex]).coordinate addressDictionary:nil];
MKMapItem *sourceMapItem = [[MKMapItem alloc] initWithPlacemark:sourcePlacemark];
[request setSource:sourceMapItem];
MKPlacemark *destPlacemark = [[MKPlacemark alloc] initWithCoordinate:((CLLocation*)pointsToUse[urlIndex + 5]).coordinate addressDictionary:nil];
MKMapItem *destMapItem = [[MKMapItem alloc] initWithPlacemark:destPlacemark];
[request setDestination:destMapItem];
// NSLog(@"Source:%f ::%f, Dest: %f :: %f", ((CLLocation*)pointsToUse[i]).coordinate.latitude,((CLLocation*)pointsToUse[i]).coordinate.longitude, ((CLLocation*)pointsToUse[i+1]).coordinate.latitude, ((CLLocation*)pointsToUse[i+1]).coordinate.longitude);
[request setTransportType:MKDirectionsTransportTypeAny];
request.requestsAlternateRoutes = NO;
MKDirections *directions = [[MKDirections alloc] initWithRequest:request];
[directions calculateDirectionsWithCompletionHandler:
^(MKDirectionsResponse *response, NSError *error) {
if (error) {
// Handle Error
NSLog(@"Error for this particular call");
urlIndex +=5;
urlFetchBlock();
} else {
for (MKRoute * route in response.routes) {
NSUInteger pointCount = route.polyline.pointCount;
NSLog(@"%lu", (unsigned long)pointCount);
//allocate a C array to hold this many points/coordinates...
CLLocationCoordinate2D *routeCoordinates
= malloc(pointCount * sizeof(CLLocationCoordinate2D));
//get the coordinates (all of them)...
[route.polyline getCoordinates:routeCoordinates
range:NSMakeRange(0, pointCount)];
GMSMutablePath *path = [GMSMutablePath path];
//this part just shows how to use the results...
for (int c=0; c < pointCount; c++)
{
[path addLatitude:routeCoordinates[c].latitude longitude:routeCoordinates[c].longitude];
}
GMSPolyline *polyline = [GMSPolyline polylineWithPath: path];
polyline.tappable = YES;
polyline.strokeWidth = width;
// polyline.strokeColor = [UIColor redColor];
polyline.geodesic = YES;
// polyline.title = @"Driving route";
polyline.map = gMapView;
polyline.spans = @[[GMSStyleSpan spanWithColor:[UIColor redColor]]];
}
urlIndex +=5;
urlFetchBlock();
}
}];
}
} copy];
// initiate the url requests
urlFetchBlock();