diff --git a/CHANGELOG.md b/CHANGELOG.md index 4eda9dfa8b1..a9c4ed2f48f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,7 +1,16 @@ +### 5.0 [not yet released] + +- use GraphHopper#setGraphHopperLocation before calling load() instead of GraphHopper#load(graphHopperLocation) (#2437) +- barrier nodes at junctions are now ignored (#2433) +- AbstractFlagEncoder#handleNodeTags was replaced by AbstractFlagEncoder#isBarrier (#2434) +- consider heading when snapping coordinates to the road network, this is especially important for navigation (#2411) +- OSMReader no longer sets the artificial 'estimated_center' tag and processNode also receives EMPTY_NODEs (971d686) +- added Toll.MISSING; custom models must be adapted to check for explicit toll values e.g `toll != NO` -> `toll == HGV || toll == ALL` (#2164) + ### 4.0 [29 Sep 2021] - faster node-based CH preparation (~20%), (#2390) -- more flexible ElevationProvider interface, support providing elevation via node tags (#2374, #23281) +- more flexible ElevationProvider interface, support providing elevation via node tags (#2374, #2381) - added country encoded value for all countries (#2353) - bike improvements (#2357, #2371, #2389) - improved handling of barriers (#2345, #2340, #2406) diff --git a/client-hc/pom.xml b/client-hc/pom.xml index 1d07077fe74..4f85b8c648c 100644 --- a/client-hc/pom.xml +++ b/client-hc/pom.xml @@ -22,21 +22,21 @@ 4.0.0 directions-api-client-hc - 4.9-SNAPSHOT + 5.0-SNAPSHOT jar GraphHopper Directions API hand-crafted Java Client. com.github.GIScience.graphhopper graphhopper-parent - 4.9-SNAPSHOT + 5.0-SNAPSHOT com.github.GIScience.graphhopper graphhopper-web-api - 4.9-SNAPSHOT + 5.0-SNAPSHOT com.squareup.okhttp3 diff --git a/config-example.yml b/config-example.yml index 0432d559945..a933e485550 100644 --- a/config-example.yml +++ b/config-example.yml @@ -164,9 +164,9 @@ graphhopper: # custom_areas.directory: path/to/custom_areas ##### Country Rules ##### - # GraphHopper applies country-specific routing rules (enabled by default) during import. - # Use this flag to disable these rules (you need to repeat the import for changes to take effect) - # country_rules.enable: false + # GraphHopper applies country-specific routing rules during import (not enabled by default). + # You need to redo the import for changes to take effect. + # country_rules.enabled: true # Dropwizard server configuration server: diff --git a/core/pom.xml b/core/pom.xml index 89f60d17b18..4b6131b7293 100644 --- a/core/pom.xml +++ b/core/pom.xml @@ -5,7 +5,7 @@ graphhopper-core GraphHopper Core - 4.12-SNAPSHOT + 5.0-SNAPSHOT jar GraphHopper is a fast and memory efficient Java road routing engine @@ -14,7 +14,7 @@ com.github.GIScience.graphhopper graphhopper-parent - 4.12-SNAPSHOT + 5.0-SNAPSHOT @@ -38,7 +38,7 @@ com.github.GIScience.graphhopper graphhopper-web-api - 4.12-SNAPSHOT + 5.0-SNAPSHOT com.carrotsearch diff --git a/core/src/main/java/com/graphhopper/GraphHopper.java b/core/src/main/java/com/graphhopper/GraphHopper.java index 69f9efeabd5..efc74b2a76d 100644 --- a/core/src/main/java/com/graphhopper/GraphHopper.java +++ b/core/src/main/java/com/graphhopper/GraphHopper.java @@ -25,10 +25,7 @@ import com.graphhopper.reader.dem.*; import com.graphhopper.reader.osm.OSMReader; import com.graphhopper.reader.osm.conditional.DateRangeParser; -import com.graphhopper.routing.DefaultWeightingFactory; -import com.graphhopper.routing.Router; -import com.graphhopper.routing.RouterConfig; -import com.graphhopper.routing.WeightingFactory; +import com.graphhopper.routing.*; import com.graphhopper.routing.ch.CHPreparationHandler; import com.graphhopper.routing.ev.*; import com.graphhopper.routing.lm.LMConfig; @@ -70,7 +67,6 @@ import static com.graphhopper.util.GHUtility.readCountries; import static com.graphhopper.util.Helper.*; import static com.graphhopper.util.Parameters.Algorithms.RoundTrip; -import static java.util.Collections.emptyList; /** * Easy to use access point to configure import and (offline) routing. @@ -103,8 +99,7 @@ public class GraphHopper { private LockFactory lockFactory = new NativeFSLockFactory(); private boolean allowWrites = true; private boolean fullyLoaded = false; - private boolean smoothElevation = false; - private double longEdgeSamplingDistance = Double.MAX_VALUE; + private final OSMReaderConfig osmReaderConfig = new OSMReaderConfig(); // for routing private final RouterConfig routerConfig = new RouterConfig(); // for index @@ -120,8 +115,6 @@ public class GraphHopper { // for data reader private String osmFile; - private double dataReaderWayPointMaxDistance = 1; - private int dataReaderWorkerThreads = 2; private ElevationProvider eleProvider = ElevationProvider.NOOP; private FlagEncoderFactory flagEncoderFactory = new DefaultFlagEncoderFactory(); private EncodedValueFactory encodedValueFactory = new DefaultEncodedValueFactory(); @@ -152,7 +145,7 @@ public EncodingManager.Builder getEncodingManagerBuilder() { public EncodingManager getEncodingManager() { if (encodingManager == null) - throw new IllegalStateException("EncodingManager not yet build"); + throw new IllegalStateException("EncodingManager not yet built"); return encodingManager; } @@ -169,29 +162,6 @@ public GraphHopper setElevationProvider(ElevationProvider 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 GraphHopper setPathDetailsBuilderFactory(PathDetailsBuilderFactory pathBuilderFactory) { this.pathBuilderFactory = pathBuilderFactory; return this; @@ -316,22 +286,6 @@ public boolean isFullyLoaded() { } // ORS-GH MOD END - /** - * Sets the distance distance between elevation samples on long edges - */ - public GraphHopper setLongEdgeSamplingDistance(double longEdgeSamplingDistance) { - this.longEdgeSamplingDistance = longEdgeSamplingDistance; - return this; - } - - /** - * Sets the max elevation discrepancy between way points and the simplified polyline in meters - */ - public GraphHopper setElevationWayPointMaxDistance(double elevationWayPointMaxDistance) { - this.routerConfig.setElevationWayPointMaxDistance(elevationWayPointMaxDistance); - return this; - } - public String getGraphHopperLocation() { return ghLocation; } @@ -473,6 +427,7 @@ public CountryRuleFactory getCountryRuleFactory() { * is read from `config.yml`. */ public GraphHopper init(GraphHopperConfig ghConfig) { + ensureNotLoaded(); // disabling_allowed config options were removed for GH 3.0 if (ghConfig.has("routing.ch.disabling_allowed")) throw new IllegalArgumentException("The 'routing.ch.disabling_allowed' configuration option is no longer supported"); @@ -492,15 +447,11 @@ public GraphHopper init(GraphHopperConfig ghConfig) { graphHopperFolder = pruneFileEnd(osmFile) + "-gh"; } + ghLocation = graphHopperFolder; - if (ghConfig.has("country_rules.enabled")) { - boolean countryRulesEnabled = ghConfig.getBool("country_rules.enabled", false); - countryRuleFactory = countryRulesEnabled ? new CountryRuleFactory() : null; - } + countryRuleFactory = ghConfig.getBool("country_rules.enabled", false) ? new CountryRuleFactory() : null; customAreasDirectory = ghConfig.getString("custom_areas.directory", customAreasDirectory); - // graph - setGraphHopperLocation(graphHopperFolder); defaultSegmentSize = ghConfig.getInt("graph.dataaccess.segment_size", defaultSegmentSize); String daTypeString = ghConfig.getString("graph.dataaccess.default_type", ghConfig.getString("graph.dataaccess", "RAM_STORE")); @@ -525,7 +476,6 @@ public GraphHopper init(GraphHopperConfig ghConfig) { if (encodingManager != null) throw new IllegalStateException("Cannot call init twice. EncodingManager was already initialized."); - emBuilder.setEnableInstructions(ghConfig.getBool("datareader.instructions", true)); emBuilder.setPreferredLanguage(ghConfig.getString("datareader.preferred_language", "")); emBuilder.setDateRangeParser(DateRangeParser.createInstance(ghConfig.getString("datareader.date_range_parser_day", ""))); @@ -538,13 +488,14 @@ public GraphHopper init(GraphHopperConfig ghConfig) { lockFactory = new NativeFSLockFactory(); // elevation - this.smoothElevation = ghConfig.getBool("graph.elevation.smoothing", false); - this.longEdgeSamplingDistance = ghConfig.getDouble("graph.elevation.long_edge_sampling_distance", Double.MAX_VALUE); - setElevationWayPointMaxDistance(ghConfig.getDouble("graph.elevation.way_point_max_distance", Double.MAX_VALUE)); + osmReaderConfig.setSmoothElevation(ghConfig.getBool("graph.elevation.smoothing", osmReaderConfig.isSmoothElevation())); + osmReaderConfig.setLongEdgeSamplingDistance(ghConfig.getDouble("graph.elevation.long_edge_sampling_distance", osmReaderConfig.getLongEdgeSamplingDistance())); + osmReaderConfig.setElevationMaxWayPointDistance(ghConfig.getDouble("graph.elevation.way_point_max_distance", osmReaderConfig.getElevationMaxWayPointDistance())); + routerConfig.setElevationWayPointMaxDistance(ghConfig.getDouble("graph.elevation.way_point_max_distance", routerConfig.getElevationWayPointMaxDistance())); ElevationProvider elevationProvider = createElevationProvider(ghConfig); setElevationProvider(elevationProvider); - if (longEdgeSamplingDistance < Double.MAX_VALUE && !elevationProvider.canInterpolate()) + if (osmReaderConfig.getLongEdgeSamplingDistance() < Double.MAX_VALUE && !elevationProvider.canInterpolate()) logger.warn("Long edge sampling enabled, but bilinear interpolation disabled. See #1953"); // optimizable prepare @@ -555,9 +506,8 @@ public GraphHopper init(GraphHopperConfig ghConfig) { lmPreparationHandler.init(ghConfig); // osm import - dataReaderWayPointMaxDistance = ghConfig.getDouble(Routing.INIT_WAY_POINT_MAX_DISTANCE, dataReaderWayPointMaxDistance); - - dataReaderWorkerThreads = ghConfig.getInt("datareader.worker_threads", dataReaderWorkerThreads); + osmReaderConfig.setMaxWayPointDistance(ghConfig.getDouble(Routing.INIT_WAY_POINT_MAX_DISTANCE, osmReaderConfig.getMaxWayPointDistance())); + osmReaderConfig.setWorkerThreads(ghConfig.getInt("datareader.worker_threads", osmReaderConfig.getWorkerThreads())); // index preciseIndexResolution = ghConfig.getInt("index.high_resolution", preciseIndexResolution); @@ -577,10 +527,12 @@ public GraphHopper init(GraphHopperConfig ghConfig) { } private EncodingManager buildEncodingManager(GraphHopperConfig ghConfig) { + if (profilesByName.isEmpty()) + throw new IllegalStateException("no profiles exist but assumed to create EncodingManager. E.g. provide them in GraphHopperConfig when calling GraphHopper.init"); + String flagEncodersStr = ghConfig.getString("graph.flag_encoders", ""); - String encodedValueStr = ghConfig.getString("graph.encoded_values", ""); - Map flagEncoderMap = new LinkedHashMap<>(), implicitFlagEncoderMap = new HashMap<>(); - for (String encoderStr : Arrays.asList(flagEncodersStr.split(","))) { + Map flagEncoderMap = new LinkedHashMap<>(); + for (String encoderStr : flagEncodersStr.split(",")) { String key = encoderStr.split("\\|")[0]; if (!key.isEmpty()) { if (flagEncoderMap.containsKey(key)) @@ -588,8 +540,7 @@ private EncodingManager buildEncodingManager(GraphHopperConfig ghConfig) { flagEncoderMap.put(key, encoderStr); } } - if (profilesByName.isEmpty()) - throw new IllegalStateException("no profiles exist but assumed to create EncodingManager. E.g. provide them in GraphHopperConfig when calling GraphHopper.init"); + Map implicitFlagEncoderMap = new HashMap<>(); for (Profile profile : profilesByName.values()) { emBuilder.add(Subnetwork.create(profile.getName())); if (!flagEncoderMap.containsKey(profile.getVehicle()) @@ -598,7 +549,9 @@ private EncodingManager buildEncodingManager(GraphHopperConfig ghConfig) { implicitFlagEncoderMap.put(profile.getVehicle(), profile.getVehicle() + (profile.isTurnCosts() ? "|turn_costs=true" : "")); } flagEncoderMap.putAll(implicitFlagEncoderMap); - flagEncoderMap.values().stream().forEach(s -> emBuilder.addIfAbsent(flagEncoderFactory, s)); + flagEncoderMap.values().forEach(s -> emBuilder.addIfAbsent(flagEncoderFactory, s)); + + String encodedValueStr = ghConfig.getString("graph.encoded_values", ""); for (String tpStr : encodedValueStr.split(",")) { if (!tpStr.isEmpty()) emBuilder.addIfAbsent(tagParserFactory, tpStr); } @@ -669,9 +622,9 @@ private void printInfo() { * disc which is usually a lot faster. */ public GraphHopper importOrLoad() { - if (!load(ghLocation)) { + if (!load()) { printInfo(); - process(ghLocation, false); + process(false); } else { printInfo(); } @@ -682,9 +635,9 @@ public GraphHopper importOrLoad() { * Imports and processes data, storing it to disk when complete. */ public void importAndClose() { - if (!load(ghLocation)) { + if (!load()) { printInfo(); - process(ghLocation, true); + process(true); } else { printInfo(); logger.info("Graph already imported into " + ghLocation); @@ -695,21 +648,21 @@ public void importAndClose() { /** * Creates the graph from OSM data. */ - private void process(String graphHopperLocation, boolean closeEarly) { - setGraphHopperLocation(graphHopperLocation); + private void process(boolean closeEarly) { GHLock lock = null; try { if (ghStorage == null) throw new IllegalStateException("GraphHopperStorage must be initialized before starting the import"); if (ghStorage.getDirectory().getDefaultType().isStoring()) { - lockFactory.setLockDir(new File(graphHopperLocation)); + lockFactory.setLockDir(new File(ghLocation)); 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()); + throw new RuntimeException("To avoid multiple writers we need to obtain a write lock but it failed. In " + ghLocation, lock.getObtainFailedReason()); } ensureWriteAccess(); importOSM(); cleanUp(); + postImport(); postProcessing(closeEarly); flush(); } finally { @@ -718,6 +671,18 @@ private void process(String graphHopperLocation, boolean closeEarly) { } } + protected void postImport() { + if (sortGraph) { + GraphHopperStorage newGraph = GHUtility.newStorage(ghStorage); + GHUtility.sortDFS(ghStorage, newGraph); + logger.info("graph sorted (" + getMemInfo() + ")"); + ghStorage = newGraph; + } + + if (hasElevation()) + interpolateBridgesTunnelsAndFerries(); + } + protected void importOSM() { if (osmFile == null) throw new IllegalStateException("Couldn't load from existing folder: " + ghLocation @@ -733,14 +698,11 @@ protected void importOSM() { AreaIndex areaIndex = new AreaIndex<>(customAreas); logger.info("start creating graph from " + osmFile); + // ORS-GH MOD START OSMReader reader = createOSMReader().setFile(_getOSMFile()). + // ORS-GH MOD END setAreaIndex(areaIndex). setElevationProvider(eleProvider). - setWorkerThreads(dataReaderWorkerThreads). - setWayPointMaxDistance(dataReaderWayPointMaxDistance). - setWayPointElevationMaxDistance(routerConfig.getElevationWayPointMaxDistance()). - setSmoothElevation(smoothElevation). - setLongEdgeSamplingDistance(longEdgeSamplingDistance). setCountryRuleFactory(countryRuleFactory); logger.info("using " + ghStorage.toString() + ", memory:" + getMemInfo()); try { @@ -756,7 +718,7 @@ protected void importOSM() { // ORS-GH MOD START add method for overriding protected OSMReader createOSMReader() { - return new OSMReader(ghStorage); + return new OSMReader(ghStorage, osmReaderConfig); } // ORS-GH MOD END @@ -788,36 +750,30 @@ protected File _getOSMFile() { } /** - * Opens existing graph folder. - * - * @param graphHopperFolder is the folder containing graphhopper files. Can be a compressed file - * too ala folder-content.ghz. + * Load from existing graph folder. */ - public boolean load(String graphHopperFolder) { - if (isEmpty(graphHopperFolder)) + public boolean load() { + if (isEmpty(ghLocation)) 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); - + File tmpFileOrFolder = new File(ghLocation); 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"); + File compressed = new File(ghLocation + ".ghz"); if (compressed.exists() && !compressed.isDirectory()) { try { - new Unzipper().unzip(compressed.getAbsolutePath(), graphHopperFolder, removeZipped); + new Unzipper().unzip(compressed.getAbsolutePath(), ghLocation, removeZipped); } catch (IOException ex) { throw new RuntimeException("Couldn't extract file " + compressed.getAbsolutePath() - + " to " + graphHopperFolder, ex); + + " to " + ghLocation, ex); } } } - setGraphHopperLocation(graphHopperFolder); - if (!allowWrites && dataAccessDefaultType.isMMap()) dataAccessDefaultType = DAType.MMAP_RO; if (encodingManager == null) { @@ -840,24 +796,17 @@ public boolean load(String graphHopperFolder) { // ORS-GH MOD END checkProfilesConsistency(); - if (lmPreparationHandler.isEnabled()) - initLMPreparationHandler(); - - List chConfigs; + // todo: move this after base graph loading/import, just like for LM. there is no real reason we have to setup CH before if (chPreparationHandler.isEnabled()) { - initCHPreparationHandler(); - chConfigs = chPreparationHandler.getCHConfigs(); - } else { - chConfigs = emptyList(); + List chConfigs = createCHConfigs(chPreparationHandler.getCHProfiles()); + ghStorage.addCHGraphs(chConfigs); } - ghStorage.addCHGraphs(chConfigs); - -// ORS-GH MOD START add preparation hook + // ORS-GH MOD START add preparation hook loadORS(); -// ORS-GH MOD START + // ORS-GH MOD START - if (!new File(graphHopperFolder).exists()) + if (!new File(ghLocation).exists()) return false; GHLock lock = null; @@ -955,32 +904,28 @@ public final CHPreparationHandler getCHPreparationHandler() { return chPreparationHandler; } -// ORS-GH MOD START change access private -> protected - protected void initCHPreparationHandler() { -// ORS-GH MOD END - if (chPreparationHandler.hasCHConfigs()) { - return; - } - - for (CHProfile chProfile : chPreparationHandler.getCHProfiles()) { + // ORS-GH MOD START expose method to ORS + protected List createCHConfigs(List chProfiles) { + // ORS-GH MOD END + List chConfigs = new ArrayList<>(); + for (CHProfile chProfile : chProfiles) { Profile profile = profilesByName.get(chProfile.getProfile()); if (profile.isTurnCosts()) { - chPreparationHandler.addCHConfig(CHConfig.edgeBased(profile.getName(), createWeighting(profile, new PMap()))); + chConfigs.add(CHConfig.edgeBased(profile.getName(), createWeighting(profile, new PMap()))); } else { - chPreparationHandler.addCHConfig(CHConfig.nodeBased(profile.getName(), createWeighting(profile, new PMap()))); + chConfigs.add(CHConfig.nodeBased(profile.getName(), createWeighting(profile, new PMap()))); } } + return chConfigs; } public final LMPreparationHandler getLMPreparationHandler() { return lmPreparationHandler; } - private void initLMPreparationHandler() { - if (lmPreparationHandler.hasLMProfiles()) - return; - - for (LMProfile lmProfile : lmPreparationHandler.getLMProfiles()) { + private List createLMConfigs(List lmProfiles) { + List lmConfigs = new ArrayList<>(); + for (LMProfile lmProfile : lmProfiles) { if (lmProfile.usesOtherPreparation()) continue; Profile profile = profilesByName.get(lmProfile.getProfile()); @@ -990,51 +935,33 @@ private void initLMPreparationHandler() { // Running the preparation without turn costs is also useful to allow e.g. changing the u_turn_costs per // request (we have to use the minimum weight settings (= no turn costs) for the preparation) Weighting weighting = createWeighting(profile, new PMap(), true); - lmPreparationHandler.addLMConfig(new LMConfig(profile.getName(), weighting)); + // ORS-MOD START + adjustLMWeighting(weighting); + // ORS-MOD END + lmConfigs.add(new LMConfig(profile.getName(), weighting)); } + return lmConfigs; } - /** - * Does the preparation and creates the location index - */ - public final void postProcessing() { - postProcessing(false); + // ORS-MOD START + protected void adjustLMWeighting(Weighting weighting) { } + // ORS-MOD END /** - * Does the preparation and creates the location index + * Runs both after the import and when loading an existing Graph * * @param closeEarly release resources as early as possible */ protected void postProcessing(boolean closeEarly) { - // 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 CH is not possible yet. See #12"); - - GraphHopperStorage newGraph = GHUtility.newStorage(ghStorage); - GHUtility.sortDFS(ghStorage, newGraph); - logger.info("graph sorted (" + getMemInfo() + ")"); - ghStorage = newGraph; - } - - if (!hasInterpolated() && hasElevation()) { - interpolateBridgesTunnelsAndFerries(); - } - initLocationIndex(); - // ORS-GH MOD START add post processing hook postProcessingHook(); // ORS-GH MOD END - importPublicTransit(); if (lmPreparationHandler.isEnabled()) - lmPreparationHandler.createPreparations(ghStorage, locationIndex); - loadOrPrepareLM(closeEarly); + loadOrPrepareLM(closeEarly); if (chPreparationHandler.isEnabled()) chPreparationHandler.createPreparations(ghStorage); @@ -1056,12 +983,6 @@ protected void postProcessingHook() {} protected void importPublicTransit() { } - private static final String INTERPOLATION_KEY = "prepare.elevation_interpolation.done"; - - private boolean hasInterpolated() { - return "true".equals(ghStorage.getProperties().get(INTERPOLATION_KEY)); - } - void interpolateBridgesTunnelsAndFerries() { if (ghStorage.getEncodingManager().hasEncodedValue(RoadEnvironment.KEY)) { EnumEncodedValue roadEnvEnc = ghStorage.getEncodingManager().getEnumEncodedValue(RoadEnvironment.KEY, RoadEnvironment.class); @@ -1075,7 +996,6 @@ void interpolateBridgesTunnelsAndFerries() { // See #2098 for mor information sw = new StopWatch().start(); new EdgeElevationInterpolator(ghStorage, roadEnvEnc, RoadEnvironment.FERRY).execute(); - ghStorage.getProperties().put(INTERPOLATION_KEY, true); logger.info("Bridge interpolation " + (int) bridge + "s, " + "tunnel interpolation " + (int) tunnel + "s, ferry interpolation " + (int) sw.stop().getSeconds() + "s"); } } @@ -1201,10 +1121,6 @@ protected void prepareCH(boolean closeEarly) { * For landmarks it is required to always call this method: either it creates the landmark data or it loads it. */ protected void loadOrPrepareLM(boolean closeEarly) { - if (!lmPreparationHandler.isEnabled() || lmPreparationHandler.getPreparations().isEmpty()) { - return; - } - for (LMProfile profile : lmPreparationHandler.getLMProfiles()) { if (!getProfileVersion(profile.getProfile()).isEmpty() && !getProfileVersion(profile.getProfile()).equals("" + profilesByName.get(profile.getProfile()).getVersion())) @@ -1212,7 +1128,8 @@ protected void loadOrPrepareLM(boolean closeEarly) { } ensureWriteAccess(); ghStorage.freeze(); - if (lmPreparationHandler.loadOrDoWork(ghStorage.getProperties(), closeEarly)) { + List lmConfigs = createLMConfigs(lmPreparationHandler.getLMProfiles()); + if (lmPreparationHandler.loadOrDoWork(lmConfigs, ghStorage, locationIndex, closeEarly)) { ghStorage.getProperties().put(Landmark.PREPARE + "done", true); for (LMProfile profile : lmPreparationHandler.getLMProfiles()) { // potentially overwrite existing keys from CH @@ -1300,4 +1217,8 @@ public boolean getFullyLoaded() { public RouterConfig getRouterConfig() { return routerConfig; } -} + + public OSMReaderConfig getReaderConfig() { + return osmReaderConfig; + } +} \ No newline at end of file diff --git a/core/src/main/java/com/graphhopper/coll/GHLongIntBTree.java b/core/src/main/java/com/graphhopper/coll/GHLongIntBTree.java index b6ded2c3d28..e556083e464 100644 --- a/core/src/main/java/com/graphhopper/coll/GHLongIntBTree.java +++ b/core/src/main/java/com/graphhopper/coll/GHLongIntBTree.java @@ -31,13 +31,13 @@ * @author Peter Karich */ public class GHLongIntBTree implements LongIntMap { + private final static Logger logger = LoggerFactory.getLogger(GHLongIntBTree.class); private final int noNumberValue = -1; - private Logger logger = LoggerFactory.getLogger(getClass()); + private final int maxLeafEntries; + private final int initLeafSize; + private final int splitIndex; + private final float factor; private long size; - private int maxLeafEntries; - private int initLeafSize; - private int splitIndex; - private float factor; private int height; private BTreeEntry root; @@ -64,8 +64,7 @@ public GHLongIntBTree(int maxLeafEntries) { clear(); } - // LATER: see OSMIDMap for a version where we use DataAccess - static int binarySearch(long keys[], int start, int len, long key) { + static int binarySearch(long[] keys, int start, int len, long key) { int high = start + len, low = start - 1, guess; while (high - low > 1) { // use >>> for average or we could get an integer overflow. @@ -184,9 +183,9 @@ public ReturnValue(int oldValue) { class BTreeEntry { int entrySize; - long keys[]; - int values[]; - BTreeEntry children[]; + long[] keys; + int[] values; + BTreeEntry[] children; boolean isLeaf; public BTreeEntry(int tmpSize, boolean leaf) { diff --git a/core/src/main/java/com/graphhopper/coll/OSMIDMap.java b/core/src/main/java/com/graphhopper/coll/OSMIDMap.java deleted file mode 100644 index 079bf2c99a5..00000000000 --- a/core/src/main/java/com/graphhopper/coll/OSMIDMap.java +++ /dev/null @@ -1,138 +0,0 @@ -/* - * 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.coll; - -import com.graphhopper.storage.DataAccess; -import com.graphhopper.storage.Directory; -import com.graphhopper.util.BitUtil; -import com.graphhopper.util.Helper; - -/** - * This is a special purpose map for writing increasing OSM IDs with consecutive values. It stores - * a map from long to int in a memory friendly way and but does NOT provide O(1) access. - *

- * - * @author Peter Karich - */ -public class OSMIDMap implements LongIntMap { - private static final BitUtil bitUtil = BitUtil.LITTLE; - private final DataAccess keys; - private final DataAccess values; - private final int noEntryValue; - private final Directory dir; - private long lastKey = Long.MIN_VALUE; - private long size; - - public OSMIDMap(Directory dir) { - this(dir, -1); - } - - public OSMIDMap(Directory dir, int noNumber) { - this.dir = dir; - this.noEntryValue = noNumber; - keys = dir.find("osmid_map_keys"); - keys.create(2000); - values = dir.find("osmid_map_values"); - values.create(1000); - } - - static long binarySearch(DataAccess da, long start, long len, long key) { - long high = start + len, low = start - 1, guess; - byte[] longBytes = new byte[8]; - while (high - low > 1) { - // use >>> for average or we could get an integer overflow. - guess = (high + low) >>> 1; - long tmp = guess << 3; - da.getBytes(tmp, longBytes, 8); - long guessedKey = bitUtil.toLong(longBytes); - if (guessedKey < key) - low = guess; - else - high = guess; - } - - if (high == start + len) - return ~(start + len); - - long tmp = high << 3; - da.getBytes(tmp, longBytes, 8); - long highKey = bitUtil.toLong(longBytes); - if (highKey == key) - return high; - else - return ~high; - } - - public void remove() { - dir.remove(keys); - } - - @Override - public int put(long key, int value) { - if (key <= lastKey) { - long oldValueIndex = binarySearch(keys, 0, getSize(), key); - if (oldValueIndex < 0) { - throw new IllegalStateException("Cannot insert keys lower than " - + "the last key " + key + " < " + lastKey + ". Only updating supported"); - } - oldValueIndex *= 4; - int oldValue = values.getInt(oldValueIndex); - values.setInt(oldValueIndex, value); - return oldValue; - } - - values.ensureCapacity(size + 4); - values.setInt(size, value); - long doubleSize = size * 2; - keys.ensureCapacity(doubleSize + 8); - - // store long => double of the orig size - byte[] longBytes = bitUtil.fromLong(key); - keys.setBytes(doubleSize, longBytes, 8); - lastKey = key; - size += 4; - return -1; - } - - @Override - public int get(long key) { - long retIndex = binarySearch(keys, 0, getSize(), key); - if (retIndex < 0) - return noEntryValue; - - return values.getInt(retIndex * 4); - } - - @Override - public long getSize() { - return size / 4; - } - - public long getCapacity() { - return keys.getCapacity(); - } - - @Override - public int getMemoryUsage() { - return Math.round(getCapacity() / Helper.MB); - } - - @Override - public void optimize() { - } -} diff --git a/core/src/main/java/com/graphhopper/reader/PillarInfo.java b/core/src/main/java/com/graphhopper/reader/PillarInfo.java index 81ec1f8c893..fd2e3eb221d 100644 --- a/core/src/main/java/com/graphhopper/reader/PillarInfo.java +++ b/core/src/main/java/com/graphhopper/reader/PillarInfo.java @@ -38,7 +38,7 @@ public class PillarInfo implements PointAccess { public PillarInfo(boolean enabled3D, Directory dir) { this.enabled3D = enabled3D; this.dir = dir; - this.da = dir.find("tmp_pillar_info").create(100); + this.da = dir.create("tmp_pillar_info").create(100); this.rowSizeInBytes = getDimension() * 4; } diff --git a/core/src/main/java/com/graphhopper/reader/ReaderElement.java b/core/src/main/java/com/graphhopper/reader/ReaderElement.java index 3688ebe925c..d313429dcf0 100644 --- a/core/src/main/java/com/graphhopper/reader/ReaderElement.java +++ b/core/src/main/java/com/graphhopper/reader/ReaderElement.java @@ -37,13 +37,13 @@ public abstract class ReaderElement { private final Map properties; protected ReaderElement(long id, int type) { - this(id, type, 4); + this(id, type, new HashMap<>(4)); } - protected ReaderElement(long id, int type, int propertyMapSize) { + protected ReaderElement(long id, int type, Map properties) { this.id = id; this.type = type; - properties = new HashMap<>(propertyMapSize); + this.properties = properties; } // ORS-GH MOD START @@ -76,11 +76,7 @@ protected String tagsToString() { return tagTxt.toString(); } - // ORS-GH MOD START - change access level - // Used in OSMReader mod to get node tags when processing edge edge - //protected Map getTags() public Map getTags() { - // ORS-GH MOD END return properties; } diff --git a/core/src/main/java/com/graphhopper/reader/ReaderNode.java b/core/src/main/java/com/graphhopper/reader/ReaderNode.java index 46106aff418..f40f520fd42 100644 --- a/core/src/main/java/com/graphhopper/reader/ReaderNode.java +++ b/core/src/main/java/com/graphhopper/reader/ReaderNode.java @@ -17,6 +17,8 @@ */ package com.graphhopper.reader; +import java.util.Map; + /** * Represents a node received from the reader. *

@@ -33,6 +35,12 @@ public ReaderNode(long id, double lat, double lon) { this.lon = lon; } + public ReaderNode(long id, double lat, double lon, Map tags) { + super(id, NODE, tags); + this.lat = lat; + this.lon = lon; + } + public double getLat() { return lat; } @@ -74,7 +82,7 @@ public String toString() { txt.append(getLat()); txt.append(" lon="); txt.append(getLon()); - if (!getTags().isEmpty()) { + if (hasTags()) { txt.append("\n"); txt.append(tagsToString()); } diff --git a/core/src/main/java/com/graphhopper/reader/ReaderRelation.java b/core/src/main/java/com/graphhopper/reader/ReaderRelation.java index e3c41549e31..4011fbb530a 100644 --- a/core/src/main/java/com/graphhopper/reader/ReaderRelation.java +++ b/core/src/main/java/com/graphhopper/reader/ReaderRelation.java @@ -19,6 +19,7 @@ import java.util.ArrayList; import java.util.Collections; +import java.util.HashMap; import java.util.List; /** @@ -31,7 +32,7 @@ public class ReaderRelation extends ReaderElement { protected List members; public ReaderRelation(long id) { - super(id, RELATION, 2); + super(id, RELATION, new HashMap<>(2)); } @Override diff --git a/core/src/main/java/com/graphhopper/reader/dem/AbstractSRTMElevationProvider.java b/core/src/main/java/com/graphhopper/reader/dem/AbstractSRTMElevationProvider.java index d6b599a2d8f..6185ed9669d 100644 --- a/core/src/main/java/com/graphhopper/reader/dem/AbstractSRTMElevationProvider.java +++ b/core/src/main/java/com/graphhopper/reader/dem/AbstractSRTMElevationProvider.java @@ -34,8 +34,6 @@ * @author Robin Boldt */ public abstract class AbstractSRTMElevationProvider extends TileBasedElevationProvider { - - private static final BitUtil BIT_UTIL = BitUtil.BIG; private final int DEFAULT_WIDTH; private final int MIN_LAT; private final int MAX_LAT; @@ -102,7 +100,7 @@ public double getEle(double lat, double lon) { if (fileName == null) return 0; - DataAccess heights = getDirectory().find("dem" + intKey); + DataAccess heights = getDirectory().create("dem" + intKey); boolean loadExisting = false; try { loadExisting = heights.loadExisting(); @@ -146,7 +144,8 @@ private void updateHeightsFromFile(double lat, double lon, DataAccess heights) t byte[] bytes = getByteArrayFromFile(lat, lon); heights.create(bytes.length); for (int bytePos = 0; bytePos < bytes.length; bytePos += 2) { - short val = BIT_UTIL.toShort(bytes, bytePos); + // we need big endianess to read the SRTM files + short val = BitUtil.BIG.toShort(bytes, bytePos); if (val < -1000 || val > 12000) val = Short.MIN_VALUE; diff --git a/core/src/main/java/com/graphhopper/reader/dem/AbstractTiffElevationProvider.java b/core/src/main/java/com/graphhopper/reader/dem/AbstractTiffElevationProvider.java index 0383d2af57b..142a85e3418 100644 --- a/core/src/main/java/com/graphhopper/reader/dem/AbstractTiffElevationProvider.java +++ b/core/src/main/java/com/graphhopper/reader/dem/AbstractTiffElevationProvider.java @@ -117,7 +117,7 @@ public double getEle(double lat, double lon) { demProvider.setInterpolate(interpolate); cacheData.put(name, demProvider); - DataAccess heights = getDirectory().find(name + ".gh"); + DataAccess heights = getDirectory().create(name + ".gh"); demProvider.setHeights(heights); boolean loadExisting = false; try { diff --git a/core/src/main/java/com/graphhopper/reader/osm/OSMInputFile.java b/core/src/main/java/com/graphhopper/reader/osm/OSMInputFile.java index 8d98c4589a4..6e2a4204a39 100644 --- a/core/src/main/java/com/graphhopper/reader/osm/OSMInputFile.java +++ b/core/src/main/java/com/graphhopper/reader/osm/OSMInputFile.java @@ -75,8 +75,8 @@ public OSMInputFile open() throws XMLStreamException { /** * Currently on for pbf format. Default is number of cores. */ - public OSMInputFile setWorkerThreads(int num) { - workerThreads = num; + public OSMInputFile setWorkerThreads(int threads) { + workerThreads = threads; return this; } diff --git a/core/src/main/java/com/graphhopper/reader/osm/OSMReader.java b/core/src/main/java/com/graphhopper/reader/osm/OSMReader.java index 2df70a635a2..2eb3da0ae09 100644 --- a/core/src/main/java/com/graphhopper/reader/osm/OSMReader.java +++ b/core/src/main/java/com/graphhopper/reader/osm/OSMReader.java @@ -17,14 +17,17 @@ */ package com.graphhopper.reader.osm; -import com.carrotsearch.hppc.*; -import com.graphhopper.coll.LongIntMap; +import com.carrotsearch.hppc.IntLongMap; +import com.carrotsearch.hppc.LongArrayList; +import com.carrotsearch.hppc.LongIndexedContainer; +import com.carrotsearch.hppc.LongSet; +import com.carrotsearch.hppc.cursors.LongCursor; import com.graphhopper.coll.*; import com.graphhopper.reader.*; import com.graphhopper.reader.dem.EdgeSampling; import com.graphhopper.reader.dem.ElevationProvider; import com.graphhopper.reader.dem.GraphElevationSmoothing; -import com.graphhopper.routing.ev.BooleanEncodedValue; +import com.graphhopper.routing.OSMReaderConfig; import com.graphhopper.routing.ev.Country; import com.graphhopper.routing.util.AreaIndex; import com.graphhopper.routing.util.CustomArea; @@ -47,6 +50,7 @@ import static com.graphhopper.util.Helper.nf; import static java.util.Collections.emptyList; +import static java.util.Collections.emptyMap; /** * This class parses an OSM xml or pbf file and creates a graph from it. It does so in a two phase @@ -56,9 +60,8 @@ * node occurs once it is a pillar node and if more it is a tower node, otherwise * {@link #osmNodeIdToInternalNodeMap} returns EMPTY. *

- * 1. b) Reads relations from OSM file. In case that the relation is a route relation, it stores - * specific relation attributes required for routing into {@link #osmWayIdToRouteWeightMap} for all the ways - * of the relation. + * 1. b) Reads relations from OSM file. We keep all route relations for each way ID. We also keep the way IDs for + * all restriction relations, so later we know which edge IDs we need to keep to create turn relation entries *

* 2.a) Reads nodes from OSM file and stores lat+lon information either into the intermediate * data structure for the pillar nodes (pillarLats/pillarLons) or, if a tower node, directly into the @@ -74,26 +77,26 @@ * @author Andrzej Oles */ public class OSMReader implements TurnCostParser.ExternalInternalMap { - protected static final int EMPTY_NODE = -1; - // pillar node is >= 3 - protected static final int PILLAR_NODE = 1; - // tower node is <= -3 - protected static final int TOWER_NODE = -2; private static final Logger LOGGER = LoggerFactory.getLogger(OSMReader.class); + private static final int JUNCTION_NODE = -2; + private static final int EMPTY_NODE = -1; + private static final int END_NODE = 0; + private static final int INTERMEDIATE_NODE = 1; + // connection nodes are those where (only) two OSM ways are connected at their ends, so they are still no junctions + private static final int CONNECTION_NODE = 2; + private final GraphHopperStorage ghStorage; + private final OSMReaderConfig config; private final Graph graph; private final NodeAccess nodeAccess; - private final LongIndexedContainer barrierNodeIds = new LongArrayList(); private final DistanceCalc distCalc = DistanceCalcEarth.DIST_EARTH; private final DouglasPeucker simplifyAlgo = new DouglasPeucker(); private CountryRuleFactory countryRuleFactory = null; - private boolean smoothElevation = false; - private double longEdgeSamplingDistance = 0; protected long zeroCounter = 0; protected PillarInfo pillarInfo; private long locations; + private long ignoredBarrierNodes; private final EncodingManager encodingManager; - private int workerThreads = 2; // Choosing the best Map is hard. We need a memory efficient and fast solution for big data sets! // // very slow: new SparseLongLongArray @@ -104,12 +107,13 @@ public class OSMReader implements TurnCostParser.ExternalInternalMap { // smaller memory overhead for bigger data sets because of avoiding a "rehash" // remember how many times a node was used to identify tower nodes private LongIntMap osmNodeIdToInternalNodeMap; - private GHLongLongHashMap osmNodeIdToNodeFlagsMap; - private GHLongLongHashMap osmWayIdToRouteWeightMap; + private GHLongLongHashMap osmWayIdToRelationFlagsMap; + private List> nodeTags = new ArrayList<>(); + private LongIntMap nodeTagIndicesByOsmNodeID = new GHLongIntBTree(200); // stores osm way ids used by relations to identify which edge ids needs to be mapped later private GHLongHashSet osmWayIdSet = new GHLongHashSet(); private IntLongMap edgeIdToOsmWayIdMap; - private boolean doSimplify = true; + private final boolean doSimplify; private int nextTowerId = 0; private int nextPillarId = 0; // negative but increasing to avoid clash with custom created OSM files @@ -120,30 +124,35 @@ public class OSMReader implements TurnCostParser.ExternalInternalMap { private Date osmDataDate; private final IntsRef tempRelFlags; private final TurnCostStorage tcs; - // ORS-GH MOD - Add variable for identifying which tags from nodes should be stored on their containing ways private Set nodeTagsToStore = new HashSet<>(); // ORS-GH MOD - Add variable for storing tags obtained from nodes private GHLongObjectHashMap> osmNodeTagValues; + // ORS-GH MOD - Collect edges created for a given way + List createdEdges; protected void initNodeTagsToStore(HashSet nodeTagsToStore) { nodeTagsToStore.addAll(nodeTagsToStore); } + // ORS-GH MOD END - public OSMReader(GraphHopperStorage ghStorage) { + public OSMReader(GraphHopperStorage ghStorage, OSMReaderConfig config) { this.ghStorage = ghStorage; + this.config = config; this.graph = ghStorage; this.nodeAccess = graph.getNodeAccess(); this.encodingManager = ghStorage.getEncodingManager(); + doSimplify = config.getMaxWayPointDistance() > 0; + simplifyAlgo.setMaxDistance(config.getMaxWayPointDistance()); + simplifyAlgo.setElevationMaxDistance(config.getElevationMaxWayPointDistance()); + osmNodeIdToInternalNodeMap = new GHLongIntBTree(200); - osmNodeIdToNodeFlagsMap = new GHLongLongHashMap(200, .5f); - osmWayIdToRouteWeightMap = new GHLongLongHashMap(200, .5f); + osmWayIdToRelationFlagsMap = new GHLongLongHashMap(200, .5f); pillarInfo = new PillarInfo(nodeAccess.is3D(), ghStorage.getDirectory()); tempRelFlags = encodingManager.createRelationFlags(); if (tempRelFlags.length != 2) throw new IllegalArgumentException("Cannot use relation flags with != 2 integers"); - tcs = graph.getTurnCostStorage(); // ORS-GH MOD START init @@ -203,12 +212,17 @@ void preProcess(File osmFile) { while ((item = in.getNext()) != null) { if (item.isType(ReaderElement.WAY)) { final ReaderWay way = (ReaderWay) item; - boolean valid = filterWay(way); - if (valid) { + if (filterWay(way)) { LongIndexedContainer wayNodes = way.getNodes(); int s = wayNodes.size(); for (int index = 0; index < s; index++) { - prepareHighwayNode(wayNodes.get(index)); + final boolean isEnd = index == 0 || index == s - 1; + final long osmId = wayNodes.get(index); + int curr = getNodeMap().get(osmId); + if (curr == EMPTY_NODE) + getNodeMap().put(osmId, isEnd ? END_NODE : INTERMEDIATE_NODE); + else + getNodeMap().put(osmId, curr == END_NODE && isEnd ? CONNECTION_NODE : JUNCTION_NODE); } if (++tmpWayCounter % 10_000_000 == 0) { @@ -291,15 +305,11 @@ private void writeOsmToGraph(File osmFile) { long relationStart = -1; long counter = 1; try (OSMInput in = openOsmInputFile(osmFile)) { - LongIntMap nodeFilter = getNodeMap(); - ReaderElement item; while ((item = in.getNext()) != null) { switch (item.getType()) { case ReaderElement.NODE: - if (nodeFilter.get(item.getId()) != EMPTY_NODE) { - processNode((ReaderNode) item); - } + processNode((ReaderNode) item); break; case ReaderElement.WAY: @@ -340,7 +350,7 @@ private void writeOsmToGraph(File osmFile) { } protected OSMInput openOsmInputFile(File osmFile) throws XMLStreamException, IOException { - return new OSMInputFile(osmFile).setWorkerThreads(workerThreads).open(); + return new OSMInputFile(osmFile).setWorkerThreads(config.getWorkerThreads()).open(); } /** @@ -354,14 +364,154 @@ protected void processWay(ReaderWay way) { if (!way.hasTags()) return; - long wayOsmId = way.getId(); - EncodingManager.AcceptWay acceptWay = new EncodingManager.AcceptWay(); if (!encodingManager.acceptWay(way, acceptWay)) return; + setArtificialWayTags(way); + IntsRef relationFlags = getRelFlagsMap(way.getId()); + IntsRef edgeFlags = encodingManager.handleWayTags(way, acceptWay, relationFlags); + if (edgeFlags.isEmpty()) + return; + // ORS-GH MOD START + createdEdges = new ArrayList<>(); + // ORS-GH MOD END + splitWayAtJunctionsAndEmptySections(way, edgeFlags); + // ORS-GH MOD START + applyNodeTagsToWay(way); + + onProcessWay(way); + + for (EdgeIteratorState edge : createdEdges) { + onProcessEdge(way, edge); + } + + storeConditionalAccess(acceptWay, createdEdges); + storeConditionalSpeed(edgeFlags, createdEdges); + // ORS-GH MOD END + } + + private void splitWayAtJunctionsAndEmptySections(ReaderWay way, IntsRef edgeFlags) { + List fullSegment = new ArrayList<>(); + for (LongCursor node : way.getNodes()) + fullSegment.add(new SegmentNode(node.value, getNodeMap().get(node.value))); + + List segment = new ArrayList<>(); + for (SegmentNode node : fullSegment) { + if (!isNodeId(node.id)) { + // this node exists in ways, but not in nodes. we ignore it, but we split the way when we encounter + // such a missing node. for example an OSM way might lead out of an area where nodes are available and + // back into it. we do not want to connect the exit/entry points using a straight line. this usually + // should only happen for OSM extracts + if (segment.size() > 1) { + splitLoopSegments(segment, way, edgeFlags); + segment = new ArrayList<>(); + } + } else if (isTowerNode(node.id)) { + if (!segment.isEmpty()) { + segment.add(node); + splitLoopSegments(segment, way, edgeFlags); + segment = new ArrayList<>(); + } + segment.add(node); + } else { + segment.add(node); + } + } + // the last segment might end at the end of the way + if (segment.size() > 1) + splitLoopSegments(segment, way, edgeFlags); + } + + private void splitLoopSegments(List segment, ReaderWay way, IntsRef edgeFlags) { + if (segment.size() < 2) + throw new IllegalStateException("Segment size must be >= 2, but was: " + segment.size()); + + boolean isLoop = segment.get(0).osmNodeId == segment.get(segment.size() - 1).osmNodeId; + if (segment.size() == 2 && isLoop) { + LOGGER.warn("Loop in OSM way: {}, will be ignored, duplicate node: {}", way.getId(), segment.get(0).osmNodeId); + } else if (isLoop) { + // split into two segments + splitSegmentAtSplitNodes(segment.subList(0, segment.size() - 1), way, edgeFlags); + splitSegmentAtSplitNodes(segment.subList(segment.size() - 2, segment.size()), way, edgeFlags); + } else { + splitSegmentAtSplitNodes(segment, way, edgeFlags); + } + } + + private void splitSegmentAtSplitNodes(List parentSegment, ReaderWay way, IntsRef edgeFlags) { + List segment = new ArrayList<>(); + for (int i = 0; i < parentSegment.size(); i++) { + SegmentNode node = parentSegment.get(i); + int nodeTagIndex = nodeTagIndicesByOsmNodeID.get(node.osmNodeId); + if (nodeTagIndex >= 0) { + // this node is a barrier. we will add an extra edge to be able to block access + SegmentNode barrierFrom = node; + SegmentNode barrierTo = addBarrierNode(barrierFrom); + if (i == parentSegment.size() - 1) { + // make sure the barrier node is always on the inside of the segment + SegmentNode tmp = barrierFrom; + barrierFrom = barrierTo; + barrierTo = tmp; + } + if (!segment.isEmpty()) { + segment.add(barrierFrom); + handleSegment(segment, way, edgeFlags, emptyMap()); + segment = new ArrayList<>(); + } + segment.add(barrierFrom); + segment.add(barrierTo); + Map nodeTags = this.nodeTags.get(nodeTagIndex); + handleSegment(segment, way, edgeFlags, nodeTags); + segment = new ArrayList<>(); + segment.add(barrierTo); + + // ignore this barrier node from now. for example a barrier can be connecting two ways (appear in both + // ways) and we only want to add a barrier edge once (but we want to add one). + nodeTagIndicesByOsmNodeID.put(node.osmNodeId, -1); + this.nodeTags.set(nodeTagIndex, emptyMap()); + } else { + segment.add(node); + } + } + if (segment.size() > 1) + handleSegment(segment, way, edgeFlags, emptyMap()); + } + + void handleSegment(List segment, ReaderWay way, IntsRef edgeFlags, Map nodeTags) { + final PointList pointList = new PointList(segment.size(), nodeAccess.is3D()); + int from = -1; + int to = -1; + for (int i = 0; i < segment.size(); i++) { + SegmentNode node = segment.get(i); + int id = node.id; + if (!isNodeId(id)) + throw new IllegalStateException("Invalid id for node: " + node.osmNodeId + " when handling segment " + segment + " for way: " + way.getId()); + if (isPillarNode(id)) { + // PILLAR node, but convert to towerNode if end-standing + boolean convertToTowerNode = i == 0 || i == segment.size() - 1; + id = node.id = handlePillarNode(id, node.osmNodeId, pointList, convertToTowerNode); + } + + if (isTowerNode(id)) { + id = -id - 3; + if (i == 0) + from = id; + else if (i == segment.size() - 1) + to = id; + else + throw new IllegalStateException("Tower nodes should only appear at the end of segments, way: " + way.getId()); + pointList.add(nodeAccess, id); + } + } + if (from < 0 || to < 0) + throw new IllegalStateException("The first and last nodes of a segment must be tower nodes, way: " + way.getId()); + addEdge(from, to, pointList, edgeFlags, way, nodeTags); + } + + private void setArtificialWayTags(ReaderWay way) { // TODO move this after we have created the edge and know the coordinates => encodingManager.applyWayTags LongArrayList osmNodeIds = way.getNodes(); // Estimate length of ways containing a route tag e.g. for ferry speed calculation @@ -372,10 +522,9 @@ protected void processWay(ReaderWay way) { GHPoint estimatedCenter = null; if (!Double.isNaN(firstLat) && !Double.isNaN(firstLon) && !Double.isNaN(lastLat) && !Double.isNaN(lastLon)) { double estimatedDist = distCalc.calcDist(firstLat, firstLon, lastLat, lastLon); - // Add artificial tag for the estimated distance and center + // Add artificial tag for the estimated distance way.setTag("estimated_distance", estimatedDist); estimatedCenter = new GHPoint((firstLat + lastLat) / 2, (firstLon + lastLon) / 2); - way.setTag("estimated_center", estimatedCenter); } // ORS-GH MOD START - Store the actual length of the way (e.g. used for better ferry duration calculations) @@ -395,7 +544,7 @@ protected void processWay(ReaderWay way) { List customAreas = estimatedCenter == null || areaIndex == null ? emptyList() : areaIndex.query(estimatedCenter.lat, estimatedCenter.lon); - // special handling for countries: since they are built-in with GraphHopper they are always fed to the encodingmanager + // special handling for countries: since they are built-in with GraphHopper they are always fed to the EncodingManager Country country = Country.MISSING; for (CustomArea customArea : customAreas) { Object countryCode = customArea.getProperties().get("ISO3166-1:alpha3"); @@ -415,86 +564,6 @@ protected void processWay(ReaderWay way) { // also add all custom areas as artificial tag way.setTag("custom_areas", customAreas); - IntsRef edgeFlags = encodingManager.handleWayTags(way, acceptWay, relationFlags); - if (edgeFlags.isEmpty()) - return; - - List createdEdges = new ArrayList<>(); - // look for barriers along the way - final int size = osmNodeIds.size(); - int lastBarrier = -1; - for (int i = 0; i < size; i++) { - long nodeId = osmNodeIds.get(i); - long nodeFlags = getNodeFlagsMap().get(nodeId); - // barrier was spotted and the way is passable for that mode of travel - if (nodeFlags > 0) { - if (isOnePassable(encodingManager.getAccessEncFromNodeFlags(nodeFlags), edgeFlags)) { - // remove barrier to avoid duplicates - getNodeFlagsMap().put(nodeId, 0); - - // create shadow node copy for zero length edge - long newNodeId = addBarrierNode(nodeId); - if (i > 0) { - // start at beginning of array if there was no previous barrier - if (lastBarrier < 0) - lastBarrier = 0; - - // add way up to barrier shadow node - int length = i - lastBarrier + 1; - LongArrayList partNodeIds = new LongArrayList(); - partNodeIds.add(osmNodeIds.buffer, lastBarrier, length); - partNodeIds.set(length - 1, newNodeId); - createdEdges.addAll(addOSMWay(partNodeIds, edgeFlags, wayOsmId)); - - // create zero length edge for barrier - createdEdges.addAll(addBarrierEdge(newNodeId, nodeId, edgeFlags, nodeFlags, wayOsmId)); - } else { - // run edge from real first node to shadow node - createdEdges.addAll(addBarrierEdge(nodeId, newNodeId, edgeFlags, nodeFlags, wayOsmId)); - - // exchange first node for created barrier node - osmNodeIds.set(0, newNodeId); - } - // remember barrier for processing the way behind it - lastBarrier = i; - } - } - } - - // just add remainder of way to graph if barrier was not the last node - if (lastBarrier >= 0) { - if (lastBarrier < size - 1) { - LongArrayList partNodeIds = new LongArrayList(); - partNodeIds.add(osmNodeIds.buffer, lastBarrier, size - lastBarrier); - createdEdges.addAll(addOSMWay(partNodeIds, edgeFlags, wayOsmId)); - } - } else { - // ORS-GH MOD START - code injection point - if (!onCreateEdges(way, osmNodeIds, edgeFlags, createdEdges)) { - // ORS-GH MOD END - // no barriers - simply add the whole way - createdEdges.addAll(addOSMWay(way.getNodes(), edgeFlags, wayOsmId)); - } - } - - for (EdgeIteratorState edge : createdEdges) { - encodingManager.applyWayTags(way, edge); - } - - // ORS-GH MOD START - code injection point - applyNodeTagsToWay(way); - // ORS-GH MOD END - // ORS-GH MOD START - code injection point - onProcessWay(way); - // ORS-GH MOD END - // ORS-GH MOD START - apply individual processing to each edge - for (EdgeIteratorState edge : createdEdges) { - onProcessEdge(way, edge); - } - // store conditionals - storeConditionalAccess(acceptWay, createdEdges); - storeConditionalSpeed(edgeFlags, createdEdges); - // ORS-GH MOD END } // ORS-GH MOD START - additional methods @@ -526,34 +595,14 @@ protected void storeConditionalSpeed(IntsRef edgeFlags, List } // ORS-GH MOD END - // ORS-GH MOD START - code injection method - protected void recordExactWayDistance(ReaderWay way, LongArrayList osmNodeIds) { - // Code here has to be in the main block as the point for the centre is required by following code statements - } - // ORS-GH MOD END - - // ORS-GH MOD START - code injection method - protected void onProcessWay(ReaderWay way){ - - } - // ORS-MOD END + // ORS-GH MOD START - code injection methods + protected void recordExactWayDistance(ReaderWay way, LongArrayList osmNodeIds) {} - // ORS-GH MOD START - code injection method - protected void applyNodeTagsToWay(ReaderWay way) { - - } - // ORS-GH MOD END - - // ORS-GH MOD START - code injection method - protected void onProcessEdge(ReaderWay way, EdgeIteratorState edge) { + protected void onProcessWay(ReaderWay way) {} - } - // ORS-GH MOD END + protected void applyNodeTagsToWay(ReaderWay way) {} - // ORS-GH MOD START - code injection method - protected boolean onCreateEdges(ReaderWay way, LongArrayList osmNodeIds, IntsRef wayFlags, List createdEdges) { - return false; - } + protected void onProcessEdge(ReaderWay way, EdgeIteratorState edge) {} // ORS-GH MOD END protected void processRelation(ReaderRelation relation) { @@ -565,7 +614,7 @@ void storeTurnRelation(List turnRelations) { for (OSMTurnRelation turnRelation : turnRelations) { int viaNode = getInternalNodeIdOfOsmNode(turnRelation.getViaOsmNodeId()); // street with restriction was not included (access or tag limits etc) - if (viaNode != EMPTY_NODE) + if (viaNode >= 0) encodingManager.handleTurnRelationTags(turnRelation, this, graph); } } @@ -582,10 +631,10 @@ public long getOsmIdOfInternalEdge(int edgeId) { @Override public int getInternalNodeIdOfOsmNode(long nodeOsmId) { int id = getNodeMap().get(nodeOsmId); - if (id < TOWER_NODE) + if (isTowerNode(id)) return -id - 3; - return EMPTY_NODE; + return -1; } // TODO remove this ugly stuff via better preprocessing phase! E.g. putting every tags etc into a helper file! @@ -593,11 +642,10 @@ public int getInternalNodeIdOfOsmNode(long nodeOsmId) { public double getTmpLatitude(int id) { if (id == EMPTY_NODE) return Double.NaN; - if (id < TOWER_NODE) { - // tower node + if (isTowerNode(id)) { id = -id - 3; return nodeAccess.getLat(id); - } else if (id > -TOWER_NODE) { + } else if (isPillarNode(id)) { // pillar node id = id - 3; return pillarInfo.getLat(id); @@ -610,12 +658,10 @@ public double getTmpLatitude(int id) { public double getTmpLongitude(int id) { if (id == EMPTY_NODE) return Double.NaN; - if (id < TOWER_NODE) { - // tower node + if (isTowerNode(id)) { id = -id - 3; return nodeAccess.getLon(id); - } else if (id > -TOWER_NODE) { - // pillar node + } else if (isPillarNode(id)) { id = id - 3; return pillarInfo.getLon(id); } else @@ -627,13 +673,34 @@ protected void processNode(ReaderNode node) { // ORS-GH MOD START - code injection point node = onProcessNode(node); // ORS-GH MOD END - addNode(node); - - // analyze node tags for barriers - if (node.hasTags()) { - long nodeFlags = encodingManager.handleNodeTags(node); - if (nodeFlags != 0) - getNodeFlagsMap().put(node.getId(), nodeFlags); + int nodeType = getNodeMap().get(node.getId()); + if (nodeType == EMPTY_NODE) + return; + else if (nodeType == JUNCTION_NODE || nodeType == CONNECTION_NODE) + addTowerNode(node.getId(), node.getLat(), node.getLon(), eleProvider.getEle(node)); + else if (nodeType == INTERMEDIATE_NODE || nodeType == END_NODE) { + addPillarNode(node.getId(), node.getLat(), node.getLon(), eleProvider.getEle(node)); + // ORS-GH MOD START - Store tags from the node so that they can be accessed later + storeNodeTags(node); + // ORS-GH MOD END + } + else + throw new IllegalStateException("Unknown node type: " + nodeType + ", duplicate OSM node ID: " + node.getId()); + + // we keep node tags for barrier nodes + if (node.getTags().containsKey("barrier")) { + if (nodeType == JUNCTION_NODE) { + LOGGER.debug("OSM node {} at {},{} is a barrier node at a junction, the barrier will be ignored", node.getId(), Helper.round(node.getLat(), 7), Helper.round(node.getLon(), 7)); + ignoredBarrierNodes++; + } else { + int tagIndex = nodeTagIndicesByOsmNodeID.get(node.getId()); + if (tagIndex == -1) { + nodeTagIndicesByOsmNodeID.put(node.getId(), nodeTags.size()); + nodeTags.add(node.getTags()); + } else { + throw new IllegalStateException("Duplicate node OSM ID: " + node.getId()); + } + } } locations++; @@ -652,79 +719,39 @@ protected ReaderNode onProcessNode(ReaderNode node) { } // ORS-GH MOD END - boolean addNode(ReaderNode node) { - int nodeType = getNodeMap().get(node.getId()); - if (nodeType == EMPTY_NODE) - return false; - - double lat = node.getLat(); - double lon = node.getLon(); - double ele = this.getElevation(node); - if (nodeType == TOWER_NODE) { - addTowerNode(node.getId(), lat, lon, ele); - } else if (nodeType == PILLAR_NODE) { - pillarInfo.setNode(nextPillarId, lat, lon, ele); - // ORS-GH MOD START - Store tags from the node so that they can be accessed later - Iterator> it = node.getTags().entrySet().iterator(); - Map temp = new HashMap<>(); - while (it.hasNext()) { - Map.Entry pairs = it.next(); - String key = pairs.getKey(); - if(!nodeTagsToStore.contains(key)) { - continue; - } - temp.put(key, pairs.getValue()); - } - if(!temp.isEmpty()){ - osmNodeTagValues.put(node.getId(), temp); + // ORS-GH MOD START - Store tags from the node so that they can be accessed later + private void storeNodeTags(ReaderNode node) { + Iterator> it = node.getTags().entrySet().iterator(); + Map temp = new HashMap<>(); + while (it.hasNext()) { + Map.Entry pairs = it.next(); + String key = pairs.getKey(); + if(!nodeTagsToStore.contains(key)) { + continue; } - // ORS-GH MOD END - getNodeMap().put(node.getId(), nextPillarId + 3); - nextPillarId++; + temp.put(key, pairs.getValue()); + } + if(!temp.isEmpty()){ + osmNodeTagValues.put(node.getId(), temp); } - return true; - } - - protected double getElevation(ReaderNode node) { - return this.eleProvider.getEle(node); } + // ORS-GH MOD END - /** - * The nodeFlags store the encoders to check for accessibility in edgeFlags. E.g. if nodeFlags==3, then the - * accessibility of the first two encoders will be check in edgeFlags - */ - private static boolean isOnePassable(List checkEncoders, IntsRef edgeFlags) { - for (BooleanEncodedValue accessEnc : checkEncoders) { - if (accessEnc.getBool(false, edgeFlags) || accessEnc.getBool(true, edgeFlags)) - return true; - } - return false; + private int addPillarNode(long osmId, double lat, double lon, double ele) { + pillarInfo.setNode(nextPillarId, lat, lon, ele); + int id = nextPillarId + 3; + getNodeMap().put(osmId, id); + nextPillarId++; + return id; } - void prepareWaysWithRelationInfo(ReaderRelation osmRelation) { - for (ReaderRelation.Member member : osmRelation.getMembers()) { + void prepareWaysWithRelationInfo(ReaderRelation relation) { + for (ReaderRelation.Member member : relation.getMembers()) { if (member.getType() != ReaderRelation.Member.WAY) continue; - - long osmId = member.getRef(); - IntsRef oldRelationFlags = getRelFlagsMap(osmId); - - // Check if our new relation data is better compared to the last one - IntsRef newRelationFlags = encodingManager.handleRelationTags(osmRelation, oldRelationFlags); - putRelFlagsMap(osmId, newRelationFlags); - } - } - - void prepareHighwayNode(long osmId) { - int tmpGHNodeId = getNodeMap().get(osmId); - if (tmpGHNodeId == EMPTY_NODE) { - // this is the first time we see this osmId - getNodeMap().put(osmId, PILLAR_NODE); - } else if (tmpGHNodeId > EMPTY_NODE) { - // mark node as tower node as it now occurred for at least the second time - getNodeMap().put(osmId, TOWER_NODE); - } else { - // tmpIndex is already negative (already tower node) + IntsRef oldRelationFlags = getRelFlagsMap(member.getRef()); + IntsRef newRelationFlags = encodingManager.handleRelationTags(relation, oldRelationFlags); + putRelFlagsMap(member.getRef(), newRelationFlags); } } @@ -740,120 +767,7 @@ int addTowerNode(long osmId, double lat, double lon, double ele) { return id; } - /** - * This method creates from an OSM way (via the osm ids) one or more edges in the graph. - */ - public Collection addOSMWay(final LongIndexedContainer osmNodeIds, final IntsRef flags, final long wayOsmId) { - final PointList pointList = new PointList(osmNodeIds.size(), nodeAccess.is3D()); - final List newEdges = new ArrayList<>(5); - int firstNode = -1; - int lastInBoundsPillarNode = -1; - try { - // #2221: ways might include nodes at the beginning or end that do not exist -> skip them - int firstExisting = -1; - int lastExisting = -1; - for (int i = 0; i < osmNodeIds.size(); ++i) { - final long tmpNode = getNodeMap().get(osmNodeIds.get(i)); - if (tmpNode > -TOWER_NODE || tmpNode < TOWER_NODE) { - firstExisting = i; - break; - } - } - for (int i = osmNodeIds.size() - 1; i >= 0; --i) { - final long tmpNode = getNodeMap().get(osmNodeIds.get(i)); - if (tmpNode > -TOWER_NODE || tmpNode < TOWER_NODE) { - lastExisting = i; - break; - } - } - if (firstExisting < 0) { - assert lastExisting < 0; - return newEdges; - } - for (int i = firstExisting; i <= lastExisting; i++) { - final long osmNodeId = osmNodeIds.get(i); - int tmpNode = getNodeMap().get(osmNodeId); - if (tmpNode == EMPTY_NODE) - continue; - - // skip osmIds with no associated pillar or tower id (e.g. !OSMReader.isBounds) - if (tmpNode == TOWER_NODE) - continue; - - if (tmpNode == PILLAR_NODE) { - // In some cases no node information is saved for the specified osmId. - // ie. a way references a which does not exist in the current file. - // => if the node before was a pillar node then convert into to tower node (as it is also end-standing). - if (!pointList.isEmpty() && lastInBoundsPillarNode > -TOWER_NODE) { - // transform the pillar node to a tower node - tmpNode = lastInBoundsPillarNode; - tmpNode = handlePillarNode(tmpNode, osmNodeId, null, true); - tmpNode = -tmpNode - 3; - if (pointList.size() > 1 && firstNode >= 0) { - // TOWER node - newEdges.add(addEdge(firstNode, tmpNode, pointList, flags, wayOsmId)); - pointList.clear(); - pointList.add(nodeAccess, tmpNode); - } - firstNode = tmpNode; - lastInBoundsPillarNode = -1; - } - continue; - } - - if (tmpNode <= -TOWER_NODE && tmpNode >= TOWER_NODE) - throw new AssertionError("Mapped index not in correct bounds " + tmpNode + ", " + osmNodeId); - - if (tmpNode > -TOWER_NODE) { - boolean convertToTowerNode = i == firstExisting || i == lastExisting; - if (!convertToTowerNode) { - lastInBoundsPillarNode = tmpNode; - } - - // PILLAR node, but convert to towerNode if end-standing - tmpNode = handlePillarNode(tmpNode, osmNodeId, pointList, convertToTowerNode); - } - - if (tmpNode < TOWER_NODE) { - // TOWER node - tmpNode = -tmpNode - 3; - - if (firstNode >= 0 && firstNode == tmpNode) { - // loop detected. See #1525 and #1533. Insert last OSM ID as tower node. Do this for all loops so that users can manipulate loops later arbitrarily. - long lastOsmNodeId = osmNodeIds.get(i - 1); - int lastGHNodeId = getNodeMap().get(lastOsmNodeId); - if (lastGHNodeId < TOWER_NODE) { - LOGGER.warn("Pillar node " + lastOsmNodeId + " is already a tower node and used in loop, see #1533. " + - "Fix mapping for way " + wayOsmId + ", nodes:" + osmNodeIds); - break; - } - - int newEndNode = -handlePillarNode(lastGHNodeId, lastOsmNodeId, pointList, true) - 3; - newEdges.add(addEdge(firstNode, newEndNode, pointList, flags, wayOsmId)); - pointList.clear(); - lastInBoundsPillarNode = -1; - pointList.add(nodeAccess, newEndNode); - firstNode = newEndNode; - } - - pointList.add(nodeAccess, tmpNode); - if (firstNode >= 0) { - newEdges.add(addEdge(firstNode, tmpNode, pointList, flags, wayOsmId)); - pointList.clear(); - lastInBoundsPillarNode = -1; - pointList.add(nodeAccess, tmpNode); - } - firstNode = tmpNode; - } - } - } catch (RuntimeException ex) { - LOGGER.error("Couldn't properly add edge with osm ids:" + osmNodeIds, ex); - throw ex; - } - return newEdges; - } - - EdgeIteratorState addEdge(int fromIndex, int toIndex, PointList pointList, IntsRef flags, long wayOsmId) { + void addEdge(int fromIndex, int toIndex, PointList pointList, IntsRef flags, ReaderWay way, Map nodeTags) { // sanity checks if (fromIndex < 0 || toIndex < 0) throw new AssertionError("to or from index is invalid for this edge " + fromIndex + "->" + toIndex + ", points:" + pointList); @@ -861,12 +775,12 @@ EdgeIteratorState addEdge(int fromIndex, int toIndex, PointList pointList, IntsR throw new AssertionError("Dimension does not match for pointList vs. nodeAccess " + pointList.getDimension() + " <-> " + nodeAccess.getDimension()); // Smooth the elevation before calculating the distance because the distance will be incorrect if calculated afterwards - if (this.smoothElevation) - pointList = GraphElevationSmoothing.smoothElevation(pointList); + if (config.isSmoothElevation()) + GraphElevationSmoothing.smoothElevation(pointList); // sample points along long edges - if (this.longEdgeSamplingDistance < Double.MAX_VALUE && pointList.is3D()) - pointList = EdgeSampling.sample(pointList, longEdgeSamplingDistance, distCalc, eleProvider); + if (config.getLongEdgeSamplingDistance() < Double.MAX_VALUE && pointList.is3D()) + pointList = EdgeSampling.sample(pointList, config.getLongEdgeSamplingDistance(), distCalc, eleProvider); if (doSimplify && pointList.size() > 2) simplifyAlgo.simplify(pointList); @@ -874,7 +788,7 @@ EdgeIteratorState addEdge(int fromIndex, int toIndex, PointList pointList, IntsR double towerNodeDistance = distCalc.calcDistance(pointList); if (towerNodeDistance < 0.001) { - // As investigation shows often two paths should have crossed via one identical point + // As investigation shows often two paths should have crossed via one identical point // but end up in two very close points. zeroCounter++; towerNodeDistance = 0.001; @@ -882,17 +796,21 @@ EdgeIteratorState addEdge(int fromIndex, int toIndex, PointList pointList, IntsR double maxDistance = (Integer.MAX_VALUE - 1) / 1000d; if (Double.isNaN(towerNodeDistance)) { - LOGGER.warn("Bug in OSM or GraphHopper. Illegal tower node distance " + towerNodeDistance + " reset to 1m, osm way " + wayOsmId); + LOGGER.warn("Bug in OSM or GraphHopper. Illegal tower node distance " + towerNodeDistance + " reset to 1m, osm way " + way.getId()); towerNodeDistance = 1; } if (Double.isInfinite(towerNodeDistance) || towerNodeDistance > maxDistance) { - // Too large is very rare and often the wrong tagging. See #435 + // Too large is very rare and often the wrong tagging. See #435 // so we can avoid the complexity of splitting the way for now (new towernodes would be required, splitting up geometry etc) - LOGGER.warn("Bug in OSM or GraphHopper. Too big tower node distance " + towerNodeDistance + " reset to large value, osm way " + wayOsmId); + LOGGER.warn("Bug in OSM or GraphHopper. Too big tower node distance " + towerNodeDistance + " reset to large value, osm way " + way.getId()); towerNodeDistance = maxDistance; } + // update edge flags to potentially block access in case there are node tags + if (!nodeTags.isEmpty()) + flags = encodingManager.handleNodeTags(nodeTags, IntsRef.deepCopyOf(flags)); + EdgeIteratorState iter = graph.edge(fromIndex, toIndex).setDistance(towerNodeDistance).setFlags(flags); // If the entire way is just the first and last point, do not waste space storing an empty way geometry @@ -903,10 +821,12 @@ EdgeIteratorState addEdge(int fromIndex, int toIndex, PointList pointList, IntsR checkCoordinates(toIndex, pointList.get(pointList.size() - 1)); iter.setWayGeometry(pointList.shallowCopy(1, pointList.size() - 1, false)); } + encodingManager.applyWayTags(way, iter); checkDistance(iter); - storeOsmWayID(iter.getEdge(), wayOsmId); - return iter; + storeOsmWayID(iter.getEdge(), way.getId()); + + createdEdges.add(iter); } private void checkCoordinates(int nodeIndex, GHPoint point) { @@ -952,6 +872,7 @@ private int handlePillarNode(int tmpNode, long osmId, PointList pointList, boole // ORS-GH MOD END throw new RuntimeException("Conversion pillarNode to towerNode already happened!? " + "osmId:" + osmId + " pillarIndex:" + tmpNode); + if (convertToTowerNode) { // convert pillarNode type to towerNode, make pillar values invalid pillarInfo.setNode(tmpNode, Double.MAX_VALUE, Double.MAX_VALUE, Double.MAX_VALUE); @@ -960,6 +881,7 @@ private int handlePillarNode(int tmpNode, long osmId, PointList pointList, boole pointList.add(lat, lon, ele); else pointList.add(lat, lon); + return tmpNode; } @@ -974,8 +896,9 @@ protected void finishedReading() { //eleProvider.release(); // ORS-GH MOD END osmNodeIdToInternalNodeMap = null; - osmNodeIdToNodeFlagsMap = null; - osmWayIdToRouteWeightMap = null; + nodeTags = null; + nodeTagIndicesByOsmNodeID = null; + osmWayIdToRelationFlagsMap = null; osmWayIdSet = null; edgeIdToOsmWayIdMap = null; } @@ -983,44 +906,31 @@ protected void finishedReading() { /** * Create a copy of the barrier node */ - long addBarrierNode(long nodeId) { + SegmentNode addBarrierNode(SegmentNode node) { ReaderNode newNode; - int graphIndex = getNodeMap().get(nodeId); - if (graphIndex < TOWER_NODE) { - graphIndex = -graphIndex - 3; - newNode = new ReaderNode(createNewNodeId(), nodeAccess.getLat(graphIndex), nodeAccess.getLon(graphIndex)); + int id = node.id; + if (isTowerNode(id)) { + id = -id - 3; + newNode = new ReaderNode(createArtificialOSMNodeId(), nodeAccess.getLat(id), nodeAccess.getLon(id)); + } else if (isPillarNode(id)) { + id = id - 3; + newNode = new ReaderNode(createArtificialOSMNodeId(), pillarInfo.getLat(id), pillarInfo.getLon(id)); } else { - graphIndex = graphIndex - 3; - newNode = new ReaderNode(createNewNodeId(), pillarInfo.getLat(graphIndex), pillarInfo.getLon(graphIndex)); + throw new IllegalStateException("Cannot add barrier nodes for osm node ids that do not appear in ways or nodes"); } - final long id = newNode.getId(); - prepareHighwayNode(id); - addNode(newNode); - return id; + final long osmId = newNode.getId(); + if (getNodeMap().get(osmId) != -1) + throw new IllegalStateException("Artificial osm node id already exists: " + osmId); + getNodeMap().put(osmId, INTERMEDIATE_NODE); + int newId = addPillarNode(osmId, newNode.getLat(), newNode.getLon(), eleProvider.getEle(newNode)); + return new SegmentNode(osmId, newId); } - private long createNewNodeId() { + private long createArtificialOSMNodeId() { return newUniqueOsmId++; } - /** - * Add a zero length edge with reduced routing options to the graph. - */ - Collection addBarrierEdge(long fromId, long toId, IntsRef inEdgeFlags, long nodeFlags, long wayOsmId) { - IntsRef edgeFlags = IntsRef.deepCopyOf(inEdgeFlags); - // clear blocked directions from flags - for (BooleanEncodedValue accessEnc : encodingManager.getAccessEncFromNodeFlags(nodeFlags)) { - accessEnc.setBool(false, edgeFlags, false); - accessEnc.setBool(true, edgeFlags, false); - } - // add edge - barrierNodeIds.clear(); - barrierNodeIds.add(fromId); - barrierNodeIds.add(toId); - return addOSMWay(barrierNodeIds, edgeFlags, wayOsmId); - } - /** * Creates turn relations out of an unspecified OSM relation */ @@ -1057,7 +967,8 @@ List createTurnRelations(ReaderRelation relation) { return osmTurnRelations; } - OSMTurnRelation createTurnRelation(ReaderRelation relation, String restrictionType, String vehicleTypeRestricted, List vehicleTypesExcept) { + OSMTurnRelation createTurnRelation(ReaderRelation relation, String restrictionType, String + vehicleTypeRestricted, List vehicleTypesExcept) { OSMTurnRelation.Type type = OSMTurnRelation.Type.getRestrictionType(restrictionType); if (type != OSMTurnRelation.Type.UNSUPPORTED) { long fromWayID = -1; @@ -1094,16 +1005,12 @@ public LongIntMap getNodeMap() { } // ORS-GH END - protected LongLongMap getNodeFlagsMap() { - return osmNodeIdToNodeFlagsMap; - } - int getRelFlagsMapSize() { - return osmWayIdToRouteWeightMap.size(); + return osmWayIdToRelationFlagsMap.size(); } IntsRef getRelFlagsMap(long osmId) { - long relFlagsAsLong = osmWayIdToRouteWeightMap.get(osmId); + long relFlagsAsLong = osmWayIdToRelationFlagsMap.get(osmId); tempRelFlags.ints[0] = (int) relFlagsAsLong; tempRelFlags.ints[1] = (int) (relFlagsAsLong >> 32); return tempRelFlags; @@ -1111,7 +1018,7 @@ IntsRef getRelFlagsMap(long osmId) { void putRelFlagsMap(long osmId, IntsRef relFlags) { long relFlagsAsLong = ((long) relFlags.ints[1] << 32) | (relFlags.ints[0] & 0xFFFFFFFFL); - osmWayIdToRouteWeightMap.put(osmId, relFlagsAsLong); + osmWayIdToRelationFlagsMap.put(osmId, relFlagsAsLong); } public OSMReader setAreaIndex(AreaIndex areaIndex) { @@ -1119,37 +1026,11 @@ public OSMReader setAreaIndex(AreaIndex areaIndex) { return this; } - public OSMReader setWayPointMaxDistance(double maxDist) { - doSimplify = maxDist > 0; - simplifyAlgo.setMaxDistance(maxDist); - return this; - } - - public OSMReader setWayPointElevationMaxDistance(double elevationWayPointMaxDistance) { - simplifyAlgo.setElevationMaxDistance(elevationWayPointMaxDistance); - return this; - } - - public OSMReader setSmoothElevation(boolean smoothElevation) { - this.smoothElevation = smoothElevation; - return this; - } - public OSMReader setCountryRuleFactory(CountryRuleFactory countryRuleFactory) { this.countryRuleFactory = countryRuleFactory; return this; } - public OSMReader setLongEdgeSamplingDistance(double longEdgeSamplingDistance) { - this.longEdgeSamplingDistance = longEdgeSamplingDistance; - return this; - } - - public OSMReader setWorkerThreads(int numOfWorkers) { - this.workerThreads = numOfWorkers; - return this; - } - public OSMReader setElevationProvider(ElevationProvider eleProvider) { if (eleProvider == null) throw new IllegalStateException("Use the NOOP elevation provider instead of null or don't call setElevationProvider"); @@ -1168,8 +1049,9 @@ public OSMReader setFile(File osmFile) { private void printInfo(String str) { LOGGER.info("finished " + str + " processing." + " nodes: " + graph.getNodes() + + ", ignored barrier nodes at junctions: " + nf(ignoredBarrierNodes) + ", osmIdMap.size:" + getNodeMap().getSize() + ", osmIdMap:" + getNodeMap().getMemoryUsage() + "MB" - + ", nodeFlagsMap.size:" + getNodeFlagsMap().size() + ", relFlagsMap.size:" + getRelFlagsMapSize() + + ", nodeTags.size:" + nodeTags.size() + ", relationsMap.size:" + getRelFlagsMapSize() + ", zeroCounter:" + zeroCounter + " " + Helper.getMemInfo()); } @@ -1201,4 +1083,38 @@ protected DistanceCalc getDistanceCalc() { public String toString() { return getClass().getSimpleName(); } + + // ORS-GH MOD START - expose method to ORS + protected boolean isTowerNode(int id) { + // ORS-GH MOD END + // tower nodes are indexed -3, -4, -5, ... + return id < JUNCTION_NODE; + } + + // ORS-GH MOD START - expose method to ORS + protected boolean isPillarNode(int id) { + // ORS-GH MOD END + // pillar nodes are indexed 3, 4, 5, .. + return id > CONNECTION_NODE; + } + + // ORS-GH MOD START - add new method for ORS + protected boolean isEmptyNode(int id) { + return id == EMPTY_NODE; + } + // ORS-GH MOD END + + private boolean isNodeId(int id) { + return id > CONNECTION_NODE || id < JUNCTION_NODE; + } + + static class SegmentNode { + long osmNodeId; + int id; + + public SegmentNode(long osmNodeId, int id) { + this.osmNodeId = osmNodeId; + this.id = id; + } + } } diff --git a/core/src/main/java/com/graphhopper/routing/DirectionResolver.java b/core/src/main/java/com/graphhopper/routing/DirectionResolver.java index 5310c348b5c..26dec3941da 100644 --- a/core/src/main/java/com/graphhopper/routing/DirectionResolver.java +++ b/core/src/main/java/com/graphhopper/routing/DirectionResolver.java @@ -67,7 +67,7 @@ public DirectionResolver(Graph graph, BiPredicate is */ public DirectionResolverResult resolveDirections(int node, GHPoint location) { AdjacentEdges adjacentEdges = calcAdjEdges(node); - if (adjacentEdges.numNonLoops == 0) { + if (adjacentEdges.numStandardEdges == 0) { return DirectionResolverResult.impossible(); } if (!adjacentEdges.hasInEdges() || !adjacentEdges.hasOutEdges()) { @@ -79,15 +79,20 @@ public DirectionResolverResult resolveDirections(int node, GHPoint location) { if (adjacentEdges.numLoops > 0) { return DirectionResolverResult.unrestricted(); } - GHPoint snappedPoint = new GHPoint(nodeAccess.getLat(node), nodeAccess.getLon(node)); + if (adjacentEdges.numZeroDistanceEdges > 0) { + // if we snap to a tower node that is adjacent to a barrier edge we apply no restrictions. this is the + // easiest thing to do, but maybe we need a more sophisticated handling of this case in the future. + return DirectionResolverResult.unrestricted(); + } + Point snappedPoint = new Point(nodeAccess.getLat(node), nodeAccess.getLon(node)); if (adjacentEdges.nextPoints.contains(snappedPoint)) { // this might happen if a pillar node of an adjacent edge has the same coordinates as the snapped point, // but this should be prevented by the map import already - throw new IllegalArgumentException("Pillar node of adjacent edge matches snapped point, this should not happen"); + throw new IllegalStateException("Pillar node of adjacent edge matches snapped point, this should not happen"); } // we can classify the different cases by the number of different next points! if (adjacentEdges.nextPoints.size() == 1) { - GHPoint neighbor = adjacentEdges.nextPoints.iterator().next(); + Point neighbor = adjacentEdges.nextPoints.iterator().next(); List inEdges = adjacentEdges.getInEdges(neighbor); List outEdges = adjacentEdges.getOutEdges(neighbor); assert inEdges.size() > 0 && outEdges.size() > 0 : "if there is only one next point there has to be an in edge and an out edge connected with it"; @@ -100,9 +105,9 @@ public DirectionResolverResult resolveDirections(int node, GHPoint location) { // side are treated equally and for both cases we use the only possible edge ids. return DirectionResolverResult.restricted(inEdges.get(0).edgeId, outEdges.get(0).edgeId, inEdges.get(0).edgeId, outEdges.get(0).edgeId); } else if (adjacentEdges.nextPoints.size() == 2) { - Iterator iter = adjacentEdges.nextPoints.iterator(); - GHPoint p1 = iter.next(); - GHPoint p2 = iter.next(); + Iterator iter = adjacentEdges.nextPoints.iterator(); + Point p1 = iter.next(); + Point p2 = iter.next(); List in1 = adjacentEdges.getInEdges(p1); List in2 = adjacentEdges.getInEdges(p2); List out1 = adjacentEdges.getOutEdges(p1); @@ -116,22 +121,23 @@ public DirectionResolverResult resolveDirections(int node, GHPoint location) { if (in1.size() + out1.size() == 0 || in2.size() + out2.size() == 0) { throw new IllegalStateException("there has to be at least one in or one out edge for each of the two next points"); } + Point locationPoint = new Point(location.lat, location.lon); if (in1.isEmpty() || out2.isEmpty()) { - return resolveDirections(snappedPoint, location, in2.get(0), out1.get(0)); + return resolveDirections(snappedPoint, locationPoint, in2.get(0), out1.get(0)); } else if (in2.isEmpty() || out1.isEmpty()) { - return resolveDirections(snappedPoint, location, in1.get(0), out2.get(0)); + return resolveDirections(snappedPoint, locationPoint, in1.get(0), out2.get(0)); } else { - return resolveDirections(snappedPoint, location, in1.get(0), out2.get(0), in2.get(0).edgeId, out1.get(0).edgeId); + return resolveDirections(snappedPoint, locationPoint, in1.get(0), out2.get(0), in2.get(0).edgeId, out1.get(0).edgeId); } } else { // we snapped to a junction, in this case we do not apply restrictions // note: TOWER and PILLAR mostly occur when location is near the end of a dead end street or a sharp - // curve, like switchbacks in the mountains of andorra + // curve, like switchbacks in the mountains of Andorra return DirectionResolverResult.unrestricted(); } } - private DirectionResolverResult resolveDirections(GHPoint snappedPoint, GHPoint queryPoint, Edge inEdge, Edge outEdge) { + private DirectionResolverResult resolveDirections(Point snappedPoint, Point queryPoint, Edge inEdge, Edge outEdge) { boolean rightLane = isOnRightLane(queryPoint, snappedPoint, inEdge.nextPoint, outEdge.nextPoint); if (rightLane) { return DirectionResolverResult.onlyRight(inEdge.edgeId, outEdge.edgeId); @@ -140,9 +146,9 @@ private DirectionResolverResult resolveDirections(GHPoint snappedPoint, GHPoint } } - private DirectionResolverResult resolveDirections(GHPoint snappedPoint, GHPoint queryPoint, Edge inEdge, Edge outEdge, int altInEdge, int altOutEdge) { - GHPoint inPoint = inEdge.nextPoint; - GHPoint outPoint = outEdge.nextPoint; + private DirectionResolverResult resolveDirections(Point snappedPoint, Point queryPoint, Edge inEdge, Edge outEdge, int altInEdge, int altOutEdge) { + Point inPoint = inEdge.nextPoint; + Point outPoint = outEdge.nextPoint; boolean rightLane = isOnRightLane(queryPoint, snappedPoint, inPoint, outPoint); if (rightLane) { return DirectionResolverResult.restricted(inEdge.edgeId, outEdge.edgeId, altInEdge, altOutEdge); @@ -151,7 +157,7 @@ private DirectionResolverResult resolveDirections(GHPoint snappedPoint, GHPoint } } - private boolean isOnRightLane(GHPoint queryPoint, GHPoint snappedPoint, GHPoint inPoint, GHPoint outPoint) { + private boolean isOnRightLane(Point queryPoint, Point snappedPoint, Point inPoint, Point outPoint) { double qX = diffLon(snappedPoint, queryPoint); double qY = diffLat(snappedPoint, queryPoint); double iX = diffLon(snappedPoint, inPoint); @@ -161,11 +167,11 @@ private boolean isOnRightLane(GHPoint queryPoint, GHPoint snappedPoint, GHPoint return !AngleCalc.ANGLE_CALC.isClockwise(iX, iY, oX, oY, qX, qY); } - private double diffLon(GHPoint p, GHPoint q) { + private double diffLon(Point p, Point q) { return q.lon - p.lon; } - private double diffLat(GHPoint p, GHPoint q) { + private double diffLat(Point p, Point q) { return q.lat - p.lat; } @@ -175,40 +181,52 @@ private AdjacentEdges calcAdjEdges(int node) { while (iter.next()) { boolean isIn = isAccessible.test(iter, true); boolean isOut = isAccessible.test(iter, false); - if (!isIn && !isOut) { + if (!isIn && !isOut) continue; - } - if (iter.getBaseNode() == iter.getAdjNode()) { - adjacentEdges.numLoops++; - } else { - adjacentEdges.numNonLoops++; - } // we are interested in the coordinates of the next point on this edge, it could be the adj tower node // but also a pillar node final PointList geometry = iter.fetchWayGeometry(FetchMode.ALL); double nextPointLat = geometry.getLat(1); double nextPointLon = geometry.getLon(1); - // todo: special treatment in case the coordinates of the first pillar node equal those of the base tower - // node, see #1694 - if (geometry.size() > 2 && PointList.equalsEps(nextPointLat, geometry.getLat(0)) && + boolean isZeroDistanceEdge = false; + if (PointList.equalsEps(nextPointLat, geometry.getLat(0)) && PointList.equalsEps(nextPointLon, geometry.getLon(0))) { - nextPointLat = geometry.getLat(2); - nextPointLon = geometry.getLon(2); + if (geometry.size() > 2) { + // todo: special treatment in case the coordinates of the first pillar node equal those of the base tower + // node, see #1694 + nextPointLat = geometry.getLat(2); + nextPointLon = geometry.getLon(2); + } else if (geometry.size() == 2) { + // an edge where base and adj node share the same coordinates. this is the case for barrier edges that + // we create artificially + isZeroDistanceEdge = true; + } else { + throw new IllegalStateException("Geometry has less than two points"); + } } - GHPoint nextPoint = new GHPoint(nextPointLat, nextPointLon); + Point nextPoint = new Point(nextPointLat, nextPointLon); Edge edge = new Edge(iter.getEdge(), iter.getAdjNode(), nextPoint); adjacentEdges.addEdge(edge, isIn, isOut); + + if (iter.getBaseNode() == iter.getAdjNode()) + adjacentEdges.numLoops++; + else if (isZeroDistanceEdge) + adjacentEdges.numZeroDistanceEdges++; + else + adjacentEdges.numStandardEdges++; + } return adjacentEdges; } private static class AdjacentEdges { - private final Map> inEdgesByNextPoint = new HashMap<>(2); - private final Map> outEdgesByNextPoint = new HashMap<>(2); - final Set nextPoints = new HashSet<>(2); + private final Map> inEdgesByNextPoint = new HashMap<>(2); + private final Map> outEdgesByNextPoint = new HashMap<>(2); + final Set nextPoints = new HashSet<>(2); int numLoops; - int numNonLoops; + int numStandardEdges; + int numZeroDistanceEdges; void addEdge(Edge edge, boolean isIn, boolean isOut) { if (isIn) { @@ -220,14 +238,14 @@ void addEdge(Edge edge, boolean isIn, boolean isOut) { addNextPoint(edge); } - List getInEdges(GHPoint p) { + List getInEdges(Point p) { List result = inEdgesByNextPoint.get(p); - return result == null ? Collections.emptyList() : result; + return result == null ? Collections.emptyList() : result; } - List getOutEdges(GHPoint p) { + List getOutEdges(Point p) { List result = outEdgesByNextPoint.get(p); - return result == null ? Collections.emptyList() : result; + return result == null ? Collections.emptyList() : result; } boolean hasInEdges() { @@ -250,7 +268,7 @@ private void addNextPoint(Edge edge) { nextPoints.add(edge.nextPoint); } - private void addEdge(Map> edgesByNextPoint, Edge edge) { + private static void addEdge(Map> edgesByNextPoint, Edge edge) { List edges = edgesByNextPoint.get(edge.nextPoint); if (edges == null) { edges = new ArrayList<>(2); @@ -262,6 +280,36 @@ private void addEdge(Map> edgesByNextPoint, Edge edge) { } } + private static class Point { + final double lat; + final double lon; + + Point(double lat, double lon) { + this.lat = lat; + this.lon = lon; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + Point other = (Point) o; + return NumHelper.equalsEps(lat, other.lat) && NumHelper.equalsEps(lon, other.lon); + } + + @Override + public int hashCode() { + // it does not matter, because we only use maps with very few elements. not using GHPoint because of it's + // broken hashCode implementation (#2445) and there is no good reason need to depend on it either + return 0; + } + + @Override + public String toString() { + return lat + ", " + lon; + } + } + private static class Edge { final int edgeId; final int adjNode; @@ -269,9 +317,9 @@ private static class Edge { * the next point of this edge, not necessarily the point corresponding to adjNode, but often this is the * next pillar (!) node. */ - final GHPoint nextPoint; + final Point nextPoint; - Edge(int edgeId, int adjNode, GHPoint nextPoint) { + Edge(int edgeId, int adjNode, Point nextPoint) { this.edgeId = edgeId; this.adjNode = adjNode; this.nextPoint = nextPoint; diff --git a/core/src/main/java/com/graphhopper/routing/HeadingResolver.java b/core/src/main/java/com/graphhopper/routing/HeadingResolver.java index 1548ffabba5..db22126a898 100644 --- a/core/src/main/java/com/graphhopper/routing/HeadingResolver.java +++ b/core/src/main/java/com/graphhopper/routing/HeadingResolver.java @@ -19,12 +19,14 @@ package com.graphhopper.routing; import com.carrotsearch.hppc.IntArrayList; +import com.graphhopper.routing.util.EdgeFilter; import com.graphhopper.storage.Graph; import com.graphhopper.util.*; +import com.graphhopper.util.shapes.GHPoint; public class HeadingResolver { private final EdgeExplorer edgeExplorer; - private double toleranceRad = deg2Rad(100); + private double toleranceRad = Math.toRadians(100); public HeadingResolver(Graph graph) { this.edgeExplorer = graph.createEdgeExplorer(); @@ -63,12 +65,7 @@ public IntArrayList getEdgesWithDifferentHeading(int baseNode, double heading) { * Sets the tolerance for {@link #getEdgesWithDifferentHeading} in degrees. */ public HeadingResolver setTolerance(double tolerance) { - this.toleranceRad = deg2Rad(tolerance); + this.toleranceRad = Math.toRadians(tolerance); return this; } - - private static double deg2Rad(double deg) { - return Math.toRadians(deg); - } - } diff --git a/core/src/main/java/com/graphhopper/routing/OSMReaderConfig.java b/core/src/main/java/com/graphhopper/routing/OSMReaderConfig.java new file mode 100644 index 00000000000..62a4fa07a4e --- /dev/null +++ b/core/src/main/java/com/graphhopper/routing/OSMReaderConfig.java @@ -0,0 +1,88 @@ +/* + * 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.routing; + +public class OSMReaderConfig { + private double maxWayPointDistance = 1; + private double elevationMaxWayPointDistance = Double.MAX_VALUE; + private boolean smoothElevation = false; + private double longEdgeSamplingDistance = Double.MAX_VALUE; + private int workerThreads = 2; + + public double getMaxWayPointDistance() { + return maxWayPointDistance; + } + + /** + * This parameter affects the routine used to simplify the edge geometries (Douglas-Peucker). Higher values mean + * more details are preserved. The default is 1 (meter). Simplification can be disabled by setting it to 0. + */ + public OSMReaderConfig setMaxWayPointDistance(double maxWayPointDistance) { + this.maxWayPointDistance = maxWayPointDistance; + return this; + } + + public double getElevationMaxWayPointDistance() { + return elevationMaxWayPointDistance; + } + + /** + * Sets the max elevation discrepancy between way points and the simplified polyline in meters + */ + public OSMReaderConfig setElevationMaxWayPointDistance(double elevationMaxWayPointDistance) { + this.elevationMaxWayPointDistance = elevationMaxWayPointDistance; + return this; + } + + public boolean isSmoothElevation() { + return smoothElevation; + } + + /** + * Enables/disables elevation smoothing + */ + public OSMReaderConfig setSmoothElevation(boolean smoothElevation) { + this.smoothElevation = smoothElevation; + return this; + } + + public double getLongEdgeSamplingDistance() { + return longEdgeSamplingDistance; + } + + /** + * Sets the distance between elevation samples on long edges + */ + public OSMReaderConfig setLongEdgeSamplingDistance(double longEdgeSamplingDistance) { + this.longEdgeSamplingDistance = longEdgeSamplingDistance; + return this; + } + + public int getWorkerThreads() { + return workerThreads; + } + + /** + * Sets the number of threads used for the OSM import + */ + public OSMReaderConfig setWorkerThreads(int workerThreads) { + this.workerThreads = workerThreads; + return this; + } +} diff --git a/core/src/main/java/com/graphhopper/routing/Router.java b/core/src/main/java/com/graphhopper/routing/Router.java index 086394e1b17..7e2c11f1d36 100644 --- a/core/src/main/java/com/graphhopper/routing/Router.java +++ b/core/src/main/java/com/graphhopper/routing/Router.java @@ -24,6 +24,7 @@ import com.graphhopper.ResponsePath; import com.graphhopper.config.Profile; import com.graphhopper.routing.ch.CHRoutingAlgorithmFactory; +import com.graphhopper.routing.ev.BooleanEncodedValue; import com.graphhopper.routing.ev.EncodedValueLookup; import com.graphhopper.routing.ev.Subnetwork; import com.graphhopper.routing.lm.LMRoutingAlgorithmFactory; @@ -176,12 +177,12 @@ private void checkHeadings(GHRequest request) { } private void checkPointHints(GHRequest request) { - if (request.getPointHints().size() > 0 && request.getPointHints().size() != request.getPoints().size()) + if (!request.getPointHints().isEmpty() && request.getPointHints().size() != request.getPoints().size()) throw new IllegalArgumentException("If you pass " + POINT_HINT + ", you need to pass exactly one hint for every point, empty hints will be ignored"); } private void checkCurbsides(GHRequest request) { - if (request.getCurbsides().size() > 0 && request.getCurbsides().size() != request.getPoints().size()) + if (!request.getCurbsides().isEmpty() && request.getCurbsides().size() != request.getPoints().size()) throw new IllegalArgumentException("If you pass " + CURBSIDE + ", you need to pass exactly one curbside for every point, empty curbsides will be ignored"); } @@ -207,7 +208,7 @@ protected GHResponse routeRoundTrip(GHRequest request, FlexSolver solver) { StopWatch sw = new StopWatch().start(); double startHeading = request.getHeadings().isEmpty() ? Double.NaN : request.getHeadings().get(0); RoundTripRouting.Params params = new RoundTripRouting.Params(request.getHints(), startHeading, routerConfig.getMaxRoundTripRetries()); - List snaps = RoundTripRouting.lookup(request.getPoints(), solver.getSnapFilter(), locationIndex, params); + List snaps = RoundTripRouting.lookup(request.getPoints(), solver.createSnapFilter(), locationIndex, params); ghRsp.addDebugInfo("idLookup:" + sw.stop().getSeconds() + "s"); // ORS-GH MOD START - additional code checkMaxSearchDistances(request, ghRsp, snaps); @@ -259,7 +260,8 @@ protected GHResponse routeAlt(GHRequest request, Solver solver) { throw new IllegalArgumentException("Currently alternative routes work only with start and end point. You tried to use: " + request.getPoints().size() + " points"); GHResponse ghRsp = new GHResponse(); StopWatch sw = new StopWatch().start(); - List snaps = ViaRouting.lookup(encodingManager, request.getPoints(), solver.getSnapFilter(), locationIndex, request.getSnapPreventions(), request.getPointHints()); + List snaps = ViaRouting.lookup(encodingManager, request.getPoints(), solver.createSnapFilter(), locationIndex, + request.getSnapPreventions(), request.getPointHints(), solver.createDirectedSnapFilter(), request.getHeadings()); ghRsp.addDebugInfo("idLookup:" + sw.stop().getSeconds() + "s"); // ORS-GH MOD START - additional code checkMaxSearchDistances(request, ghRsp, snaps); @@ -308,7 +310,8 @@ protected GHResponse routeAlt(GHRequest request, Solver solver) { protected GHResponse routeVia(GHRequest request, Solver solver) { GHResponse ghRsp = new GHResponse(); StopWatch sw = new StopWatch().start(); - List snaps = ViaRouting.lookup(encodingManager, request.getPoints(), solver.getSnapFilter(), locationIndex, request.getSnapPreventions(), request.getPointHints()); + List snaps = ViaRouting.lookup(encodingManager, request.getPoints(), solver.createSnapFilter(), locationIndex, + request.getSnapPreventions(), request.getPointHints(), solver.createDirectedSnapFilter(), request.getHeadings()); ghRsp.addDebugInfo("idLookup:" + sw.stop().getSeconds() + "s"); // ORS-GH MOD START - additional code checkMaxSearchDistances(request, ghRsp, snaps); @@ -321,7 +324,8 @@ protected GHResponse routeVia(GHRequest request, Solver solver) { boolean forceCurbsides = getForceCurbsides(request.getHints()); // ORS-GH MOD START: enable TD routing long time = getTime(request.getHints()); - ViaRouting.Result result = ViaRouting.calcPaths(request.getPoints(), queryGraph, snaps, solver.weighting, pathCalculator, request.getCurbsides(), forceCurbsides, request.getHeadings(), passThrough, time); + ViaRouting.Result result = ViaRouting.calcPaths(request.getPoints(), queryGraph, snaps, solver.weighting, + pathCalculator, request.getCurbsides(), forceCurbsides, request.getHeadings(), passThrough, time); // ORS-GH MOD END if (request.getPoints().size() != result.paths.size() + 1) @@ -490,10 +494,15 @@ protected void checkProfileCompatibility() { protected abstract Weighting createWeighting(); - protected EdgeFilter getSnapFilter() { + protected EdgeFilter createSnapFilter() { return new DefaultSnapFilter(weighting, lookup.getBooleanEncodedValue(Subnetwork.key(profile.getName()))); } + protected EdgeFilter createDirectedSnapFilter() { + BooleanEncodedValue inSubnetworkEnc = lookup.getBooleanEncodedValue(Subnetwork.key(profile.getName())); + return edgeState -> !edgeState.get(inSubnetworkEnc) && Double.isFinite(weighting.calcEdgeWeightWithAccess(edgeState, false)); + } + protected abstract PathCalculator createPathCalculator(QueryGraph queryGraph); private List getTurnCostProfiles() { @@ -611,7 +620,7 @@ protected FlexiblePathCalculator createPathCalculator(QueryGraph queryGraph) { // ORS-GH MOD START: pass edgeFilter @Override - protected EdgeFilter getSnapFilter() { + protected EdgeFilter createSnapFilter() { EdgeFilter defaultSnapFilter = new DefaultSnapFilter(weighting, lookup.getBooleanEncodedValue(Subnetwork.key(profile.getName()))); if (edgeFilterFactory != null) return edgeFilterFactory.createEdgeFilter(request.getAdditionalHints(), weighting.getFlagEncoder(), ghStorage, defaultSnapFilter); diff --git a/core/src/main/java/com/graphhopper/routing/ViaRouting.java b/core/src/main/java/com/graphhopper/routing/ViaRouting.java index 8f9eb474b49..66ea5751f01 100644 --- a/core/src/main/java/com/graphhopper/routing/ViaRouting.java +++ b/core/src/main/java/com/graphhopper/routing/ViaRouting.java @@ -24,7 +24,7 @@ import com.graphhopper.routing.ev.RoadEnvironment; import com.graphhopper.routing.querygraph.QueryGraph; import com.graphhopper.routing.util.EdgeFilter; -import com.graphhopper.routing.util.FiniteWeightFilter; +import com.graphhopper.routing.util.HeadingEdgeFilter; import com.graphhopper.routing.util.NameSimilarityEdgeFilter; import com.graphhopper.routing.util.SnapPreventionEdgeFilter; import com.graphhopper.routing.weighting.Weighting; @@ -54,27 +54,36 @@ public class ViaRouting { /** * @throws MultiplePointsNotFoundException in case one or more points could not be resolved */ - public static List lookup(EncodedValueLookup lookup, List points, EdgeFilter edgeFilter, LocationIndex locationIndex, List snapPreventions, List pointHints) { + public static List lookup(EncodedValueLookup lookup, List points, EdgeFilter snapFilter, + LocationIndex locationIndex, List snapPreventions, List pointHints, + EdgeFilter directedSnapFilter, List headings) { if (points.size() < 2) throw new IllegalArgumentException("At least 2 points have to be specified, but was:" + points.size()); final EnumEncodedValue roadClassEnc = lookup.getEnumEncodedValue(RoadClass.KEY, RoadClass.class); final EnumEncodedValue roadEnvEnc = lookup.getEnumEncodedValue(RoadEnvironment.KEY, RoadEnvironment.class); EdgeFilter strictEdgeFilter = snapPreventions.isEmpty() - ? edgeFilter - : new SnapPreventionEdgeFilter(edgeFilter, roadClassEnc, roadEnvEnc, snapPreventions); + ? snapFilter + : new SnapPreventionEdgeFilter(snapFilter, roadClassEnc, roadEnvEnc, snapPreventions); List snaps = new ArrayList<>(points.size()); IntArrayList pointsNotFound = new IntArrayList(); for (int placeIndex = 0; placeIndex < points.size(); placeIndex++) { GHPoint point = points.get(placeIndex); Snap snap = null; - if (!pointHints.isEmpty()) + if (placeIndex < headings.size() && !Double.isNaN(headings.get(placeIndex))) { + if (!pointHints.isEmpty() && !Helper.isEmpty(pointHints.get(placeIndex))) + throw new IllegalArgumentException("Cannot specify heading and point_hint at the same time. " + + "Make sure you specify either an empty point_hint (String) or a NaN heading (double) for point " + placeIndex); + snap = locationIndex.findClosest(point.lat, point.lon, new HeadingEdgeFilter(directedSnapFilter, headings.get(placeIndex), point)); + } else if (!pointHints.isEmpty()) { snap = locationIndex.findClosest(point.lat, point.lon, new NameSimilarityEdgeFilter(strictEdgeFilter, pointHints.get(placeIndex), point, 100)); - else if (!snapPreventions.isEmpty()) + } else if (!snapPreventions.isEmpty()) { snap = locationIndex.findClosest(point.lat, point.lon, strictEdgeFilter); + } + if (snap == null || !snap.isValid()) - snap = locationIndex.findClosest(point.lat, point.lon, edgeFilter); + snap = locationIndex.findClosest(point.lat, point.lon, snapFilter); if (!snap.isValid()) pointsNotFound.add(placeIndex); diff --git a/core/src/main/java/com/graphhopper/routing/ch/CHPreparationHandler.java b/core/src/main/java/com/graphhopper/routing/ch/CHPreparationHandler.java index dd2b460cc82..daf724b1f10 100644 --- a/core/src/main/java/com/graphhopper/routing/ch/CHPreparationHandler.java +++ b/core/src/main/java/com/graphhopper/routing/ch/CHPreparationHandler.java @@ -41,12 +41,11 @@ * @author easbar */ public class CHPreparationHandler { - private final Logger LOGGER = LoggerFactory.getLogger(getClass()); + private static final Logger LOGGER = LoggerFactory.getLogger(CHPreparationHandler.class); private final List preparations = new ArrayList<>(); // we first add the profiles and later read them to create the config objects (because they require // the actual Weightings) private final List chProfiles = new ArrayList<>(); - private final List chConfigs = new ArrayList<>(); private int preparationThreads; private ExecutorService threadPool; // ORS-GH MOD START change visibility private-> protected and allow overriding String constants @@ -74,57 +73,7 @@ public void init(GraphHopperConfig ghConfig) { } public final boolean isEnabled() { - return !chProfiles.isEmpty() || !chConfigs.isEmpty() || !preparations.isEmpty(); - } - - /** - * Decouple CH profiles from PrepareContractionHierarchies as we need CH profiles for the - * graphstorage and the graphstorage for the preparation. - */ - public CHPreparationHandler addCHConfig(CHConfig chConfig) { - chConfigs.add(chConfig); - return this; - } - - public CHPreparationHandler addPreparation(PrepareContractionHierarchies pch) { - // we want to make sure that CH preparations are added in the same order as their corresponding profiles - if (preparations.size() >= chConfigs.size()) { - throw new IllegalStateException("You need to add the corresponding CH configs before adding preparations."); - } - CHConfig expectedConfig = chConfigs.get(preparations.size()); - if (!pch.getCHConfig().equals(expectedConfig)) { - throw new IllegalArgumentException("CH config of preparation: " + pch + " needs to be identical to previously added CH config: " + expectedConfig); - } - preparations.add(pch); - return this; - } - - public final boolean hasCHConfigs() { - return !chConfigs.isEmpty(); - } - - public List getCHConfigs() { - return chConfigs; - } - - public List getNodeBasedCHConfigs() { - List result = new ArrayList<>(); - for (CHConfig chConfig : chConfigs) { - if (!chConfig.getTraversalMode().isEdgeBased()) { - result.add(chConfig); - } - } - return result; - } - - public List getEdgeBasedCHConfigs() { - List result = new ArrayList<>(); - for (CHConfig chConfig : chConfigs) { - if (chConfig.getTraversalMode().isEdgeBased()) { - result.add(chConfig); - } - } - return result; + return !chProfiles.isEmpty() || !preparations.isEmpty(); } public CHPreparationHandler setCHProfiles(CHProfile... chProfiles) { @@ -132,10 +81,6 @@ public CHPreparationHandler setCHProfiles(CHProfile... chProfiles) { return this; } - /** - * Enables the use of contraction hierarchies to reduce query times. - * "fastest|u_turn_costs=30 or your own weight-calculation type. - */ public CHPreparationHandler setCHProfiles(Collection chProfiles) { this.chProfiles.clear(); this.chProfiles.addAll(chProfiles); @@ -150,25 +95,21 @@ public List getPreparations() { return preparations; } - public PrepareContractionHierarchies getPreparation(String profile) { + public PrepareContractionHierarchies getPreparation(String chGraphName) { if (preparations.isEmpty()) throw new IllegalStateException("No CH preparations added yet"); List profileNames = new ArrayList<>(preparations.size()); for (PrepareContractionHierarchies preparation : preparations) { profileNames.add(preparation.getCHConfig().getName()); - if (preparation.getCHConfig().getName().equalsIgnoreCase(profile)) { + if (preparation.getCHConfig().getName().equalsIgnoreCase(chGraphName)) { return preparation; } } - throw new IllegalArgumentException("Cannot find CH preparation for the requested profile: '" + profile + "'" + + throw new IllegalArgumentException("Cannot find CH preparation for the requested profile: '" + chGraphName + "'" + "\nYou can try disabling CH using " + DISABLE + "=true" + "\navailable CH profiles: " + profileNames); } - public PrepareContractionHierarchies getPreparation(CHConfig chConfig) { - return getPreparation(chConfig.getName()); - } - public int getPreparationThreads() { return preparationThreads; } @@ -179,7 +120,6 @@ public int getPreparationThreads() { */ public void setPreparationThreads(int preparationThreads) { this.preparationThreads = preparationThreads; - LOGGER.info("Using {} threads for ch preparation threads", preparationThreads); this.threadPool = java.util.concurrent.Executors.newFixedThreadPool(preparationThreads); } @@ -215,14 +155,11 @@ public void prepare(final StorableProperties properties, final boolean closeEarl } public void createPreparations(GraphHopperStorage ghStorage) { - if (!isEnabled() || !preparations.isEmpty()) - return; - if (!hasCHConfigs()) - throw new IllegalStateException("No CH profiles found"); - + if (!preparations.isEmpty()) + throw new IllegalStateException("CH preparations were created already"); LOGGER.info("Creating CH preparations, {}", getMemInfo()); - for (CHConfig chConfig : chConfigs) { - addPreparation(createCHPreparation(ghStorage, chConfig)); + for (CHConfig chConfig : ghStorage.getCHConfigs()) { + preparations.add(createCHPreparation(ghStorage, chConfig)); } } diff --git a/core/src/main/java/com/graphhopper/routing/ev/Toll.java b/core/src/main/java/com/graphhopper/routing/ev/Toll.java index ef8bdc5db0f..a01bc548063 100644 --- a/core/src/main/java/com/graphhopper/routing/ev/Toll.java +++ b/core/src/main/java/com/graphhopper/routing/ev/Toll.java @@ -17,13 +17,12 @@ */ package com.graphhopper.routing.ev; -import com.graphhopper.util.Helper; - /** - * This enum defines the toll value like NO (default), ALL (all vehicles) and HGV (toll for heavy goods vehicles) + * This enum defines the toll value like MISSING (default), NO (no toll), HGV + * (toll for heavy goods vehicles) and ALL (all vehicles) */ public enum Toll { - NO("no"), ALL("all"), HGV("hgv"); + MISSING("missing"), NO("no"), HGV("hgv"), ALL("all"); public static final String KEY = "toll"; @@ -33,16 +32,6 @@ public enum Toll { this.name = name; } - public static Toll find(String name) { - if (name == null) - return NO; - try { - return Toll.valueOf(Helper.toUpperCase(name)); - } catch (IllegalArgumentException ex) { - return NO; - } - } - @Override public String toString() { return name; diff --git a/core/src/main/java/com/graphhopper/routing/lm/LMPreparationHandler.java b/core/src/main/java/com/graphhopper/routing/lm/LMPreparationHandler.java index 967ad754901..8fb4879c50a 100644 --- a/core/src/main/java/com/graphhopper/routing/lm/LMPreparationHandler.java +++ b/core/src/main/java/com/graphhopper/routing/lm/LMPreparationHandler.java @@ -36,6 +36,7 @@ import java.io.IOException; import java.io.InputStreamReader; import java.io.Reader; +import java.net.URL; import java.util.*; import java.util.concurrent.ExecutorCompletionService; import java.util.concurrent.ExecutorService; @@ -56,10 +57,7 @@ public class LMPreparationHandler { private int landmarkCount = 16; private final List preparations = new ArrayList<>(); - // we first add the profiles and later read them to create the config objects (because they require - // the actual Weightings) private final List lmProfiles = new ArrayList<>(); - private final List lmConfigs = new ArrayList<>(); private final Map maximumWeights = new HashMap<>(); private int minNodes = -1; private final List lmSuggestionsLocations = new ArrayList<>(5); @@ -92,7 +90,6 @@ protected void init(GraphHopperConfig ghConfig, List lmProfiles) { setPreparationThreads(ghConfig.getInt(PREPARE + "threads", getPreparationThreads())); // ORS-GH MOD START - //setLMProfiles(ghConfig.getLMProfiles()); setLMProfiles(lmProfiles); // ORS-GH MOD END @@ -123,7 +120,7 @@ public int getLandmarks() { } public final boolean isEnabled() { - return !lmProfiles.isEmpty() || !lmConfigs.isEmpty() || !preparations.isEmpty(); + return !lmProfiles.isEmpty() || !preparations.isEmpty(); } public int getPreparationThreads() { @@ -136,7 +133,6 @@ public int getPreparationThreads() { */ public void setPreparationThreads(int preparationThreads) { this.preparationThreads = preparationThreads; - LOGGER.info("Using {} threads for lm preparation threads", preparationThreads); this.threadPool = java.util.concurrent.Executors.newFixedThreadPool(preparationThreads); } @@ -163,40 +159,10 @@ public List getLMProfiles() { return lmProfiles; } - /** - * Decouple weightings from PrepareLandmarks as we need weightings for the graphstorage and the - * graphstorage for the preparation. - */ - public LMPreparationHandler addLMConfig(LMConfig lmConfig) { - lmConfigs.add(lmConfig); - return this; - } - - public LMPreparationHandler addPreparation(PrepareLandmarks plm) { - preparations.add(plm); - int lastIndex = preparations.size() - 1; - if (lastIndex >= lmConfigs.size()) - throw new IllegalStateException("Cannot access profile for PrepareLandmarks with " + plm.getLMConfig() - + ". Call add(LMConfig) before"); - - if (preparations.get(lastIndex).getLMConfig() != lmConfigs.get(lastIndex)) - throw new IllegalArgumentException("LMConfig of PrepareLandmarks " + preparations.get(lastIndex).getLMConfig() - + " needs to be identical to previously added " + lmConfigs.get(lastIndex)); - return this; - } - - public boolean hasLMProfiles() { - return !lmConfigs.isEmpty(); - } - public int size() { return preparations.size(); } - public List getLMConfigs() { - return lmConfigs; - } - public List getPreparations() { return preparations; } @@ -223,13 +189,15 @@ public PrepareLandmarks getPreparation(String profile) { * @return true if the preparation data for at least one profile was calculated. * @see CHPreparationHandler#prepare(StorableProperties, boolean) for a very similar method */ - public boolean loadOrDoWork(final StorableProperties properties, final boolean closeEarly) { + public boolean loadOrDoWork(List lmConfigs, GraphHopperStorage ghStorage, LocationIndex locationIndex, final boolean closeEarly) { + createPreparations(lmConfigs, ghStorage, locationIndex); for (PrepareLandmarks prep : preparations) { // using the area index we separate certain areas from each other but we do not change the base graph for this // so that other algorithms still can route between these areas if (areaIndex != null) prep.setAreaIndex(areaIndex); } + StorableProperties properties = ghStorage.getProperties(); ExecutorCompletionService completionService = new ExecutorCompletionService<>(threadPool); int counter = 0; final AtomicBoolean prepared = new AtomicBoolean(false); @@ -270,12 +238,9 @@ public boolean loadOrDoWork(final StorableProperties properties, final boolean c /** * This method creates the landmark storages ready for landmark creation. */ - public void createPreparations(GraphHopperStorage ghStorage, LocationIndex locationIndex) { - if (!isEnabled() || !preparations.isEmpty()) - return; - if (lmConfigs.isEmpty()) - throw new IllegalStateException("No landmark weightings found"); - + void createPreparations(List lmConfigs, GraphHopperStorage ghStorage, LocationIndex locationIndex) { + if (!preparations.isEmpty()) + throw new IllegalStateException("LM preparations were created already"); LOGGER.info("Creating LM preparations, {}", getMemInfo()); List lmSuggestions = new ArrayList<>(lmSuggestionsLocations.size()); if (!lmSuggestionsLocations.isEmpty()) { @@ -289,10 +254,10 @@ public void createPreparations(GraphHopperStorage ghStorage, LocationIndex locat } // ORS-GH MOD START abstract to a method in order to facilitate overriding - createPreparationsInternal(ghStorage, lmSuggestions); + createPreparationsInternal(ghStorage, lmConfigs, lmSuggestions); } - protected void createPreparationsInternal(GraphHopperStorage ghStorage, List lmSuggestions) { + protected void createPreparationsInternal(GraphHopperStorage ghStorage, List lmConfigs, List lmSuggestions) { // ORS-GH MOD END for (LMConfig lmConfig : lmConfigs) { Double maximumWeight = maximumWeights.get(lmConfig.getName()); @@ -300,25 +265,36 @@ protected void createPreparationsInternal(GraphHopperStorage ghStorage, List 1) - tmpPrepareLM.setMinimumNodes(minNodes); - addPreparation(tmpPrepareLM); + prepareLandmarks.setMinimumNodes(minNodes); + preparations.add(prepareLandmarks); } } + // ORS-GH MOD START for tests + public void createPreparations(List lmConfigs, GraphHopperStorage ghStorage) { + createPreparations(lmConfigs, ghStorage, null); + } + // ORS-GH MOD END + private JsonFeatureCollection loadLandmarkSplittingFeatureCollection(String splitAreaLocation) { ObjectMapper objectMapper = new ObjectMapper(); objectMapper.registerModule(new JtsModule()); + URL builtinSplittingFile = LandmarkStorage.class.getResource("map.geo.json"); try (Reader reader = splitAreaLocation.isEmpty() ? - new InputStreamReader(LandmarkStorage.class.getResource("map.geo.json").openStream(), UTF_CS) : + new InputStreamReader(builtinSplittingFile.openStream(), UTF_CS) : new InputStreamReader(new FileInputStream(splitAreaLocation), UTF_CS)) { JsonFeatureCollection result = objectMapper.readValue(reader, JsonFeatureCollection.class); - LOGGER.info("Loaded landmark splitting collection from " + splitAreaLocation); + if (splitAreaLocation.isEmpty()) { + LOGGER.info("Loaded built-in landmark splitting collection from {}", builtinSplittingFile); + } else { + LOGGER.info("Loaded landmark splitting collection from {}", splitAreaLocation); + } return result; } catch (IOException e) { LOGGER.error("Problem while reading border map GeoJSON. Skipping this.", e); @@ -327,10 +303,6 @@ private JsonFeatureCollection loadLandmarkSplittingFeatureCollection(String spli } // ORS-GH MOD START add methods - public List getWeightings() { - return lmConfigs.stream().map(lmConfig -> lmConfig.getWeighting()).collect(Collectors.toList()); - } - public Map getMaximumWeights() { return maximumWeights; } diff --git a/core/src/main/java/com/graphhopper/routing/lm/LandmarkSuggestion.java b/core/src/main/java/com/graphhopper/routing/lm/LandmarkSuggestion.java index 9fe0d1f2ea2..1f48738e82e 100644 --- a/core/src/main/java/com/graphhopper/routing/lm/LandmarkSuggestion.java +++ b/core/src/main/java/com/graphhopper/routing/lm/LandmarkSuggestion.java @@ -16,8 +16,8 @@ * suboptimal automatic landmark finding process. */ public class LandmarkSuggestion { - private List nodeIds; - private BBox box; + private final List nodeIds; + private final BBox box; public LandmarkSuggestion(List nodeIds, BBox box) { this.nodeIds = nodeIds; @@ -36,10 +36,10 @@ public BBox getBox() { * The expected format is lon,lat per line where lines starting with characters will be ignored. You can create * such a file manually via geojson.io -> Save as CSV. Optionally add a second line with *

#BBOX:minLat,minLon,maxLat,maxLon
- * + *

* to specify an explicit bounding box. TODO: support GeoJSON instead. */ - public static final LandmarkSuggestion readLandmarks(String file, LocationIndex locationIndex) throws IOException { + public static LandmarkSuggestion readLandmarks(String file, LocationIndex locationIndex) throws IOException { // landmarks should be suited for all vehicles EdgeFilter edgeFilter = EdgeFilter.ALL_EDGES; List lines = Helper.readFile(file); diff --git a/core/src/main/java/com/graphhopper/routing/subnetwork/SubnetworkStorage.java b/core/src/main/java/com/graphhopper/routing/subnetwork/SubnetworkStorage.java index 18e107f6e6a..703bba5d36e 100644 --- a/core/src/main/java/com/graphhopper/routing/subnetwork/SubnetworkStorage.java +++ b/core/src/main/java/com/graphhopper/routing/subnetwork/SubnetworkStorage.java @@ -32,7 +32,7 @@ public class SubnetworkStorage { public SubnetworkStorage(Directory dir, String postfix) { DAType type = dir.getDefaultType(); - da = dir.find("subnetwork_" + postfix, type.isMMap() ? DAType.MMAP : (type.isStoring() ? DAType.RAM_STORE : DAType.RAM)); + da = dir.create("subnetwork_" + postfix, type.isMMap() ? DAType.MMAP : (type.isStoring() ? DAType.RAM_STORE : DAType.RAM)); } /** diff --git a/core/src/main/java/com/graphhopper/routing/util/AbstractFlagEncoder.java b/core/src/main/java/com/graphhopper/routing/util/AbstractFlagEncoder.java index 3aefa959849..c097d3bd135 100644 --- a/core/src/main/java/com/graphhopper/routing/util/AbstractFlagEncoder.java +++ b/core/src/main/java/com/graphhopper/routing/util/AbstractFlagEncoder.java @@ -58,7 +58,6 @@ public abstract class AbstractFlagEncoder implements FlagEncoder { protected final int speedBits; protected final double speedFactor; private final int maxTurnCosts; - private long encoderBit; protected BooleanEncodedValue accessEnc; protected BooleanEncodedValue roundaboutEnc; protected DecimalEncodedValue avgSpeedEnc; @@ -153,11 +152,10 @@ public void setConditionalSpeedInspector(ConditionalSpeedInspector conditionalSp /** * Defines bits used for edge flags used for access, speed etc. */ - public void createEncodedValues(List registerNewEncodedValue, String prefix, int index) { + public void createEncodedValues(List registerNewEncodedValue, String prefix) { // define the first 2 bits in flags for access registerNewEncodedValue.add(accessEnc = new SimpleBooleanEncodedValue(EncodingManager.getKey(prefix, "access"), true)); roundaboutEnc = getBooleanEncodedValue(Roundabout.KEY); - encoderBit = 1L << index; } /** @@ -179,38 +177,30 @@ public int getMaxTurnCosts() { public abstract EncodingManager.Access getAccess(ReaderWay way); /** - * Parse tags on nodes. Node tags can add to speed (like traffic_signals) where the value is - * strict negative or blocks access (like a barrier), then the value is strictly positive. This - * method is called in the second parsing step. - * - * @return encoded values or 0 if not blocking or no value stored + * @return true if the given OSM node blocks access for this vehicle, false otherwise */ - public long handleNodeTags(ReaderNode node) { + public boolean isBarrier(ReaderNode node) { boolean blockByDefault = node.hasTag("barrier", blockByDefaultBarriers); if (blockByDefault || node.hasTag("barrier", passByDefaultBarriers)) { - boolean locked = false; - if (node.hasTag("locked", "yes")) - locked = true; + boolean locked = node.hasTag("locked", "yes"); for (String res : restrictions) { if (!locked && node.hasTag(res, intendedValues)) - return 0; + return false; if (node.hasTag(res, restrictedValues)) - return encoderBit; + return true; } - if (blockByDefault) - return encoderBit; - return 0; + return blockByDefault; } if ((node.hasTag("highway", "ford") || node.hasTag("ford", "yes")) && (blockFords && !node.hasTag(restrictions, intendedValues) || node.hasTag(restrictions, restrictedValues))) { - return encoderBit; + return true; } - return 0; + return false; } @Override @@ -241,25 +231,6 @@ protected boolean isValidSpeed(double speed) { return !Double.isNaN(speed); } - @Override - public int hashCode() { - int hash = 7; - hash = 61 * hash + this.accessEnc.hashCode(); - hash = 61 * hash + this.toString().hashCode(); - return hash; - } - - @Override - public boolean equals(Object obj) { - if (obj == null) - return false; - - if (getClass() != obj.getClass()) - return false; - AbstractFlagEncoder afe = (AbstractFlagEncoder) obj; - return toString().equals(afe.toString()) && encoderBit == afe.encoderBit && accessEnc.equals(afe.accessEnc); - } - /** * Second parsing step. Invoked after splitting the edges. Currently used to offer a hook to * calculate precise speed values based on elevation data stored in the specified edge. @@ -372,12 +343,6 @@ protected String getPropertiesString() { return "speed_factor=" + speedFactor + "|speed_bits=" + speedBits + "|turn_costs=" + (maxTurnCosts > 0); } - // ORS-GH MOD START - additional method for overriding handleNodeTags() - protected long getEncoderBit() { - return this.encoderBit; - } - // ORS-GH MOD END - @Override public List getEncodedValues() { return encodedValueLookup.getEncodedValues(); diff --git a/core/src/main/java/com/graphhopper/routing/util/BikeCommonFlagEncoder.java b/core/src/main/java/com/graphhopper/routing/util/BikeCommonFlagEncoder.java index c1c90a29954..e528c51a657 100644 --- a/core/src/main/java/com/graphhopper/routing/util/BikeCommonFlagEncoder.java +++ b/core/src/main/java/com/graphhopper/routing/util/BikeCommonFlagEncoder.java @@ -194,9 +194,9 @@ public TransportationMode getTransportationMode() { } @Override - public void createEncodedValues(List registerNewEncodedValue, String prefix, int index) { + public void createEncodedValues(List registerNewEncodedValue, String prefix) { // first two bits are reserved for route handling in superclass - super.createEncodedValues(registerNewEncodedValue, prefix, index); + super.createEncodedValues(registerNewEncodedValue, prefix); registerNewEncodedValue.add(avgSpeedEnc = new UnsignedDecimalEncodedValue(getKey(prefix, "average_speed"), speedBits, speedFactor, speedTwoDirections)); registerNewEncodedValue.add(priorityEnc = new UnsignedDecimalEncodedValue(getKey(prefix, "priority"), 4, PriorityCode.getFactor(1), false)); registerNewEncodedValue.add(conditionalEncoder = new SimpleBooleanEncodedValue(getKey(prefix, ConditionalEdges.ACCESS), false)); diff --git a/core/src/main/java/com/graphhopper/routing/util/CarFlagEncoder.java b/core/src/main/java/com/graphhopper/routing/util/CarFlagEncoder.java index 5b6f92027a3..57c25b26706 100644 --- a/core/src/main/java/com/graphhopper/routing/util/CarFlagEncoder.java +++ b/core/src/main/java/com/graphhopper/routing/util/CarFlagEncoder.java @@ -180,9 +180,9 @@ protected void init(DateRangeParser dateRangeParser) { * Define the place of the speedBits in the edge flags for car. */ @Override - public void createEncodedValues(List registerNewEncodedValue, String prefix, int index) { + public void createEncodedValues(List registerNewEncodedValue, String prefix) { // first two bits are reserved for route handling in superclass - super.createEncodedValues(registerNewEncodedValue, prefix, index); + super.createEncodedValues(registerNewEncodedValue, prefix); registerNewEncodedValue.add(avgSpeedEnc = new UnsignedDecimalEncodedValue(EncodingManager.getKey(prefix, "average_speed"), speedBits, speedFactor, speedTwoDirections)); // ORS-GH MOD START - additional encoders for conditionals registerNewEncodedValue.add(conditionalEncoder = new SimpleBooleanEncodedValue(EncodingManager.getKey(prefix, ConditionalEdges.ACCESS), false)); diff --git a/core/src/main/java/com/graphhopper/routing/util/EncodingManager.java b/core/src/main/java/com/graphhopper/routing/util/EncodingManager.java index f94334f6193..8593fd2b470 100644 --- a/core/src/main/java/com/graphhopper/routing/util/EncodingManager.java +++ b/core/src/main/java/com/graphhopper/routing/util/EncodingManager.java @@ -423,19 +423,17 @@ private void setPreferredLanguage(String preferredLanguage) { } private void addEncoder(AbstractFlagEncoder encoder) { - int encoderCount = edgeEncoders.size(); - encoder.setEncodedValueLookup(this); List list = new ArrayList<>(); - encoder.createEncodedValues(list, encoder.toString(), encoderCount); - for (EncodedValue ev : list) { + encoder.createEncodedValues(list, encoder.toString()); + for (EncodedValue ev : list) addEncodedValue(ev, true); - } - edgeEncoders.add(encoder); } + // ORS-GH MOD START expose method public void addEncodedValue(EncodedValue ev, boolean withNamespace) { + // ORS-GH MOD END String normalizedKey = ev.getName().replaceAll(SPECIAL_SEPARATOR, "_"); if (hasEncodedValue(normalizedKey)) throw new IllegalStateException("EncodedValue " + ev.getName() + " collides with " + normalizedKey); @@ -680,15 +678,20 @@ public int hashCode() { } /** - * Analyze tags on osm node. Store node tags (barriers etc) for later usage while parsing way. + * Updates the given edge flags based on node tags */ - public long handleNodeTags(ReaderNode node) { - long flags = 0; + public IntsRef handleNodeTags(Map nodeTags, IntsRef edgeFlags) { for (AbstractFlagEncoder encoder : edgeEncoders) { - flags |= encoder.handleNodeTags(node); + // for now we just create a dummy reader node, because our encoders do not make use of the coordinates anyway + ReaderNode readerNode = new ReaderNode(0, 0, 0, nodeTags); + // block access for all encoders that treat this node as a barrier + if (encoder.isBarrier(readerNode)) { + BooleanEncodedValue accessEnc = encoder.getAccessEnc(); + accessEnc.setBool(false, edgeFlags, false); + accessEnc.setBool(true, edgeFlags, false); + } } - - return flags; + return edgeFlags; } public void applyWayTags(ReaderWay way, EdgeIteratorState edge) { @@ -750,16 +753,6 @@ public boolean hasConditionalSpeed() { } // ORS-GH MOD END - public List getAccessEncFromNodeFlags(long importNodeFlags) { - List list = new ArrayList<>(edgeEncoders.size()); - for (int i = 0; i < edgeEncoders.size(); i++) { - FlagEncoder encoder = edgeEncoders.get(i); - if (((1L << i) & importNodeFlags) != 0) - list.add(encoder.getAccessEnc()); - } - return list; - } - @Override public List getEncodedValues() { return Collections.unmodifiableList(new ArrayList<>(encodedValueMap.values())); @@ -869,4 +862,4 @@ private static boolean isNumber(char c) { private static boolean isLowerLetter(char c) { return c >= 'a' && c <= 'z'; } -} +} \ No newline at end of file diff --git a/core/src/main/java/com/graphhopper/routing/util/FootFlagEncoder.java b/core/src/main/java/com/graphhopper/routing/util/FootFlagEncoder.java index ae8edfa0654..232d46a7ea0 100644 --- a/core/src/main/java/com/graphhopper/routing/util/FootFlagEncoder.java +++ b/core/src/main/java/com/graphhopper/routing/util/FootFlagEncoder.java @@ -143,9 +143,9 @@ public TransportationMode getTransportationMode() { } @Override - public void createEncodedValues(List registerNewEncodedValue, String prefix, int index) { + public void createEncodedValues(List registerNewEncodedValue, String prefix) { // first two bits are reserved for route handling in superclass - super.createEncodedValues(registerNewEncodedValue, prefix, index); + super.createEncodedValues(registerNewEncodedValue, prefix); // larger value required - ferries are faster than pedestrians registerNewEncodedValue.add(avgSpeedEnc = new UnsignedDecimalEncodedValue(getKey(prefix, "average_speed"), speedBits, speedFactor, speedTwoDirections)); registerNewEncodedValue.add(priorityWayEncoder = new UnsignedDecimalEncodedValue(getKey(prefix, "priority"), 4, PriorityCode.getFactor(1), speedTwoDirections)); diff --git a/core/src/main/java/com/graphhopper/routing/util/HeadingEdgeFilter.java b/core/src/main/java/com/graphhopper/routing/util/HeadingEdgeFilter.java new file mode 100644 index 00000000000..74ff0aed256 --- /dev/null +++ b/core/src/main/java/com/graphhopper/routing/util/HeadingEdgeFilter.java @@ -0,0 +1,67 @@ +package com.graphhopper.routing.util; + +import com.graphhopper.util.*; +import com.graphhopper.util.shapes.GHPoint; + +public class HeadingEdgeFilter implements EdgeFilter { + + private final double heading; + private final EdgeFilter directedEdgeFilter; + private final GHPoint pointNearHeading; + + public HeadingEdgeFilter(EdgeFilter directedEdgeFilter, double heading, GHPoint pointNearHeading) { + this.directedEdgeFilter = directedEdgeFilter; + this.heading = heading; + this.pointNearHeading = pointNearHeading; + } + + @Override + public boolean accept(EdgeIteratorState edgeState) { + final double tolerance = 30; + // we only accept edges that are not too far away. It might happen that only far away edges match the heading + // in which case we rather rely on the fallback snapping than return a match here. + final double maxDistance = 20; + double headingOfEdge = getHeadingOfGeometryNearPoint(edgeState, pointNearHeading, maxDistance); + if (Double.isNaN(headingOfEdge)) + // this edge is too far away. we do not accept it. + return false; + // we accept the edge if either of the two directions roughly has the right heading + return Math.abs(headingOfEdge - heading) < tolerance && directedEdgeFilter.accept(edgeState) || + Math.abs((headingOfEdge + 180) % 360 - heading) < tolerance && directedEdgeFilter.accept(edgeState.detach(true)); + } + + /** + * Calculates the heading (in degrees) of the given edge in fwd direction near the given point. If the point is + * too far away from the edge (according to the maxDistance parameter) it returns Double.NaN. + */ + static double getHeadingOfGeometryNearPoint(EdgeIteratorState edgeState, GHPoint point, double maxDistance) { + final DistanceCalc calcDist = DistanceCalcEarth.DIST_EARTH; + double closestDistance = Double.POSITIVE_INFINITY; + PointList points = edgeState.fetchWayGeometry(FetchMode.ALL); + int closestPoint = -1; + for (int i = 1; i < points.size(); i++) { + double fromLat = points.getLat(i - 1), fromLon = points.getLon(i - 1); + double toLat = points.getLat(i), toLon = points.getLon(i); + // the 'distance' between the point and an edge segment is either the vertical distance to the segment or + // the distance to the closer one of the two endpoints. here we save one call to calcDist per segment, + // because each endpoint appears in two segments (except the first and last). + double distance = calcDist.validEdgeDistance(point.lat, point.lon, fromLat, fromLon, toLat, toLon) + ? calcDist.calcDenormalizedDist(calcDist.calcNormalizedEdgeDistance(point.lat, point.lon, fromLat, fromLon, toLat, toLon)) + : calcDist.calcDist(fromLat, fromLon, point.lat, point.lon); + if (i == points.size() - 1) + distance = Math.min(distance, calcDist.calcDist(toLat, toLon, point.lat, point.lon)); + if (distance > maxDistance) + continue; + if (distance < closestDistance) { + closestDistance = distance; + closestPoint = i; + } + } + if (closestPoint < 0) + return Double.NaN; + + double fromLat = points.getLat(closestPoint - 1), fromLon = points.getLon(closestPoint - 1); + double toLat = points.getLat(closestPoint), toLon = points.getLon(closestPoint); + return AngleCalc.ANGLE_CALC.calcAzimuth(fromLat, fromLon, toLat, toLon); + } +} diff --git a/core/src/main/java/com/graphhopper/routing/util/MotorcycleFlagEncoder.java b/core/src/main/java/com/graphhopper/routing/util/MotorcycleFlagEncoder.java index 79f062fb437..c1f7184a576 100644 --- a/core/src/main/java/com/graphhopper/routing/util/MotorcycleFlagEncoder.java +++ b/core/src/main/java/com/graphhopper/routing/util/MotorcycleFlagEncoder.java @@ -107,9 +107,9 @@ public MotorcycleFlagEncoder(PMap properties) { * Define the place of the speedBits in the edge flags for car. */ @Override - public void createEncodedValues(List registerNewEncodedValue, String prefix, int index) { + public void createEncodedValues(List registerNewEncodedValue, String prefix) { // first two bits are reserved for route handling in superclass - super.createEncodedValues(registerNewEncodedValue, prefix, index); + super.createEncodedValues(registerNewEncodedValue, prefix); registerNewEncodedValue.add(priorityWayEncoder = new UnsignedDecimalEncodedValue(getKey(prefix, "priority"), 4, PriorityCode.getFactor(1), false)); registerNewEncodedValue.add(curvatureEncoder = new UnsignedDecimalEncodedValue(getKey(prefix, "curvature"), 4, 0.1, false)); diff --git a/core/src/main/java/com/graphhopper/routing/util/countryrules/AustriaCountryRule.java b/core/src/main/java/com/graphhopper/routing/util/countryrules/AustriaCountryRule.java index ae51cfb1970..64b0a73e87f 100644 --- a/core/src/main/java/com/graphhopper/routing/util/countryrules/AustriaCountryRule.java +++ b/core/src/main/java/com/graphhopper/routing/util/countryrules/AustriaCountryRule.java @@ -21,10 +21,10 @@ import com.graphhopper.reader.ReaderWay; import com.graphhopper.routing.ev.RoadAccess; import com.graphhopper.routing.ev.RoadClass; +import com.graphhopper.routing.ev.Toll; import com.graphhopper.routing.util.TransportationMode; public class AustriaCountryRule implements CountryRule { - public final static AustriaCountryRule RULE = new AustriaCountryRule(); @Override public double getMaxSpeed(ReaderWay readerWay, TransportationMode transportationMode, double currentMaxSpeed) { @@ -72,4 +72,18 @@ public RoadAccess getAccess(ReaderWay readerWay, TransportationMode transportati return RoadAccess.YES; } } + + @Override + public Toll getToll(ReaderWay readerWay, TransportationMode transportationMode, Toll currentToll) { + if (!transportationMode.isMotorVehicle() || currentToll != Toll.MISSING) { + return currentToll; + } + + RoadClass roadClass = RoadClass.find(readerWay.getTag("highway", "")); + if (roadClass == RoadClass.MOTORWAY || roadClass == RoadClass.TRUNK) { + return Toll.ALL; + } + + return currentToll; + } } diff --git a/core/src/main/java/com/graphhopper/routing/util/countryrules/CountryRule.java b/core/src/main/java/com/graphhopper/routing/util/countryrules/CountryRule.java index 447e2f51d2e..cf67f15761a 100644 --- a/core/src/main/java/com/graphhopper/routing/util/countryrules/CountryRule.java +++ b/core/src/main/java/com/graphhopper/routing/util/countryrules/CountryRule.java @@ -19,9 +19,8 @@ package com.graphhopper.routing.util.countryrules; import com.graphhopper.reader.ReaderWay; -import com.graphhopper.routing.ev.Country; import com.graphhopper.routing.ev.RoadAccess; -import com.graphhopper.routing.ev.RoadClass; +import com.graphhopper.routing.ev.Toll; import com.graphhopper.routing.util.TransportationMode; /** @@ -35,4 +34,8 @@ default double getMaxSpeed(ReaderWay readerWay, TransportationMode transportatio default RoadAccess getAccess(ReaderWay readerWay, TransportationMode transportationMode, RoadAccess currentRoadAccess) { return currentRoadAccess; } + + default Toll getToll(ReaderWay readerWay, TransportationMode transportationMode, Toll currentToll) { + return currentToll; + } } diff --git a/core/src/main/java/com/graphhopper/routing/util/countryrules/CountryRuleFactory.java b/core/src/main/java/com/graphhopper/routing/util/countryrules/CountryRuleFactory.java index 2ccd8a2ccbc..7a68a6dbfd1 100644 --- a/core/src/main/java/com/graphhopper/routing/util/countryrules/CountryRuleFactory.java +++ b/core/src/main/java/com/graphhopper/routing/util/countryrules/CountryRuleFactory.java @@ -18,18 +18,28 @@ package com.graphhopper.routing.util.countryrules; + +import static com.graphhopper.routing.ev.Country.*; + +import java.util.EnumMap; +import java.util.Map; + import com.graphhopper.routing.ev.Country; public class CountryRuleFactory { + + private final Map rules = new EnumMap<>(Country.class); + + public CountryRuleFactory() { + rules.put(AUT, new AustriaCountryRule()); + rules.put(DEU, new GermanyCountryRule()); + } public CountryRule getCountryRule(Country country) { - switch (country) { - case DEU: - return GermanyCountryRule.RULE; - case AUT: - return AustriaCountryRule.RULE; - default: - return null; - } + return rules.get(country); + } + + public Map getCountryToRuleMap() { + return rules; } } diff --git a/core/src/main/java/com/graphhopper/routing/util/countryrules/GermanyCountryRule.java b/core/src/main/java/com/graphhopper/routing/util/countryrules/GermanyCountryRule.java index 8c4adcee1e4..49a9d01aa39 100644 --- a/core/src/main/java/com/graphhopper/routing/util/countryrules/GermanyCountryRule.java +++ b/core/src/main/java/com/graphhopper/routing/util/countryrules/GermanyCountryRule.java @@ -22,13 +22,13 @@ import com.graphhopper.routing.ev.MaxSpeed; import com.graphhopper.routing.ev.RoadAccess; import com.graphhopper.routing.ev.RoadClass; +import com.graphhopper.routing.ev.Toll; import com.graphhopper.routing.util.TransportationMode; /** * @author Robin Boldt */ public class GermanyCountryRule implements CountryRule { - public final static GermanyCountryRule RULE = new GermanyCountryRule(); /** * In Germany there are roads without a speed limit. For these roads, this method @@ -80,4 +80,18 @@ public RoadAccess getAccess(ReaderWay readerWay, TransportationMode transportati return RoadAccess.YES; } } + + @Override + public Toll getToll(ReaderWay readerWay, TransportationMode transportationMode, Toll currentToll) { + if (!transportationMode.isMotorVehicle() || currentToll != Toll.MISSING) { + return currentToll; + } + + RoadClass roadClass = RoadClass.find(readerWay.getTag("highway", "")); + if (roadClass == RoadClass.MOTORWAY || roadClass == RoadClass.TRUNK || roadClass == RoadClass.PRIMARY) { + return Toll.HGV; + } + + return currentToll; + } } diff --git a/core/src/main/java/com/graphhopper/routing/util/parsers/OSMRoadAccessParser.java b/core/src/main/java/com/graphhopper/routing/util/parsers/OSMRoadAccessParser.java index 41e83851dd3..b98e6660070 100644 --- a/core/src/main/java/com/graphhopper/routing/util/parsers/OSMRoadAccessParser.java +++ b/core/src/main/java/com/graphhopper/routing/util/parsers/OSMRoadAccessParser.java @@ -59,7 +59,7 @@ public IntsRef handleWayTags(IntsRef edgeFlags, ReaderWay readerWay, boolean fer CountryRule countryRule = readerWay.getTag("country_rule", null); if (countryRule != null) - accessValue = countryRule.getAccess(readerWay, TransportationMode.CAR, YES); + accessValue = countryRule.getAccess(readerWay, TransportationMode.CAR, accessValue); roadAccessEnc.setEnum(false, edgeFlags, accessValue); return edgeFlags; diff --git a/core/src/main/java/com/graphhopper/routing/util/parsers/OSMTollParser.java b/core/src/main/java/com/graphhopper/routing/util/parsers/OSMTollParser.java index c9c566b94b6..ec26822358e 100644 --- a/core/src/main/java/com/graphhopper/routing/util/parsers/OSMTollParser.java +++ b/core/src/main/java/com/graphhopper/routing/util/parsers/OSMTollParser.java @@ -22,12 +22,17 @@ import com.graphhopper.routing.ev.EncodedValueLookup; import com.graphhopper.routing.ev.EnumEncodedValue; import com.graphhopper.routing.ev.Toll; +import com.graphhopper.routing.util.TransportationMode; +import com.graphhopper.routing.util.countryrules.CountryRule; import com.graphhopper.storage.IntsRef; +import java.util.Arrays; +import java.util.Collections; import java.util.List; public class OSMTollParser implements TagParser { + private static final List HGV_TAGS = Collections.unmodifiableList(Arrays.asList("toll:hgv", "toll:N2", "toll:N3")); private final EnumEncodedValue tollEnc; public OSMTollParser() { @@ -45,14 +50,23 @@ public void createEncodedValues(EncodedValueLookup lookup, List li @Override public IntsRef handleWayTags(IntsRef edgeFlags, ReaderWay readerWay, boolean ferry, IntsRef relationFlags) { - if (readerWay.hasTag("toll", "yes")) - tollEnc.setEnum(false, edgeFlags, Toll.ALL); - else if (readerWay.hasTag("toll:hgv", "yes")) - tollEnc.setEnum(false, edgeFlags, Toll.HGV); - else if (readerWay.hasTag("toll:N2", "yes")) - tollEnc.setEnum(false, edgeFlags, Toll.HGV); - else if (readerWay.hasTag("toll:N3", "yes")) - tollEnc.setEnum(false, edgeFlags, Toll.HGV); + Toll toll; + if (readerWay.hasTag("toll", "yes")) { + toll = Toll.ALL; + } else if (readerWay.hasTag(HGV_TAGS, Collections.singletonList("yes"))) { + toll = Toll.HGV; + } else if (readerWay.hasTag("toll", "no")) { + toll = Toll.NO; + } else { + toll = Toll.MISSING; + } + + CountryRule countryRule = readerWay.getTag("country_rule", null); + if (countryRule != null) + toll = countryRule.getToll(readerWay, TransportationMode.CAR, toll); + + tollEnc.setEnum(false, edgeFlags, toll); + return edgeFlags; } } diff --git a/core/src/main/java/com/graphhopper/routing/util/parsers/TurnCostParser.java b/core/src/main/java/com/graphhopper/routing/util/parsers/TurnCostParser.java index b39e947548d..cd9e4035f1b 100644 --- a/core/src/main/java/com/graphhopper/routing/util/parsers/TurnCostParser.java +++ b/core/src/main/java/com/graphhopper/routing/util/parsers/TurnCostParser.java @@ -37,7 +37,7 @@ public interface TurnCostParser { /** * This map associates the internal GraphHopper nodes IDs with external IDs (OSM) and similarly for the edge IDs - * required to write the turn costs. + * required to write the turn costs. Returns -1 if there is no entry for the given OSM ID. */ interface ExternalInternalMap { int getInternalNodeIdOfOsmNode(long nodeOsmId); diff --git a/core/src/main/java/com/graphhopper/search/NameIndex.java b/core/src/main/java/com/graphhopper/search/NameIndex.java index 165afdf42f8..9489571f6d8 100644 --- a/core/src/main/java/com/graphhopper/search/NameIndex.java +++ b/core/src/main/java/com/graphhopper/search/NameIndex.java @@ -43,7 +43,7 @@ public NameIndex(Directory dir) { } protected NameIndex(Directory dir, String filename) { - names = dir.find(filename); + names = dir.create(filename); } public NameIndex create(long initBytes) { diff --git a/core/src/main/java/com/graphhopper/search/StringIndex.java b/core/src/main/java/com/graphhopper/search/StringIndex.java index 6e9c71263b4..1b5a720285d 100644 --- a/core/src/main/java/com/graphhopper/search/StringIndex.java +++ b/core/src/main/java/com/graphhopper/search/StringIndex.java @@ -20,6 +20,8 @@ import com.graphhopper.storage.DataAccess; import com.graphhopper.storage.Directory; import com.graphhopper.util.BitUtil; +import com.graphhopper.util.Constants; +import com.graphhopper.util.GHUtility; import com.graphhopper.util.Helper; import java.util.*; @@ -77,7 +79,11 @@ public boolean loadExisting() { if (vals.loadExisting()) { if (!keys.loadExisting()) throw new IllegalStateException("Loaded values but cannot load keys"); - bytePointer = BitUtil.LITTLE.combineIntsToLong(vals.getHeader(0), vals.getHeader(4)); + int stringIndexKeysVersion = keys.getHeader(0); + int stringIndexValsVersion = vals.getHeader(0); + GHUtility.checkDAVersion(keys.getName(), Constants.VERSION_STRING_IDX, stringIndexKeysVersion); + GHUtility.checkDAVersion(vals.getName(), Constants.VERSION_STRING_IDX, stringIndexValsVersion); + bytePointer = BitUtil.LITTLE.combineIntsToLong(vals.getHeader(4), vals.getHeader(8)); // load keys into memory int count = keys.getShort(0); @@ -294,6 +300,7 @@ private byte[] getBytesForString(String info, String name) { } public void flush() { + keys.setHeader(0, Constants.VERSION_STRING_IDX); keys.ensureCapacity(2); keys.setShort(0, (short) keysInMem.size()); long keyBytePointer = 2; @@ -308,8 +315,9 @@ public void flush() { } keys.flush(); - vals.setHeader(0, BitUtil.LITTLE.getIntLow(bytePointer)); - vals.setHeader(4, BitUtil.LITTLE.getIntHigh(bytePointer)); + vals.setHeader(0, Constants.VERSION_STRING_IDX); + vals.setHeader(4, BitUtil.LITTLE.getIntLow(bytePointer)); + vals.setHeader(8, BitUtil.LITTLE.getIntHigh(bytePointer)); vals.flush(); } diff --git a/core/src/main/java/com/graphhopper/storage/BaseGraph.java b/core/src/main/java/com/graphhopper/storage/BaseGraph.java index 9658aaefe7e..0e5be09708c 100644 --- a/core/src/main/java/com/graphhopper/storage/BaseGraph.java +++ b/core/src/main/java/com/graphhopper/storage/BaseGraph.java @@ -26,7 +26,6 @@ import com.graphhopper.util.shapes.BBox; import java.util.Collections; -import java.util.Locale; import static com.graphhopper.util.Helper.nf; @@ -42,110 +41,40 @@ * loadExisting, (4) usage, (5) flush, (6) close */ class BaseGraph implements Graph { - // Currently distances are stored as 4 byte integers. using a conversion factor of 1000 the minimum distance - // that is not considered zero is 0.0005m (=0.5mm) and the maximum distance per edge is about 2.147.483m=2147km. - // See OSMReader.addEdge and #1871. - private static final double INT_DIST_FACTOR = 1000d; - static double MAX_DIST = Integer.MAX_VALUE / INT_DIST_FACTOR; - - final DataAccess edges; - final DataAccess nodes; - final BBox bounds; - final NodeAccess nodeAccess; private final static String STRING_IDX_NAME_KEY = "name"; + final BaseGraphNodesAndEdges store; + final NodeAccess nodeAccess; final StringIndex stringIndex; // can be null if turn costs are not supported final TurnCostStorage turnCostStorage; final BitUtil bitUtil; - private final int intsForFlags; // length | nodeA | nextNode | ... | nodeB - // as we use integer index in 'egdes' area => 'geometry' area is limited to 4GB (we use pos&neg values!) + // as we use integer index in 'edges' area => 'geometry' area is limited to 4GB (we use pos&neg values!) private final DataAccess wayGeometry; private final Directory dir; - /** - * interval [0,n) - */ - protected int edgeCount; - // node memory layout: - protected int N_EDGE_REF, N_LAT, N_LON, N_ELE, N_TC; - // edge memory layout: - int E_NODEA, E_NODEB, E_LINKA, E_LINKB, E_FLAGS, E_DIST, E_GEO, E_NAME; - /** - * Specifies how many entries (integers) are used per edge. - */ - int edgeEntryBytes; - /** - * Specifies how many entries (integers) are used per node - */ - int nodeEntryBytes; private boolean initialized = false; - /** - * interval [0,n) - */ - private int nodeCount; - private int edgeEntryIndex, nodeEntryIndex; private long maxGeoRef; - private boolean frozen = false; public BaseGraph(Directory dir, int intsForFlags, boolean withElevation, boolean withTurnCosts, int segmentSize) { this.dir = dir; - this.intsForFlags = intsForFlags; this.bitUtil = BitUtil.LITTLE; this.wayGeometry = dir.create("geometry", segmentSize); this.stringIndex = new StringIndex(dir, 1000, segmentSize); - this.nodes = dir.create("nodes", dir.getDefaultType("nodes", true), segmentSize); - this.edges = dir.create("edges", dir.getDefaultType("edges", true), segmentSize); - this.bounds = BBox.createInverse(withElevation); - this.nodeAccess = new GHNodeAccess(this, withElevation); + this.store = new BaseGraphNodesAndEdges(dir, intsForFlags, withElevation, withTurnCosts, segmentSize); + this.nodeAccess = new GHNodeAccess(store); this.turnCostStorage = withTurnCosts ? new TurnCostStorage(this, dir.create("turn_costs", dir.getDefaultType("turn_costs", true), segmentSize)) : null; if (segmentSize >= 0) { checkNotInitialized(); } } - private void setEdgeRef(int nodeId, int edgeId) { - nodes.setInt(toNodePointer(nodeId) + N_EDGE_REF, edgeId); - } - - int getEdgeRef(int nodeId) { - return nodes.getInt(toNodePointer(nodeId) + N_EDGE_REF); - } - - private int getNodeA(long edgePointer) { - return edges.getInt(edgePointer + E_NODEA); - } - - private int getNodeB(long edgePointer) { - return edges.getInt(edgePointer + E_NODEB); - } - - private int getLinkA(long edgePointer) { - return edges.getInt(edgePointer + E_LINKA); - } - - private int getLinkB(long edgePointer) { - return edges.getInt(edgePointer + E_LINKB); - } - - long toNodePointer(int node) { - if (node < 0 || node >= nodeCount) - throw new IllegalArgumentException("node: " + node + " out of bounds [0," + nodeCount + "["); - return (long) node * nodeEntryBytes; - } - - private long toEdgePointer(int edge) { - if (edge < 0 || edge >= edgeCount) - throw new IllegalArgumentException("edge: " + edge + " out of bounds [0," + edgeCount + "["); - return (long) edge * edgeEntryBytes; - } - private int getOtherNode(int nodeThis, long edgePointer) { - int nodeA = getNodeA(edgePointer); - return nodeThis == nodeA ? getNodeB(edgePointer) : nodeA; + int nodeA = store.getNodeA(edgePointer); + return nodeThis == nodeA ? store.getNodeB(edgePointer) : nodeA; } private boolean isAdjacentToNode(int node, long edgePointer) { - return getNodeA(edgePointer) == node || getNodeB(edgePointer) == node; + return store.getNodeA(edgePointer) == node || store.getNodeB(edgePointer) == node; } private static boolean isTestingEnabled() { @@ -165,90 +94,22 @@ void checkNotInitialized() { + "after calling create or loadExisting. Calling one of the methods twice is also not allowed."); } - void checkInitialized() { - if (!initialized) - throw new IllegalStateException("The graph has not yet been initialized."); - } - - private void loadNodesHeader() { - nodeEntryBytes = nodes.getHeader(0 * 4); - nodeCount = nodes.getHeader(1 * 4); - bounds.minLon = Helper.intToDegree(nodes.getHeader(2 * 4)); - bounds.maxLon = Helper.intToDegree(nodes.getHeader(3 * 4)); - bounds.minLat = Helper.intToDegree(nodes.getHeader(4 * 4)); - bounds.maxLat = Helper.intToDegree(nodes.getHeader(5 * 4)); - - if (bounds.hasElevation()) { - bounds.minEle = Helper.intToEle(nodes.getHeader(6 * 4)); - bounds.maxEle = Helper.intToEle(nodes.getHeader(7 * 4)); - } - - frozen = nodes.getHeader(8 * 4) == 1; - } - - private void setNodesHeader() { - nodes.setHeader(0 * 4, nodeEntryBytes); - nodes.setHeader(1 * 4, nodeCount); - nodes.setHeader(2 * 4, Helper.degreeToInt(bounds.minLon)); - nodes.setHeader(3 * 4, Helper.degreeToInt(bounds.maxLon)); - nodes.setHeader(4 * 4, Helper.degreeToInt(bounds.minLat)); - nodes.setHeader(5 * 4, Helper.degreeToInt(bounds.maxLat)); - if (bounds.hasElevation()) { - nodes.setHeader(6 * 4, Helper.eleToInt(bounds.minEle)); - nodes.setHeader(7 * 4, Helper.eleToInt(bounds.maxEle)); - } - - nodes.setHeader(8 * 4, isFrozen() ? 1 : 0); - } - - protected void loadEdgesHeader() { - edgeEntryBytes = edges.getHeader(0 * 4); - edgeCount = edges.getHeader(1 * 4); - } - - protected void setEdgesHeader() { - edges.setHeader(0, edgeEntryBytes); - edges.setHeader(1 * 4, edgeCount); - } - - protected int loadWayGeometryHeader() { - maxGeoRef = bitUtil.combineIntsToLong(wayGeometry.getHeader(0), wayGeometry.getHeader(4)); - return 1; + private void loadWayGeometryHeader() { + int geometryVersion = wayGeometry.getHeader(0); + GHUtility.checkDAVersion(wayGeometry.getName(), Constants.VERSION_GEOMETRY, geometryVersion); + maxGeoRef = bitUtil.combineIntsToLong( + wayGeometry.getHeader(4), + wayGeometry.getHeader(8) + ); } - protected int setWayGeometryHeader() { - wayGeometry.setHeader(0, bitUtil.getIntLow(maxGeoRef)); - wayGeometry.setHeader(4, bitUtil.getIntHigh(maxGeoRef)); - return 1; + private void setWayGeometryHeader() { + wayGeometry.setHeader(0, Constants.VERSION_GEOMETRY); + wayGeometry.setHeader(4, bitUtil.getIntLow(maxGeoRef)); + wayGeometry.setHeader(8, bitUtil.getIntHigh(maxGeoRef)); } - void initStorage() { - edgeEntryIndex = 0; - nodeEntryIndex = 0; - E_NODEA = nextEdgeEntryIndex(4); - E_NODEB = nextEdgeEntryIndex(4); - E_LINKA = nextEdgeEntryIndex(4); - E_LINKB = nextEdgeEntryIndex(4); - E_FLAGS = nextEdgeEntryIndex(intsForFlags * 4); - - E_DIST = nextEdgeEntryIndex(4); - E_GEO = nextEdgeEntryIndex(4); - E_NAME = nextEdgeEntryIndex(4); - - N_EDGE_REF = nextNodeEntryIndex(4); - N_LAT = nextNodeEntryIndex(4); - N_LON = nextNodeEntryIndex(4); - if (nodeAccess.is3D()) - N_ELE = nextNodeEntryIndex(4); - else - N_ELE = -1; - - if (supportsTurnCosts()) - N_TC = nextNodeEntryIndex(4); - else - N_TC = -1; - - initNodeAndEdgeEntrySize(); + private void setInitialized() { initialized = true; } @@ -256,64 +117,14 @@ boolean supportsTurnCosts() { return turnCostStorage != null; } - /** - * Initializes the node storage such that each node has no edge and no turn cost entry - */ - void initNodeRefs(long oldCapacity, long newCapacity) { - for (long pointer = oldCapacity + N_EDGE_REF; pointer < newCapacity; pointer += nodeEntryBytes) { - nodes.setInt(pointer, EdgeIterator.NO_EDGE); - } - if (supportsTurnCosts()) { - for (long pointer = oldCapacity + N_TC; pointer < newCapacity; pointer += nodeEntryBytes) { - nodes.setInt(pointer, TurnCostStorage.NO_TURN_ENTRY); - } - } - } - - protected final int nextEdgeEntryIndex(int sizeInBytes) { - int tmp = edgeEntryIndex; - edgeEntryIndex += sizeInBytes; - return tmp; - } - - protected final int nextNodeEntryIndex(int sizeInBytes) { - int tmp = nodeEntryIndex; - nodeEntryIndex += sizeInBytes; - return tmp; - } - - protected final void initNodeAndEdgeEntrySize() { - nodeEntryBytes = nodeEntryIndex; - edgeEntryBytes = edgeEntryIndex; - } - - /** - * Check if byte capacity of DataAcess nodes object is sufficient to include node index, else - * extend byte capacity - */ - final void ensureNodeIndex(int nodeIndex) { - checkInitialized(); - - if (nodeIndex < nodeCount) - return; - - long oldNodes = nodeCount; - nodeCount = nodeIndex + 1; - boolean capacityIncreased = nodes.ensureCapacity((long) nodeCount * nodeEntryBytes); - if (capacityIncreased) { - long newBytesCapacity = nodes.getCapacity(); - initNodeRefs(oldNodes * nodeEntryBytes, newBytesCapacity); - } - } - @Override public int getNodes() { - return nodeCount; + return store.getNodes(); } @Override public int getEdges() { - return edgeCount; + return store.getEdges(); } @Override @@ -323,23 +134,21 @@ public NodeAccess getNodeAccess() { @Override public BBox getBounds() { - return bounds; + return store.getBounds(); } synchronized void freeze() { if (isFrozen()) throw new IllegalStateException("base graph already frozen"); - - frozen = true; + store.setFrozen(true); } synchronized boolean isFrozen() { - return frozen; + return store.getFrozen(); } void create(long initSize) { - nodes.create(initSize); - edges.create(initSize); + store.create(initSize); initSize = Math.min(initSize, 2000); wayGeometry.create(initSize); @@ -347,51 +156,15 @@ void create(long initSize) { if (supportsTurnCosts()) { turnCostStorage.create(initSize); } - initStorage(); + setInitialized(); // 0 stands for no separate geoRef maxGeoRef = 4; - - initNodeRefs(0, nodes.getCapacity()); } String toDetailsString() { - return "edges:" + nf(edgeCount) + "(" + edges.getCapacity() / Helper.MB + "MB), " - + "nodes:" + nf(getNodes()) + "(" + nodes.getCapacity() / Helper.MB + "MB), " + return store.toDetailsString() + ", " + "name:(" + stringIndex.getCapacity() / Helper.MB + "MB), " - + "geo:" + nf(maxGeoRef) + "(" + wayGeometry.getCapacity() / Helper.MB + "MB), " - + "bounds:" + bounds; - } - - public void debugPrint() { - final int printMax = 100; - System.out.println("nodes:"); - String formatNodes = "%12s | %12s | %12s | %12s \n"; - System.out.format(Locale.ROOT, formatNodes, "#", "N_EDGE_REF", "N_LAT", "N_LON"); - NodeAccess nodeAccess = getNodeAccess(); - for (int i = 0; i < Math.min(nodeCount, printMax); ++i) { - System.out.format(Locale.ROOT, formatNodes, i, getEdgeRef(i), nodeAccess.getLat(i), nodeAccess.getLon(i)); - } - if (nodeCount > printMax) { - System.out.format(Locale.ROOT, " ... %d more nodes\n", nodeCount - printMax); - } - System.out.println("edges:"); - String formatEdges = "%12s | %12s | %12s | %12s | %12s | %12s | %12s \n"; - System.out.format(Locale.ROOT, formatEdges, "#", "E_NODEA", "E_NODEB", "E_LINKA", "E_LINKB", "E_FLAGS", "E_DIST"); - IntsRef intsRef = new IntsRef(intsForFlags); - for (int i = 0; i < Math.min(edgeCount, printMax); ++i) { - long edgePointer = toEdgePointer(i); - readFlags(edgePointer, intsRef); - System.out.format(Locale.ROOT, formatEdges, i, - getNodeA(edgePointer), - getNodeB(edgePointer), - getLinkA(edgePointer), - getLinkB(edgePointer), - intsRef, - getDist(edgePointer)); - } - if (edgeCount > printMax) { - System.out.printf(Locale.ROOT, " ... %d more edges", edgeCount - printMax); - } + + "geo:" + nf(maxGeoRef) + "(" + wayGeometry.getCapacity() / Helper.MB + "MB)"; } /** @@ -416,10 +189,7 @@ public void flush() { if (!stringIndex.isClosed()) stringIndex.flush(); - setNodesHeader(); - setEdgesHeader(); - edges.flush(); - nodes.flush(); + store.flush(); if (supportsTurnCosts()) { turnCostStorage.flush(); } @@ -430,15 +200,14 @@ public void close() { wayGeometry.close(); if (!stringIndex.isClosed()) stringIndex.close(); - edges.close(); - nodes.close(); + store.close(); if (supportsTurnCosts()) { turnCostStorage.close(); } } long getCapacity() { - return edges.getCapacity() + nodes.getCapacity() + stringIndex.getCapacity() + return store.getCapacity() + stringIndex.getCapacity() + wayGeometry.getCapacity() + (supportsTurnCosts() ? turnCostStorage.getCapacity() : 0); } @@ -447,16 +216,13 @@ long getMaxGeoRef() { } void loadExisting(String dim) { - if (!nodes.loadExisting()) - throw new IllegalStateException("Cannot load nodes. corrupt file or directory? " + dir); + if (!store.loadExisting()) + throw new IllegalStateException("Cannot load edges or nodes. corrupt file or directory? " + dir); if (!dim.equalsIgnoreCase("" + nodeAccess.getDimension())) throw new IllegalStateException("Configured dimension (" + nodeAccess.getDimension() + ") is not equal " + "to dimension of loaded graph (" + dim + ")"); - if (!edges.loadExisting()) - throw new IllegalStateException("Cannot load edges. corrupt file or directory? " + dir); - if (!wayGeometry.loadExisting()) throw new IllegalStateException("Cannot load geometry. corrupt file or directory? " + dir); @@ -466,12 +232,7 @@ void loadExisting(String dim) { if (supportsTurnCosts() && !turnCostStorage.loadExisting()) throw new IllegalStateException("Cannot load turn cost storage. corrupt file or directory? " + dir); - // first define header indices of this storage - initStorage(); - - // now load some properties from stored data - loadNodesHeader(); - loadEdgesHeader(); + setInitialized(); loadWayGeometryHeader(); } @@ -481,8 +242,8 @@ void loadExisting(String dim) { * @return the updated iterator the properties where copied to. */ EdgeIteratorState copyProperties(EdgeIteratorState from, EdgeIteratorStateImpl to) { - long edgePointer = toEdgePointer(to.getEdge()); - writeFlags(edgePointer, from.getFlags()); + long edgePointer = store.toEdgePointer(to.getEdge()); + store.writeFlags(edgePointer, from.getFlags()); // copy the rest with higher level API to.setDistance(from.getDistance()). @@ -501,71 +262,13 @@ EdgeIteratorState copyProperties(EdgeIteratorState from, EdgeIteratorStateImpl t public EdgeIteratorState edge(int nodeA, int nodeB) { if (isFrozen()) throw new IllegalStateException("Cannot create edge if graph is already frozen"); - - ensureNodeIndex(Math.max(nodeA, nodeB)); - int edgeId = internalEdgeAdd(nextEdgeId(), nodeA, nodeB); + int edgeId = store.edge(nodeA, nodeB); EdgeIteratorStateImpl edge = new EdgeIteratorStateImpl(this); boolean valid = edge.init(edgeId, nodeB); assert valid; return edge; } - /** - * Writes a new edge to the array of edges and adds it to the linked list of edges at nodeA and nodeB - */ - final int internalEdgeAdd(int newEdgeId, int nodeA, int nodeB) { - writeEdge(newEdgeId, nodeA, nodeB); - long edgePointer = toEdgePointer(newEdgeId); - - int edge = getEdgeRef(nodeA); - if (edge > EdgeIterator.NO_EDGE) - edges.setInt(E_LINKA + edgePointer, edge); - setEdgeRef(nodeA, newEdgeId); - - if (nodeA != nodeB) { - edge = getEdgeRef(nodeB); - if (edge > EdgeIterator.NO_EDGE) - edges.setInt(E_LINKB + edgePointer, edge); - setEdgeRef(nodeB, newEdgeId); - } - return newEdgeId; - } - - /** - * Writes plain edge information to the edges index - */ - private long writeEdge(int edgeId, int nodeA, int nodeB) { - if (!EdgeIterator.Edge.isValid(edgeId)) - throw new IllegalStateException("Cannot write edge with illegal ID:" + edgeId + "; nodeA:" + nodeA + ", nodeB:" + nodeB); - - long edgePointer = toEdgePointer(edgeId); - edges.setInt(edgePointer + E_NODEA, nodeA); - edges.setInt(edgePointer + E_NODEB, nodeB); - edges.setInt(edgePointer + E_LINKA, EdgeIterator.NO_EDGE); - edges.setInt(edgePointer + E_LINKB, EdgeIterator.NO_EDGE); - return edgePointer; - } - - // for test only - void setEdgeCount(int cnt) { - edgeCount = cnt; - } - - /** - * Determine next free edgeId and ensure byte capacity to store edge - * - * @return next free edgeId - */ - protected int nextEdgeId() { - int nextEdge = edgeCount; - edgeCount++; - if (edgeCount < 0) - throw new IllegalStateException("too many edges. new edge id would be negative. " + toString()); - - edges.ensureCapacity(((long) edgeCount + 1) * edgeEntryBytes); - return nextEdge; - } - @Override public EdgeIteratorState getEdgeIteratorState(int edgeId, int adjNode) { EdgeIteratorStateImpl edge = new EdgeIteratorStateImpl(this); @@ -582,11 +285,6 @@ public EdgeIteratorState getEdgeIteratorStateForKey(int edgeKey) { return edge; } - final void checkAdjNodeBounds(int adjNode) { - if (adjNode < 0 && adjNode != Integer.MIN_VALUE || adjNode >= nodeCount) - throw new IllegalStateException("adjNode " + adjNode + " out of bounds [0," + nf(nodeCount) + ")"); - } - @Override public EdgeExplorer createEdgeExplorer(EdgeFilter filter) { return new EdgeIteratorImpl(this, filter); @@ -614,64 +312,23 @@ public Weighting wrapWeighting(Weighting weighting) { @Override public int getOtherNode(int edge, int node) { - long edgePointer = toEdgePointer(edge); + long edgePointer = store.toEdgePointer(edge); return getOtherNode(node, edgePointer); } @Override public boolean isAdjacentToNode(int edge, int node) { - long edgePointer = toEdgePointer(edge); + long edgePointer = store.toEdgePointer(edge); return isAdjacentToNode(node, edgePointer); } - private void readFlags(long edgePointer, IntsRef edgeFlags) { - int size = edgeFlags.ints.length; - for (int i = 0; i < size; i++) { - edgeFlags.ints[i] = edges.getInt(edgePointer + E_FLAGS + i * 4); - } - } - - private void writeFlags(long edgePointer, IntsRef edgeFlags) { - int size = edgeFlags.ints.length; - for (int i = 0; i < size; i++) { - edges.setInt(edgePointer + E_FLAGS + i * 4, edgeFlags.ints[i]); - } - } - - private void setDist(long edgePointer, double distance) { - edges.setInt(edgePointer + E_DIST, distToInt(distance)); - } - - /** - * Translates double distance to integer in order to save it in a DataAccess object - */ - private int distToInt(double distance) { - if (distance < 0) - throw new IllegalArgumentException("Distance cannot be negative: " + distance); - if (distance > MAX_DIST) { - distance = MAX_DIST; - } - int integ = (int) Math.round(distance * INT_DIST_FACTOR); - assert integ >= 0 : "distance out of range"; - return integ; - } - - /** - * returns distance (already translated from integer to double) - */ - private double getDist(long pointer) { - int val = edges.getInt(pointer + E_DIST); - // do never return infinity even if INT MAX, see #435 - return val / INT_DIST_FACTOR; - } - private void setWayGeometry_(PointList pillarNodes, long edgePointer, boolean reverse) { if (pillarNodes != null && !pillarNodes.isEmpty()) { if (pillarNodes.getDimension() != nodeAccess.getDimension()) throw new IllegalArgumentException("Cannot use pointlist which is " + pillarNodes.getDimension() + "D for graph which is " + nodeAccess.getDimension() + "D"); - long existingGeoRef = Helper.toUnsignedLong(edges.getInt(edgePointer + E_GEO)); + long existingGeoRef = Helper.toUnsignedLong(store.getGeoRef(edgePointer)); int len = pillarNodes.size(); int dim = nodeAccess.getDimension(); @@ -686,7 +343,7 @@ private void setWayGeometry_(PointList pillarNodes, long edgePointer, boolean re long nextGeoRef = nextGeoRef(len * dim); setWayGeometryAtGeoRef(pillarNodes, edgePointer, reverse, nextGeoRef); } else { - edges.setInt(edgePointer + E_GEO, 0); + store.setGeoRef(edgePointer, 0); } } @@ -698,7 +355,7 @@ private void setWayGeometryAtGeoRef(PointList pillarNodes, long edgePointer, boo ensureGeometry(geoRefPosition, totalLen); byte[] wayGeometryBytes = createWayGeometryBytes(pillarNodes, reverse); wayGeometry.setBytes(geoRefPosition, wayGeometryBytes, wayGeometryBytes.length); - edges.setInt(edgePointer + E_GEO, Helper.toSignedInt(geoRef)); + store.setGeoRef(edgePointer, Helper.toSignedInt(geoRef)); } private byte[] createWayGeometryBytes(PointList pillarNodes, boolean reverse) { @@ -735,7 +392,7 @@ private PointList fetchWayGeometry_(long edgePointer, boolean reverse, FetchMode pillarNodes.add(nodeAccess, adjNode); return pillarNodes; } - long geoRef = Helper.toUnsignedLong(edges.getInt(edgePointer + E_GEO)); + long geoRef = Helper.toUnsignedLong(store.getGeoRef(edgePointer)); int count = 0; byte[] bytes = null; if (geoRef > 0) { @@ -799,8 +456,7 @@ private void setName(long edgePointer, String name) { int stringIndexRef = (int) stringIndex.add(Collections.singletonMap(STRING_IDX_NAME_KEY, name)); if (stringIndexRef < 0) throw new IllegalStateException("Too many names are stored, currently limited to int pointer"); - - edges.setInt(edgePointer + E_NAME, stringIndexRef); + store.setNameRef(edgePointer, stringIndexRef); } private void ensureGeometry(long bytePos, int byteLength) { @@ -816,6 +472,10 @@ private long nextGeoRef(int arrayLength) { return tmp; } + public boolean isClosed() { + return store.isClosed(); + } + protected static class EdgeIteratorImpl extends EdgeIteratorStateImpl implements EdgeExplorer, EdgeIterator { final EdgeFilter filter; int nextEdgeId; @@ -830,7 +490,7 @@ public EdgeIteratorImpl(BaseGraph baseGraph, EdgeFilter filter) { @Override public EdgeIterator setBaseNode(int baseNode) { - nextEdgeId = edgeId = baseGraph.getEdgeRef(baseNode); + nextEdgeId = edgeId = store.getEdgeRef(store.toNodePointer(baseNode)); this.baseNode = baseNode; return this; } @@ -846,16 +506,16 @@ public final boolean next() { } void goToNext() { - edgePointer = baseGraph.toEdgePointer(nextEdgeId); + edgePointer = store.toEdgePointer(nextEdgeId); edgeId = nextEdgeId; - int nodeA = baseGraph.getNodeA(edgePointer); + int nodeA = store.getNodeA(edgePointer); boolean baseNodeIsNodeA = baseNode == nodeA; - adjNode = baseNodeIsNodeA ? baseGraph.getNodeB(edgePointer) : nodeA; + adjNode = baseNodeIsNodeA ? store.getNodeB(edgePointer) : nodeA; reverse = !baseNodeIsNodeA; freshFlags = false; // position to next edge - nextEdgeId = baseNodeIsNodeA ? baseGraph.getLinkA(edgePointer) : baseGraph.getLinkB(edgePointer); + nextEdgeId = baseNodeIsNodeA ? store.getLinkA(edgePointer) : store.getLinkB(edgePointer); assert nextEdgeId != edgeId : ("endless loop detected for base node: " + baseNode + ", adj node: " + adjNode + ", edge pointer: " + edgePointer + ", edge: " + edgeId); } @@ -878,17 +538,17 @@ public AllEdgeIterator(BaseGraph baseGraph) { @Override public int length() { - return baseGraph.edgeCount; + return store.getEdges(); } @Override public boolean next() { edgeId++; - if (edgeId >= baseGraph.edgeCount) + if (edgeId >= store.getEdges()) return false; - edgePointer = baseGraph.toEdgePointer(edgeId); - baseNode = baseGraph.getNodeA(edgePointer); - adjNode = baseGraph.getNodeB(edgePointer); + edgePointer = store.toEdgePointer(edgeId); + baseNode = store.getNodeA(edgePointer); + adjNode = store.getNodeB(edgePointer); freshFlags = false; reverse = false; return true; @@ -917,6 +577,7 @@ public final EdgeIteratorState detach(boolean reverseArg) { static class EdgeIteratorStateImpl implements EdgeIteratorState { final BaseGraph baseGraph; + final BaseGraphNodesAndEdges store; long edgePointer = -1; int baseNode; int adjNode; @@ -928,19 +589,20 @@ static class EdgeIteratorStateImpl implements EdgeIteratorState { public EdgeIteratorStateImpl(BaseGraph baseGraph) { this.baseGraph = baseGraph; - this.edgeFlags = new IntsRef(baseGraph.intsForFlags); + this.edgeFlags = new IntsRef(baseGraph.store.getIntsForFlags()); + store = baseGraph.store; } /** * @return false if the edge has not a node equal to expectedAdjNode */ final boolean init(int edgeId, int expectedAdjNode) { - if (edgeId < 0 || edgeId >= baseGraph.edgeCount) - throw new IllegalArgumentException("edge: " + edgeId + " out of bounds: [0," + baseGraph.edgeCount + "["); + if (edgeId < 0 || edgeId >= store.getEdges()) + throw new IllegalArgumentException("edge: " + edgeId + " out of bounds: [0," + store.getEdges() + "["); this.edgeId = edgeId; - edgePointer = baseGraph.toEdgePointer(edgeId); - baseNode = baseGraph.getNodeA(edgePointer); - adjNode = baseGraph.getNodeB(edgePointer); + edgePointer = store.toEdgePointer(edgeId); + baseNode = store.getNodeA(edgePointer); + adjNode = store.getNodeB(edgePointer); freshFlags = false; if (expectedAdjNode == adjNode || expectedAdjNode == Integer.MIN_VALUE) { @@ -963,9 +625,9 @@ final void init(int edgeKey) { if (edgeKey < 0) throw new IllegalArgumentException("edge keys must not be negative, given: " + edgeKey); this.edgeId = GHUtility.getEdgeFromEdgeKey(edgeKey); - edgePointer = baseGraph.toEdgePointer(edgeId); - baseNode = baseGraph.getNodeA(edgePointer); - adjNode = baseGraph.getNodeB(edgePointer); + edgePointer = store.toEdgePointer(edgeId); + baseNode = store.getNodeA(edgePointer); + adjNode = store.getNodeB(edgePointer); freshFlags = false; if (edgeKey % 2 == 0 || baseNode == adjNode) { @@ -990,19 +652,19 @@ public final int getAdjNode() { @Override public double getDistance() { - return baseGraph.getDist(edgePointer); + return store.getDist(edgePointer); } @Override public EdgeIteratorState setDistance(double dist) { - baseGraph.setDist(edgePointer, dist); + store.setDist(edgePointer, dist); return this; } @Override public IntsRef getFlags() { if (!freshFlags) { - baseGraph.readFlags(edgePointer, edgeFlags); + store.readFlags(edgePointer, edgeFlags); freshFlags = true; } return edgeFlags; @@ -1010,8 +672,8 @@ public IntsRef getFlags() { @Override public final EdgeIteratorState setFlags(IntsRef edgeFlags) { - assert edgeId < baseGraph.edgeCount : "must be edge but was shortcut: " + edgeId + " >= " + baseGraph.edgeCount + ". Use setFlagsAndWeight"; - baseGraph.writeFlags(edgePointer, edgeFlags); + assert edgeId < store.getEdges() : "must be edge but was shortcut: " + edgeId + " >= " + store.getEdges() + ". Use setFlagsAndWeight"; + store.writeFlags(edgePointer, edgeFlags); for (int i = 0; i < edgeFlags.ints.length; i++) { this.edgeFlags.ints[i] = edgeFlags.ints[i]; } @@ -1027,7 +689,7 @@ public boolean get(BooleanEncodedValue property) { @Override public EdgeIteratorState set(BooleanEncodedValue property, boolean value) { property.setBool(reverse, getFlags(), value); - baseGraph.writeFlags(edgePointer, getFlags()); + store.writeFlags(edgePointer, getFlags()); return this; } @@ -1039,7 +701,7 @@ public boolean getReverse(BooleanEncodedValue property) { @Override public EdgeIteratorState setReverse(BooleanEncodedValue property, boolean value) { property.setBool(!reverse, getFlags(), value); - baseGraph.writeFlags(edgePointer, getFlags()); + store.writeFlags(edgePointer, getFlags()); return this; } @@ -1049,7 +711,7 @@ public EdgeIteratorState set(BooleanEncodedValue property, boolean fwd, boolean throw new IllegalArgumentException("EncodedValue " + property.getName() + " supports only one direction"); property.setBool(reverse, getFlags(), fwd); property.setBool(!reverse, getFlags(), bwd); - baseGraph.writeFlags(edgePointer, getFlags()); + store.writeFlags(edgePointer, getFlags()); return this; } @@ -1061,7 +723,7 @@ public int get(IntEncodedValue property) { @Override public EdgeIteratorState set(IntEncodedValue property, int value) { property.setInt(reverse, getFlags(), value); - baseGraph.writeFlags(edgePointer, getFlags()); + store.writeFlags(edgePointer, getFlags()); return this; } @@ -1073,7 +735,7 @@ public int getReverse(IntEncodedValue property) { @Override public EdgeIteratorState setReverse(IntEncodedValue property, int value) { property.setInt(!reverse, getFlags(), value); - baseGraph.writeFlags(edgePointer, getFlags()); + store.writeFlags(edgePointer, getFlags()); return this; } @@ -1083,7 +745,7 @@ public EdgeIteratorState set(IntEncodedValue property, int fwd, int bwd) { throw new IllegalArgumentException("EncodedValue " + property.getName() + " supports only one direction"); property.setInt(reverse, getFlags(), fwd); property.setInt(!reverse, getFlags(), bwd); - baseGraph.writeFlags(edgePointer, getFlags()); + store.writeFlags(edgePointer, getFlags()); return this; } @@ -1095,7 +757,7 @@ public double get(DecimalEncodedValue property) { @Override public EdgeIteratorState set(DecimalEncodedValue property, double value) { property.setDecimal(reverse, getFlags(), value); - baseGraph.writeFlags(edgePointer, getFlags()); + store.writeFlags(edgePointer, getFlags()); return this; } @@ -1107,7 +769,7 @@ public double getReverse(DecimalEncodedValue property) { @Override public EdgeIteratorState setReverse(DecimalEncodedValue property, double value) { property.setDecimal(!reverse, getFlags(), value); - baseGraph.writeFlags(edgePointer, getFlags()); + store.writeFlags(edgePointer, getFlags()); return this; } @@ -1117,7 +779,7 @@ public EdgeIteratorState set(DecimalEncodedValue property, double fwd, double bw throw new IllegalArgumentException("EncodedValue " + property.getName() + " supports only one direction"); property.setDecimal(reverse, getFlags(), fwd); property.setDecimal(!reverse, getFlags(), bwd); - baseGraph.writeFlags(edgePointer, getFlags()); + store.writeFlags(edgePointer, getFlags()); return this; } @@ -1129,7 +791,7 @@ public > T get(EnumEncodedValue property) { @Override public > EdgeIteratorState set(EnumEncodedValue property, T value) { property.setEnum(reverse, getFlags(), value); - baseGraph.writeFlags(edgePointer, getFlags()); + store.writeFlags(edgePointer, getFlags()); return this; } @@ -1141,7 +803,7 @@ public > T getReverse(EnumEncodedValue property) { @Override public > EdgeIteratorState setReverse(EnumEncodedValue property, T value) { property.setEnum(!reverse, getFlags(), value); - baseGraph.writeFlags(edgePointer, getFlags()); + store.writeFlags(edgePointer, getFlags()); return this; } @@ -1151,7 +813,7 @@ public > EdgeIteratorState set(EnumEncodedValue property, T throw new IllegalArgumentException("EncodedValue " + property.getName() + " supports only one direction"); property.setEnum(reverse, getFlags(), fwd); property.setEnum(!reverse, getFlags(), bwd); - baseGraph.writeFlags(edgePointer, getFlags()); + store.writeFlags(edgePointer, getFlags()); return this; } @@ -1163,7 +825,7 @@ public String get(StringEncodedValue property) { @Override public EdgeIteratorState set(StringEncodedValue property, String value) { property.setString(reverse, getFlags(), value); - baseGraph.writeFlags(edgePointer, getFlags()); + store.writeFlags(edgePointer, getFlags()); return this; } @@ -1175,7 +837,7 @@ public String getReverse(StringEncodedValue property) { @Override public EdgeIteratorState setReverse(StringEncodedValue property, String value) { property.setString(!reverse, getFlags(), value); - baseGraph.writeFlags(edgePointer, getFlags()); + store.writeFlags(edgePointer, getFlags()); return this; } @@ -1185,7 +847,7 @@ public EdgeIteratorState set(StringEncodedValue property, String fwd, String bwd throw new IllegalArgumentException("EncodedValue " + property.getName() + " supports only one direction"); property.setString(reverse, getFlags(), fwd); property.setString(!reverse, getFlags(), bwd); - baseGraph.writeFlags(edgePointer, getFlags()); + store.writeFlags(edgePointer, getFlags()); return this; } @@ -1227,7 +889,7 @@ public int getOrigEdgeLast() { @Override public String getName() { - int stringIndexRef = baseGraph.edges.getInt(edgePointer + baseGraph.E_NAME); + int stringIndexRef = store.getNameRef(edgePointer); String name = baseGraph.stringIndex.get(stringIndexRef, STRING_IDX_NAME_KEY); // preserve backward compatibility (returns null if not explicitly set) return name == null ? "" : name; diff --git a/core/src/main/java/com/graphhopper/storage/BaseGraphNodesAndEdges.java b/core/src/main/java/com/graphhopper/storage/BaseGraphNodesAndEdges.java new file mode 100644 index 00000000000..05478a54073 --- /dev/null +++ b/core/src/main/java/com/graphhopper/storage/BaseGraphNodesAndEdges.java @@ -0,0 +1,403 @@ +/* + * 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.storage; + +import com.graphhopper.util.Constants; +import com.graphhopper.util.EdgeIterator; +import com.graphhopper.util.GHUtility; +import com.graphhopper.util.Helper; +import com.graphhopper.util.shapes.BBox; + +import java.util.Locale; + +import static com.graphhopper.util.EdgeIterator.NO_EDGE; +import static com.graphhopper.util.Helper.nf; + +/** + * Underlying storage for nodes and edges of {@link BaseGraph}. Nodes and edges are stored using two {@link DataAccess} + * instances. Nodes and edges are simply stored sequentially, see the memory layout in the constructor. + */ +class BaseGraphNodesAndEdges { + // Currently distances are stored as 4 byte integers. using a conversion factor of 1000 the minimum distance + // that is not considered zero is 0.0005m (=0.5mm) and the maximum distance per edge is about 2.147.483m=2147km. + // See OSMReader.addEdge and #1871. + private static final double INT_DIST_FACTOR = 1000d; + static double MAX_DIST = Integer.MAX_VALUE / INT_DIST_FACTOR; + + // nodes + private final DataAccess nodes; + private final int N_EDGE_REF, N_LAT, N_LON, N_ELE, N_TC; + private int nodeEntryBytes; + private int nodeCount; + + // edges + private final DataAccess edges; + private final int E_NODEA, E_NODEB, E_LINKA, E_LINKB, E_FLAGS, E_DIST, E_GEO, E_NAME; + private final int intsForFlags; + private int edgeEntryBytes; + private int edgeCount; + + private final boolean withTurnCosts; + private final boolean withElevation; + + // we do not write the bounding box directly to storage, but rather to this bbox object. we only write to storage + // when flushing. why? just because we did it like this in the past, and otherwise we run into rounding errors, + // because of: #2393 + public final BBox bounds; + private boolean frozen; + + public BaseGraphNodesAndEdges(Directory dir, int intsForFlags, boolean withElevation, boolean withTurnCosts, int segmentSize) { + this.nodes = dir.create("nodes", dir.getDefaultType("nodes", true), segmentSize); + this.edges = dir.create("edges", dir.getDefaultType("edges", true), segmentSize); + this.intsForFlags = intsForFlags; + this.withTurnCosts = withTurnCosts; + this.withElevation = withElevation; + bounds = BBox.createInverse(withElevation); + + // memory layout for nodes + N_EDGE_REF = 0; + N_LAT = 4; + N_LON = 8; + N_ELE = N_LON + (withElevation ? 4 : 0); + N_TC = N_ELE + (withTurnCosts ? 4 : 0); + nodeEntryBytes = N_TC + 4; + + // memory layout for edges + E_NODEA = 0; + E_NODEB = 4; + E_LINKA = 8; + E_LINKB = 12; + E_FLAGS = 16; + E_DIST = E_FLAGS + intsForFlags * 4; + E_GEO = E_DIST + 4; + E_NAME = E_GEO + 4; + edgeEntryBytes = E_NAME + 4; + } + + public void create(long initSize) { + nodes.create(initSize); + edges.create(initSize); + } + + public boolean loadExisting() { + if (!nodes.loadExisting() || !edges.loadExisting()) + return false; + + // now load some properties from stored data + final int nodesVersion = nodes.getHeader(0 * 4); + GHUtility.checkDAVersion("nodes", Constants.VERSION_NODE, nodesVersion); + nodeEntryBytes = nodes.getHeader(1 * 4); + nodeCount = nodes.getHeader(2 * 4); + bounds.minLon = Helper.intToDegree(nodes.getHeader(3 * 4)); + bounds.maxLon = Helper.intToDegree(nodes.getHeader(4 * 4)); + bounds.minLat = Helper.intToDegree(nodes.getHeader(5 * 4)); + bounds.maxLat = Helper.intToDegree(nodes.getHeader(6 * 4)); + if (withElevation) { + bounds.minEle = Helper.intToEle(nodes.getHeader(7 * 4)); + bounds.maxEle = Helper.intToEle(nodes.getHeader(8 * 4)); + } + frozen = nodes.getHeader(9 * 4) == 1; + + final int edgesVersion = edges.getHeader(0 * 4); + GHUtility.checkDAVersion("edges", Constants.VERSION_EDGE, edgesVersion); + edgeEntryBytes = edges.getHeader(1 * 4); + edgeCount = edges.getHeader(2 * 4); + return true; + } + + public void flush() { + nodes.setHeader(0 * 4, Constants.VERSION_NODE); + nodes.setHeader(1 * 4, nodeEntryBytes); + nodes.setHeader(2 * 4, nodeCount); + nodes.setHeader(3 * 4, Helper.degreeToInt(bounds.minLon)); + nodes.setHeader(4 * 4, Helper.degreeToInt(bounds.maxLon)); + nodes.setHeader(5 * 4, Helper.degreeToInt(bounds.minLat)); + nodes.setHeader(6 * 4, Helper.degreeToInt(bounds.maxLat)); + if (withElevation) { + nodes.setHeader(7 * 4, Helper.eleToInt(bounds.minEle)); + nodes.setHeader(8 * 4, Helper.eleToInt(bounds.maxEle)); + } + nodes.setHeader(9 * 4, frozen ? 1 : 0); + + edges.setHeader(0 * 4, Constants.VERSION_EDGE); + edges.setHeader(1 * 4, edgeEntryBytes); + edges.setHeader(2 * 4, edgeCount); + + edges.flush(); + nodes.flush(); + } + + public void close() { + edges.close(); + nodes.close(); + } + + public int getNodes() { + return nodeCount; + } + + public int getEdges() { + return edgeCount; + } + + public int getIntsForFlags() { + return intsForFlags; + } + + public boolean withElevation() { + return withElevation; + } + + public boolean withTurnCosts() { + return withTurnCosts; + } + + public BBox getBounds() { + return bounds; + } + + public long getCapacity() { + return nodes.getCapacity() + edges.getCapacity(); + } + + public boolean isClosed() { + assert nodes.isClosed() == edges.isClosed(); + return nodes.isClosed(); + } + + public int edge(int nodeA, int nodeB) { + if (edgeCount == Integer.MAX_VALUE) + throw new IllegalStateException("Maximum edge count exceeded: " + edgeCount); + ensureNodeCapacity(Math.max(nodeA, nodeB)); + final int edge = edgeCount; + final long edgePointer = (long) edgeCount * edgeEntryBytes; + edgeCount++; + edges.ensureCapacity((long) edgeCount * edgeEntryBytes); + + setNodeA(edgePointer, nodeA); + setNodeB(edgePointer, nodeB); + // we keep a linked list of edges at each node. here we prepend the new edge at the already existing linked + // list of edges. + long nodePointerA = toNodePointer(nodeA); + int edgeRefA = getEdgeRef(nodePointerA); + setLinkA(edgePointer, EdgeIterator.Edge.isValid(edgeRefA) ? edgeRefA : NO_EDGE); + setEdgeRef(nodePointerA, edge); + + if (nodeA != nodeB) { + long nodePointerB = toNodePointer(nodeB); + int edgeRefB = getEdgeRef(nodePointerB); + setLinkB(edgePointer, EdgeIterator.Edge.isValid(edgeRefB) ? edgeRefB : NO_EDGE); + setEdgeRef(nodePointerB, edge); + } + return edge; + } + + public void ensureNodeCapacity(int node) { + if (node < nodeCount) + return; + + int oldNodes = nodeCount; + nodeCount = node + 1; + nodes.ensureCapacity((long) nodeCount * nodeEntryBytes); + for (int n = oldNodes; n < nodeCount; ++n) { + setEdgeRef(toNodePointer(n), NO_EDGE); + if (withTurnCosts) + setTurnCostRef(toNodePointer(n), TurnCostStorage.NO_TURN_ENTRY); + } + } + + public long toNodePointer(int node) { + if (node < 0 || node >= nodeCount) + throw new IllegalArgumentException("node: " + node + " out of bounds [0," + nodeCount + "["); + return (long) node * nodeEntryBytes; + } + + public long toEdgePointer(int edge) { + if (edge < 0 || edge >= edgeCount) + throw new IllegalArgumentException("edge: " + edge + " out of bounds [0," + edgeCount + "["); + return (long) edge * edgeEntryBytes; + } + + public void readFlags(long edgePointer, IntsRef edgeFlags) { + int size = edgeFlags.ints.length; + for (int i = 0; i < size; ++i) + edgeFlags.ints[i] = edges.getInt(edgePointer + E_FLAGS + i * 4); + } + + public void writeFlags(long edgePointer, IntsRef edgeFlags) { + int size = edgeFlags.ints.length; + for (int i = 0; i < size; ++i) + edges.setInt(edgePointer + E_FLAGS + i * 4, edgeFlags.ints[i]); + } + + public void setNodeA(long edgePointer, int nodeA) { + edges.setInt(edgePointer + E_NODEA, nodeA); + } + + public void setNodeB(long edgePointer, int nodeB) { + edges.setInt(edgePointer + E_NODEB, nodeB); + } + + public void setLinkA(long edgePointer, int linkA) { + edges.setInt(edgePointer + E_LINKA, linkA); + } + + public void setLinkB(long edgePointer, int linkB) { + edges.setInt(edgePointer + E_LINKB, linkB); + } + + public void setDist(long edgePointer, double distance) { + edges.setInt(edgePointer + E_DIST, distToInt(distance)); + } + + public void setGeoRef(long edgePointer, int geoRef) { + edges.setInt(edgePointer + E_GEO, geoRef); + } + + public void setNameRef(long edgePointer, int nameRef) { + edges.setInt(edgePointer + E_NAME, nameRef); + } + + public int getNodeA(long edgePointer) { + return edges.getInt(edgePointer + E_NODEA); + } + + public int getNodeB(long edgePointer) { + return edges.getInt(edgePointer + E_NODEB); + } + + public int getLinkA(long edgePointer) { + return edges.getInt(edgePointer + E_LINKA); + } + + public int getLinkB(long edgePointer) { + return edges.getInt(edgePointer + E_LINKB); + } + + public double getDist(long pointer) { + int val = edges.getInt(pointer + E_DIST); + // do never return infinity even if INT MAX, see #435 + return val / INT_DIST_FACTOR; + } + + public int getGeoRef(long edgePointer) { + return edges.getInt(edgePointer + E_GEO); + } + + public int getNameRef(long edgePointer) { + return edges.getInt(edgePointer + E_NAME); + } + + public void setEdgeRef(long nodePointer, int edgeRef) { + nodes.setInt(nodePointer + N_EDGE_REF, edgeRef); + } + + public void setLat(long nodePointer, double lat) { + nodes.setInt(nodePointer + N_LAT, Helper.degreeToInt(lat)); + } + + public void setLon(long nodePointer, double lon) { + nodes.setInt(nodePointer + N_LON, Helper.degreeToInt(lon)); + } + + public void setEle(long elePointer, double ele) { + nodes.setInt(elePointer + N_ELE, Helper.eleToInt(ele)); + } + + public void setTurnCostRef(long nodePointer, int tcRef) { + nodes.setInt(nodePointer + N_TC, tcRef); + } + + public int getEdgeRef(long nodePointer) { + return nodes.getInt(nodePointer + N_EDGE_REF); + } + + public double getLat(long nodePointer) { + return Helper.intToDegree(nodes.getInt(nodePointer + N_LAT)); + } + + public double getLon(long nodePointer) { + return Helper.intToDegree(nodes.getInt(nodePointer + N_LON)); + } + + public double getEle(long nodePointer) { + return Helper.intToEle(nodes.getInt(nodePointer + N_ELE)); + } + + public int getTurnCostRef(long nodePointer) { + return nodes.getInt(nodePointer + N_TC); + } + + public void setFrozen(boolean frozen) { + this.frozen = frozen; + } + + public boolean getFrozen() { + return frozen; + } + + public void debugPrint() { + final int printMax = 100; + System.out.println("nodes:"); + String formatNodes = "%12s | %12s | %12s | %12s \n"; + System.out.format(Locale.ROOT, formatNodes, "#", "N_EDGE_REF", "N_LAT", "N_LON"); + for (int i = 0; i < Math.min(nodeCount, printMax); ++i) { + long nodePointer = toNodePointer(i); + System.out.format(Locale.ROOT, formatNodes, i, getEdgeRef(nodePointer), getLat(nodePointer), getLon(nodePointer)); + } + if (nodeCount > printMax) { + System.out.format(Locale.ROOT, " ... %d more nodes\n", nodeCount - printMax); + } + System.out.println("edges:"); + String formatEdges = "%12s | %12s | %12s | %12s | %12s | %12s | %12s \n"; + System.out.format(Locale.ROOT, formatEdges, "#", "E_NODEA", "E_NODEB", "E_LINKA", "E_LINKB", "E_FLAGS", "E_DIST"); + IntsRef intsRef = new IntsRef(intsForFlags); + for (int i = 0; i < Math.min(edgeCount, printMax); ++i) { + long edgePointer = toEdgePointer(i); + readFlags(edgePointer, intsRef); + System.out.format(Locale.ROOT, formatEdges, i, + getNodeA(edgePointer), + getNodeB(edgePointer), + getLinkA(edgePointer), + getLinkB(edgePointer), + intsRef, + getDist(edgePointer)); + } + if (edgeCount > printMax) { + System.out.printf(Locale.ROOT, " ... %d more edges", edgeCount - printMax); + } + } + + private int distToInt(double distance) { + if (distance < 0) + throw new IllegalArgumentException("Distance cannot be negative: " + distance); + if (distance > MAX_DIST) { + distance = MAX_DIST; + } + int intDist = (int) Math.round(distance * INT_DIST_FACTOR); + assert intDist >= 0 : "distance out of range"; + return intDist; + } + + public String toDetailsString() { + return "edges: " + nf(edgeCount) + "(" + edges.getCapacity() / Helper.MB + "MB), " + + "nodes: " + nf(nodeCount) + "(" + nodes.getCapacity() / Helper.MB + "MB), " + + "bounds: " + bounds; + } +} diff --git a/core/src/main/java/com/graphhopper/storage/CHStorage.java b/core/src/main/java/com/graphhopper/storage/CHStorage.java index 90d97205fc6..f5bad765375 100644 --- a/core/src/main/java/com/graphhopper/storage/CHStorage.java +++ b/core/src/main/java/com/graphhopper/storage/CHStorage.java @@ -20,6 +20,8 @@ import com.graphhopper.routing.ch.NodeOrderingProvider; import com.graphhopper.routing.ch.PrepareEncoder; +import com.graphhopper.util.Constants; +import com.graphhopper.util.GHUtility; import com.graphhopper.util.Helper; import java.util.Locale; @@ -31,7 +33,7 @@ * DataAccess-based storage for CH shortcuts. Stores shortcuts and CH levels sequentially using two DataAccess objects * and gives read/write access to the different shortcut and node fields. *

- * This can be seen as an extension to a base graph: We assign a CH level to each nodes and add additional edges to + * This can be seen as an extension to a base graph: We assign a CH level to each node and add additional edges to * the graph ('shortcuts'). The shortcuts need to be ordered in a certain way, but this is not enforced here. * * @see CHStorageBuilder to build a valid storage that can be used for routing @@ -58,7 +60,7 @@ public class CHStorage { private int nodeCount = -1; private boolean edgeBased; - // some shortcuts exceed the maximum storable weight and we count them here + // some shortcuts exceed the maximum storable weight, and we count them here private int numShortcutsExceedingWeight; // use this to report shortcuts with too small weights @@ -159,18 +161,20 @@ public void init(int nodes, int expectedShortcuts) { public void flush() { // nodes - nodesCH.setHeader(0, nodeCount); - nodesCH.setHeader(4, nodeCHEntryBytes); -// ORS-GH MOD START added header field - nodesCH.setHeader(8, coreNodeCount); -// ORS-GH MOD END + nodesCH.setHeader(0, Constants.VERSION_NODE_CH); + nodesCH.setHeader(4, nodeCount); + nodesCH.setHeader(8, nodeCHEntryBytes); + // ORS-GH MOD START added header field + nodesCH.setHeader(12, coreNodeCount); + // ORS-GH MOD END nodesCH.flush(); // shortcuts - shortcuts.setHeader(0, shortcutCount); - shortcuts.setHeader(4, shortcutEntryBytes); - shortcuts.setHeader(8, numShortcutsExceedingWeight); - shortcuts.setHeader(12, edgeBased ? 1 : 0); + shortcuts.setHeader(0, Constants.VERSION_SHORTCUT); + shortcuts.setHeader(4, shortcutCount); + shortcuts.setHeader(8, shortcutEntryBytes); + shortcuts.setHeader(12, numShortcutsExceedingWeight); + shortcuts.setHeader(16, edgeBased ? 1 : 0); shortcuts.flush(); } @@ -179,17 +183,21 @@ public boolean loadExisting() { return false; // nodes - nodeCount = nodesCH.getHeader(0); - nodeCHEntryBytes = nodesCH.getHeader(4); + int nodesCHVersion = nodesCH.getHeader(0); + GHUtility.checkDAVersion(nodesCH.getName(), Constants.VERSION_NODE_CH, nodesCHVersion); + nodeCount = nodesCH.getHeader(4); + nodeCHEntryBytes = nodesCH.getHeader(8); // ORS-GH MOD START added header field - coreNodeCount = nodesCH.getHeader(8); + coreNodeCount = nodesCH.getHeader(12); // ORS-GH MOD END // shortcuts - shortcutCount = shortcuts.getHeader(0); - shortcutEntryBytes = shortcuts.getHeader(4); - numShortcutsExceedingWeight = shortcuts.getHeader(8); - edgeBased = shortcuts.getHeader(12) == 1; + int shortcutsVersion = shortcuts.getHeader(0); + GHUtility.checkDAVersion(shortcuts.getName(), Constants.VERSION_SHORTCUT, shortcutsVersion); + shortcutCount = shortcuts.getHeader(4); + shortcutEntryBytes = shortcuts.getHeader(8); + numShortcutsExceedingWeight = shortcuts.getHeader(12); + edgeBased = shortcuts.getHeader(16) == 1; return true; } diff --git a/core/src/main/java/com/graphhopper/storage/ConditionalEdges.java b/core/src/main/java/com/graphhopper/storage/ConditionalEdges.java index d63614c501e..c1c1141d973 100644 --- a/core/src/main/java/com/graphhopper/storage/ConditionalEdges.java +++ b/core/src/main/java/com/graphhopper/storage/ConditionalEdges.java @@ -32,7 +32,7 @@ public ConditionalEdges(EncodingManager encodingManager, String encoderName, Dir String name = this.encodingManager.getKey(encoder, this.encoderName); if (this.encodingManager.hasEncodedValue(name)) { String mapName = this.encoderName + "_" + encoder.toString(); - ConditionalEdgesMap conditionalEdgesMap = new ConditionalEdgesMap(mapName, conditionalIndex, dir.find(mapName)); + ConditionalEdgesMap conditionalEdgesMap = new ConditionalEdgesMap(mapName, conditionalIndex, dir.create(mapName)); conditionalEdgesMaps.put(encoder.toString(), conditionalEdgesMap); } } diff --git a/core/src/main/java/com/graphhopper/storage/ConditionalEdgesMap.java b/core/src/main/java/com/graphhopper/storage/ConditionalEdgesMap.java index 5ff1e7471e2..70daf39b730 100644 --- a/core/src/main/java/com/graphhopper/storage/ConditionalEdgesMap.java +++ b/core/src/main/java/com/graphhopper/storage/ConditionalEdgesMap.java @@ -92,7 +92,7 @@ public void init(Graph graph, Directory dir) { if (edgesCount > 0) throw new AssertionError("The conditional restrictions storage must be initialized only once."); - this.edges = dir.find(name); + this.edges = dir.create(name); } public ConditionalEdgesMap create(long byteCount) { diff --git a/core/src/main/java/com/graphhopper/storage/Directory.java b/core/src/main/java/com/graphhopper/storage/Directory.java index cd1588f32a3..03a3fc02f5d 100644 --- a/core/src/main/java/com/graphhopper/storage/Directory.java +++ b/core/src/main/java/com/graphhopper/storage/Directory.java @@ -17,9 +17,6 @@ */ package com.graphhopper.storage; -import java.nio.ByteOrder; -import java.util.Map; - /** * Maintains a collection of DataAccess objects stored at the same location. One GraphStorage per * Directory as we need one to maintain one DataAccess object for nodes, edges and location2id @@ -34,74 +31,26 @@ public interface Directory { */ String getLocation(); - /** - * @return the order in which the data is stored - * @deprecated - */ - @Deprecated - default ByteOrder getByteOrder() { - return ByteOrder.LITTLE_ENDIAN; - } - /** * Creates a new DataAccess object with the given name in the location of this Directory. Each name can only * be used once. */ DataAccess create(String name); - /** - * @deprecated use {@link #create(String)} instead. - */ - @Deprecated - default DataAccess find(String name) { - return create(name); - } - /** * @param segmentSize segment size in bytes or -1 to use the default of the corresponding DataAccess implementation */ DataAccess create(String name, int segmentSize); - /** - * @deprecated use {@link #create(String, int)} instead. - */ - @Deprecated - default DataAccess find(String name, int segmentSize) { - return create(name, segmentSize); - } - DataAccess create(String name, DAType type); - /** - * @deprecated use {@link #create(String, DAType)} instead. - */ - @Deprecated - default DataAccess find(String name, DAType type) { - return create(name, type); - } - DataAccess create(String name, DAType type, int segmentSize); - /** - * @deprecated use {@link #create(String, DAType, int)} instead. - */ - @Deprecated - default DataAccess find(String name, DAType type, int segmentSize) { - return create(name, type, segmentSize); - } - /** * Removes the specified object from the directory. */ - void remove(String name); + void remove(DataAccess da); - /** - * @deprecated use {@link #remove(String)} instead. - */ - @Deprecated - default void remove(DataAccess da) { - remove(da.getName()); - } /** * @return the default type of a newly created DataAccess object */ diff --git a/core/src/main/java/com/graphhopper/storage/GHDirectory.java b/core/src/main/java/com/graphhopper/storage/GHDirectory.java index 1024d10fc3d..c5b409fb073 100644 --- a/core/src/main/java/com/graphhopper/storage/GHDirectory.java +++ b/core/src/main/java/com/graphhopper/storage/GHDirectory.java @@ -18,6 +18,7 @@ package com.graphhopper.storage; import java.io.File; +import java.util.HashMap; import java.util.*; import static com.graphhopper.storage.DAType.RAM_INT; @@ -169,13 +170,13 @@ public void clear() { } @Override - public void remove(String name) { - DataAccess old = map.remove(name); + public void remove(DataAccess da) { + DataAccess old = map.remove(da.getName()); if (old == null) - throw new IllegalStateException("Couldn't remove DataAccess: " + name); + throw new IllegalStateException("Couldn't remove DataAccess: " + da.getName()); old.close(); - removeBackingFile(old, name); + removeBackingFile(old, da.getName()); } private void removeBackingFile(DataAccess da, String name) { diff --git a/core/src/main/java/com/graphhopper/storage/GHNodeAccess.java b/core/src/main/java/com/graphhopper/storage/GHNodeAccess.java index 6ad8cdaa3e9..9d0d396e4a0 100644 --- a/core/src/main/java/com/graphhopper/storage/GHNodeAccess.java +++ b/core/src/main/java/com/graphhopper/storage/GHNodeAccess.java @@ -17,8 +17,6 @@ */ package com.graphhopper.storage; -import com.graphhopper.util.Helper; - /** * A helper class for GraphHopperStorage for its node access. *

@@ -26,59 +24,55 @@ * @author Peter Karich */ class GHNodeAccess implements NodeAccess { - private final BaseGraph baseGraph; - private final boolean elevation; + private final BaseGraphNodesAndEdges store; - public GHNodeAccess(BaseGraph baseGraph, boolean withElevation) { - this.baseGraph = baseGraph; - this.elevation = withElevation; + public GHNodeAccess(BaseGraphNodesAndEdges store) { + this.store = store; } @Override public void ensureNode(int nodeId) { - baseGraph.ensureNodeIndex(nodeId); + store.ensureNodeCapacity(nodeId); } @Override public final void setNode(int nodeId, double lat, double lon, double ele) { - baseGraph.ensureNodeIndex(nodeId); - long tmp = baseGraph.toNodePointer(nodeId); - baseGraph.nodes.setInt(tmp + baseGraph.N_LAT, Helper.degreeToInt(lat)); - baseGraph.nodes.setInt(tmp + baseGraph.N_LON, Helper.degreeToInt(lon)); + store.ensureNodeCapacity(nodeId); + store.setLat(store.toNodePointer(nodeId), lat); + store.setLon(store.toNodePointer(nodeId), lon); - if (is3D()) { + if (store.withElevation()) { // meter precision is sufficient for now - baseGraph.nodes.setInt(tmp + baseGraph.N_ELE, Helper.eleToInt(ele)); - baseGraph.bounds.update(lat, lon, ele); - + store.setEle(store.toNodePointer(nodeId), ele); + store.bounds.update(lat, lon, ele); } else { - baseGraph.bounds.update(lat, lon); + store.bounds.update(lat, lon); } } @Override public final double getLat(int nodeId) { - return Helper.intToDegree(baseGraph.nodes.getInt(baseGraph.toNodePointer(nodeId) + baseGraph.N_LAT)); + return store.getLat(store.toNodePointer(nodeId)); } @Override public final double getLon(int nodeId) { - return Helper.intToDegree(baseGraph.nodes.getInt(baseGraph.toNodePointer(nodeId) + baseGraph.N_LON)); + return store.getLon(store.toNodePointer(nodeId)); } @Override public final double getEle(int nodeId) { - if (!elevation) - throw new IllegalStateException("Cannot access elevation - 3D is not enabled"); - - return Helper.intToEle(baseGraph.nodes.getInt(baseGraph.toNodePointer(nodeId) + baseGraph.N_ELE)); + if (!store.withElevation()) + throw new IllegalStateException("elevation is disabled"); + return store.getEle(store.toNodePointer(nodeId)); } + @Override public final void setTurnCostIndex(int index, int turnCostIndex) { - if (baseGraph.supportsTurnCosts()) { - baseGraph.ensureNodeIndex(index); - long tmp = baseGraph.toNodePointer(index); - baseGraph.nodes.setInt(tmp + baseGraph.N_TC, turnCostIndex); + if (store.withTurnCosts()) { + // todo: remove ensure? + store.ensureNodeCapacity(index); + store.setTurnCostRef(store.toNodePointer(index), turnCostIndex); } else { throw new AssertionError("This graph does not support turn costs"); } @@ -86,21 +80,19 @@ public final void setTurnCostIndex(int index, int turnCostIndex) { @Override public final int getTurnCostIndex(int index) { - if (baseGraph.supportsTurnCosts()) - return baseGraph.nodes.getInt(baseGraph.toNodePointer(index) + baseGraph.N_TC); + if (store.withTurnCosts()) + return store.getTurnCostRef(store.toNodePointer(index)); else throw new AssertionError("This graph does not support turn costs"); } @Override public final boolean is3D() { - return elevation; + return store.withElevation(); } @Override public int getDimension() { - if (elevation) - return 3; - return 2; + return store.withElevation() ? 3 : 2; } } diff --git a/core/src/main/java/com/graphhopper/storage/GraphHopperStorage.java b/core/src/main/java/com/graphhopper/storage/GraphHopperStorage.java index b19452f3ae6..887ee3eed8e 100644 --- a/core/src/main/java/com/graphhopper/storage/GraphHopperStorage.java +++ b/core/src/main/java/com/graphhopper/storage/GraphHopperStorage.java @@ -22,6 +22,7 @@ import com.graphhopper.routing.util.EncodingManager; import com.graphhopper.routing.util.FlagEncoder; import com.graphhopper.routing.weighting.Weighting; +import com.graphhopper.util.Constants; import com.graphhopper.util.EdgeExplorer; import com.graphhopper.util.EdgeIteratorState; import com.graphhopper.util.Helper; @@ -159,9 +160,8 @@ protected CHEntry createCHEntry(CHConfig chConfig) { * @see #addCHGraph(CHConfig) */ public GraphHopperStorage addCHGraphs(List chConfigs) { - for (CHConfig chConfig : chConfigs) { + for (CHConfig chConfig : chConfigs) addCHGraph(chConfig); - } return this; } @@ -292,9 +292,7 @@ public GraphHopperStorage create(long byteCount) { properties.put("graph.encoded_values", encodingManager.toEncodedValuesAsString()); properties.put("graph.flag_encoders", encodingManager.toFlagEncodersAsString()); - properties.put("graph.byte_order", dir.getByteOrder()); properties.put("graph.dimension", baseGraph.nodeAccess.getDimension()); - properties.putCurrentVersions(); baseGraph.create(initSize); @@ -333,7 +331,9 @@ public StorableProperties getProperties() { public boolean loadExisting() { baseGraph.checkNotInitialized(); if (properties.loadExisting()) { - properties.checkVersions(false); + if (properties.containsVersion()) + throw new IllegalStateException("The GraphHopper file format is not compatible with the data you are " + + "trying to load. You either need to use an older version of GraphHopper or run a new import"); // check encoding for compatibility String flagEncodersStr = properties.get("graph.flag_encoders"); @@ -352,10 +352,6 @@ public boolean loadExisting() { + "\nChange configuration to match the graph or delete " + dir.getLocation()); } - String byteOrder = properties.get("graph.byte_order"); - if (!byteOrder.equalsIgnoreCase("" + dir.getByteOrder())) - throw new IllegalStateException("Configured graph.byte_order (" + dir.getByteOrder() + ") is not equal to loaded " + byteOrder + ""); - String dim = properties.get("graph.dimension"); baseGraph.loadExisting(dim); @@ -441,7 +437,7 @@ public void close() { } public boolean isClosed() { - return baseGraph.nodes.isClosed(); + return baseGraph.isClosed(); } public long getCapacity() { @@ -498,7 +494,17 @@ public String toString() { + "|" + getDirectory().getDefaultType() + "|" + baseGraph.nodeAccess.getDimension() + "D" + "|" + (baseGraph.supportsTurnCosts() ? baseGraph.turnCostStorage : "no_turn_cost") - + "|" + getProperties().versionsToString(); + + "|" + getVersionsString(); + } + + private String getVersionsString() { + return "nodes:" + Constants.VERSION_NODE + + ",edges:" + Constants.VERSION_EDGE + + ",geometry:" + Constants.VERSION_GEOMETRY + + ",location_index:" + Constants.VERSION_LOCATION_IDX + + ",string_index:" + Constants.VERSION_STRING_IDX + + ",nodesCH:" + Constants.VERSION_NODE_CH + + ",shortcuts:" + Constants.VERSION_SHORTCUT; } // now delegate all Graph methods to BaseGraph to avoid ugly programming flow ala diff --git a/core/src/main/java/com/graphhopper/storage/RoutingCHEdgeIteratorImpl.java b/core/src/main/java/com/graphhopper/storage/RoutingCHEdgeIteratorImpl.java index 0180f96a0b8..f0245508d97 100644 --- a/core/src/main/java/com/graphhopper/storage/RoutingCHEdgeIteratorImpl.java +++ b/core/src/main/java/com/graphhopper/storage/RoutingCHEdgeIteratorImpl.java @@ -54,7 +54,7 @@ public RoutingCHEdgeIterator setBaseNode(int baseNode) { assert baseGraph.isFrozen(); baseIterator.setBaseNode(baseNode); int lastShortcut = store.getLastShortcut(store.toNodePointer(baseNode)); - nextEdgeId = edgeId = lastShortcut < 0 ? baseIterator.edgeId : baseGraph.edgeCount + lastShortcut; + nextEdgeId = edgeId = lastShortcut < 0 ? baseIterator.edgeId : baseGraph.getEdges() + lastShortcut; return this; } @@ -63,13 +63,13 @@ public boolean next() { // we first traverse shortcuts (in decreasing order) and when we are done we use the base iterator to traverse // the base edges as well. shortcuts are filtered using shortcutFilter, but base edges are only filtered by // access/finite weight. - while (nextEdgeId >= baseGraph.edgeCount) { - shortcutPointer = store.toShortcutPointer(nextEdgeId - baseGraph.edgeCount); + while (nextEdgeId >= baseGraph.getEdges()) { + shortcutPointer = store.toShortcutPointer(nextEdgeId - baseGraph.getEdges()); baseNode = store.getNodeA(shortcutPointer); adjNode = store.getNodeB(shortcutPointer); edgeId = nextEdgeId; nextEdgeId--; - if (nextEdgeId < baseGraph.edgeCount || store.getNodeA(store.toShortcutPointer(nextEdgeId - baseGraph.edgeCount)) != baseNode) + if (nextEdgeId < baseGraph.getEdges() || store.getNodeA(store.toShortcutPointer(nextEdgeId - baseGraph.getEdges())) != baseNode) nextEdgeId = baseIterator.edgeId; // todo: note that it would be more efficient (but cost more memory) to separate in/out edges, // especially for edge-based where we do not use bidirectional shortcuts diff --git a/core/src/main/java/com/graphhopper/storage/RoutingCHEdgeIteratorStateImpl.java b/core/src/main/java/com/graphhopper/storage/RoutingCHEdgeIteratorStateImpl.java index 5d2d5801880..b89c9eae1a8 100644 --- a/core/src/main/java/com/graphhopper/storage/RoutingCHEdgeIteratorStateImpl.java +++ b/core/src/main/java/com/graphhopper/storage/RoutingCHEdgeIteratorStateImpl.java @@ -44,11 +44,11 @@ public RoutingCHEdgeIteratorStateImpl(CHStorage store, BaseGraph baseGraph, Base } boolean init(int edge, int expectedAdjNode) { - if (edge < 0 || edge >= baseGraph.edgeCount + store.getShortcuts()) - throw new IllegalArgumentException("edge must be in bounds: [0," + (baseGraph.edgeCount + store.getShortcuts()) + "["); + if (edge < 0 || edge >= baseGraph.getEdges() + store.getShortcuts()) + throw new IllegalArgumentException("edge must be in bounds: [0," + (baseGraph.getEdges() + store.getShortcuts()) + "["); edgeId = edge; if (isShortcut()) { - shortcutPointer = store.toShortcutPointer(edge - baseGraph.edgeCount); + shortcutPointer = store.toShortcutPointer(edge - baseGraph.getEdges()); baseNode = store.getNodeA(shortcutPointer); adjNode = store.getNodeB(shortcutPointer); @@ -102,7 +102,7 @@ public int getAdjNode() { @Override public boolean isShortcut() { - return edgeId >= baseGraph.edgeCount; + return edgeId >= baseGraph.getEdges(); } @Override diff --git a/core/src/main/java/com/graphhopper/storage/StorableProperties.java b/core/src/main/java/com/graphhopper/storage/StorableProperties.java index 9af52d7299f..73519d33a94 100644 --- a/core/src/main/java/com/graphhopper/storage/StorableProperties.java +++ b/core/src/main/java/com/graphhopper/storage/StorableProperties.java @@ -17,7 +17,6 @@ */ package com.graphhopper.storage; -import com.graphhopper.util.Constants; import com.graphhopper.util.Helper; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -127,63 +126,12 @@ public synchronized long getCapacity() { return da.getCapacity(); } - public synchronized void putCurrentVersions() { - put("nodes.version", Constants.VERSION_NODE); - put("edges.version", Constants.VERSION_EDGE); - put("geometry.version", Constants.VERSION_GEOMETRY); - put("location_index.version", Constants.VERSION_LOCATION_IDX); - put("string_index.version", Constants.VERSION_STRING_IDX); - put("shortcuts.version", Constants.VERSION_SHORTCUT); - } - - public synchronized String versionsToString() { - return get("nodes.version") + "," - + get("edges.version") + "," - + get("geometry.version") + "," - + get("location_index.version") + "," - + get("string_index.version"); - } - - public synchronized boolean checkVersions(boolean silent) { - if (!check("nodes", Constants.VERSION_NODE, silent)) - return false; - - if (!check("edges", Constants.VERSION_EDGE, silent)) - return false; - - if (!check("geometry", Constants.VERSION_GEOMETRY, silent)) - return false; - - if (!check("location_index", Constants.VERSION_LOCATION_IDX, silent)) - return false; - - if (!check("string_index", Constants.VERSION_STRING_IDX, silent)) - return false; - - if (!check("shortcuts", Constants.VERSION_SHORTCUT, silent)) - return false; - return true; - } - - boolean check(String key, int vers, boolean silent) { - String str = get(key + ".version"); - if (!str.equals(vers + "")) { - if (silent) - return false; - - throw new IllegalStateException("Version of " + key + " unsupported: " + str + ", expected:" + vers + ". " - + "Make sure you are using the same GraphHopper version for reading the files that was used for creating them. " - + "See https://discuss.graphhopper.com/t/722"); - } - return true; - } - public synchronized boolean containsVersion() { return map.containsKey("nodes.version") || - map.containsKey("edges.version") || - map.containsKey("geometry.version") || - map.containsKey("location_index.version") || - map.containsKey("string_index.version"); + map.containsKey("edges.version") || + map.containsKey("geometry.version") || + map.containsKey("location_index.version") || + map.containsKey("string_index.version"); } @Override diff --git a/core/src/main/java/com/graphhopper/storage/TurnCostStorage.java b/core/src/main/java/com/graphhopper/storage/TurnCostStorage.java index 44ff63b77d9..8464898290b 100644 --- a/core/src/main/java/com/graphhopper/storage/TurnCostStorage.java +++ b/core/src/main/java/com/graphhopper/storage/TurnCostStorage.java @@ -239,7 +239,7 @@ public interface TurnRelationIterator { private class Itr implements TurnRelationIterator { private int viaNode = -1; private int turnCostIndex = -1; - private IntsRef intsRef = TurnCost.createFlags(); + private final IntsRef intsRef = TurnCost.createFlags(); private long turnCostPtr() { return (long) turnCostIndex * BYTES_PER_ENTRY; diff --git a/core/src/main/java/com/graphhopper/storage/index/LineIntIndex.java b/core/src/main/java/com/graphhopper/storage/index/LineIntIndex.java index 4237dc38fe6..ce687f468e8 100644 --- a/core/src/main/java/com/graphhopper/storage/index/LineIntIndex.java +++ b/core/src/main/java/com/graphhopper/storage/index/LineIntIndex.java @@ -24,14 +24,14 @@ import com.graphhopper.storage.DAType; import com.graphhopper.storage.DataAccess; import com.graphhopper.storage.Directory; +import com.graphhopper.util.Constants; +import com.graphhopper.util.GHUtility; import com.graphhopper.util.Helper; import com.graphhopper.util.shapes.BBox; import java.util.function.IntConsumer; public class LineIntIndex { - - private final int MAGIC_INT = Integer.MAX_VALUE / 22318; // do not start with 0 as a positive value means leaf and a negative means "entry with subentries" static final int START_POINTER = 1; @@ -49,7 +49,7 @@ public class LineIntIndex { public LineIntIndex(BBox bBox, Directory dir, String name) { this.bounds = bBox; - this.dataAccess = dir.find(name, DAType.getPreferredInt(dir.getDefaultType())); + this.dataAccess = dir.create(name, DAType.getPreferredInt(dir.getDefaultType())); } public boolean loadExisting() { @@ -59,9 +59,7 @@ public boolean loadExisting() { if (!dataAccess.loadExisting()) return false; - if (dataAccess.getHeader(0) != MAGIC_INT) - throw new IllegalStateException("incorrect location index version, expected:" + MAGIC_INT); - + GHUtility.checkDAVersion("location_index", Constants.VERSION_LOCATION_IDX, dataAccess.getHeader(0)); checksum = dataAccess.getHeader(1 * 4); minResolutionInMeter = dataAccess.getHeader(2 * 4); indexStructureInfo = IndexStructureInfo.create(bounds, minResolutionInMeter); @@ -180,9 +178,9 @@ public void onEdge(int edgeId) { } private void query(int intPointer, BBox queryBBox, - double minLat, double minLon, - double deltaLatPerDepth, double deltaLonPerDepth, - LocationIndex.Visitor function, int depth) { + double minLat, double minLon, + double deltaLatPerDepth, double deltaLonPerDepth, + LocationIndex.Visitor function, int depth) { long pointer = (long) intPointer * 4; if (depth == entries.length) { int nextIntPointer = dataAccess.getInt(pointer); @@ -225,11 +223,11 @@ private void query(int intPointer, BBox queryBBox, /** * This method collects edge ids from the neighborhood of a point and puts them into foundEntries. - * + *

* If it is called with iteration = 0, it just looks in the tile the query point is in. * If it is called with iteration = 0,1,2,.., it will look in additional tiles further and further * from the start tile. (In a square that grows by one pixel in all four directions per iteration). - * + *

* See discussion at issue #221. *

*/ @@ -278,7 +276,7 @@ public void setMinResolutionInMeter(int minResolutionInMeter) { } public void flush() { - dataAccess.setHeader(0, MAGIC_INT); + dataAccess.setHeader(0, Constants.VERSION_LOCATION_IDX); dataAccess.setHeader(1 * 4, checksum); dataAccess.setHeader(2 * 4, minResolutionInMeter); diff --git a/core/src/main/java/com/graphhopper/storage/index/LocationIndexTree.java b/core/src/main/java/com/graphhopper/storage/index/LocationIndexTree.java index 41c14f11919..17d0e807f08 100644 --- a/core/src/main/java/com/graphhopper/storage/index/LocationIndexTree.java +++ b/core/src/main/java/com/graphhopper/storage/index/LocationIndexTree.java @@ -371,58 +371,59 @@ public interface EdgeCheck { public void traverseEdge(double queryLat, double queryLon, EdgeIteratorState currEdge, EdgeCheck edgeCheck) { int baseNode = currEdge.getBaseNode(); - double currLat = nodeAccess.getLat(baseNode); - double currLon = nodeAccess.getLon(baseNode); - double currNormedDist = DIST_PLANE.calcNormalizedDist(queryLat, queryLon, currLat, currLon); - - int tmpClosestNode = baseNode; - edgeCheck.check(tmpClosestNode, currNormedDist, 0, Snap.Position.TOWER); - if (currNormedDist <= equalNormedDelta) - return; + double baseLat = nodeAccess.getLat(baseNode); + double baseLon = nodeAccess.getLon(baseNode); + double baseDist = DIST_PLANE.calcNormalizedDist(queryLat, queryLon, baseLat, baseLon); int adjNode = currEdge.getAdjNode(); double adjLat = nodeAccess.getLat(adjNode); double adjLon = nodeAccess.getLon(adjNode); - double adjDist = DIST_PLANE.calcNormalizedDist(adjLat, adjLon, queryLat, queryLon); - // if there are wayPoints this is only an approximation - if (adjDist < currNormedDist) - tmpClosestNode = adjNode; - - double tmpLat = currLat; - double tmpLon = currLon; - double tmpNormedDist; + double adjDist = DIST_PLANE.calcNormalizedDist(queryLat, queryLon, adjLat, adjLon); + PointList pointList = currEdge.fetchWayGeometry(FetchMode.PILLAR_AND_ADJ); - int len = pointList.size(); - for (int pointIndex = 0; pointIndex < len; pointIndex++) { - double wayLat = pointList.getLat(pointIndex); - double wayLon = pointList.getLon(pointIndex); - Snap.Position pos = Snap.Position.EDGE; - if (DIST_PLANE.isCrossBoundary(tmpLon, wayLon)) { - tmpLat = wayLat; - tmpLon = wayLon; + final int len = pointList.size(); + + int closestTowerNode; + double closestDist; + if (baseDist < adjDist) { + closestTowerNode = baseNode; + closestDist = baseDist; + edgeCheck.check(baseNode, baseDist, 0, Snap.Position.TOWER); + } else { + closestTowerNode = adjNode; + closestDist = adjDist; + edgeCheck.check(adjNode, adjDist, len, Snap.Position.TOWER); + } + if (closestDist <= equalNormedDelta) + // if a tower node is close to the query point we stop + return; + + double lastLat = baseLat; + double lastLon = baseLon; + for (int i = 0; i < len; i++) { + double lat = pointList.getLat(i); + double lon = pointList.getLon(i); + if (DIST_PLANE.isCrossBoundary(lastLon, lon)) { + lastLat = lat; + lastLon = lon; continue; } - if (DIST_PLANE.validEdgeDistance(queryLat, queryLon, tmpLat, tmpLon, wayLat, wayLon)) { - tmpNormedDist = DIST_PLANE.calcNormalizedEdgeDistance(queryLat, queryLon, - tmpLat, tmpLon, wayLat, wayLon); - edgeCheck.check(tmpClosestNode, tmpNormedDist, pointIndex, pos); + // +1 because we skipped the base node + final int indexInFullPointList = i + 1; + if (DIST_PLANE.validEdgeDistance(queryLat, queryLon, lastLat, lastLon, lat, lon)) { + closestDist = DIST_PLANE.calcNormalizedEdgeDistance(queryLat, queryLon, lastLat, lastLon, lat, lon); + edgeCheck.check(closestTowerNode, closestDist, indexInFullPointList - 1, Snap.Position.EDGE); + } else if (i < len - 1) { + closestDist = DIST_PLANE.calcNormalizedDist(queryLat, queryLon, lat, lon); + edgeCheck.check(closestTowerNode, closestDist, indexInFullPointList, Snap.Position.PILLAR); } else { - if (pointIndex + 1 == len) { - tmpNormedDist = adjDist; - pos = Snap.Position.TOWER; - } else { - tmpNormedDist = DIST_PLANE.calcNormalizedDist(queryLat, queryLon, wayLat, wayLon); - pos = Snap.Position.PILLAR; - } - edgeCheck.check(tmpClosestNode, tmpNormedDist, pointIndex + 1, pos); + // we snapped onto the last tower node, but we already handled this before so do nothing } - - if (tmpNormedDist <= equalNormedDelta) + if (closestDist <= equalNormedDelta) return; - - tmpLat = wayLat; - tmpLon = wayLon; + lastLat = lat; + lastLon = lon; } } diff --git a/core/src/main/java/com/graphhopper/util/Constants.java b/core/src/main/java/com/graphhopper/util/Constants.java index 74ca9bd43e7..2f32414bb91 100644 --- a/core/src/main/java/com/graphhopper/util/Constants.java +++ b/core/src/main/java/com/graphhopper/util/Constants.java @@ -66,12 +66,13 @@ public class Constants { private static final int JVM_MAJOR_VERSION; private static final int JVM_MINOR_VERSION; - public static final int VERSION_NODE = 7; - public static final int VERSION_EDGE = 20; - public static final int VERSION_SHORTCUT = 7; - public static final int VERSION_GEOMETRY = 5; - public static final int VERSION_LOCATION_IDX = 4; - public static final int VERSION_STRING_IDX = 5; + public static final int VERSION_NODE = 8; + public static final int VERSION_EDGE = 21; + public static final int VERSION_SHORTCUT = 8; + public static final int VERSION_NODE_CH = 0; + public static final int VERSION_GEOMETRY = 6; + public static final int VERSION_LOCATION_IDX = 5; + public static final int VERSION_STRING_IDX = 6; /** * The version without the snapshot string */ diff --git a/core/src/main/java/com/graphhopper/util/GHUtility.java b/core/src/main/java/com/graphhopper/util/GHUtility.java index 25aa861d120..378540018cf 100644 --- a/core/src/main/java/com/graphhopper/util/GHUtility.java +++ b/core/src/main/java/com/graphhopper/util/GHUtility.java @@ -435,6 +435,15 @@ public static int getAdjNode(Graph g, int edge, int adjNode) { return adjNode; } + public static void checkDAVersion(String name, int expectedVersion, int version) { + if (version != expectedVersion) { + throw new IllegalStateException("Unexpected version for '" + name + "'. Got: " + version + ", " + + "expected: " + expectedVersion + ". " + + "Make sure you are using the same GraphHopper version for reading the files that was used for creating them. " + + "See https://discuss.graphhopper.com/t/722"); + } + } + public static EdgeIteratorState createMockedEdgeIteratorState(final double distance, final IntsRef flags) { return createMockedEdgeIteratorState(distance, flags, 0, 1, 2, 3, 4); } @@ -514,15 +523,6 @@ public int getOrigEdgeLast() { }; } - public static void checkDAVersion(String name, int expectedVersion, int version) { - if (version != expectedVersion) { - throw new IllegalStateException("Unexpected version for '" + name + "'. Got: " + version + ", " + - "expected: " + expectedVersion + ". " - + "Make sure you are using the same GraphHopper version for reading the files that was used for creating them. " - + "See https://discuss.graphhopper.com/t/722"); - } - } - /** * @return the the edge between base and adj, or null if there is no such edge * @throws IllegalArgumentException when there are multiple edges diff --git a/core/src/test/java/com/graphhopper/GraphHopperProfileTest.java b/core/src/test/java/com/graphhopper/GraphHopperProfileTest.java index d5569597e07..b2c9c6edf55 100644 --- a/core/src/test/java/com/graphhopper/GraphHopperProfileTest.java +++ b/core/src/test/java/com/graphhopper/GraphHopperProfileTest.java @@ -52,16 +52,11 @@ public void deserialize() throws IOException { @Test public void duplicateProfileName_error() { final GraphHopper hopper = createHopper(); - assertIllegalArgument(new Runnable() { - @Override - public void run() { - hopper.setProfiles( - new Profile("my_profile").setVehicle("car").setWeighting("fastest"), - new Profile("your_profile").setVehicle("car").setWeighting("short_fastest"), - new Profile("my_profile").setVehicle("car").setWeighting("shortest") - ); - } - }, "Profile names must be unique. Duplicate name: 'my_profile'"); + assertIllegalArgument(() -> hopper.setProfiles( + new Profile("my_profile").setVehicle("car").setWeighting("fastest"), + new Profile("your_profile").setVehicle("car").setWeighting("short_fastest"), + new Profile("my_profile").setVehicle("car").setWeighting("shortest") + ), "Profile names must be unique. Duplicate name: 'my_profile'"); } @Test @@ -70,25 +65,14 @@ public void vehicleDoesNotExist_error() { hopper.getEncodingManagerBuilder().add(new CarFlagEncoder()); hopper.setGraphHopperLocation(GH_LOCATION).setStoreOnFlush(false). setProfiles(new Profile("profile").setVehicle("your_car")); - assertIllegalArgument(new Runnable() { - @Override - public void run() { - hopper.load(GH_LOCATION); - } - }, "entry in encoder list not supported: your_car"); + assertIllegalArgument(hopper::load, "entry in encoder list not supported: your_car"); } @Test public void vehicleDoesNotExist_error2() { final GraphHopper hopper = new GraphHopper().setGraphHopperLocation(GH_LOCATION).setStoreOnFlush(false). setProfiles(new Profile("profile").setVehicle("your_car")); - - assertIllegalArgument(new Runnable() { - @Override - public void run() { - hopper.load(GH_LOCATION); - } - }, "entry in encoder list not supported: your_car"); + assertIllegalArgument(hopper::load, "entry in encoder list not supported: your_car"); } @Test @@ -97,7 +81,7 @@ public void oneVehicleTwoProfilesWithAndWithoutTC_noError() { hopper.setProfiles( new Profile("profile1").setVehicle("car").setTurnCosts(false), new Profile("profile2").setVehicle("car").setTurnCosts(true)); - hopper.load(GH_LOCATION); + hopper.load(); } @Test @@ -106,19 +90,14 @@ public void oneVehicleTwoProfilesWithAndWithoutTC2_noError() { hopper.setProfiles( new Profile("profile2").setVehicle("car").setTurnCosts(true), new Profile("profile1").setVehicle("car").setTurnCosts(false)); - hopper.load(GH_LOCATION); + hopper.load(); } @Test public void profileWithUnknownWeighting_error() { final GraphHopper hopper = createHopper(); hopper.setProfiles(new Profile("profile").setVehicle("car").setWeighting("your_weighting")); - assertIllegalArgument(new Runnable() { - @Override - public void run() { - hopper.load(GH_LOCATION); - } - }, + assertIllegalArgument(hopper::load, "Could not create weighting for profile: 'profile'", "Weighting 'your_weighting' not supported" ); @@ -129,12 +108,7 @@ public void chProfileDoesNotExist_error() { final GraphHopper hopper = createHopper(); hopper.setProfiles(new Profile("profile1").setVehicle("car")); hopper.getCHPreparationHandler().setCHProfiles(new CHProfile("other_profile")); - assertIllegalArgument(new Runnable() { - @Override - public void run() { - hopper.load(GH_LOCATION); - } - }, "CH profile references unknown profile 'other_profile'"); + assertIllegalArgument(hopper::load, "CH profile references unknown profile 'other_profile'"); } @Test @@ -145,12 +119,7 @@ public void duplicateCHProfile_error() { new CHProfile("profile"), new CHProfile("profile") ); - assertIllegalArgument(new Runnable() { - @Override - public void run() { - hopper.load(GH_LOCATION); - } - }, "Duplicate CH reference to profile 'profile'"); + assertIllegalArgument(hopper::load, "Duplicate CH reference to profile 'profile'"); } @Test @@ -158,12 +127,7 @@ public void lmProfileDoesNotExist_error() { final GraphHopper hopper = createHopper(); hopper.setProfiles(new Profile("profile1").setVehicle("car")); hopper.getLMPreparationHandler().setLMProfiles(new LMProfile("other_profile")); - assertIllegalArgument(new Runnable() { - @Override - public void run() { - hopper.load(GH_LOCATION); - } - }, "LM profile references unknown profile 'other_profile'"); + assertIllegalArgument(hopper::load, "LM profile references unknown profile 'other_profile'"); } @Test @@ -174,12 +138,7 @@ public void duplicateLMProfile_error() { new LMProfile("profile"), new LMProfile("profile") ); - assertIllegalArgument(new Runnable() { - @Override - public void run() { - hopper.load(GH_LOCATION); - } - }, "Multiple LM profiles are using the same profile 'profile'"); + assertIllegalArgument(hopper::load, "Multiple LM profiles are using the same profile 'profile'"); } @Test @@ -189,12 +148,7 @@ public void unknownLMPreparationProfile_error() { hopper.getLMPreparationHandler().setLMProfiles( new LMProfile("profile").setPreparationProfile("xyz") ); - assertIllegalArgument(new Runnable() { - @Override - public void run() { - hopper.load(GH_LOCATION); - } - }, "LM profile references unknown preparation profile 'xyz'"); + assertIllegalArgument(hopper::load, "LM profile references unknown preparation profile 'xyz'"); } @Test @@ -210,12 +164,7 @@ public void lmPreparationProfileChain_error() { new LMProfile("profile2").setPreparationProfile("profile1"), new LMProfile("profile3").setPreparationProfile("profile2") ); - assertIllegalArgument(new Runnable() { - @Override - public void run() { - hopper.load(GH_LOCATION); - } - }, "Cannot use 'profile2' as preparation_profile for LM profile 'profile3', because it uses another profile for preparation itself."); + assertIllegalArgument(hopper::load, "Cannot use 'profile2' as preparation_profile for LM profile 'profile3', because it uses another profile for preparation itself."); } @Test @@ -229,12 +178,7 @@ public void noLMProfileForPreparationProfile_error() { hopper.getLMPreparationHandler().setLMProfiles( new LMProfile("profile1").setPreparationProfile("profile2") ); - assertIllegalArgument(new Runnable() { - @Override - public void run() { - hopper.load(GH_LOCATION); - } - }, "Unknown LM preparation profile 'profile2' in LM profile 'profile1' cannot be used as preparation_profile"); + assertIllegalArgument(hopper::load, "Unknown LM preparation profile 'profile2' in LM profile 'profile1' cannot be used as preparation_profile"); } private GraphHopper createHopper() { diff --git a/core/src/test/java/com/graphhopper/GraphHopperTest.java b/core/src/test/java/com/graphhopper/GraphHopperTest.java index 517e763bc96..1f695e917b8 100644 --- a/core/src/test/java/com/graphhopper/GraphHopperTest.java +++ b/core/src/test/java/com/graphhopper/GraphHopperTest.java @@ -90,12 +90,12 @@ public void setup() { @ParameterizedTest @CsvSource({ - DIJKSTRA + ",false,505", - ASTAR + ",false,438", - DIJKSTRA_BI + ",false,224", - ASTAR_BI + ",false,180", - ASTAR_BI + ",true,41", - DIJKSTRA_BI + ",true,41" + DIJKSTRA + ",false,511", + ASTAR + ",false,444", + DIJKSTRA_BI + ",false,228", + ASTAR_BI + ",false,184", + ASTAR_BI + ",true,36", + DIJKSTRA_BI + ",true,30" }) public void testMonacoDifferentAlgorithms(String algo, boolean withCH, int expectedVisitedNodes) { final String vehicle = "car"; @@ -142,7 +142,7 @@ public void testMonacoWithInstructions() { setAlgorithm(ASTAR).setProfile(profile)); // identify the number of counts to compare with CH foot route - assertEquals(700, rsp.getHints().getLong("visited_nodes.sum", 0)); + assertEquals(706, rsp.getHints().getLong("visited_nodes.sum", 0)); ResponsePath res = rsp.getBest(); assertEquals(3437.1, res.getDistance(), .1); @@ -230,7 +230,7 @@ public void testUTurn() { request.addPoint(new GHPoint(43.743887, 7.431151)); request.addPoint(new GHPoint(43.744007, 7.431076)); //Force initial U-Turn - request.setHeadings(Arrays.asList(200., Double.NaN)); + request.setHeadings(Arrays.asList(200.)); request.setAlgorithm(ASTAR).setProfile(profile); GHResponse rsp = hopper.route(request); @@ -238,12 +238,12 @@ public void testUTurn() { assertFalse(rsp.hasErrors()); ResponsePath res = rsp.getBest(); InstructionList il = res.getInstructions(); - assertEquals(3, il.size()); + assertEquals(4, il.size()); // Initial U-turn - assertEquals("make a U-turn onto Avenue Princesse Grace", il.get(0).getTurnDescription(tr)); - // Second U-turn to get to destination assertEquals("make a U-turn onto Avenue Princesse Grace", il.get(1).getTurnDescription(tr)); + // Second U-turn to get to destination + assertEquals("make a U-turn onto Avenue Princesse Grace", il.get(2).getTurnDescription(tr)); } private void testImportCloseAndLoad(boolean ch, boolean lm, boolean sort, boolean custom) { @@ -1024,47 +1024,9 @@ public void testSRTMWithInstructions() { assertEquals(52, res.getPoints().get(10).getEle(), 1e-2); } - @Test - public void testSRTMWithoutTunnelInterpolation() { - final String profile = "profile"; - final String vehicle = "foot"; - final String weighting = "shortest"; - - GraphHopper hopper = new GraphHopper(); - hopper.getEncodingManagerBuilder().add(new OSMRoadEnvironmentParser() { - @Override - public IntsRef handleWayTags(IntsRef edgeFlags, ReaderWay readerWay, boolean ferry, IntsRef relationFlags) { - // do not change RoadEnvironment to avoid triggering tunnel interpolation - return edgeFlags; - } - }).addIfAbsent(new DefaultFlagEncoderFactory(), vehicle); - hopper.setOSMFile(MONACO) - .setStoreOnFlush(true) - .setGraphHopperLocation(GH_LOCATION) - .setProfiles(new Profile(profile).setVehicle(vehicle).setWeighting(weighting)); - - hopper.setElevationProvider(new SRTMProvider(DIR)); - hopper.importOrLoad(); - - GHResponse rsp = hopper.route(new GHRequest(43.74056471749763, 7.4299266210693755, - 43.73790260334179, 7.427984089259056).setAlgorithm(ASTAR) - .setProfile(profile)); - ResponsePath res = rsp.getBest(); - assertEquals(356.5, res.getDistance(), .1); - PointList pointList = res.getPoints(); - assertEquals(6, pointList.size()); - assertTrue(pointList.is3D()); - - assertEquals(20.0, pointList.getEle(0), .1); - assertEquals(23.0, pointList.getEle(1), .1); - assertEquals(23.0, pointList.getEle(2), .1); - assertEquals(41.0, pointList.getEle(3), .1); - assertEquals(19.0, pointList.getEle(4), .1); - assertEquals(26.5, pointList.getEle(5), .1); - } - - @Test - public void testSRTMWithTunnelInterpolation() { + @ParameterizedTest + @ValueSource(booleans = {true, false}) + public void testSRTMWithTunnelInterpolation(boolean withTunnelInterpolation) { final String profile = "profile"; final String vehicle = "foot"; final String weighting = "shortest"; @@ -1072,29 +1034,53 @@ public void testSRTMWithTunnelInterpolation() { GraphHopper hopper = new GraphHopper(). setGraphHopperLocation(GH_LOCATION). setOSMFile(MONACO). - setProfiles(new Profile(profile).setVehicle(vehicle).setWeighting(weighting), - new Profile("car").setVehicle("foot").setWeighting(weighting)). + setProfiles(new Profile(profile).setVehicle(vehicle).setWeighting(weighting)). setStoreOnFlush(true); + if (!withTunnelInterpolation) { + hopper.getEncodingManagerBuilder().add(new OSMRoadEnvironmentParser() { + @Override + public IntsRef handleWayTags(IntsRef edgeFlags, ReaderWay readerWay, boolean ferry, IntsRef relationFlags) { + // do not change RoadEnvironment to avoid triggering tunnel interpolation + return edgeFlags; + } + }).addIfAbsent(new DefaultFlagEncoderFactory(), vehicle); + } + hopper.setElevationProvider(new SRTMProvider(DIR)); hopper.importOrLoad(); - GHResponse rsp = hopper.route(new GHRequest(43.74056471749763, 7.4299266210693755, - 43.73790260334179, 7.427984089259056).setAlgorithm(ASTAR) + GHPoint from = new GHPoint(43.7405647, 7.4299266); + GHPoint to = new GHPoint(43.7378990, 7.4279780); + + // make sure we hit tower nodes, because all we really want is test the elevation interpolation + assertEquals(Snap.Position.TOWER, hopper.getLocationIndex().findClosest(from.lat, from.lon, EdgeFilter.ALL_EDGES).getSnappedPosition()); + assertEquals(Snap.Position.TOWER, hopper.getLocationIndex().findClosest(to.lat, to.lon, EdgeFilter.ALL_EDGES).getSnappedPosition()); + + GHResponse rsp = hopper.route(new GHRequest(from, to) .setProfile(profile)); ResponsePath res = rsp.getBest(); - // Without interpolation: 356.5 - assertEquals(351, res.getDistance(), .1); PointList pointList = res.getPoints(); assertEquals(6, pointList.size()); assertTrue(pointList.is3D()); - assertEquals(18, pointList.getEle(0), .1); - assertEquals(19.04, pointList.getEle(1), .1); - assertEquals(21.67, pointList.getEle(2), .1); - assertEquals(25.03, pointList.getEle(3), .1); - assertEquals(28.65, pointList.getEle(4), .1); - assertEquals(31.32, pointList.getEle(5), .1); + if (withTunnelInterpolation) { + assertEquals(351.8, res.getDistance(), .1); + assertEquals(17, pointList.getEle(0), .1); + assertEquals(19.04, pointList.getEle(1), .1); + assertEquals(21.67, pointList.getEle(2), .1); + assertEquals(25.03, pointList.getEle(3), .1); + assertEquals(28.65, pointList.getEle(4), .1); + assertEquals(34.00, pointList.getEle(5), .1); + } else { + assertEquals(358.3, res.getDistance(), .1); + assertEquals(17.0, pointList.getEle(0), .1); + assertEquals(23.0, pointList.getEle(1), .1); + assertEquals(23.0, pointList.getEle(2), .1); + assertEquals(41.0, pointList.getEle(3), .1); + assertEquals(19.0, pointList.getEle(4), .1); + assertEquals(34.0, pointList.getEle(5), .1); + } } @Test @@ -1107,8 +1093,10 @@ public void testSRTMWithLongEdgeSampling() { setGraphHopperLocation(GH_LOCATION). setOSMFile(MONACO). setStoreOnFlush(true). - setElevationWayPointMaxDistance(1). - setProfiles(new Profile("profile").setVehicle(vehicle).setWeighting(weighting)). + setProfiles(new Profile("profile").setVehicle(vehicle).setWeighting(weighting)); + hopper.getRouterConfig().setElevationWayPointMaxDistance(1.); + hopper.getReaderConfig(). + setElevationMaxWayPointDistance(1.). setLongEdgeSamplingDistance(30); SRTMProvider elevationProvider = new SRTMProvider(DIR); @@ -1564,17 +1552,17 @@ public void testCrossQuery() { hopper.importOrLoad(); // flex - testCrossQueryAssert(profile1, hopper, 528.3, 160, true); - testCrossQueryAssert(profile2, hopper, 635.8, 158, true); - testCrossQueryAssert(profile3, hopper, 815.2, 154, true); + testCrossQueryAssert(profile1, hopper, 528.3, 166, true); + testCrossQueryAssert(profile2, hopper, 635.8, 160, true); + testCrossQueryAssert(profile3, hopper, 815.2, 158, true); // LM (should be the same as flex, but with less visited nodes!) testCrossQueryAssert(profile1, hopper, 528.3, 74, false); - testCrossQueryAssert(profile2, hopper, 635.8, 82, false); + testCrossQueryAssert(profile2, hopper, 635.8, 124, false); // this is actually interesting: the number of visited nodes *increases* once again (while it strictly decreases // with rising distance factor for flex): cross-querying 'works', but performs *worse*, because the landmarks // were not customized for the weighting in use. Creating a separate LM preparation for profile3 yields 74 - testCrossQueryAssert(profile3, hopper, 815.2, 148, false); + testCrossQueryAssert(profile3, hopper, 815.2, 162, false); } private void testCrossQueryAssert(String profile, GraphHopper hopper, double expectedWeight, int expectedVisitedNodes, boolean disableLM) { @@ -2261,8 +2249,9 @@ void interpolateBridgesTunnelsAndFerries() { } }. setProfiles(new Profile("profile")). - setElevation(true); - hopper.load(GH_LOCATION); + setElevation(true). + setGraphHopperLocation(GH_LOCATION); + hopper.load(); } assertEquals(1, counter.get()); } @@ -2308,6 +2297,78 @@ public void issue2306_2() { } } + @Test + public void testBarriers() { + GraphHopper hopper = new GraphHopper(). + setGraphHopperLocation(GH_LOCATION). + setOSMFile("../map-matching/files/leipzig_germany.osm.pbf"). + setProfiles( + new Profile("car").setVehicle("car").setWeighting("fastest"), + new Profile("bike").setVehicle("bike").setWeighting("fastest"), + new Profile("foot").setVehicle("foot").setWeighting("fastest") + ). + setMinNetworkSize(0); + hopper.importOrLoad(); + + { + // the bollard blocks the road for bikes, and we need to take a big detour. note that this bollard connects + // two ways + GHResponse bikeRsp = hopper.route(new GHRequest(51.257709, 12.309269, 51.257594, 12.308882).setProfile("bike")); + assertEquals(1185, bikeRsp.getBest().getDistance(), 1); + // pedestrians can just pass the bollard + GHResponse footRsp = hopper.route(new GHRequest(51.257709, 12.309269, 51.257594, 12.308882).setProfile("foot")); + assertEquals(28, footRsp.getBest().getDistance(), 1); + } + + { + // here the bollard blocks the road for cars + GHResponse carRsp = hopper.route(new GHRequest(51.301113, 12.432168, 51.30123, 12.431728).setProfile("car")); + assertEquals(368, carRsp.getBest().getDistance(), 1); + // ... but not for bikes + GHResponse bikeRsp = hopper.route(new GHRequest(51.301113, 12.432168, 51.30123, 12.431728).setProfile("bike")); + assertEquals(48, bikeRsp.getBest().getDistance(), 1); + } + + { + // cars need to take a detour to the south (on newer maps an even bigger detour going north is necessary) + GHResponse carRsp = hopper.route(new GHRequest(51.350105, 12.289968, 51.350246, 12.287779).setProfile("car")); + assertEquals(285, carRsp.getBest().getDistance(), 1); + // ... bikes can just pass the bollard + GHResponse bikeRsp = hopper.route(new GHRequest(51.350105, 12.289968, 51.350246, 12.287779).setProfile("bike")); + assertEquals(152, bikeRsp.getBest().getDistance(), 1); + } + + { + // these are bollards that are located right on a junction. this should never happen according to OSM mapping + // rules, but it still does. the problem with such barriers is that we can only block one direction and it + // is unclear which one is right. therefore we simply ignore such barriers. + + // here the barrier node actually disconnected a dead-end road that should rather be connected before we + // started ignoring barriers at junctions. + GHResponse carRsp = hopper.route(new GHRequest(51.327121, 12.572396, 51.327173, 12.574038).setProfile("car")); + assertEquals(124, carRsp.getBest().getDistance(), 1); + GHResponse bikeRsp = hopper.route(new GHRequest(51.327121, 12.572396, 51.327173, 12.574038).setProfile("bike")); + assertEquals(124, bikeRsp.getBest().getDistance(), 1); + + // Here the barrier could prevent us from travelling straight along Pufendorfstraße. But it could also + // prevent us from turning from Pufendorfstraße onto 'An der Streuobstwiese' (or vice versa). What should + // be allowed depends on whether the barrier is before or behind the junction. And since we can't tell + // we just ignore this barrier. Note that the mapping was fixed in newer OSM versions, so the barrier is no + // longer at the junction + carRsp = hopper.route(new GHRequest(51.344134, 12.317986, 51.344231, 12.317482).setProfile("car")); + assertEquals(36, carRsp.getBest().getDistance(), 1); + bikeRsp = hopper.route(new GHRequest(51.344134, 12.317986, 51.344231, 12.317482).setProfile("bike")); + assertEquals(36, bikeRsp.getBest().getDistance(), 1); + + // Here we'd have to go all the way around, but the bollard node could also mean that continuing on Adenauerallee + // is fine, and we just cannot enter the little path. Since we cannot tell we just ignore this barrier. + carRsp = hopper.route(new GHRequest(51.355455, 12.40202, 51.355318, 12.401741).setProfile("car")); + assertEquals(24, carRsp.getBest().getDistance(), 1); + bikeRsp = hopper.route(new GHRequest(51.355455, 12.40202, 51.355318, 12.401741).setProfile("bike")); + assertEquals(24, bikeRsp.getBest().getDistance(), 1); + } + } + @Test public void germanyCountryRuleAvoidsTracks() { final String profile = "profile"; diff --git a/core/src/test/java/com/graphhopper/coll/OSMIDMapTest.java b/core/src/test/java/com/graphhopper/coll/OSMIDMapTest.java deleted file mode 100644 index fa7128f4f8f..00000000000 --- a/core/src/test/java/com/graphhopper/coll/OSMIDMapTest.java +++ /dev/null @@ -1,138 +0,0 @@ -/* - * 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.coll; - -import com.graphhopper.storage.DataAccess; -import com.graphhopper.storage.RAMDirectory; -import org.junit.jupiter.api.Test; - -import static org.junit.jupiter.api.Assertions.assertEquals; - -/** - * @author Peter Karich - */ -public class OSMIDMapTest { - @Test - public void testGet() { - OSMIDMap map = new OSMIDMap(new RAMDirectory()); - map.put(9, 0); - map.put(10, -50); - map.put(11, 2); - map.put(12, 3); - map.put(20, 6); - map.put(21, 5); - map.put(31, 2); - - assertEquals(7, map.getSize()); - assertEquals(-1, map.get(8)); - assertEquals(0, map.get(9)); - assertEquals(-50, map.get(10)); - assertEquals(2, map.get(11)); - assertEquals(3, map.get(12)); - assertEquals(-1, map.get(13)); - assertEquals(-1, map.get(19)); - assertEquals(6, map.get(20)); - assertEquals(5, map.get(21)); - assertEquals(2, map.get(31)); - assertEquals(-1, map.get(32)); - - for (int i = 0; i < 50; i++) { - map.put(i + 50, i + 7); - } - assertEquals(57, map.getSize()); - } - - @Test - public void testBinSearch() { - DataAccess da = new RAMDirectory().find(""); - da.create(100); - - da.setInt(0 * 4, 1); - da.setInt(1 * 4, 0); - - da.setInt(2 * 4, 5); - da.setInt(3 * 4, 0); - - da.setInt(4 * 4, 100); - da.setInt(5 * 4, 0); - - da.setInt(6 * 4, 300); - da.setInt(7 * 4, 0); - - da.setInt(8 * 4, 333); - da.setInt(9 * 4, 0); - - assertEquals(2, OSMIDMap.binarySearch(da, 0, 5, 100)); - assertEquals(3, OSMIDMap.binarySearch(da, 0, 5, 300)); - assertEquals(~3, OSMIDMap.binarySearch(da, 0, 5, 200)); - assertEquals(0, OSMIDMap.binarySearch(da, 0, 5, 1)); - assertEquals(1, OSMIDMap.binarySearch(da, 0, 5, 5)); - } - - @Test - public void testGetLong() { - OSMIDMap map = new OSMIDMap(new RAMDirectory()); - map.put(12, 0); - map.put(Long.MAX_VALUE / 10, 1); - map.put(Long.MAX_VALUE / 9, 2); - map.put(Long.MAX_VALUE / 7, 3); - - assertEquals(1, map.get(Long.MAX_VALUE / 10)); - assertEquals(3, map.get(Long.MAX_VALUE / 7)); - assertEquals(-1, map.get(13)); - } - - @Test - public void testGet2() { - OSMIDMap map = new OSMIDMap(new RAMDirectory()); - map.put(9, 0); - map.put(10, 1); - map.put(11, 2); - map.put(12, 3); - map.put(13, 4); - map.put(14, 5); - map.put(16, 6); - map.put(18, 7); - map.put(19, 8); - - assertEquals(9, map.getSize()); - assertEquals(-1, map.get(8)); - assertEquals(0, map.get(9)); - assertEquals(1, map.get(10)); - assertEquals(2, map.get(11)); - assertEquals(3, map.get(12)); - assertEquals(4, map.get(13)); - assertEquals(5, map.get(14)); - assertEquals(6, map.get(16)); - assertEquals(-1, map.get(17)); - assertEquals(7, map.get(18)); - assertEquals(8, map.get(19)); - } - - @Test - public void testUpdateOfLowerKeys() { - OSMIDMap map = new OSMIDMap(new RAMDirectory()); - map.put(9, 0); - map.put(10, 1); - map.put(11, 2); - map.put(9, 3); - - assertEquals(2, map.get(11)); - assertEquals(3, map.get(9)); - } -} diff --git a/core/src/test/java/com/graphhopper/geohash/SpatialKeyAlgoTest.java b/core/src/test/java/com/graphhopper/geohash/SpatialKeyAlgoTest.java index 61426432187..58b9ddb600d 100644 --- a/core/src/test/java/com/graphhopper/geohash/SpatialKeyAlgoTest.java +++ b/core/src/test/java/com/graphhopper/geohash/SpatialKeyAlgoTest.java @@ -31,7 +31,7 @@ public class SpatialKeyAlgoTest { public void testEncode() { SpatialKeyAlgo algo = new SpatialKeyAlgo(32, new BBox(-180, 180, -90, 90)); long val = algo.encodeLatLon(-24.235345f, 47.234234f); - assertEquals("01100110101000111100000110010100", BitUtil.BIG.toLastBitString(val, 32)); + assertEquals("01100110101000111100000110010100", BitUtil.LITTLE.toLastBitString(val, 32)); } @Test diff --git a/core/src/test/java/com/graphhopper/reader/dem/CGIARProviderTest.java b/core/src/test/java/com/graphhopper/reader/dem/CGIARProviderTest.java index 1907b9e5428..e62213bf86b 100644 --- a/core/src/test/java/com/graphhopper/reader/dem/CGIARProviderTest.java +++ b/core/src/test/java/com/graphhopper/reader/dem/CGIARProviderTest.java @@ -89,7 +89,7 @@ public void downloadFile(String url, String toFile) throws IOException { }); assertEquals(0, instance.getEle(46, -20), 1); - // file not found => small! + // file not found assertTrue(file.exists()); assertEquals(1048676, file.length()); diff --git a/core/src/test/java/com/graphhopper/reader/dem/GMTEDProviderTest.java b/core/src/test/java/com/graphhopper/reader/dem/GMTEDProviderTest.java index ab3c9abf9a4..64eb6c1c005 100644 --- a/core/src/test/java/com/graphhopper/reader/dem/GMTEDProviderTest.java +++ b/core/src/test/java/com/graphhopper/reader/dem/GMTEDProviderTest.java @@ -96,7 +96,7 @@ public void downloadFile(String url, String toFile) throws IOException { }); assertEquals(0, instance.getEle(46, -20), 1); - // file not found => small! + // file not found assertTrue(file.exists()); assertEquals(1048676, file.length()); diff --git a/core/src/test/java/com/graphhopper/reader/dem/HeightTileTest.java b/core/src/test/java/com/graphhopper/reader/dem/HeightTileTest.java index a6bb09b5bd7..02a70c42f99 100644 --- a/core/src/test/java/com/graphhopper/reader/dem/HeightTileTest.java +++ b/core/src/test/java/com/graphhopper/reader/dem/HeightTileTest.java @@ -34,7 +34,7 @@ public void testGetHeight() { int width = 10; int height = 20; HeightTile instance = new HeightTile(0, 0, width, height, 1e-6, 10, 20); - DataAccess heights = new RAMDirectory().find("tmp"); + DataAccess heights = new RAMDirectory().create("tmp"); heights.create(2 * width * height); instance.setHeights(heights); init(heights, width, height, 1); @@ -77,7 +77,7 @@ public void testGetHeight() { public void testGetHeightForNegativeTile() { int width = 10; HeightTile instance = new HeightTile(-20, -20, width, width, 1e-6, 10, 10); - DataAccess heights = new RAMDirectory().find("tmp"); + DataAccess heights = new RAMDirectory().create("tmp"); heights.create(2 * 10 * 10); instance.setHeights(heights); init(heights, width, width, 1); @@ -98,7 +98,7 @@ public void testGetHeightForNegativeTile() { @Test public void testInterpolate() { HeightTile instance = new HeightTile(0, 0, 2, 2, 1e-6, 10, 10).setInterpolate(true); - DataAccess heights = new RAMDirectory().find("tmp"); + DataAccess heights = new RAMDirectory().create("tmp"); heights.create(2 * 2 * 2); instance.setHeights(heights); double topLeft = 0; diff --git a/core/src/test/java/com/graphhopper/reader/osm/GraphHopperOSMTest.java b/core/src/test/java/com/graphhopper/reader/osm/GraphHopperOSMTest.java index 9bf68ec512c..a2dbe940074 100644 --- a/core/src/test/java/com/graphhopper/reader/osm/GraphHopperOSMTest.java +++ b/core/src/test/java/com/graphhopper/reader/osm/GraphHopperOSMTest.java @@ -26,7 +26,10 @@ import com.graphhopper.routing.ch.CHPreparationHandler; import com.graphhopper.routing.ch.PrepareContractionHierarchies; import com.graphhopper.routing.lm.PrepareLandmarks; -import com.graphhopper.routing.util.*; +import com.graphhopper.routing.util.CarFlagEncoder; +import com.graphhopper.routing.util.EdgeFilter; +import com.graphhopper.routing.util.EncodingManager; +import com.graphhopper.routing.util.FlagEncoder; import com.graphhopper.routing.weighting.FastestWeighting; import com.graphhopper.routing.weighting.Weighting; import com.graphhopper.routing.weighting.custom.CustomProfile; @@ -98,7 +101,8 @@ public void testLoadOSM() { setProfiles(new Profile(profile).setVehicle(vehicle).setWeighting(weighting)). setStoreOnFlush(true); hopper.getCHPreparationHandler().setCHProfiles(new CHProfile(profile)); - assertTrue(hopper.load(ghLoc)); + hopper.setGraphHopperLocation(ghLoc); + assertTrue(hopper.load()); rsp = hopper.route(new GHRequest(51.2492152, 9.4317166, 51.2, 9.4). setProfile(profile)); assertFalse(rsp.hasErrors()); @@ -143,8 +147,9 @@ public void testLoadOSMNoCH() { gh.close(); gh = new GraphHopper(). setProfiles(new Profile(profile).setVehicle(vehicle).setWeighting(weighting)). - setStoreOnFlush(true); - assertTrue(gh.load(ghLoc)); + setStoreOnFlush(true). + setGraphHopperLocation(ghLoc); + assertTrue(gh.load()); rsp = gh.route(new GHRequest(51.2492152, 9.4317166, 51.2, 9.4) .setProfile(profile)); assertFalse(rsp.hasErrors()); @@ -236,8 +241,9 @@ public void testLoadingWithDifferentCHConfig_issue471_pr1488() { // now load GH without CH profile gh = new GraphHopper(). setProfiles(new Profile(profile).setVehicle(vehicle).setWeighting(weighting)). - setStoreOnFlush(true); - gh.load(ghLoc); + setStoreOnFlush(true). + setGraphHopperLocation(ghLoc); + gh.load(); // no error Helper.removeDir(new File(ghLoc)); @@ -258,13 +264,10 @@ public void testLoadingWithDifferentCHConfig_issue471_pr1488() { setProfiles(new Profile(profile).setVehicle(vehicle).setWeighting(weighting)). setStoreOnFlush(true); gh.getCHPreparationHandler().setCHProfiles(new CHProfile("profile")); - - try { - gh.load(ghLoc); - fail(); - } catch (Exception ex) { - assertTrue(ex.getMessage().contains("is not contained in loaded CH profiles"), ex.getMessage()); - } + gh.setGraphHopperLocation(ghLoc); + final GraphHopper tmpGh = gh; + Exception ex = assertThrows(Exception.class, tmpGh::load); + assertTrue(ex.getMessage().contains("is not contained in loaded CH profiles"), ex.getMessage()); } @Test @@ -279,13 +282,15 @@ public void testAllowMultipleReadingInstances() { GraphHopper instance2 = new GraphHopper(). setStoreOnFlush(true). - setOSMFile(testOsm); - instance2.load(ghLoc); + setOSMFile(testOsm). + setGraphHopperLocation(ghLoc); + instance2.load(); GraphHopper instance3 = new GraphHopper(). setStoreOnFlush(true). - setOSMFile(testOsm); - instance3.load(ghLoc); + setOSMFile(testOsm). + setGraphHopperLocation(ghLoc); + instance3.load(); instance1.close(); instance2.close(); @@ -327,12 +332,13 @@ public void run() { GraphHopper instance2 = new GraphHopper(). setProfiles(new Profile("car").setVehicle("car").setWeighting("fastest")). setStoreOnFlush(true). - setOSMFile(testOsm); + setOSMFile(testOsm). + setGraphHopperLocation(ghLoc); try { // let thread reach the CountDownLatch latch2.await(3, TimeUnit.SECONDS); // now importOrLoad should have create a lock which this load call does not like - instance2.load(ghLoc); + instance2.load(); fail("There should have been an error because of the lock"); } catch (RuntimeException ex) { assertNotNull(ex); @@ -468,14 +474,14 @@ public void testFootAndCar() { @Test public void testFailsForWrongConfig() { instance = new GraphHopper().init( - new GraphHopperConfig(). - putObject("datareader.file", testOsm3). - putObject("datareader.dataaccess", "RAM"). - putObject("graph.flag_encoders", "foot,car"). - setProfiles(Arrays.asList( - new Profile("foot").setVehicle("foot").setWeighting("fastest"), - new Profile("car").setVehicle("car").setWeighting("fastest") - ))). + new GraphHopperConfig(). + putObject("datareader.file", testOsm3). + putObject("datareader.dataaccess", "RAM"). + putObject("graph.flag_encoders", "foot,car"). + setProfiles(Arrays.asList( + new Profile("foot").setVehicle("foot").setWeighting("fastest"), + new Profile("car").setVehicle("car").setWeighting("fastest") + ))). setGraphHopperLocation(ghLoc); instance.importOrLoad(); assertEquals(5, instance.getGraphHopperStorage().getNodes()); @@ -484,15 +490,16 @@ public void testFailsForWrongConfig() { // different config (flagEncoder list) try { GraphHopper tmpGH = new GraphHopper().init( - new GraphHopperConfig(). - putObject("datareader.file", testOsm3). - putObject("datareader.dataaccess", "RAM"). - putObject("graph.flag_encoders", "foot"). - setProfiles(Collections.singletonList( - new Profile("foot").setVehicle("foot").setWeighting("fastest") - ))). - setOSMFile(testOsm3); - tmpGH.load(ghLoc); + new GraphHopperConfig(). + putObject("datareader.file", testOsm3). + putObject("datareader.dataaccess", "RAM"). + putObject("graph.flag_encoders", "foot"). + setProfiles(Collections.singletonList( + new Profile("foot").setVehicle("foot").setWeighting("fastest") + ))). + setOSMFile(testOsm3). + setGraphHopperLocation(ghLoc); + tmpGH.load(); fail(); } catch (Exception ex) { assertTrue(ex.getMessage().startsWith("Encoding does not match"), ex.getMessage()); @@ -501,15 +508,16 @@ public void testFailsForWrongConfig() { // different order is no longer okay, see #350 try { GraphHopper tmpGH = new GraphHopper().init(new GraphHopperConfig(). - putObject("datareader.file", testOsm3). - putObject("datareader.dataaccess", "RAM"). - putObject("graph.flag_encoders", "car,foot"). - setProfiles(Arrays.asList( - new Profile("car").setVehicle("car").setWeighting("fastest"), - new Profile("foot").setVehicle("foot").setWeighting("fastest") - ))). - setOSMFile(testOsm3); - tmpGH.load(ghLoc); + putObject("datareader.file", testOsm3). + putObject("datareader.dataaccess", "RAM"). + putObject("graph.flag_encoders", "car,foot"). + setProfiles(Arrays.asList( + new Profile("car").setVehicle("car").setWeighting("fastest"), + new Profile("foot").setVehicle("foot").setWeighting("fastest") + ))). + setOSMFile(testOsm3) + .setGraphHopperLocation(ghLoc); + tmpGH.load(); fail(); } catch (Exception ex) { assertTrue(ex.getMessage().startsWith("Encoding does not match"), ex.getMessage()); @@ -517,18 +525,19 @@ public void testFailsForWrongConfig() { // different encoded values should fail to load instance = new GraphHopper().init( - new GraphHopperConfig(). - putObject("datareader.file", testOsm3). - putObject("datareader.dataaccess", "RAM"). - putObject("graph.encoded_values", "road_class"). - putObject("graph.flag_encoders", "foot,car"). - setProfiles(Arrays.asList( - new Profile("foot").setVehicle("foot").setWeighting("fastest"), - new Profile("car").setVehicle("car").setWeighting("fastest") - ))). - setOSMFile(testOsm3); + new GraphHopperConfig(). + putObject("datareader.file", testOsm3). + putObject("datareader.dataaccess", "RAM"). + putObject("graph.encoded_values", "road_class"). + putObject("graph.flag_encoders", "foot,car"). + setProfiles(Arrays.asList( + new Profile("foot").setVehicle("foot").setWeighting("fastest"), + new Profile("car").setVehicle("car").setWeighting("fastest") + ))). + setOSMFile(testOsm3). + setGraphHopperLocation(ghLoc); try { - instance.load(ghLoc); + instance.load(); fail(); } catch (Exception ex) { assertTrue(ex.getMessage().startsWith("Encoded values do not match"), ex.getMessage()); @@ -538,14 +547,14 @@ public void testFailsForWrongConfig() { @Test public void testFailsForWrongEVConfig() { instance = new GraphHopper().init( - new GraphHopperConfig(). - putObject("datareader.file", testOsm3). - putObject("datareader.dataaccess", "RAM"). - putObject("graph.flag_encoders", "foot,car"). - setProfiles(Arrays.asList( - new Profile("foot").setVehicle("foot").setWeighting("fastest"), - new Profile("car").setVehicle("car").setWeighting("fastest") - ))). + new GraphHopperConfig(). + putObject("datareader.file", testOsm3). + putObject("datareader.dataaccess", "RAM"). + putObject("graph.flag_encoders", "foot,car"). + setProfiles(Arrays.asList( + new Profile("foot").setVehicle("foot").setWeighting("fastest"), + new Profile("car").setVehicle("car").setWeighting("fastest") + ))). setGraphHopperLocation(ghLoc); instance.importOrLoad(); // older versions <= 0.12 did not store this property, ensure that we fail to load it @@ -556,22 +565,19 @@ public void testFailsForWrongEVConfig() { // different encoded values should fail to load instance = new GraphHopper().init( - new GraphHopperConfig(). - putObject("datareader.file", testOsm3). - putObject("datareader.dataaccess", "RAM"). - putObject("graph.encoded_values", "road_environment,road_class"). - putObject("graph.flag_encoders", "foot,car"). - setProfiles(Arrays.asList( - new Profile("foot").setVehicle("foot").setWeighting("fastest"), - new Profile("car").setVehicle("car").setWeighting("fastest") - ))). + new GraphHopperConfig(). + putObject("datareader.file", testOsm3). + putObject("datareader.dataaccess", "RAM"). + putObject("graph.location", ghLoc). + putObject("graph.encoded_values", "road_environment,road_class"). + putObject("graph.flag_encoders", "foot,car"). + setProfiles(Arrays.asList( + new Profile("foot").setVehicle("foot").setWeighting("fastest"), + new Profile("car").setVehicle("car").setWeighting("fastest") + ))). setOSMFile(testOsm3); - try { - instance.load(ghLoc); - fail(); - } catch (Exception ex) { - assertTrue(ex.getMessage().startsWith("Encoded values do not match"), ex.getMessage()); - } + Exception ex = assertThrows(Exception.class, () -> instance.load()); + assertTrue(ex.getMessage().startsWith("Encoded values do not match"), ex.getMessage()); } @Test @@ -581,11 +587,12 @@ public void testNoNPE_ifLoadNotSuccessful() { String weighting = "fastest"; instance = new GraphHopper(). setProfiles(new Profile(profile).setVehicle(vehicle).setWeighting(weighting)). - setStoreOnFlush(true); + setStoreOnFlush(true). + setGraphHopperLocation(ghLoc); try { // loading from empty directory new File(ghLoc).mkdirs(); - assertFalse(instance.load(ghLoc)); + assertFalse(instance.load()); instance.route(new GHRequest(10, 40, 12, 32).setProfile(profile)); fail(); } catch (IllegalStateException ex) { @@ -597,7 +604,8 @@ public void testNoNPE_ifLoadNotSuccessful() { public void testDoesNotCreateEmptyFolderIfLoadingFromNonExistingPath() { instance = new GraphHopper(); instance.setProfiles(new Profile("car").setVehicle("car").setWeighting("fastest")); - assertFalse(instance.load(ghLoc)); + instance.setGraphHopperLocation(ghLoc); + assertFalse(instance.load()); assertFalse(new File(ghLoc).exists()); } @@ -849,22 +857,10 @@ public String toString() { CHConfig simpleTruckConfig = CHConfig.nodeBased("simple_truck", fwSimpleTruck); CHConfig truckConfig = CHConfig.nodeBased("truck", fwTruck); GraphHopperStorage storage = new GraphBuilder(em).setCHConfigs(Arrays.asList(simpleTruckConfig, truckConfig)).build(); - chHandler.addCHConfig(simpleTruckConfig); - chHandler.addCHConfig(truckConfig); - chHandler.addPreparation(PrepareContractionHierarchies.fromGraphHopperStorage(storage, simpleTruckConfig)); - chHandler.addPreparation(PrepareContractionHierarchies.fromGraphHopperStorage(storage, truckConfig)); + chHandler.createPreparations(storage); assertEquals("fastest|truck", chHandler.getPreparation("truck").getCHConfig().getWeighting().toString()); assertEquals("fastest|simple_truck", chHandler.getPreparation("simple_truck").getCHConfig().getWeighting().toString()); - - // make sure weighting cannot be mixed - chHandler.addCHConfig(truckConfig); - chHandler.addCHConfig(simpleTruckConfig); - try { - chHandler.addPreparation(PrepareContractionHierarchies.fromGraphHopperStorage(storage, simpleTruckConfig)); - fail(); - } catch (Exception ex) { - } } @Test @@ -903,16 +899,18 @@ public void testLoadingLMAndCHProfiles() { .setProfiles(new Profile("car").setVehicle("car").setWeighting("fastest")); hopper.getLMPreparationHandler().setLMProfiles(new LMProfile("car")); hopper.getCHPreparationHandler().setCHProfiles(new CHProfile("car")); - assertTrue(hopper.load(ghLoc)); + hopper.setGraphHopperLocation(ghLoc); + assertTrue(hopper.load()); hopper.close(); // problem: changed weighting in profile although LM preparation was enabled hopper = new GraphHopper() .setProfiles(new Profile("car").setVehicle("car").setWeighting("shortest")); hopper.getLMPreparationHandler().setLMProfiles(new LMProfile("car")); + hopper.setGraphHopperLocation(ghLoc); // do not load CH try { - assertFalse(hopper.load(ghLoc)); + assertFalse(hopper.load()); fail("load should fail"); } catch (Exception ex) { assertEquals("LM preparation of car already exists in storage and doesn't match configuration", ex.getMessage()); @@ -924,9 +922,10 @@ public void testLoadingLMAndCHProfiles() { hopper = new GraphHopper() .setProfiles(new Profile("car").setVehicle("car").setWeighting("shortest")); hopper.getCHPreparationHandler().setCHProfiles(new CHProfile("car")); + hopper.setGraphHopperLocation(ghLoc); // do not load LM try { - assertFalse(hopper.load(ghLoc)); + assertFalse(hopper.load()); fail("load should fail"); } catch (Exception ex) { assertEquals("CH preparation of car already exists in storage and doesn't match configuration", ex.getMessage()); @@ -950,7 +949,8 @@ public void testLoadingCustomProfiles() { hopper = new GraphHopper() .setProfiles(new CustomProfile("car").setCustomModel(customModel)); hopper.getLMPreparationHandler().setLMProfiles(new LMProfile("car")); - assertTrue(hopper.load(ghLoc)); + hopper.setGraphHopperLocation(ghLoc); + assertTrue(hopper.load()); hopper.close(); // do not load changed CustomModel @@ -958,8 +958,9 @@ public void testLoadingCustomProfiles() { hopper = new GraphHopper() .setProfiles(new CustomProfile("car").setCustomModel(customModel)); hopper.getLMPreparationHandler().setLMProfiles(new LMProfile("car")); + hopper.setGraphHopperLocation(ghLoc); try { - assertFalse(hopper.load(ghLoc)); + assertFalse(hopper.load()); fail("load should fail"); } catch (Exception ex) { assertEquals("LM preparation of car already exists in storage and doesn't match configuration", ex.getMessage()); diff --git a/core/src/test/java/com/graphhopper/reader/osm/OSMReaderTest.java b/core/src/test/java/com/graphhopper/reader/osm/OSMReaderTest.java index 2a60f5a1cfb..46de5a0fb69 100644 --- a/core/src/test/java/com/graphhopper/reader/osm/OSMReaderTest.java +++ b/core/src/test/java/com/graphhopper/reader/osm/OSMReaderTest.java @@ -17,7 +17,7 @@ */ package com.graphhopper.reader.osm; -import com.carrotsearch.hppc.LongIndexedContainer; +import com.carrotsearch.hppc.LongArrayList; import com.graphhopper.GHRequest; import com.graphhopper.GHResponse; import com.graphhopper.GraphHopper; @@ -27,6 +27,7 @@ import com.graphhopper.reader.ReaderWay; import com.graphhopper.reader.dem.ElevationProvider; import com.graphhopper.reader.dem.SRTMProvider; +import com.graphhopper.routing.OSMReaderConfig; import com.graphhopper.routing.ev.*; import com.graphhopper.routing.util.*; import com.graphhopper.routing.util.countryrules.CountryRuleFactory; @@ -38,14 +39,16 @@ import com.graphhopper.storage.index.Snap; import com.graphhopper.util.*; import com.graphhopper.util.details.PathDetail; -import com.graphhopper.util.shapes.GHPoint; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import java.io.File; import java.io.IOException; -import java.util.*; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; import static com.graphhopper.util.GHUtility.readCountries; import static org.junit.jupiter.api.Assertions.*; @@ -79,10 +82,6 @@ public void tearDown() { Helper.removeDir(new File(dir)); } - GraphHopperStorage newGraph(String directory, EncodingManager encodingManager, boolean is3D, boolean turnRestrictionsImport) { - return new GraphHopperStorage(new RAMDirectory(directory, false), encodingManager, is3D, turnRestrictionsImport); - } - @Test public void testMain() { GraphHopper hopper = new GraphHopperFacade(file1).importOrLoad(); @@ -249,6 +248,8 @@ public void testWayReferencesNotExistingAdjNode_issue19() { Graph graph = hopper.getGraphHopperStorage(); assertEquals(2, graph.getNodes()); + // the missing node is ignored, but the separated nodes are still connected + assertEquals(1, graph.getEdges()); int n10 = AbstractGraphStorageTester.getIdOf(graph, 51.2492152); int n30 = AbstractGraphStorageTester.getIdOf(graph, 51.2); @@ -346,7 +347,9 @@ public void testBarriers() { importOrLoad(); Graph graph = hopper.getGraphHopperStorage(); - assertEquals(8, graph.getNodes()); + // we ignore the barrier at node 50, but not the one at node 20 + assertEquals(7, graph.getNodes()); + assertEquals(7, graph.getEdges()); int n10 = AbstractGraphStorageTester.getIdOf(graph, 51); int n20 = AbstractGraphStorageTester.getIdOf(graph, 52); @@ -376,6 +379,27 @@ public void testBarriers() { assertFalse(iter.next()); } + @Test + public void testBarrierBetweenWays() { + GraphHopper hopper = new GraphHopperFacade("test-barriers2.xml"). + setMinNetworkSize(0). + importOrLoad(); + + Graph graph = hopper.getGraphHopperStorage(); + // there are seven ways, but there should also be six barrier edges + // we first split the loop way into two parts, and then we split the barrier node => 3 edges total + assertEquals(7 + 6, graph.getEdges()); + int loops = 0; + AllEdgesIterator iter = graph.getAllEdges(); + while (iter.next()) { + // there are 'loop' edges, but only between different nodes + assertNotEquals(iter.getBaseNode(), iter.getAdjNode()); + if (graph.getNodeAccess().getLat(iter.getBaseNode()) == graph.getNodeAccess().getLat(iter.getAdjNode())) + loops++; + } + assertEquals(5, loops); + } + @Test public void avoidsLoopEdges_1525() { // loops in OSM should be avoided by adding additional tower node (see #1525, #1531) @@ -410,19 +434,10 @@ public void avoidsLoopEdgesIdenticalLatLon_1533() { @Test public void avoidsLoopEdgesIdenticalNodeIds_1533() { - // We can handle the following case with the proper result: + // BDCBB checkLoop(new GraphHopperFacade("test-avoid-loops3.xml").importOrLoad()); - // We cannot handle the following case, i.e. no loop is created. so we only check that there are no loops - GraphHopper hopper = new GraphHopperFacade("test-avoid-loops4.xml").importOrLoad(); - GraphHopperStorage graph = hopper.getGraphHopperStorage(); - AllEdgesIterator iter = graph.getAllEdges(); - assertEquals(2, iter.length()); - while (iter.next()) { - assertTrue(iter.getAdjNode() != iter.getBaseNode(), "found a loop"); - } - int nodeB = AbstractGraphStorageTester.getIdOf(graph, 12); - assertTrue(nodeB > -1, "could not find OSM node B"); - assertEquals(2, GHUtility.count(graph.createEdgeExplorer().setBaseNode(nodeB))); + // BBCDB + checkLoop(new GraphHopperFacade("test-avoid-loops4.xml").importOrLoad()); } @Test @@ -431,49 +446,49 @@ public void testBarriersOnTowerNodes() { setMinNetworkSize(0). importOrLoad(); Graph graph = hopper.getGraphHopperStorage(); - assertEquals(8, graph.getNodes()); + // we ignore the barrier at node 50 + // 10-20-30 produces three edges: 10-20, 20-2x, 2x-30, the second one is a barrier edge + assertEquals(7, graph.getNodes()); + assertEquals(7, graph.getEdges()); int n60 = AbstractGraphStorageTester.getIdOf(graph, 56); - int newId = 5; - assertEquals(GHUtility.asSet(newId), GHUtility.getNeighbors(carOutExplorer.setBaseNode(n60))); + int n50 = AbstractGraphStorageTester.getIdOf(graph, 55); + int n30 = AbstractGraphStorageTester.getIdOf(graph, 53); + int n80 = AbstractGraphStorageTester.getIdOf(graph, 58); + assertEquals(GHUtility.asSet(n50), GHUtility.getNeighbors(carOutExplorer.setBaseNode(n60))); EdgeIterator iter = carOutExplorer.setBaseNode(n60); assertTrue(iter.next()); - assertEquals(newId, iter.getAdjNode()); + assertEquals(n50, iter.getAdjNode()); assertFalse(iter.next()); - iter = carOutExplorer.setBaseNode(newId); - assertTrue(iter.next()); - assertEquals(n60, iter.getAdjNode()); - assertFalse(iter.next()); + assertTrue(GHUtility.getNeighbors(carOutExplorer.setBaseNode(n30)).contains(n50)); + assertEquals(GHUtility.asSet(n30, n80, n60), GHUtility.getNeighbors(carOutExplorer.setBaseNode(n50))); } @Test public void testRelation() { EncodingManager manager = EncodingManager.create("bike"); - GraphHopperStorage ghStorage = new GraphHopperStorage(new RAMDirectory(), manager, false); - OSMReader reader = new OSMReader(ghStorage); ReaderRelation osmRel = new ReaderRelation(1); osmRel.add(new ReaderRelation.Member(ReaderRelation.WAY, 1, "")); osmRel.add(new ReaderRelation.Member(ReaderRelation.WAY, 2, "")); osmRel.setTag("route", "bicycle"); osmRel.setTag("network", "lcn"); - reader.prepareWaysWithRelationInfo(osmRel); - IntsRef flags = IntsRef.deepCopyOf(reader.getRelFlagsMap(1)); + IntsRef flags = manager.createRelationFlags(); + manager.handleRelationTags(osmRel, flags); assertFalse(flags.isEmpty()); // unchanged network - reader.prepareWaysWithRelationInfo(osmRel); - IntsRef flags2 = reader.getRelFlagsMap(1); - assertEquals(flags, flags2); + IntsRef before = IntsRef.deepCopyOf(flags); + manager.handleRelationTags(osmRel, flags); + assertEquals(before, flags); // overwrite network osmRel.setTag("network", "ncn"); - reader.prepareWaysWithRelationInfo(osmRel); - IntsRef flags3 = reader.getRelFlagsMap(1); - assertNotEquals(flags, flags3); + manager.handleRelationTags(osmRel, flags); + assertNotEquals(before, flags); } @Test @@ -593,10 +608,10 @@ public void testRoadAttributes() { } @Test - public void testEstimatedCenter() { + public void testEstimatedDistance() { final CarFlagEncoder encoder = new CarFlagEncoder(); EncodingManager manager = EncodingManager.create(encoder); - GraphHopperStorage ghStorage = newGraph(dir, manager, false, false); + GraphHopperStorage ghStorage = new GraphHopperStorage(new RAMDirectory(dir, false), manager, false, false); final Map latMap = new HashMap<>(); final Map lonMap = new HashMap<>(); latMap.put(1, 1.1d); @@ -605,7 +620,7 @@ public void testEstimatedCenter() { lonMap.put(1, 1.0d); lonMap.put(2, 1.0d); - OSMReader osmreader = new OSMReader(ghStorage) { + OSMReader osmreader = new OSMReader(ghStorage, new OSMReaderConfig()) { // mock data access @Override // ORS-GH MOD - change access level due to change in superclass @@ -620,8 +635,7 @@ public double getTmpLongitude(int id) { } @Override - public Collection addOSMWay(LongIndexedContainer osmNodeIds, IntsRef wayFlags, long osmId) { - return Collections.emptyList(); + void handleSegment(List segment, ReaderWay way, IntsRef edgeFlags, Map nodeTags) { } }; @@ -633,9 +647,6 @@ public Collection addOSMWay(LongIndexedContainer osmNodeIds, osmreader.getNodeMap().put(2, 2); osmreader.processWay(way); - GHPoint p = way.getTag("estimated_center", null); - assertEquals(1.15, p.lat, 1e-3); - assertEquals(1.0, p.lon, 1e-3); Double d = way.getTag("estimated_distance", null); assertEquals(11119.5, d, 1e-1); } @@ -927,7 +938,7 @@ public void testCountries() throws IOException { EncodingManager em = EncodingManager.create("car"); EnumEncodedValue roadAccessEnc = em.getEnumEncodedValue(RoadAccess.KEY, RoadAccess.class); GraphHopperStorage graph = new GraphBuilder(em).build(); - OSMReader reader = new OSMReader(graph); + OSMReader reader = new OSMReader(graph, new OSMReaderConfig()); reader.setCountryRuleFactory(new CountryRuleFactory()); reader.setAreaIndex(createCountryIndex()); // there are two edges, both with highway=track, one in Berlin, one in Paris @@ -978,8 +989,7 @@ public GraphHopperFacade(String osmFile, boolean turnCosts, String prefLang) { @Override protected void importOSM() { - GraphHopperStorage tmpGraph = newGraph(dir, getEncodingManager(), hasElevation(), - getEncodingManager().needsTurnCostsSupport()); + GraphHopperStorage tmpGraph = new GraphHopperStorage(new RAMDirectory(dir, false), getEncodingManager(), hasElevation(), getEncodingManager().needsTurnCostsSupport()); setGraphHopperStorage(tmpGraph); super.importOSM(); carAccessEnc = carEncoder.getAccessEnc(); diff --git a/core/src/test/java/com/graphhopper/routing/DirectionResolverOnQueryGraphTest.java b/core/src/test/java/com/graphhopper/routing/DirectionResolverOnQueryGraphTest.java index 053bd1ad081..0c626c0ce73 100644 --- a/core/src/test/java/com/graphhopper/routing/DirectionResolverOnQueryGraphTest.java +++ b/core/src/test/java/com/graphhopper/routing/DirectionResolverOnQueryGraphTest.java @@ -27,10 +27,7 @@ import com.graphhopper.storage.RAMDirectory; import com.graphhopper.storage.index.LocationIndexTree; import com.graphhopper.storage.index.Snap; -import com.graphhopper.util.EdgeExplorer; -import com.graphhopper.util.EdgeIterator; -import com.graphhopper.util.EdgeIteratorState; -import com.graphhopper.util.GHUtility; +import com.graphhopper.util.*; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -40,7 +37,8 @@ import static com.graphhopper.routing.DirectionResolverResult.unrestricted; import static com.graphhopper.util.EdgeIterator.NO_EDGE; import static com.graphhopper.util.Helper.createPointList; -import static org.junit.jupiter.api.Assertions.*; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.fail; /** * This test simulates incoming lat/lon coordinates that get snapped to graph edges (using {@link QueryGraph}) and the @@ -255,6 +253,43 @@ public void duplicateCoordinatesAtBaseOrAdjNode() { checkResult(0.9, 0.9, restricted(edge(0, 2), edge(2, 1), edge(1, 2), edge(2, 0))); } + @Test + public void closeToTowerNode_issue2443() { + // 0x-1 + addNode(0, 51.986000, 19.255000); + addNode(1, 51.985500, 19.254000); + DistancePlaneProjection distCalc = new DistancePlaneProjection(); + addEdge(0, 1, true).setDistance(distCalc.calcDist(na.getLat(0), na.getLon(0), na.getLat(1), na.getLon(1))); + init(); + + double lat = 51.9855003; + double lon = 19.2540003; + Snap snap = snapCoordinate(lat, lon); + queryGraph = QueryGraph.create(graph, snap); + DirectionResolver resolver = new DirectionResolver(queryGraph, this::isAccessible); + DirectionResolverResult result = resolver.resolveDirections(snap.getClosestNode(), snap.getQueryPoint()); + assertEquals(0, result.getInEdgeRight()); + assertEquals(0, result.getOutEdgeRight()); + assertEquals(0, result.getInEdgeLeft()); + assertEquals(0, result.getOutEdgeRight()); + } + + @Test + public void unblockedBarrierEdge_issue2443() { + // 0---1-2 + addNode(0, 51.9860, 19.2550); + addNode(1, 51.9861, 19.2551); + addNode(2, 51.9861, 19.2551); + DistancePlaneProjection distCalc = new DistancePlaneProjection(); + addEdge(0, 1, true).setDistance(distCalc.calcDist(na.getLat(0), na.getLon(0), na.getLat(1), na.getLon(1))); + // a barrier edge connects two different nodes (it is not a loop), but they have the same coordinates (distance is 0) + // barrier edges **can** be accessible, for example they could be blocked only for certain vehicles + addEdge(1, 2, true).setDistance(0); + init(); + // currently we just use unrestricted when we snap to a barrier edge node, see #2447 + assertUnrestricted(51.9861, 19.2551); + } + private void addNode(int nodeId, double lat, double lon) { na.setNode(nodeId, lat, lon); } diff --git a/core/src/test/java/com/graphhopper/routing/HeadingRoutingTest.java b/core/src/test/java/com/graphhopper/routing/HeadingRoutingTest.java index 37b037a10c5..23d911f108a 100644 --- a/core/src/test/java/com/graphhopper/routing/HeadingRoutingTest.java +++ b/core/src/test/java/com/graphhopper/routing/HeadingRoutingTest.java @@ -155,7 +155,7 @@ public void headingTest5() { GHPoint via = new GHPoint(0.000, 0.0015); GHRequest req = new GHRequest(). setPoints(Arrays.asList(start, via, end)). - setHeadings(Arrays.asList(0., 3.14 / 2, Double.NaN)). + setHeadings(Arrays.asList(0., 90., Double.NaN)). setProfile("profile"). setPathDetails(Collections.singletonList("edge_key")); req.putHint(Parameters.Routing.PASS_THROUGH, true); @@ -164,6 +164,96 @@ public void headingTest5() { assertArrayEquals(new int[]{5, 4, 3, 8, 7, 6, 5, 4, 3, 2}, calcNodes(graph, response.getAll().get(0))); } + @Test + public void testHeadingWithSnapFilter() { + GraphHopperStorage graph = createSquareGraphWithTunnel(); + Router router = createRouter(graph); + // Start at 8 (slightly north to make it independent on some edge ordering and always use 8-3 or 3-8 as fallback) + GHPoint start = new GHPoint(0.0011, 0.001); + // End at middle of edge 2-3 + GHPoint end = new GHPoint(0.002, 0.0005); + + // no heading + GHRequest req = new GHRequest(). + setPoints(Arrays.asList(start, end)). + setProfile("profile"). + setPathDetails(Collections.singletonList("edge_key")); + req.putHint(Parameters.Routing.PASS_THROUGH, true); + GHResponse response = router.route(req); + assertFalse(response.hasErrors()); + assertArrayEquals(new int[]{8, 3, 2}, calcNodes(graph, response.getAll().get(0))); + + // same start + end but heading=0, parallel to 3-8-7 + req = new GHRequest(). + setPoints(Arrays.asList(start, end)). + setHeadings(Arrays.asList(0.)). + setProfile("profile"). + setPathDetails(Collections.singletonList("edge_key")); + req.putHint(Parameters.Routing.PASS_THROUGH, true); + response = router.route(req); + assertFalse(response.hasErrors()); + assertArrayEquals(new int[]{8, 3, 2}, calcNodes(graph, response.getAll().get(0))); + + // heading=90 parallel to 1->5 + req = new GHRequest(). + setPoints(Arrays.asList(start, end)). + setHeadings(Arrays.asList(90., Double.NaN)). + setProfile("profile"). + setPathDetails(Collections.singletonList("edge_key")); + req.putHint(Parameters.Routing.PASS_THROUGH, true); + response = router.route(req); + assertFalse(response.hasErrors()); + assertArrayEquals(new int[]{1, 5, 4, 3, 2}, calcNodes(graph, response.getAll().get(0))); + + for (double angle = 0; angle < 360; angle += 10) { + // Ignore angles nearly parallel to 1->5. I.e. it should fallback to results with 8-3.. or 3-8.. + if (angle >= 60 && angle <= 120) continue; + + req = new GHRequest(). + setPoints(Arrays.asList(start, end)). + setHeadings(Arrays.asList(angle, Double.NaN)). + setProfile("profile"). + setPathDetails(Collections.singletonList("edge_key")); + req.putHint(Parameters.Routing.PASS_THROUGH, true); + response = router.route(req); + assertFalse(response.hasErrors()); + + int[] expectedNodes = (angle >= 130 && angle <= 250) ? new int[]{3, 8, 7, 0, 1, 2, 3} : new int[]{8, 3, 2}; + // System.out.println(Arrays.toString(calcNodes(graph, response.getAll().get(0))) + " angle:" + angle); + assertArrayEquals(expectedNodes, calcNodes(graph, response.getAll().get(0)), "angle: " + angle); + } + } + + @Test + public void testHeadingWithSnapFilter2() { + GraphHopperStorage graph = createSquareGraphWithTunnel(); + Router router = createRouter(graph); + // Start at 8 (slightly east to snap to edge 1->5 per default) + GHPoint start = new GHPoint(0.001, 0.0011); + // End at middle of edge 2-3 + GHPoint end = new GHPoint(0.002, 0.0005); + + GHRequest req = new GHRequest(). + setPoints(Arrays.asList(start, end)). + setProfile("profile"). + setHeadings(Arrays.asList(0.)). + setPathDetails(Collections.singletonList("edge_key")); + req.putHint(Parameters.Routing.PASS_THROUGH, true); + GHResponse response = router.route(req); + assertFalse(response.hasErrors()); + assertArrayEquals(new int[]{8, 3, 2}, calcNodes(graph, response.getAll().get(0))); + + req = new GHRequest(). + setPoints(Arrays.asList(start, end)). + setProfile("profile"). + setHeadings(Arrays.asList(180.)). + setPathDetails(Collections.singletonList("edge_key")); + req.putHint(Parameters.Routing.PASS_THROUGH, true); + response = router.route(req); + assertFalse(response.hasErrors()); + assertArrayEquals(new int[]{8, 3, 2}, calcNodes(graph, response.getAll().get(0))); + } + @Test public void headingTest6() { // Test if snaps at tower nodes are ignored @@ -199,11 +289,11 @@ private GraphHopperStorage createSquareGraph() { EncodingManager encodingManager = new EncodingManager.Builder().add(carEncoder).add(Subnetwork.create("profile")).build(); GraphHopperStorage g = new GraphBuilder(encodingManager).create(); - // 2---3---4 - // / | \ - // 1----8----5 - // / | / - // 0----7---6 + // 2---3---4 + // | | | + // 1---8---5 + // | | | + // 0---7---6 NodeAccess na = g.getNodeAccess(); na.setNode(0, 0.000, 0.000); na.setNode(1, 0.001, 0.000); @@ -232,6 +322,43 @@ private GraphHopperStorage createSquareGraph() { return g; } + private GraphHopperStorage createSquareGraphWithTunnel() { + CarFlagEncoder carEncoder = new CarFlagEncoder(); + EncodingManager encodingManager = new EncodingManager.Builder().add(carEncoder).add(Subnetwork.create("profile")).build(); + GraphHopperStorage g = new GraphBuilder(encodingManager).create(); + + // 2----3---4 + // | | | + // 1->- 8 >-5 (edge 1->5 is not connected to 8) + // | | | + // 0----7---6 + NodeAccess na = g.getNodeAccess(); + na.setNode(0, 0.000, 0.000); + na.setNode(1, 0.001, 0.000); + na.setNode(2, 0.002, 0.000); + na.setNode(3, 0.002, 0.001); + na.setNode(4, 0.002, 0.002); + na.setNode(5, 0.001, 0.002); + na.setNode(6, 0.000, 0.002); + na.setNode(7, 0.000, 0.001); + na.setNode(8, 0.001, 0.001); + + GHUtility.setSpeed(60, true, true, carEncoder, g.edge(0, 1).setDistance(100)); + GHUtility.setSpeed(60, true, true, carEncoder, g.edge(1, 2).setDistance(100)); + GHUtility.setSpeed(60, true, true, carEncoder, g.edge(2, 3).setDistance(100)); + GHUtility.setSpeed(60, true, true, carEncoder, g.edge(3, 4).setDistance(100)); + GHUtility.setSpeed(60, true, true, carEncoder, g.edge(4, 5).setDistance(100)); + GHUtility.setSpeed(60, true, true, carEncoder, g.edge(5, 6).setDistance(100)); + GHUtility.setSpeed(60, true, true, carEncoder, g.edge(6, 7).setDistance(100)); + GHUtility.setSpeed(60, true, true, carEncoder, g.edge(7, 0).setDistance(100)); + + GHUtility.setSpeed(60, true, false, carEncoder, g.edge(1, 5).setDistance(110)); + GHUtility.setSpeed(60, true, true, carEncoder, g.edge(3, 8).setDistance(110)); + GHUtility.setSpeed(60, true, true, carEncoder, g.edge(7, 8).setDistance(110)); + + return g; + } + private int[] calcNodes(Graph graph, ResponsePath responsePath) { List edgeKeys = responsePath.getPathDetails().get("edge_key"); int[] result = new int[edgeKeys.size() + 1]; diff --git a/core/src/test/java/com/graphhopper/routing/RoutingAlgorithmWithOSMTest.java b/core/src/test/java/com/graphhopper/routing/RoutingAlgorithmWithOSMTest.java index 401b905e05a..af2ff6963d4 100644 --- a/core/src/test/java/com/graphhopper/routing/RoutingAlgorithmWithOSMTest.java +++ b/core/src/test/java/com/graphhopper/routing/RoutingAlgorithmWithOSMTest.java @@ -33,6 +33,7 @@ import com.graphhopper.util.shapes.GHPoint; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import java.io.File; @@ -335,7 +336,7 @@ public void testMonacoBike3D_twoSpeedsPerEdge() { // 1. queries.add(new Query(43.727687, 7.418737, 43.730864, 7.420771, 2599, 115)); queries.add(new Query(43.74958, 7.436566, 43.728499, 7.417907, 4180, 165)); - queries.add(new Query(43.739213, 7.427806, 43.728677, 7.41016, 3244, 177)); + queries.add(new Query(43.739213, 7.427806, 43.728677, 7.41016, 3244, 179)); // 4. avoid tunnel(s)! queries.add(new Query(43.739662, 7.424355, 43.733802, 7.413433, 2436, 112)); GraphHopper hopper = createHopper(MONACO, new Profile("bike2").setVehicle("bike2").setWeighting("fastest")); @@ -599,7 +600,7 @@ public void testDisconnectedAreaAndMultiplePoints() { @Test public void testMonacoParallel() throws InterruptedException { GraphHopper hopper = createHopper(MONACO, new Profile("car").setVehicle("car").setWeighting("shortest")); - hopper.setWayPointMaxDistance(0); + hopper.getReaderConfig().setMaxWayPointDistance(0); hopper.getRouterConfig().setSimplifyResponse(false); hopper.importOrLoad(); final List queries = createMonacoCarQueries(); @@ -687,7 +688,7 @@ private GraphHopper createHopper(String osmFile, Profile... profiles) { setGraphHopperLocation(GH_LOCATION); hopper.getRouterConfig().setSimplifyResponse(false); hopper.setMinNetworkSize(0); - hopper.setWayPointMaxDistance(0); + hopper.getReaderConfig().setMaxWayPointDistance(0); hopper.getLMPreparationHandler().setLMProfiles(new LMProfile(profiles[0].getName())); hopper.getCHPreparationHandler().setCHProfiles(new CHProfile(profiles[0].getName())); return hopper; diff --git a/core/src/test/java/com/graphhopper/routing/TrafficChangeWithNodeOrderingReusingTest.java b/core/src/test/java/com/graphhopper/routing/TrafficChangeWithNodeOrderingReusingTest.java index 1bb5bd74590..a398ef53b8a 100644 --- a/core/src/test/java/com/graphhopper/routing/TrafficChangeWithNodeOrderingReusingTest.java +++ b/core/src/test/java/com/graphhopper/routing/TrafficChangeWithNodeOrderingReusingTest.java @@ -87,7 +87,7 @@ public void testPerformanceForRandomTrafficChange(Fixture f) throws IOException LOGGER.info("Running performance test, max deviation percentage: " + f.maxDeviationPercentage); // read osm - OSMReader reader = new OSMReader(f.ghStorage); + OSMReader reader = new OSMReader(f.ghStorage, new OSMReaderConfig()); reader.setFile(new File(OSM_FILE)); reader.readGraph(); f.ghStorage.freeze(); diff --git a/core/src/test/java/com/graphhopper/routing/ch/CHPreparationHandlerTest.java b/core/src/test/java/com/graphhopper/routing/ch/CHPreparationHandlerTest.java index e12ee8e5b6a..6416f581a7a 100644 --- a/core/src/test/java/com/graphhopper/routing/ch/CHPreparationHandlerTest.java +++ b/core/src/test/java/com/graphhopper/routing/ch/CHPreparationHandlerTest.java @@ -33,104 +33,13 @@ * @author Peter Karich */ public class CHPreparationHandlerTest { - private CHPreparationHandler instance; - private CHConfig configNode1; - private CHConfig configNode2; - private CHConfig configNode3; - private CHConfig configEdge1; - private CHConfig configEdge2; - private CHConfig configEdge3; - private GraphHopperStorage ghStorage; - - @BeforeEach - public void setup() { - instance = new CHPreparationHandler(); - EncodingManager encodingManager = EncodingManager.create("car"); - ghStorage = new GraphBuilder(encodingManager).withTurnCosts(true) - .setCHConfigStrings( - "p1|car|fastest|node", - "p2|car|shortest|node", - "p3|car|short_fastest|node", - "p4|car|fastest|edge|30", - "p5|car|shortest|edge|30", - "p6|car|short_fastest|edge|30" - ) - .create(); - List chConfigs = ghStorage.getCHConfigs(); - configNode1 = chConfigs.get(0); - configNode2 = chConfigs.get(1); - configNode3 = chConfigs.get(2); - configEdge1 = chConfigs.get(3); - configEdge2 = chConfigs.get(4); - configEdge3 = chConfigs.get(5); - } @Test public void testEnabled() { + CHPreparationHandler instance = new CHPreparationHandler(); assertFalse(instance.isEnabled()); instance.setCHProfiles(new CHProfile("myconfig")); assertTrue(instance.isEnabled()); } - @Test - public void testAddingPreparationBeforeProfile_throws() { - assertThrows(IllegalStateException.class, () -> { - PrepareContractionHierarchies preparation = createPreparation(configNode1); - instance.addPreparation(preparation); - }); - } - - @Test - public void testAddingPreparationWithWrongProfile_throws() { - assertThrows(IllegalArgumentException.class, () -> { - instance.addCHConfig(configNode1); - PrepareContractionHierarchies preparation = createPreparation(configNode2); - instance.addPreparation(preparation); - }); - } - - @Test - public void testAddingPreparationsInWrongOrder_throws() { - assertThrows(IllegalArgumentException.class, () -> { - instance.addCHConfig(configNode1); - instance.addCHConfig(configNode2); - instance.addPreparation(createPreparation(configNode2)); - instance.addPreparation(createPreparation(configNode1)); - }); - } - - @Test - public void testAddingPreparationsWithEdgeAndNodeBasedIntermixed_works() { - instance.addCHConfig(configNode1); - instance.addCHConfig(configEdge1); - instance.addCHConfig(configNode2); - instance.addPreparation(createPreparation(configNode1)); - instance.addPreparation(createPreparation(configEdge1)); - instance.addPreparation(createPreparation(configNode2)); - } - - @Test - public void testAddingEdgeAndNodeBased_works() { - instance.addCHConfig(configNode1); - instance.addCHConfig(configNode2); - instance.addCHConfig(configEdge1); - instance.addCHConfig(configEdge2); - instance.addCHConfig(configNode3); - instance.addPreparation(createPreparation(configNode1)); - instance.addPreparation(createPreparation(configNode2)); - instance.addPreparation(createPreparation(configEdge1)); - instance.addPreparation(createPreparation(configEdge2)); - instance.addPreparation(createPreparation(configNode3)); - - CHConfig[] expectedConfigs = new CHConfig[]{configNode1, configNode2, configEdge1, configEdge2, configNode3}; - List preparations = instance.getPreparations(); - for (int i = 0; i < preparations.size(); ++i) { - assertSame(expectedConfigs[i], preparations.get(i).getCHConfig()); - } - } - - private PrepareContractionHierarchies createPreparation(CHConfig chConfig) { - return PrepareContractionHierarchies.fromGraphHopperStorage(ghStorage, chConfig); - } - } diff --git a/core/src/test/java/com/graphhopper/routing/ch/PrepareContractionHierarchiesTest.java b/core/src/test/java/com/graphhopper/routing/ch/PrepareContractionHierarchiesTest.java index b41966165e2..4dccec482cf 100644 --- a/core/src/test/java/com/graphhopper/routing/ch/PrepareContractionHierarchiesTest.java +++ b/core/src/test/java/com/graphhopper/routing/ch/PrepareContractionHierarchiesTest.java @@ -465,8 +465,8 @@ public void testBits() { int endNode = Integer.MAX_VALUE / 37 * 17; long edgeId = (long) fromNode << 32 | endNode; - assertEquals((BitUtil.BIG.toBitString(edgeId)), - BitUtil.BIG.toLastBitString(fromNode, 32) + BitUtil.BIG.toLastBitString(endNode, 32)); + assertEquals((BitUtil.LITTLE.toBitString(edgeId)), + BitUtil.LITTLE.toLastBitString(fromNode, 32) + BitUtil.LITTLE.toLastBitString(endNode, 32)); } @Test diff --git a/core/src/test/java/com/graphhopper/routing/lm/LMPreparationHandlerTest.java b/core/src/test/java/com/graphhopper/routing/lm/LMPreparationHandlerTest.java index 1460742fe32..ab6b28e6e79 100644 --- a/core/src/test/java/com/graphhopper/routing/lm/LMPreparationHandlerTest.java +++ b/core/src/test/java/com/graphhopper/routing/lm/LMPreparationHandlerTest.java @@ -12,7 +12,9 @@ import com.graphhopper.storage.RAMDirectory; import org.junit.jupiter.api.Test; +import java.util.Arrays; import java.util.Collections; +import java.util.List; import static org.junit.jupiter.api.Assertions.*; @@ -36,10 +38,11 @@ public void maximumLMWeight() { ); FlagEncoder car = new CarFlagEncoder(); EncodingManager em = EncodingManager.create(car); - handler - .addLMConfig(new LMConfig("conf1", new FastestWeighting(car))) - .addLMConfig(new LMConfig("conf2", new ShortestWeighting(car))); - handler.createPreparations(new GraphHopperStorage(new RAMDirectory(), em, false), null); + List lmConfigs = Arrays.asList( + new LMConfig("conf1", new FastestWeighting(car)), + new LMConfig("conf2", new ShortestWeighting(car)) + ); + handler.createPreparations(lmConfigs, new GraphHopperStorage(new RAMDirectory(), em, false), null); assertEquals(1, handler.getPreparations().get(0).getLandmarkStorage().getFactor(), .1); assertEquals(0.3, handler.getPreparations().get(1).getLandmarkStorage().getFactor(), .1); } diff --git a/core/src/test/java/com/graphhopper/routing/lm/LandmarkStorageTest.java b/core/src/test/java/com/graphhopper/routing/lm/LandmarkStorageTest.java index 55f268c287a..db8adc40d5b 100644 --- a/core/src/test/java/com/graphhopper/routing/lm/LandmarkStorageTest.java +++ b/core/src/test/java/com/graphhopper/routing/lm/LandmarkStorageTest.java @@ -107,6 +107,14 @@ public void testSetGetWeight() { assertEquals(65534, lms.getFromWeight(0, 0)); lms.setWeight(0, 79999); assertEquals(65534, lms.getFromWeight(0, 0)); + + lms._getInternalDA().setInt(0, Integer.MAX_VALUE); + assertTrue(lms.isInfinity(0)); + // for infinity return much bigger value + // assertEquals(Integer.MAX_VALUE, lms.getFromWeight(0, 0)); + + lms.setWeight(0, 79999); + assertFalse(lms.isInfinity(0)); } @Test diff --git a/core/src/test/java/com/graphhopper/routing/util/AbstractBikeFlagEncoderTester.java b/core/src/test/java/com/graphhopper/routing/util/AbstractBikeFlagEncoderTester.java index ffbea2d1cb6..01e0c6b0537 100644 --- a/core/src/test/java/com/graphhopper/routing/util/AbstractBikeFlagEncoderTester.java +++ b/core/src/test/java/com/graphhopper/routing/util/AbstractBikeFlagEncoderTester.java @@ -374,38 +374,38 @@ public void testBarrierAccess() { ReaderNode node = new ReaderNode(1, -1, -1); node.setTag("barrier", "gate"); // no barrier! - assertTrue(encoder.handleNodeTags(node) == 0); + assertFalse(encoder.isBarrier(node)); node.setTag("bicycle", "yes"); // no barrier! - assertTrue(encoder.handleNodeTags(node) == 0); + assertFalse(encoder.isBarrier(node)); node = new ReaderNode(1, -1, -1); node.setTag("barrier", "gate"); node.setTag("access", "no"); // barrier! - assertTrue(encoder.handleNodeTags(node) > 0); + assertTrue(encoder.isBarrier(node)); node = new ReaderNode(1, -1, -1); node.setTag("barrier", "gate"); node.setTag("access", "yes"); node.setTag("bicycle", "no"); // barrier! - assertTrue(encoder.handleNodeTags(node) > 0); + assertTrue(encoder.isBarrier(node)); node = new ReaderNode(1, -1, -1); node.setTag("barrier", "gate"); node.setTag("access", "no"); node.setTag("foot", "yes"); // barrier! - assertTrue(encoder.handleNodeTags(node) > 0); + assertTrue(encoder.isBarrier(node)); node = new ReaderNode(1, -1, -1); node.setTag("barrier", "gate"); node.setTag("access", "no"); node.setTag("bicycle", "yes"); // no barrier! - assertTrue(encoder.handleNodeTags(node) == 0); + assertFalse(encoder.isBarrier(node)); } @Test @@ -413,11 +413,11 @@ public void testBarrierAccessFord() { ReaderNode node = new ReaderNode(1, -1, -1); node.setTag("ford", "yes"); // barrier! - assertTrue(encoder.handleNodeTags(node) > 0); + assertTrue(encoder.isBarrier(node)); node.setTag("bicycle", "yes"); // no barrier! - assertTrue(encoder.handleNodeTags(node) == 0); + assertFalse(encoder.isBarrier(node)); } @Test diff --git a/core/src/test/java/com/graphhopper/routing/util/BikeFlagEncoderTest.java b/core/src/test/java/com/graphhopper/routing/util/BikeFlagEncoderTest.java index 4f8abfafeb3..e113cfdefb5 100644 --- a/core/src/test/java/com/graphhopper/routing/util/BikeFlagEncoderTest.java +++ b/core/src/test/java/com/graphhopper/routing/util/BikeFlagEncoderTest.java @@ -626,19 +626,19 @@ public void testBarrierAccess() { ReaderNode node = new ReaderNode(1, -1, -1); node.setTag("barrier", "kissing_gate"); // barrier! - assertFalse(encoder.handleNodeTags(node) == 0); + assertTrue(encoder.isBarrier(node)); // kissing_gate with bicycle tag node = new ReaderNode(1, -1, -1); node.setTag("barrier", "kissing_gate"); node.setTag("bicycle", "yes"); // no barrier! - assertTrue(encoder.handleNodeTags(node) == 0); + assertFalse(encoder.isBarrier(node)); // Test if cattle_grid is non blocking node = new ReaderNode(1, -1, -1); node.setTag("barrier", "cattle_grid"); - assertTrue(encoder.handleNodeTags(node) == 0); + assertFalse(encoder.isBarrier(node)); } @Test diff --git a/core/src/test/java/com/graphhopper/routing/util/CarFlagEncoderTest.java b/core/src/test/java/com/graphhopper/routing/util/CarFlagEncoderTest.java index 031b30825e3..c180c830b36 100644 --- a/core/src/test/java/com/graphhopper/routing/util/CarFlagEncoderTest.java +++ b/core/src/test/java/com/graphhopper/routing/util/CarFlagEncoderTest.java @@ -155,12 +155,12 @@ public void testFordAccess() { // Node and way are initially blocking assertTrue(encoder.isBlockFords()); assertTrue(encoder.getAccess(way).canSkip()); - assertTrue(encoder.handleNodeTags(node) > 0); + assertTrue(encoder.isBarrier(node)); CarFlagEncoder tmpEncoder = new CarFlagEncoder(new PMap("block_fords=false")); EncodingManager.create(tmpEncoder); assertTrue(tmpEncoder.getAccess(way).isWay()); - assertFalse(tmpEncoder.handleNodeTags(node) > 0); + assertFalse(tmpEncoder.isBarrier(node)); } @Test @@ -543,32 +543,32 @@ public void testBarrierAccess() { node.setTag("barrier", "lift_gate"); node.setTag("access", "yes"); // no barrier! - assertTrue(encoder.handleNodeTags(node) == 0); + assertFalse(encoder.isBarrier(node)); node = new ReaderNode(1, -1, -1); node.setTag("barrier", "lift_gate"); node.setTag("bicycle", "yes"); // no barrier! - assertTrue(encoder.handleNodeTags(node) == 0); + assertFalse(encoder.isBarrier(node)); node = new ReaderNode(1, -1, -1); node.setTag("barrier", "lift_gate"); node.setTag("access", "yes"); node.setTag("bicycle", "yes"); // should this be a barrier for motorcars too? - // assertTrue(encoder.handleNodeTags(node) > 0); + // assertTrue(encoder.handleNodeTags(node) == true); node = new ReaderNode(1, -1, -1); node.setTag("barrier", "lift_gate"); node.setTag("access", "no"); node.setTag("motorcar", "yes"); // no barrier! - assertTrue(encoder.handleNodeTags(node) == 0); + assertFalse(encoder.isBarrier(node)); node = new ReaderNode(1, -1, -1); node.setTag("barrier", "bollard"); // barrier! - assertTrue(encoder.handleNodeTags(node) > 0); + assertTrue(encoder.isBarrier(node)); CarFlagEncoder tmpEncoder = new CarFlagEncoder(); EncodingManager.create(tmpEncoder); @@ -576,7 +576,7 @@ public void testBarrierAccess() { // Test if cattle_grid is not blocking node = new ReaderNode(1, -1, -1); node.setTag("barrier", "cattle_grid"); - assertTrue(tmpEncoder.handleNodeTags(node) == 0); + assertFalse(tmpEncoder.isBarrier(node)); } @Test @@ -584,11 +584,11 @@ public void testChainBarrier() { // by default allow access through the gate for bike & foot! ReaderNode node = new ReaderNode(1, -1, -1); node.setTag("barrier", "chain"); - assertTrue(encoder.handleNodeTags(node) == 0); + assertFalse(encoder.isBarrier(node)); node.setTag("motor_vehicle", "no"); - assertTrue(encoder.handleNodeTags(node) > 0); + assertTrue(encoder.isBarrier(node)); node.setTag("motor_vehicle", "yes"); - assertTrue(encoder.handleNodeTags(node) == 0); + assertFalse(encoder.isBarrier(node)); } @Test @@ -666,7 +666,7 @@ public void testIssue_1256() { EncodingManager.create(lowFactorCar); List list = new ArrayList<>(); lowFactorCar.setEncodedValueLookup(em); - lowFactorCar.createEncodedValues(list, "car", 0); + lowFactorCar.createEncodedValues(list, "car"); assertEquals(2.5, encoder.ferrySpeedCalc.getSpeed(way), .1); assertEquals(.5, lowFactorCar.ferrySpeedCalc.getSpeed(way), .1); } diff --git a/core/src/test/java/com/graphhopper/routing/util/EncodingManagerTest.java b/core/src/test/java/com/graphhopper/routing/util/EncodingManagerTest.java index e58095a56e9..6db31711ab1 100644 --- a/core/src/test/java/com/graphhopper/routing/util/EncodingManagerTest.java +++ b/core/src/test/java/com/graphhopper/routing/util/EncodingManagerTest.java @@ -36,32 +36,8 @@ public class EncodingManagerTest { @Test - public void testCompatibility() { - EncodingManager manager = EncodingManager.create("car,bike,foot"); - BikeFlagEncoder bike = (BikeFlagEncoder) manager.getEncoder("bike"); - CarFlagEncoder car = (CarFlagEncoder) manager.getEncoder("car"); - FootFlagEncoder foot = (FootFlagEncoder) manager.getEncoder("foot"); - assertNotEquals(car, bike); - assertNotEquals(car, foot); - assertNotEquals(car.hashCode(), bike.hashCode()); - assertNotEquals(car.hashCode(), foot.hashCode()); - - FootFlagEncoder foot2 = new FootFlagEncoder(); - EncodingManager.create(foot2); - assertNotEquals(foot, foot2); - assertNotEquals(foot.hashCode(), foot2.hashCode()); - - FootFlagEncoder foot3 = new FootFlagEncoder(); - EncodingManager.create(foot3); - assertEquals(foot3, foot2); - assertEquals(foot3.hashCode(), foot2.hashCode()); - - try { - EncodingManager.create("car,car"); - fail("there should have been an exception, do not allow duplicate flag encoders"); - } catch (Exception ex) { - // ok - } + public void duplicateNamesNotAllowed() { + assertThrows(IllegalArgumentException.class, () -> EncodingManager.create("car,car")); } @Test diff --git a/core/src/test/java/com/graphhopper/routing/util/FootFlagEncoderTest.java b/core/src/test/java/com/graphhopper/routing/util/FootFlagEncoderTest.java index 4c73f6c85fe..79e173bcf20 100644 --- a/core/src/test/java/com/graphhopper/routing/util/FootFlagEncoderTest.java +++ b/core/src/test/java/com/graphhopper/routing/util/FootFlagEncoderTest.java @@ -343,39 +343,39 @@ public void testBarrierAccess() { ReaderNode node = new ReaderNode(1, -1, -1); node.setTag("barrier", "gate"); // no barrier! - assertEquals(0, footEncoder.handleNodeTags(node)); + assertFalse(footEncoder.isBarrier(node)); node = new ReaderNode(1, -1, -1); node.setTag("barrier", "gate"); node.setTag("access", "yes"); // no barrier! - assertEquals(0, footEncoder.handleNodeTags(node)); + assertFalse(footEncoder.isBarrier(node)); node = new ReaderNode(1, -1, -1); node.setTag("barrier", "gate"); node.setTag("access", "no"); // barrier! - assertTrue(footEncoder.handleNodeTags(node) > 0); + assertTrue(footEncoder.isBarrier(node)); node.setTag("bicycle", "yes"); // no barrier!? - // assertTrue(footEncoder.handleNodeTags(node) == 0); + // assertTrue(footEncoder.handleNodeTags(node) == false); node = new ReaderNode(1, -1, -1); node.setTag("barrier", "gate"); node.setTag("access", "no"); node.setTag("foot", "yes"); // no barrier! - assertEquals(0, footEncoder.handleNodeTags(node)); + assertFalse(footEncoder.isBarrier(node)); node.setTag("locked", "yes"); // barrier! - assertTrue(footEncoder.handleNodeTags(node) > 0); + assertTrue(footEncoder.isBarrier(node)); node.clearTags(); node.setTag("barrier", "yes"); node.setTag("access", "no"); - assertTrue(footEncoder.handleNodeTags(node) > 0); + assertTrue(footEncoder.isBarrier(node)); } @Test @@ -383,9 +383,9 @@ public void testChainBarrier() { // by default allow access through the gate for bike & foot! ReaderNode node = new ReaderNode(1, -1, -1); node.setTag("barrier", "chain"); - assertEquals(0, footEncoder.handleNodeTags(node)); + assertFalse(footEncoder.isBarrier(node)); node.setTag("foot", "no"); - assertTrue(footEncoder.handleNodeTags(node) > 0); + assertTrue(footEncoder.isBarrier(node)); } @Test @@ -393,26 +393,26 @@ public void testFord() { // by default do not block access due to fords! ReaderNode node = new ReaderNode(1, -1, -1); node.setTag("ford", "no"); - assertEquals(0, footEncoder.handleNodeTags(node)); + assertFalse(footEncoder.isBarrier(node)); node = new ReaderNode(1, -1, -1); node.setTag("ford", "yes"); // no barrier! - assertEquals(0, footEncoder.handleNodeTags(node)); + assertFalse(footEncoder.isBarrier(node)); // barrier! node.setTag("foot", "no"); - assertTrue(footEncoder.handleNodeTags(node) > 0); + assertTrue(footEncoder.isBarrier(node)); FootFlagEncoder tmpEncoder = new FootFlagEncoder(new PMap("block_fords=true")); EncodingManager.create(tmpEncoder); node = new ReaderNode(1, -1, -1); node.setTag("ford", "no"); - assertEquals(0, tmpEncoder.handleNodeTags(node)); + assertFalse(tmpEncoder.isBarrier(node)); node = new ReaderNode(1, -1, -1); node.setTag("ford", "yes"); - assertTrue(tmpEncoder.handleNodeTags(node) != 0); + assertTrue(tmpEncoder.isBarrier(node)); } @Test @@ -423,36 +423,36 @@ public void testBlockByDefault() { ReaderNode node = new ReaderNode(1, -1, -1); node.setTag("barrier", "gate"); // potential barriers are no barrier by default - assertEquals(0, tmpFootEncoder.handleNodeTags(node)); + assertFalse(tmpFootEncoder.isBarrier(node)); node.setTag("access", "no"); - assertTrue(tmpFootEncoder.handleNodeTags(node) > 0); + assertTrue(tmpFootEncoder.isBarrier(node)); // absolute barriers always block node = new ReaderNode(1, -1, -1); node.setTag("barrier", "fence"); - assertTrue(tmpFootEncoder.handleNodeTags(node) > 0); + assertTrue(tmpFootEncoder.isBarrier(node)); node.setTag("barrier", "fence"); node.setTag("access", "yes"); - assertFalse(tmpFootEncoder.handleNodeTags(node) > 0); + assertFalse(tmpFootEncoder.isBarrier(node)); // pass potential barriers per default (if no other access tag exists) tmpFootEncoder = new FootFlagEncoder(); EncodingManager.create(tmpFootEncoder); node = new ReaderNode(1, -1, -1); node.setTag("barrier", "gate"); - assertFalse(tmpFootEncoder.handleNodeTags(node) > 0); + assertFalse(tmpFootEncoder.isBarrier(node)); node.setTag("access", "yes"); - assertEquals(0, tmpFootEncoder.handleNodeTags(node)); + assertFalse(tmpFootEncoder.isBarrier(node)); node = new ReaderNode(1, -1, -1); node.setTag("barrier", "fence"); - assertTrue(tmpFootEncoder.handleNodeTags(node) > 0); + assertTrue(tmpFootEncoder.isBarrier(node)); // don't block potential barriers: barrier:cattle_grid should not block here tmpFootEncoder = new FootFlagEncoder(); EncodingManager.create(tmpFootEncoder); node = new ReaderNode(1, -1, -1); node.setTag("barrier", "cattle_grid"); - assertEquals(0, tmpFootEncoder.handleNodeTags(node)); + assertFalse(tmpFootEncoder.isBarrier(node)); } } diff --git a/core/src/test/java/com/graphhopper/routing/util/HeadingEdgeFilterTest.java b/core/src/test/java/com/graphhopper/routing/util/HeadingEdgeFilterTest.java new file mode 100644 index 00000000000..95045e55f8f --- /dev/null +++ b/core/src/test/java/com/graphhopper/routing/util/HeadingEdgeFilterTest.java @@ -0,0 +1,26 @@ +package com.graphhopper.routing.util; + +import com.graphhopper.storage.GraphBuilder; +import com.graphhopper.storage.GraphHopperStorage; +import com.graphhopper.util.EdgeIteratorState; +import com.graphhopper.util.shapes.GHPoint; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +class HeadingEdgeFilterTest { + + @Test + public void getHeading() { + GHPoint point = new GHPoint(55.67093, 12.577294); + CarFlagEncoder carEncoder = new CarFlagEncoder(); + EncodingManager encodingManager = new EncodingManager.Builder().add(carEncoder).build(); + GraphHopperStorage g = new GraphBuilder(encodingManager).create(); + EdgeIteratorState edge = g.edge(0, 1); + g.getNodeAccess().setNode(0, 55.671044, 12.5771583); + g.getNodeAccess().setNode(1, 55.6704136, 12.5784324); + // GHUtility.setSpeed(50, 0, carEncoder, edge.getFlags()); + + assertEquals(131.2, HeadingEdgeFilter.getHeadingOfGeometryNearPoint(edge, point, 20), .1); + } +} \ No newline at end of file diff --git a/core/src/test/java/com/graphhopper/routing/util/MountainBikeFlagEncoderTest.java b/core/src/test/java/com/graphhopper/routing/util/MountainBikeFlagEncoderTest.java index 33c12fb48c6..ffa0793a191 100644 --- a/core/src/test/java/com/graphhopper/routing/util/MountainBikeFlagEncoderTest.java +++ b/core/src/test/java/com/graphhopper/routing/util/MountainBikeFlagEncoderTest.java @@ -195,21 +195,21 @@ public void testBarrierAccess() { ReaderNode node = new ReaderNode(1, -1, -1); node.setTag("barrier", "kissing_gate"); // No barrier! - assertTrue(encoder.handleNodeTags(node) == 0); + assertFalse(encoder.isBarrier(node)); // kissing_gate with bicycle tag = no node = new ReaderNode(1, -1, -1); node.setTag("barrier", "kissing_gate"); node.setTag("bicycle", "no"); // barrier! - assertFalse(encoder.handleNodeTags(node) == 0); + assertTrue(encoder.isBarrier(node)); // kissing_gate with bicycle tag node = new ReaderNode(1, -1, -1); node.setTag("barrier", "kissing_gate"); node.setTag("bicycle", "yes"); // No barrier! - assertTrue(encoder.handleNodeTags(node) == 0); + assertFalse(encoder.isBarrier(node)); } } diff --git a/core/src/test/java/com/graphhopper/routing/util/WheelchairFlagEncoderTest.java b/core/src/test/java/com/graphhopper/routing/util/WheelchairFlagEncoderTest.java index 1d3a5db7ddc..3cab78f40f3 100644 --- a/core/src/test/java/com/graphhopper/routing/util/WheelchairFlagEncoderTest.java +++ b/core/src/test/java/com/graphhopper/routing/util/WheelchairFlagEncoderTest.java @@ -361,34 +361,34 @@ public void testBarrierAccess() { ReaderNode node = new ReaderNode(1, -1, -1); node.setTag("barrier", "gate"); // no barrier! - assertEquals(0, wheelchairEncoder.handleNodeTags(node)); + assertFalse(wheelchairEncoder.isBarrier(node)); node = new ReaderNode(1, -1, -1); node.setTag("barrier", "gate"); node.setTag("access", "yes"); // no barrier! - assertEquals(0, wheelchairEncoder.handleNodeTags(node)); + assertFalse(wheelchairEncoder.isBarrier(node)); node = new ReaderNode(1, -1, -1); node.setTag("barrier", "gate"); node.setTag("access", "no"); // barrier! - assertTrue(wheelchairEncoder.handleNodeTags(node) > 0); + assertTrue(wheelchairEncoder.isBarrier(node)); node.setTag("bicycle", "yes"); // no barrier!? - // assertTrue(wheelchairEncoder.handleNodeTags(node) == 0); + // assertTrue(wheelchairEncoder.handleNodeTags(node) == false); node = new ReaderNode(1, -1, -1); node.setTag("barrier", "gate"); node.setTag("access", "no"); node.setTag("foot", "yes"); // no barrier! - assertEquals(0, wheelchairEncoder.handleNodeTags(node)); + assertFalse(wheelchairEncoder.isBarrier(node)); node.setTag("locked", "yes"); // barrier! - assertTrue(wheelchairEncoder.handleNodeTags(node) > 0); + assertTrue(wheelchairEncoder.isBarrier(node)); } @Test @@ -399,39 +399,39 @@ public void testBlockByDefault() { ReaderNode node = new ReaderNode(1, -1, -1); node.setTag("barrier", "gate"); // passByDefaultBarriers are no barrier by default - assertEquals(0, tmpWheelchairEncoder.handleNodeTags(node)); + assertFalse(tmpWheelchairEncoder.isBarrier(node)); node.setTag("access", "no"); - assertTrue(tmpWheelchairEncoder.handleNodeTags(node) > 0); + assertTrue(tmpWheelchairEncoder.isBarrier(node)); // these barriers block node = new ReaderNode(1, -1, -1); node.setTag("barrier", "fence"); - assertTrue(tmpWheelchairEncoder.handleNodeTags(node) > 0); + assertTrue(tmpWheelchairEncoder.isBarrier(node)); node.setTag("barrier", "wall"); - assertTrue(tmpWheelchairEncoder.handleNodeTags(node) > 0); + assertTrue(tmpWheelchairEncoder.isBarrier(node)); node.setTag("barrier", "handrail"); - assertTrue(tmpWheelchairEncoder.handleNodeTags(node) > 0); + assertTrue(tmpWheelchairEncoder.isBarrier(node)); node.setTag("barrier", "turnstile"); - assertTrue(tmpWheelchairEncoder.handleNodeTags(node) > 0); + assertTrue(tmpWheelchairEncoder.isBarrier(node)); // Explictly allowed access is allowed node.setTag("barrier", "fence"); node.setTag("access", "yes"); - assertTrue(tmpWheelchairEncoder.handleNodeTags(node) == 0); + assertFalse(tmpWheelchairEncoder.isBarrier(node)); node = new ReaderNode(1, -1, -1); node.setTag("barrier", "gate"); node.setTag("access", "yes"); - assertEquals(0, tmpWheelchairEncoder.handleNodeTags(node)); + assertFalse(tmpWheelchairEncoder.isBarrier(node)); node = new ReaderNode(1, -1, -1); node.setTag("barrier", "kerb"); - assertTrue(tmpWheelchairEncoder.handleNodeTags(node) == 0); + assertFalse(tmpWheelchairEncoder.isBarrier(node)); node.setTag("wheelchair", "yes"); - assertEquals(0, tmpWheelchairEncoder.handleNodeTags(node)); + assertFalse(tmpWheelchairEncoder.isBarrier(node)); node = new ReaderNode(1, -1, -1); node.setTag("barrier", "fence"); - assertTrue(tmpWheelchairEncoder.handleNodeTags(node) > 0); + assertTrue(tmpWheelchairEncoder.isBarrier(node)); } @Test diff --git a/core/src/test/java/com/graphhopper/routing/util/parsers/OSMTollParserTest.java b/core/src/test/java/com/graphhopper/routing/util/parsers/OSMTollParserTest.java index bd645c022bf..4510d49f500 100644 --- a/core/src/test/java/com/graphhopper/routing/util/parsers/OSMTollParserTest.java +++ b/core/src/test/java/com/graphhopper/routing/util/parsers/OSMTollParserTest.java @@ -29,7 +29,7 @@ public void testSimpleTags() { IntsRef intsRef = em.createEdgeFlags(); readerWay.setTag("highway", "primary"); parser.handleWayTags(intsRef, readerWay, false, relFlags); - assertEquals(Toll.NO, tollEnc.getEnum(false, intsRef)); + assertEquals(Toll.MISSING, tollEnc.getEnum(false, intsRef)); intsRef = em.createEdgeFlags(); readerWay.setTag("highway", "primary"); diff --git a/core/src/test/java/com/graphhopper/routing/util/parsers/OSMTurnRelationParserTest.java b/core/src/test/java/com/graphhopper/routing/util/parsers/OSMTurnRelationParserTest.java index 056f20dda90..cd9c1eb03fc 100644 --- a/core/src/test/java/com/graphhopper/routing/util/parsers/OSMTurnRelationParserTest.java +++ b/core/src/test/java/com/graphhopper/routing/util/parsers/OSMTurnRelationParserTest.java @@ -37,7 +37,7 @@ public void testGetRestrictionAsEntries() { @Override public int getInternalNodeIdOfOsmNode(long nodeOsmId) { - return osmNodeToInternal.get(nodeOsmId); + return osmNodeToInternal.getOrDefault(nodeOsmId, -1); } @Override diff --git a/core/src/test/java/com/graphhopper/routing/weighting/custom/CustomWeightingTest.java b/core/src/test/java/com/graphhopper/routing/weighting/custom/CustomWeightingTest.java index 95f9978b9eb..40ab0b5332c 100644 --- a/core/src/test/java/com/graphhopper/routing/weighting/custom/CustomWeightingTest.java +++ b/core/src/test/java/com/graphhopper/routing/weighting/custom/CustomWeightingTest.java @@ -170,7 +170,7 @@ public void testIssueSameKey() { set(avSpeedEnc, 80).set(accessEnc, true, true); CustomModel vehicleModel = new CustomModel(); - vehicleModel.addToSpeed(If("toll != NO", MULTIPLY, 0.8)); + vehicleModel.addToSpeed(If("toll == HGV || toll == ALL", MULTIPLY, 0.8)); vehicleModel.addToSpeed(If("hazmat != NO", MULTIPLY, 0.8)); assertEquals(1.26, createWeighting(vehicleModel).calcEdgeWeight(withToll, false), 0.01); assertEquals(1.26, createWeighting(vehicleModel).calcEdgeWeight(noToll, false), 0.01); diff --git a/core/src/test/java/com/graphhopper/storage/AbstractDirectoryTester.java b/core/src/test/java/com/graphhopper/storage/AbstractDirectoryTester.java index 65b202a44e6..cc2d6eda0dd 100644 --- a/core/src/test/java/com/graphhopper/storage/AbstractDirectoryTester.java +++ b/core/src/test/java/com/graphhopper/storage/AbstractDirectoryTester.java @@ -58,7 +58,7 @@ public void testNoDuplicates() { @Test public void testNoErrorForDACreate() { Directory dir = createDir(); - da = dir.find("testing"); + da = dir.create("testing"); da.create(100); da.flush(); } diff --git a/core/src/test/java/com/graphhopper/storage/AbstractGraphStorageTester.java b/core/src/test/java/com/graphhopper/storage/AbstractGraphStorageTester.java index 9e0d3b9cc46..60b1f6ad127 100644 --- a/core/src/test/java/com/graphhopper/storage/AbstractGraphStorageTester.java +++ b/core/src/test/java/com/graphhopper/storage/AbstractGraphStorageTester.java @@ -26,7 +26,6 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import java.io.Closeable; import java.io.File; import java.util.ArrayList; import java.util.List; @@ -117,7 +116,7 @@ public void tearDown() { public void testSetTooBigDistance_435() { graph = createGHStorage(); - double maxDist = BaseGraph.MAX_DIST; + double maxDist = BaseGraphNodesAndEdges.MAX_DIST; EdgeIteratorState edge1 = GHUtility.setSpeed(60, true, true, carEncoder, graph.edge(0, 1).setDistance(maxDist)); assertEquals(maxDist, edge1.getDistance(), 1); @@ -658,6 +657,7 @@ public void testStringIndex() { @Test public void test8AndMoreBytesForEdgeFlags() { + Directory dir = new RAMDirectory(); List list = new ArrayList<>(); list.add(new CarFlagEncoder(29, 0.001, 0) { @Override @@ -667,7 +667,7 @@ public String toString() { }); list.add(new CarFlagEncoder(29, 0.001, 0)); EncodingManager manager = EncodingManager.create(list); - graph = new GraphHopperStorage(new RAMDirectory(), manager, false).create(defaultSize); + graph = new GraphHopperStorage(dir, manager, false).create(defaultSize); EdgeIteratorState edge = graph.edge(0, 1); IntsRef intsRef = manager.createEdgeFlags(); @@ -677,7 +677,8 @@ public String toString() { assertEquals(Integer.MAX_VALUE / 3, edge.getFlags().ints[0]); graph.close(); - graph = new GraphHopperStorage(new RAMDirectory(), manager, false).create(defaultSize); + dir = new RAMDirectory(); + graph = new GraphHopperStorage(dir, manager, false).create(defaultSize); DecimalEncodedValue avSpeed0Enc = manager.getDecimalEncodedValue(getKey("car0", "average_speed")); BooleanEncodedValue access0Enc = manager.getBooleanEncodedValue(getKey("car0", "access")); diff --git a/core/src/test/java/com/graphhopper/storage/GraphHopperStorageTest.java b/core/src/test/java/com/graphhopper/storage/GraphHopperStorageTest.java index 13956f7b8d1..51187b706ef 100644 --- a/core/src/test/java/com/graphhopper/storage/GraphHopperStorageTest.java +++ b/core/src/test/java/com/graphhopper/storage/GraphHopperStorageTest.java @@ -24,6 +24,8 @@ import com.graphhopper.routing.util.EncodingManager; import com.graphhopper.util.*; import com.graphhopper.util.shapes.BBox; +import org.junit.Ignore; +import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import java.io.File; @@ -55,18 +57,7 @@ protected GraphHopperStorage newGHStorage(Directory dir, boolean enabled3D, int return GraphBuilder.start(encodingManager).setDir(dir).set3D(enabled3D).setSegmentSize(segmentSize).build(); } - @Test - public void testNoCreateCalled() { - try (GraphHopperStorage gs = GraphBuilder.start(encodingManager).build()) { - ((BaseGraph) gs.getBaseGraph()).ensureNodeIndex(123); - fail("IllegalStateException should be raised"); - } catch (IllegalStateException err) { - // ok - } catch (Exception ex) { - fail("IllegalStateException should be raised, but was " + ex.toString()); - } - } - + @Disabled @Test public void testSave_and_fileFormat() { graph = newGHStorage(new RAMDirectory(defaultGraphLoc, true), true).create(defaultSize); @@ -160,16 +151,6 @@ protected void checkGraph(Graph g) { assertEquals(GHUtility.asSet(0), GHUtility.getNeighbors(explorer.setBaseNode(2))); } - @Test - public void testBigDataEdge() { - Directory dir = new RAMDirectory(); - GraphHopperStorage graph = new GraphHopperStorage(dir, encodingManager, false); - graph.create(defaultSize); - ((BaseGraph) graph.getBaseGraph()).setEdgeCount(Integer.MAX_VALUE / 2); - assertTrue(graph.getAllEdges().next()); - graph.close(); - } - @Test public void testDoThrowExceptionIfDimDoesNotMatch() { graph = newGHStorage(new RAMDirectory(defaultGraphLoc, true), false); @@ -272,7 +253,8 @@ public void testLoadGraph_implicitEncodedValues_issue1862() { if (ch) { hopper.getCHPreparationHandler().setCHProfiles(new CHProfile("p_car")); } - assertTrue(hopper.load(defaultGraphLoc)); + hopper.setGraphHopperLocation(defaultGraphLoc); + assertTrue(hopper.load()); graph = hopper.getGraphHopperStorage(); assertEquals(nodes, graph.getNodes()); assertEquals(edges, graph.getAllEdges().length()); @@ -282,10 +264,10 @@ public void testLoadGraph_implicitEncodedValues_issue1862() { // load via explicitly configured FlagEncoders then we can define only one profile hopper.getEncodingManagerBuilder().add(createCarFlagEncoder()).add(new BikeFlagEncoder()); hopper.setProfiles(Collections.singletonList(new Profile("p_car").setVehicle("car").setWeighting("fastest"))); - if (ch) { + if (ch) hopper.getCHPreparationHandler().setCHProfiles(new CHProfile("p_car")); - } - assertTrue(hopper.load(defaultGraphLoc)); + hopper.setGraphHopperLocation(defaultGraphLoc); + assertTrue(hopper.load()); graph = hopper.getGraphHopperStorage(); assertEquals(nodes, graph.getNodes()); assertEquals(edges, graph.getAllEdges().length()); diff --git a/core/src/test/java/com/graphhopper/storage/StorablePropertiesTest.java b/core/src/test/java/com/graphhopper/storage/StorablePropertiesTest.java index 7a98e900659..8df51886b1f 100644 --- a/core/src/test/java/com/graphhopper/storage/StorablePropertiesTest.java +++ b/core/src/test/java/com/graphhopper/storage/StorablePropertiesTest.java @@ -47,23 +47,6 @@ public void testLoad() { instance.close(); } - @Test - public void testVersionCheck() { - StorableProperties instance = new StorableProperties(createDir("", false)); - instance.putCurrentVersions(); - assertTrue(instance.checkVersions(true)); - - instance.put("nodes.version", 0); - assertFalse(instance.checkVersions(true)); - - try { - instance.checkVersions(false); - assertTrue(false); - } catch (Exception ex) { - } - instance.close(); - } - @Test public void testStore() { String dir = "./target/test"; diff --git a/core/src/test/java/com/graphhopper/storage/index/LocationIndexTreeTest.java b/core/src/test/java/com/graphhopper/storage/index/LocationIndexTreeTest.java index c042d57ebdf..38625cc2693 100644 --- a/core/src/test/java/com/graphhopper/storage/index/LocationIndexTreeTest.java +++ b/core/src/test/java/com/graphhopper/storage/index/LocationIndexTreeTest.java @@ -25,12 +25,11 @@ import com.graphhopper.util.shapes.BBox; import com.graphhopper.util.shapes.GHPoint; import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; import java.io.Closeable; -import java.util.Arrays; -import java.util.HashSet; -import java.util.List; -import java.util.Random; +import java.util.*; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertTrue; @@ -42,14 +41,14 @@ public class LocationIndexTreeTest { protected final EncodingManager encodingManager = EncodingManager.create("car"); public static void initSimpleGraph(Graph g, EncodingManager em) { - // 6 | 4 + // 6 | 4 // 5 | // | 6 // 4 | 5 // 3 | // 2 | 1 // 1 | 3 - // 0 | 2 + // 0 | 2 // -1 | 0 // ---|------------------- // |-2 -1 0 1 2 3 4 @@ -62,7 +61,8 @@ public static void initSimpleGraph(Graph g, EncodingManager em) { na.setNode(4, 6, 1); na.setNode(5, 4, 4); na.setNode(6, 4.5, -0.5); - List list = Arrays.asList(g.edge(0, 1), + List list = Arrays.asList( + g.edge(0, 1), g.edge(0, 2), g.edge(2, 3), g.edge(3, 4), @@ -613,4 +613,63 @@ public void testDifferentVehicles() { assertEquals(2, idx.findClosest(1, -1, AccessFilter.allEdges(footEncoder.getAccessEnc())).getClosestNode()); Helper.close((Closeable) g); } + + @ParameterizedTest + @ValueSource(booleans = {true, false}) + public void closeToTowerNode(boolean snapAtBase) { + // 0 - 1 + GraphHopperStorage graph = new GraphBuilder(encodingManager).create(); + NodeAccess na = graph.getNodeAccess(); + na.setNode(0, 51.985500, 19.254000); + na.setNode(1, 51.986000, 19.255000); + DistancePlaneProjection distCalc = new DistancePlaneProjection(); + // we query the location index close to node 0. since the query point is so close to the tower node we expect + // a TOWER snap. this should not depend on whether node 0 is the base or adj node of our edge. + final int snapNode = 0; + final int base = snapAtBase ? 0 : 1; + final int adj = snapAtBase ? 1 : 0; + graph.edge(base, adj).setDistance(distCalc.calcDist(na.getLat(0), na.getLon(0), na.getLat(1), na.getLon(1))); + LocationIndexTree index = new LocationIndexTree(graph, graph.getDirectory()); + index.prepareIndex(); + + GHPoint queryPoint = new GHPoint(51.9855003, 19.2540003); + double distFromTower = distCalc.calcDist(queryPoint.lat, queryPoint.lon, na.getLat(snapNode), na.getLon(snapNode)); + assertTrue(distFromTower < 0.1); + Snap snap = index.findClosest(queryPoint.lat, queryPoint.lon, EdgeFilter.ALL_EDGES); + assertEquals(Snap.Position.TOWER, snap.getSnappedPosition()); + } + + @Test + public void queryBehindBeforeOrBehindLastTowerNode() { + // 0 -x- 1 + GraphHopperStorage graph = new GraphBuilder(encodingManager).create(); + NodeAccess na = graph.getNodeAccess(); + na.setNode(0, 51.985000, 19.254000); + na.setNode(1, 51.986000, 19.255000); + DistancePlaneProjection distCalc = new DistancePlaneProjection(); + EdgeIteratorState edge = graph.edge(0, 1).setDistance(distCalc.calcDist(na.getLat(0), na.getLon(0), na.getLat(1), na.getLon(1))); + edge.setWayGeometry(Helper.createPointList(51.985500, 19.254500)); + LocationIndexTree index = new LocationIndexTree(graph, graph.getDirectory()); + index.prepareIndex(); + { + // snap before last tower node + List output = new ArrayList<>(); + index.traverseEdge(51.985700, 19.254700, edge, (node, normedDist, wayIndex, pos) -> + output.add(node + ", " + Math.round(distCalc.calcDenormalizedDist(normedDist)) + ", " + wayIndex + ", " + pos)); + assertEquals(Arrays.asList( + "1, 39, 2, TOWER", + "1, 26, 1, PILLAR", + "1, 0, 1, EDGE"), output); + } + + { + // snap behind last tower node + List output = new ArrayList<>(); + index.traverseEdge(51.986100, 19.255100, edge, (node, normedDist, wayIndex, pos) -> + output.add(node + ", " + Math.round(distCalc.calcDenormalizedDist(normedDist)) + ", " + wayIndex + ", " + pos)); + assertEquals(Arrays.asList( + "1, 13, 2, TOWER", + "1, 78, 1, PILLAR"), output); + } + } } diff --git a/core/src/test/java/com/graphhopper/util/GHUtilityTest.java b/core/src/test/java/com/graphhopper/util/GHUtilityTest.java index 32ba5e1a2bc..4cdd04b2780 100644 --- a/core/src/test/java/com/graphhopper/util/GHUtilityTest.java +++ b/core/src/test/java/com/graphhopper/util/GHUtilityTest.java @@ -18,13 +18,13 @@ package com.graphhopper.util; import com.graphhopper.coll.GHIntLongHashMap; -import com.graphhopper.routing.ev.BooleanEncodedValue; import com.graphhopper.routing.util.AllEdgesIterator; import com.graphhopper.routing.util.CarFlagEncoder; import com.graphhopper.routing.util.EncodingManager; import com.graphhopper.routing.util.FlagEncoder; -import com.graphhopper.routing.weighting.FastestWeighting; -import com.graphhopper.storage.*; +import com.graphhopper.storage.Graph; +import com.graphhopper.storage.GraphBuilder; +import com.graphhopper.storage.NodeAccess; import org.junit.jupiter.api.Test; import static org.junit.jupiter.api.Assertions.*; @@ -35,7 +35,6 @@ public class GHUtilityTest { private final FlagEncoder carEncoder = new CarFlagEncoder(); private final EncodingManager encodingManager = EncodingManager.create(carEncoder); - private final BooleanEncodedValue accessEnc = carEncoder.getAccessEnc(); Graph createGraph() { return new GraphBuilder(encodingManager).create(); diff --git a/core/src/test/resources/com/graphhopper/reader/osm/test-barriers.xml b/core/src/test/resources/com/graphhopper/reader/osm/test-barriers.xml index b525437cfe1..1465a4a77bb 100644 --- a/core/src/test/resources/com/graphhopper/reader/osm/test-barriers.xml +++ b/core/src/test/resources/com/graphhopper/reader/osm/test-barriers.xml @@ -22,9 +22,12 @@ - + diff --git a/core/src/test/resources/com/graphhopper/reader/osm/test-barriers2.xml b/core/src/test/resources/com/graphhopper/reader/osm/test-barriers2.xml new file mode 100644 index 00000000000..02d97b2cffc --- /dev/null +++ b/core/src/test/resources/com/graphhopper/reader/osm/test-barriers2.xml @@ -0,0 +1,88 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/core/custom-models.md b/docs/core/custom-models.md index 3283d1adf63..94a56c4d3ea 100644 --- a/docs/core/custom-models.md +++ b/docs/core/custom-models.md @@ -70,7 +70,7 @@ encoded values are the following (some of their possible values are given in bra - road_access: (DESTINATION, DELIVERY, PRIVATE, NO, ...) - surface: (PAVED, DIRT, SAND, GRAVEL, ...) - smoothness: (EXCELLENT, GOOD, INTERMEDIATE, ...) -- toll: (NO, ALL, HGV) +- toll: (MISSING, NO, HGV, ALL) To learn about all available encoded values you can query the `/info` endpoint. @@ -120,7 +120,7 @@ Here is a complete request example for a POST /route query in berlin that includ "custom_model": { "speed": [ { - "if": true, + "if": "true", "limit_to": 100 } ], diff --git a/docs/core/profiles.md b/docs/core/profiles.md index d5410409493..780f844dcb1 100644 --- a/docs/core/profiles.md +++ b/docs/core/profiles.md @@ -54,6 +54,9 @@ weightings: - curvature (prefers routes with lots of curves for enjoyable motorcycle rides) - custom (enables custom profiles, see the next section) +Another important profile setting is `turn_costs: true/false`. Use this to enable turn restrictions for each profile. +You can learn more about this setting [here](./turn-restrictions.md) + The profile name is used to select the profile when executing routing queries. To do this use the `profile` request parameter, for example `/route?point=49.5,11.1&profile=car` or `/route?point=49.5,11.1&profile=some_other_profile`. diff --git a/example/pom.xml b/example/pom.xml index 339c8d7455b..0a904b48fe8 100644 --- a/example/pom.xml +++ b/example/pom.xml @@ -5,21 +5,21 @@ 4.0.0 graphhopper-example - 4.9-SNAPSHOT + 5.0-SNAPSHOT jar GraphHopper Example com.github.GIScience.graphhopper graphhopper-parent - 4.9-SNAPSHOT + 5.0-SNAPSHOT com.github.GIScience.graphhopper graphhopper-core - 4.9-SNAPSHOT + 5.0-SNAPSHOT diff --git a/hmm-lib/pom.xml b/hmm-lib/pom.xml index 0958134e3c4..0d4b7dba87a 100644 --- a/hmm-lib/pom.xml +++ b/hmm-lib/pom.xml @@ -19,7 +19,7 @@ 4.0.0 hmm-lib-external - 4.12-SNAPSHOT + 5.0-SNAPSHOT jar hmm-lib @@ -29,7 +29,7 @@ com.github.GIScience.graphhopper graphhopper-parent - 4.12-SNAPSHOT + 5.0-SNAPSHOT diff --git a/map-matching/pom.xml b/map-matching/pom.xml index 6a503b0c7fc..45800119d61 100644 --- a/map-matching/pom.xml +++ b/map-matching/pom.xml @@ -3,20 +3,21 @@ 4.0.0 graphhopper-map-matching + 5.0-SNAPSHOT jar GraphHopper Map Matching com.github.GIScience.graphhopper graphhopper-parent - 4.12-SNAPSHOT + 5.0-SNAPSHOT com.github.GIScience.graphhopper graphhopper-core - 4.12-SNAPSHOT + 5.0-SNAPSHOT org.slf4j @@ -25,7 +26,7 @@ com.github.GIScience.graphhopper hmm-lib-external - 4.12-SNAPSHOT + 5.0-SNAPSHOT ch.qos.logback diff --git a/navigation/pom.xml b/navigation/pom.xml index 680097cfcfb..269aea47b1c 100644 --- a/navigation/pom.xml +++ b/navigation/pom.xml @@ -5,26 +5,26 @@ 4.0.0 graphhopper-nav - 4.9-SNAPSHOT + 5.0-SNAPSHOT jar GraphHopper Navigation com.github.GIScience.graphhopper graphhopper-parent - 4.9-SNAPSHOT + 5.0-SNAPSHOT com.github.GIScience.graphhopper graphhopper-web-api - 4.9-SNAPSHOT + 5.0-SNAPSHOT com.github.GIScience.graphhopper graphhopper-core - 4.9-SNAPSHOT + 5.0-SNAPSHOT io.dropwizard diff --git a/pom.xml b/pom.xml index e9a575d5671..b6f014bf6f6 100644 --- a/pom.xml +++ b/pom.xml @@ -5,8 +5,7 @@ com.github.GIScience.graphhopper graphhopper-parent - GraphHopper Parent Project - 4.12-SNAPSHOT + GraphHopper Parent Project5.0-SNAPSHOT pom https://www.graphhopper.com 2012 @@ -219,7 +218,7 @@ maven-surefire-plugin 2.22.2 - -Xmx180m -Xms180m -Duser.language=en + -Xmx190m -Xms190m -Duser.language=en diff --git a/reader-gtfs/README.md b/reader-gtfs/README.md index c66d7b75929..dc01f957dd9 100644 --- a/reader-gtfs/README.md +++ b/reader-gtfs/README.md @@ -12,7 +12,7 @@ git clone https://github.com/graphhopper/graphhopper cd graphhopper # download GTFS from Berlin & Brandenburg in Germany (VBB) and the 'surrounding' OpenStreetMap data for the walk network -wget -O gtfs-vbb.zip https://www.vbb.de/media/download/2029 +wget -O gtfs-vbb.zip https://www.vbb.de/fileadmin/user_upload/VBB/Dokumente/API-Datensaetze/GTFS.zip wget http://download.geofabrik.de/europe/germany/brandenburg-latest.osm.pbf mvn clean package -DskipTests diff --git a/reader-gtfs/pom.xml b/reader-gtfs/pom.xml index faf0f195a52..5b4f43873ae 100644 --- a/reader-gtfs/pom.xml +++ b/reader-gtfs/pom.xml @@ -10,14 +10,14 @@ com.github.GIScience.graphhopper graphhopper-parent - 4.12-SNAPSHOT + 5.0-SNAPSHOT com.github.GIScience.graphhopper graphhopper-core - 4.12-SNAPSHOT + 5.0-SNAPSHOT com.google.guava diff --git a/reader-gtfs/src/main/java/com/graphhopper/gtfs/PtGraph.java b/reader-gtfs/src/main/java/com/graphhopper/gtfs/PtGraph.java index ce2158d994b..e9a122be848 100644 --- a/reader-gtfs/src/main/java/com/graphhopper/gtfs/PtGraph.java +++ b/reader-gtfs/src/main/java/com/graphhopper/gtfs/PtGraph.java @@ -53,9 +53,9 @@ public PtGraph(Directory dir, int firstNode) { // nodes = dir.create("pt_nodes", dir.getDefaultType("pt_nodes", true), -1); // edges = dir.create("pt_edges", dir.getDefaultType("pt_edges", true), -1); // attrs = dir.create("pt_edge_attrs", dir.getDefaultType("pt_edge_attrs", true), -1); - nodes = dir.find("pt_nodes", DAType.getPreferredInt(dir.getDefaultType())); - edges = dir.find("pt_edges", DAType.getPreferredInt(dir.getDefaultType())); - attrs = dir.find("pt_edge_attrs", DAType.getPreferredInt(dir.getDefaultType())); + nodes = dir.create("pt_nodes", DAType.getPreferredInt(dir.getDefaultType())); + edges = dir.create("pt_edges", DAType.getPreferredInt(dir.getDefaultType())); + attrs = dir.create("pt_edge_attrs", DAType.getPreferredInt(dir.getDefaultType())); nodeEntryBytes = 8; diff --git a/tools/pom.xml b/tools/pom.xml index 72cf4613606..ec7b44dc7a6 100644 --- a/tools/pom.xml +++ b/tools/pom.xml @@ -10,7 +10,7 @@ com.github.GIScience.graphhopper graphhopper-parent - 4.9-SNAPSHOT + 5.0-SNAPSHOT package @@ -20,12 +20,12 @@ com.github.GIScience.graphhopper graphhopper-core - 4.9-SNAPSHOT + 5.0-SNAPSHOT com.github.GIScience.graphhopper graphhopper-web-api - 4.9-SNAPSHOT + 5.0-SNAPSHOT diff --git a/tools/src/main/java/com/graphhopper/tools/Measurement.java b/tools/src/main/java/com/graphhopper/tools/Measurement.java index 5c02adb5152..ec310f0c47d 100644 --- a/tools/src/main/java/com/graphhopper/tools/Measurement.java +++ b/tools/src/main/java/com/graphhopper/tools/Measurement.java @@ -127,17 +127,15 @@ protected void prepareCH(boolean closeEarly) { // note that we measure the total time of all (possibly edge&node) CH preparations put(Parameters.CH.PREPARE + "time", sw.stop().getMillis()); int edges = getGraphHopperStorage().getEdges(); - if (!getCHPreparationHandler().getNodeBasedCHConfigs().isEmpty()) { - CHConfig chConfig = getCHPreparationHandler().getNodeBasedCHConfigs().get(0); - int edgesAndShortcuts = getGraphHopperStorage().getRoutingCHGraph(chConfig.getName()).getEdges(); + if (getGraphHopperStorage().getRoutingCHGraph("profile_no_tc") != null) { + int edgesAndShortcuts = getGraphHopperStorage().getRoutingCHGraph("profile_no_tc").getEdges(); put(Parameters.CH.PREPARE + "node.shortcuts", edgesAndShortcuts - edges); - put(Parameters.CH.PREPARE + "node.time", getCHPreparationHandler().getPreparation(chConfig).getTotalPrepareTime()); + put(Parameters.CH.PREPARE + "node.time", getCHPreparationHandler().getPreparation("profile_no_tc").getTotalPrepareTime()); } - if (!getCHPreparationHandler().getEdgeBasedCHConfigs().isEmpty()) { - CHConfig chConfig = getCHPreparationHandler().getEdgeBasedCHConfigs().get(0); - int edgesAndShortcuts = getGraphHopperStorage().getRoutingCHGraph(chConfig.getName()).getEdges(); + if (getGraphHopperStorage().getRoutingCHGraph("profile_tc") != null) { + int edgesAndShortcuts = getGraphHopperStorage().getRoutingCHGraph("profile_tc").getEdges(); put(Parameters.CH.PREPARE + "edge.shortcuts", edgesAndShortcuts - edges); - put(Parameters.CH.PREPARE + "edge.time", getCHPreparationHandler().getPreparation(chConfig).getTotalPrepareTime()); + put(Parameters.CH.PREPARE + "edge.time", getCHPreparationHandler().getPreparation("profile_tc").getTotalPrepareTime()); } } @@ -226,10 +224,9 @@ protected void importOSM() { boolean isCH = true; boolean isLM = false; gcAndWait(); - if (!hopper.getCHPreparationHandler().getNodeBasedCHConfigs().isEmpty()) { - CHConfig chConfig = hopper.getCHPreparationHandler().getNodeBasedCHConfigs().get(0); - RoutingCHGraph lg = g.getRoutingCHGraph(chConfig.getName()); - measureGraphTraversalCH(lg, count * 100); + RoutingCHGraph nodeBasedCH = g.getRoutingCHGraph("profile_no_tc"); + if (nodeBasedCH != null) { + measureGraphTraversalCH(nodeBasedCH, count * 100); gcAndWait(); measureRouting(hopper, new QuerySettings("routingCH", count, isCH, isLM). withInstructions().sod()); @@ -249,7 +246,8 @@ protected void importOSM() { measureRouting(hopper, new QuerySettings("routingCH_via_100_full", count / 100, isCH, isLM). withPoints(100).sod().withInstructions().simplify().pathDetails()); } - if (!hopper.getCHPreparationHandler().getEdgeBasedCHConfigs().isEmpty()) { + RoutingCHGraph edgeBasedCH = g.getRoutingCHGraph("profile_tc"); + if (edgeBasedCH != null) { measureRouting(hopper, new QuerySettings("routingCH_edge", count, isCH, isLM). edgeBased().withInstructions()); measureRouting(hopper, new QuerySettings("routingCH_edge_alt", count / 10, isCH, isLM). diff --git a/tools/src/main/java/com/graphhopper/ui/MiniGraphUI.java b/tools/src/main/java/com/graphhopper/ui/MiniGraphUI.java index ef99a8ea82a..bca6bedcd56 100644 --- a/tools/src/main/java/com/graphhopper/ui/MiniGraphUI.java +++ b/tools/src/main/java/com/graphhopper/ui/MiniGraphUI.java @@ -336,10 +336,8 @@ public void paintComponent(final Graphics2D g2) { private RoutingAlgorithm createAlgo(GraphHopper hopper) { Profile profile = hopper.getProfiles().iterator().next(); if (useCH) { - CHConfig chConfig = hopper.getCHPreparationHandler().getNodeBasedCHConfigs().get(0); - Weighting weighting = chConfig.getWeighting(); - RoutingCHGraph chGraph = hopper.getGraphHopperStorage().getRoutingCHGraph(chConfig.getName()); - logger.info("CH algo, weighting: " + weighting); + RoutingCHGraph chGraph = hopper.getGraphHopperStorage().getRoutingCHGraph(profile.getName()); + logger.info("CH algo, profile: " + profile.getName()); QueryGraph qGraph = QueryGraph.create(hopper.getGraphHopperStorage(), fromRes, toRes); QueryRoutingCHGraph queryRoutingCHGraph = new QueryRoutingCHGraph(chGraph, qGraph); return new CHDebugAlgo(queryRoutingCHGraph, mg); diff --git a/web-api/pom.xml b/web-api/pom.xml index 4750186bb25..856e5808ee7 100644 --- a/web-api/pom.xml +++ b/web-api/pom.xml @@ -5,14 +5,14 @@ 4.0.0 graphhopper-web-api jar - 4.12-SNAPSHOT + 5.0-SNAPSHOT GraphHopper Web API JSON Representation of the API classes com.github.GIScience.graphhopper graphhopper-parent - 4.12-SNAPSHOT + 5.0-SNAPSHOT diff --git a/web-bundle/pom.xml b/web-bundle/pom.xml index 4f1a585ad56..e3fa7ff36b7 100644 --- a/web-bundle/pom.xml +++ b/web-bundle/pom.xml @@ -5,36 +5,36 @@ 4.0.0 graphhopper-web-bundle jar - 4.9-SNAPSHOT + 5.0-SNAPSHOT GraphHopper Dropwizard Bundle Use the GraphHopper routing engine as a web-service com.github.GIScience.graphhopper graphhopper-parent - 4.9-SNAPSHOT + 5.0-SNAPSHOT com.github.GIScience.graphhopper graphhopper-web-api - 4.9-SNAPSHOT + 5.0-SNAPSHOT com.github.GIScience.graphhopper graphhopper-core - 4.9-SNAPSHOT + 5.0-SNAPSHOT com.github.GIScience.graphhopper graphhopper-reader-gtfs - 4.9-SNAPSHOT + 5.0-SNAPSHOT com.github.GIScience.graphhopper graphhopper-map-matching - 4.9-SNAPSHOT + 5.0-SNAPSHOT @@ -118,7 +118,7 @@ com.github.GIScience.graphhopper directions-api-client-hc - 4.9-SNAPSHOT + 5.0-SNAPSHOT test diff --git a/web-bundle/src/main/java/com/graphhopper/resources/IsochroneResource.java b/web-bundle/src/main/java/com/graphhopper/resources/IsochroneResource.java index 12a9b471e56..b2910476f88 100644 --- a/web-bundle/src/main/java/com/graphhopper/resources/IsochroneResource.java +++ b/web-bundle/src/main/java/com/graphhopper/resources/IsochroneResource.java @@ -143,13 +143,11 @@ public Response doGet( for (Double z : zs) { logger.info("Building contour z={}", z); MultiPolygon isochrone = contourBuilder.computeIsoline(z, result.seedEdges); - if (!isochrone.isEmpty()) { - if (fullGeometry) { - isochrones.add(isochrone); - } else { - Polygon maxPolygon = heuristicallyFindMainConnectedComponent(isochrone, isochrone.getFactory().createPoint(new Coordinate(point.get().lon, point.get().lat))); - isochrones.add(isochrone.getFactory().createPolygon(((LinearRing) maxPolygon.getExteriorRing()))); - } + if (fullGeometry) { + isochrones.add(isochrone); + } else { + Polygon maxPolygon = heuristicallyFindMainConnectedComponent(isochrone, isochrone.getFactory().createPoint(new Coordinate(point.get().lon, point.get().lat))); + isochrones.add(isochrone.getFactory().createPolygon(((LinearRing) maxPolygon.getExteriorRing()))); } } ArrayList features = new ArrayList<>(); diff --git a/web-bundle/src/main/java/com/graphhopper/resources/PtIsochroneResource.java b/web-bundle/src/main/java/com/graphhopper/resources/PtIsochroneResource.java index 6e70aae5cef..5ce7475950f 100644 --- a/web-bundle/src/main/java/com/graphhopper/resources/PtIsochroneResource.java +++ b/web-bundle/src/main/java/com/graphhopper/resources/PtIsochroneResource.java @@ -27,7 +27,6 @@ import java.util.List; import java.util.Locale; import java.util.Map; -import java.util.function.Function; import java.util.function.Predicate; import javax.inject.Inject; import javax.ws.rs.DefaultValue; @@ -86,8 +85,6 @@ public class PtIsochroneResource { private final GraphHopperStorage graphHopperStorage; private final LocationIndex locationIndex; - private final Function z = label -> (double) label.currentTime; - @Inject public PtIsochroneResource(GtfsStorage gtfsStorage, EncodingManager encodingManager, GraphHopperStorage graphHopperStorage, LocationIndex locationIndex) { this.gtfsStorage = gtfsStorage; @@ -121,7 +118,7 @@ public Response doGet( throw new IllegalArgumentException(String.format(Locale.ROOT, "Illegal value for required parameter %s: [%s]", "pt.earliest_departure_time", departureTimeString)); } - double targetZ = initialTime.toEpochMilli() + seconds * 1000; + double targetZ = seconds * 1000; GeometryFactory geometryFactory = new GeometryFactory(); final FlagEncoder footEncoder = encodingManager.getEncoder("foot"); @@ -142,15 +139,15 @@ public Response doGet( MultiCriteriaLabelSetting.SPTVisitor sptVisitor = nodeLabel -> { Coordinate nodeCoordinate = new Coordinate(nodeAccess.getLon(nodeLabel.adjNode), nodeAccess.getLat(nodeLabel.adjNode)); - z1.merge(nodeCoordinate, this.z.apply(nodeLabel), Math::min); + z1.merge(nodeCoordinate, (double) (nodeLabel.currentTime - initialTime.toEpochMilli()) * (reverseFlow ? -1 : 1), Math::min); }; if (format.equals("multipoint")) { - calcLabels(router, snap.getClosestNode(), initialTime, sptVisitor, label -> label.currentTime <= targetZ); + calcLabels(router, snap.getClosestNode(), initialTime, sptVisitor, label -> (label.currentTime - initialTime.toEpochMilli()) * (reverseFlow ? -1 : 1) <= targetZ); MultiPoint exploredPoints = geometryFactory.createMultiPointFromCoords(z1.keySet().toArray(new Coordinate[0])); return wrap(exploredPoints); } else { - calcLabels(router, snap.getClosestNode(), initialTime, sptVisitor, label -> label.currentTime <= targetZ); + calcLabels(router, snap.getClosestNode(), initialTime, sptVisitor, label -> (label.currentTime - initialTime.toEpochMilli()) * (reverseFlow ? -1 : 1) <= targetZ); MultiPoint exploredPoints = geometryFactory.createMultiPointFromCoords(z1.keySet().toArray(new Coordinate[0])); // Get at least all nodes within our bounding box (I think convex hull would be enough.) diff --git a/web-bundle/src/main/resources/com/graphhopper/maps/index.html b/web-bundle/src/main/resources/com/graphhopper/maps/index.html index 011f6d92920..bc0519f073a 100644 --- a/web-bundle/src/main/resources/com/graphhopper/maps/index.html +++ b/web-bundle/src/main/resources/com/graphhopper/maps/index.html @@ -36,7 +36,7 @@ - + diff --git a/web-bundle/src/main/resources/com/graphhopper/maps/js/main-template.js b/web-bundle/src/main/resources/com/graphhopper/maps/js/main-template.js index e387bed0bc8..4576fef8023 100644 --- a/web-bundle/src/main/resources/com/graphhopper/maps/js/main-template.js +++ b/web-bundle/src/main/resources/com/graphhopper/maps/js/main-template.js @@ -302,7 +302,7 @@ $(document).ready(function (e) { checkInput(); }, function (err) { console.log(err); - $('#error').html('GraphHopper API offline? Refresh' + '
Status: ' + err.statusText + '
' + host); + $('#error').html('GraphHopper API offline? Refresh' + '
Status: ' + err.statusText + '
' + host); bounds = { "minLon": -180, diff --git a/web/pom.xml b/web/pom.xml index 331fd367bff..cdf33042d43 100644 --- a/web/pom.xml +++ b/web/pom.xml @@ -5,14 +5,14 @@ 4.0.0 graphhopper-web jar - 4.9-SNAPSHOT + 5.0-SNAPSHOT GraphHopper Web Use the GraphHopper routing engine as a web-service com.github.GIScience.graphhopper graphhopper-parent - 4.9-SNAPSHOT + 5.0-SNAPSHOT package @@ -30,12 +30,12 @@ com.github.GIScience.graphhopper graphhopper-web-bundle - 4.9-SNAPSHOT + 5.0-SNAPSHOT com.github.GIScience.graphhopper graphhopper-nav - 4.9-SNAPSHOT + 5.0-SNAPSHOT @@ -66,7 +66,7 @@ com.github.GIScience.graphhopper directions-api-client-hc - 4.9-SNAPSHOT + 5.0-SNAPSHOT test diff --git a/web/src/test/java/com/graphhopper/http/resources/RouteResourceTest.java b/web/src/test/java/com/graphhopper/http/resources/RouteResourceTest.java index b6c9799f261..09600480a10 100644 --- a/web/src/test/java/com/graphhopper/http/resources/RouteResourceTest.java +++ b/web/src/test/java/com/graphhopper/http/resources/RouteResourceTest.java @@ -295,9 +295,9 @@ public void testPathDetails() { List edgeIdDetails = pathDetails.get("edge_id"); assertEquals(77, edgeIdDetails.size()); - assertEquals(880L, edgeIdDetails.get(0).getValue()); + assertEquals(882L, edgeIdDetails.get(0).getValue()); assertEquals(2, edgeIdDetails.get(0).getLength()); - assertEquals(881L, edgeIdDetails.get(1).getValue()); + assertEquals(883L, edgeIdDetails.get(1).getValue()); assertEquals(8, edgeIdDetails.get(1).getLength()); long expectedTime = rsp.getBest().getTime(); @@ -352,8 +352,8 @@ public void testPathDetailsWithoutGraphHopperWeb() { JsonNode edgeIds = details.get("edge_id"); int firstLink = edgeIds.get(0).get(2).asInt(); int lastLink = edgeIds.get(edgeIds.size() - 1).get(2).asInt(); - assertEquals(880, firstLink); - assertEquals(1420, lastLink); + assertEquals(882, firstLink); + assertEquals(1425, lastLink); JsonNode maxSpeed = details.get("max_speed"); assertEquals(-1, maxSpeed.get(0).get(2).asDouble(-1), .01); diff --git a/web/src/test/java/com/graphhopper/http/resources/SPTResourceTest.java b/web/src/test/java/com/graphhopper/http/resources/SPTResourceTest.java index e92a100c71a..29509764072 100644 --- a/web/src/test/java/com/graphhopper/http/resources/SPTResourceTest.java +++ b/web/src/test/java/com/graphhopper/http/resources/SPTResourceTest.java @@ -96,9 +96,9 @@ public void requestSPTEdgeBased() { String[] lines = rspCsvString.split("\n"); assertTrue(lines.length > 500); assertEquals("prev_node_id,edge_id,node_id,time,distance", lines[0]); - assertEquals("-1,-1,1941,0,0", lines[1]); - assertEquals("1941,2270,1324,3817,74", lines[2]); - assertEquals("1941,2269,263,13495,262", lines[3]); + assertEquals("-1,-1,1948,0,0", lines[1]); + assertEquals("1948,2277,1324,3817,74", lines[2]); + assertEquals("1948,2276,263,13495,262", lines[3]); } @Test