package com.nutiteq.services;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.Reader;
import java.util.Vector;
import javax.microedition.io.Connector;
import javax.microedition.io.HttpConnection;
import org.kxml2.io.KXmlParser;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
import com.nutiteq.cache.Cache;
import com.nutiteq.components.Distance;
import com.nutiteq.components.DurationTime;
import com.nutiteq.components.Line;
import com.nutiteq.components.Route;
import com.nutiteq.components.RouteInstruction;
import com.nutiteq.components.RouteSummary;
import com.nutiteq.components.WgsBoundingBox;
import com.nutiteq.components.WgsPoint;
import com.nutiteq.core.MappingCore;
import com.nutiteq.io.ResourceDataWaiter;
import com.nutiteq.io.ResourceRequestor;
import com.nutiteq.log.Log;
import com.nutiteq.utils.IOUtils;
import com.nutiteq.utils.Utils;
/**
* Routing service using CloudMade routing, version 0.3.
*/
public class CloudMadeDirections implements DirectionsService, ResourceRequestor,
ResourceDataWaiter {
private static final String ATTRIBUTE_LONGITUDE = "lon";
private static final String ATTRIBUTE_LATITUDE = "lat";
private static final String ROUTE_POINT_TAG = "rtept";
private static final String WAYPOINT_TAG = "wpt";
private static final String DESCRIPTION_TAG = "desc";
private static final String TURN_TAG = "turn";
private static final String ROUTE_EXTENSION_TAG = "extensions";
private static final String TIME_TAG = "time";
private static final String DISTANCE_TAG = "distance";
private static final String OFFSET_TAG = "offset";
public static final String ROUTE_TYPE_CAR = "car";
public static final String ROUTE_TYPE_FOOT = "foot";
public static final String ROUTE_TYPE_BICYCLE = "bicycle";
public static final String ROUTE_TYPE_MODIFIER_SHORTEST = "shortest";
private static final String BASEURL = "http://routes.cloudmade.com/";
private static final String RESPONSE_TYPE = "gpx";
private static final String API_VERSION = "0.3";
private final DirectionsWaiter directionsWaiter;
private final WgsPoint start;
private final WgsPoint end;
private final String routeType;
private final String routeTypeModifier;
private final String apiKey;
private boolean canceled;
private String cloudMadeToken = null;
public CloudMadeDirections(final DirectionsWaiter directionsWaiter, final WgsPoint start,
final WgsPoint end, final String routeType, final String apiKey, final String userId) {
this(directionsWaiter, start, end, routeType, "", apiKey, userId);
}
public CloudMadeDirections(final DirectionsWaiter directionsWaiter, final WgsPoint start,
final WgsPoint end, final String routeType, final String routeTypeModifier,
final String apiKey,final String userId){
this(CloudMadeToken.getCloudMadeToken(apiKey,userId), directionsWaiter, start, end, routeType, routeTypeModifier,apiKey);
}
/**
* @param token CloudMade token
* @param directionsWaiter listener for directions result (callback)
* @param start start point
* @param end end point of route
* @param routeType route type: ROUTE_TYPE_CAR, ROUTE_TYPE_FOOT or ROUTE_TYPE_BICYCLE
* @param routeTypeModifier ROUTE_TYPE_MODIFIER_SHORTEST (default is FASTEST)
* @param apiKey your CloudMade HTTP API key, get it from www.cloudmade.com
*/
public CloudMadeDirections(final String token, final DirectionsWaiter directionsWaiter, final WgsPoint start,
final WgsPoint end, final String routeType, final String routeTypeModifier, final String apiKey
) {
this.directionsWaiter = directionsWaiter;
this.start = start;
this.end = end;
this.routeType = routeType;
this.routeTypeModifier = routeTypeModifier;
this.cloudMadeToken=token;
this.apiKey=apiKey;
}
public void execute() {
MappingCore.getInstance().getTasksRunner().enqueueDownload(this, Cache.CACHE_LEVEL_NONE);
}
public String resourcePath() {
final StringBuffer url = new StringBuffer(BASEURL);
url.append(apiKey).append("/api/").append(API_VERSION).append("/");
url.append(start.getLat()).append(",").append(start.getLon());
url.append(",").append(end.getLat()).append(",").append(end.getLon());
url.append("/").append(routeType);
if (routeTypeModifier != null && !"".equals(routeTypeModifier)) {
url.append("/").append(routeTypeModifier);
}
url.append(".").append(RESPONSE_TYPE);
url.append("?token=").append(cloudMadeToken);
return url.toString();
}
public void notifyError() {
Log.error("CloudMade Directions network error");
directionsWaiter.networkError();
}
public void dataRetrieved(final byte[] data) {
Log.debug("CloudMade Directions data retrieved, bytes:"+data.length);
if (canceled) {
return;
}
final Reader reader = Utils.createInputStreamReader(data);
final Route route = readRoute(reader);
if (route.getRouteLine().getPoints().length < 2) {
directionsWaiter.routingParsingError(new String(data));
} else {
directionsWaiter.routeFound(route);
}
IOUtils.closeReader(reader);
}
protected Route readRoute(final Reader reader) {
final Vector wayPoints = new Vector();
final Vector instructionPoints = new Vector();
RouteSummary summary = null;
final KXmlParser parser = new KXmlParser();
try {
parser.setInput(reader);
int eventType = parser.getEventType();
while (eventType != XmlPullParser.END_DOCUMENT) {
if (eventType == XmlPullParser.START_TAG) {
final String tagName = parser.getName();
if (ROUTE_EXTENSION_TAG.equals(tagName) && summary == null) {
summary = readRouteSummary(parser);
}else if (WAYPOINT_TAG.equals(tagName)) {
final String lat = parser.getAttributeValue(null, ATTRIBUTE_LATITUDE);
final String lon = parser.getAttributeValue(null, ATTRIBUTE_LONGITUDE);
wayPoints.addElement(Utils.parseWgsFromString(lon, lat));
}else if (ROUTE_POINT_TAG.equals(tagName)) {
instructionPoints.addElement(readInstruction(parser, instructionPoints.size()));
}
}
eventType = parser.next();
}
} catch (final Exception e) {
Log.error("Route: read " + e.getMessage());
Log.printStackTrace(e);
}
final WgsPoint[] points = new WgsPoint[wayPoints.size()];
wayPoints.copyInto(points);
final RouteInstruction[] instructions = new RouteInstruction[instructionPoints.size()];
instructionPoints.copyInto(instructions);
return new Route(summary, new Line(points), instructions);
}
private RouteSummary readRouteSummary(final KXmlParser parser) throws IOException {
DurationTime totalTime = null;
Distance distance = null;
WgsBoundingBox boundingBox = null;
try {
int eventType = parser.next();
while (!ROUTE_EXTENSION_TAG.equals(parser.getName())) {
if (XmlPullParser.START_TAG == eventType) {
final String tagName = parser.getName();
if (TIME_TAG.equals(tagName)) {
totalTime = parseDuration(parser.nextText());
} else if (DISTANCE_TAG.equals(tagName)) {
distance = readDistance(parser);
}
}
eventType = parser.next();
}
} catch (final XmlPullParserException e) {
Log.printStackTrace(e);
}
return new RouteSummary(totalTime, distance, boundingBox);
}
private Distance readDistance(final KXmlParser parser) throws IOException {
String distanceString;
try {
distanceString = parser.nextText();
try {
return new Distance(Float.parseFloat(distanceString), "m");
} catch (final NumberFormatException e) {
Log.error("NumberFormatException in readDistance");
Log.printStackTrace(e);
return new Distance(0, "");
}
} catch (XmlPullParserException e1) {
Log.error("XML parsing exception in readDistance");
e1.printStackTrace();
return new Distance(0, "");
}
}
protected DurationTime parseDuration(final String time) {
if (time == null || "".equals(time.trim())) {
return new DurationTime(0, 0, 0, 0);
}
try {
int timeInt = Integer.parseInt(time);
final int daysInt = timeInt / 86400;
final int hoursInt = (int)(timeInt/3600) - (daysInt*24);
final int minutesInt = (int)(timeInt/60) - (hoursInt*60) - (daysInt*60*24);
final int secondsInt = timeInt - (minutesInt*60)- (hoursInt*60*60) - (daysInt*60*60*24);
return new DurationTime(daysInt, hoursInt, minutesInt, secondsInt);
} catch (final NumberFormatException e) {
Log.error("Error parsing duration from " + time);
Log.printStackTrace(e);
return null;
}
}
private RouteInstruction readInstruction(final KXmlParser parser, final int count)
throws Exception {
final String lat = parser.getAttributeValue(null, ATTRIBUTE_LATITUDE);
final String lon = parser.getAttributeValue(null, ATTRIBUTE_LONGITUDE);
final WgsPoint location = Utils.parseWgsFromString(lon, lat);
String description = null;
int eventType = parser.next();
if (DESCRIPTION_TAG.equals(parser.getName())) {
description = parser.nextText();
}
eventType = parser.next();
eventType = parser.next();
DurationTime time = null;
Distance distance = null;
int turn = IMAGE_ROUTE_START;
while (!ROUTE_EXTENSION_TAG.equals(parser.getName())) {
if (XmlPullParser.START_TAG == eventType) {
final String tagName = parser.getName();
if (TIME_TAG.equals(tagName)) {
time = parseDuration(parser.nextText());
} else if (DISTANCE_TAG.equals(tagName)) {
distance = readDistance(parser);
} else if (TURN_TAG.equals(tagName)) {
turn = parseTurn(parser.nextText());
}else if (OFFSET_TAG.equals(tagName)) {
String offset = parser.nextText();
if(offset.equals("0")){
turn=DirectionsService.IMAGE_ROUTE_START;
}
}
}
eventType = parser.next();
}
return new RouteInstruction(count, turn, time, description, distance, location);
}
private int parseTurn(String turn) {
if (turn.equals("TSLR") || turn.equals("TR")) {
return DirectionsService.IMAGE_ROUTE_RIGHT;
}
if (turn.equals("TSLL") || turn.equals("TL")) {
return DirectionsService.IMAGE_ROUTE_LEFT;
}
// all other cases (C, EXITn ... are taken as Stright/Continue
return DirectionsService.IMAGE_ROUTE_STRAIGHT;
}
public void cancel() {
canceled = true;
}
public int getCachingLevel() {
return Cache.CACHE_LEVEL_NONE;
}
}