26

Possible Duplicate:
Google Maps output=kml broken?

I started to get Error when I'm trying to retrieve Google Directions using KML data since few days. The Error seems that the URL I'm requesting doesn't retreieve KML data anymore, it returns a whole page. I followed this in order to achieve that.

What is the solution for this? Is there any alternatives?

Community
  • 1
  • 1
Hesham Saeed
  • 5,358
  • 7
  • 37
  • 57

1 Answers1

90

Update:

Since Android Google Maps v2 is now used, the code needs to be adjusted to work on v2 maps which can be found here.


Original Answer:

This way of extracting the Google Directions from Google by parsing the KML file is no longer available since 27 July 2012 (because Google has changed the structure of retrieving Google Directions, now you can only get it by JSON or XML), it is time to migrate your code to JSON instead of KML.

I did it by creating 6 classes like this:

Parser.java:

public interface Parser {
        public Route parse();
}

XMLParser.java:

public class XMLParser {
        // names of the XML tags
        protected static final String MARKERS = "markers";
        protected static final String MARKER = "marker";

        protected URL feedUrl;

        protected XMLParser(final String feedUrl) {
                try {
                        this.feedUrl = new URL(feedUrl);
                } catch (MalformedURLException e) {
                        //Log.e(e.getMessage(), "XML parser - " + feedUrl);
                }
        }

        protected InputStream getInputStream() {
                try {
                        return feedUrl.openConnection().getInputStream();
                } catch (IOException e) {
                        //Log.e(e.getMessage(), "XML parser - " + feedUrl);
                        return null;
                }
        }
}

Segment.java:

public class Segment {
        /** Points in this segment. **/
        private GeoPoint start;
        /** Turn instruction to reach next segment. **/
        private String instruction;
        /** Length of segment. **/
        private int length;
        /** Distance covered. **/
        private double distance;

        /**
         * Create an empty segment.
         */

        public Segment() {
        }


        /**
         * Set the turn instruction.
         * @param turn Turn instruction string.
         */

        public void setInstruction(final String turn) {
                this.instruction = turn;
        }

        /**
         * Get the turn instruction to reach next segment.
         * @return a String of the turn instruction.
         */

        public String getInstruction() {
                return instruction;
        }

        /**
         * Add a point to this segment.
         * @param point GeoPoint to add.
         */

        public void setPoint(final GeoPoint point) {
                start = point;
        }

        /** Get the starting point of this 
         * segment.
         * @return a GeoPoint
         */

        public GeoPoint startPoint() {
                return start;
        }

        /** Creates a segment which is a copy of this one.
         * @return a Segment that is a copy of this one.
         */

        public Segment copy() {
                final Segment copy = new Segment();
                copy.start = start;
                copy.instruction = instruction;
                copy.length = length;
                copy.distance = distance;
                return copy;
        }

        /**
         * @param length the length to set
         */
        public void setLength(final int length) {
                this.length = length;
        }

        /**
         * @return the length
         */
        public int getLength() {
                return length;
        }

        /**
         * @param distance the distance to set
         */
        public void setDistance(double distance) {
                this.distance = distance;
        }

        /**
         * @return the distance
         */
        public double getDistance() {
                return distance;
        }

}

Route.java:

public class Route {
        private String name;
        private final List<GeoPoint> points;
        private List<Segment> segments;
        private String copyright;
        private String warning;
        private String country;
        private int length;
        private String polyline;

        public Route() {
                points = new ArrayList<GeoPoint>();
                segments = new ArrayList<Segment>();
        }

        public void addPoint(final GeoPoint p) {
                points.add(p);
        }

        public void addPoints(final List<GeoPoint> points) {
                this.points.addAll(points);
        }

        public List<GeoPoint> getPoints() {
                return points;
        }

        public void addSegment(final Segment s) {
                segments.add(s);
        }

        public List<Segment> getSegments() {
                return segments;
        }

        /**
         * @param name the name to set
         */
        public void setName(final String name) {
                this.name = name;
        }

        /**
         * @return the name
         */
        public String getName() {
                return name;
        }

        /**
         * @param copyright the copyright to set
         */
        public void setCopyright(String copyright) {
                this.copyright = copyright;
        }

        /**
         * @return the copyright
         */
        public String getCopyright() {
                return copyright;
        }

        /**
         * @param warning the warning to set
         */
        public void setWarning(String warning) {
                this.warning = warning;
        }

        /**
         * @return the warning
         */
        public String getWarning() {
                return warning;
        }

        /**
         * @param country the country to set
         */
        public void setCountry(String country) {
                this.country = country;
        }

        /**
         * @return the country
         */
        public String getCountry() {
                return country;
        }

        /**
         * @param length the length to set
         */
        public void setLength(int length) {
                this.length = length;
        }

        /**
         * @return the length
         */
        public int getLength() {
                return length;
        }


        /**
         * @param polyline the polyline to set
         */
        public void setPolyline(String polyline) {
                this.polyline = polyline;
        }

        /**
         * @return the polyline
         */
        public String getPolyline() {
                return polyline;
        }

}

GoogleParser.java:

public class GoogleParser extends XMLParser implements Parser {
        /** Distance covered. **/
        private int distance;

        public GoogleParser(String feedUrl) {
                super(feedUrl);
        }

        /**
         * Parses a url pointing to a Google JSON object to a Route object.
         * @return a Route object based on the JSON object.
         */

        public Route parse() {
                // turn the stream into a string
                final String result = convertStreamToString(this.getInputStream());
                //Create an empty route
                final Route route = new Route();
                //Create an empty segment
                final Segment segment = new Segment();
                try {
                        //Tranform the string into a json object
                        final JSONObject json = new JSONObject(result);
                        //Get the route object
                        final JSONObject jsonRoute = json.getJSONArray("routes").getJSONObject(0);
                        //Get the leg, only one leg as we don't support waypoints
                        final JSONObject leg = jsonRoute.getJSONArray("legs").getJSONObject(0);
                        //Get the steps for this leg
                        final JSONArray steps = leg.getJSONArray("steps");
                        //Number of steps for use in for loop
                        final int numSteps = steps.length();
                        //Set the name of this route using the start & end addresses
                        route.setName(leg.getString("start_address") + " to " + leg.getString("end_address"));
                        //Get google's copyright notice (tos requirement)
                        route.setCopyright(jsonRoute.getString("copyrights"));
                        //Get the total length of the route.
                        route.setLength(leg.getJSONObject("distance").getInt("value"));
                        //Get any warnings provided (tos requirement)
                        if (!jsonRoute.getJSONArray("warnings").isNull(0)) {
                                route.setWarning(jsonRoute.getJSONArray("warnings").getString(0));
                        }
                        /* Loop through the steps, creating a segment for each one and
                         * decoding any polylines found as we go to add to the route object's
                         * map array. Using an explicit for loop because it is faster!
                         */
                        for (int i = 0; i < numSteps; i++) {
                                //Get the individual step
                                final JSONObject step = steps.getJSONObject(i);
                                //Get the start position for this step and set it on the segment
                                final JSONObject start = step.getJSONObject("start_location");
                                final GeoPoint position = new GeoPoint((int) (start.getDouble("lat")*1E6), 
                                        (int) (start.getDouble("lng")*1E6));
                                segment.setPoint(position);
                                //Set the length of this segment in metres
                                final int length = step.getJSONObject("distance").getInt("value");
                                distance += length;
                                segment.setLength(length);
                                segment.setDistance(distance/1000);
                                //Strip html from google directions and set as turn instruction
                                segment.setInstruction(step.getString("html_instructions").replaceAll("<(.*?)*>", ""));
                                //Retrieve & decode this segment's polyline and add it to the route.
                                route.addPoints(decodePolyLine(step.getJSONObject("polyline").getString("points")));
                                //Push a copy of the segment to the route
                                route.addSegment(segment.copy());
                        }
                } catch (JSONException e) {
                        Log.e(e.getMessage(), "Google JSON Parser - " + feedUrl);
                }
                return route;
        }

        /**
         * Convert an inputstream to a string.
         * @param input inputstream to convert.
         * @return a String of the inputstream.
         */

        private static String convertStreamToString(final InputStream input) {
        final BufferedReader reader = new BufferedReader(new InputStreamReader(input));
        final StringBuilder sBuf = new StringBuilder();

        String line = null;
        try {
            while ((line = reader.readLine()) != null) {
                sBuf.append(line);
            }
        } catch (IOException e) {
                Log.e(e.getMessage(), "Google parser, stream2string");
        } finally {
            try {
                input.close();
            } catch (IOException e) {
                Log.e(e.getMessage(), "Google parser, stream2string");
            }
        }
        return sBuf.toString();
    }

        /**
         * Decode a polyline string into a list of GeoPoints.
         * @param poly polyline encoded string to decode.
         * @return the list of GeoPoints represented by this polystring.
         */

        private List<GeoPoint> decodePolyLine(final String poly) {
                int len = poly.length();
                int index = 0;
                List<GeoPoint> decoded = new ArrayList<GeoPoint>();
                int lat = 0;
                int lng = 0;

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

                decoded.add(new GeoPoint(
                        (int) (lat*1E6 / 1E5), (int) (lng*1E6 / 1E5)));
                }

                return decoded;
                }
}

RouteOverlay.java:

public class RouteOverlay extends Overlay {
        /** GeoPoints representing this routePoints. **/
        private final List<GeoPoint> routePoints;
        /** Colour to paint routePoints. **/
        private int colour;
        /** Alpha setting for route overlay. **/
        private static final int ALPHA = 120;
        /** Stroke width. **/
        private static final float STROKE = 4.5f;
        /** Route path. **/
        private final Path path;
        /** Point to draw with. **/
        private final Point p;
        /** Paint for path. **/
        private final Paint paint;


        /**
         * Public constructor.
         * 
         * @param route Route object representing the route.
         * @param defaultColour default colour to draw route in.
         */

        public RouteOverlay(final Route route, final int defaultColour) {
                super();
                routePoints = route.getPoints();
                colour = defaultColour;
                path = new Path();
                p = new Point();
                paint = new Paint();
        }

        @Override
        public final void draw(final Canvas c, final MapView mv,
                        final boolean shadow) {
                super.draw(c, mv, shadow);

                paint.setColor(colour);
                paint.setAlpha(ALPHA);
                paint.setAntiAlias(true);
                paint.setStrokeWidth(STROKE);
                paint.setStyle(Paint.Style.STROKE);

                redrawPath(mv);
                c.drawPath(path, paint);
        }

        /**
         * Set the colour to draw this route's overlay with.
         * 
         * @param c  Int representing colour.
         */
        public final void setColour(final int c) {
                colour = c;
        }

        /**
         * Clear the route overlay.
         */
        public final void clear() {
                routePoints.clear();
        }

        /**
         * Recalculate the path accounting for changes to
         * the projection and routePoints.
         * @param mv MapView the path is drawn to.
         */

        private void redrawPath(final MapView mv) {
                final Projection prj = mv.getProjection();
                path.rewind();
                final Iterator<GeoPoint> it = routePoints.iterator();
                prj.toPixels(it.next(), p);
                path.moveTo(p.x, p.y);
                while (it.hasNext()) {
                        prj.toPixels(it.next(), p);
                        path.lineTo(p.x, p.y);
                }
                path.setLastPoint(p.x, p.y);
        }

}

And then you do this inside the Activity that includes the Map:

1-Add this function:

private Route directions(final GeoPoint start, final GeoPoint dest) {
    Parser parser;
    //https://developers.google.com/maps/documentation/directions/#JSON <- get api
    String jsonURL = "http://maps.googleapis.com/maps/api/directions/json?";
    final StringBuffer sBuf = new StringBuffer(jsonURL);
    sBuf.append("origin=");
    sBuf.append(start.getLatitudeE6()/1E6);
    sBuf.append(',');
    sBuf.append(start.getLongitudeE6()/1E6);
    sBuf.append("&destination=");
    sBuf.append(dest.getLatitudeE6()/1E6);
    sBuf.append(',');
    sBuf.append(dest.getLongitudeE6()/1E6);
    sBuf.append("&sensor=true&mode=driving");
    parser = new GoogleParser(sBuf.toString());
    Route r =  parser.parse();
    return r;
}

2- Add this in onCreate() function:

MapView mapView = (MapView) findViewById(R.id.mapview); //or you can declare it directly with the API key
Route route = directions(new GeoPoint((int)(26.2*1E6),(int)(50.6*1E6)), new GeoPoint((int)(26.3*1E6),(int)(50.7*1E6)));
RouteOverlay routeOverlay = new RouteOverlay(route, Color.BLUE);
mapView.getOverlays().add(routeOverlay);
mapView.invalidate();

EDIT: If you get an exception, please use directions() function in an AsyncTask to avoid network processing on the UI thread.

Hesham Saeed
  • 5,358
  • 7
  • 37
  • 57
  • +1 for great answer it help me. but what is US_API in StringBuffer sBuf = new StringBuffer(US_API); is it US_API = "http://maps.google.com/maps/api/directions/json?"; – Deepak Swami Aug 13 '12 at 07:16
  • @DeepakSwami Hi i have followed above but am getting force close could you help me on this issue am bit confused what about main activity code am getting error there only can you post that code also? – KAREEM MAHAMMED Aug 13 '12 at 07:44
  • @DeepakSwami: Yes this is it, I forgot to add it because I declared it as global variable in my Activity. – Hesham Saeed Aug 13 '12 at 10:07
  • @KAREEMMAHAMMED: Your main Activity should extend MapActivity class, and add the two last snippets (I edited one of them) of the code in it. If still you face a problem post the stacktrace or post another question about the part it is not wokring. – Hesham Saeed Aug 13 '12 at 10:11
  • @HeshamSaeed :Thanks for the response i have another question to ask you that i want to draw line between two locations initially it worked fine but when am running now am getting nullpointerexception at doc = db.parse(urlConnection.getInputStream()); here is the link http://stackoverflow.com/questions/11932797/want-to-draw-a-line-between-any-two-location-in-android where i have posted my source code and in that also i mentioned where am getting null value within the comments so please any one help me out from this error thanks in advance – KAREEM MAHAMMED Aug 13 '12 at 10:50
  • @KAREEMMAHAMMED: maybe you didn't understand the problem here, KML is no longer supported by Google, In your code you are using KML, the code I posted is the alternative of KML, its JSON. In order to make this work, drop the part of code you do for drawing line between two locations and follow the instructions in my answer. – Hesham Saeed Aug 13 '12 at 10:58
  • @HeshamSaeed i have added my another part of my application so could you please check once. If possible can you send me the sample Project regarding to this issue. It will be more helpful to me. – KAREEM MAHAMMED Aug 13 '12 at 11:13
  • @KAREEMMAHAMMED: The problem is that I have this code inside my whole App, lil hard to extract it. But it is not hard to implement it with snippets I provided in my answer, you just need to try. You will have to create 6 new class files, and copy & paste the top 6 snippets, then you copy & paste the bottom 2 snippets in your MapActivity (that contains MapView) and if there is any problems or errors notify me. – Hesham Saeed Aug 13 '12 at 15:48
  • @HeshamSaeed as you told to work with 6 classes i have implemented that in my application but im the main activity at this line Route route = directions(new GeoPoint((int)(26.2*1E6),(int)(50.6*1E6)), new GeoPoint((int)(26.3*1E6),(int)(50.7*1E6))); am getting error and Route r = parser.parse(); here also one more error and in googleparser class inside the route pass() at this line Route r = parser.parse(); showing one more error could you please have a look on it .Thanks in advance – KAREEM MAHAMMED Aug 14 '12 at 04:01
  • @HeshamSaeed as you told to with6 classes i have implemented that in my application but im the main activity at this line Route route = directions(new GeoPoint((int)(26.2*1E6),(int)(50.6*1E6)), new GeoPoint((int)(26.3*1E6),(int)(50.7*1E6))); am getting error and Route r = parser.parse(); here also one more error and in googleparser class inside the route pass() at this line Route r = parser.parse(); showing one more error could you please have a look on it here is the http://stackoverflow.com/questions/11945681/displaying-route-path-names activity classes .Thanks in advance – KAREEM MAHAMMED Aug 14 '12 at 04:15
  • @KAREEMMAHAMMED: I think the error you have is Parser.java you are writing it wrong, the code is `public interface Parser { public Route parse(); }` it is an interface, not a class. – Hesham Saeed Aug 14 '12 at 20:51
  • Hi, i'm trying your code, but i get a NullPointerException on return feedUrl.openConnection().getInputStream(); in the XMLParser... – Emaborsa Aug 15 '12 at 22:25
  • EDIT: I solved it, changing the url, but now i get a NetworkOnMainThread Exception. Should i run your code in a different Thread? – Emaborsa Aug 15 '12 at 22:40
  • @Emaborsa: I think you should do an AsyncTask to implement `directions()` method inside `doInBackground()` so you avoid doing Network I/O on the main thread, Also AsyncTask would give you the ability to give the user ProgressBar for loading the directions which will be better. – Hesham Saeed Aug 16 '12 at 06:07
  • I arranged it. Is it normal that the map, once drawn the route, is very slow by zooming or moving? Actually every action on the MapActivity is very slow... – Emaborsa Aug 16 '12 at 07:56
  • @Emaborsa: It shouldn't be slow because you are adding only one overlay, either your processor is very slow or you are doing something wrong or not efficient. – Hesham Saeed Aug 16 '12 at 08:21
  • I copied the code above. The application slows down if the displayed route is very big (lot of points). If the route is small, i don't have problems – Emaborsa Aug 16 '12 at 18:52
  • @Emaborsa: if you want to control the size of the Overlay, you have to control the Paint object, in RouteOverlay class: change this variable private static final float STROKE = 4.5f; make the number smaller, the route will be more thin. – Hesham Saeed Aug 17 '12 at 11:36
  • I don't think that is the origin of my problem. I mean, the mapView becomes heavy when the displayed route is very long, e.g from the northest to the southest of europe, since there are a lot of GeoPoints in the RouteOverlay. If i draw a shorter route i don't have this problem... – Emaborsa Aug 17 '12 at 20:30
  • I did a check: Routes that have about 4000 points work fine. Longer routes, i did an example with 76000 points slow down dramatically the view... – Emaborsa Aug 17 '12 at 21:37
  • @Emaborsa: I didn't face this problem because my Country area is too small. Maybe you would ask another question about this, and see if someone faced that and solved it. – Hesham Saeed Aug 18 '12 at 08:21
  • I see you are from Bahrain. Try to trace a route from Oman to Syria. It could have more than 20000 points... If it's not possible, it doesn't matter, thank you anyway. – Emaborsa Aug 18 '12 at 13:51
  • @Hesham Saeed Thanks for your above code now it works fine for me and its sucessfully generating route between source and destination.Now i would like to know how to read the direction names and need to display it on the emulater could you please help me on this issue – KAREEM MAHAMMED Aug 29 '12 at 10:32
  • `String direction; List steps = route.getSegments(); for(int i=1; i – Hesham Saeed Aug 29 '12 at 11:06
  • I imolemented this, but it does nothing in the map, where to find my solution? – Reyjohn Sep 17 '12 at 20:08
  • It works good thanks dude, you did really good work. I have only one question: when I do zoom level 16 or more, the overlay of the route dissapear, and when I do zoom out to 15 it appears again. I search in the code but there is not explanation for it, thanks for reply. – ƒernando Valle Nov 28 '12 at 08:40
  • There should be no problem with the zoom but, you are using too far zoom level and the overlay will be very small which it won't be seen from that zoom level and it won't make sense anyway.. – Hesham Saeed Nov 28 '12 at 09:09
  • yes thats the problem, I figured and I checked it, thanks for the response anyways. I will try to do something to solve it. Thanks – ƒernando Valle Nov 28 '12 at 09:12
  • good answer, that code work great in Android 2.2 emulator and real device, but it seems not work in Android 4 (api 15 / api 17) simulator and device. I have no idea. what's the problem pls help – AITAALI_ABDERRAHMANE Dec 26 '12 at 09:21
  • @AT_AB you have to do any network/internet work in an AsyncTask and not on the UI thread. Other than this it is all good. – Hesham Saeed Dec 27 '12 at 10:21
  • Thank you for the nice example, is there any equivalent to this code for api v2 ? thank you – Rami Jan 24 '13 at 16:06
  • @HeshamSaeed..Is your code available in Google Maps API v2?...And Which class do you extend in your mainActivity, Activity or MapActivity??... – BBonDoo Feb 20 '13 at 10:07
  • @BBonDoo I didn't test it there, you extend MapActivity ofcourse. – Hesham Saeed Feb 21 '13 at 05:41
  • @HeshamSaeed..Thanks for your rapid reply..Now I tested it on Google Maps API v2, but I got one error in the "mapView.getOverlays().add(routeOverlay);" under the OnCreat() code. : "getOverlays()" is an error. please let me know why.. – BBonDoo Feb 21 '13 at 06:11
  • @BBonDoo make sure you assigned the mapView correctly in this line `MapView mapView = (MapView) findViewById(R.id.mapview); ` Otherwise maybe getOverlays() name is different in v2. You can post another question if someone faced the same problem he'd answer you. Thanks. – Hesham Saeed Feb 21 '13 at 08:40
  • @HeshamSaeed..Thanks a lot...Maybe in Google Maps API v2, it is likely not to be able to deploy MapView,even if extending MapActivity or FragmentActivity(SupportFragment). I got failed to apply your code continuously. My test SDK Targer range was from API 7 to 17. I got ANR in the OnCreat(). – BBonDoo Feb 21 '13 at 08:54
  • @HeshamSaeed is it possible to get alternative directions to? – CodersSC Mar 03 '13 at 20:36
  • Thansk for this Hesham, I have coupled your code for use in an Android Library.Check it out here : https://github.com/jd-alexander/Google-Directions-Android – Joel Dean Aug 29 '13 at 01:57
  • Awesome library, nice implementation, works great, thank you – Henadzi Rabkin Feb 26 '14 at 12:59
  • 80% of the classes you use here do not exist anymore – Odaym Feb 21 '15 at 11:55