Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Smooth MKPolyline to follow the road

I know this has been asked before a few years back, but all the answers involved using Google Maps API to solve this. I wonder if there is a proper way to solve this now that iOS8 is out and it brought many improvements to native MapKit.

Basically I draw a polyline on the road, which consists 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)

At one time there are between 5 and 30 points on the map. When a polyline connects them I get more or less fair representation of the trip. The problem is: It does NOT sticks to the road.

enter image description here

So I'm getting "rough" edges from time to time. Even IF I were using Google Direction API, it is limited to 8 ways points and basically decides for you how to get from point A to point B, drawing smooth polyline along the way. Also, Directions API is limited to 2500 usages per 24 hours. All I need to do is to adjust my current polyline to the closest road

Many thanks

like image 361
kernelpanic Avatar asked Oct 25 '25 06:10

kernelpanic


2 Answers

After some digging I managed to find the answer to this question, although I'm not really sure about its impact on overall performance of whether or not this going to make Apple happy, since it sends out a lot of small MKDirectionsRequest's. For me 30+ points worked 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 goes to Anna for pointing me in the right direction.

enter image description here

like image 92
kernelpanic Avatar answered Oct 27 '25 20:10

kernelpanic


I used recursive block to get the route for 72 points. always it works till 50 requests then api throws me error.I guess 50 is a limit for a minute. after 50 I have to wait some time to get the api work.

I have used google map to draw the polyline. so the following code takes an array of lat,long and gets the direction and converting path into set of points and rendering on Google map.

//Pathstr is | separated // Let me know if this need any improvement. // I am using recursive blocks because miscalculating may throw error on calculating the direction so having this is as a sequential blocks 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();
like image 26
Satheesh Prabhu Gurusamy Avatar answered Oct 27 '25 21:10

Satheesh Prabhu Gurusamy