Another mystery without much relevant results on Google search that turned out to be quite an easy task.
Cloudmade seems to make a lot of effort to provide developers with a lot of mapping features, and as I haven't found anywhere on the site how to pay them, I take it that their service is free. It seems also that they use OpenStreetMaps as a back-end, therefore (here goes the disclaimer) ROUTING IS NOT VERY ACCURATE. At least not yet, but it's being improved a lot; I've added a Starbucks shop about a month ago, and now it's visible on the map!
Among other services Cloudmade offers Routing HTTP API, which seems to be a breeze for use within HTML code (they support JSONP-style callback for script injection to go around cross-origin resource sharing restrictions). This is the one to be used in the open map view with open map controller, because Google Maps doesn't allow routing anywhere outside their domain (Google even removed routing API from Android right after the very first release!).
My implementation of the BlueLine does everything from requesting directions, all the way to drawing them on the map.
Create a class extending PathOverlay with the following code:
The Cloudmade API key goes into the manifest file within the application element like here
And that's pretty much it. It's very easy to make it work now with the application:
That's really it! Hope it helps.
Cloudmade seems to make a lot of effort to provide developers with a lot of mapping features, and as I haven't found anywhere on the site how to pay them, I take it that their service is free. It seems also that they use OpenStreetMaps as a back-end, therefore (here goes the disclaimer) ROUTING IS NOT VERY ACCURATE. At least not yet, but it's being improved a lot; I've added a Starbucks shop about a month ago, and now it's visible on the map!
Among other services Cloudmade offers Routing HTTP API, which seems to be a breeze for use within HTML code (they support JSONP-style callback for script injection to go around cross-origin resource sharing restrictions). This is the one to be used in the open map view with open map controller, because Google Maps doesn't allow routing anywhere outside their domain (Google even removed routing API from Android right after the very first release!).
My implementation of the BlueLine does everything from requesting directions, all the way to drawing them on the map.
Create a class extending PathOverlay with the following code:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import java.io.BufferedReader; | |
import java.io.InputStreamReader; | |
import java.io.OutputStreamWriter; | |
import java.io.StringReader; | |
import java.net.URL; | |
import java.net.URLConnection; | |
import java.util.ArrayList; | |
import javax.xml.parsers.SAXParserFactory; | |
import org.osmdroid.tileprovider.util.CloudmadeUtil; | |
import org.osmdroid.views.overlay.PathOverlay; | |
import org.xml.sax.Attributes; | |
import org.xml.sax.InputSource; | |
import org.xml.sax.SAXException; | |
import org.xml.sax.XMLReader; | |
import org.xml.sax.helpers.DefaultHandler; | |
import android.content.Context; | |
import android.graphics.Point; | |
public class BlueLine extends PathOverlay { | |
String CMURL; | |
public BlueLine(int lineColor, Context ctx) { | |
super(lineColor, ctx); | |
getPaint().setStrokeWidth(5f); | |
CloudmadeUtil.retrieveCloudmadeKey(ctx); | |
//prebuild the URL http://routes.cloudmade.com/YOUR-API-KEY-GOES-HERE/api/0.3/PARAMS-GO-HERE | |
CMURL = "http://routes.cloudmade.com/" + CloudmadeUtil.getCloudmadeKey() + "/api/0.3/"; | |
} | |
double endLat, endLng; | |
public void showDirectionsToPredefinedLocation(double startLat, double startLng, boolean shortest, String lang, boolean metric) throws Exception { | |
showDirections(startLat, startLng, endLat, endLng, shortest, lang, metric); | |
} | |
/** | |
* Request directions from a point to another point. | |
* Line is drawn automatically in the draw method, need to just request directions and parse the XML into the List of Point's. | |
* @param startLat Latitude of starting point | |
* @param startLng Longitude of starting point | |
* @param endLat Latitude of ending point | |
* @param endLng Longitude of ending point | |
* @param shortest Whether select shortest or fastest route | |
* @param lang 2-char ISO code of the language of directions | |
* @param metric Whether use metric or imperial system | |
* @throws Exception | |
*/ | |
public void showDirections(double startLat, double startLng, double endLat, double endLng, boolean shortest, String lang, boolean metric) throws Exception { | |
/* | |
* Query up the routing service to figure out the turn points | |
* documentation for cloudmade routing service http://developers.cloudmade.com/wiki/routing-http-api/Documentation | |
*/ | |
//add PARAMS to the URL | |
//start_point,[transit_point1,...,transit_pointN],end_point/route_type[/route_type_modifier].output_format[?lang=(Two letter ISO 3166-1 code)][&units=(km|miles)] | |
CMURL += startLat + "," + startLng + "," + endLat + "," + endLng | |
+ "/car/" + (shortest? "shortest" : "fastest") + ".gpx?lang=" + lang + "&units=" + (metric? "km" : "miles"); | |
//fetch directions and parse returned XML | |
String directionsXML = getContent(CMURL, null, "UTF8"); | |
//parse XML | |
TurnPointsParser parser = new TurnPointsParser(); | |
XMLReader xmlReader = SAXParserFactory.newInstance().newSAXParser().getXMLReader(); | |
xmlReader.setContentHandler(parser); | |
if(!"".equals(directionsXML)) { | |
xmlReader.parse(new InputSource(new StringReader(directionsXML))); | |
} | |
} | |
public class TurnPointsParser extends DefaultHandler { | |
public static final String ROOT_ELEMENT = "wpt"; | |
ArrayList<Point> tempPoints = new ArrayList<Point>(); | |
@Override | |
public void startElement(String uri, String localName, String qName, Attributes atts) throws SAXException { | |
if(qName.equals(ROOT_ELEMENT)) { | |
try { | |
int late6 = degreesToMicrodegrees(Double.parseDouble(atts.getValue("lat"))); | |
int lnge6 = degreesToMicrodegrees(Double.parseDouble(atts.getValue("lon"))); | |
tempPoints.add(new Point(late6, lnge6)); | |
} | |
catch(Exception e) { | |
e.printStackTrace(); | |
} | |
} | |
} | |
@Override | |
public void endDocument() { | |
setPoints(tempPoints); | |
} | |
} | |
void setPoints(ArrayList<Point> points) { | |
clearPath(); | |
for(Point p : points) { | |
addPoint(p.x, p.y); | |
} | |
} | |
/** | |
* Fetch the content of service at the given URL with provided parameters. | |
* Also transcode the input into UTF-8 according to specified charset used by the service. | |
* @param serviceUrl | |
* @param requestParams | |
* @param serviceCharset | |
* @return String with fetched content | |
* @throws Exception | |
*/ | |
public static String getContent(String serviceUrl, String requestParams, String serviceCharset) throws Exception { | |
URL service = new URL(serviceUrl); | |
URLConnection serviceConnection = service.openConnection(); | |
serviceConnection.setDoOutput(true); | |
//write request to the connection | |
OutputStreamWriter request = new OutputStreamWriter(serviceConnection.getOutputStream()); | |
if (requestParams != null) { | |
request.write(requestParams); | |
} | |
request.flush(); | |
//read returned output | |
BufferedReader in = | |
new BufferedReader(new InputStreamReader(serviceConnection.getInputStream(), serviceCharset)); | |
String inputLine; | |
String returnedContent = ""; | |
while ((inputLine = in.readLine()) != null) { | |
returnedContent += inputLine; | |
} | |
in.close(); | |
request.close(); | |
return returnedContent; | |
} | |
public static final double MICRODEGREES_COEFF = 1E6; | |
public static int degreesToMicrodegrees(double degrees) { | |
return (int) (degrees * MICRODEGREES_COEFF); | |
} | |
public static double microdegreesToDegrees(int microdegrees) { | |
return (double) microdegrees / (double) MICRODEGREES_COEFF; | |
} | |
public void setEndLat(double endLat) { | |
this.endLat = endLat; | |
} | |
public void setEndLng(double endLng) { | |
this.endLng = endLng; | |
} | |
} |
The Cloudmade API key goes into the manifest file within the application element like here
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<application android:name=".App" | |
android:icon="@drawable/icon" | |
android:label="@string/app_name" | |
android:debuggable="true" | |
android:theme="@android:style/Theme.NoTitleBar"> | |
<meta-data android:name="CLOUDMADE_KEY" android:value="API key obtained from Cloudmade"/> | |
. | |
. | |
. | |
</application> |
And that's pretty much it. It's very easy to make it work now with the application:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
@Override | |
public void onCreate(Bundle savedInstanceState) { | |
super.onCreate(savedInstanceState); | |
. | |
. | |
. | |
BlueLine blueLine = new BlueLine(Color.BLUE, this); | |
} | |
Location lastKnownLocation; | |
//this activity implements LocationListener and has registered for location updates with the system | |
@Override | |
public void onLocationChanged(Location loc) { | |
lastKnownLocation = loc; //save latest location | |
} | |
protected void showDirectionsTo(double endLat, double endLng) { | |
//the routing will be requested from another thread, need to make coordinates accessible from there | |
blueLine.setEndLat(endLat); | |
blueLine.setEndLng(endLng); | |
new Handler().post(new Runnable() { | |
public void run() { | |
try { | |
//will always request directions from current location, destination was assigned previously | |
blueLine.showDirectionsToPredefinedLocation(lastKnownLocation.getLatitude(), lastKnownLocation.getLongitude(), false, "en", true); | |
mapView.getOverlays().add(blueLine); | |
} | |
catch(Exception e) { | |
e.printStackTrace(); | |
} | |
} | |
}); | |
} |
That's really it! Hope it helps.
Hi Elijah, I use your code in my android application. There is no error but I can not see route. I do not understand what the problem is. On the other hand start point is GPS location point, it is Ok, but I do not understand how do we determine the destination points. Please say what are my mistakes. Thank you for your atttention.
ReplyDeleteTo determine a destination point from an address you can use Geocoding (http://code.google.com/apis/maps/documentation/geocoding/). That is, if that's your problem. Other than that, just for the sake of an example, drop a random coordinates pair into this method.
ReplyDeleteTo be honest I don't really see what is the exact issue you are running into. Would you like me to take a look at your code?
Hi Elijah, I created a class (BlueLine Class) with your code and then I try to use this class in my application code. My code is running but the route application could not start. I get start point coordinates by GPS, I used random coordinates pair for destination point for testing but the route application could not start too. I would like you to examine my code. But I need your mail adress because I can not send my code in this platform because of limitation. My email adress is hzselvi@yahoo.com. Thank you for your attention.
ReplyDeleteJust bear in mind that start and end coordinates have to have the same format (if I remember correctly, it has to be in the same format as the one used by the GPS receiver, e.g. [37.9, -122.5] for San Francisco). Just watch out to avoid converting them to/from microdegrees, as the Android platform uses latter.
ReplyDeleteAs a test, create a link to the routing service with required coordinates and insert it into the browser to see what the service returns.
For testing, I create a link and insert it into the browser and service returns gpx file to me. But I can not succeed in android application. There is no error, application runs well but route line can not be shown. I think I have a defect but I do not know what it is.
ReplyDeleteAnd I can not understand this sentence. Which thread?
"the routing will be requested from another thread, need to make coordinates accessible from there".
Thank you for your interest.
I can not understand this sentence.
this example is missing the Draw method, right?
ReplyDeleteI have tried to implement your codes into my program sir Elijah. But i encountered a lot of issues during execution example, force close. I posted a question on stackoverflow, please check it out: http://stackoverflow.com/questions/8728737/shortest-path-calculation-on-maps-using-osmdroid-library
ReplyDeleteSir please help me this. here's my email: lordzden_1991@yahoo.com