/*
* Geopaparazzi - Digital field mapping on Android based devices
* Copyright (C) 2016 HydroloGIS (www.hydrologis.com)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package eu.geopaparazzi.library.routing.osmbonuspack;
import android.os.Parcel;
import android.os.Parcelable;
import android.util.Log;
import java.util.ArrayList;
/** describes the way to go from a position to an other.
* Normally returned by a call to a Directions API (from MapQuest, GoogleMaps, OSRM or other)
* @see MapQuestRoadManager
* @see GoogleRoadManager
* @see OSRMRoadManager
*
* @author M.Kergall
*/
public class Road implements Parcelable {
/**
* STATUS_OK = road properly retrieved and built.
* STATUS_INVALID = road has not been built yet.
* STATUS_TECHNICAL_ISSUE = technical issue, no answer from the service provider.
* All other values: functional errors/issues, depending on the service provider.
* */
public int mStatus;
/** length of the whole route in km. */
public double mLength;
/** duration of the whole trip in sec. */
public double mDuration;
/** list of intersections or "nodes" */
public ArrayList<RoadNode> mNodes;
/** there is one leg between each waypoint */
public ArrayList<RoadLeg> mLegs;
/** full shape: polyline, as an array of GeoPoints */
public ArrayList<GeoPoint> mRouteHigh;
/** the same, in low resolution (less points) */
private ArrayList<GeoPoint> mRouteLow;
/** road bounding box */
public BoundingBox mBoundingBox;
public static final int STATUS_INVALID=-1;
public static final int STATUS_OK=0;
public static final int STATUS_HTTP_OK=200;
public static final int STATUS_TECHNICAL_ISSUE=2;
private void init(){
mStatus = STATUS_INVALID;
mLength = 0.0;
mDuration = 0.0;
mNodes = new ArrayList<>();
mRouteHigh = new ArrayList<>();
mRouteLow = null;
mLegs = new ArrayList<>();
mBoundingBox = 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; mStatus set to TECHNICAL_ISSUE.
*/
public Road(ArrayList<GeoPoint> waypoints){
init();
int n = waypoints.size();
for (int i=0; i<n; i++){
GeoPoint p = waypoints.get(i);
mRouteHigh.add(p);
}
for (int i=0; i<n-1; i++){
RoadLeg leg = new RoadLeg(/*i, i+1, mLinks*/);
mLegs.add(leg);
}
mBoundingBox = BoundingBox.fromGeoPoints(mRouteHigh);
mStatus = STATUS_TECHNICAL_ISSUE;
}
/**
* @return the road shape in "low resolution" = simplified by around 10 factor.
*/
public ArrayList<GeoPoint> getRouteLow(){
if (mRouteLow == null){
//Simplify the route (divide number of points by around 10):
int n = mRouteHigh.size();
mRouteLow = DouglasPeuckerReducer.reduceWithTolerance(mRouteHigh, 1500.0);
Log.d(BonusPackHelper.LOG_TAG, "Road reduced from "+n+" to "+mRouteLow.size()+ " points");
}
return mRouteLow;
}
public void setRouteLow(ArrayList<GeoPoint> route){
mRouteLow = route;
}
/**
* @param length in km
* @param duration in sec
* @return a human-readable length&duration text.
*/
public static String getLengthDurationText(double length, double duration){
String result;
if (length >= 100.0){
result = (int)(length) + "km, ";
} else if (length >= 1.0){
result = Math.round(length*10)/10.0 + "km, ";
} else {
result = (int)(length*1000) + "m, ";
}
int totalSeconds = (int)duration;
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 + "sec";
}
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 length = (leg == -1 ? mLength : mLegs.get(leg).mLength);
double duration = (leg == -1 ? mDuration : mLegs.get(leg).mDuration);
return getLengthDurationText(length, duration);
}
protected double distanceLLSquared(GeoPoint p1, GeoPoint p2){
double deltaLat = p2.getLatitudeE6()-p1.getLatitudeE6();
double deltaLon = p2.getLongitudeE6()-p1.getLongitudeE6();
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.
*/
public void buildLegs(ArrayList<GeoPoint> waypoints){
mLegs = new ArrayList<>();
int firstNodeIndex = 0;
//For all intermediate waypoints, search the node closest to the waypoint
int w = waypoints.size();
int n = mNodes.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 = mNodes.get(j).mLocation;
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, mNodes);
mLegs.add(leg);
firstNodeIndex = nodeIndexMin+1; //restart next leg from end
}
//Build last leg ending with last node:
RoadLeg lastLeg = new RoadLeg(firstNodeIndex, n-1, mNodes);
mLegs.add(lastLeg);
}
//--- Parcelable implementation
@Override public int describeContents() {
return 0;
}
@Override public void writeToParcel(Parcel out, int flags) {
out.writeInt(mStatus);
out.writeDouble(mLength);
out.writeDouble(mDuration);
out.writeList(mNodes);
out.writeList(mLegs);
out.writeList(mRouteHigh);
out.writeParcelable(mBoundingBox, 0);
}
public static final Creator<Road> CREATOR = new Creator<Road>() {
@Override public Road createFromParcel(Parcel source) {
return new Road(source);
}
@Override public Road[] newArray(int size) {
return new Road[size];
}
};
private Road(Parcel in){
mStatus = in.readInt();
mLength = in.readDouble();
mDuration = in.readDouble();
mNodes = in.readArrayList(RoadNode.class.getClassLoader());
mLegs = in.readArrayList(RoadLeg.class.getClassLoader());
mRouteHigh = in.readArrayList(GeoPoint.class.getClassLoader());
mBoundingBox = in.readParcelable(BoundingBox.class.getClassLoader());
}
}