package org.osmdroid.routing; import java.util.ArrayList; import org.oscim.core.BoundingBox; import org.oscim.core.GeoPoint; import org.osmdroid.routing.provider.GoogleRoadManager; import org.osmdroid.routing.provider.MapQuestRoadManager; import org.osmdroid.routing.provider.OSRMRoadManager; import org.osmdroid.utils.BonusPackHelper; import org.osmdroid.utils.DouglasPeuckerReducer; import android.os.Parcel; import android.os.Parcelable; import android.util.Log; /** * describes the way to go from a position to an other. Normally returned by a * call to a Directions API (from MapQuest, GoogleMaps or other) * @see MapQuestRoadManager * @see GoogleRoadManager * @see OSRMRoadManager * @author M.Kergall */ public class Road implements Parcelable { /** * @see #STATUS_INVALID STATUS_INVALID * @see #STATUS_OK STATUS_OK * @see #STATUS_DEFAULT STATUS_DEFAULT */ public int status; /** length of the whole route in km. */ public double length; /** duration of the whole trip in sec. */ public double duration; public ArrayList<RoadNode> nodes; /** */ /** there is one leg between each waypoint */ public ArrayList<RoadLeg> legs; /** full shape: polyline, as an array of GeoPoints */ public ArrayList<GeoPoint> routeHigh; /** the same, in low resolution (less points) */ private ArrayList<GeoPoint> routeLow; /** road bounding box */ public BoundingBox boundingBox; /** STATUS_INVALID = road not built */ public static final int STATUS_INVALID = 0; /** STATUS_OK = road properly retrieved and built */ public static final int STATUS_OK = 1; /** * STATUS_DEFAULT = any issue (technical issue, or no possible route) led to * build a default road */ public static final int STATUS_DEFAULT = 2; private void init() { status = STATUS_INVALID; length = 0.0; duration = 0.0; nodes = new ArrayList<RoadNode>(); routeHigh = new ArrayList<GeoPoint>(); routeLow = null; legs = new ArrayList<RoadLeg>(); boundingBox = null; } public Road() { init(); } /** * default constructor when normal loading failed: the road shape only * contains the waypoints; All distances and times are at 0; there is no * node; status equals DEFAULT. * @param waypoints * ... */ public Road(ArrayList<GeoPoint> waypoints) { init(); int n = waypoints.size(); for (int i = 0; i < n; i++) { GeoPoint p = waypoints.get(i); routeHigh.add(p); } for (int i = 0; i < n - 1; i++) { RoadLeg leg = new RoadLeg(/* i, i+1, mLinks */); legs.add(leg); } boundingBox = BoundingBox.fromGeoPoints(routeHigh); status = STATUS_DEFAULT; } /** * @return the road shape in "low resolution" = simplified by around 10 * factor. */ public ArrayList<GeoPoint> getRouteLow() { if (routeLow == null) { // Simplify the route (divide number of points by around 10): int n = routeHigh.size(); routeLow = DouglasPeuckerReducer.reduceWithTolerance(routeHigh, 1500.0); Log.d(BonusPackHelper.LOG_TAG, "Road reduced from " + n + " to " + routeLow.size() + " points"); } return routeLow; } public void setRouteLow(ArrayList<GeoPoint> route) { routeLow = route; } /** * @param pLength * in km * @param pDuration * in sec * @return a human-readable length&duration text. */ public String getLengthDurationText(double pLength, double pDuration) { String result; if (pLength >= 100.0) { result = (int) (pLength) + " km, "; } else if (pLength >= 1.0) { result = Math.round(pLength * 10) / 10.0 + " km, "; } else { result = (int) (pLength * 1000) + " m, "; } int totalSeconds = (int) pDuration; int hours = totalSeconds / 3600; int minutes = (totalSeconds / 60) - (hours * 60); int seconds = (totalSeconds % 60); if (hours != 0) { result += hours + " h "; } if (minutes != 0) { result += minutes + " min"; } if (hours == 0 && minutes == 0) { result += seconds + " s"; } return result; } /** * @return length and duration of the whole road, or of a leg of the road, * as a String, in a readable format. * @param leg * leg index, starting from 0. -1 for the whole road */ public String getLengthDurationText(int leg) { double len = (leg == -1 ? this.length : legs.get(leg).length); double dur = (leg == -1 ? this.duration : legs.get(leg).duration); return getLengthDurationText(len, dur); } protected double distanceLLSquared(GeoPoint p1, GeoPoint p2) { double deltaLat = p2.latitudeE6 - p1.latitudeE6; double deltaLon = p2.longitudeE6 - p1.longitudeE6; return (deltaLat * deltaLat + deltaLon * deltaLon); } /** * As MapQuest and OSRM doesn't provide legs information, we have to rebuild * it, using the waypoints and the road nodes. <br> * Note that MapQuest legs fit well with waypoints, as there is a * "dedicated" node for each waypoint. But OSRM legs are not precise, as * there is no node "dedicated" to waypoints. * @param waypoints * ... */ public void buildLegs(ArrayList<GeoPoint> waypoints) { legs = new ArrayList<RoadLeg>(); int firstNodeIndex = 0; // For all intermediate waypoints, search the node closest to the // waypoint int w = waypoints.size(); int n = nodes.size(); for (int i = 1; i < w - 1; i++) { GeoPoint waypoint = waypoints.get(i); double distanceMin = -1.0; int nodeIndexMin = -1; for (int j = firstNodeIndex; j < n; j++) { GeoPoint roadPoint = nodes.get(j).location; double dSquared = distanceLLSquared(roadPoint, waypoint); if (nodeIndexMin == -1 || dSquared < distanceMin) { distanceMin = dSquared; nodeIndexMin = j; } } // Build the leg as ending with this closest node: RoadLeg leg = new RoadLeg(firstNodeIndex, nodeIndexMin, nodes); legs.add(leg); firstNodeIndex = nodeIndexMin + 1; // restart next leg from end } // Build last leg ending with last node: RoadLeg lastLeg = new RoadLeg(firstNodeIndex, n - 1, nodes); legs.add(lastLeg); } // --- Parcelable implementation @Override public int describeContents() { return 0; } @Override public void writeToParcel(Parcel out, int flags) { out.writeInt(status); out.writeDouble(length); out.writeDouble(duration); out.writeList(nodes); out.writeList(legs); out.writeList(routeHigh); out.writeParcelable(boundingBox, 0); } public static final Parcelable.Creator<Road> CREATOR = new Parcelable.Creator<Road>() { @Override public Road createFromParcel(Parcel source) { return new Road(source); } @Override public Road[] newArray(int size) { return new Road[size]; } }; @SuppressWarnings("unchecked") private Road(Parcel in) { status = in.readInt(); length = in.readDouble(); duration = in.readDouble(); nodes = in.readArrayList(RoadNode.class.getClassLoader()); legs = in.readArrayList(RoadLeg.class.getClassLoader()); routeHigh = in.readArrayList(GeoPoint.class.getClassLoader()); boundingBox = in.readParcelable(BoundingBox.class.getClassLoader()); } }