9

I would like to use this java code inside Mathematica using J/Link. It converts Google Maps API Encoded Polylines to a list of points. I tried the Mathematica tutorial but got lost.

There is the Java code:

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;
}

As example:

wjiGtdpcNrAlBJZ

should return

43.64175, -79.38651
43.64133, -79.38706
43.64127, -79.3872

I have no experience in Java. The code came from here.

Szabolcs
  • 234,956
  • 30
  • 623
  • 1,263
Murta
  • 26,275
  • 6
  • 76
  • 166
  • 4
    This answer by Leonid is exactly what you want. – rm -rf Oct 03 '12 at 22:01
  • I sow the post, tks. But it do not worked (the original post test code worked). I just replaced the code above in the argument jlcsCode and tried to compile it. I should have done something more? – Murta Oct 04 '12 at 02:39
  • Leonid's code tests for the presence of a class, and this is only a function. Also, if it were a class, we would still need access to the GeoPoint class, and I have no idea where to get it. Lastly, Mark McClure has Mathematica code to do perform the decoding, so you could use his – rcollyer Oct 04 '12 at 04:15
  • 1
    @rcollyer Actually, my Mathematica code only encodes polylines, rather than decoding them. The decoding scheme is bitwise operation based and would likely be a pain to translate. I responded with instructions on accessing a Java class, though. – Mark McClure Oct 04 '12 at 08:06
  • Also, I believe the GeoPoint class is part of Google's Android map development kit, which seems a bit heavyweight if you just want to decode a polyline. – Mark McClure Oct 04 '12 at 08:08
  • @MarkMcClure to be honest, I didn't look that deep. Thanks. :) – rcollyer Oct 04 '12 at 10:33

2 Answers2

11

Comment

I've placed a Notebook version of this post on my webspace here: http://facstaff.unca.edu/mcmcclur/polylineDecoder.zip

The Notebook is actually contained in a ZIP file that also contains all the Java class files necessary to get this code to work. The Notebook sets the Java class relative to it's own directory using AddToClassPath[NotebookDirectory[]], so everything should just run without difficulty.

Answer

Here's my preferred java polyline decoder: https://github.com/scoutant/polyline-decoder

The advantage of this version is that it is self-contained, while Jeffery's depends on a GeoPoint class defined in a rather large SDK.

After compiling the files in src/main/java/org/scoutant/polyline, you can use the decoder like so:

Needs["JLink`"];
InstallJava[];
AddToClassPath["scoutant-polyline-decoder-76406ba/src/main/java/"];
polylineDecoder = JavaNew["org.scoutant.polyline.PolylineDecoder"];
decoded = polylineDecoder@decode["wjiGtdpcNrAlBJZ"];
length = decoded@size[];
Table[
 point = decoded@get[k];
 {point@getLat[], point@getLng[]},
 {k, 0, length - 1}]

(* Out:
   {{1.3638, -79.3865}, {1.36338, -79.3871}, {1.36332, -79.3872}}
*)

While the latitudes all disagree with your expected result, it agrees with my javascript decoder: http://facstaff.unca.edu/mcmcclur/GoogleMaps/EncodePolyline/decode.html

It also agrees with Google's own polyline decoder utility: https://developers.google.com/maps/documentation/utilities/polylineutility

An example using imported directions

As a fun example, we could download some driving directions from Google Maps, extract the encoded polyline string, and display it on a CountryData map.

Needs["JLink`"];
InstallJava[];
AddToClassPath[NotebookDirectory[]];
polylineDecoder = JavaNew[
 "org.scoutant.polyline.PolylineDecoder"
];
jsonString = StringDrop[Import[
  "https://maps.google.com/maps?saddr=NY,+NY&daddr=LA,+CA&output=json"
], 9];
json = ImportString[jsonString, "JSON"];
polylines = "polylines" /. 
  Cases[json, HoldPattern["polylines" -> _], Infinity];
pointStrings = Table["points" /. polyline, {polyline, polylines}];
pointString = First[pointStrings];
pointString = StringReplace[pointString, "\\\\" -> "\\"];
decoded = polylineDecoder@decode[pointString];
length = decoded@size[];
pts = {#@getLng[], #@getLat[]} & /@ decoded@toArray[];
Show[{
  CountryData["UnitedStates", {"Shape", "Equirectangular"}],
  Graphics[{{Blue, Line[pts]},
   {PointSize[Large], Green, Point[First[pts]], Red, 
    Point[Last[pts]]}}]
}]

enter image description here

Mark McClure
  • 32,469
  • 3
  • 103
  • 161
  • Tks Mark!. Very good post, exact what I need. Just one more question: I download the file "scoutant-polyline-decoder-76406ba" from github as you pointed, but how I compile it to use in Mathematica? It's using "ReinstallJava[ClassPath -> "~/MyJavaDir/MyPackage.jar:~/MyJavaDir"]" ? Some clue? I use Mac OS. – Murta Oct 04 '12 at 11:32
  • @Murta Awesome! If you use Mac OS, open the terminal application (Go menu -> Applications, then terminal is in Utilities). You should get a command line interface. Use the cd command to get to the polyline directory and then type "javac *". If you have a java compiler, which you might, then this should generate java class files. If not, you can get one by installing XCode. If that's all a bit too much, I'd be happy to compile and post the executables with a working notebook in a ZIP file. – Mark McClure Oct 04 '12 at 11:40
  • HI @Mark. I compiled it, and 3 .class files where created together with pre existent .java. Sorry, but I don't know how to connect it with your first code. The notebook is welcome. Tks in advance! – Murta Oct 04 '12 at 12:13
  • @Murta If you've got the .class files, then you should be ready to go! The only issue I can imagine is at the AddToClassPath step. The easiest thing to do would be to save your working notebook right into the scoutant-polyline-decoder-76406ba directory. (I wonder if the "76506ba" is randomly generated?). Then the AddToClassPath call should be AddToClassPath["src/main/java/"]. I'll probably create the notebook anyway, but not until later. – Mark McClure Oct 04 '12 at 12:22
  • Also, note that I've made some edits to the code in the post to fix a couple of errors. – Mark McClure Oct 04 '12 at 12:23
  • @Murta I'm not sure if you got the Java code working or not but I've gone ahead and posted a link to a ZIP file that should ease the process. See the new comment at the top of the post. – Mark McClure Oct 04 '12 at 18:25
  • @Mart it's Perfect! Beautiful notebook! Works like a charm. Now I will study it to see what I was missing It's is a good example to learn java implementation in Mathematica. Tks a lot. – Murta Oct 04 '12 at 18:43
  • @MarkMcClure Was a great help for me today! +1 – PlatoManiac Dec 03 '13 at 21:33
  • @PlatoManiac Glad to hear it! – Mark McClure Dec 20 '13 at 20:27
7

I recently implemented a native Mathematica decoder for the encoded Google polyline strings returned during a request to its API. This uses Compile and I found it faster than the above J/Link based solution. I guess it could have been even faster if we could compile the BitShiftLeft and BitShiftRight. But I guess these two functions are pretty optimized in Mathematica. My compiled version for these two functions were slower so decided to stick with the built-ins.

The decoder:

Clear[Com];
Com = Compile[ {{barray, _Integer, 1}},
   Module[ {index = 1, lat = 0, lang = 0, latlng = {{0., 0.}}, b, 
     shift, result, dlat, bRight},
    While[index <=  Length@barray,
     b = shift = result = 0;
     b = -63 + barray[[index++]];
     result = BitXor[result, BitShiftLeft[BitAnd[b, 31], shift]];
     shift += 5;
     While[b >= 32,
      b = -63 + barray[[index++]];
      result = BitXor[result, BitShiftLeft[BitAnd[b, 31], shift]];
      shift += 5;];
     bRight = BitShiftRight[result, 1];
     lat += If[BitAnd[result, 1] > 0, BitNot[bRight], bRight];
     shift = result = 0;
     b = -63 + barray[[index++]];
     result = BitXor[result, BitShiftLeft[BitAnd[b, 31], shift]];
     shift += 5;
     While[b >= 32,
      b = -63 + barray[[index++]];
      result = BitXor[result, BitShiftLeft[BitAnd[b, 31], shift]];
      shift += 5;];
     bRight = BitShiftRight[result, 1];
     lang += If[BitAnd[result, 1] > 0, BitNot[bRight], bRight];
     AppendTo[latlng, {lang*10.^-5, lat*10.^-5}];
     ]; latlng], RuntimeOptions -> "Speed", CompilationTarget -> "C", 
   RuntimeAttributes -> {Listable}
         , CompilationOptions -> {"ExpressionOptimization" -> True}
   ];
MathematicaDecode[encoded_?StringQ] := Block[{barray},
   barray = ToCharacterCode@encoded;
   (Rest@Com[barray])
   ];

Utility Function:

Lets take a route connecting several cities given as a list.

route2 = {"Vienna,Austria", "Erlangen,Germany","Nurnberg,Germany","Heilbronn,Germany",
"Heidelberg,Germany", "Mannheim,Germany","Ludwigshafen,Germany", "Berlin,Germany"};

Here is a function that takes routes like above as input and forms the API request URL and do a respective data fetch from Google. For free users the map API serves up to 11-13 way-points requests in one single call.

UrlData[route_?(VectorQ[#, StringQ] &)] /; (2 <=  Length@route < 11) :=
   Block[{WaypointSeperatorPosition, url, urlData, encoded},
   WaypointSeperatorPosition = 
    Transpose@{1 + Range[-2 + Length@route]};
   url = "http://maps.googleapis.com/maps/api/directions/json" <>
     "?origin=" <> First@route <>
     "&destination=" <> Last@route <>
     "&waypoints=" <> 
     StringJoin[Most@Insert[route[[2 ;; -2]], "|", WaypointSeperatorPosition]] <>
   "&sensor=false&mode=driving&units=metric";
   URLFetch[url]
   ];

Testing:

Now we can test our decoder to extract the longitude and latitude pairs of each points that constitute the Google polyline.

dat = UrlData[route2];
strings=(Flatten@(
ImportString[dat,{"JSON","Data","routes",1,"legs",All,"steps",All,"polyline","points"}]));
res = MathematicaDecode[#] & /@strings; // AbsoluteTiming

{2.685532, Null}

The J/Link version

res1 = With[{pointString = #}, 
      decoded = polylineDecoder@decode[pointString];
      pts = {#@getLng[], #@getLat[]} & /@ decoded@toArray[];
      pts] & /@ strings; // AbsoluteTiming

{9.156631, Null}

Both the results agree with each other.

Norm@(Chop@(Flatten[res1, 1] - Flatten[res, 1]))

0

Visualize:

Here goes the route from Vienna to Berlin. With all the points on the polygon one could compute the road curvature/bending at each subsequent stretches on the polyline. enter image description here

Szabolcs
  • 234,956
  • 30
  • 623
  • 1,263
PlatoManiac
  • 14,723
  • 2
  • 42
  • 74