/*
* The CroudTrip! application aims at revolutionizing the car-ride-sharing market with its easy,
* user-friendly and highly automated way of organizing shared Trips. Copyright (C) 2015 Nazeeh Ammari,
* Philipp Eichhorn, Ricarda Hohn, Vanessa Lange, Alexander Popp, Frederik Simon, Michael Weber
* This program is free software: you can redistribute it and/or modify it under the terms of the GNU
* Affero 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 Affero General Public License for more details.
* You should have received a copy of the GNU Affero General Public License along with this program.
* If not, see http://www.gnu.org/licenses/.
*/
package org.croudtrip.directions;
import com.google.maps.DirectionsApi;
import com.google.maps.DistanceMatrixApi;
import com.google.maps.GeoApiContext;
import com.google.maps.model.DirectionsLeg;
import com.google.maps.model.DirectionsRoute;
import com.google.maps.model.DirectionsStep;
import com.google.maps.model.DistanceMatrix;
import com.google.maps.model.DistanceMatrixRow;
import com.google.maps.model.EncodedPolyline;
import com.google.maps.model.LatLng;
import org.croudtrip.api.directions.Route;
import org.croudtrip.api.directions.RouteDistanceDuration;
import org.croudtrip.api.directions.RouteLocation;
import org.croudtrip.logs.LogManager;
import org.croudtrip.utils.Pair;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import javax.inject.Inject;
public class DirectionsManager {
private final GeoApiContext geoApiContext;
private final LogManager logManager;
private static HashMap<CachingRouteKey, List<Route>> cachedRoutes = new HashMap<>();
private static class CachingRouteKey{
private List<RouteLocation> waypoints;
private long creationTimestamp;
public CachingRouteKey(RouteLocation startLocaction, RouteLocation destinationLocation, List<RouteLocation> waypoints) {
this.waypoints = new ArrayList<>();
this.waypoints.add(startLocaction);
this.waypoints.addAll( waypoints );
this.waypoints.add(destinationLocation);
this.creationTimestamp = System.currentTimeMillis();
}
public long getCreationTimestamp() {
return creationTimestamp;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
CachingRouteKey that = (CachingRouteKey) o;
return waypoints.equals(that.waypoints) && Math.abs(creationTimestamp - that.creationTimestamp) < 10 * 60*1000;
}
@Override
public int hashCode() {
return waypoints.hashCode();
}
}
@Inject
DirectionsManager(GeoApiContext geoApiContext, LogManager logManager) {
this.geoApiContext = geoApiContext;
this.logManager = logManager;
}
/**
* Find a simple route using GoogleDirectionAPI from a starting point to a destination
* @param startLocation the start of the directions query
* @param endLocation the destination of the query
* @return a list of possible routes to get to the given destination
*/
public List<Route> getDirections(RouteLocation startLocation, RouteLocation endLocation) {
return getDirections(startLocation, endLocation, new ArrayList<RouteLocation>());
}
/**
* Find a route using Google's Directions API from a starting point to a destination with serveral
* given waypoints.
* @param startLocation the start of the directions query
* @param endLocation the destination of the query
* @param waypoints the waypoints that should be visited
* @return a list of possible routes to the given destination
*/
public List<Route> getDirections(RouteLocation startLocation, RouteLocation endLocation, List<RouteLocation> waypoints) {
CachingRouteKey cachingRouteKey = new CachingRouteKey( startLocation, endLocation, waypoints );
if( cachedRoutes.containsKey( cachingRouteKey ) ){
logManager.d("DIRECTIONS REQUEST " + startLocation + " to " + endLocation + " --> USED CACHED RESULT");
return cachedRoutes.get( cachingRouteKey );
}
LatLng origin = new LatLng(startLocation.getLat(), startLocation.getLng());
LatLng destination = new LatLng(endLocation.getLat(), endLocation.getLng());
logManager.d("DIRECTIONS REQUEST " + startLocation + " to " + endLocation + " (" + waypoints.size() + " wps)");
List<LatLng> llWaypoints = new ArrayList<LatLng>();
if( waypoints.size() > 0 ) {
for (RouteLocation loc : waypoints) {
LatLng waypoint = new LatLng(loc.getLat(), loc.getLng());
llWaypoints.add(waypoint);
}
}
String[] stringWaypoints = new String[llWaypoints.size()];
for (int i = 0; i < stringWaypoints.length; ++i) {
/*GeocodingResult[] result = GeocodingApi.newRequest(geoApiContext).latlng(new LatLng(loc.getLat(), loc.getLng())).await();
// no route found ...
if (result == null || result.length == 0) return new ArrayList<>();
stringWaypoints[i] = result[0].formattedAddress;*/
LatLng waypoint = llWaypoints.get(i);
stringWaypoints[i] = waypoint.toUrlValue();
}
// create a list containing all the waypoints (also start and destination)
// don't modify given waypoints list.
List<RouteLocation> allWaypoints = new ArrayList<RouteLocation>();
allWaypoints.add( startLocation );
for( RouteLocation loc : waypoints )
allWaypoints.add( loc );
allWaypoints.add( endLocation );
List<Route> result = new ArrayList<>();
DirectionsRoute[] googleRoutes = new DirectionsRoute[0];
try {
googleRoutes = DirectionsApi.newRequest(geoApiContext)
.origin(origin)
.destination(destination)
.waypoints(stringWaypoints)
.await();
for (DirectionsRoute googleRoute : googleRoutes) {
result.add(createRoute(allWaypoints, googleRoute));
}
cachedRoutes.put( cachingRouteKey, result );
return result;
} catch (Exception e) {
throw new RuntimeException(e);
}
}
/**
* Computes distance and duration for a given start and destination location using Google's Distance API Matrix
* @param startLocation the start of the directions query
* @param destinationLocation the destination of the query
* @return A {@link RouteDistanceDuration} that contains the distance and the duration of the trip from start to destination
*/
public RouteDistanceDuration getDistanceAndDurationForDirection( RouteLocation startLocation, RouteLocation destinationLocation ){
LatLng origin = new LatLng(startLocation.getLat(), startLocation.getLng());
LatLng destination = new LatLng(destinationLocation.getLat(), destinationLocation.getLng());
try {
DistanceMatrix distanceMatrix = DistanceMatrixApi.newRequest( geoApiContext )
.origins(origin)
.destinations(destination)
.await();
if( distanceMatrix.rows.length == 0 ||
distanceMatrix.rows[0].elements.length == 0 )
throw new RuntimeException("No distance and duration found.");
return new RouteDistanceDuration( distanceMatrix.rows[0].elements[0].distance.inMeters, distanceMatrix.rows[0].elements[0].duration.inSeconds );
} catch (Exception e) {
e.printStackTrace();
throw new RuntimeException(e);
}
}
private Route createRoute(List<RouteLocation> waypoints, DirectionsRoute googleRoute) {
long distanceInMeters = 0;
long durationInSeconds = 0;
List<Long> legDurationsInSeconds = new ArrayList<>();
List<Long> legDistancesInMeters = new ArrayList<>();
List<Integer> waypointIndices = new ArrayList<>();
List<LatLng> points = new ArrayList<>();
for (DirectionsLeg leg : googleRoute.legs) {
//logManager.d("Leg: " + leg.distance.inMeters);
distanceInMeters += leg.distance.inMeters;
durationInSeconds += leg.duration.inSeconds;
legDurationsInSeconds.add( leg.duration.inSeconds );
legDistancesInMeters.add(leg.distance.inMeters);
waypointIndices.add( points.size() );
for (DirectionsStep step : leg.steps) {
points.addAll(step.polyline.decodePath());
}
}
Polyline polyline = PolylineEncoder.encode( points, waypointIndices );
String warnings;
if (googleRoute.warnings.length > 0) {
boolean firstIter = true;
warnings = "";
for (String warning : googleRoute.warnings) {
if (firstIter) {
warnings += "\n";
firstIter = false;
}
warnings += warning;
}
} else {
warnings = null;
}
return new Route(
waypoints,
polyline.getPolyline(),
distanceInMeters,
durationInSeconds,
legDurationsInSeconds,
legDistancesInMeters,
googleRoute.copyrights,
warnings,
System.currentTimeMillis()/1000,
polyline.getPolylineStringIndices());
}
}