package org.androad.sys.ors.adt.rs; import java.io.Serializable; import java.util.ArrayList; import java.util.List; import org.osmdroid.util.BoundingBoxE6; import org.osmdroid.util.GeoPoint; import org.androad.adt.other.GraphicsPoint; import org.androad.util.constants.Constants; import android.graphics.Point; import android.os.Parcel; import android.os.Parcelable; import android.util.FloatMath; /** * @since 2008-04-06 19:05:37 * @author Nicolas 'plusminus' Gramlich * License: * @see Creative Commons Attribution-Noncommercial-Share Alike 2.0 Germany License . * Permissions beyond the scope of this license may be requested at plusminus {at} anddev {dot} org */ public class Route implements Parcelable, Serializable { // =========================================================== // Constants // =========================================================== static final long serialVersionUID = 4L; protected static final int LATITUDE_OVERMAX = (int)(81 * 1E6); protected static final int LONGITUDE_OVERMAX = (int)(181 * 1E6); // =========================================================== // Fields // =========================================================== protected int mDurationSeconds; protected String mDurationTextual; protected int mDistanceMeters; protected String mDistanceTextual; protected GeoPoint mStart; protected List<GeoPoint> mVias; protected GeoPoint mDestination; protected List<GeoPoint> mPolyLine; protected int[] mRouteSegmentLengths; protected int[] mRouteSegmentLengthsUpToDestination; protected int[] mLatitudeMinSpans; protected int[] mLatitudeMaxSpans; protected int[] mLongitudeMinSpans; protected int[] mLongitudeMaxSpans; protected RouteInstruction mStartInstruction; protected List<RouteInstruction> mRouteInstructions; protected RouteInstruction mDestinationInstruction; protected BoundingBoxE6 mBoundingBoxE6; protected int mHashCode; protected long mRouteHandleID; // =========================================================== // Constructors // =========================================================== public Route(){ this.mHashCode = new Long(System.currentTimeMillis()).intValue(); } // =========================================================== // Getter & Setter // =========================================================== public boolean hasRouteHandleID() { return this.mRouteHandleID != Constants.NOT_SET; } public long getRouteHandleID() { return this.mRouteHandleID; } public List<GeoPoint> getVias() { if(this.mVias == null) { this.mVias = new ArrayList<GeoPoint>(); } return this.mVias; } /** * @return the mDurationSeconds */ public int getDurationSeconds() { return this.mDurationSeconds; } /** * @return the mDurationTextual */ public String getDurationHtmlFormatted() { return this.mDurationTextual; } /** * @return the mDistanceMeters */ public int getDistanceMeters() { return this.mDistanceMeters; } /** * @return the mDistanceTextual */ public String getDistanceHtmlFormatted() { return this.mDistanceTextual; } /** * @return the mLatitudeMaxSpans */ public int[] getLatitudeMaxSpans() { return this.mLatitudeMaxSpans; } public int getLatitudeMaxSpan(final int index) { return this.mLatitudeMaxSpans[index]; } /** * @return the mLatitudeMinSpans */ public int[] getLatitudeMinSpans() { return this.mLatitudeMinSpans; } public int getLatitudeMinSpan(final int index) { return this.mLatitudeMinSpans[index]; } /** * @return the mLongitudeMaxSpans */ public int[] getLongitudeMaxSpans() { return this.mLongitudeMaxSpans; } public int getLongitudeMaxSpan(final int index) { return this.mLongitudeMaxSpans[index]; } /** * @return the mLongitudeMinSpans */ public int[] getLongitudeMinSpans() { return this.mLongitudeMinSpans; } public int getLongitudeMinSpan(final int index) { return this.mLongitudeMinSpans[index]; } /** * @return the mStart */ public GeoPoint getStart() { return this.mStart; } /** * @return the mDestination */ public GeoPoint getDestination() { return this.mDestination; } /** * @return the mPolyLine */ public List<GeoPoint> getPolyLine() { return this.mPolyLine; } /** * @return the mRouteSegmentLengths */ public int[] getRouteSegmentLengths() { return this.mRouteSegmentLengths; } /** * @return the mRouteSegmentLengthsUpToDestination */ public int[] getRouteSegmentLengthsUpToDestination() { return this.mRouteSegmentLengthsUpToDestination; } public RouteInstruction getStartRouteInstruction() { return this.mStartInstruction; } public RouteInstruction getDestinationRouteInstruction() { return this.mDestinationInstruction; } /** * @return the mTurnPointsRaw */ public List<RouteInstruction> getRouteInstructions() { return this.mRouteInstructions; } public BoundingBoxE6 getBoundingBoxE6(){ return this.mBoundingBoxE6; } public int getEstimatedRestSeconds(final int indexInRoute, final int distanceToNextTurnPoint) { int out = 0; for(int i = this.mRouteInstructions.size() - 1; i >= 0; i--){ final RouteInstruction ri = this.mRouteInstructions.get(i); if(!ri.contains(indexInRoute)){ out += ri.mDurationSeconds; }else{ out += ri.getEstimatedRestSeconds(indexInRoute, distanceToNextTurnPoint); break; } } return out; } public int findInPolyLine(final GeoPoint findMe, final int pStartingFrom) { final int polyLineLength = this.mPolyLine.size(); for (int i = pStartingFrom; i < polyLineLength; i++){ if(this.mPolyLine.get(i).equals(findMe)) { return i; } } throw new IllegalArgumentException("Could not find GeoPoint: " + findMe.toDoubleString() + " (started search at index: " + pStartingFrom + ")"); } public void setRouteHandleID(final long routeHandleID) { this.mRouteHandleID = routeHandleID; } public void setDurationSeconds(final int durationSeconds) { this.mDurationSeconds = durationSeconds; } public void setDurationTextual(final String durationTextual) { this.mDurationTextual = durationTextual; } public void setDistanceMeters(final int distanceMeters) { this.mDistanceMeters = distanceMeters; } public void setDistanceTextual(final String distanceTextual) { this.mDistanceTextual = distanceTextual; } public void setStart(final GeoPoint start) { this.mStart = start; } public void setVias(final ArrayList<GeoPoint> vias) { this.mVias = vias; } public void setDestination(final GeoPoint destination) { this.mDestination = destination; } public void setPolyLine(final ArrayList<GeoPoint> polyLine) { this.mPolyLine = polyLine; } public void setRouteSegmentLengths(final int[] routeSegmentLengths) { this.mRouteSegmentLengths = routeSegmentLengths; } public void setRouteSegmentLengthsUpToDestination(final int[] routeSegmentLengthsUpToDestination) { this.mRouteSegmentLengthsUpToDestination = routeSegmentLengthsUpToDestination; } public void setLatitudeMinSpans(final int[] latitudeMinSpans) { this.mLatitudeMinSpans = latitudeMinSpans; } public void setLatitudeMaxSpans(final int[] latitudeMaxSpans) { this.mLatitudeMaxSpans = latitudeMaxSpans; } public void setLongitudeMinSpans(final int[] longitudeMinSpans) { this.mLongitudeMinSpans = longitudeMinSpans; } public void setLongitudeMaxSpans(final int[] longitudeMaxSpans) { this.mLongitudeMaxSpans = longitudeMaxSpans; } public void setStartInstruction(final RouteInstruction startInstruction) { this.mStartInstruction = startInstruction; } public void setRouteInstructions(final ArrayList<RouteInstruction> routeInstructions) { this.mRouteInstructions = routeInstructions; } public void setDestinationInstruction(final RouteInstruction destinationInstruction) { this.mDestinationInstruction = destinationInstruction; } public void setBoundingBoxE6(final BoundingBoxE6 boundingBoxE6) { this.mBoundingBoxE6 = boundingBoxE6; } public int insert(final Route partialroute, final GeoPoint start, final GeoPoint end) { int index = this.findInPolyLine(start, 0); int add = 0; int k = 1; for (int i = 1; i < partialroute.getPolyLine().size(); i++) { GeoPoint point = partialroute.getPolyLine().get(i); if (point.equals(start) || point.equals(end)) { continue; } try { int j = this.findInPolyLine(point, index); index = j; k = 1; } catch (IllegalArgumentException ex) { mPolyLine.add(index + k, point); add++; k++; } } return add; } public void finalizeRoute(List<GeoPoint> pVias) { /* Drag to local variables, for performance reasons */ final List<GeoPoint> polyLine = getPolyLine(); final List<RouteInstruction> routeInstructions = getRouteInstructions(); final int[] routeSegmentLengths = new int[polyLine.size() - 1]; setRouteSegmentLengths(routeSegmentLengths); { final int[] routeSegmentLengthsUpToDestination = new int[polyLine.size()]; setRouteSegmentLengthsUpToDestination(routeSegmentLengthsUpToDestination); final int routeSegmentLengthsCount = routeSegmentLengths.length; int routeSegmentLengthsSummed = 0; /* Loop through all the MapPoints and determine the lengths of the segments and the length. */ for(int i = routeSegmentLengthsCount - 1; i > 0; i--){ routeSegmentLengthsUpToDestination[i-1] = routeSegmentLengthsSummed += routeSegmentLengths[i-1] = polyLine.get(i-1).distanceTo(polyLine.get(i)); } // TODO Check if list-edges get filled properly } { /* Fill the TurnPoints-Array. */ final Point vIn = new Point(), vOut = new Point(); /* Needed to calculate the angles. */ int curIndexInPolyLine; final int polyLineLenght = polyLine.size(); for(final RouteInstruction ri : getRouteInstructions()){ curIndexInPolyLine = ri.getFirstMotherPolylineIndex(); if(pVias != null) { for(final GeoPoint v : pVias) { if(this.mPolyLine.get(curIndexInPolyLine).equals(v)) { ri.setIsWaypoint(true); } } } if(curIndexInPolyLine == 0 || curIndexInPolyLine >= polyLineLenght - 1){ ri.setAngle(0); }else{ /* To calculate the angle, we need the line "into" that turnpoint. */ vIn.x = polyLine.get(curIndexInPolyLine - 1).getLongitudeE6() - polyLine.get(curIndexInPolyLine).getLongitudeE6(); vIn.y = polyLine.get(curIndexInPolyLine - 1).getLatitudeE6() - polyLine.get(curIndexInPolyLine).getLatitudeE6(); /* And the line 'out of' that turnpoint. */ vOut.x = polyLine.get(curIndexInPolyLine + 1).getLongitudeE6() - polyLine.get(curIndexInPolyLine).getLongitudeE6(); vOut.y = polyLine.get(curIndexInPolyLine + 1).getLatitudeE6() - polyLine.get(curIndexInPolyLine).getLatitudeE6(); /* Formula: angle = acos[(x * y) / (|x| * |y|)] */ if(GraphicsPoint.crossProduct(vIn, vOut) > 0) { ri.setAngle(- 180 + (float)Math.toDegrees(Math.acos(GraphicsPoint.dotProduct(vIn, vOut) / (FloatMath.sqrt(vIn.x * vIn.x + vIn.y * vIn.y) * FloatMath.sqrt(vOut.x * vOut.x + vOut.y * vOut.y))))); } else { ri.setAngle(180 - (float)Math.toDegrees(Math.acos(GraphicsPoint.dotProduct(vIn, vOut) / (FloatMath.sqrt(vIn.x * vIn.x + vIn.y * vIn.y) * FloatMath.sqrt(vOut.x * vOut.x + vOut.y * vOut.y))))); } } } } { /* Calculate the Lat/Lng Spans for each Point up to the next TurnPoint. */ setLatitudeMinSpans(new int[polyLine.size()]); setLatitudeMaxSpans(new int[polyLine.size()]); setLongitudeMinSpans(new int[polyLine.size()]); setLongitudeMaxSpans(new int[polyLine.size()]); /* Minimum & maximum latitude so we can span it * The latitude is clamped between -80 degrees and +80 degrees inclusive * thus we ensure that we go beyond that number. */ int minLatitude = LATITUDE_OVERMAX; int maxLatitude = -LATITUDE_OVERMAX; /* Minimum & maximum longitude so we can span it * The longitude is clamped between -180 degrees and +180 degrees inclusive * thus we ensure that we go beyond that number. */ int minLongitude = LONGITUDE_OVERMAX; int maxLongitude = -LONGITUDE_OVERMAX; int currentLatitude; int currentLongitude; GeoPoint current = null; int currentNextTurnIndex = routeInstructions.size() - 1; /* Starting backwards! */ for(int j = polyLine.size() - 1; j >= 0 ; j--){ current = polyLine.get(j); // FIXME Wird net richtig zurueckgesetzt... new :( Bug noch am leben? /* Check if we are 'on' a turnPoint. */ final int turnPointIndexInRoute; if(currentNextTurnIndex < 0) { turnPointIndexInRoute = 0; } else { turnPointIndexInRoute = routeInstructions.get(currentNextTurnIndex).getFirstMotherPolylineIndex(); } if(j == turnPointIndexInRoute){ currentNextTurnIndex--; /* Reset min/max. */ minLatitude = LATITUDE_OVERMAX; maxLatitude = -LATITUDE_OVERMAX; minLongitude = LONGITUDE_OVERMAX; maxLongitude = -LONGITUDE_OVERMAX; } /* Store to local field. */ currentLatitude = current.getLatitudeE6(); currentLongitude = current.getLongitudeE6(); /* Sets the minimum and maximum latitude so we can span and zoom */ if(minLatitude > currentLatitude) { minLatitude = currentLatitude; } if(maxLatitude < currentLatitude) { maxLatitude = currentLatitude; } /* Sets the minimum and maximum latitude so we can span and zoom */ if(minLongitude > currentLongitude) { minLongitude = currentLongitude; } if(maxLongitude < currentLongitude) { maxLongitude = currentLongitude; } getLatitudeMaxSpans()[j] = maxLatitude; getLatitudeMinSpans()[j] = minLatitude; getLongitudeMaxSpans()[j] = maxLongitude; getLongitudeMinSpans()[j] = minLongitude; } } } // =========================================================== // Methods from SuperClasses // =========================================================== @Override public int hashCode() { return this.mHashCode; } @Override public boolean equals(final Object other){ return (other != null) && (other instanceof Route) && (other.hashCode() == hashCode()); } // =========================================================== // Parcelable // =========================================================== public static final Parcelable.Creator<Route> CREATOR = new Parcelable.Creator<Route>() { public Route createFromParcel(final Parcel in) { return readFromParcel(in); } public Route[] newArray(final int size) { return new Route[size]; } }; @Override public int describeContents() { return 0; } @Override public void writeToParcel(final Parcel out, final int flags) { out.writeInt(this.mDurationSeconds); out.writeString(this.mDurationTextual); out.writeInt(this.mDistanceMeters); out.writeString(this.mDistanceTextual); out.writeParcelable(this.mStart, 0); out.writeTypedList(this.mVias); out.writeParcelable(this.mDestination, 0); out.writeTypedList(this.mPolyLine); out.writeIntArray(this.mRouteSegmentLengths); out.writeIntArray(this.mRouteSegmentLengthsUpToDestination); out.writeIntArray(this.mLatitudeMinSpans); out.writeIntArray(this.mLatitudeMaxSpans); out.writeIntArray(this.mLongitudeMinSpans); out.writeIntArray(this.mLongitudeMaxSpans); out.writeParcelable(this.mStartInstruction, 0); out.writeTypedList(this.mRouteInstructions); out.writeParcelable(this.mDestinationInstruction, 0); out.writeParcelable(this.mBoundingBoxE6, 0); out.writeInt(this.mHashCode); out.writeLong(this.mRouteHandleID); } private static Route readFromParcel(final Parcel in){ final Route out = new Route(); final ClassLoader geoPointClassLoader = GeoPoint.class.getClassLoader(); out.mDurationSeconds = in.readInt(); out.mDurationTextual = in.readString(); out.mDistanceMeters = in.readInt(); out.mDistanceTextual = in.readString(); out.mStart = in.readParcelable(geoPointClassLoader); out.mVias = new ArrayList<GeoPoint>(); in.readTypedList(out.mVias, GeoPoint.CREATOR); out.mDestination = in.readParcelable(geoPointClassLoader); out.mPolyLine = new ArrayList<GeoPoint>(); in.readTypedList(out.mPolyLine, GeoPoint.CREATOR); out.mRouteSegmentLengths = in.createIntArray(); out.mRouteSegmentLengthsUpToDestination = in.createIntArray(); out.mLatitudeMinSpans = in.createIntArray(); out.mLatitudeMaxSpans = in.createIntArray(); out.mLongitudeMinSpans = in.createIntArray(); out.mLongitudeMaxSpans = in.createIntArray(); out.mStartInstruction = in.readParcelable(Thread.currentThread().getContextClassLoader()); out.mRouteInstructions = new ArrayList<RouteInstruction>(); in.readTypedList(out.mRouteInstructions, RouteInstruction.CREATOR); out.mDestinationInstruction = in.readParcelable(Thread.currentThread().getContextClassLoader()); out.mBoundingBoxE6 = in.readParcelable(Thread.currentThread().getContextClassLoader()); out.mHashCode = in.readInt(); out.mRouteHandleID = in.readLong(); return out; } }