กลับมาเขียนบล็อกสักที หลังจากห่างหายไปนานแสนนาน
ใจจริง ก็อยากมาอัปนะ แต่ไม่รู้จะอัปอะไร
ล่าสุดต้องเขียน 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 ก็จะออกมาเป็นแบบนี้…
สำหรับที่มา คลิ้กที่นี่ เลยครับ