/*
This file is part of RouteConverter.
RouteConverter 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 2 of the License, or
(at your option) any later version.
RouteConverter 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 RouteConverter; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
Copyright (C) 2007 Christian Pesch. All Rights Reserved.
*/
package slash.navigation.graphhopper;
import com.graphhopper.GHRequest;
import com.graphhopper.GHResponse;
import com.graphhopper.PathWrapper;
import com.graphhopper.reader.osm.GraphHopperOSM;
import com.graphhopper.routing.util.EncodingManager;
import com.graphhopper.util.PointList;
import slash.navigation.common.BoundingBox;
import slash.navigation.common.DistanceAndTime;
import slash.navigation.common.LongitudeAndLatitude;
import slash.navigation.common.NavigationPosition;
import slash.navigation.common.SimpleNavigationPosition;
import slash.navigation.datasources.DataSource;
import slash.navigation.datasources.Downloadable;
import slash.navigation.download.Action;
import slash.navigation.download.Download;
import slash.navigation.download.DownloadManager;
import slash.navigation.download.FileAndChecksum;
import slash.navigation.routing.DownloadFuture;
import slash.navigation.routing.RoutingResult;
import slash.navigation.routing.RoutingService;
import slash.navigation.routing.TravelMode;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.logging.Logger;
import java.util.prefs.Preferences;
import static java.lang.System.currentTimeMillis;
import static java.util.Arrays.asList;
import static java.util.Collections.singletonList;
import static slash.common.io.Directories.ensureDirectory;
import static slash.common.io.Directories.getApplicationDirectory;
import static slash.navigation.graphhopper.PbfUtil.DOT_OSM;
import static slash.navigation.graphhopper.PbfUtil.DOT_PBF;
/**
* Encapsulates access to the GraphHopper.
*
* @author Christian Pesch
*/
public class GraphHopper implements RoutingService {
private static final Preferences preferences = Preferences.userNodeForPackage(GraphHopper.class);
private static final Logger log = Logger.getLogger(GraphHopper.class.getName());
private static final String DIRECTORY_PREFERENCE = "directory";
private static final String BASE_URL_PREFERENCE = "baseUrl";
private static final TravelMode CAR = new TravelMode("Car");
private static final List<TravelMode> TRAVEL_MODES = asList(new TravelMode("Bike"), CAR, new TravelMode("Foot"));
// omitted: Bike2, Hike, MotorCycle, MTB, RacingBike
private final DownloadManager downloadManager;
private DataSource dataSource;
private DownloadableFinder finder;
private com.graphhopper.GraphHopper hopper;
private java.io.File osmPbfFile = null;
public GraphHopper(DownloadManager downloadManager) {
this.downloadManager = downloadManager;
}
public String getName() {
return "GraphHopper";
}
public synchronized boolean isInitialized() {
return getDataSource() != null;
}
public synchronized DataSource getDataSource() {
return dataSource;
}
public synchronized void setDataSource(DataSource dataSource) {
this.dataSource = dataSource;
finder = new DownloadableFinder(dataSource, getDirectory());
}
public boolean isDownload() {
return true;
}
public boolean isSupportTurnpoints() {
return false;
}
public boolean isSupportAvoidFerries() {
return false;
}
public boolean isSupportAvoidHighways() {
return false;
}
public boolean isSupportAvoidTolls() {
return false;
}
public List<TravelMode> getAvailableTravelModes() {
return TRAVEL_MODES;
}
public TravelMode getPreferredTravelMode() {
return CAR;
}
public String getPath() {
return preferences.get(DIRECTORY_PREFERENCE, "");
}
public void setPath(String path) {
preferences.put(DIRECTORY_PREFERENCE, path);
}
private String getBaseUrl() {
return preferences.get(BASE_URL_PREFERENCE, getDataSource().getBaseUrl());
}
private java.io.File getDirectory() {
String directoryName = getPath();
java.io.File f = new java.io.File(directoryName);
if (!f.exists())
directoryName = getApplicationDirectory(getDataSource().getDirectory()).getAbsolutePath();
return ensureDirectory(directoryName);
}
private java.io.File createFile(String key) {
return new java.io.File(getDirectory(), key);
}
private java.io.File createPath(java.io.File file) {
String name = file.getName().replace(DOT_PBF, "").replace(DOT_OSM, "");
return new java.io.File(file.getParent(), name);
}
public RoutingResult getRouteBetween(NavigationPosition from, NavigationPosition to, TravelMode travelMode) {
initializeHopper();
long start = currentTimeMillis();
try {
GHRequest request = new GHRequest(from.getLatitude(), from.getLongitude(), to.getLatitude(), to.getLongitude());
request.setVehicle(travelMode.getName().toUpperCase());
GHResponse response = hopper.route(request);
PathWrapper best = response.getBest();
return new RoutingResult(asPositions(best.getPoints()), new DistanceAndTime(best.getDistance(), best.getTime() / 1000), true);
} finally {
long end = currentTimeMillis();
log.info("GraphHopper: routing from " + from + " to " + to + " took " + (end - start) + " milliseconds");
}
}
private String getAvailableTravelModeNames() {
StringBuilder result = new StringBuilder();
List<TravelMode> availableTravelModes = getAvailableTravelModes();
for(int i=0; i < availableTravelModes.size(); i++) {
result.append(availableTravelModes.get(i).getName().toLowerCase());
if(i < availableTravelModes.size() - 1)
result.append(",");
}
return result.toString();
}
private static final Object initializationLock = new Object();
private java.io.File getOsmPbfFile() {
synchronized (initializationLock) {
return osmPbfFile;
}
}
void setOsmPbfFile(java.io.File osmPbfFile) {
synchronized (initializationLock) {
this.osmPbfFile = osmPbfFile;
}
}
void initializeHopper() {
synchronized (initializationLock) {
java.io.File file = getOsmPbfFile();
if (file == null)
return;
if (hopper != null)
hopper.close();
hopper = new GraphHopperOSM().
setOSMFile(file.getAbsolutePath()).
forDesktop().
setEncodingManager(new EncodingManager(getAvailableTravelModeNames())).
setCHEnabled(false).
setEnableInstructions(false).
setGraphHopperLocation(createPath(file).getAbsolutePath()).
importOrLoad();
setOsmPbfFile(null);
}
}
private List<NavigationPosition> asPositions(PointList points) {
List<NavigationPosition> result = new ArrayList<>();
for (int i = 0, c = points.getSize(); i < c; i++) {
result.add(new SimpleNavigationPosition(points.getLongitude(i), points.getLatitude(i), points.getElevation(i), null));
}
return result;
}
public DownloadFuture downloadRoutingDataFor(List<LongitudeAndLatitude> longitudeAndLatitudes) {
BoundingBox routeBoundingBox = createBoundingBox(longitudeAndLatitudes);
final Downloadable downloadable = finder.getDownloadableFor(routeBoundingBox);
final java.io.File file = downloadable != null ? createFile(downloadable.getUri()) : null;
setOsmPbfFile(file);
return new DownloadFuture() {
public boolean isRequiresDownload() {
return file != null && !file.exists();
}
public boolean isRequiresProcessing() {
return file != null && !createPath(file).exists();
}
public void download() {
downloadAndWait(downloadable);
}
public void process() {
initializeHopper();
}
};
}
private BoundingBox createBoundingBox(List<LongitudeAndLatitude> longitudeAndLatitudes) {
List<NavigationPosition> positions = new ArrayList<>();
for (LongitudeAndLatitude longitudeAndLatitude : longitudeAndLatitudes) {
positions.add(new SimpleNavigationPosition(longitudeAndLatitude.longitude, longitudeAndLatitude.latitude));
}
return new BoundingBox(positions);
}
private void downloadAndWait(Downloadable downloadable) {
Download download = download(downloadable);
downloadManager.waitForCompletion(singletonList(download));
}
private Download download(Downloadable downloadable) {
String uri = downloadable.getUri();
String url = getBaseUrl() + uri;
return downloadManager.queueForDownload(getName() + " Routing Data: " + uri, url, Action.valueOf(dataSource.getAction()),
new FileAndChecksum(createFile(downloadable.getUri()), downloadable.getLatestChecksum()), null);
}
public long calculateRemainingDownloadSize(List<BoundingBox> boundingBoxes) {
Collection<Downloadable> downloadables = finder.getDownloadableFor(boundingBoxes);
long notExists = 0L;
for(Downloadable downloadable : downloadables) {
Long contentLength = downloadable.getLatestChecksum().getContentLength();
if(contentLength == null)
continue;
java.io.File file = createFile(downloadable.getUri());
if(!file.exists())
notExists += contentLength;
}
return notExists;
}
public void downloadRoutingData(List<BoundingBox> boundingBoxes) {
Collection<Downloadable> downloadables = finder.getDownloadableFor(boundingBoxes);
for (Downloadable downloadable : downloadables) {
download(downloadable);
}
}
}