Decoding polyline with new Google Maps API
Asked Answered
P

8

28

I am drawing a route between two points in a map. I receive the points this way:

StringBuilder urlString = new StringBuilder();
    urlString.append("http://maps.googleapis.com/maps/api/directions/json");
    urlString.append("?origin=");// from
    urlString.append(Double.toString(src.latitude));
    urlString.append(",");
    urlString.append(Double.toString(src.longitude));
    urlString.append("&destination=");// to
    urlString.append(Double.toString(dest.latitude));
    urlString.append(",");
    urlString.append(Double.toString(dest.longitude));
    urlString.append("&sensor=false&mode=");
    if (tipo != null) {
        urlString.append(tipo);
    }
return urlString.toString;

I receive the response from Google, and get the JSON:

resp = new JSONObject(builder.toString());
                Log.i("Location", "Contenido del kml: "+resp);
                JSONArray routeObject = resp.getJSONArray("routes");
                JSONObject routes = routeObject.getJSONObject(0);
                JSONObject overviewPolylines = routes
                        .getJSONObject("overview_polyline");
                String encodedString = overviewPolylines.getString("points");
                ArrayList<LatLng> puntos=decodePoly(encodedString);

The response I get from google: (in JSON format):

Response: {"status":"OK","routes":[{"waypoint_order":[],"summary":"R-3 and A-3","bounds":{"southwest":{"lng":-3.676540000000001,"lat":40.00040000000001},"northeast":{"lng":-2.99933,"lat":40.43357}},"legs":[{"duration":{"value":3267,"text":"54 mins"},"distance":{"value":85039,"text":"85.0 km"},"end_location":{"lng":-2.99933,"lat":40.00040000000001},"start_address":"Calle del General Díaz Porlier, 91, 28006 Madrid, Spain","end_address":"Camino Veinticuatro, 2, 16400 Tarancón, Cuenca, Spain","start_location":{"lng":-3.676540000000001,"lat":40.43331000000001},"via_waypoint":[],"steps":[{"html_instructions":"Head <b>north<\/b> on <b>Calle del Gral Díaz Porlier<\/b> toward <b>Calle de Maldonado<\/b>","duration":{"value":6,"text":"1 min"},"distance":{"value":29,"text":"29 m"},"end_location":{"lng":-3.676520000000001,"lat":40.43357},"polyline":{"points":"ecxuFjamUs@C"},"travel_mode":"DRIVING","start_location":{"lng":-3.676540000000001,"lat":40.43331000000001}},{"html_instructions":"Take the 1st <b>right<\/b> onto <b>Calle de Maldonado<\/b>","duration":{"value":62,"text":"1 min"},"distance":{"value":266,"text":"0.3 km"},"end_location":{"lng":-3.6734,"lat":40.43345},"polyline":{"points":"ydxuFfamUPkG@a@JuGGk@"},"travel_mode":"DRIVING","start_location":{"lng":-3.676520000000001,"lat":40.43357}},{"html_instructions":"Take the 2nd <b>right<\/b> onto <b>Calle de Fco. Silvela<\/b>","duration":{"value":57,"text":"1 min"},"distance":{"value":245,"text":"0.2 km"},"end_location":{"lng":-3.671830000000001,"lat":40.4316},"polyline":{"points":"adxuFvmlURQl@e@zAoATSTMf@]pBeBLK"},"travel_mode":"DRIVING","start_location":{"lng":-3.6734,"lat":40.43345}},{"html_instructions":"Slight <b>right<\/b> onto <b>Calle de Francisco Silvela<\/b>","duration":{"value":51,"text":"1 min"},"distance":{"value":437,"text":"0.4 km"},"end_location":{"lng":-3.66913,"lat":40.42827},"polyline":{"points":"oxwuF|clUJADAFCbBkAjAeA`BoA^[XUPMLMFEnAeALIdBuAZUPMXS"},"travel_mode":"DRIVING","start_location":{"lng":-3.671830000000001,"lat":40.4316}},{"html_instructions":"Turn <b>right<\/b> onto <b>Pl. de Manuel Becerra<\/b>","duration":{"value":32,"text":"1 min"},"distance":{"value":154,"text":"0.2 km"},"end_location":{"lng":-3.668380000000001,"lat":40.42827},"polyline":{"points":"ucwuF`skU@?@@@@@?@@@?FBD?F@FAFAFADEDCFGDGDIBI@I@K@KAI?IAGAGAGCGCC?ACEEEIGGEICI?I?G@IBGD"},"travel_mode":"DRIVING","start_location":{"lng":-3.66913,"lat":40.42827}},{"html_instructions":"Turn <b>right<\/b> onto <b>Calle Alcala<\/b>","duration":{"value":61,"text":"1 min"},"distance":{"value":709,"text":"0.7 km"},"end_location":{"lng":-3.66107,"lat":40.43126000000001},"polyline":{"points":"ucwuFjnkUo@}A_AcCiEeLKYq@mBYq@[o@u@kB?KAQ]aAI[EQQu@Gc@QwAM_BEa@AU"},"travel_mode":"DRIVING","start_location":{"lng":-3.668380000000001,"lat":40.42827}},{"html_instructions":"Turn <b>right<\/b> onto the ramp to <b>M-30\/A-3\/A-4<\/b>","duration":{"value":44,"text":"1 min"},"distance":{"value":453,"text":"0.5 km"},"end_location":{"lng":-3.660580000000001,"lat":40.42720000000001},"polyline":{"points":"kvwuFt`jU\\CjIg@nBKD?rCOvDY"},"travel_mode":"DRIVING","start_location":{"lng":-3.66107,"lat":40.43126000000001}},{"html_instructions":"Continue straight","duration":{"value":45,"text":"1 min"},"distance":{"value":1009,"text":"1.0 km"},"end_location":{"lng":-3.659120000000001,"lat":40.41822000000001},"polyline":{"points":"_}vuFr}iUzKgBvCe@t@KvGm@hDUbAMPCZElAM|D_@^En@EjBEd@Df@DnAD"},"travel_mode":"DRIVING","start_location":{"lng":-3.660580000000001,"lat":40.42720000000001}},{"html_instructions":"Take exit <b>7B<\/b> to merge onto <b>M-23<\/b> toward <b>Vicálvaro\/R-3\/Valencia<\/b>","duration":{"value":117,"text":"2 mins"},"distance":{"value":2552,"text":"2.6 km"},"end_location":{"lng":-3.63392,"lat":40.41499},"polyline":{"points":"{duuFntiUFHB@^PDBFFFDDFDFBDDFBH@BBJ@F@H?F@H?FAH?FAF?FADAFAFCDADADEFCFEDEDEDEBEBEBGBE@G@E@G?G?EAG?GCGAGCEEGEEEEEEGEICIEIAKCKAK?K?MDgA?G?E?KJq@BODY@CBODW?C@I@AFe@F_@V{An@gED[@C?EHe@Ho@@G?A^{BjA}HF[tAiGzBwIdAcEVgApAuFr@sEF]Ju@DW?EDUHa@f@qEFo@f@iGXmHDgFCgGG{DM

And finally, I decode the received string this way, which I found in plenty of answers here in Stack Overflow:

private ArrayList<LatLng> decodePoly(String encoded) {

    Log.i("Location", "String received: "+encoded);
    ArrayList<LatLng> poly = new ArrayList<LatLng>();
    int index = 0, len = encoded.length();
    int lat = 0, lng = 0;

    while (index < len) {
        int b, shift = 0, result = 0;
        do {
            b = encoded.charAt(index++) - 63;
            result |= (b & 0x1f) << shift;
            shift += 5;
        } while (b >= 0x20);
        int dlat = ((result & 1) != 0 ? ~(result >> 1) : (result >> 1));
        lat += dlat;

        shift = 0;
        result = 0;
        do {
            b = encoded.charAt(index++) - 63;
            result |= (b & 0x1f) << shift;
            shift += 5;
        } while (b >= 0x20);
        int dlng = ((result & 1) != 0 ? ~(result >> 1) : (result >> 1));
        lng += dlng;

        LatLng p = new LatLng((int) (((double) lat /1E5)* 1E6), (int) (((double) lng/1E5   * 1E6)));
        poly.add(p);
    }
   
   for(int i=0;i<poly.size();i++){
       Log.i("Location", "Point sent: Latitude: "+poly.get(i).latitude+" Longitude: "+poly.get(i).longitude);
   }
    return poly;
}

This is where I think the error is, because I set a for to see the points that are being decoded, and the answer is something like:

04-10 13:33:54.578: I/Location(25065): Point sent: Latitude: 90.0 Longitude: -110.0
04-10 13:33:54.578: I/Location(25065): Point sent: Latitude: 90.0 Longitude: -120.0
04-10 13:33:54.578: I/Location(25065): Point sent: Latitude: 90.0 Longitude: 100.0
04-10 13:33:54.578: I/Location(25065): Point sent: Latitude: 90.0 Longitude: 120.0
04-10 13:33:54.578: I/Location(25065): Point sent: Latitude: 90.0 Longitude: 40.0

Obviosly, these points are badly parsed. The result is, no line drawn anywhere.

My question is... does anybody know how to parse the answer from Google, and translate it to LatLng objects (NOT GeoPoints Objects)?

EDIT:

This is the String that is about to be decoded:

04-10 13:50:51.608: I/Location(25065): String to decode: gcxuFjamUq@CPkGLwHJ}@hCuBdEeDf@SrJuHnG{Ed@Fr@q@AmAm@k@e@DaIaSiEuLqAwJ~Mw@~ZwDtZuC`FKbCVjA`ANdAs@nBw@Py@]c@oAT}DhC{PlBcM~GaY|DyS`BmPRy]yBu\UsNvAqObAuEnC}KpAqIh@gK[}NcByWXoRt@}GlDeWvFcq@nFi`@~G}\|@aDdDoLn`@}gAfe@upA`JyV|CiN~@uId@{ZyBwVkBmOwB{^AsNt@iWb@qYcCyS_DkPmAaK{@gRN{RbCkWrFgUbHmPxLsP|JwIpPwN|G}JfE}I|C_KlCsNvAwSFaQj@i]jCmXbHg\pIgSpGaLnN}P`JsHfVaNbQkHtZyIrOsCnTaCx\aA`i@b@nSRz]K`J{AxJiDfNkJjU{WbKkJpGeEpVkPpEiEzMcQjQw]nJyM~IoJ`IoKnH{NrOaWtIeJdNgNrLcPjKqOhIuI~MeJxKgErR{Cn]EjQkBjHqBfJeE|\yTlI_JvEqJxHsS|Xuc@~IeR|EiRr@kEz@sNOoL_B}Lk@mCsFgKkCoGwAsG]}N`CuO`D}GjHoHtFwC|HgBhFg@fJ}BtHaF~D_FjLqSfGqE|FcBnGEnEv@xK`BzNeAxQfAlJ{@jIwEjE}FvBuF|B{NtCsNrBgEdHiIdGuIxDoFpGkFhDaBrHyBbF[pHPlDg@jEuBnCkCtFgKjEqEfIcGpC{D|GoNdAcExAkSjBgDrCsBpDa@vHLfSAfBk@nE_F|Im`@tO_ZxCyEzJmKtHyElG_ClZeF|j@sIlQaD|EuChGkHnK{MdIsMfPwa@bDeJpD_RpFsz@nA}IrCqIrYoa@|BeGlEiYpD}HbKoRfCyKdBwUjEmo@zBkKzDyJvCeQdB}LJqGmAqQJ_Gx@uFtHkO`ImMfMmXtFySpCkXRuN]aYVoT~@}LxB{NvD{OjJaYbFsLnCcEtLoMfJyXzIiXbIiL~D_EzQcMjDeF|AcFhA_M`BkXpC_KjCsDnDeClQcHvEgElFqJfF_Jp\mWzd@k]`WkWlVy`@nOcTz`@_s@rP_ZbNkQ~Tu^jJaUhCuIpHgUrPi\zd@uq@~McQh\e]vTaU~KcF~KoGhG{KzF{HzGmDbDwBfDiEzB}GbEmTrBsHpCuFxe@oq@xb@el@tDgC`ZeJnXkHrKmB~EeB~C{A`Ce@bCUrBoAnAuGSqIn@yEhDeOrCuQ|A}EvAwFdDgAfGSrHUJ\p@Bj@_A|Ao@tCsD|C}Rj@wAdCuW~Ds]VCJw@OUpEc_@jDKbDwB

If with LatLng format you mean the way the LatLng objects are created, you can see in the decodePoly method. These latLng objects are added to the PolylineOptions objects, and the this added to the map, this way:

PolylineOption ruta=new PolylineOptions();
for(int i=0;i<puntos.size();i++){
ruta.add(new LatLng(puntos.get(i).latitude, puntos.get(i).longitude));                      
}//puntos is an array where the array returned by the decodePoly method are stored                  
ruta.color(Color.RED).width(7);                     
Polyline polygon=mapa.addPolyline(ruta);
Pantheas answered 10/4, 2013 at 11:41 Comment(6)
Can you show what is your response ? and your latlong formate ?Quicken
Add an example response. Most likely, it's the 1E6 format that's causing the trouble.Eyeful
I have tried to just do new LatLng(lat,lng) in decodePoly method, and no result. Also tried removing the 1E6 part, and no result...Pantheas
That won't work since the method is doing conversion anyways. Just take the values from your JSON and put them (raw) in the LatLng-constructor.Eyeful
I posted a similar answer to this question on another post for Android/Java. Decoding the poly and returning Latlng instead of Geopoints.Mikimikihisa
The decode function gives StringIndexOutOfBoundException for the string "ipswCw{cjN@@@f@Fd@Ff@Hb…@zECxEpKpJDDFD@H@FAHAN" Any idea why is it happening?Owain
P
28

I changed the decodePoly that I was using for this one I found after a long search in Google, and now the route is drawn properly.

http://wptrafficanalyzer.in/blog/route-between-two-locations-with-waypoints-in-google-map-android-api-v2/

Changing

LatLng p = new LatLng((int) (((double) lat /1E5)* 1E6), (int) (((double) lng/1E5   * 1E6)));

for

LatLng p = new LatLng((((double) lat / 1E5)),(((double) lng / 1E5)));

And now works.

Pantheas answered 10/4, 2013 at 15:38 Comment(2)
android-maps-utils has utility methods for this and many other useful things you can do on Google MapsWert
I also had to make that change that @Pantheas mentioned, but I didn't see his note about that change until I spent 20 minutes figuring it out myself and then shaking my head once I realize he already post the fix for it. LOL I'm a dummy.Taster
F
31

For those who need this now, there's an open-source library with a lot of useful stuff regarding the Google Maps Android API, including decoding and encoding of polylines.

Check it out at Android Maps Utils and Android Maps Util Github. For decoding and encoding use:

PolyUtil.decode(String encodedPath);
PolyUtil.encode(List<LatLng> path);
Flintlock answered 22/7, 2016 at 23:19 Comment(4)
This should now be the correct way to go instead of writing the decoder yourself.Tiptoe
the dependency looks old it has compile written is it deprecated or not??Caleb
@PembaTamang, library has been updated recently, including gradle update.Flintlock
thats great, hey could you help me with this stackoverflow.com/q/58636511/10800406Caleb
P
28

I changed the decodePoly that I was using for this one I found after a long search in Google, and now the route is drawn properly.

http://wptrafficanalyzer.in/blog/route-between-two-locations-with-waypoints-in-google-map-android-api-v2/

Changing

LatLng p = new LatLng((int) (((double) lat /1E5)* 1E6), (int) (((double) lng/1E5   * 1E6)));

for

LatLng p = new LatLng((((double) lat / 1E5)),(((double) lng / 1E5)));

And now works.

Pantheas answered 10/4, 2013 at 15:38 Comment(2)
android-maps-utils has utility methods for this and many other useful things you can do on Google MapsWert
I also had to make that change that @Pantheas mentioned, but I didn't see his note about that change until I spent 20 minutes figuring it out myself and then shaking my head once I realize he already post the fix for it. LOL I'm a dummy.Taster
P
11

Same in Javascript

    function decodePolyline(encoded) {
        if (!encoded) {
            return [];
        }
        var poly = [];
        var index = 0, len = encoded.length;
        var lat = 0, lng = 0;

        while (index < len) {
            var b, shift = 0, result = 0;

            do {
                b = encoded.charCodeAt(index++) - 63;
                result = result | ((b & 0x1f) << shift);
                shift += 5;
            } while (b >= 0x20);

            var dlat = (result & 1) != 0 ? ~(result >> 1) : (result >> 1);
            lat += dlat;

            shift = 0;
            result = 0;

            do {
                b = encoded.charCodeAt(index++) - 63;
                result = result | ((b & 0x1f) << shift);
                shift += 5;
            } while (b >= 0x20);

            var dlng = (result & 1) != 0 ? ~(result >> 1) : (result >> 1);
            lng += dlng;

            var p = {
                latitude: lat / 1e5,
                longitude: lng / 1e5,
            };
            poly.push(p);
        }
        return poly;
    }
Pruitt answered 2/2, 2016 at 11:55 Comment(0)
U
3

This method is useful for decoding polyline

 private List<LatLng> decodePoly(String encoded) {
    List<LatLng> poly = new ArrayList<>();
    int index = 0, len = encoded.length();
    int lat = 0, lng = 0;

    while (index < len) {
        int b, shift = 0, result = 0;
        do {
            b = encoded.charAt(index++) - 63;
            result |= (b & 0x1f) << shift;
            shift += 5;
        } while (b >= 0x20);
        int dlat = ((result & 1) != 0 ? ~(result >> 1) : (result >> 1));
        lat += dlat;

        shift = 0;
        result = 0;
        do {
            b = encoded.charAt(index++) - 63;
            result |= (b & 0x1f) << shift;
            shift += 5;
        } while (b >= 0x20);
        int dlng = ((result & 1) != 0 ? ~(result >> 1) : (result >> 1));
        lng += dlng;

        LatLng p = new LatLng((((double) lat / 1E5)),
                (((double) lng / 1E5)));
        poly.add(p);
    }

    return poly;
}

This is how i'm getting polyline

JSONObject poly = route.getJSONObject("overview_polyline");
String polyline = poly.getString("points");
polyLineList = decodePoly(polyline);
Unpleasant answered 11/1, 2018 at 6:24 Comment(1)
new LatLng((((double) lat / 1E5)), (((double) lng / 1E5))) -- this happens to be a private constructor. Maybe it was recently made private in an update.Stithy
K
2

For those who use the Maps JavaScript SDK use @googlemaps/polyline-codec

npm install @googlemaps/polyline-codec
import { decode, encode } from "@googlemaps/polyline-codec";

const encoded = "_p~iF~ps|U_ulLnnqC_mqNvxq`@";
console.log(decode(encoded, 5));
// [
//   [38.5, -120.2],
//   [40.7, -120.95],
//   [43.252, -126.453],
// ]

const path = [
  [38.5, -120.2],
  [40.7, -120.95],
  [43.252, -126.453],
];
console.log(encode(path, 5));
// "_p~iF~ps|U_ulLnnqC_mqNvxq`@"
Keim answered 27/12, 2022 at 12:0 Comment(0)
T
1

Here is the implementation in iOS, for anybody who is curious, which is entirely based upon @Fustigador's answer, but converted to iOS. Note that I have not formatted the code accordingly. I only added some variables for the sake of returning the promised object.

- (GMSPath *)decodedPolylinePathFromEncodedPolylineString:(NSString *)encodedPolylineString {


    NSString *decodedPolylineString = @"";
    GMSMutablePath *decodedPolylinePath = [GMSMutablePath new];
    CLLocationCoordinate2D decodedCoordinate;
    CLLocationDegrees latitude, longitude;

    int index = 0;
    NSUInteger len = encodedPolylineString.length;

    int lat = 0, lng = 0;

    while (index < len) {
        int b, shift = 0, result = 0;
        do {

            b = [encodedPolylineString characterAtIndex:index++] - 63;
            result |= (b & 0x1f) << shift;
            shift += 5;
        } while (b >= 0x20);
        int dlat = ((result & 1) != 0 ? ~(result >> 1) : (result >> 1));
        lat += dlat;

        shift = 0;
        result = 0;
        do {
            b = [encodedPolylineString characterAtIndex:index++] - 63;
            result |= (b & 0x1f) << shift;
            shift += 5;
        } while (b >= 0x20);
        int dlng = ((result & 1) != 0 ? ~(result >> 1) : (result >> 1));
        lng += dlng;

        latitude =  (((double) lat / 1E5));
        longitude = (((double) lng / 1E5));

        decodedCoordinate = CLLocationCoordinate2DMake(latitude, longitude);

        decodedPolylineString = [NSString stringWithFormat:@"%f%f", latitude, longitude];
        ;
        NSLog(@"%@",decodedPolylineString);

        [decodedPolylinePath addCoordinate:decodedCoordinate];
    }


    return decodedPolylinePath;
}
Taster answered 3/1, 2016 at 6:5 Comment(0)
L
1

Same in Pascal (Delphi):

function DecodeLine(inputstring: String): String;
var
  lat, lon, lat_f, lon_f: Double;
  index: Integer;
  len: Integer;
  b: Integer;
  shift: Integer;
  decodeResult: Integer;
  encoded: String;
  dlat: Integer;
  dlng: Integer;
begin
  Result := '';

  encoded := inputstring;
  len := Length(encoded);
  index := 1;
  lat := 0;
  lon := 0;

  while (index <= len) do begin
    b := $20;
    shift := 0;
    decodeResult := 0;

    while (b >= $20) do begin
      b := ord(encoded[index]);
      b := b - 63;
      inc(index);
      decodeResult := decodeResult or (b and $1f) Shl shift;
      shift := shift + 5;
    end;

    if (decodeResult and 1) <> 0 then
      dlat := not (decodeResult shr 1)
    else
      dlat := decodeResult shr 1;
    lat := lat+dlat;

    shift := 0;
    decodeResult := 0;
    b := $20;
    while (b >= $20) do begin
      b := ord(encoded[index]);
      b := b-63;
      inc(index);
      decodeResult := decodeResult or (b and $1f) Shl shift;
      shift := shift + 5;
    end;

    if (decodeResult and 1) <> 0 then
      dlng := not (decodeResult shr 1)
    else
      dlng := decodeResult shr 1;
    lon := lon + dlng;

    { The coordinates of the point are used for our purposes }
    lon_f := lon/100000.0;
    lat_f := lat/100000.0;

    if Result <> '' then
      Result := Result + ', ';
    Result := Result + '(' + FloatToStr(lon_f) + ' - ' + FloatToStr(lat_f) + ')';
  end; //while
end;


procedure TForm1.Button1Click(Sender: TObject);
var
  str: String;
begin
  // Do stuff
  str := DecodeLine('ibgqGq}ycIMvM');
  ShowMessage(str);
end;
Letha answered 1/6, 2017 at 7:23 Comment(0)
P
0
private List<GeoPoint> decodePoly(String encoded){
     
     List<GeoPoint> poly = new ArrayList<GeoPoint>();
     int index = 0, len = encoded.length();
     int lat = 0, lng = 0;

     while (index < len){
            int b, shift = 0, result = 0;
            do{
            
               b = encoded.charAt(index++) - 63;
               result |= (b & 0x1f) << shift;
               shift += 5;
            }while(b >= 0x20);
            int dlat = ((result & 1) != 0 ?~(result >> 1) : (result >> 1));
            lat += dlat;

            shift = 0;
            result = 0;
            do{
               b = encoded.charAt(index++) - 63;
               result |= (b & 0x1f) << shift;
               shift += 5;
            }while(b >= 0x20);
            int dlng = ((result & 1) != 0 ?~(result >> 1) : (result >> 1));
            lng += dlng;

            GeoPoint p = new GeoPoint((int) (((double) lat / 1E5) * 1E6), (int) (((double) lng / 1E5) * 1E6));
            poly.add(p);
     } 
     return poly;
}
Paleobiology answered 17/5, 2021 at 6:36 Comment(1)
While this code snippet may solve the problem, it doesn't explain why or how it answers the question. Please include an explanation for your code, as that really helps to improve the quality of your post. Remember that you are answering the question for readers in the future, and those people might not know the reasons for your code suggestion. You can use the edit button to improve this answer to get more votes and reputation!Headache

© 2022 - 2024 — McMap. All rights reserved.