/* 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.brouter; import btools.router.OsmNodeNamed; import btools.router.OsmPathElement; import btools.router.OsmTrack; import btools.router.RoutingContext; import btools.router.RoutingEngine; import slash.navigation.common.Bearing; 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.io.File; import java.io.FilenameFilter; import java.util.ArrayList; import java.util.Collection; import java.util.HashSet; import java.util.List; import java.util.logging.Logger; import java.util.prefs.Preferences; import static java.lang.String.format; import static java.lang.System.currentTimeMillis; import static java.util.Arrays.asList; import static slash.common.io.Directories.ensureDirectory; import static slash.common.io.Directories.getApplicationDirectory; import static slash.common.io.Files.getExtension; import static slash.common.io.Files.removeExtension; import static slash.navigation.common.Bearing.calculateBearing; /** * Encapsulates access to the BRouter. * * @author Christian Pesch */ public class BRouter implements RoutingService { private static final Preferences preferences = Preferences.userNodeForPackage(BRouter.class); private static final Logger log = Logger.getLogger(BRouter.class.getName()); private static final String DIRECTORY_PREFERENCE = "directory"; private static final String PROFILES_BASE_URL_PREFERENCE = "profilesBaseUrl"; private static final String SEGMENTS_BASE_URL_PREFERENCE = "segmentsBaseUrl"; private static final TravelMode MOPED = new TravelMode("moped"); private final DownloadManager downloadManager; private DataSource profiles, segments; private final RoutingContext routingContext = new RoutingContext(); public BRouter(DownloadManager downloadManager) { this.downloadManager = downloadManager; } public String getName() { return "BRouter"; } public synchronized boolean isInitialized() { return getProfiles() != null && getSegments() != null; } public synchronized DataSource getProfiles() { return profiles; } public synchronized DataSource getSegments() { return segments; } public synchronized void setProfilesAndSegments(DataSource profiles, DataSource segments) { this.profiles = profiles; this.segments = segments; } 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() { List<TravelMode> result = new ArrayList<>(); File[] files = getProfilesDirectory().listFiles(new FilenameFilter() { public boolean accept(File dir, String name) { return getExtension(name).equals(".brf"); } }); if (files != null) { for(File file : files) { result.add(new TravelMode(removeExtension(file.getName()))); } } return result; } public TravelMode getPreferredTravelMode() { return MOPED; } public String getPath() { return preferences.get(DIRECTORY_PREFERENCE, ""); } public void setPath(String path) { preferences.put(DIRECTORY_PREFERENCE, path); } private String getProfilesBaseUrl() { return preferences.get(PROFILES_BASE_URL_PREFERENCE, getProfiles().getBaseUrl()); } private String getSegmentsBaseUrl() { return preferences.get(SEGMENTS_BASE_URL_PREFERENCE, getSegments().getBaseUrl()); } private java.io.File getDirectory(DataSource dataSource, String directoryName) { String path = getPath() + "/" + directoryName; java.io.File f = new java.io.File(path); if (!f.exists()) directoryName = getApplicationDirectory(dataSource.getDirectory()).getAbsolutePath(); return ensureDirectory(directoryName); } private java.io.File getProfilesDirectory() { return getDirectory(getProfiles(), "profiles"); } private java.io.File createProfileFile(String key) { return new java.io.File(getProfilesDirectory(), key); } private java.io.File getSegmentsDirectory() { return getDirectory(getSegments(), "segments4"); } private java.io.File createSegmentFile(String key) { return new java.io.File(getSegmentsDirectory(), key); } String createFileKey(double longitude, double latitude) { int longitudeAsInteger = ((int) longitude / 5) * 5; int latitudeAsInteger = ((int) latitude / 5) * 5; return format("%s%d_%s%d.rd5", longitude < 0 ? "W" : "E", longitude < 0 ? -longitudeAsInteger : longitudeAsInteger, latitude < 0 ? "S" : "N", latitude < 0 ? -latitudeAsInteger : latitudeAsInteger); } public RoutingResult getRouteBetween(NavigationPosition from, NavigationPosition to, TravelMode travelMode) { long start = currentTimeMillis(); try { File profile = new File(getProfilesDirectory(), travelMode.getName() + ".brf"); if (!profile.exists()) { profile = new File(getProfilesDirectory(), getPreferredTravelMode().getName() + ".brf"); log.warning(format("Failed to find profile for travel mode %s; using preferred travel mode %s", travelMode, getPreferredTravelMode())); } if (!profile.exists()) { List<TravelMode> availableTravelModes = getAvailableTravelModes(); if (availableTravelModes.size() == 0) { log.warning(format("Cannot route between %s and %s: no travel modes found in %s", from, to, getProfilesDirectory())); return new RoutingResult(asList(from, to), new DistanceAndTime(calculateBearing(from.getLongitude(), from.getLatitude(), to.getLongitude(), to.getLatitude()).getDistance(), null), false); } TravelMode firstTravelMode = availableTravelModes.get(0); profile = new File(getProfilesDirectory(), firstTravelMode.getName() + ".brf"); log.warning(format("Failed to find profile for travel mode %s; using first travel mode %s", travelMode, firstTravelMode)); } routingContext.localFunction = profile.getPath(); double bearing = Bearing.calculateBearing(from.getLongitude(), from.getLatitude(), to.getLongitude(), to.getLatitude()).getDistance(); long routingTimeout = (long) (1000L + bearing / 20.0); log.info(format("Distance %f results to default routing timeout %d", bearing, routingTimeout)); RoutingEngine routingEngine = new RoutingEngine(null, null, getSegmentsDirectory().getPath(), createWaypoints(from, to), routingContext); routingEngine.quite = true; routingEngine.doRun(preferences.getLong("routingTimeout", routingTimeout)); if (routingEngine.getErrorMessage() != null) throw new RoutingException(format("Cannot route between %s and %s", from, to), routingEngine.getErrorMessage()); OsmTrack track = routingEngine.getFoundTrack(); double distance = routingEngine.getDistance(); return new RoutingResult(asPositions(track), new DistanceAndTime(distance, null), true); } finally { long end = currentTimeMillis(); log.info("BRouter: routing from " + from + " to " + to + " took " + (end - start) + " milliseconds"); } } private List<OsmNodeNamed> createWaypoints(NavigationPosition from, NavigationPosition to) { List<OsmNodeNamed> result = new ArrayList<>(); result.add(asOsmNodeNamed(from.getDescription(), from.getLongitude(), from.getLatitude())); result.add(asOsmNodeNamed(to.getDescription(), to.getLongitude(), to.getLatitude())); return result; } private OsmNodeNamed asOsmNodeNamed(String name, Double toLongitude, Double toLatitude) { OsmNodeNamed result = new OsmNodeNamed(); result.name = name; result.ilon = asLongitude(toLongitude); result.ilat = asLatitude(toLatitude); return result; } int asLongitude(Double longitude) { return longitude != null ? (int) ((longitude + 180.0) * 1000000.0 + 0.5) : 0; } int asLatitude(Double latitude) { return latitude != null ? (int) ((latitude + 90.0) * 1000000.0 + 0.5) : 0; } private List<NavigationPosition> asPositions(OsmTrack track) { List<NavigationPosition> result = new ArrayList<>(); for (OsmPathElement element : track.nodes) { result.add(new SimpleNavigationPosition(asLongitude(element.getILon()), asLatitude(element.getILat()), element.getElev(), null)); } return result; } double asLongitude(int longitude) { return (longitude / 1000000.0) - 180.0; } double asLatitude(int latitude) { return (latitude / 1000000.0) - 90.0; } public DownloadFuture downloadRoutingDataFor(List<LongitudeAndLatitude> longitudeAndLatitudes) { Collection<String> uris = new HashSet<>(); for (LongitudeAndLatitude longitudeAndLatitude : longitudeAndLatitudes) { uris.add(createFileKey(longitudeAndLatitude.longitude, longitudeAndLatitude.latitude)); } final Collection<Downloadable> notExistingSegments = new HashSet<>(); if(isInitialized()) { for (String key : uris) { Downloadable downloadable = getSegments().getDownloadable(key); if (downloadable != null) { if (!createSegmentFile(downloadable.getUri()).exists()) notExistingSegments.add(downloadable); } } } final Collection<Downloadable> notExistingProfiles = new HashSet<>(); if (isInitialized()) { for (Downloadable downloadable : getProfiles().getFiles()) { if (!createProfileFile(downloadable.getUri()).exists()) notExistingProfiles.add(downloadable); } } return new DownloadFuture() { public boolean isRequiresDownload() { return !notExistingProfiles.isEmpty() || !notExistingSegments.isEmpty(); } public boolean isRequiresProcessing() { return false; } public void download() { downloadAndWait(notExistingProfiles, notExistingSegments); } public void process() {} }; } private void downloadAndWait(Collection<Downloadable> profiles, Collection<Downloadable> segments) { Collection<Download> downloads = new HashSet<>(); for (Downloadable downloadable : profiles) downloads.add(downloadProfile(downloadable)); for (Downloadable downloadable : segments) downloads.add(downloadSegment(downloadable)); if (!downloads.isEmpty()) downloadManager.waitForCompletion(downloads); } private Download downloadProfile(Downloadable downloadable) { String uri = downloadable.getUri(); String url = getProfilesBaseUrl() + uri; return downloadManager.queueForDownload(getName() + " Routing Profile: " + uri, url, Action.valueOf(getProfiles().getAction()), new FileAndChecksum(createProfileFile(downloadable.getUri()), downloadable.getLatestChecksum()), null); } private Download downloadSegment(Downloadable downloadable) { String uri = downloadable.getUri(); String url = getSegmentsBaseUrl() + uri; return downloadManager.queueForDownload(getName() + " Routing Segment: " + uri, url, Action.valueOf(getSegments().getAction()), new FileAndChecksum(createSegmentFile(downloadable.getUri()), downloadable.getLatestChecksum()), null); } private Collection<Downloadable> getDownloadablesFor(BoundingBox boundingBox) { Collection<Downloadable> result = new HashSet<>(); double longitude = boundingBox.getSouthWest().getLongitude(); while (longitude < boundingBox.getNorthEast().getLongitude()) { double latitude = boundingBox.getSouthWest().getLatitude(); while (latitude < boundingBox.getNorthEast().getLatitude()) { String key = createFileKey(longitude, latitude); Downloadable downloadable = getSegments().getDownloadable(key); if (downloadable != null) result.add(downloadable); latitude += 1.0; } longitude += 1.0; } return result; } private Collection<Downloadable> getDownloadablesFor(List<BoundingBox> boundingBoxes) { Collection<Downloadable> result = new HashSet<>(); for (BoundingBox boundingBox : boundingBoxes) result.addAll(getDownloadablesFor(boundingBox)); return result; } public long calculateRemainingDownloadSize(List<BoundingBox> boundingBoxes) { Collection<Downloadable> downloadables = getDownloadablesFor(boundingBoxes); long notExists = 0L; for(Downloadable downloadable : downloadables) { Long contentLength = downloadable.getLatestChecksum().getContentLength(); if(contentLength == null) continue; java.io.File file = createSegmentFile(downloadable.getUri()); if(!file.exists()) notExists += contentLength; } return notExists; } public void downloadRoutingData(List<BoundingBox> boundingBoxes) { Collection<Downloadable> downloadables = getDownloadablesFor(boundingBoxes); for(Downloadable downloadable : downloadables) { downloadSegment(downloadable); } } }