Why retrieving Google Directions for Android using KML data is not working anymore? [duplicate]
Asked Answered
E

1

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?

Economic answered 31/7, 2012 at 17:0 Comment(0)
E
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.

Economic answered 31/7, 2012 at 17:0 Comment(40)
+1 for great answer it help me. but what is US_API in StringBuffer sBuf = new StringBuffer(US_API); is it US_API = "maps.google.com/maps/api/directions/json?";Circumstantial
@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?Vidal
@DeepakSwami: Yes this is it, I forgot to add it because I declared it as global variable in my Activity.Economic
@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.Economic
@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 #11933297 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 advanceVidal
@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.Economic
@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.Vidal
@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.Economic
@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 advanceVidal
@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 #11946181 activity classes .Thanks in advanceVidal
@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.Economic
Hi, i'm trying your code, but i get a NullPointerException on return feedUrl.openConnection().getInputStream(); in the XMLParser...Mcgruder
EDIT: I solved it, changing the url, but now i get a NetworkOnMainThread Exception. Should i run your code in a different Thread?Mcgruder
@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.Economic
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...Mcgruder
@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.Economic
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 problemsMcgruder
@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.Economic
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...Mcgruder
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...Mcgruder
@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.Economic
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.Mcgruder
@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 issueVidal
String direction; List<Segment> steps = route.getSegments(); for(int i=1; i<steps.size(); i++) { direction = steps.get(i).getInstruction(); //here you put the direction string into array to use it in list view} Economic
I imolemented this, but it does nothing in the map, where to find my solution?Banausic
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.Lablab
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..Economic
yes thats the problem, I figured and I checked it, thanks for the response anyways. I will try to do something to solve it. ThanksLablab
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 helpCardiogram
@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.Economic
Thank you for the nice example, is there any equivalent to this code for api v2 ? thank youBoff
@HeshamSaeed..Is your code available in Google Maps API v2?...And Which class do you extend in your mainActivity, Activity or MapActivity??...Seem
@Seem I didn't test it there, you extend MapActivity ofcourse.Economic
@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..Seem
@Seem 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.Economic
@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().Seem
@HeshamSaeed is it possible to get alternative directions to?Habiliment
Thansk for this Hesham, I have coupled your code for use in an Android Library.Check it out here : github.com/jd-alexander/Google-Directions-AndroidGreenheart
Awesome library, nice implementation, works great, thank youTopdress
80% of the classes you use here do not exist anymoreSurfeit

© 2022 - 2024 — McMap. All rights reserved.