Search code examples
iosgoogle-mapsgoogle-maps-api-3google-roads-api

iOS - Snap To Road API with more than 100 coordinates


I know how to use the snap to road API and parse the response, show it on the map etc.. However, the snap to road API has a limitation of 100 coordinates/request.

Let's say I have an array of 100+ coordinates and want all of them to snap to the road.

The snap to road API docs describe a workaround if working with more than 100+ coordinates, however it is Java code (I think) and I don't really understand it.

How could I do this in Objective-C?

In my implementation, I call the snap to roads api multiple times by taking ranges of a 100 coordinates from my array. However this results in weird polylines, when draw on the map: enter image description here

Here is my code:

-(void)getDirectionsFrom:(CLLocation*)startLocation to:(CLLocation*)endLocation{

    //Create placemarks from the passed in locations
    MKPlacemark *start = [[MKPlacemark alloc] initWithCoordinate:locManager.location.coordinate addressDictionary:NULL];
    MKPlacemark *finsih = [[MKPlacemark alloc] initWithCoordinate:endLocation.coordinate addressDictionary:NULL];

    //Create direction request
    MKDirectionsRequest *request = [[MKDirectionsRequest alloc]init];
    [request setSource:[[MKMapItem alloc] initWithPlacemark:start]];
    [request setDestination:[[MKMapItem alloc] initWithPlacemark:finsih]];
    [request setTransportType:MKDirectionsTransportTypeAutomobile];

    //Calculate directions
    MKDirections *direction = [[MKDirections alloc]initWithRequest:request];

    [direction calculateDirectionsWithCompletionHandler: ^(MKDirectionsResponse *response, NSError *error) {

        if (error) {
            NSLog(@"Error is %@",error);
        }

        else {

            MKRoute *route = [response.routes firstObject];

            NSUInteger pointCount = route.polyline.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)];

            NSMutableArray *locationsArray = [[NSMutableArray alloc] init];

            CLLocation *lastLocation = [[CLLocation alloc] init];


            //Call snap to road with 100 locations at a time
            for (int start = 0; start < pointCount; start += 99) {
                [locationsArray removeAllObjects];
                NSInteger length = MIN(99, pointCount-start);
                if(start != 0){
                    [locationsArray addObject:lastLocation];
                }
                for(int i = start;i<start+length;i++){
                    CLLocationCoordinate2D loc2D = routeCoordinates[i];
                    CLLocation *location = [[CLLocation alloc] initWithLatitude:loc2D.latitude longitude:loc2D.longitude];
                    [locationsArray addObject:location];
                }

                lastLocation = [locationsArray lastObject];
                [self snapPathToRoad:locationsArray];
            }

            //Free the memory used by the C array
            free(routeCoordinates);

        }
    }];

}


-(void)snapPathToRoad:(NSMutableArray*)passedInArray{

    //Create string to store coordinates in for the URL
    NSString *tempcoordinatesForURL = @"";

    //Append tempcoordinatesForURL string by the coordinates in the right format
    for(int i = 0;i<[passedInArray count];i++){

        CLLocationCoordinate2D coordinates = [[passedInArray objectAtIndex:i] coordinate];

        NSString *coordinatesString = [NSString stringWithFormat:@"|%f,%f|",coordinates.latitude,coordinates.longitude];

        tempcoordinatesForURL = [tempcoordinatesForURL stringByAppendingString:coordinatesString];
    }

    //Remove unnecessary charchters from tempcoordinatesForURL
    NSString *coordinatesForURL = [[tempcoordinatesForURL substringToIndex:[tempcoordinatesForURL length]-1] stringByReplacingOccurrencesOfString:@"||" withString:@"|"];

    //Create url by removing last charachter from coordinatesForURL string
    NSString *urlPath = [NSString stringWithFormat:@"https://roads.googleapis.com/v1/snapToRoads?path=%@&interpolate=true&key=AIzaSyDrtHA-AMiVVylUPcp46_Vf1eZJJFBwRCY",[coordinatesForURL substringFromIndex:1]];

    //Remove unsupproted charchters from urlPath and create an NSURL
    NSString *escapedUrlPath = [urlPath stringByAddingPercentEncodingWithAllowedCharacters:[NSCharacterSet URLQueryAllowedCharacterSet]];
    NSURL *url = [NSURL URLWithString:escapedUrlPath];

    //Create request
    NSURLRequest *request = [NSURLRequest requestWithURL:url];

    //Send request to server
    [NSURLConnection sendAsynchronousRequest:request queue:[NSOperationQueue mainQueue] completionHandler:^(NSURLResponse *response, NSData *data, NSError *connectionError) {

        //If response, parse JSON
        if(response){

            //Dictionary with the whole JSON file
            NSDictionary *result = [NSJSONSerialization JSONObjectWithData:data options:0 error:nil];

            //Array of snapped points from the JSON
            NSArray *snappedPoints = [result objectForKey:@"snappedPoints"];

            //Loop through the snapped points array and add each coordinate to the path
            for (int i = 0; i<[snappedPoints count]; i++) {
                NSDictionary *location = [[snappedPoints objectAtIndex:i] objectForKey:@"location"];

                double latitude = [[location objectForKey:@"latitude"] doubleValue];
                double longitude = [[location objectForKey:@"longitude"] doubleValue];

                [pathToDraw addCoordinate:CLLocationCoordinate2DMake(latitude, longitude)];

            }

            //As this method is called multiple times, clear the previous polylines so only the final one will be visible on the map
            [self.mapView clear];

            //Draw polyline with path
            GMSPolyline *polyline = [GMSPolyline polylineWithPath:pathToDraw];
            polyline.map = self.mapView;
            polyline.strokeColor = [UIColor darkGrayColor];
            polyline.strokeWidth = 6;

        }

        //If error, log it
        else if(connectionError){
            NSLog(@"%@",connectionError);
        }

    }];

}

"patToDraw is a GMSMutablePath ivar"


Solution

  • Eventually, I found the problem.

    If multiple requests are called asynchronously, not all of them will finish at the same time. Because each of them adds the coordinates to the same path, requests with less coordinates for example, will finish sooner, despite the fact that they were called later. This leads to the coordinates added to the path in the wrong order.