How to decode the Google Directions API polylines field into lat long points in Objective-C for iPhone?

后端 未结 13 975
孤街浪徒
孤街浪徒 2020-11-30 19:05

I want to draw routes on a map corresponding to directions JSON which I am getting through the Google Directions API: https://developers.google.com/maps/documentation/direct

相关标签:
13条回答
  • 2020-11-30 19:35

    Python Implementation

    This isn't in Objective-C, but this thread is where Google drops you if you're looking to decode polyline strings from Google Maps. In case anyone else needs it (much like I did), here's a Python implementation for decoding polyline strings. This is ported from the Mapbox JavaScript version; more info found on my repo page.

    def decode_polyline(polyline_str):
        index, lat, lng = 0, 0, 0
        coordinates = []
        changes = {'latitude': 0, 'longitude': 0}
    
        # Coordinates have variable length when encoded, so just keep
        # track of whether we've hit the end of the string. In each
        # while loop iteration, a single coordinate is decoded.
        while index < len(polyline_str):
            # Gather lat/lon changes, store them in a dictionary to apply them later
            for unit in ['latitude', 'longitude']: 
                shift, result = 0, 0
    
                while True:
                    byte = ord(polyline_str[index]) - 63
                    index+=1
                    result |= (byte & 0x1f) << shift
                    shift += 5
                    if not byte >= 0x20:
                        break
    
                if (result & 1):
                    changes[unit] = ~(result >> 1)
                else:
                    changes[unit] = (result >> 1)
    
            lat += changes['latitude']
            lng += changes['longitude']
    
            coordinates.append((lat / 100000.0, lng / 100000.0))
    
        return coordinates
    
    0 讨论(0)
  • 2020-11-30 19:36

    I hope it's not against the rules to link to my own blog post if it's relevant to the question, but I've solved this problem in the past. Stand-alone answer from linked post:

    @implementation MKPolyline (MKPolyline_EncodedString)
    
    + (MKPolyline *)polylineWithEncodedString:(NSString *)encodedString {
        const char *bytes = [encodedString UTF8String];
        NSUInteger length = [encodedString lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
        NSUInteger idx = 0;
    
        NSUInteger count = length / 4;
        CLLocationCoordinate2D *coords = calloc(count, sizeof(CLLocationCoordinate2D));
        NSUInteger coordIdx = 0;
    
        float latitude = 0;
        float longitude = 0;
        while (idx < length) {
            char byte = 0;
            int res = 0;
            char shift = 0;
    
            do {
                byte = bytes[idx++] - 63;
                res |= (byte & 0x1F) << shift;
                shift += 5;
            } while (byte >= 0x20);
    
            float deltaLat = ((res & 1) ? ~(res >> 1) : (res >> 1));
            latitude += deltaLat;
    
            shift = 0;
            res = 0;
    
            do {
                byte = bytes[idx++] - 0x3F;
                res |= (byte & 0x1F) << shift;
                shift += 5;
            } while (byte >= 0x20);
    
            float deltaLon = ((res & 1) ? ~(res >> 1) : (res >> 1));
            longitude += deltaLon;
    
            float finalLat = latitude * 1E-5;
            float finalLon = longitude * 1E-5;
    
            CLLocationCoordinate2D coord = CLLocationCoordinate2DMake(finalLat, finalLon);
            coords[coordIdx++] = coord;
    
            if (coordIdx == count) {
                NSUInteger newCount = count + 10;
                coords = realloc(coords, newCount * sizeof(CLLocationCoordinate2D));
                count = newCount;
            }
        }
    
        MKPolyline *polyline = [MKPolyline polylineWithCoordinates:coords count:coordIdx];
        free(coords);
    
        return polyline;
    }
    
    @end
    
    0 讨论(0)
  • 2020-11-30 19:36

    For Google maps it already have a straight forward method , polylineWithPath, so I prefer this snippet.

    -(void)drawPathFrom:(CLLocation*)source toDestination:(CLLocation*)destination{
    
        NSString *baseUrl = [NSString stringWithFormat:@"http://maps.googleapis.com/maps/api/directions/json?origin=%f,%f&destination=%f,%f&sensor=true", source.coordinate.latitude,  source.coordinate.longitude, destination.coordinate.latitude,  destination.coordinate.longitude];
    
        NSURL *url = [NSURL URLWithString:[baseUrl stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]];
        NSLog(@"Url: %@", url);
        NSURLRequest *request = [NSURLRequest requestWithURL:url];
    
        [NSURLConnection sendAsynchronousRequest:request queue:[NSOperationQueue mainQueue] completionHandler:^(NSURLResponse *response, NSData *data, NSError *connectionError) {
            if(!connectionError){
                NSDictionary *result        = [NSJSONSerialization JSONObjectWithData:data options:0 error:nil];
                NSArray *routes             = [result objectForKey:@"routes"];
                NSDictionary *firstRoute    = [routes objectAtIndex:0];
                NSString *encodedPath       = [firstRoute[@"overview_polyline"] objectForKey:@"points"];
    
                GMSPolyline *polyPath       = [GMSPolyline polylineWithPath:[GMSPath pathFromEncodedPath:encodedPath]];
                polyPath.strokeColor        = [UIColor redColor];
                polyPath.strokeWidth        = 3.5f;
                polyPath.map                = _mapView;
            }
        }];
    
    }
    
    0 讨论(0)
  • 2020-11-30 19:40

    If you are working with Google Map on iOS and want to draw the route including the polylines, google itself provides an easier way to get the GMSPath from polyline as,

    GMSPath *pathFromPolyline = [GMSPath pathFromEncodedPath:polyLinePoints];
    

    Here is the complete code:

    + (void)callGoogleServiceToGetRouteDataFromSource:(CLLocation *)sourceLocation toDestination:(CLLocation *)destinationLocation onMap:(GMSMapView *)mapView_{
        NSString *baseUrl = [NSString stringWithFormat:@"http://maps.googleapis.com/maps/api/directions/json?origin=%f,%f&destination=%f,%f&sensor=false", sourceLocation.coordinate.latitude,  sourceLocation.coordinate.longitude, destinationLocation.coordinate.latitude,  destinationLocation.coordinate.longitude];
    
        NSURL *url = [NSURL URLWithString:[baseUrl stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]];
    
        NSLog(@"Url: %@", url);
    
        NSURLRequest *request = [NSURLRequest requestWithURL:url];
    
        [NSURLConnection sendAsynchronousRequest:request queue:[NSOperationQueue mainQueue] completionHandler:^(NSURLResponse *response, NSData *data, NSError *connectionError) {
    
            GMSMutablePath *path = [GMSMutablePath path];
    
            NSError *error = nil;
            NSDictionary *result = [NSJSONSerialization JSONObjectWithData:data options:0 error:&error];
    
            NSArray *routes = [result objectForKey:@"routes"];
    
            NSDictionary *firstRoute = [routes objectAtIndex:0];
    
            NSDictionary *leg =  [[firstRoute objectForKey:@"legs"] objectAtIndex:0];
    
            NSArray *steps = [leg objectForKey:@"steps"];
    
            int stepIndex = 0;
    
            CLLocationCoordinate2D stepCoordinates[1  + [steps count] + 1];
    
            for (NSDictionary *step in steps) {
    
                NSDictionary *start_location = [step objectForKey:@"start_location"];
                stepCoordinates[++stepIndex] = [self coordinateWithLocation:start_location];
                [path addCoordinate:[self coordinateWithLocation:start_location]];
    
                NSString *polyLinePoints = [[step objectForKey:@"polyline"] objectForKey:@"points"];
                GMSPath *polyLinePath = [GMSPath pathFromEncodedPath:polyLinePoints];
                for (int p=0; p<polyLinePath.count; p++) {
                    [path addCoordinate:[polyLinePath coordinateAtIndex:p]];
                }
    
    
                if ([steps count] == stepIndex){
                    NSDictionary *end_location = [step objectForKey:@"end_location"];
                    stepCoordinates[++stepIndex] = [self coordinateWithLocation:end_location];
                    [path addCoordinate:[self coordinateWithLocation:end_location]];
                }
            }
    
            GMSPolyline *polyline = nil;
            polyline = [GMSPolyline polylineWithPath:path];
            polyline.strokeColor = [UIColor grayColor];
            polyline.strokeWidth = 3.f;
            polyline.map = mapView_;
        }];
    }
    
    + (CLLocationCoordinate2D)coordinateWithLocation:(NSDictionary*)location
    {
        double latitude = [[location objectForKey:@"lat"] doubleValue];
        double longitude = [[location objectForKey:@"lng"] doubleValue];
    
        return CLLocationCoordinate2DMake(latitude, longitude);
    }
    
    0 讨论(0)
  • 2020-11-30 19:41

    This is my own revisitation of Sedate Alien's answer. It is the same implementation save for removing duplicated code and using NSMutableData instead of manually allocating stuff.

    @implementation MKPolyline (EncodedString)
    
    + (float)decodeBytes:(const char *)bytes atPos:(NSUInteger *)idx toValue:(float *)value {
      char byte  = 0;
      int  res   = 0;
      char shift = 0;
    
      do {
        byte   = bytes[(*idx)++] - 0x3F;
        res   |= (byte & 0x1F) << shift;
        shift += 5;
      }
      while (byte >= 0x20);
    
      (*value) += ((res & 1) ? ~(res >> 1) : (res >> 1));
    
      return (*value) * 1E-5;
    }
    
    + (MKPolyline *)polylineWithEncodedString:(NSString *)encodedString {
      const char             *bytes  = [encodedString UTF8String];
      NSUInteger              length = [encodedString lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
      NSUInteger              idx    = 0;
      NSMutableData          *data   = [NSMutableData data];
      float                   lat    = 0;
      float                   lon    = 0;
      CLLocationCoordinate2D  coords = CLLocationCoordinate2DMake(0, 0);
    
      while (idx < length) {
    
        coords.latitude  = [self decodeBytes:bytes atPos:&idx toValue:&lat];
        coords.longitude = [self decodeBytes:bytes atPos:&idx toValue:&lon];
    
        [data appendBytes:&coords length:sizeof(CLLocationCoordinate2D)];
      }
    
      return [MKPolyline polylineWithCoordinates:(CLLocationCoordinate2D *)data.bytes count:data.length / sizeof(CLLocationCoordinate2D)];
    }
    
    @end
    
    0 讨论(0)
  • 2020-11-30 19:42
    - (MKPolyline *)polylineWithEncodedString:(NSString *)encodedString {
        const char *bytes = [encodedString UTF8String];
        NSUInteger length = [encodedString lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
        NSUInteger idx = 0;
        NSUInteger count = length / 4;
        CLLocationCoordinate2D *coords = calloc(count, sizeof(CLLocationCoordinate2D));
        NSUInteger coordIdx = 0;
        float latitude = 0;
        float longitude = 0;
        while (idx < length) {
            char byte = 0;
            int res = 0;
            char shift = 0;
            do {
                byte = bytes[idx++] - 63;
                res |= (byte & 0x1F) << shift;
                shift += 5;
            } while (byte >= 0x20);
    
            float deltaLat = ((res & 1) ? ~(res >> 1) : (res >> 1));
            latitude += deltaLat;
    
            shift = 0;
            res = 0;
    
            do {
                byte = bytes[idx++] - 0x3F;
                res |= (byte & 0x1F) << shift;
                shift += 5;
            } while (byte >= 0x20);
    
            float deltaLon = ((res & 1) ? ~(res >> 1) : (res >> 1));
            longitude += deltaLon;
    
            float finalLat = latitude * 1E-5;
            float finalLon = longitude * 1E-5;
    
            CLLocationCoordinate2D coord = CLLocationCoordinate2DMake(finalLat, finalLon);
            coords[coordIdx++] = coord;
    
            if (coordIdx == count) {
                NSUInteger newCount = count + 10;
                coords = realloc(coords, newCount * sizeof(CLLocationCoordinate2D));
                count = newCount;
            }
        }
    
        MKPolyline *polyline = [MKPolyline polylineWithCoordinates:coords count:coordIdx];
        free(coords);
        return polyline;
    }
    - (MKPolygonRenderer *)mapView:(MKMapView *)mapView viewForOverlay:(id <MKOverlay>)overlay {
      //  MKPolygonRenderer *polylineView = [[MKPolygonRenderer alloc] initWithOverlay:overlay];
        MKPolylineView *polylineView = [[MKPolylineView alloc] initWithPolyline:overlay];
        polylineView.strokeColor = [UIColor redColor];
        polylineView.lineWidth = 4.0;
        [self zoomToPolyLine:mapview polyline:overlay animated:YES];
        return polylineView;
    }
    -(void)zoomToPolyLine: (MKMapView*)map polyline: (MKPolyline*)polyline animated: (BOOL)animated
    {
        [map setVisibleMapRect:[polyline boundingMapRect] edgePadding:UIEdgeInsetsMake(25.0, 25.0, 25.0, 25.0) animated:animated];
    }
    - (void)locationManager:(CLLocationManager *)manager didUpdateToLocation:(CLLocation *)newLocation fromLocation:(CLLocation *)oldLocation
    {
       // NSLog(@"didUpdateToLocation: %@", newLocation);
        CLLocation *currentLocation = newLocation;
        if (currentLocation != nil) {
          currlong  =  [NSString stringWithFormat:@"%.8f", currentLocation.coordinate.longitude];
           currlt = [NSString stringWithFormat:@"%.8f", currentLocation.coordinate.latitude];
        }
        NSString *origin = [NSString stringWithFormat:@"%@%@%@",currlt,@",",currlong];
    
        //I have just mention static location
        NSString *drivein = @"23.0472963,72.52757040000006";
        NSString *apikey = [NSString stringWithFormat:@"https://maps.googleapis.com/maps/api/directions/json?origin=%@&destination=%@",origin,drivein];
    
        NSURL *url = [NSURL URLWithString:apikey];
        NSURLRequest *request = [NSURLRequest requestWithURL:url];
        NSURLResponse *response;
        NSError *error;
        NSData *responseData = [NSURLConnection sendSynchronousRequest:request returningResponse:&response error:&error];
        NSString *responseString = [[NSString alloc] initWithData:responseData encoding:NSUTF8StringEncoding];
    
        if(!error)
        {
            NSData *data = [responseString dataUsingEncoding:NSUTF8StringEncoding];
            NSDictionary *jsonResponse = [NSJSONSerialization JSONObjectWithData:data
                                                                         options:kNilOptions
                                                                           error:&error];
            NSArray *routesArray = [jsonResponse objectForKey:@"routes"];
            NSLog(@"route array %@",routesArray);
            if ([routesArray count] > 0)
            {
                NSDictionary *routeDict = [routesArray objectAtIndex:0];
                NSDictionary *routeOverviewPolyline = [routeDict objectForKey:@"overview_polyline"];
                NSString *points = [routeOverviewPolyline objectForKey:@"points"];
                MKPolyline *line = [self polylineWithEncodedString:points];
                [mapview addOverlay:line];
            }
        }
        MKCoordinateRegion viewRegion = MKCoordinateRegionMakeWithDistance(currentLocation.coordinate, 500, 500);
        MKCoordinateRegion adjustedRegion = [mapview regionThatFits:viewRegion];
        [mapview setRegion:adjustedRegion animated:YES];
        mapview.showsUserLocation = YES;
    
        MKPointAnnotation *point = [[MKPointAnnotation alloc] init];
        point.coordinate = currentLocation.coordinate;
        point.title = @"Your current Locations";
        point.subtitle = @"You are here!";
        [mapview addAnnotation:point];
        [locationmanger stopUpdatingLocation];
    }
    
    0 讨论(0)
提交回复
热议问题