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.