/*
* Licensed to GraphHopper GmbH under one or more contributor
* license agreements. See the NOTICE file distributed with this work for
* additional information regarding copyright ownership.
*
* GraphHopper GmbH licenses this file to you under the Apache License,
* Version 2.0 (the "License"); you may not use this file except in
* compliance with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.graphhopper;
import com.graphhopper.json.geo.JsonFeature;
import com.graphhopper.reader.DataReader;
import com.graphhopper.reader.dem.*;
import com.graphhopper.routing.*;
import com.graphhopper.routing.ch.CHAlgoFactoryDecorator;
import com.graphhopper.routing.ch.PrepareContractionHierarchies;
import com.graphhopper.routing.lm.LMAlgoFactoryDecorator;
import com.graphhopper.routing.subnetwork.PrepareRoutingSubnetworks;
import com.graphhopper.routing.template.AlternativeRoutingTemplate;
import com.graphhopper.routing.template.RoundTripRoutingTemplate;
import com.graphhopper.routing.template.RoutingTemplate;
import com.graphhopper.routing.template.ViaRoutingTemplate;
import com.graphhopper.routing.util.*;
import com.graphhopper.routing.weighting.*;
import com.graphhopper.storage.*;
import com.graphhopper.storage.change.ChangeGraphHelper;
import com.graphhopper.storage.change.ChangeGraphResponse;
import com.graphhopper.storage.index.LocationIndex;
import com.graphhopper.storage.index.LocationIndexTree;
import com.graphhopper.storage.index.QueryResult;
import com.graphhopper.util.*;
import com.graphhopper.util.Parameters.CH;
import com.graphhopper.util.Parameters.Landmark;
import com.graphhopper.util.Parameters.Routing;
import com.graphhopper.util.exceptions.PointDistanceExceededException;
import com.graphhopper.util.exceptions.PointOutOfBoundsException;
import com.graphhopper.util.shapes.BBox;
import com.graphhopper.util.shapes.GHPoint;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.File;
import java.io.IOException;
import java.text.DateFormat;
import java.util.*;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import static com.graphhopper.util.Parameters.Algorithms.*;
/**
* Easy to use access point to configure import and (offline) routing.
*
* @author Peter Karich
* @see GraphHopperAPI
*/
public class GraphHopper implements GraphHopperAPI {
private final Logger logger = LoggerFactory.getLogger(getClass());
private final String fileLockName = "gh.lock";
private final Set<RoutingAlgorithmFactoryDecorator> algoDecorators = new LinkedHashSet<>();
// utils
private final TranslationMap trMap = new TranslationMap().doImport();
boolean removeZipped = true;
boolean enableInstructions = true;
// for graph:
private GraphHopperStorage ghStorage;
private EncodingManager encodingManager;
private int defaultSegmentSize = -1;
private String ghLocation = "";
private DAType dataAccessType = DAType.RAM_STORE;
private boolean sortGraph = false;
private boolean elevation = false;
private LockFactory lockFactory = new NativeFSLockFactory();
private boolean allowWrites = true;
private String preferredLanguage = "";
private boolean fullyLoaded = false;
// for routing
private int maxRoundTripRetries = 3;
private boolean simplifyResponse = true;
private TraversalMode traversalMode = TraversalMode.NODE_BASED;
private int maxVisitedNodes = Integer.MAX_VALUE;
private String blockedRectangularAreas = "";
private int nonChMaxWaypointDistance = Integer.MAX_VALUE;
// for index
private LocationIndex locationIndex;
private int preciseIndexResolution = 300;
private int maxRegionSearch = 4;
// for prepare
private int minNetworkSize = 200;
private int minOneWayNetworkSize = 0;
// for LM prepare
private final LMAlgoFactoryDecorator lmFactoryDecorator = new LMAlgoFactoryDecorator();
// for CH prepare
private final CHAlgoFactoryDecorator chFactoryDecorator = new CHAlgoFactoryDecorator();
// for data reader
private String dataReaderFile;
private double dataReaderWayPointMaxDistance = 1;
private int dataReaderWorkerThreads = 2;
private boolean calcPoints = true;
private ElevationProvider eleProvider = ElevationProvider.NOOP;
private FlagEncoderFactory flagEncoderFactory = FlagEncoderFactory.DEFAULT;
private final ReadWriteLock readWriteLock = new ReentrantReadWriteLock();
public GraphHopper() {
chFactoryDecorator.setEnabled(true);
lmFactoryDecorator.setEnabled(false);
// order is important to use CH as base algo and set the approximation in the followed lm factory decorator
algoDecorators.add(chFactoryDecorator);
algoDecorators.add(lmFactoryDecorator);
}
/**
* For testing only
*/
protected GraphHopper loadGraph(GraphHopperStorage g) {
this.ghStorage = g;
fullyLoaded = true;
initLocationIndex();
return this;
}
/**
* @return the first flag encoder of the encoding manager
*/
FlagEncoder getDefaultVehicle() {
if (encodingManager == null)
throw new IllegalStateException("No encoding manager specified or loaded");
return encodingManager.fetchEdgeEncoders().get(0);
}
public EncodingManager getEncodingManager() {
return encodingManager;
}
/**
* Specify which vehicles can be read by this GraphHopper instance. An encoding manager defines
* how data from every vehicle is written (und read) into edges of the graph.
*/
public GraphHopper setEncodingManager(EncodingManager em) {
ensureNotLoaded();
this.encodingManager = em;
if (em.needsTurnCostsSupport())
traversalMode = TraversalMode.EDGE_BASED_2DIR;
return this;
}
public ElevationProvider getElevationProvider() {
return eleProvider;
}
public GraphHopper setElevationProvider(ElevationProvider eleProvider) {
if (eleProvider == null || eleProvider == ElevationProvider.NOOP)
setElevation(false);
else
setElevation(true);
this.eleProvider = eleProvider;
return this;
}
/**
* Threads for data reading.
*/
protected int getWorkerThreads() {
return dataReaderWorkerThreads;
}
/**
* Return maximum distance (in meter) to reduce points via douglas peucker while OSM import.
*/
protected double getWayPointMaxDistance() {
return dataReaderWayPointMaxDistance;
}
/**
* This parameter specifies how to reduce points via douglas peucker while OSM import. Higher
* value means more details, unit is meter. Default is 1. Disable via 0.
*/
public GraphHopper setWayPointMaxDistance(double wayPointMaxDistance) {
this.dataReaderWayPointMaxDistance = wayPointMaxDistance;
return this;
}
public TraversalMode getTraversalMode() {
return traversalMode;
}
/**
* Sets the default traversal mode used for the algorithms and preparation.
*/
public GraphHopper setTraversalMode(TraversalMode traversalMode) {
this.traversalMode = traversalMode;
return this;
}
/**
* Configures the underlying storage and response to be used on a well equipped server. Result
* also optimized for usage in the web module i.e. try reduce network IO.
*/
public GraphHopper forServer() {
setSimplifyResponse(true);
return setInMemory();
}
/**
* Configures the underlying storage to be used on a Desktop computer or within another Java
* application with enough RAM but no network latency.
*/
public GraphHopper forDesktop() {
setSimplifyResponse(false);
return setInMemory();
}
/**
* Configures the underlying storage to be used on a less powerful machine like Android or
* Raspberry Pi with only few MB of RAM.
*/
public GraphHopper forMobile() {
setSimplifyResponse(false);
return setMemoryMapped();
}
/**
* Precise location resolution index means also more space (disc/RAM) could be consumed and
* probably slower query times, which would be e.g. not suitable for Android. The resolution
* specifies the tile width (in meter).
*/
public GraphHopper setPreciseIndexResolution(int precision) {
ensureNotLoaded();
preciseIndexResolution = precision;
return this;
}
public GraphHopper setMinNetworkSize(int minNetworkSize, int minOneWayNetworkSize) {
this.minNetworkSize = minNetworkSize;
this.minOneWayNetworkSize = minOneWayNetworkSize;
return this;
}
/**
* This method call results in an in-memory graph.
*/
public GraphHopper setInMemory() {
ensureNotLoaded();
dataAccessType = DAType.RAM_STORE;
return this;
}
/**
* Only valid option for in-memory graph and if you e.g. want to disable store on flush for unit
* tests. Specify storeOnFlush to true if you want that existing data will be loaded FROM disc
* and all in-memory data will be flushed TO disc after flush is called e.g. while OSM import.
*
* @param storeOnFlush true by default
*/
public GraphHopper setStoreOnFlush(boolean storeOnFlush) {
ensureNotLoaded();
if (storeOnFlush)
dataAccessType = DAType.RAM_STORE;
else
dataAccessType = DAType.RAM;
return this;
}
/**
* Enable memory mapped configuration if not enough memory is available on the target platform.
*/
public GraphHopper setMemoryMapped() {
ensureNotLoaded();
dataAccessType = DAType.MMAP;
return this;
}
/**
* Not yet stable enough to offer it for everyone
*/
private GraphHopper setUnsafeMemory() {
ensureNotLoaded();
dataAccessType = DAType.UNSAFE_STORE;
return this;
}
/**
* This method enabled or disables the speed mode (Contraction Hierarchies)
*
* @deprecated use {@link #setCHEnabled(boolean)} instead
*/
public GraphHopper setCHEnable(boolean enable) {
return setCHEnabled(enable);
}
public final boolean isCHEnabled() {
return chFactoryDecorator.isEnabled();
}
/**
* Enables or disables contraction hierarchies (CH). This speed-up mode is enabled by default.
*/
public GraphHopper setCHEnabled(boolean enable) {
ensureNotLoaded();
chFactoryDecorator.setEnabled(enable);
return this;
}
public int getMaxVisitedNodes() {
return maxVisitedNodes;
}
/**
* This methods stops the algorithm from searching further if the resulting path would go over
* the specified node count, important if none-CH routing is used.
*/
public void setMaxVisitedNodes(int maxVisitedNodes) {
this.maxVisitedNodes = maxVisitedNodes;
}
/**
* @return true if storing and fetching elevation data is enabled. Default is false
*/
public boolean hasElevation() {
return elevation;
}
/**
* Enable storing and fetching elevation data. Default is false
*/
public GraphHopper setElevation(boolean includeElevation) {
this.elevation = includeElevation;
return this;
}
public boolean isEnableInstructions() {
return enableInstructions;
}
/**
* This method specifies if the import should include way names to be able to return
* instructions for a route.
*/
public GraphHopper setEnableInstructions(boolean b) {
ensureNotLoaded();
enableInstructions = b;
return this;
}
public String getPreferredLanguage() {
return preferredLanguage;
}
/**
* This method specifies the preferred language for way names during import.
* <p>
* Language code as defined in ISO 639-1 or ISO 639-2.
* <ul>
* <li>If no preferred language is specified, only the default language with no tag will be
* imported.</li>
* <li>If a language is specified, it will be imported if its tag is found, otherwise fall back
* to default language.</li>
* </ul>
*/
public GraphHopper setPreferredLanguage(String preferredLanguage) {
ensureNotLoaded();
if (preferredLanguage == null)
throw new IllegalArgumentException("preferred language cannot be null");
this.preferredLanguage = preferredLanguage;
return this;
}
/**
* This methods enables gps point calculation. If disabled only distance will be calculated.
*/
public GraphHopper setEnableCalcPoints(boolean b) {
calcPoints = b;
return this;
}
/**
* This method specifies if the returned path should be simplified or not, via douglas-peucker
* or similar algorithm.
*/
private GraphHopper setSimplifyResponse(boolean doSimplify) {
this.simplifyResponse = doSimplify;
return this;
}
public String getGraphHopperLocation() {
return ghLocation;
}
/**
* Sets the graphhopper folder.
*/
public GraphHopper setGraphHopperLocation(String ghLocation) {
ensureNotLoaded();
if (ghLocation == null)
throw new IllegalArgumentException("graphhopper location cannot be null");
this.ghLocation = ghLocation;
return this;
}
public String getDataReaderFile() {
return dataReaderFile;
}
/**
* This file can be any file type supported by the DataReader. E.g. for the OSMReader it is the
* OSM xml (.osm), a compressed xml (.osm.zip or .osm.gz) or a protobuf file (.pbf)
*/
public GraphHopper setDataReaderFile(String dataReaderFileStr) {
ensureNotLoaded();
if (Helper.isEmpty(dataReaderFileStr))
throw new IllegalArgumentException("Data reader file cannot be empty.");
dataReaderFile = dataReaderFileStr;
return this;
}
/**
* The underlying graph used in algorithms.
*
* @throws IllegalStateException if graph is not instantiated.
*/
public GraphHopperStorage getGraphHopperStorage() {
if (ghStorage == null)
throw new IllegalStateException("GraphHopper storage not initialized");
return ghStorage;
}
public void setGraphHopperStorage(GraphHopperStorage ghStorage) {
this.ghStorage = ghStorage;
fullyLoaded = true;
}
/**
* The location index created from the graph.
*
* @throws IllegalStateException if index is not initialized
*/
public LocationIndex getLocationIndex() {
if (locationIndex == null)
throw new IllegalStateException("Location index not initialized");
return locationIndex;
}
protected void setLocationIndex(LocationIndex locationIndex) {
this.locationIndex = locationIndex;
}
/**
* Sorts the graph which requires more RAM while import. See #12
*/
public GraphHopper setSortGraph(boolean sortGraph) {
ensureNotLoaded();
this.sortGraph = sortGraph;
return this;
}
public boolean isAllowWrites() {
return allowWrites;
}
/**
* Specifies if it is allowed for GraphHopper to write. E.g. for read only filesystems it is not
* possible to create a lock file and so we can avoid write locks.
*/
public GraphHopper setAllowWrites(boolean allowWrites) {
this.allowWrites = allowWrites;
return this;
}
public TranslationMap getTranslationMap() {
return trMap;
}
public GraphHopper setFlagEncoderFactory(FlagEncoderFactory factory) {
this.flagEncoderFactory = factory;
return this;
}
public FlagEncoderFactory getFlagEncoderFactory() {
return this.flagEncoderFactory;
}
/**
* Reads configuration from a CmdArgs object. Which can be manually filled, or via main(String[]
* args) ala CmdArgs.read(args) or via configuration file ala
* CmdArgs.readFromConfig("config.properties", "graphhopper.config")
*/
public GraphHopper init(CmdArgs args) {
args = CmdArgs.readFromConfigAndMerge(args, "config", "graphhopper.config");
if (args.has("osmreader.osm"))
throw new IllegalArgumentException("Instead osmreader.osm use datareader.file, for other changes see core/files/changelog.txt");
String tmpOsmFile = args.get("datareader.file", "");
if (!Helper.isEmpty(tmpOsmFile))
dataReaderFile = tmpOsmFile;
String graphHopperFolder = args.get("graph.location", "");
if (Helper.isEmpty(graphHopperFolder) && Helper.isEmpty(ghLocation)) {
if (Helper.isEmpty(dataReaderFile))
throw new IllegalArgumentException("You need to specify an OSM file.");
graphHopperFolder = Helper.pruneFileEnd(dataReaderFile) + "-gh";
}
// graph
setGraphHopperLocation(graphHopperFolder);
defaultSegmentSize = args.getInt("graph.dataaccess.segment_size", defaultSegmentSize);
String graphDATypeStr = args.get("graph.dataaccess", "RAM_STORE");
dataAccessType = DAType.fromString(graphDATypeStr);
sortGraph = args.getBool("graph.do_sort", sortGraph);
removeZipped = args.getBool("graph.remove_zipped", removeZipped);
int bytesForFlags = args.getInt("graph.bytes_for_flags", 4);
String flagEncodersStr = args.get("graph.flag_encoders", "");
if (!flagEncodersStr.isEmpty())
setEncodingManager(new EncodingManager(flagEncoderFactory, flagEncodersStr, bytesForFlags));
if (args.get("graph.locktype", "native").equals("simple"))
lockFactory = new SimpleFSLockFactory();
else
lockFactory = new NativeFSLockFactory();
// elevation
String eleProviderStr = args.get("graph.elevation.provider", "noop").toLowerCase();
// keep fallback until 0.8
boolean eleCalcMean = args.has("graph.elevation.calcmean")
? args.getBool("graph.elevation.calcmean", false)
: args.getBool("graph.elevation.calc_mean", false);
String cacheDirStr = args.get("graph.elevation.cache_dir", "");
if (cacheDirStr.isEmpty())
cacheDirStr = args.get("graph.elevation.cachedir", "");
String baseURL = args.get("graph.elevation.base_url", "");
if (baseURL.isEmpty())
args.get("graph.elevation.baseurl", "");
DAType elevationDAType = DAType.fromString(args.get("graph.elevation.dataaccess", "MMAP"));
ElevationProvider tmpProvider = ElevationProvider.NOOP;
if (eleProviderStr.equalsIgnoreCase("srtm")) {
tmpProvider = new SRTMProvider();
} else if (eleProviderStr.equalsIgnoreCase("cgiar")) {
CGIARProvider cgiarProvider = new CGIARProvider();
cgiarProvider.setAutoRemoveTemporaryFiles(args.getBool("graph.elevation.cgiar.clear", true));
tmpProvider = cgiarProvider;
}
tmpProvider.setCalcMean(eleCalcMean);
tmpProvider.setCacheDir(new File(cacheDirStr));
if (!baseURL.isEmpty())
tmpProvider.setBaseURL(baseURL);
tmpProvider.setDAType(elevationDAType);
setElevationProvider(tmpProvider);
// optimizable prepare
minNetworkSize = args.getInt("prepare.min_network_size", minNetworkSize);
minOneWayNetworkSize = args.getInt("prepare.min_one_way_network_size", minOneWayNetworkSize);
// prepare CH, LM, ...
for (RoutingAlgorithmFactoryDecorator decorator : algoDecorators) {
decorator.init(args);
}
// osm import
dataReaderWayPointMaxDistance = args.getDouble(Routing.INIT_WAY_POINT_MAX_DISTANCE, dataReaderWayPointMaxDistance);
dataReaderWorkerThreads = args.getInt("datareader.worker_threads", dataReaderWorkerThreads);
enableInstructions = args.getBool("datareader.instructions", enableInstructions);
preferredLanguage = args.get("datareader.preferred_language", preferredLanguage);
// index
preciseIndexResolution = args.getInt("index.high_resolution", preciseIndexResolution);
maxRegionSearch = args.getInt("index.max_region_search", maxRegionSearch);
// routing
maxVisitedNodes = args.getInt(Routing.INIT_MAX_VISITED_NODES, Integer.MAX_VALUE);
maxRoundTripRetries = args.getInt(RoundTrip.INIT_MAX_RETRIES, maxRoundTripRetries);
nonChMaxWaypointDistance = args.getInt(Parameters.NON_CH.MAX_NON_CH_POINT_DISTANCE, Integer.MAX_VALUE);
blockedRectangularAreas = args.get(Routing.BLOCK_AREA, "");
return this;
}
private void printInfo() {
logger.info("version " + Constants.VERSION + "|" + Constants.BUILD_DATE + " (" + Constants.getVersions() + ")");
if (ghStorage != null)
logger.info("graph " + ghStorage.toString() + ", details:" + ghStorage.toDetailsString());
}
/**
* Imports provided data from disc and creates graph. Depending on the settings the resulting
* graph will be stored to disc so on a second call this method will only load the graph from
* disc which is usually a lot faster.
*/
public GraphHopper importOrLoad() {
if (!load(ghLocation)) {
printInfo();
process(ghLocation);
} else {
printInfo();
}
return this;
}
/**
* Creates the graph from OSM data.
*/
private GraphHopper process(String graphHopperLocation) {
setGraphHopperLocation(graphHopperLocation);
GHLock lock = null;
try {
if (ghStorage.getDirectory().getDefaultType().isStoring()) {
lockFactory.setLockDir(new File(graphHopperLocation));
lock = lockFactory.create(fileLockName, true);
if (!lock.tryLock())
throw new RuntimeException("To avoid multiple writers we need to obtain a write lock but it failed. In " + graphHopperLocation, lock.getObtainFailedReason());
}
try {
DataReader reader = importData();
DateFormat f = Helper.createFormatter();
ghStorage.getProperties().put("datareader.import.date", f.format(new Date()));
if (reader.getDataDate() != null)
ghStorage.getProperties().put("datareader.data.date", f.format(reader.getDataDate()));
} catch (IOException ex) {
throw new RuntimeException("Cannot read file " + getDataReaderFile(), ex);
}
cleanUp();
postProcessing();
flush();
} finally {
if (lock != null)
lock.release();
}
return this;
}
protected DataReader importData() throws IOException {
ensureWriteAccess();
if (ghStorage == null)
throw new IllegalStateException("Load graph before importing OSM data");
if (dataReaderFile == null)
throw new IllegalStateException("Couldn't load from existing folder: " + ghLocation
+ " but also cannot use file for DataReader as it wasn't specified!");
encodingManager.setEnableInstructions(enableInstructions);
encodingManager.setPreferredLanguage(preferredLanguage);
DataReader reader = createReader(ghStorage);
logger.info("using " + ghStorage.toString() + ", memory:" + Helper.getMemInfo());
reader.readGraph();
return reader;
}
protected DataReader createReader(GraphHopperStorage ghStorage) {
throw new UnsupportedOperationException("Cannot create DataReader. Solutions: avoid import via calling load directly, "
+ "provide a DataReader or use e.g. GraphHopperOSM or a different subclass");
}
protected DataReader initDataReader(DataReader reader) {
if (dataReaderFile == null)
throw new IllegalArgumentException("No file for DataReader specified");
logger.info("start creating graph from " + dataReaderFile);
return reader.setFile(new File(dataReaderFile)).
setElevationProvider(eleProvider).
setWorkerThreads(dataReaderWorkerThreads).
setWayPointMaxDistance(dataReaderWayPointMaxDistance);
}
/**
* Opens existing graph folder.
*
* @param graphHopperFolder is the folder containing graphhopper files. Can be a compressed file
* too ala folder-content.ghz.
*/
@Override
public boolean load(String graphHopperFolder) {
if (Helper.isEmpty(graphHopperFolder))
throw new IllegalStateException("GraphHopperLocation is not specified. Call setGraphHopperLocation or init before");
if (fullyLoaded)
throw new IllegalStateException("graph is already successfully loaded");
File tmpFileOrFolder = new File(graphHopperFolder);
if (!tmpFileOrFolder.isDirectory() && tmpFileOrFolder.exists()) {
throw new IllegalArgumentException("GraphHopperLocation cannot be an existing file. Has to be either non-existing or a folder.");
} else {
File compressed = new File(graphHopperFolder + ".ghz");
if (compressed.exists() && !compressed.isDirectory()) {
try {
new Unzipper().unzip(compressed.getAbsolutePath(), graphHopperFolder, removeZipped);
} catch (IOException ex) {
throw new RuntimeException("Couldn't extract file " + compressed.getAbsolutePath()
+ " to " + graphHopperFolder, ex);
}
}
}
setGraphHopperLocation(graphHopperFolder);
if (encodingManager == null)
setEncodingManager(EncodingManager.create(flagEncoderFactory, ghLocation));
if (!allowWrites && dataAccessType.isMMap())
dataAccessType = DAType.MMAP_RO;
GHDirectory dir = new GHDirectory(ghLocation, dataAccessType);
GraphExtension ext = encodingManager.needsTurnCostsSupport()
? new TurnCostExtension() : new GraphExtension.NoOpExtension();
if (lmFactoryDecorator.isEnabled())
initLMAlgoFactoryDecorator();
if (chFactoryDecorator.isEnabled()) {
initCHAlgoFactoryDecorator();
ghStorage = new GraphHopperStorage(chFactoryDecorator.getWeightings(), dir, encodingManager, hasElevation(), ext);
} else {
ghStorage = new GraphHopperStorage(dir, encodingManager, hasElevation(), ext);
}
ghStorage.setSegmentSize(defaultSegmentSize);
if (!new File(graphHopperFolder).exists())
return false;
GHLock lock = null;
try {
// create locks only if writes are allowed, if they are not allowed a lock cannot be created
// (e.g. on a read only filesystem locks would fail)
if (ghStorage.getDirectory().getDefaultType().isStoring() && isAllowWrites()) {
lockFactory.setLockDir(new File(ghLocation));
lock = lockFactory.create(fileLockName, false);
if (!lock.tryLock())
throw new RuntimeException("To avoid reading partial data we need to obtain the read lock but it failed. In " + ghLocation, lock.getObtainFailedReason());
}
if (!ghStorage.loadExisting())
return false;
postProcessing();
fullyLoaded = true;
return true;
} finally {
if (lock != null)
lock.release();
}
}
public RoutingAlgorithmFactory getAlgorithmFactory(HintsMap map) {
RoutingAlgorithmFactory routingAlgorithmFactory = new RoutingAlgorithmFactorySimple();
for (RoutingAlgorithmFactoryDecorator decorator : algoDecorators) {
if (decorator.isEnabled())
routingAlgorithmFactory = decorator.getDecoratedAlgorithmFactory(routingAlgorithmFactory, map);
}
return routingAlgorithmFactory;
}
public GraphHopper addAlgorithmFactoryDecorator(RoutingAlgorithmFactoryDecorator algoFactoryDecorator) {
if (!algoDecorators.add(algoFactoryDecorator))
throw new IllegalArgumentException("Decorator was already added " + algoFactoryDecorator.getClass());
return this;
}
public final CHAlgoFactoryDecorator getCHFactoryDecorator() {
return chFactoryDecorator;
}
private void initCHAlgoFactoryDecorator() {
if (!chFactoryDecorator.hasWeightings()) {
for (FlagEncoder encoder : encodingManager.fetchEdgeEncoders()) {
for (String chWeightingStr : chFactoryDecorator.getWeightingsAsStrings()) {
Weighting weighting = createWeighting(new HintsMap(chWeightingStr), encoder, null);
chFactoryDecorator.addWeighting(weighting);
}
}
}
}
public final LMAlgoFactoryDecorator getLMFactoryDecorator() {
return lmFactoryDecorator;
}
private void initLMAlgoFactoryDecorator() {
if (lmFactoryDecorator.hasWeightings())
return;
for (FlagEncoder encoder : encodingManager.fetchEdgeEncoders()) {
for (String lmWeightingStr : lmFactoryDecorator.getWeightingsAsStrings()) {
Weighting weighting = createWeighting(new HintsMap(lmWeightingStr), encoder, null);
lmFactoryDecorator.addWeighting(weighting);
}
}
}
/**
* Does the preparation and creates the location index
*/
public void postProcessing() {
// Later: move this into the GraphStorage.optimize method
// Or: Doing it after preparation to optimize shortcuts too. But not possible yet #12
if (sortGraph) {
if (ghStorage.isCHPossible() && isCHPrepared())
throw new IllegalArgumentException("Sorting a prepared CHGraph is not possible yet. See #12");
GraphHopperStorage newGraph = GHUtility.newStorage(ghStorage);
GHUtility.sortDFS(ghStorage, newGraph);
logger.info("graph sorted (" + Helper.getMemInfo() + ")");
ghStorage = newGraph;
}
if (hasElevation()) {
interpolateBridgesAndOrTunnels();
}
initLocationIndex();
if (chFactoryDecorator.isEnabled())
chFactoryDecorator.createPreparations(ghStorage, traversalMode);
if (!isCHPrepared())
prepareCH();
if (lmFactoryDecorator.isEnabled())
lmFactoryDecorator.createPreparations(ghStorage, traversalMode, locationIndex);
loadOrPrepareLM();
}
private void interpolateBridgesAndOrTunnels() {
if (ghStorage.getEncodingManager().supports("generic")) {
final FlagEncoder genericFlagEncoder = ghStorage.getEncodingManager()
.getEncoder("generic");
if (!(genericFlagEncoder instanceof DataFlagEncoder)) {
throw new IllegalStateException("'generic' flag encoder for elevation interpolation of "
+ "bridges and tunnels is enabled but does not have the expected type "
+ DataFlagEncoder.class.getName() + ".");
}
final DataFlagEncoder dataFlagEncoder = (DataFlagEncoder) genericFlagEncoder;
StopWatch sw = new StopWatch().start();
new TunnelElevationInterpolator(ghStorage, dataFlagEncoder).execute();
float tunnel = sw.stop().getSeconds();
sw = new StopWatch().start();
new BridgeElevationInterpolator(ghStorage, dataFlagEncoder).execute();
logger.info("Bridge interpolation " + (int) sw.stop().getSeconds() + "s, "
+ "tunnel interpolation " + (int) tunnel + "s");
}
}
/**
* Based on the hintsMap and the specified encoder a Weighting instance can be
* created. Note that all URL parameters are available in the hintsMap as String if
* you use the web module.
*
* @param hintsMap all parameters influencing the weighting. E.g. parameters coming via
* GHRequest.getHints or directly via "&api.xy=" from the URL of the web UI
* @param encoder the required vehicle
* @param graph The Graph enables the Weighting for NodeAccess and more
* @return the weighting to be used for route calculation
* @see HintsMap
*/
public Weighting createWeighting(HintsMap hintsMap, FlagEncoder encoder, Graph graph) {
String weighting = hintsMap.getWeighting().toLowerCase();
if (encoder.supports(GenericWeighting.class)) {
DataFlagEncoder dataEncoder = (DataFlagEncoder) encoder;
ConfigMap cMap = dataEncoder.readStringMap(hintsMap);
// add default blocked rectangular areas from config properties
if (!this.blockedRectangularAreas.isEmpty()) {
String val = this.blockedRectangularAreas;
String blockedAreasFromRequest = hintsMap.get(Parameters.Routing.BLOCK_AREA, "");
if (!blockedAreasFromRequest.isEmpty())
val += ";" + blockedAreasFromRequest;
hintsMap.put(Parameters.Routing.BLOCK_AREA, val);
}
cMap = new GraphEdgeIdFinder(graph, locationIndex).parseStringHints(cMap, hintsMap, new DefaultEdgeFilter(encoder));
GenericWeighting genericWeighting = new GenericWeighting(dataEncoder, cMap);
genericWeighting.setGraph(graph);
return genericWeighting;
} else if ("shortest".equalsIgnoreCase(weighting)) {
return new ShortestWeighting(encoder);
} else if ("fastest".equalsIgnoreCase(weighting) || weighting.isEmpty()) {
if (encoder.supports(PriorityWeighting.class))
return new PriorityWeighting(encoder, hintsMap);
else
return new FastestWeighting(encoder, hintsMap);
} else if ("curvature".equalsIgnoreCase(weighting)) {
if (encoder.supports(CurvatureWeighting.class))
return new CurvatureWeighting(encoder, hintsMap);
} else if ("short_fastest".equalsIgnoreCase(weighting)) {
return new ShortFastestWeighting(encoder, hintsMap);
}
throw new IllegalArgumentException("weighting " + weighting + " not supported");
}
/**
* Potentially wraps the specified weighting into a TurnWeighting instance.
*/
public Weighting createTurnWeighting(Graph graph, Weighting weighting, TraversalMode tMode) {
FlagEncoder encoder = weighting.getFlagEncoder();
if (encoder.supports(TurnWeighting.class) && !tMode.equals(TraversalMode.NODE_BASED))
return new TurnWeighting(weighting, (TurnCostExtension) graph.getExtension());
return weighting;
}
@Override
public GHResponse route(GHRequest request) {
GHResponse response = new GHResponse();
calcPaths(request, response);
return response;
}
/**
* This method calculates the alternative path list using the low level Path objects.
*/
public List<Path> calcPaths(GHRequest request, GHResponse ghRsp) {
if (ghStorage == null || !fullyLoaded)
throw new IllegalStateException("Do a successful call to load or importOrLoad before routing");
if (ghStorage.isClosed())
throw new IllegalStateException("You need to create a new GraphHopper instance as it is already closed");
// default handling
String vehicle = request.getVehicle();
if (vehicle.isEmpty()) {
vehicle = getDefaultVehicle().toString();
request.setVehicle(vehicle);
}
Lock readLock = readWriteLock.readLock();
readLock.lock();
try {
if (!encodingManager.supports(vehicle))
throw new IllegalArgumentException("Vehicle " + vehicle + " unsupported. "
+ "Supported are: " + getEncodingManager());
HintsMap hints = request.getHints();
String tModeStr = hints.get("traversal_mode", traversalMode.toString());
TraversalMode tMode = TraversalMode.fromString(tModeStr);
if (hints.has(Routing.EDGE_BASED))
tMode = hints.getBool(Routing.EDGE_BASED, false) ? TraversalMode.EDGE_BASED_2DIR : TraversalMode.NODE_BASED;
FlagEncoder encoder = encodingManager.getEncoder(vehicle);
boolean disableCH = hints.getBool(CH.DISABLE, false);
if (!chFactoryDecorator.isDisablingAllowed() && disableCH)
throw new IllegalArgumentException("Disabling CH not allowed on the server-side");
boolean disableLM = hints.getBool(Landmark.DISABLE, false);
if (!lmFactoryDecorator.isDisablingAllowed() && disableLM)
throw new IllegalArgumentException("Disabling LM not allowed on the server-side");
String algoStr = request.getAlgorithm();
if (algoStr.isEmpty())
algoStr = chFactoryDecorator.isEnabled() && !disableCH &&
!(lmFactoryDecorator.isEnabled() && !disableLM) ? DIJKSTRA_BI : ASTAR_BI;
List<GHPoint> points = request.getPoints();
// TODO Maybe we should think about a isRequestValid method that checks all that stuff that we could do to fail fast
// For example see #734
checkIfPointsAreInBounds(points);
RoutingTemplate routingTemplate;
if (ROUND_TRIP.equalsIgnoreCase(algoStr))
routingTemplate = new RoundTripRoutingTemplate(request, ghRsp, locationIndex, maxRoundTripRetries);
else if (ALT_ROUTE.equalsIgnoreCase(algoStr))
routingTemplate = new AlternativeRoutingTemplate(request, ghRsp, locationIndex);
else
routingTemplate = new ViaRoutingTemplate(request, ghRsp, locationIndex);
List<Path> altPaths = null;
int maxRetries = routingTemplate.getMaxRetries();
Locale locale = request.getLocale();
Translation tr = trMap.getWithFallBack(locale);
for (int i = 0; i < maxRetries; i++) {
StopWatch sw = new StopWatch().start();
List<QueryResult> qResults = routingTemplate.lookup(points, encoder);
ghRsp.addDebugInfo("idLookup:" + sw.stop().getSeconds() + "s");
if (ghRsp.hasErrors())
return Collections.emptyList();
RoutingAlgorithmFactory tmpAlgoFactory = getAlgorithmFactory(hints);
Weighting weighting;
QueryGraph queryGraph;
if (chFactoryDecorator.isEnabled() && !disableCH) {
boolean forceCHHeading = hints.getBool(CH.FORCE_HEADING, false);
if (!forceCHHeading && request.hasFavoredHeading(0))
throw new IllegalArgumentException("Heading is not (fully) supported for CHGraph. See issue #483");
// if LM is enabled we have the LMFactory with the CH algo!
RoutingAlgorithmFactory chAlgoFactory = tmpAlgoFactory;
if (tmpAlgoFactory instanceof LMAlgoFactoryDecorator.LMRAFactory)
chAlgoFactory = ((LMAlgoFactoryDecorator.LMRAFactory) tmpAlgoFactory).getDefaultAlgoFactory();
if (chAlgoFactory instanceof PrepareContractionHierarchies)
weighting = ((PrepareContractionHierarchies) chAlgoFactory).getWeighting();
else
throw new IllegalStateException("Although CH was enabled a non-CH algorithm factory was returned " + tmpAlgoFactory);
tMode = getCHFactoryDecorator().getNodeBase();
queryGraph = new QueryGraph(ghStorage.getGraph(CHGraph.class, weighting));
queryGraph.lookup(qResults);
} else {
checkNonChMaxWaypointDistance(points);
queryGraph = new QueryGraph(ghStorage);
queryGraph.lookup(qResults);
weighting = createWeighting(hints, encoder, queryGraph);
ghRsp.addDebugInfo("tmode:" + tMode.toString());
}
int maxVisitedNodesForRequest = hints.getInt(Routing.MAX_VISITED_NODES, maxVisitedNodes);
if (maxVisitedNodesForRequest > maxVisitedNodes)
throw new IllegalArgumentException("The max_visited_nodes parameter has to be below or equal to:" + maxVisitedNodes);
weighting = createTurnWeighting(queryGraph, weighting, tMode);
AlgorithmOptions algoOpts = AlgorithmOptions.start().
algorithm(algoStr).traversalMode(tMode).weighting(weighting).
maxVisitedNodes(maxVisitedNodesForRequest).
hints(hints).
build();
altPaths = routingTemplate.calcPaths(queryGraph, tmpAlgoFactory, algoOpts);
boolean tmpEnableInstructions = hints.getBool(Routing.INSTRUCTIONS, enableInstructions);
boolean tmpCalcPoints = hints.getBool(Routing.CALC_POINTS, calcPoints);
double wayPointMaxDistance = hints.getDouble(Routing.WAY_POINT_MAX_DISTANCE, 1d);
DouglasPeucker peucker = new DouglasPeucker().setMaxDistance(wayPointMaxDistance);
PathMerger pathMerger = new PathMerger().
setCalcPoints(tmpCalcPoints).
setDouglasPeucker(peucker).
setEnableInstructions(tmpEnableInstructions).
setSimplifyResponse(simplifyResponse && wayPointMaxDistance > 0);
if (routingTemplate.isReady(pathMerger, tr))
break;
}
return altPaths;
} catch (IllegalArgumentException ex) {
ghRsp.addError(ex);
return Collections.emptyList();
} finally {
readLock.unlock();
}
}
/**
* This method applies the changes to the graph specified as feature collection. It does so by locking the routing
* to avoid concurrent changes which could result in incorrect routing (like when done while a Dijkstra search) or
* also while just reading one edge row (inconsistent edge properties).
*/
public ChangeGraphResponse changeGraph(Collection<JsonFeature> collection) {
// TODO allow calling this method if called before CH preparation
if (getCHFactoryDecorator().isEnabled())
throw new IllegalArgumentException("To use the changeGraph API you need to turn off CH");
Lock writeLock = readWriteLock.writeLock();
writeLock.lock();
try {
ChangeGraphHelper overlay = createChangeGraphHelper(ghStorage, locationIndex);
long updateCount = overlay.applyChanges(encodingManager, collection);
return new ChangeGraphResponse(updateCount);
} finally {
writeLock.unlock();
}
}
protected ChangeGraphHelper createChangeGraphHelper(Graph graph, LocationIndex locationIndex) {
return new ChangeGraphHelper(graph, locationIndex);
}
private void checkIfPointsAreInBounds(List<GHPoint> points) {
BBox bounds = getGraphHopperStorage().getBounds();
for (int i = 0; i < points.size(); i++) {
GHPoint point = points.get(i);
if (!bounds.contains(point.getLat(), point.getLon())) {
throw new PointOutOfBoundsException("Point " + i + " is ouf of bounds: " + point, i);
}
}
}
private void checkNonChMaxWaypointDistance(List<GHPoint> points) {
if (nonChMaxWaypointDistance == Integer.MAX_VALUE) {
return;
}
GHPoint lastPoint = points.get(0);
GHPoint point;
double dist;
DistanceCalc calc = Helper.DIST_3D;
for (int i = 1; i < points.size(); i++) {
point = points.get(i);
dist = calc.calcDist(lastPoint.getLat(), lastPoint.getLon(), point.getLat(), point.getLon());
if (dist > nonChMaxWaypointDistance) {
Map<String, Object> detailMap = new HashMap<>(2);
detailMap.put("from", i - 1);
detailMap.put("to", i);
throw new PointDistanceExceededException("Point " + i + " is too far from Point " + (i - 1) + ": " + point, detailMap);
}
lastPoint = point;
}
}
protected LocationIndex createLocationIndex(Directory dir) {
LocationIndexTree tmpIndex = new LocationIndexTree(ghStorage, dir);
tmpIndex.setResolution(preciseIndexResolution);
tmpIndex.setMaxRegionSearch(maxRegionSearch);
if (!tmpIndex.loadExisting()) {
ensureWriteAccess();
tmpIndex.prepareIndex();
}
return tmpIndex;
}
/**
* Initializes the location index after the import is done.
*/
protected void initLocationIndex() {
if (locationIndex != null)
throw new IllegalStateException("Cannot initialize locationIndex twice!");
locationIndex = createLocationIndex(ghStorage.getDirectory());
}
private boolean isCHPrepared() {
return "true".equals(ghStorage.getProperties().get(CH.PREPARE + "done"))
// remove old property in >0.9
|| "true".equals(ghStorage.getProperties().get("prepare.done"));
}
private boolean isLMPrepared() {
return "true".equals(ghStorage.getProperties().get(Landmark.PREPARE + "done"));
}
protected void prepareCH() {
boolean tmpPrepare = chFactoryDecorator.isEnabled();
if (tmpPrepare) {
ensureWriteAccess();
ghStorage.freeze();
chFactoryDecorator.prepare(ghStorage.getProperties());
ghStorage.getProperties().put(CH.PREPARE + "done", true);
}
}
/**
* For landmarks it is required to always call this method: either it creates the landmark data or it loads it.
*/
protected void loadOrPrepareLM() {
boolean tmpPrepare = lmFactoryDecorator.isEnabled();
if (tmpPrepare) {
ensureWriteAccess();
ghStorage.freeze();
if (lmFactoryDecorator.loadOrDoWork(ghStorage.getProperties()))
ghStorage.getProperties().put(Landmark.PREPARE + "done", true);
}
}
/**
* Internal method to clean up the graph.
*/
protected void cleanUp() {
int prevNodeCount = ghStorage.getNodes();
PrepareRoutingSubnetworks preparation = new PrepareRoutingSubnetworks(ghStorage, encodingManager.fetchEdgeEncoders());
preparation.setMinNetworkSize(minNetworkSize);
preparation.setMinOneWayNetworkSize(minOneWayNetworkSize);
preparation.doWork();
int currNodeCount = ghStorage.getNodes();
logger.info("edges: " + ghStorage.getAllEdges().getMaxId() + ", nodes " + currNodeCount
+ ", there were " + preparation.getMaxSubnetworks()
+ " subnetworks. removed them => " + (prevNodeCount - currNodeCount)
+ " less nodes");
}
protected void flush() {
logger.info("flushing graph " + ghStorage.toString() + ", details:" + ghStorage.toDetailsString() + ", "
+ Helper.getMemInfo() + ")");
ghStorage.flush();
logger.info("flushed graph " + Helper.getMemInfo() + ")");
fullyLoaded = true;
}
/**
* Releases all associated resources like memory or files. But it does not remove them. To
* remove the files created in graphhopperLocation you have to call clean().
*/
public void close() {
if (ghStorage != null)
ghStorage.close();
if (locationIndex != null)
locationIndex.close();
try {
lockFactory.forceRemove(fileLockName, true);
} catch (Exception ex) {
// silently fail e.g. on Windows where we cannot remove an unreleased native lock
}
}
/**
* Removes the on-disc routing files. Call only after calling close or before importOrLoad or
* load
*/
public void clean() {
if (getGraphHopperLocation().isEmpty())
throw new IllegalStateException("Cannot clean GraphHopper without specified graphHopperLocation");
File folder = new File(getGraphHopperLocation());
Helper.removeDir(folder);
}
protected void ensureNotLoaded() {
if (fullyLoaded)
throw new IllegalStateException("No configuration changes are possible after loading the graph");
}
protected void ensureWriteAccess() {
if (!allowWrites)
throw new IllegalStateException("Writes are not allowed!");
}
public void setNonChMaxWaypointDistance(int nonChMaxWaypointDistance) {
this.nonChMaxWaypointDistance = nonChMaxWaypointDistance;
}
}