[Android] มาแสดงเส้น Route บน Mapview กันเถอะ

กลับมาเขียนบล็อกสักที หลังจากห่างหายไปนานแสนนาน

ใจจริง ก็อยากมาอัปนะ แต่ไม่รู้จะอัปอะไร

ล่าสุดต้องเขียน App ที่ต้องมีการแสดงเส้น Route บน Mapview

ก็ลอง ๆ ไปหาข้อมูลมา

เจอแบบที่ต้องไปเอาข้อมูลจาก Google Map มาแสดง

แต่…ในโค้ดตัวอย่าง ดันเป็นการรับข้อมูลที่เป็น KML ซึ่งทาง Google Map เขาเลิกใช้ไปแล้ว…

หาต่ออีกนิด ก็เลยเจอตัวอย่างล่าสุด…ที่ใช้งานได้จริง

เลยอยากเอามาแบ่งปันกัน

เริ่มแรก ก็สร้างโปรเจคขึ้นมาใหม่ ตามปกติ แล้วก็อย่าลืมใส่

<uses-library android:name="com.google.android.maps" />

และ

<uses-permission android:name="android.permission.INTERNET" />

ใน Manifest นะครับ

จากนั้น เราจะมาเตรียมวัตถุดิบ (Class) ต่าง ๆ ที่เราจะต้องใช้ในการนี้กันนะครับ

เริ่มจาก สร้าง Class ใหม่ ชื่อ Route นะครับ

package com.example.testroutemap;

import java.util.ArrayList;
import java.util.List;

import com.google.android.maps.GeoPoint;

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

ต่อด้วย Class RouteOverlay

package com.example.testroutemap;

import java.util.Iterator;
import java.util.List;

import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Point;
import android.graphics.Path;

import com.google.android.maps.GeoPoint;
import com.google.android.maps.MapView;
import com.google.android.maps.Overlay;
import com.google.android.maps.Projection;

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

ต่อด้วย Class Segment

package com.example.testroutemap;

import com.google.android.maps.GeoPoint;

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

}

จากนั้นก็ Class XMLParser

package com.example.testroutemap;

import java.io.InputStream;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLConnection;

import android.util.Log;

public class XMLParser {
    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 {
                    URLConnection urlConnection = feedUrl.openConnection();
                    urlConnection.setDoInput(true);
                    urlConnection.setDoOutput(true);

                    InputStream is = urlConnection.getInputStream();

                    return is;
            } catch (Exception e) {
                    e.printStackTrace();
                    return null;
            }
    }
}

และ Class Parser

package com.example.testroutemap;

public interface Parser {
    public Route parse();
}

ก่อนที่จะสร้าง Class GoogleParser

package com.example.testroutemap;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.List;

import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;

import android.util.Log;

import com.google.android.maps.GeoPoint;

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
        InputStream is = this.getInputStream();
        final String result = convertStreamToString(is);
        // 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) {
        Log.d("convertStreamToString", "new BufferedReader");
        final BufferedReader reader = new BufferedReader(new InputStreamReader(
                input));
        Log.d("convertStreamToString", "new StringBuilder");
        final StringBuilder sBuf = new StringBuilder();

        String line = null;
        Log.d("convertStreamToString", "before try");
        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;
    }
}

และ…สุดท้าย ก็ที่ MainActivity ของเรา

package com.example.testroutemap;

import android.graphics.Color;
import android.os.Bundle;
import android.os.Handler;
import android.util.Log;

import com.google.android.maps.GeoPoint;
import com.google.android.maps.MapActivity;
import com.google.android.maps.MapView;

public class MainActivity extends MapActivity {

    MapView mapView;

    Route route;

    GeoPoint startPoint,endpoint;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        mapView = (MapView) findViewById(R.id.mapview);
        mapView.setBuiltInZoomControls(true);

        startPoint = new GeoPoint((int)(14.077099*1E6),(int)(100.603284*1E6));
        endpoint = new GeoPoint((int)(13.845789*1E6),(int)(100.567943*1E6));

        new Thread() {
            @Override
            public void run() {

                route = directions(startPoint,endpoint);

                mHandler.sendEmptyMessage(0);

            }
        }.start();

    }

    Handler mHandler = new Handler() {
        public void handleMessage(android.os.Message msg) {
            RouteOverlay routeOverlay = new RouteOverlay(route, Color.BLUE);
            mapView.getOverlays().add(routeOverlay);
            mapView.invalidate();
            mapView.getController().animateTo(startPoint);
            mapView.getController().setZoom(13);
        };
    };

    @Override
    protected boolean isRouteDisplayed() {
        return false;
    }

    private Route directions(final GeoPoint start, final GeoPoint dest) {
        Parser parser;
        String jsonURL = "http://maps.google.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");
        Log.d("url", sBuf.toString());
        parser = new GoogleParser(sBuf.toString());
        Route r =  parser.parse();
        return r;
    }

}

ก็เท่านี้ครับ… พอกด Debug ก็จะออกมาเป็นแบบนี้…

สำหรับที่มา คลิ้กที่นี่ เลยครับ

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.