diff --git a/CHANGELOG.md b/CHANGELOG.md index de0f0924fa2..22d12544e32 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,9 @@ -### 9.0 [not yet released] +### 10.0 [not yet released] + +- constructor of BaseGraph.Builder uses byte instead of integer count. +- KeyValue is now KValue as it holds the value only. Note, the two parameter constructor uses one value for the forward and one for the backward direction (and no longer "key, value") + +### 9.0 [23 Apr 2024] - max_slope is now a signed decimal, see #2955 - move sac_scale handling out of foot_access parser and made foot safer via lowering to sac_scale<2, same for hike sac_scale<5 diff --git a/README.md b/README.md index 1bc4103e9ad..d5f228bca1e 100644 --- a/README.md +++ b/README.md @@ -12,31 +12,37 @@ can import [other data sources too](README.md#OpenStreetMap-Support). # Community -We have an open community and welcome everyone. Let us know your problems, use cases or just [say hello](https://discuss.graphhopper.com/). Please see our [community guidelines](https://graphhopper.com/agreements/cccoc.html). +We have an open community and welcome everyone. Let us know your problems, use cases or just [say hello](https://discuss.graphhopper.com/). +Please see our [community guidelines](https://graphhopper.com/agreements/cccoc.html). ## Questions -All questions go to our [forum](https://discuss.graphhopper.com/) where we also have subsections specially for developers, mobile usage, and [our map matching component](./map-matching). You can also search [Stackoverflow](http://stackoverflow.com/questions/tagged/graphhopper) for answers. Please do not use our issue section for questions :) +All questions go to our [forum](https://discuss.graphhopper.com/) where we also have subsections specially for developers, mobile usage, and [our map matching component](./map-matching). +You can also search [Stackoverflow](http://stackoverflow.com/questions/tagged/graphhopper) for answers. ## Contribute -Read through [how to contribute](CONTRIBUTING.md) for information on topics -like finding and fixing bugs and improving our documentation or translations! -We even have [good first issues](https://github.com/graphhopper/graphhopper/issues?q=is%3Aopen+is%3Aissue+label%3A%22good+first+issue%22) to get started. +Read through our [contributing guide](CONTRIBUTING.md) for information on topics +like finding and fixing bugs and improving our documentation or translations! +We also have [good first issues](https://github.com/graphhopper/graphhopper/issues?q=is%3Aopen+is%3Aissue+label%3A%22good+first+issue%22) +to get started with contribution. ## Get Started -To get started you can try [GraphHopper Maps](README.md#graphhopper-maps), read through our documentation and install the GraphHopper Web Service locally. +To get started you can try [GraphHopper Maps](README.md#graphhopper-maps), read through [our documentation](./docs/index.md) and install GraphHopper including the Maps UI locally. -* 8.x: [documentation](https://github.com/graphhopper/graphhopper/blob/8.x/docs/index.md) - , [web service jar](https://repo1.maven.org/maven2/com/graphhopper/graphhopper-web/8.0/graphhopper-web-8.0.jar) - , [announcement](https://www.graphhopper.com/blog/2023/10/18/graphhopper-routing-engine-8-0-released/) +* 9.x: [documentation](https://github.com/graphhopper/graphhopper/blob/9.x/docs/index.md) + , [web service jar](https://repo1.maven.org/maven2/com/graphhopper/graphhopper-web/9.1/graphhopper-web-9.1.jar) + , [announcement](https://www.graphhopper.com/blog/2024/04/23/graphhopper-routing-engine-9-0-released) * unstable master: [documentation](https://github.com/graphhopper/graphhopper/blob/master/docs/index.md)
Click to see older releases * See our [changelog file](./CHANGELOG.md) for Java API Changes. +* 8.x: [documentation](https://github.com/graphhopper/graphhopper/blob/8.x/docs/index.md) + , [web service jar](https://repo1.maven.org/maven2/com/graphhopper/graphhopper-web/8.0/graphhopper-web-8.0.jar) + , [announcement](https://www.graphhopper.com/blog/2023/10/18/graphhopper-routing-engine-8-0-released/) * 7.x: [documentation](https://github.com/graphhopper/graphhopper/blob/7.x/docs/index.md) , [web service jar](https://repo1.maven.org/maven2/com/graphhopper/graphhopper-web/7.0/graphhopper-web-7.0.jar) , [announcement](https://www.graphhopper.com/blog/2023/03/14/graphhopper-routing-engine-7-0-released/) @@ -94,7 +100,7 @@ To get started you can try [GraphHopper Maps](README.md#graphhopper-maps), read To install the [GraphHopper Maps](https://graphhopper.com/maps/) UI and the web service locally you [need a JVM](https://adoptium.net) (>= Java 17) and do: ```bash -wget https://repo1.maven.org/maven2/com/graphhopper/graphhopper-web/8.0/graphhopper-web-8.0.jar https://raw.githubusercontent.com/graphhopper/graphhopper/8.x/config-example.yml http://download.geofabrik.de/europe/germany/berlin-latest.osm.pbf +wget https://repo1.maven.org/maven2/com/graphhopper/graphhopper-web/9.1/graphhopper-web-9.1.jar https://raw.githubusercontent.com/graphhopper/graphhopper/9.x/config-example.yml http://download.geofabrik.de/europe/germany/berlin-latest.osm.pbf java -D"dw.graphhopper.datareader.file=berlin-latest.osm.pbf" -jar graphhopper*.jar server config-example.yml ``` @@ -114,9 +120,14 @@ To see the road routing feature of GraphHopper in action please go to [GraphHopp [![GraphHopper Maps](https://www.graphhopper.com/wp-content/uploads/2022/10/maps2-1024x661.png)](https://graphhopper.com/maps) -GraphHopper Maps is an open source user interface, which you can find [here](https://github.com/graphhopper/graphhopper-maps). It can use this open source routing engine or the [GraphHopper Directions API](https://www.graphhopper.com), which provides the Routing API, a Route Optimization API (based on [jsprit](http://jsprit.github.io/)), a fast Matrix API and an address search (based on [photon](https://github.com/komoot/photon)). The photon project is also supported by the GraphHopper GmbH. Additionally to the GraphHopper Directions API, map tiles from various providers are used where the default is [Omniscale](http://omniscale.com/). +GraphHopper Maps is an open source user interface, which you can find [here](https://github.com/graphhopper/graphhopper-maps). +It can use this open source routing engine or the [GraphHopper Directions API](https://www.graphhopper.com), +which provides the Routing API, a Route Optimization API (based on [jsprit](http://jsprit.github.io/)), +a fast Matrix API and an address search (based on [photon](https://github.com/komoot/photon)). +The photon project is also supported by the GraphHopper GmbH. Additionally to the GraphHopper +Directions API, map tiles from various providers are used, with the default being [Omniscale](http://omniscale.com/). -All this is available for free, via encrypted connections and from German servers for a nice and private route planning experience! +All this is available for free, via encrypted connections and from German servers - for a nice and private route planning experience! ## Public Transit @@ -134,16 +145,18 @@ There is a [web service](./navigation) that can be consumed by [our navigation A ### Offline -Offline routing is [no longer officially supported](https://github.com/graphhopper/graphhopper/issues/1940) but should still work. See -[version 1.0](https://github.com/graphhopper/graphhopper/blob/1.0/docs/android/index.md) with still an Android -demo and [this pull request](http://github.com/graphhopper/graphhopper-ios) of the iOS fork including a demo for iOS. +Offline routing is [no longer officially supported](https://github.com/graphhopper/graphhopper/issues/1940) +but should still work as Android supports most of Java. See [version 1.0](https://github.com/graphhopper/graphhopper/blob/1.0/docs/android/index.md) +with the Android demo and also see [this pull request](http://github.com/graphhopper/graphhopper-ios) of the iOS fork including a demo for iOS. [![simple routing](https://www.graphhopper.com/wp-content/uploads/2016/10/android-demo-screenshot-2.png)](./android/README.md) ## Analysis -Use isochrones to calculate and visualize the reachable area for a certain travel mode +Use isochrones to calculate and visualize the reachable area for a certain travel mode. + +You can try the debug user interface at http://localhost:8989/maps/isochrone to see the `/isochrone` and `/spt` endpoint in action. ### [Isochrone Web API](./docs/web/api-doc.md#isochrone) @@ -153,9 +166,6 @@ Use isochrones to calculate and visualize the reachable area for a certain trave [![high precision reachability image](https://www.graphhopper.com/wp-content/uploads/2018/06/berlin-reachability-768x401.png)](https://www.graphhopper.com/blog/2018/07/04/high-precision-reachability/) -To support these high precision reachability approaches there is the /spt -endpoint (shortest path tree). [See #1577](https://github.com/graphhopper/graphhopper/pull/1577) - ### [Map Matching](./map-matching) There is the map matching subproject to snap GPX traces to the road. @@ -229,34 +239,33 @@ client. ### Desktop -GraphHopper also runs on the Desktop in a Java application without internet access. -For debugging purposes GraphHopper can produce vector tiles, i.e. a visualization of the road network in the browser (see #1572). Also a more low level Swing-based UI is provided via MiniGraphUI in the tools module, see some -visualizations done with it [here](https://graphhopper.com/blog/2016/01/19/alternative-roads-to-rome/). -A fast and production ready map visualization for the Desktop can be implemented via [mapsforge](https://github.com/mapsforge/mapsforge) or [mapsforge vtm](https://github.com/mapsforge/vtm). +GraphHopper also runs on the Desktop in a Java application without internet access. For debugging +purposes GraphHopper can produce vector tiles, i.e. a visualization of the road network in the +browser (see #1572). Also a more low level Swing-based UI is provided via MiniGraphUI in the +tools module, see some visualizations done with it [here](https://graphhopper.com/blog/2016/01/19/alternative-roads-to-rome/). +A fast and production-ready map visualization for the Desktop can be implemented via [mapsforge](https://github.com/mapsforge/mapsforge) or [mapsforge vtm](https://github.com/mapsforge/vtm). # Features -Here is a list of the more detailed features: - * Works out of the box with OpenStreetMap (osm/xml and pbf) and can be adapted to custom data - * OpenStreetMap integration: stores and considers road type, speed limit, the surface, barriers, access restrictions, ferries, [conditional access restrictions](https://github.com/graphhopper/graphhopper/pull/621), ... + * OpenStreetMap integration: stores and considers road type, speed limit, the surface, barriers, access restrictions, ferries, conditional access restrictions and more * GraphHopper is fast. And with the so called "Contraction Hierarchies" it can be even faster (enabled by default). * Memory efficient data structures, algorithms and [the low and high level API](./docs/core/low-level-api.md) is tuned towards ease of use and efficiency - * Pre-built routing profiles: car, bike, racing bike, mountain bike, foot, hike, motorcycle, ... - * [Customization of these profiles](./docs/core/profiles.md#custom-profiles) are possible and e.g. get truck routing or support for cargo bikes and [many other changes](https://www.graphhopper.com/blog/2020/05/31/examples-for-customizable-routing/) + * Pre-built routing profiles: car, bike, racing bike, mountain bike, foot, hike, truck, bus, motorcycle, ... + * [Customization of these profiles](./docs/core/profiles.md#custom-profiles) are possible. Read about it [here](https://www.graphhopper.com/blog/2020/05/31/examples-for-customizable-routing/). * Provides a powerful [web API](./docs/web/api-doc.md) that exposes the data from OpenStreetMap and allows customizing the vehicle profiles per request. With JavaScript and Java clients. - * Does [map matching](./map-matching) - * Supports public transit routing and [GTFS](./reader-gtfs/README.md). - * Offers turn instructions in more than 45 languages, contribute or improve [here](./docs/core/translations.md) - * Displays and takes into account [elevation data](./docs/core/elevation.md) - * [Alternative routes](https://discuss.graphhopper.com/t/alternative-routes/424) - * [Turn costs and restrictions](./docs/core/turn-restrictions.md) - * Country specific routing via country rules - * Allows customizing routing behavior using custom areas - * The core uses only a few dependencies (hppc, jts, janino and slf4j) - * Scales from small indoor-sized to world-wide-sized graphs - * Finds nearest point on street e.g. to get elevation or 'snap to road' or being used as spatial index (see [#1485](https://github.com/graphhopper/graphhopper/pull/1485)) - * Calculates isochrones and [shortest path trees](https://github.com/graphhopper/graphhopper/pull/1577) - * Shows the whole road network in the browser for debugging purposes ("vector tile support") [#1572](https://github.com/graphhopper/graphhopper/pull/1572) - * Shows details along a route like road_class or max_speed ("path details") [#1142](https://github.com/graphhopper/graphhopper/pull/1142) - * Written Java and simple start for developers via Maven. + * Provides [map matching](./map-matching) i.e. "snap to road". + * Supports time-dependent public transit routing and reading [GTFS](./reader-gtfs/README.md). + * Offers turn instructions in more than 45 languages. Contribute or improve [here](./docs/core/translations.md). + * Displays and takes into account [elevation data](./docs/core/elevation.md). + * Supports [alternative routes](https://discuss.graphhopper.com/t/alternative-routes/424). + * Supports [turn costs and restrictions](./docs/core/turn-restrictions.md). + * Offers country-specific routing via country rules. + * Allows customizing routing behavior using custom areas. + * The core uses only a few dependencies (hppc, jts, janino and slf4j). + * Scales from small indoor-sized to world-wide-sized graphs. + * Finds nearest point on street e.g. to get elevation or 'snap to road' or being used as spatial index (see [#1485](https://github.com/graphhopper/graphhopper/pull/1485)). + * Calculates isochrones and [shortest path trees](https://github.com/graphhopper/graphhopper/pull/1577). + * Shows the whole road network in the browser for debugging purposes ("vector tile support"), see [#1572](https://github.com/graphhopper/graphhopper/pull/1572). + * Shows so called "path details" along a route like road_class or max_speed, see [#1142](https://github.com/graphhopper/graphhopper/pull/1142) or the web documentation. + * Written in Java and simple to start for developers via Maven. diff --git a/benchmark/benchmark.sh b/benchmark/benchmark.sh index a063553032e..d41d6e0a594 100755 --- a/benchmark/benchmark.sh +++ b/benchmark/benchmark.sh @@ -90,7 +90,7 @@ measurement.count=5000 \ measurement.use_measurement_time_as_ref_time=${USE_MEASUREMENT_TIME_AS_REF_TIME} echo "3 - big map with a custom model that is 'very customized', i.e. has many custom weighting rules" -echo "node-based CH + LM" +echo "node-based CH + LM + slow routing" java -cp tools/target/graphhopper-tools-*-jar-with-dependencies.jar \ -XX:+UseParallelGC -Xmx20g -Xms20g \ com.graphhopper.tools.Measurement \ @@ -102,7 +102,7 @@ measurement.clean=true \ measurement.stop_on_error=true \ measurement.summaryfile=${SUMMARY_DIR}summary_big_very_custom.dat \ measurement.repeats=1 \ -measurement.run_slow_routing=false \ +measurement.run_slow_routing=true \ measurement.weighting=custom \ measurement.custom_model_file=benchmark/very_custom.json \ graph.encoded_values=max_width,max_height,toll,hazmat,road_access,road_class \ diff --git a/client-hc/pom.xml b/client-hc/pom.xml index 069d1716972..052a7589d4e 100644 --- a/client-hc/pom.xml +++ b/client-hc/pom.xml @@ -22,14 +22,14 @@ 4.0.0 directions-api-client-hc - 9.0-SNAPSHOT + 10.0-SNAPSHOT jar GraphHopper Directions API hand-crafted Java Client. com.graphhopper graphhopper-parent - 9.0-SNAPSHOT + 10.0-SNAPSHOT diff --git a/config-example.yml b/config-example.yml index ae0bc91b42f..a7c0e7a0141 100644 --- a/config-example.yml +++ b/config-example.yml @@ -35,6 +35,8 @@ graphhopper: # u_turn_costs: 60 custom_model_files: [car.json] +# You can use the following in-built profiles. After you start GraphHopper it will print which encoded values you'll have to add to graph.encoded_values in this config file. +# # - name: foot # custom_model_files: [foot.json, foot_elevation.json] # @@ -46,10 +48,20 @@ graphhopper: # # - name: mtb # custom_model_files: [mtb.json, bike_elevation.json] +# +# # See the bus.json for more details. +# - name: bus +# turn_costs: +# vehicle_types: [bus, motor_vehicle] +# u_turn_costs: 60 +# custom_model_files: [bus.json] +# +# Other custom models not listed here are: car4wd.json, motorcycle.json, truck.json or cargo-bike.json. You might need to modify and test them before production usage. +# See ./core/src/main/resources/com/graphhopper/custom_models and let us know if you customize them, improve them or create new onces! +# Also there is the curvature.json custom model which might be useful for a motorcyle profile or the opposite for a truck profile. +# Then specify a folder where to find your own custom model files: +# custom_models.directory: custom_models - # instead of the inbuilt custom models (see ./core/src/main/resources/com/graphhopper/custom_models) - # you can specify a folder where to find your own custom model files - # custom_models.directory: custom_models # Speed mode: # It's possible to speed up routing by doing a special graph preparation (Contraction Hierarchies, CH). This requires diff --git a/core/pom.xml b/core/pom.xml index 0591d7da9d9..166b975b719 100644 --- a/core/pom.xml +++ b/core/pom.xml @@ -5,7 +5,7 @@ graphhopper-core GraphHopper Core - 9.0-SNAPSHOT + 10.0-SNAPSHOT jar GraphHopper is a fast and memory efficient Java road routing engine @@ -14,7 +14,7 @@ com.graphhopper graphhopper-parent - 9.0-SNAPSHOT + 10.0-SNAPSHOT diff --git a/core/src/main/java/com/graphhopper/GraphHopper.java b/core/src/main/java/com/graphhopper/GraphHopper.java index 5c3e2903fa5..56c742094a3 100644 --- a/core/src/main/java/com/graphhopper/GraphHopper.java +++ b/core/src/main/java/com/graphhopper/GraphHopper.java @@ -663,7 +663,11 @@ public static Map parseEncodedValueString(String encodedValuesStr) Map encodedValuesWithProps = new LinkedHashMap<>(); Arrays.stream(encodedValuesStr.split(",")) .filter(evStr -> !evStr.isBlank()) - .forEach(evStr -> encodedValuesWithProps.put(evStr.trim().split("\\|")[0], new PMap(evStr))); + .forEach(evStr -> { + String key = evStr.trim().split("\\|")[0]; + if (encodedValuesWithProps.put(key, new PMap(evStr)) != null) + throw new IllegalArgumentException("duplicate encoded value in config graph.encoded_values: " + key); + }); return encodedValuesWithProps; } diff --git a/core/src/main/java/com/graphhopper/reader/dem/SRTMProvider.java b/core/src/main/java/com/graphhopper/reader/dem/SRTMProvider.java index 6c043aa5336..27f2a7365fe 100644 --- a/core/src/main/java/com/graphhopper/reader/dem/SRTMProvider.java +++ b/core/src/main/java/com/graphhopper/reader/dem/SRTMProvider.java @@ -169,4 +169,4 @@ String getFileName(double lat, double lon) { String getDownloadURL(double lat, double lon) { return getFileName(lat, lon) + ".hgt.zip"; } -} \ No newline at end of file +} diff --git a/core/src/main/java/com/graphhopper/reader/osm/OSMNodeData.java b/core/src/main/java/com/graphhopper/reader/osm/OSMNodeData.java index 7749ce266de..ef60d92fb2a 100644 --- a/core/src/main/java/com/graphhopper/reader/osm/OSMNodeData.java +++ b/core/src/main/java/com/graphhopper/reader/osm/OSMNodeData.java @@ -252,9 +252,9 @@ public void addCoordinatesToPointList(long id, PointList pointList) { public void setTags(ReaderNode node) { int tagIndex = Math.toIntExact(nodeTagIndicesByOsmNodeIds.get(node.getId())); if (tagIndex == -1) { - long pointer = nodeKVStorage.add(node.getTags().entrySet().stream().map(m -> new KVStorage.KeyValue(m.getKey(), - m.getValue() instanceof String ? KVStorage.cutString((String) m.getValue()) : m.getValue())). - collect(Collectors.toList())); + long pointer = nodeKVStorage.add(node.getTags().entrySet().stream().collect( + Collectors.toMap(Map.Entry::getKey, // same key + e -> new KVStorage.KValue(e.getValue() instanceof String ? KVStorage.cutString((String) e.getValue()) : e.getValue())))); if (pointer > Integer.MAX_VALUE) throw new IllegalStateException("Too many key value pairs are stored in node tags, was " + pointer); nodeTagIndicesByOsmNodeIds.put(node.getId(), (int) pointer); 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 9aa47e3704b..088f20dee76 100644 --- a/core/src/main/java/com/graphhopper/reader/osm/OSMReader.java +++ b/core/src/main/java/com/graphhopper/reader/osm/OSMReader.java @@ -60,9 +60,10 @@ import java.util.regex.Pattern; import java.util.stream.Collectors; -import static com.graphhopper.search.KVStorage.KeyValue.*; +import static com.graphhopper.search.KVStorage.KValue; import static com.graphhopper.util.GHUtility.OSM_WARNING_LOGGER; import static com.graphhopper.util.Helper.nf; +import static com.graphhopper.util.Parameters.Details.*; import static java.util.Collections.emptyList; /** @@ -103,7 +104,7 @@ public class OSMReader { public OSMReader(BaseGraph baseGraph, OSMParsers osmParsers, OSMReaderConfig config) { this.baseGraph = baseGraph; - this.edgeIntAccess = baseGraph.createEdgeIntAccess(); + this.edgeIntAccess = baseGraph.getEdgeAccess(); this.config = config; this.nodeAccess = baseGraph.getNodeAccess(); this.osmParsers = osmParsers; @@ -165,7 +166,7 @@ public void readGraph() throws IOException { throw new IllegalStateException("BaseGraph must be initialize before we can read OSM"); WaySegmentParser waySegmentParser = new WaySegmentParser.Builder(baseGraph.getNodeAccess(), baseGraph.getDirectory()) - .setElevationProvider(eleProvider) + .setElevationProvider(this::getElevation) .setWayFilter(this::acceptWay) .setSplitNodeFilter(this::isBarrierNode) .setWayPreprocessor(this::preprocessWay) @@ -175,7 +176,7 @@ public void readGraph() throws IOException { .setWorkerThreads(config.getWorkerThreads()) .build(); waySegmentParser.readOSM(osmFile); - osmDataDate = waySegmentParser.getTimeStamp(); + osmDataDate = waySegmentParser.getTimestamp(); if (baseGraph.getNodes() == 0) throw new RuntimeException("Graph after reading OSM must not be empty"); releaseEverythingExceptRestrictionData(); @@ -192,6 +193,11 @@ public Date getDataDate() { return osmDataDate; } + protected double getElevation(ReaderNode node) { + double ele = eleProvider.getEle(node); + return Double.isNaN(ele) ? config.getDefaultElevation() : ele; + } + /** * This method is called for each way during the first and second pass of the {@link WaySegmentParser}. All OSM * ways that are not accepted here and all nodes that are not referenced by any such way will be ignored. @@ -370,9 +376,9 @@ else if (!config.getElevationSmoothing().isEmpty()) IntsRef relationFlags = getRelFlagsMap(way.getId()); EdgeIteratorState edge = baseGraph.edge(fromIndex, toIndex).setDistance(distance); osmParsers.handleWayTags(edge.getEdge(), edgeIntAccess, way, relationFlags); - List list = way.getTag("key_values", Collections.emptyList()); - if (!list.isEmpty()) - edge.setKeyValues(list); + Map map = way.getTag("key_values", Collections.emptyMap()); + if (!map.isEmpty()) + edge.setKeyValues(map); // If the entire way is just the first and last point, do not waste space storing an empty way geometry if (pointList.size() > 2) { @@ -414,7 +420,7 @@ else if (Math.abs(edgeDistance - geometryDistance) > tolerance) */ protected void preprocessWay(ReaderWay way, WaySegmentParser.CoordinateSupplier coordinateSupplier, WaySegmentParser.NodeTagSupplier nodeTagSupplier) { - List list = new ArrayList<>(); + Map map = new LinkedHashMap<>(); if (config.isParseWayNames()) { // http://wiki.openstreetmap.org/wiki/Key:name String name = ""; @@ -423,28 +429,28 @@ protected void preprocessWay(ReaderWay way, WaySegmentParser.CoordinateSupplier if (name.isEmpty()) name = fixWayName(way.getTag("name")); if (!name.isEmpty()) - list.add(new KVStorage.KeyValue(STREET_NAME, name)); + map.put(STREET_NAME, new KValue(name)); // http://wiki.openstreetmap.org/wiki/Key:ref String refName = fixWayName(way.getTag("ref")); if (!refName.isEmpty()) - list.add(new KVStorage.KeyValue(STREET_REF, refName)); + map.put(STREET_REF, new KValue(refName)); if (way.hasTag("destination:ref")) { - list.add(new KVStorage.KeyValue(STREET_DESTINATION_REF, fixWayName(way.getTag("destination:ref")))); + map.put(STREET_DESTINATION_REF, new KValue(fixWayName(way.getTag("destination:ref")))); } else { - if (way.hasTag("destination:ref:forward")) - list.add(new KVStorage.KeyValue(STREET_DESTINATION_REF, fixWayName(way.getTag("destination:ref:forward")), true, false)); - if (way.hasTag("destination:ref:backward")) - list.add(new KVStorage.KeyValue(STREET_DESTINATION_REF, fixWayName(way.getTag("destination:ref:backward")), false, true)); + String fwdStr = fixWayName(way.getTag("destination:ref:forward")); + String bwdStr = fixWayName(way.getTag("destination:ref:backward")); + if (!fwdStr.isEmpty() || !bwdStr.isEmpty()) + map.put(STREET_DESTINATION_REF, new KValue(fwdStr.isEmpty() ? null : fwdStr, bwdStr.isEmpty() ? null : bwdStr)); } if (way.hasTag("destination")) { - list.add(new KVStorage.KeyValue(STREET_DESTINATION, fixWayName(way.getTag("destination")))); + map.put(STREET_DESTINATION, new KValue(fixWayName(way.getTag("destination")))); } else { - if (way.hasTag("destination:forward")) - list.add(new KVStorage.KeyValue(STREET_DESTINATION, fixWayName(way.getTag("destination:forward")), true, false)); - if (way.hasTag("destination:backward")) - list.add(new KVStorage.KeyValue(STREET_DESTINATION, fixWayName(way.getTag("destination:backward")), false, true)); + String fwdStr = fixWayName(way.getTag("destination:forward")); + String bwdStr = fixWayName(way.getTag("destination:backward")); + if (!fwdStr.isEmpty() || !bwdStr.isEmpty()) + map.put(STREET_DESTINATION, new KValue(fwdStr.isEmpty() ? null : fwdStr, bwdStr.isEmpty() ? null : bwdStr)); } // copy node name of motorway_junction @@ -454,7 +460,7 @@ protected void preprocessWay(ReaderWay way, WaySegmentParser.CoordinateSupplier Map nodeTags = nodeTagSupplier.getTags(nodes.get(0)); String nodeName = (String) nodeTags.getOrDefault("name", ""); if (!nodeName.isEmpty() && "motorway_junction".equals(nodeTags.getOrDefault("highway", ""))) - list.add(new KVStorage.KeyValue(MOTORWAY_JUNCTION, nodeName)); + map.put(MOTORWAY_JUNCTION, new KValue(nodeName)); } } @@ -469,14 +475,16 @@ protected void preprocessWay(ReaderWay way, WaySegmentParser.CoordinateSupplier String key = entry.getKey().replace(':', '_').replace("bicycle", "bike"); boolean fwd = key.contains("forward"); boolean bwd = key.contains("backward"); - if (!fwd && !bwd) - list.add(new KVStorage.KeyValue(key, value, true, true)); - else - list.add(new KVStorage.KeyValue(key, value, fwd, bwd)); + if (!value.isEmpty()) { + if (fwd == bwd) + map.put(key, new KValue(value)); + else + map.put(key, new KValue(fwd ? value : null, bwd ? value : null)); + } } } - way.setTag("key_values", list); + way.setTag("key_values", map); if (!isCalculateWayDistance(way)) return; diff --git a/core/src/main/java/com/graphhopper/reader/osm/PillarInfo.java b/core/src/main/java/com/graphhopper/reader/osm/PillarInfo.java index 3aca5a49a44..d5485f89cf4 100644 --- a/core/src/main/java/com/graphhopper/reader/osm/PillarInfo.java +++ b/core/src/main/java/com/graphhopper/reader/osm/PillarInfo.java @@ -62,7 +62,7 @@ public void setNode(long nodeId, double lat, double lon, double ele) { da.setInt(tmp + LON, Helper.degreeToInt(lon)); if (is3D()) - da.setInt(tmp + ELE, Helper.eleToInt(ele)); + da.setInt(tmp + ELE, Helper.eleToUInt(ele)); } public double getLat(long id) { @@ -80,7 +80,7 @@ public double getEle(long id) { return Double.NaN; int intVal = da.getInt(id * rowSizeInBytes + ELE); - return Helper.intToEle(intVal); + return Helper.uIntToEle(intVal); } public void clear() { diff --git a/core/src/main/java/com/graphhopper/reader/osm/WaySegmentParser.java b/core/src/main/java/com/graphhopper/reader/osm/WaySegmentParser.java index 33312167ec4..fe7ee35f411 100644 --- a/core/src/main/java/com/graphhopper/reader/osm/WaySegmentParser.java +++ b/core/src/main/java/com/graphhopper/reader/osm/WaySegmentParser.java @@ -23,7 +23,6 @@ import com.graphhopper.reader.ReaderNode; import com.graphhopper.reader.ReaderRelation; import com.graphhopper.reader.ReaderWay; -import com.graphhopper.reader.dem.ElevationProvider; import com.graphhopper.storage.Directory; import com.graphhopper.util.Helper; import com.graphhopper.util.PointAccess; @@ -38,9 +37,7 @@ import java.io.IOException; import java.text.ParseException; import java.util.*; -import java.util.function.Consumer; -import java.util.function.LongToIntFunction; -import java.util.function.Predicate; +import java.util.function.*; import static com.graphhopper.reader.osm.OSMNodeData.*; import static com.graphhopper.util.Helper.nf; @@ -65,7 +62,7 @@ public class WaySegmentParser { private static final Logger LOGGER = LoggerFactory.getLogger(WaySegmentParser.class); private static final Set INCLUDE_IF_NODE_TAGS = new HashSet<>(Arrays.asList("barrier", "highway", "railway", "crossing", "ford")); - private ElevationProvider elevationProvider = ElevationProvider.NOOP; + private ToDoubleFunction elevationProvider = node -> 0d; private Predicate wayFilter = way -> true; private Predicate splitNodeFilter = node -> false; private WayPreprocessor wayPreprocessor = (way, coordinateSupplier, nodeTagSupplier) -> { @@ -118,7 +115,7 @@ public void readOSM(File osmFile) { /** * @return the timestamp read from the OSM file, or null if nothing was read yet */ - public Date getTimeStamp() { + public Date getTimestamp() { return timestamp; } @@ -206,7 +203,7 @@ public void handleNode(ReaderNode node) { LOGGER.info("pass2 - processed nodes: " + nf(nodeCounter) + ", accepted nodes: " + nf(acceptedNodes) + ", " + Helper.getMemInfo()); - long nodeType = nodeData.addCoordinatesIfMapped(node.getId(), node.getLat(), node.getLon(), () -> elevationProvider.getEle(node)); + long nodeType = nodeData.addCoordinatesIfMapped(node.getId(), node.getLat(), node.getLon(), () -> elevationProvider.applyAsDouble(node)); if (nodeType == EMPTY_NODE) return; @@ -425,7 +422,7 @@ public Builder(PointAccess pointAccess, Directory directory) { /** * @param elevationProvider used to determine the elevation of an OSM node */ - public Builder setElevationProvider(ElevationProvider elevationProvider) { + public Builder setElevationProvider(ToDoubleFunction elevationProvider) { waySegmentParser.elevationProvider = elevationProvider; return this; } diff --git a/core/src/main/java/com/graphhopper/routing/AbstractBidirCHAlgo.java b/core/src/main/java/com/graphhopper/routing/AbstractBidirCHAlgo.java index e9c9ad76eb4..dcdab2a89c9 100644 --- a/core/src/main/java/com/graphhopper/routing/AbstractBidirCHAlgo.java +++ b/core/src/main/java/com/graphhopper/routing/AbstractBidirCHAlgo.java @@ -247,7 +247,7 @@ protected double calcWeight(RoutingCHEdgeIteratorState iter, SPTEntry currEdge, @Override protected double getInEdgeWeight(SPTEntry entry) { - return graph.getEdgeIteratorState(getIncomingEdge(entry), entry.adjNode).getWeight(false); + throw new UnsupportedOperationException(); } @Override diff --git a/core/src/main/java/com/graphhopper/routing/InstructionsFromEdges.java b/core/src/main/java/com/graphhopper/routing/InstructionsFromEdges.java index 5230dc16710..8b5e6bb822d 100644 --- a/core/src/main/java/com/graphhopper/routing/InstructionsFromEdges.java +++ b/core/src/main/java/com/graphhopper/routing/InstructionsFromEdges.java @@ -24,7 +24,7 @@ import com.graphhopper.util.*; import com.graphhopper.util.shapes.GHPoint; -import static com.graphhopper.search.KVStorage.KeyValue.*; +import static com.graphhopper.util.Parameters.Details.*; /** * This class calculates instructions from the edges in a Path. @@ -262,7 +262,7 @@ public void next(EdgeIteratorState edge, int index, int prevEdgeId) { && (Math.abs(sign) == Instruction.TURN_SLIGHT_RIGHT || Math.abs(sign) == Instruction.TURN_RIGHT || Math.abs(sign) == Instruction.TURN_SHARP_RIGHT) && (Math.abs(prevInstruction.getSign()) == Instruction.TURN_SLIGHT_RIGHT || Math.abs(prevInstruction.getSign()) == Instruction.TURN_RIGHT || Math.abs(prevInstruction.getSign()) == Instruction.TURN_SHARP_RIGHT) && Double.isFinite(weighting.calcEdgeWeight(edge, false)) != Double.isFinite(weighting.calcEdgeWeight(edge, true)) - && InstructionsHelper.isNameSimilar(prevInstructionName, name)) { + && InstructionsHelper.isSameName(prevInstructionName, name)) { // Chances are good that this is a u-turn, we only need to check if the orientation matches GHPoint point = InstructionsHelper.getPointForOrientationCalculation(edge, nodeAccess); double lat = point.getLat(); @@ -352,7 +352,7 @@ private int getTurn(EdgeIteratorState edge, int baseNode, int prevNode, int adjN // there is no other turn possible if (nrOfPossibleTurns <= 1) { - if (Math.abs(sign) > 1 && outgoingEdges.getVisibleTurns() > 1) { + if (Math.abs(sign) > 1 && outgoingEdges.getVisibleTurns() > 1 && !outgoingEdges.mergedOrSplitWay(lanesEnc)) { // This is an actual turn because |sign| > 1 // There could be some confusion, if we would not create a turn instruction, even though it is the only // possible turn, also see #1048 @@ -366,8 +366,8 @@ private int getTurn(EdgeIteratorState edge, int baseNode, int prevNode, int adjN if (Math.abs(sign) > 1) { // Don't show an instruction if the user is following a street, even though the street is // bending. We should only do this, if following the street is the obvious choice. - if (InstructionsHelper.isNameSimilar(name, prevName) - && (outgoingEdges.outgoingEdgesAreSlowerByFactor(2) || isDirectionSeparatelyTagged(edge, prevEdge))) { + if (InstructionsHelper.isSameName(name, prevName) && outgoingEdges.outgoingEdgesAreSlowerByFactor(2) + || outgoingEdges.mergedOrSplitWay(lanesEnc)) { return Instruction.IGNORE; } @@ -400,9 +400,9 @@ private int getTurn(EdgeIteratorState edge, int baseNode, int prevNode, int adjN // For _links, comparing flags works quite good, as links usually have different speeds => different flags if (otherContinue != null) { // We are at a fork - if (!InstructionsHelper.isNameSimilar(name, prevName) - || !InstructionsHelper.isNameSimilar(destinationAndRef, prevDestinationAndRef) - || InstructionsHelper.isNameSimilar(otherContinue.getName(), prevName) + if (!InstructionsHelper.isSameName(name, prevName) + || !InstructionsHelper.isSameName(destinationAndRef, prevDestinationAndRef) + || InstructionsHelper.isSameName(otherContinue.getName(), prevName) || !outgoingEdgesAreSlower) { final RoadClass roadClass = edge.get(roadClassEnc); @@ -423,7 +423,7 @@ private int getTurn(EdgeIteratorState edge, int baseNode, int prevNode, int adjN double otherDelta = InstructionsHelper.calculateOrientationDelta(prevLat, prevLon, tmpPoint.getLat(), tmpPoint.getLon(), prevOrientation); // This is required to avoid keep left/right on the motorway at off-ramps/motorway_links - if (Math.abs(delta) < .1 && Math.abs(otherDelta) > .15 && InstructionsHelper.isNameSimilar(name, prevName)) { + if (Math.abs(delta) < .1 && Math.abs(otherDelta) > .15 && InstructionsHelper.isSameName(name, prevName)) { return Instruction.CONTINUE_ON_STREET; } @@ -435,7 +435,8 @@ private int getTurn(EdgeIteratorState edge, int baseNode, int prevNode, int adjN } } - if (!outgoingEdgesAreSlower && !isDirectionSeparatelyTagged(edge, prevEdge) + if (!outgoingEdgesAreSlower + && !outgoingEdges.mergedOrSplitWay(lanesEnc) && (Math.abs(delta) > .6 || outgoingEdges.isLeavingCurrentStreet(prevName, name))) { // Leave the current road -> create instruction return sign; @@ -444,15 +445,6 @@ private int getTurn(EdgeIteratorState edge, int baseNode, int prevNode, int adjN return Instruction.IGNORE; } - private boolean isDirectionSeparatelyTagged(EdgeIteratorState edge, EdgeIteratorState prevEdge) { - if (lanesEnc == null) return false; - // for cases like in #2946 we should not create instructions as they are only "tagging artifacts" - int lanes = edge.get(lanesEnc); - int prevLanes = prevEdge.get(lanesEnc); - // Usually it is a 2+2 split and then the equal sign applies. In case of a "3+2 split" we need ">=". - return lanes * 2 >= prevLanes || lanes <= 2 * prevLanes; - } - private void updatePointsAndInstruction(EdgeIteratorState edge, PointList pl) { // skip adjNode int len = pl.size() - 1; diff --git a/core/src/main/java/com/graphhopper/routing/InstructionsHelper.java b/core/src/main/java/com/graphhopper/routing/InstructionsHelper.java index ef26d371f07..5d6ff85858e 100644 --- a/core/src/main/java/com/graphhopper/routing/InstructionsHelper.java +++ b/core/src/main/java/com/graphhopper/routing/InstructionsHelper.java @@ -60,7 +60,7 @@ static int calculateSign(double prevLatitude, double prevLongitude, double latit return Instruction.TURN_SHARP_RIGHT; } - static boolean isNameSimilar(String name1, String name2) { + static boolean isSameName(String name1, String name2) { // We don't want two empty names to be similar (they usually don't have names if they are random tracks) if (name1 == null || name2 == null || name1.isEmpty() || name2.isEmpty()) return false; diff --git a/core/src/main/java/com/graphhopper/routing/InstructionsOutgoingEdges.java b/core/src/main/java/com/graphhopper/routing/InstructionsOutgoingEdges.java index ac2be1e2ba8..b27137e2561 100644 --- a/core/src/main/java/com/graphhopper/routing/InstructionsOutgoingEdges.java +++ b/core/src/main/java/com/graphhopper/routing/InstructionsOutgoingEdges.java @@ -17,10 +17,7 @@ */ package com.graphhopper.routing; -import com.graphhopper.routing.ev.BooleanEncodedValue; -import com.graphhopper.routing.ev.DecimalEncodedValue; -import com.graphhopper.routing.ev.EnumEncodedValue; -import com.graphhopper.routing.ev.RoadClass; +import com.graphhopper.routing.ev.*; import com.graphhopper.routing.weighting.Weighting; import com.graphhopper.storage.NodeAccess; import com.graphhopper.util.EdgeExplorer; @@ -58,15 +55,17 @@ class InstructionsOutgoingEdges { private final EdgeIteratorState prevEdge; private final EdgeIteratorState currentEdge; - // Outgoing edges that we would be allowed to turn on + // edges that one can turn onto private final List allowedAlternativeTurns; - // All outgoing edges, including oneways in the wrong direction + // edges, including oneways in the wrong direction private final List visibleAlternativeTurns; private final DecimalEncodedValue maxSpeedEnc; private final EnumEncodedValue roadClassEnc; private final BooleanEncodedValue roadClassLinkEnc; private final NodeAccess nodeAccess; private final Weighting weighting; + private final int baseNode; + private final EdgeExplorer allExplorer; public InstructionsOutgoingEdges(EdgeIteratorState prevEdge, EdgeIteratorState currentEdge, @@ -86,6 +85,8 @@ public InstructionsOutgoingEdges(EdgeIteratorState prevEdge, this.roadClassEnc = roadClassEnc; this.roadClassLinkEnc = roadClassLinkEnc; this.nodeAccess = nodeAccess; + this.baseNode = baseNode; + this.allExplorer = allExplorer; visibleAlternativeTurns = new ArrayList<>(); allowedAlternativeTurns = new ArrayList<>(); @@ -179,7 +180,7 @@ public EdgeIteratorState getOtherContinue(double prevLat, double prevLon, double * If either of these properties is true, we can be quite certain that a turn instruction should be provided. */ public boolean isLeavingCurrentStreet(String prevName, String name) { - if (InstructionsHelper.isNameSimilar(name, prevName)) { + if (InstructionsHelper.isSameName(name, prevName)) { return false; } @@ -187,11 +188,11 @@ public boolean isLeavingCurrentStreet(String prevName, String name) { for (EdgeIteratorState edge : allowedAlternativeTurns) { String edgeName = edge.getName(); // leave the current street - if (InstructionsHelper.isNameSimilar(prevName, edgeName) || (roadClassOrLinkChange && isTheSameRoadClassAndLink(prevEdge, edge))) { + if (InstructionsHelper.isSameName(prevName, edgeName) || (roadClassOrLinkChange && isTheSameRoadClassAndLink(prevEdge, edge))) { return true; } // enter a different street - if (InstructionsHelper.isNameSimilar(name, edgeName) || (roadClassOrLinkChange && isTheSameRoadClassAndLink(currentEdge, edge))) { + if (InstructionsHelper.isSameName(name, edgeName) || (roadClassOrLinkChange && isTheSameRoadClassAndLink(currentEdge, edge))) { return true; } } @@ -202,4 +203,54 @@ private boolean isTheSameRoadClassAndLink(EdgeIteratorState edge1, EdgeIteratorS return edge1.get(roadClassEnc) == edge2.get(roadClassEnc) && edge1.get(roadClassLinkEnc) == edge2.get(roadClassLinkEnc); } + // for cases like in #2946 we should not create instructions as they are only "tagging artifacts" + public boolean mergedOrSplitWay(IntEncodedValue lanesEnc) { + if (lanesEnc == null) return false; + + String name = currentEdge.getName(); + RoadClass roadClass = currentEdge.get(roadClassEnc); + if (!InstructionsHelper.isSameName(name, prevEdge.getName()) || roadClass != prevEdge.get(roadClassEnc)) + return false; + + EdgeIterator edgeIter = allExplorer.setBaseNode(baseNode); + EdgeIteratorState otherEdge = null; + while (edgeIter.next()) { + if (currentEdge.getEdge() != edgeIter.getEdge() + && prevEdge.getEdge() != edgeIter.getEdge() + && roadClass == edgeIter.get(roadClassEnc) + && InstructionsHelper.isSameName(name, edgeIter.getName()) + && (Double.isFinite(weighting.calcEdgeWeight(edgeIter, false)) + || Double.isFinite(weighting.calcEdgeWeight(edgeIter, true)))) { + if (otherEdge != null) return false; // too many possible other edges + otherEdge = edgeIter.detach(false); + } + } + if (otherEdge == null) return false; + + if (Double.isFinite(weighting.calcEdgeWeight(currentEdge, true))) { + // assume two ways are merged into one way + // -> prev -> + // <- edge -> + // -> other -> + if (Double.isFinite(weighting.calcEdgeWeight(prevEdge, true))) return false; + // otherEdge has direction from junction outwards + if (!Double.isFinite(weighting.calcEdgeWeight(otherEdge, false))) return false; + if (Double.isFinite(weighting.calcEdgeWeight(otherEdge, true))) return false; + + int delta = Math.abs(prevEdge.get(lanesEnc) + otherEdge.get(lanesEnc) - currentEdge.get(lanesEnc)); + return delta <= 1; + } + + // assume one way is split into two ways + // -> edge -> + // <- prev -> + // -> other -> + if (!Double.isFinite(weighting.calcEdgeWeight(prevEdge, true))) return false; + // otherEdge has direction from junction outwards + if (Double.isFinite(weighting.calcEdgeWeight(otherEdge, false))) return false; + if (!Double.isFinite(weighting.calcEdgeWeight(otherEdge, true))) return false; + + int delta = prevEdge.get(lanesEnc) - (currentEdge.get(lanesEnc) + otherEdge.get(lanesEnc)); + return delta <= 1; + } } diff --git a/core/src/main/java/com/graphhopper/routing/OSMReaderConfig.java b/core/src/main/java/com/graphhopper/routing/OSMReaderConfig.java index 7440bfefc13..384e50d42ff 100644 --- a/core/src/main/java/com/graphhopper/routing/OSMReaderConfig.java +++ b/core/src/main/java/com/graphhopper/routing/OSMReaderConfig.java @@ -33,6 +33,7 @@ public class OSMReaderConfig { private int ramerElevationSmoothingMax = 5; private double longEdgeSamplingDistance = Double.MAX_VALUE; private int workerThreads = 2; + private double defaultElevation = 0; public List getIgnoredHighways() { return ignoredHighways; @@ -155,4 +156,16 @@ public OSMReaderConfig setWorkerThreads(int workerThreads) { this.workerThreads = workerThreads; return this; } + + public double getDefaultElevation() { + return defaultElevation; + } + + /** + * Sets the elevation in meters that shall be used if the elevation data source is missing a value + */ + public OSMReaderConfig setDefaultElevation(double defaultElevation) { + this.defaultElevation = defaultElevation; + return this; + } } diff --git a/core/src/main/java/com/graphhopper/routing/ch/CHPreparationGraph.java b/core/src/main/java/com/graphhopper/routing/ch/CHPreparationGraph.java index bd3868f8432..7d11df57096 100644 --- a/core/src/main/java/com/graphhopper/routing/ch/CHPreparationGraph.java +++ b/core/src/main/java/com/graphhopper/routing/ch/CHPreparationGraph.java @@ -21,16 +21,9 @@ import com.carrotsearch.hppc.*; import com.carrotsearch.hppc.sorting.IndirectComparator; import com.carrotsearch.hppc.sorting.IndirectSort; -import com.graphhopper.routing.ev.DecimalEncodedValue; import com.graphhopper.routing.util.AllEdgesIterator; -import com.graphhopper.routing.weighting.AbstractWeighting; -import com.graphhopper.routing.weighting.DefaultTurnCostProvider; -import com.graphhopper.routing.weighting.TurnCostProvider; import com.graphhopper.routing.weighting.Weighting; import com.graphhopper.storage.Graph; -import com.graphhopper.storage.TurnCostStorage; -import com.graphhopper.util.BitUtil; -import com.graphhopper.util.EdgeIterator; import com.graphhopper.util.GHUtility; import static com.graphhopper.util.ArrayUtil.zero; @@ -800,20 +793,20 @@ public String toString() { /** * This helper graph can be used to quickly obtain the edge-keys of the edges of a node. It is only used for - * edge-based CH. In principle we could use base graph for this, but it turned out it is faster to use this + * edge-based CH. In principle, we could use base graph for this, but it turned out it is faster to use this * graph (because it does not need to read all the edge flags to determine the access flags). */ static class OrigGraph { - // we store a list of 'edges' in the format: adjNode|edgeId|accessFlags, we use two ints for each edge - private final IntArrayList adjNodes; - private final IntArrayList keysAndFlags; + // we store a list of 'edges' in the format: adjNode|fwdAccess|edgeKey|bwdAccess, we use two ints for each edge + private final IntArrayList adjNodesAndFwdFlags; + private final IntArrayList keysAndBwdFlags; // for each node we store the index at which the edges for this node begin in the above edge list private final IntArrayList firstEdgesByNode; - private OrigGraph(IntArrayList firstEdgesByNode, IntArrayList adjNodes, IntArrayList keysAndFlags) { + private OrigGraph(IntArrayList firstEdgesByNode, IntArrayList adjNodesAndFwdFlags, IntArrayList keysAndBwdFlags) { this.firstEdgesByNode = firstEdgesByNode; - this.adjNodes = adjNodes; - this.keysAndFlags = keysAndFlags; + this.adjNodesAndFwdFlags = adjNodesAndFwdFlags; + this.keysAndBwdFlags = keysAndBwdFlags; } PrepareGraphOrigEdgeExplorer createOutOrigEdgeExplorer() { @@ -826,21 +819,21 @@ PrepareGraphOrigEdgeExplorer createInOrigEdgeExplorer() { static class Builder { private final IntArrayList fromNodes = new IntArrayList(); - private final IntArrayList toNodes = new IntArrayList(); - private final IntArrayList keysAndFlags = new IntArrayList(); + private final IntArrayList toNodesAndFwdFlags = new IntArrayList(); + private final IntArrayList keysAndBwdFlags = new IntArrayList(); private int maxFrom = -1; private int maxTo = -1; void addEdge(int from, int to, int edge, boolean fwd, boolean bwd) { fromNodes.add(from); - toNodes.add(to); - keysAndFlags.add(getKeyWithFlags(GHUtility.createEdgeKey(edge, false), fwd, bwd)); + toNodesAndFwdFlags.add(getIntWithFlag(to, fwd)); + keysAndBwdFlags.add(getIntWithFlag(GHUtility.createEdgeKey(edge, false), bwd)); maxFrom = Math.max(maxFrom, from); maxTo = Math.max(maxTo, to); fromNodes.add(to); - toNodes.add(from); - keysAndFlags.add(getKeyWithFlags(GHUtility.createEdgeKey(edge, true), bwd, fwd)); + toNodesAndFwdFlags.add(getIntWithFlag(from, bwd)); + keysAndBwdFlags.add(getIntWithFlag(GHUtility.createEdgeKey(edge, true), fwd)); maxFrom = Math.max(maxFrom, to); maxTo = Math.max(maxTo, from); } @@ -848,25 +841,23 @@ void addEdge(int from, int to, int edge, boolean fwd, boolean bwd) { OrigGraph build() { int[] sortOrder = IndirectSort.mergesort(0, fromNodes.elementsCount, new IndirectComparator.AscendingIntComparator(fromNodes.buffer)); sortAndTrim(fromNodes, sortOrder); - sortAndTrim(toNodes, sortOrder); - sortAndTrim(keysAndFlags, sortOrder); - return new OrigGraph(buildFirstEdgesByNode(), toNodes, keysAndFlags); + sortAndTrim(toNodesAndFwdFlags, sortOrder); + sortAndTrim(keysAndBwdFlags, sortOrder); + return new OrigGraph(buildFirstEdgesByNode(), toNodesAndFwdFlags, keysAndBwdFlags); } - private static int getKeyWithFlags(int key, boolean fwd, boolean bwd) { - // we use only 30 bits for the key and store two access flags along with the same int - // this allows for a maximum of 536mio edges in base graph which is still enough for planet-wide OSM, - // but if we exceed this limit we should probably move one of the fwd/bwd bits to the nodes field or - // store the edge instead of the key as we did before #2567 (only here) - if (key > Integer.MAX_VALUE >> 1) - throw new IllegalArgumentException("Maximum edge key exceeded: " + key + ", max: " + (Integer.MAX_VALUE >> 1)); - key <<= 1; - if (fwd) - key++; - key <<= 1; - if (bwd) - key++; - return key; + private static int getIntWithFlag(int val, boolean access) { + // we use only 31 bits for the val and store an access flag along with the same int + // this allows for a maximum of 1073mio edges (and 2147mio nodes) in base graph + // which is still enough for planet-wide OSM, but if we exceed this limit we need to + // move the access bits somewhere else or store the edge instead of the val as we + // did before #2567 (only here) + if (val < 0) + throw new IllegalArgumentException("Maximum node or edge key exceeded: " + val + ", max: " + Integer.MAX_VALUE); + val <<= 1; + if (access) + val++; + return val; } private IntArrayList buildFirstEdgesByNode() { @@ -931,12 +922,12 @@ public int getBaseNode() { @Override public int getAdjNode() { - return graph.adjNodes.get(index); + return graph.adjNodesAndFwdFlags.get(index) >>> 1; } @Override public int getOrigEdgeKeyFirst() { - return graph.keysAndFlags.get(index) >>> 2; + return graph.keysAndBwdFlags.get(index) >>> 1; } @Override @@ -945,11 +936,10 @@ public int getOrigEdgeKeyLast() { } private boolean hasAccess() { - int e = graph.keysAndFlags.get(index); - if (reverse) - return (e & 0b01) == 0b01; - else - return (e & 0b10) == 0b10; + int e = reverse + ? graph.keysAndBwdFlags.get(index) + : graph.adjNodesAndFwdFlags.get(index); + return (e & 0b01) == 0b01; } @Override diff --git a/core/src/main/java/com/graphhopper/routing/ev/ArrayEdgeIntAccess.java b/core/src/main/java/com/graphhopper/routing/ev/ArrayEdgeIntAccess.java index 6de724fd94b..f6a9c22fb78 100644 --- a/core/src/main/java/com/graphhopper/routing/ev/ArrayEdgeIntAccess.java +++ b/core/src/main/java/com/graphhopper/routing/ev/ArrayEdgeIntAccess.java @@ -28,6 +28,13 @@ public ArrayEdgeIntAccess(int intsPerEdge) { this.intsPerEdge = intsPerEdge; } + /** + * Ensures that the underlying storage has enough integers reserved for the specified bytes. + */ + public static ArrayEdgeIntAccess createFromBytes(int bytes) { + return new ArrayEdgeIntAccess((int) Math.ceil((double) bytes / 4)); + } + @Override public int getInt(int edgeId, int index) { int arrIndex = edgeId * intsPerEdge + index; @@ -42,4 +49,4 @@ public void setInt(int edgeId, int index, int value) { arr.set(arrIndex, value); } -} \ No newline at end of file +} diff --git a/core/src/main/java/com/graphhopper/routing/ev/EncodedValue.java b/core/src/main/java/com/graphhopper/routing/ev/EncodedValue.java index 6f8040b00af..7fc8358e894 100644 --- a/core/src/main/java/com/graphhopper/routing/ev/EncodedValue.java +++ b/core/src/main/java/com/graphhopper/routing/ev/EncodedValue.java @@ -68,12 +68,16 @@ void next(int usedBits) { nextShift = shift + usedBits; } - public int getRequiredBits() { + private int getRequiredBits() { return (dataIndex) * 32 + nextShift; } public int getRequiredInts() { return (int) Math.ceil((double) getRequiredBits() / 32.0); } + + public int getRequiredBytes() { + return (int) Math.ceil((double) getRequiredBits() / 8.0); + } } -} \ No newline at end of file +} diff --git a/core/src/main/java/com/graphhopper/routing/ev/HovAccess.java b/core/src/main/java/com/graphhopper/routing/ev/HovAccess.java index f67b3b201a5..71d77cfd792 100644 --- a/core/src/main/java/com/graphhopper/routing/ev/HovAccess.java +++ b/core/src/main/java/com/graphhopper/routing/ev/HovAccess.java @@ -1,5 +1,9 @@ package com.graphhopper.routing.ev; +/** + * High-occupancy vehicle (carpool, diamond, transit, T2, or T3). + * See also here. + */ public class HovAccess { public final static String KEY = "hov_access"; diff --git a/core/src/main/java/com/graphhopper/routing/ev/IntEncodedValueImpl.java b/core/src/main/java/com/graphhopper/routing/ev/IntEncodedValueImpl.java index 71c14628985..334a1794b75 100644 --- a/core/src/main/java/com/graphhopper/routing/ev/IntEncodedValueImpl.java +++ b/core/src/main/java/com/graphhopper/routing/ev/IntEncodedValueImpl.java @@ -189,6 +189,9 @@ final void uncheckedSet(boolean reverse, int edgeId, EdgeIntAccess edgeIntAccess @Override public final int getInt(boolean reverse, int edgeId, EdgeIntAccess edgeIntAccess) { + assert fwdShift >= 0 : "incorrect shift " + fwdShift + " for " + getName(); + assert bits > 0 : "incorrect bits " + bits + " for " + getName(); + int flags; // if we do not store both directions ignore reverse == true for convenient reading if (storeTwoDirections && reverse) { diff --git a/core/src/main/java/com/graphhopper/routing/ev/MaxWeight.java b/core/src/main/java/com/graphhopper/routing/ev/MaxWeight.java index 5f0cca0a205..cf5794662f6 100644 --- a/core/src/main/java/com/graphhopper/routing/ev/MaxWeight.java +++ b/core/src/main/java/com/graphhopper/routing/ev/MaxWeight.java @@ -28,11 +28,11 @@ public class MaxWeight { public static final String KEY = "max_weight"; /** - * Currently enables to store 0.1 to max=0.1*2⁸ tons and infinity. If a value is between the maximum and infinity + * Currently enables to store 0.1 to max=0.1*2⁹ tons and infinity. If a value is between the maximum and infinity * it is assumed to use the maximum value. To save bits it might make more sense to store only a few values like * it was done with the MappedDecimalEncodedValue still handling (or rounding) of unknown values is unclear. */ public static DecimalEncodedValue create() { - return new DecimalEncodedValueImpl(KEY, 8, 0, 0.1, false, false, true); + return new DecimalEncodedValueImpl(KEY, 9, 0, 0.1, false, false, true); } } diff --git a/core/src/main/java/com/graphhopper/routing/ev/RoadClass.java b/core/src/main/java/com/graphhopper/routing/ev/RoadClass.java index 806b6dbccf0..7529f99d323 100644 --- a/core/src/main/java/com/graphhopper/routing/ev/RoadClass.java +++ b/core/src/main/java/com/graphhopper/routing/ev/RoadClass.java @@ -25,7 +25,8 @@ */ public enum RoadClass { OTHER, MOTORWAY, TRUNK, PRIMARY, SECONDARY, TERTIARY, RESIDENTIAL, UNCLASSIFIED, - SERVICE, ROAD, TRACK, BRIDLEWAY, STEPS, CYCLEWAY, PATH, LIVING_STREET, FOOTWAY, PEDESTRIAN, PLATFORM, CORRIDOR; + SERVICE, ROAD, TRACK, BRIDLEWAY, STEPS, CYCLEWAY, PATH, LIVING_STREET, FOOTWAY, + PEDESTRIAN, PLATFORM, CORRIDOR, CONSTRUCTION; public static final String KEY = "road_class"; diff --git a/core/src/main/java/com/graphhopper/routing/querygraph/QueryGraph.java b/core/src/main/java/com/graphhopper/routing/querygraph/QueryGraph.java index dc768586d18..cb6488d6bdd 100644 --- a/core/src/main/java/com/graphhopper/routing/querygraph/QueryGraph.java +++ b/core/src/main/java/com/graphhopper/routing/querygraph/QueryGraph.java @@ -280,7 +280,7 @@ public TurnCostStorage getTurnCostStorage() { @Override public Weighting wrapWeighting(Weighting weighting) { - return new QueryGraphWeighting(weighting, baseGraph.getNodes(), baseGraph.getEdges(), queryOverlay.getClosestEdges()); + return new QueryGraphWeighting(baseGraph, weighting, queryOverlay.getClosestEdges()); } @Override diff --git a/core/src/main/java/com/graphhopper/routing/querygraph/QueryOverlayBuilder.java b/core/src/main/java/com/graphhopper/routing/querygraph/QueryOverlayBuilder.java index 79d9a53cb61..18af896d9d4 100644 --- a/core/src/main/java/com/graphhopper/routing/querygraph/QueryOverlayBuilder.java +++ b/core/src/main/java/com/graphhopper/routing/querygraph/QueryOverlayBuilder.java @@ -28,10 +28,7 @@ import com.graphhopper.util.shapes.GHPoint; import com.graphhopper.util.shapes.GHPoint3D; -import java.util.ArrayList; -import java.util.Collections; -import java.util.Comparator; -import java.util.List; +import java.util.*; import static com.graphhopper.util.DistancePlaneProjection.DIST_PLANE; @@ -233,7 +230,7 @@ private void createEdges(int origEdgeKey, int origRevEdgeKey, boolean reverse = closestEdge.get(EdgeIteratorState.REVERSE_STATE); // edges between base and snapped point - List keyValues = closestEdge.getKeyValues(); + Map keyValues = closestEdge.getKeyValues(); VirtualEdgeIteratorState baseEdge = new VirtualEdgeIteratorState(origEdgeKey, GHUtility.createEdgeKey(virtEdgeId, false), prevNodeId, nodeId, baseDistance, closestEdge.getFlags(), keyValues, basePoints, reverse); VirtualEdgeIteratorState baseReverseEdge = new VirtualEdgeIteratorState(origRevEdgeKey, GHUtility.createEdgeKey(virtEdgeId, true), diff --git a/core/src/main/java/com/graphhopper/routing/querygraph/VirtualEdgeIterator.java b/core/src/main/java/com/graphhopper/routing/querygraph/VirtualEdgeIterator.java index 9541130fdeb..641dfd90c6f 100644 --- a/core/src/main/java/com/graphhopper/routing/querygraph/VirtualEdgeIterator.java +++ b/core/src/main/java/com/graphhopper/routing/querygraph/VirtualEdgeIterator.java @@ -27,6 +27,7 @@ import com.graphhopper.util.PointList; import java.util.List; +import java.util.Map; /** * @author Peter Karich @@ -262,12 +263,12 @@ public String getName() { } @Override - public List getKeyValues() { + public Map getKeyValues() { return getCurrentEdge().getKeyValues(); } @Override - public EdgeIteratorState setKeyValues(List list) { + public EdgeIteratorState setKeyValues(Map list) { return getCurrentEdge().setKeyValues(list); } diff --git a/core/src/main/java/com/graphhopper/routing/querygraph/VirtualEdgeIteratorState.java b/core/src/main/java/com/graphhopper/routing/querygraph/VirtualEdgeIteratorState.java index 5ef6e8a09e9..e88a4b909d3 100644 --- a/core/src/main/java/com/graphhopper/routing/querygraph/VirtualEdgeIteratorState.java +++ b/core/src/main/java/com/graphhopper/routing/querygraph/VirtualEdgeIteratorState.java @@ -25,7 +25,9 @@ import com.graphhopper.util.GHUtility; import com.graphhopper.util.PointList; -import java.util.List; +import java.util.Map; + +import static com.graphhopper.util.Parameters.Details.STREET_NAME; /** * Creates an edge state decoupled from a graph where nodes, pointList, etc are kept in memory. @@ -42,14 +44,14 @@ public class VirtualEdgeIteratorState implements EdgeIteratorState { private double distance; private IntsRef edgeFlags; private EdgeIntAccess edgeIntAccess; - private List keyValues; + private Map keyValues; // true if edge should be avoided as start/stop private boolean unfavored; private EdgeIteratorState reverseEdge; private final boolean reverse; public VirtualEdgeIteratorState(int originalEdgeKey, int edgeKey, int baseNode, int adjNode, double distance, - IntsRef edgeFlags, List keyValues, PointList pointList, boolean reverse) { + IntsRef edgeFlags, Map keyValues, PointList pointList, boolean reverse) { this.originalEdgeKey = originalEdgeKey; this.edgeKey = edgeKey; this.baseNode = baseNode; @@ -313,27 +315,28 @@ public EdgeIteratorState set(StringEncodedValue property, String fwd, String bwd @Override public String getName() { - String name = (String) getValue(KVStorage.KeyValue.STREET_NAME); + String name = (String) getValue(STREET_NAME); // preserve backward compatibility (returns empty string if name tag missing) return name == null ? "" : name; } @Override - public EdgeIteratorState setKeyValues(List list) { + public EdgeIteratorState setKeyValues(Map list) { this.keyValues = list; return this; } @Override - public List getKeyValues() { + public Map getKeyValues() { return keyValues; } @Override public Object getValue(String key) { - for (KVStorage.KeyValue keyValue : keyValues) { - if (keyValue.key.equals(key) && (!reverse && keyValue.fwd || reverse && keyValue.bwd)) - return keyValue.value; + KVStorage.KValue value = keyValues.get(key); + if (value != null) { + if (!reverse && value.getFwd() != null) return value.getFwd(); + if (reverse && value.getBwd() != null) return value.getBwd(); } return null; } 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 e859eb49ab8..464eb0e40a9 100644 --- a/core/src/main/java/com/graphhopper/routing/util/EncodingManager.java +++ b/core/src/main/java/com/graphhopper/routing/util/EncodingManager.java @@ -44,12 +44,12 @@ public class EncodingManager implements EncodedValueLookup { private final LinkedHashMap encodedValueMap; private final LinkedHashMap turnEncodedValueMap; - private int intsForFlags; + private int bytesForFlags; private int intsForTurnCostFlags; public static void putEncodingManagerIntoProperties(EncodingManager encodingManager, StorableProperties properties) { properties.put("graph.em.version", Constants.VERSION_EM); - properties.put("graph.em.ints_for_flags", encodingManager.intsForFlags); + properties.put("graph.em.bytes_for_flags", encodingManager.bytesForFlags); properties.put("graph.em.ints_for_turn_cost_flags", encodingManager.intsForTurnCostFlags); properties.put("graph.encoded_values", encodingManager.toEncodedValuesAsString()); properties.put("graph.turn_encoded_values", encodingManager.toTurnEncodedValuesAsString()); @@ -82,9 +82,8 @@ public static EncodingManager fromProperties(StorableProperties properties) { throw new IllegalStateException("Duplicate turn encoded value name: " + encodedValue.getName() + " in: graph.turn_encoded_values=" + turnEncodedValueStr); }); - return new EncodingManager(encodedValues, turnEncodedValues, - getIntegerProperty(properties, "graph.em.ints_for_flags"), - getIntegerProperty(properties, "graph.em.ints_for_turn_cost_flags") + return new EncodingManager(getIntegerProperty(properties, "graph.em.bytes_for_flags"), getIntegerProperty(properties, "graph.em.ints_for_turn_cost_flags"), encodedValues, + turnEncodedValues ); } @@ -110,17 +109,17 @@ public static Builder start() { return new Builder(); } - public EncodingManager(LinkedHashMap encodedValueMap, - LinkedHashMap turnEncodedValueMap, - int intsForFlags, int intsForTurnCostFlags) { + public EncodingManager(int bytesForFlags, int intsForTurnCostFlags, + LinkedHashMap encodedValueMap, + LinkedHashMap turnEncodedValueMap) { this.encodedValueMap = encodedValueMap; this.turnEncodedValueMap = turnEncodedValueMap; - this.intsForFlags = intsForFlags; + this.bytesForFlags = bytesForFlags; this.intsForTurnCostFlags = intsForTurnCostFlags; } private EncodingManager() { - this(new LinkedHashMap<>(), new LinkedHashMap<>(), 0, 0); + this(0, 0, new LinkedHashMap<>(), new LinkedHashMap<>()); } public static class Builder { @@ -157,17 +156,16 @@ private void checkNotBuiltAlready() { public EncodingManager build() { checkNotBuiltAlready(); - em.intsForFlags = edgeConfig.getRequiredInts(); + em.bytesForFlags = edgeConfig.getRequiredBytes(); em.intsForTurnCostFlags = turnCostConfig.getRequiredInts(); EncodingManager result = em; em = null; return result; } - } - public int getIntsForFlags() { - return intsForFlags; + public int getBytesForFlags() { + return bytesForFlags; } public boolean hasEncodedValue(String key) { @@ -205,7 +203,7 @@ public String toString() { // TODO hide IntsRef even more in a later version: https://gist.github.com/karussell/f4c2b2b1191be978d7ee9ec8dd2cd48f public IntsRef createEdgeFlags() { - return new IntsRef(getIntsForFlags()); + return new IntsRef((int) Math.ceil((double) getBytesForFlags() / 4)); } public IntsRef createRelationFlags() { diff --git a/core/src/main/java/com/graphhopper/routing/util/MaxSpeedCalculator.java b/core/src/main/java/com/graphhopper/routing/util/MaxSpeedCalculator.java index 91fca9bf9f9..c4342807a03 100644 --- a/core/src/main/java/com/graphhopper/routing/util/MaxSpeedCalculator.java +++ b/core/src/main/java/com/graphhopper/routing/util/MaxSpeedCalculator.java @@ -120,8 +120,8 @@ public void createDataAccessForParser(Directory directory) { EncodedValue.InitializerConfig config = new EncodedValue.InitializerConfig(); ruralMaxSpeedEnc.init(config); urbanMaxSpeedEnc.init(config); - if (config.getRequiredBits() > 16) - throw new IllegalStateException("bits are not sufficient " + config.getRequiredBits()); + if (config.getRequiredBytes() > 2) + throw new IllegalStateException("bytes are not sufficient " + config.getRequiredBytes()); parser.init(ruralMaxSpeedEnc, urbanMaxSpeedEnc, internalMaxSpeedStorage); } diff --git a/core/src/main/java/com/graphhopper/routing/util/parsers/OSMMaxWeightParser.java b/core/src/main/java/com/graphhopper/routing/util/parsers/OSMMaxWeightParser.java index 43b268cb764..bfd3548cbc2 100644 --- a/core/src/main/java/com/graphhopper/routing/util/parsers/OSMMaxWeightParser.java +++ b/core/src/main/java/com/graphhopper/routing/util/parsers/OSMMaxWeightParser.java @@ -31,7 +31,7 @@ public class OSMMaxWeightParser implements TagParser { // do not include OSM tag "height" here as it has completely different meaning (height of peak) - private static final List MAX_WEIGHT_TAGS = Arrays.asList("maxweight", "maxgcweight"/*abandoned*/, "maxweightrating:hgv"); + private static final List MAX_WEIGHT_TAGS = Arrays.asList("maxweight", "maxweightrating", "maxweightrating:hgv", "maxgcweight"/*abandoned*/); private static final List HGV_RESTRICTIONS = OSMRoadAccessParser.toOSMRestrictions(TransportationMode.HGV).stream() .map(e -> e + ":conditional").collect(Collectors.toList()); private final DecimalEncodedValue weightEncoder; diff --git a/core/src/main/java/com/graphhopper/routing/util/parsers/RestrictionSetter.java b/core/src/main/java/com/graphhopper/routing/util/parsers/RestrictionSetter.java index 17fffd77e99..4ce4fa5084d 100644 --- a/core/src/main/java/com/graphhopper/routing/util/parsers/RestrictionSetter.java +++ b/core/src/main/java/com/graphhopper/routing/util/parsers/RestrictionSetter.java @@ -31,9 +31,8 @@ import com.graphhopper.util.EdgeExplorer; import com.graphhopper.util.EdgeIterator; import com.graphhopper.util.EdgeIteratorState; -import com.graphhopper.util.FetchMode; -import java.util.List; +import java.util.*; import static com.graphhopper.reader.osm.RestrictionType.NO; import static com.graphhopper.reader.osm.RestrictionType.ONLY; @@ -72,12 +71,7 @@ private void addArtificialEdges(List> re int viaEdge = p.first.getViaEdges().get(0); int artificialEdge = artificialEdgesByEdges.getOrDefault(viaEdge, -1); if (artificialEdge < 0) { - EdgeIteratorState viaEdgeState = baseGraph.getEdgeIteratorState(p.first.getViaEdges().get(0), Integer.MIN_VALUE); - EdgeIteratorState artificialEdgeState = baseGraph.edge(viaEdgeState.getBaseNode(), viaEdgeState.getAdjNode()) - .setFlags(viaEdgeState.getFlags()) - .setWayGeometry(viaEdgeState.fetchWayGeometry(FetchMode.PILLAR_ONLY)) - .setDistance(viaEdgeState.getDistance()) - .setKeyValues(viaEdgeState.getKeyValues()); + EdgeIteratorState artificialEdgeState = baseGraph.copyEdge(viaEdge, true); artificialEdge = artificialEdgeState.getEdge(); artificialEdgesByEdges.put(viaEdge, artificialEdge); } diff --git a/core/src/main/java/com/graphhopper/routing/weighting/QueryGraphWeighting.java b/core/src/main/java/com/graphhopper/routing/weighting/QueryGraphWeighting.java index e8db6064533..44a53697321 100644 --- a/core/src/main/java/com/graphhopper/routing/weighting/QueryGraphWeighting.java +++ b/core/src/main/java/com/graphhopper/routing/weighting/QueryGraphWeighting.java @@ -20,6 +20,8 @@ import com.carrotsearch.hppc.IntArrayList; import com.graphhopper.routing.querygraph.QueryGraph; +import com.graphhopper.storage.BaseGraph; +import com.graphhopper.util.EdgeExplorer; import com.graphhopper.util.EdgeIterator; import com.graphhopper.util.EdgeIteratorState; @@ -29,15 +31,17 @@ * edges will not be calculated correctly. */ public class QueryGraphWeighting implements Weighting { + private final BaseGraph graph; private final Weighting weighting; private final int firstVirtualNodeId; private final int firstVirtualEdgeId; private final IntArrayList closestEdges; - public QueryGraphWeighting(Weighting weighting, int firstVirtualNodeId, int firstVirtualEdgeId, IntArrayList closestEdges) { + public QueryGraphWeighting(BaseGraph graph, Weighting weighting, IntArrayList closestEdges) { + this.graph = graph; this.weighting = weighting; - this.firstVirtualNodeId = firstVirtualNodeId; - this.firstVirtualEdgeId = firstVirtualEdgeId; + this.firstVirtualNodeId = graph.getNodes(); + this.firstVirtualEdgeId = graph.getEdges(); this.closestEdges = closestEdges; } @@ -69,13 +73,36 @@ public double calcTurnWeight(int inEdge, int viaNode, int outEdge) { } // to calculate the actual turn costs or detect u-turns we need to look at the original edge of each virtual // edge, see #1593 - if (isVirtualEdge(inEdge)) { - inEdge = getOriginalEdge(inEdge); + if (isVirtualEdge(inEdge) && isVirtualEdge(outEdge)) { + var minTurnWeight = new Object() { + double value = Double.POSITIVE_INFINITY; + }; + EdgeExplorer innerExplorer = graph.createEdgeExplorer(); + graph.forEdgeAndCopiesOfEdge(graph.createEdgeExplorer(), viaNode, getOriginalEdge(inEdge), p -> { + graph.forEdgeAndCopiesOfEdge(innerExplorer, viaNode, getOriginalEdge(outEdge), q -> { + minTurnWeight.value = Math.min(minTurnWeight.value, weighting.calcTurnWeight(p, viaNode, q)); + }); + }); + return minTurnWeight.value; + } else if (isVirtualEdge(inEdge)) { + var minTurnWeight = new Object() { + double value = Double.POSITIVE_INFINITY; + }; + graph.forEdgeAndCopiesOfEdge(graph.createEdgeExplorer(), viaNode, getOriginalEdge(inEdge), e -> { + minTurnWeight.value = Math.min(minTurnWeight.value, weighting.calcTurnWeight(e, viaNode, outEdge)); + }); + return minTurnWeight.value; + } else if (isVirtualEdge(outEdge)) { + var minTurnWeight = new Object() { + double value = Double.POSITIVE_INFINITY; + }; + graph.forEdgeAndCopiesOfEdge(graph.createEdgeExplorer(), viaNode, getOriginalEdge(outEdge), e -> { + minTurnWeight.value = Math.min(minTurnWeight.value, weighting.calcTurnWeight(inEdge, viaNode, e)); + }); + return minTurnWeight.value; + } else { + return weighting.calcTurnWeight(inEdge, viaNode, outEdge); } - if (isVirtualEdge(outEdge)) { - outEdge = getOriginalEdge(outEdge); - } - return weighting.calcTurnWeight(inEdge, viaNode, outEdge); } private boolean isUTurn(int inEdge, int outEdge) { diff --git a/core/src/main/java/com/graphhopper/search/KVStorage.java b/core/src/main/java/com/graphhopper/search/KVStorage.java index f9859e0fa20..2a4bba637d2 100644 --- a/core/src/main/java/com/graphhopper/search/KVStorage.java +++ b/core/src/main/java/com/graphhopper/search/KVStorage.java @@ -81,7 +81,7 @@ public class KVStorage { private final BitUtil bitUtil = BitUtil.LITTLE; private long bytePointer = START_POINTER; private long lastEntryPointer = -1; - private List lastEntries; + private Map lastEntries; /** * Specify a larger cacheSize to reduce disk usage. Note that this increases the memory usage of this object. @@ -109,7 +109,8 @@ public KVStorage create(long initBytes) { public boolean loadExisting() { if (vals.loadExisting()) { - if (!keys.loadExisting()) throw new IllegalStateException("Loaded values but cannot load keys"); + if (!keys.loadExisting()) + throw new IllegalStateException("Loaded values but cannot load keys"); bytePointer = bitUtil.toLong(vals.getHeader(0), vals.getHeader(4)); GHUtility.checkDAVersion(vals.getName(), Constants.VERSION_KV_STORAGE, vals.getHeader(8)); GHUtility.checkDAVersion(keys.getName(), Constants.VERSION_KV_STORAGE, keys.getHeader(0)); @@ -144,57 +145,67 @@ Collection getKeys() { return indexToKey; } - private long setKVList(long currentPointer, final List entries) { + private long setKVList(long currentPointer, final Map entries) { if (currentPointer == EMPTY_POINTER) return currentPointer; currentPointer += 1; // skip stored count - for (KeyValue entry : entries) { - String key = entry.key; - if (key == null) throw new IllegalArgumentException("key cannot be null"); - Object value = entry.value; - if (value == null) throw new IllegalArgumentException("value for key " + key + " cannot be null"); - if (!entry.fwd && !entry.bwd) - throw new IllegalArgumentException("Do not add KeyValue pair where fwd and bwd is false"); - Integer keyIndex = keyToIndex.get(key); - Class clazz; - if (keyIndex == null) { - keyIndex = keyToIndex.size(); - if (keyIndex >= MAX_UNIQUE_KEYS) - throw new IllegalArgumentException("Cannot store more than " + MAX_UNIQUE_KEYS + " unique keys"); - keyToIndex.put(key, keyIndex); - indexToKey.add(key); - indexToClass.add(clazz = value.getClass()); + for (Map.Entry entry : entries.entrySet()) { + if (entry.getValue().fwdBwdEqual) { + currentPointer = add(currentPointer, entry.getKey(), entry.getValue().fwdValue, true, true); } else { - clazz = indexToClass.get(keyIndex); - if (clazz != value.getClass()) - throw new IllegalArgumentException("Class of value for key " + key + " must be " + clazz.getSimpleName() + " but was " + value.getClass().getSimpleName()); + // potentially add two internal values + if (entry.getValue().fwdValue != null) + currentPointer = add(currentPointer, entry.getKey(), entry.getValue().fwdValue, true, false); + if (entry.getValue().bwdValue != null) + currentPointer = add(currentPointer, entry.getKey(), entry.getValue().bwdValue, false, true); } - boolean hasDynLength = hasDynLength(clazz); - if (hasDynLength) { - // optimization for empty string or empty byte array - if (clazz.equals(String.class) && ((String) value).isEmpty() - || clazz.equals(byte[].class) && ((byte[]) value).length == 0) { - vals.ensureCapacity(currentPointer + 3); - vals.setShort(currentPointer, keyIndex.shortValue()); - // ensure that also in case of MMap value is set to 0 - vals.setByte(currentPointer + 2, (byte) 0); - currentPointer += 3; - continue; - } - } + } + return currentPointer; + } - final byte[] valueBytes = getBytesForValue(clazz, value); - vals.ensureCapacity(currentPointer + 2 + 1 + valueBytes.length); - vals.setShort(currentPointer, (short) (keyIndex << 2 | (entry.fwd ? 2 : 0) | (entry.bwd ? 1 : 0))); - currentPointer += 2; - if (hasDynLength) { - vals.setByte(currentPointer, (byte) valueBytes.length); - currentPointer++; + long add(long currentPointer, String key, Object value, boolean fwd, boolean bwd) { + if (key == null) throw new IllegalArgumentException("key cannot be null"); + if (value == null) + throw new IllegalArgumentException("value for key " + key + " cannot be null"); + + Integer keyIndex = keyToIndex.get(key); + Class clazz; + if (keyIndex == null) { + keyIndex = keyToIndex.size(); + if (keyIndex >= MAX_UNIQUE_KEYS) + throw new IllegalArgumentException("Cannot store more than " + MAX_UNIQUE_KEYS + " unique keys"); + keyToIndex.put(key, keyIndex); + indexToKey.add(key); + indexToClass.add(clazz = value.getClass()); + } else { + clazz = indexToClass.get(keyIndex); + if (clazz != value.getClass()) + throw new IllegalArgumentException("Class of value for key " + key + " must be " + clazz.getSimpleName() + " but was " + value.getClass().getSimpleName()); + } + + boolean hasDynLength = hasDynLength(clazz); + if (hasDynLength) { + // optimization for empty string or empty byte array + if (clazz.equals(String.class) && ((String) value).isEmpty() + || clazz.equals(byte[].class) && ((byte[]) value).length == 0) { + vals.ensureCapacity(currentPointer + 3); + vals.setShort(currentPointer, keyIndex.shortValue()); + // ensure that also in case of MMap value is set to 0 + vals.setByte(currentPointer + 2, (byte) 0); + return currentPointer + 3; } - vals.setBytes(currentPointer, valueBytes, valueBytes.length); - currentPointer += valueBytes.length; } - return currentPointer; + + final byte[] valueBytes = getBytesForValue(clazz, value); + vals.ensureCapacity(currentPointer + 2 + 1 + valueBytes.length); + vals.setShort(currentPointer, (short) (keyIndex << 2 | (fwd ? 2 : 0) | (bwd ? 1 : 0))); + currentPointer += 2; + if (hasDynLength) { + vals.setByte(currentPointer, (byte) valueBytes.length); + currentPointer++; + } + vals.setBytes(currentPointer, valueBytes, valueBytes.length); + return currentPointer + valueBytes.length; } /** @@ -205,7 +216,7 @@ private long setKVList(long currentPointer, final List entries) { * * @return entryPointer with which you can later fetch the entryMap via the get or getAll method */ - public long add(final List entries) { + public long add(final Map entries) { if (entries == null) throw new IllegalArgumentException("specified List must not be null"); if (entries.isEmpty()) return EMPTY_POINTER; else if (entries.size() > 200) @@ -213,47 +224,48 @@ else if (entries.size() > 200) // This is a very important "compression" mechanism because one OSM way is split into multiple edges and so we // can often re-use the serialized key-value pairs of the previous edge. - if (isEquals(entries, lastEntries)) return lastEntryPointer; + if (entries.equals(lastEntries)) return lastEntryPointer; + + int entryCount = 0; + for (Map.Entry kv : entries.entrySet()) { + + if (kv.getValue().fwdBwdEqual) { + entryCount++; + } else { + // note, if fwd and bwd are different we create two internal entries! + if (kv.getValue().getFwd() != null) entryCount++; + if (kv.getValue().getBwd() != null) entryCount++; + } - // If the Class of a value is unknown it should already fail here, before we modify internal data. (see #2597#discussion_r896469840) - for (KeyValue kv : entries) - if (keyToIndex.get(kv.key) != null) - getBytesForValue(indexToClass.get(keyToIndex.get(kv.key)), kv.value); + // If the Class of a value is unknown it should already fail here, before we modify internal data. (see #2597#discussion_r896469840) + if (keyToIndex.get(kv.getKey()) != null) { + if (kv.getValue().fwdValue != null) + getBytesForValue(indexToClass.get(keyToIndex.get(kv.getKey())), kv.getValue().fwdValue); + if (kv.getValue().bwdValue != null) + getBytesForValue(indexToClass.get(keyToIndex.get(kv.getKey())), kv.getValue().bwdValue); + } + } lastEntries = entries; lastEntryPointer = bytePointer; vals.ensureCapacity(bytePointer + 1); - vals.setByte(bytePointer, (byte) entries.size()); + vals.setByte(bytePointer, (byte) entryCount); bytePointer = setKVList(bytePointer, entries); if (bytePointer < 0) throw new IllegalStateException("Negative bytePointer in KVStorage"); return lastEntryPointer; } - // compared to entries.equals(lastEntries) this method avoids a NPE if a value is null and throws an IAE instead - private boolean isEquals(List entries, List lastEntries) { - if (lastEntries != null && entries.size() == lastEntries.size()) { - for (int i = 0; i < entries.size(); i++) { - KeyValue kv = entries.get(i); - if (kv.value == null) - throw new IllegalArgumentException("value for key " + kv.key + " cannot be null"); - if (!kv.equals(lastEntries.get(i))) return false; - } - return true; - } - return false; - } - - public List getAll(final long entryPointer) { + public Map getAll(final long entryPointer) { if (entryPointer < 0) throw new IllegalStateException("Pointer to access KVStorage cannot be negative:" + entryPointer); - if (entryPointer == EMPTY_POINTER) return Collections.emptyList(); + if (entryPointer == EMPTY_POINTER) return Collections.emptyMap(); int keyCount = vals.getByte(entryPointer) & 0xFF; - if (keyCount == 0) return Collections.emptyList(); + if (keyCount == 0) return Collections.emptyMap(); - List list = new ArrayList<>(keyCount); + Map map = new LinkedHashMap<>(); long tmpPointer = entryPointer + 1; AtomicInteger sizeOfObject = new AtomicInteger(); for (int i = 0; i < keyCount; i++) { @@ -266,10 +278,16 @@ public List getAll(final long entryPointer) { Object object = deserializeObj(sizeOfObject, tmpPointer, indexToClass.get(currentKeyIndex)); tmpPointer += sizeOfObject.get(); String key = indexToKey.get(currentKeyIndex); - list.add(new KeyValue(key, object, fwd, bwd)); + KValue oldValue = map.get(key); + if (oldValue != null) + map.put(key, new KValue(fwd ? object : oldValue.fwdValue, bwd ? object : oldValue.bwdValue)); + else if (fwd && bwd) + map.put(key, new KValue(object)); + else + map.put(key, new KValue(fwd ? object : null, bwd ? object : null)); } - return list; + return map; } /** @@ -411,8 +429,9 @@ public Object get(final long entryPointer, String key, boolean reverse) { assert currentKeyIndex < indexToKey.size() : "invalid key index " + currentKeyIndex + ">=" + indexToKey.size() + ", entryPointer=" + entryPointer + ", max=" + bytePointer; tmpPointer += 2; - if ((!reverse && fwd || reverse && bwd) && currentKeyIndex == keyIndex) + if ((!reverse && fwd || reverse && bwd) && currentKeyIndex == keyIndex) { return deserializeObj(null, tmpPointer, indexToClass.get(keyIndex)); + } // skip to next entry of same edge via skipping the real value Class clazz = indexToClass.get(currentKeyIndex); @@ -473,63 +492,58 @@ public long getCapacity() { return vals.getCapacity() + keys.getCapacity(); } - public static class KeyValue { - public static final String STREET_NAME = "street_name"; - public static final String STREET_REF = "street_ref"; - public static final String STREET_DESTINATION = "street_destination"; - public static final String STREET_DESTINATION_REF = "street_destination_ref"; - public static final String MOTORWAY_JUNCTION = "motorway_junction"; - - public String key; - public Object value; - public boolean fwd, bwd; - - public KeyValue(String key, Object value) { - this.key = key; - this.value = value; - this.fwd = true; - this.bwd = true; - } + public static class KValue { + private final Object fwdValue; + private final Object bwdValue; + final boolean fwdBwdEqual; - public Object getValue() { - return value; + public KValue(Object obj) { + if (obj == null) + throw new IllegalArgumentException("Object cannot be null if forward and backward is both true"); + fwdValue = bwdValue = obj; + fwdBwdEqual = true; } - public String getKey() { - return key; + public KValue(Object fwd, Object bwd) { + fwdValue = fwd; + bwdValue = bwd; + if (fwdValue != null && bwdValue != null && fwd.getClass() != bwd.getClass()) + throw new IllegalArgumentException("If both values are not null they have to be they same class but was: " + + fwdValue.getClass() + " vs " + bwdValue.getClass()); + if (fwdValue == null && bwdValue == null) + throw new IllegalArgumentException("If both values are null just do not store them"); + fwdBwdEqual = false; } - public KeyValue(String key, Object value, boolean fwd, boolean bwd) { - this.key = key; - this.value = value; - this.fwd = fwd; - this.bwd = bwd; + public Object getFwd() { + return fwdValue; } - public static List createKV(String key, Object value) { - return Collections.singletonList(new KeyValue(key, value)); + public Object getBwd() { + return bwdValue; } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; - KeyValue keyValue = (KeyValue) o; - return key.equals(keyValue.key) - && fwd == keyValue.fwd - && bwd == keyValue.bwd - && (value instanceof byte[] && keyValue.value instanceof byte[] && - Arrays.equals((byte[]) value, (byte[]) keyValue.value) || value.equals(keyValue.value)); + KValue value = (KValue) o; + // due to check in constructor we can assume that fwdValue and bwdValue are of same type. + // I.e. if one is a byte array the other is too. + if (fwdValue instanceof byte[] || bwdValue instanceof byte[]) + return fwdBwdEqual == value.fwdBwdEqual && (Arrays.equals((byte[]) fwdValue, (byte[]) value.fwdValue) || Arrays.equals((byte[]) bwdValue, (byte[]) value.bwdValue)); + + return fwdBwdEqual == value.fwdBwdEqual && Objects.equals(fwdValue, value.fwdValue) && Objects.equals(bwdValue, value.bwdValue); } @Override public int hashCode() { - return Objects.hash(key, value, fwd, bwd); + return Objects.hash(fwdValue, bwdValue, fwdBwdEqual); } @Override public String toString() { - return key + '=' + value + " (" + fwd + "|" + bwd + ")"; + return fwdBwdEqual ? fwdValue.toString() : fwdValue + " | " + bwdValue; } } diff --git a/core/src/main/java/com/graphhopper/storage/BaseGraph.java b/core/src/main/java/com/graphhopper/storage/BaseGraph.java index aae60e2783e..e915fa87320 100644 --- a/core/src/main/java/com/graphhopper/storage/BaseGraph.java +++ b/core/src/main/java/com/graphhopper/storage/BaseGraph.java @@ -27,9 +27,12 @@ import com.graphhopper.util.shapes.BBox; import java.io.Closeable; -import java.util.List; +import java.util.Map; +import java.util.function.Consumer; +import java.util.function.IntConsumer; import static com.graphhopper.util.Helper.nf; +import static com.graphhopper.util.Parameters.Details.STREET_NAME; /** * The base graph handles nodes and edges file format. It can be used with different Directory @@ -55,17 +58,24 @@ public class BaseGraph implements Graph, Closeable { private final Directory dir; private final int segmentSize; private boolean initialized = false; + private long minGeoRef; private long maxGeoRef; + private final int eleBytesPerCoord; - public BaseGraph(Directory dir, int intsForFlags, boolean withElevation, boolean withTurnCosts, int segmentSize) { + public BaseGraph(Directory dir, boolean withElevation, boolean withTurnCosts, int segmentSize, int bytesForFlags) { this.dir = dir; this.bitUtil = BitUtil.LITTLE; this.wayGeometry = dir.create("geometry", segmentSize); this.edgeKVStorage = new KVStorage(dir, true); - this.store = new BaseGraphNodesAndEdges(dir, intsForFlags, withElevation, withTurnCosts, segmentSize); + this.store = new BaseGraphNodesAndEdges(dir, withElevation, withTurnCosts, segmentSize, bytesForFlags); this.nodeAccess = new GHNodeAccess(store); this.segmentSize = segmentSize; - turnCostStorage = withTurnCosts ? new TurnCostStorage(this, dir.create("turn_costs", dir.getDefaultType("turn_costs", true), segmentSize)) : null; + this.turnCostStorage = withTurnCosts ? new TurnCostStorage(this, dir.create("turn_costs", dir.getDefaultType("turn_costs", true), segmentSize)) : null; + this.eleBytesPerCoord = (nodeAccess.getDimension() == 3 ? 3 : 0); + } + + BaseGraphNodesAndEdges getStore() { + return store; } private int getOtherNode(int nodeThis, long edgePointer) { @@ -105,16 +115,22 @@ void checkNotInitialized() { private void loadWayGeometryHeader() { int geometryVersion = wayGeometry.getHeader(0); GHUtility.checkDAVersion(wayGeometry.getName(), Constants.VERSION_GEOMETRY, geometryVersion); - maxGeoRef = bitUtil.toLong( + minGeoRef = bitUtil.toLong( wayGeometry.getHeader(4), wayGeometry.getHeader(8) ); + maxGeoRef = bitUtil.toLong( + wayGeometry.getHeader(12), + wayGeometry.getHeader(16) + ); } private void setWayGeometryHeader() { wayGeometry.setHeader(0, Constants.VERSION_GEOMETRY); - wayGeometry.setHeader(4, bitUtil.getIntLow(maxGeoRef)); - wayGeometry.setHeader(8, bitUtil.getIntHigh(maxGeoRef)); + wayGeometry.setHeader(4, bitUtil.getIntLow(minGeoRef)); + wayGeometry.setHeader(8, bitUtil.getIntHigh(minGeoRef)); + wayGeometry.setHeader(12, bitUtil.getIntLow(maxGeoRef)); + wayGeometry.setHeader(16, bitUtil.getIntHigh(maxGeoRef)); } private void setInitialized() { @@ -167,19 +183,16 @@ public BaseGraph create(long initSize) { turnCostStorage.create(initSize); } setInitialized(); - // 0 stands for no separate geoRef - maxGeoRef = 4; + // 0 stands for no separate geoRef, <0 stands for no separate geoRef but existing edge copies + minGeoRef = -1; + maxGeoRef = 1; return this; } - public int getIntsForFlags() { - return store.getIntsForFlags(); - } - public String toDetailsString() { return store.toDetailsString() + ", " + "name:(" + edgeKVStorage.getCapacity() / Helper.MB + "MB), " - + "geo:" + nf(maxGeoRef) + "(" + wayGeometry.getCapacity() / Helper.MB + "MB)"; + + "geo:" + nf(maxGeoRef) + "/" + nf(minGeoRef) + "(" + wayGeometry.getCapacity() / Helper.MB + "MB)"; } /** @@ -292,6 +305,74 @@ public EdgeIteratorState edge(int nodeA, int nodeB) { return edge; } + /** + * Creates a copy of a given edge with the same properties. + * + * @param reuseGeometry If true the copy uses the same pointer to the geometry, + * so changing the geometry would alter the geometry for both edges! + */ + public EdgeIteratorState copyEdge(int edge, boolean reuseGeometry) { + EdgeIteratorStateImpl edgeState = (EdgeIteratorStateImpl) getEdgeIteratorState(edge, Integer.MIN_VALUE); + EdgeIteratorStateImpl newEdge = (EdgeIteratorStateImpl) edge(edgeState.getBaseNode(), edgeState.getAdjNode()) + .setFlags(edgeState.getFlags()) + .setDistance(edgeState.getDistance()) + .setKeyValues(edgeState.getKeyValues()); + if (reuseGeometry) { + // We use the same geo ref for the copied edge. This saves memory because we are not duplicating + // the geometry, and it allows to identify the copies of a given edge. + long edgePointer = edgeState.edgePointer; + long geoRef = store.getGeoRef(edgePointer); + if (geoRef == 0) { + // No geometry for this edge, but we need to be able to identify the copied edges later, so + // we use a dedicated negative value for the geo ref. + geoRef = minGeoRef; + store.setGeoRef(edgePointer, geoRef); + minGeoRef--; + } + store.setGeoRef(newEdge.edgePointer, geoRef); + } else { + newEdge.setWayGeometry(edgeState.fetchWayGeometry(FetchMode.PILLAR_ONLY)); + } + return newEdge; + } + + /** + * Runs the given action on the given edge and all its copies that were created with 'reuseGeometry=true'. + */ + public void forEdgeAndCopiesOfEdge(EdgeExplorer explorer, EdgeIteratorState edge, Consumer consumer) { + final long geoRef = store.getGeoRef(((EdgeIteratorStateImpl) edge).edgePointer); + if (geoRef == 0) { + // 0 means there is no geometry (and no copy of this edge), but of course not all edges + // without geometry are copies of each other, so we need to return early + consumer.accept(edge); + return; + } + EdgeIterator iter = explorer.setBaseNode(edge.getBaseNode()); + while (iter.next()) { + long geoRefBefore = store.getGeoRef(((EdgeIteratorStateImpl) iter).edgePointer); + if (geoRefBefore == geoRef) + consumer.accept(iter); + if (store.getGeoRef(((EdgeIteratorStateImpl) iter).edgePointer) != geoRefBefore) + throw new IllegalStateException("The consumer must not change the geo ref"); + } + } + + public void forEdgeAndCopiesOfEdge(EdgeExplorer explorer, int node, int edge, IntConsumer consumer) { + final long geoRef = store.getGeoRef(store.toEdgePointer(edge)); + if (geoRef == 0) { + // 0 means there is no geometry (and no copy of this edge), but of course not all edges + // without geometry are copies of each other, so we need to return early + consumer.accept(edge); + return; + } + EdgeIterator iter = explorer.setBaseNode(node); + while (iter.next()) { + long geoRefBefore = store.getGeoRef(((EdgeIteratorStateImpl) iter).edgePointer); + if (geoRefBefore == geoRef) + consumer.accept(iter.getEdge()); + } + } + @Override public EdgeIteratorState getEdgeIteratorState(int edgeId, int adjNode) { EdgeIteratorStateImpl edge = new EdgeIteratorStateImpl(this); @@ -352,58 +433,51 @@ private void setWayGeometry_(PointList pillarNodes, long edgePointer, boolean re + "D for graph which is " + nodeAccess.getDimension() + "D"); long existingGeoRef = store.getGeoRef(edgePointer); + if (existingGeoRef < 0) + // users of this method might not be aware that after changing the geo ref it is no + // longer possible to find the copies corresponding to an edge, so we deny this + throw new IllegalStateException("This edge has already been copied so we can no longer change the geometry, pointer=" + edgePointer); int len = pillarNodes.size(); - int dim = nodeAccess.getDimension(); if (existingGeoRef > 0) { - final int count = wayGeometry.getInt(existingGeoRef * 4L); + final int count = getPillarCount(existingGeoRef); if (len <= count) { setWayGeometryAtGeoRef(pillarNodes, edgePointer, reverse, existingGeoRef); return; + } else { + throw new IllegalStateException("This edge already has a way geometry so it cannot be changed to a bigger geometry, pointer=" + edgePointer); } } - - long nextGeoRef = nextGeoRef(len * dim); + long nextGeoRef = nextGeoRef(3 + len * (8 + eleBytesPerCoord)); setWayGeometryAtGeoRef(pillarNodes, edgePointer, reverse, nextGeoRef); } else { store.setGeoRef(edgePointer, 0L); } } - public EdgeIntAccess createEdgeIntAccess() { - return new EdgeIntAccess() { - @Override - public int getInt(int edgeId, int index) { - long edgePointer = store.toEdgePointer(edgeId); - return store.getFlagInt(edgePointer, index); - } - - @Override - public void setInt(int edgeId, int index, int value) { - long edgePointer = store.toEdgePointer(edgeId); - store.setFlagInt(edgePointer, index, value); - } - }; + public EdgeIntAccess getEdgeAccess() { + return store; } private void setWayGeometryAtGeoRef(PointList pillarNodes, long edgePointer, boolean reverse, long geoRef) { - long geoRefPosition = geoRef * 4; byte[] wayGeometryBytes = createWayGeometryBytes(pillarNodes, reverse); - wayGeometry.ensureCapacity(geoRefPosition + wayGeometryBytes.length); - wayGeometry.setBytes(geoRefPosition, wayGeometryBytes, wayGeometryBytes.length); + wayGeometry.ensureCapacity(geoRef + wayGeometryBytes.length); + wayGeometry.setBytes(geoRef, wayGeometryBytes, wayGeometryBytes.length); store.setGeoRef(edgePointer, geoRef); } private byte[] createWayGeometryBytes(PointList pillarNodes, boolean reverse) { int len = pillarNodes.size(); - int dim = nodeAccess.getDimension(); - int totalLen = len * dim * 4 + 4; + int totalLen = 3 + len * (8 + eleBytesPerCoord); + if ((totalLen & 0xFF00_0000) != 0) + throw new IllegalArgumentException("too long way geometry " + totalLen + ", " + len); + byte[] bytes = new byte[totalLen]; - bitUtil.fromInt(bytes, len, 0); + bitUtil.fromUInt3(bytes, len, 0); if (reverse) pillarNodes.reverse(); - int tmpOffset = 4; + int tmpOffset = 3; boolean is3D = nodeAccess.is3D(); for (int i = 0; i < len; i++) { double lat = pillarNodes.getLat(i); @@ -413,13 +487,17 @@ private byte[] createWayGeometryBytes(PointList pillarNodes, boolean reverse) { tmpOffset += 4; if (is3D) { - bitUtil.fromInt(bytes, Helper.eleToInt(pillarNodes.getEle(i)), tmpOffset); - tmpOffset += 4; + bitUtil.fromUInt3(bytes, Helper.eleToUInt(pillarNodes.getEle(i)), tmpOffset); + tmpOffset += 3; } } return bytes; } + private int getPillarCount(long geoRef) { + return (wayGeometry.getByte(geoRef + 2) & 0xFF << 16) | wayGeometry.getShort(geoRef); + } + private PointList fetchWayGeometry_(long edgePointer, boolean reverse, FetchMode mode, int baseNode, int adjNode) { if (mode == FetchMode.TOWER_ONLY) { // no reverse handling required as adjNode and baseNode is already properly switched @@ -432,11 +510,9 @@ private PointList fetchWayGeometry_(long edgePointer, boolean reverse, FetchMode int count = 0; byte[] bytes = null; if (geoRef > 0) { - geoRef *= 4L; - count = wayGeometry.getInt(geoRef); - - geoRef += 4L; - bytes = new byte[count * nodeAccess.getDimension() * 4]; + count = getPillarCount(geoRef); + geoRef += 3L; + bytes = new byte[count * (8 + eleBytesPerCoord)]; wayGeometry.getBytes(geoRef, bytes, bytes.length); } else if (mode == FetchMode.PILLAR_ONLY) return PointList.EMPTY; @@ -455,8 +531,8 @@ private PointList fetchWayGeometry_(long edgePointer, boolean reverse, FetchMode double lon = Helper.intToDegree(bitUtil.toInt(bytes, index)); index += 4; if (nodeAccess.is3D()) { - pillarNodes.add(lat, lon, Helper.intToEle(bitUtil.toInt(bytes, index))); - index += 4; + pillarNodes.add(lat, lon, Helper.uIntToEle(bitUtil.toUInt3(bytes, index))); + index += 3; } else { pillarNodes.add(lat, lon); } @@ -488,9 +564,9 @@ static int getPointListLength(int pillarNodes, FetchMode mode) { throw new IllegalArgumentException("Mode isn't handled " + mode); } - private long nextGeoRef(int arrayLength) { + private long nextGeoRef(int bytes) { long tmp = maxGeoRef; - maxGeoRef += arrayLength + 1L; + maxGeoRef += bytes; return tmp; } @@ -507,7 +583,7 @@ public int getSegmentSize() { } public static class Builder { - private final int intsForFlags; + private final int bytesForFlags; private Directory directory = new RAMDirectory(); private boolean withElevation = false; private boolean withTurnCosts = false; @@ -515,12 +591,12 @@ public static class Builder { private int segmentSize = -1; public Builder(EncodingManager em) { - this(em.getIntsForFlags()); + this(em.getBytesForFlags()); withTurnCosts(em.needsTurnCostsSupport()); } - public Builder(int intsForFlags) { - this.intsForFlags = intsForFlags; + public Builder(int bytesForFlags) { + this.bytesForFlags = bytesForFlags; } // todo: maybe rename later, but for now this makes it easier to replace GraphBuilder @@ -552,7 +628,7 @@ public Builder setBytes(long bytes) { } public BaseGraph build() { - return new BaseGraph(directory, intsForFlags, withElevation, withTurnCosts, segmentSize); + return new BaseGraph(directory, withElevation, withTurnCosts, segmentSize, bytesForFlags); } public BaseGraph create() { @@ -671,7 +747,7 @@ static class EdgeIteratorStateImpl implements EdgeIteratorState { public EdgeIteratorStateImpl(BaseGraph baseGraph) { this.baseGraph = baseGraph; - edgeIntAccess = baseGraph.createEdgeIntAccess(); + edgeIntAccess = baseGraph.getEdgeAccess(); store = baseGraph.store; } @@ -743,7 +819,7 @@ public EdgeIteratorState setDistance(double dist) { @Override public IntsRef getFlags() { - IntsRef edgeFlags = new IntsRef(store.getIntsForFlags()); + IntsRef edgeFlags = store.createEdgeFlags(); store.readFlags(edgePointer, edgeFlags); return edgeFlags; } @@ -942,7 +1018,7 @@ public int getReverseEdgeKey() { } @Override - public EdgeIteratorState setKeyValues(List entries) { + public EdgeIteratorState setKeyValues(Map entries) { long pointer = baseGraph.edgeKVStorage.add(entries); if (pointer > MAX_UNSIGNED_INT) throw new IllegalStateException("Too many key value pairs are stored, currently limited to " + MAX_UNSIGNED_INT + " was " + pointer); @@ -951,7 +1027,7 @@ public EdgeIteratorState setKeyValues(List entries) { } @Override - public List getKeyValues() { + public Map getKeyValues() { long kvEntryRef = Integer.toUnsignedLong(store.getKeyValuesRef(edgePointer)); return baseGraph.edgeKVStorage.getAll(kvEntryRef); } @@ -964,7 +1040,7 @@ public Object getValue(String key) { @Override public String getName() { - String name = (String) getValue(KVStorage.KeyValue.STREET_NAME); + String name = (String) getValue(STREET_NAME); // preserve backward compatibility (returns empty string if name tag missing) 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 index 0e2e6c2cc2c..f2981dba3c1 100644 --- a/core/src/main/java/com/graphhopper/storage/BaseGraphNodesAndEdges.java +++ b/core/src/main/java/com/graphhopper/storage/BaseGraphNodesAndEdges.java @@ -18,6 +18,7 @@ package com.graphhopper.storage; +import com.graphhopper.routing.ev.EdgeIntAccess; import com.graphhopper.util.*; import com.graphhopper.util.shapes.BBox; @@ -30,7 +31,7 @@ * 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 { +class BaseGraphNodesAndEdges implements EdgeIntAccess { // 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. @@ -45,8 +46,8 @@ class BaseGraphNodesAndEdges { // edges private final DataAccess edges; - private final int E_NODEA, E_NODEB, E_LINKA, E_LINKB, E_FLAGS, E_DIST, E_GEO_1, E_GEO_2, E_KV; - private final int intsForFlags; + private final int E_NODEA, E_NODEB, E_LINKA, E_LINKB, E_DIST, E_KV, E_FLAGS, E_GEO; + private final int bytesForFlags; private int edgeEntryBytes; private int edgeCount; @@ -59,10 +60,10 @@ class BaseGraphNodesAndEdges { public final BBox bounds; private boolean frozen; - public BaseGraphNodesAndEdges(Directory dir, int intsForFlags, boolean withElevation, boolean withTurnCosts, int segmentSize) { + public BaseGraphNodesAndEdges(Directory dir, boolean withElevation, boolean withTurnCosts, int segmentSize, int bytesForFlags) { nodes = dir.create("nodes", dir.getDefaultType("nodes", true), segmentSize); - edges = dir.create("edges", dir.getDefaultType("edges", true), segmentSize); - this.intsForFlags = intsForFlags; + edges = dir.create("edges", dir.getDefaultType("edges", false), segmentSize); + this.bytesForFlags = bytesForFlags; this.withTurnCosts = withTurnCosts; this.withElevation = withElevation; bounds = BBox.createInverse(withElevation); @@ -80,12 +81,11 @@ public BaseGraphNodesAndEdges(Directory dir, int intsForFlags, boolean withEleva E_NODEB = 4; E_LINKA = 8; E_LINKB = 12; - E_FLAGS = 16; - E_DIST = E_FLAGS + intsForFlags * 4; - E_GEO_1 = E_DIST + 4; - E_GEO_2 = E_GEO_1 + 4; - E_KV = E_GEO_2 + 4; - edgeEntryBytes = E_KV + 4; + E_DIST = 16; + E_KV = 20; + E_FLAGS = 24; + E_GEO = E_FLAGS + bytesForFlags; + edgeEntryBytes = E_GEO + 5; } public void create(long initSize) { @@ -112,8 +112,8 @@ public boolean loadExisting() { throw new IllegalStateException("Configured dimension elevation=" + withElevation + " is not equal " + "to dimension of loaded graph elevation =" + hasElevation); if (withElevation) { - bounds.minEle = Helper.intToEle(nodes.getHeader(8 * 4)); - bounds.maxEle = Helper.intToEle(nodes.getHeader(9 * 4)); + bounds.minEle = Helper.uIntToEle(nodes.getHeader(8 * 4)); + bounds.maxEle = Helper.uIntToEle(nodes.getHeader(9 * 4)); } frozen = nodes.getHeader(10 * 4) == 1; @@ -134,8 +134,8 @@ public void flush() { nodes.setHeader(6 * 4, Helper.degreeToInt(bounds.maxLat)); nodes.setHeader(7 * 4, withElevation ? 1 : 0); if (withElevation) { - nodes.setHeader(8 * 4, Helper.eleToInt(bounds.minEle)); - nodes.setHeader(9 * 4, Helper.eleToInt(bounds.maxEle)); + nodes.setHeader(8 * 4, Helper.eleToUInt(bounds.minEle)); + nodes.setHeader(9 * 4, Helper.eleToUInt(bounds.maxEle)); } nodes.setHeader(10 * 4, frozen ? 1 : 0); @@ -160,8 +160,12 @@ public int getEdges() { return edgeCount; } - public int getIntsForFlags() { - return intsForFlags; + IntsRef createEdgeFlags() { + return new IntsRef((int) Math.ceil((double) getBytesForFlags() / 4)); + } + + public int getBytesForFlags() { + return bytesForFlags; } public boolean withElevation() { @@ -243,21 +247,59 @@ public long toEdgePointer(int edge) { public void readFlags(long edgePointer, IntsRef edgeFlags) { int size = edgeFlags.ints.length; for (int i = 0; i < size; ++i) - edgeFlags.ints[i] = getFlagInt(edgePointer, i); + edgeFlags.ints[i] = getFlagInt(edgePointer, i * 4); } public void writeFlags(long edgePointer, IntsRef edgeFlags) { int size = edgeFlags.ints.length; for (int i = 0; i < size; ++i) - setFlagInt(edgePointer, i, edgeFlags.ints[i]); + setFlagInt(edgePointer, i * 4, edgeFlags.ints[i]); + } + + private int getFlagInt(long edgePointer, int byteOffset) { + if (byteOffset >= bytesForFlags) + throw new IllegalArgumentException("too large byteOffset " + byteOffset + " vs " + bytesForFlags); + edgePointer += byteOffset; + if (byteOffset + 3 == bytesForFlags) { + return (edges.getShort(edgePointer + E_FLAGS) << 8) & 0x00FF_FFFF | edges.getByte(edgePointer + E_FLAGS + 2) & 0xFF; + } else if (byteOffset + 2 == bytesForFlags) { + return edges.getShort(edgePointer + E_FLAGS) & 0xFFFF; + } else if (byteOffset + 1 == bytesForFlags) { + return edges.getByte(edgePointer + E_FLAGS) & 0xFF; + } + return edges.getInt(edgePointer + E_FLAGS); + } + + private void setFlagInt(long edgePointer, int byteOffset, int value) { + if (byteOffset >= bytesForFlags) + throw new IllegalArgumentException("too large byteOffset " + byteOffset + " vs " + bytesForFlags); + edgePointer += byteOffset; + if (byteOffset + 3 == bytesForFlags) { + if ((value & 0xFF00_0000) != 0) + throw new IllegalArgumentException("value at byteOffset " + byteOffset + " must not have the highest byte set but was " + value); + edges.setShort(edgePointer + E_FLAGS, (short) (value >> 8)); + edges.setByte(edgePointer + E_FLAGS + 2, (byte) value); + } else if (byteOffset + 2 == bytesForFlags) { + if ((value & 0xFFFF_0000) != 0) + throw new IllegalArgumentException("value at byteOffset " + byteOffset + " must not have the 2 highest bytes set but was " + value); + edges.setShort(edgePointer + E_FLAGS, (short) value); + } else if (byteOffset + 1 == bytesForFlags) { + if ((value & 0xFFFF_FF00) != 0) + throw new IllegalArgumentException("value at byteOffset " + byteOffset + " must not have the 3 highest bytes set but was " + value); + edges.setByte(edgePointer + E_FLAGS, (byte) value); + } else { + edges.setInt(edgePointer + E_FLAGS, value); + } } - public int getFlagInt(long edgePointer, int index) { - return edges.getInt(edgePointer + E_FLAGS + index * 4); + @Override + public int getInt(int edgeId, int index) { + return getFlagInt(toEdgePointer(edgeId), index * 4); } - public void setFlagInt(long edgePointer, int index, int value) { - edges.setInt(edgePointer + E_FLAGS + index * 4, value); + @Override + public void setInt(int edgeId, int index, int value) { + setFlagInt(toEdgePointer(edgeId), index * 4, value); } public void setNodeA(long edgePointer, int nodeA) { @@ -281,10 +323,13 @@ public void setDist(long edgePointer, double distance) { } public void setGeoRef(long edgePointer, long geoRef) { - int geo1 = BitUtil.LITTLE.getIntLow(geoRef); - int geo2 = BitUtil.LITTLE.getIntHigh(geoRef); - edges.setInt(edgePointer + E_GEO_1, geo1); - edges.setInt(edgePointer + E_GEO_2, geo2); + int highest25Bits = (int) (geoRef >>> 39); + // Only two cases are allowed for highest bits. If geoRef is positive then all high bits are 0. If negative then all are 1. + if (highest25Bits != 0 && highest25Bits != 0x1_FF_FFFF) + throw new IllegalArgumentException("geoRef is too " + (geoRef > 0 ? "large " : "small ") + geoRef + ", " + Long.toBinaryString(geoRef)); + + edges.setInt(edgePointer + E_GEO, (int) (geoRef)); + edges.setByte(edgePointer + E_GEO + 4, (byte) (geoRef >> 32)); } public void setKeyValuesRef(long edgePointer, int nameRef) { @@ -315,9 +360,9 @@ public double getDist(long pointer) { public long getGeoRef(long edgePointer) { return BitUtil.LITTLE.toLong( - edges.getInt(edgePointer + E_GEO_1), - edges.getInt(edgePointer + E_GEO_2) - ); + edges.getInt(edgePointer + E_GEO), + // to support negative georefs (#2985) do not mask byte with 0xFF: + edges.getByte(edgePointer + E_GEO + 4)); } public int getKeyValuesRef(long edgePointer) { @@ -337,7 +382,7 @@ public void setLon(long nodePointer, double lon) { } public void setEle(long elePointer, double ele) { - nodes.setInt(elePointer + N_ELE, Helper.eleToInt(ele)); + nodes.setInt(elePointer + N_ELE, Helper.eleToUInt(ele)); } public void setTurnCostRef(long nodePointer, int tcRef) { @@ -357,7 +402,7 @@ public double getLon(long nodePointer) { } public double getEle(long nodePointer) { - return Helper.intToEle(nodes.getInt(nodePointer + N_ELE)); + return Helper.uIntToEle(nodes.getInt(nodePointer + N_ELE)); } public int getTurnCostRef(long nodePointer) { @@ -387,16 +432,16 @@ public void debugPrint() { 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); + IntsRef edgeFlags = createEdgeFlags(); for (int i = 0; i < Math.min(edgeCount, printMax); ++i) { long edgePointer = toEdgePointer(i); - readFlags(edgePointer, intsRef); + readFlags(edgePointer, edgeFlags); System.out.format(Locale.ROOT, formatEdges, i, getNodeA(edgePointer), getNodeB(edgePointer), getLinkA(edgePointer), getLinkB(edgePointer), - intsRef, + edgeFlags, getDist(edgePointer)); } if (edgeCount > printMax) { diff --git a/core/src/main/java/com/graphhopper/storage/MMapDataAccess.java b/core/src/main/java/com/graphhopper/storage/MMapDataAccess.java index 666309c08ca..91518618352 100644 --- a/core/src/main/java/com/graphhopper/storage/MMapDataAccess.java +++ b/core/src/main/java/com/graphhopper/storage/MMapDataAccess.java @@ -254,20 +254,43 @@ public void close() { public void setInt(long bytePos, int value) { int bufferIndex = (int) (bytePos >> segmentSizePower); int index = (int) (bytePos & indexDivisor); - if (index + 4 > segmentSizeInBytes) - throw new IllegalStateException("Padding required. Currently an int cannot be distributed over two segments. " + bytePos); - ByteBuffer byteBuffer = segments.get(bufferIndex); - byteBuffer.putInt(index, value); + ByteBuffer b1 = segments.get(bufferIndex); + if (index + 3 >= segmentSizeInBytes) { + // seldom and special case if int has to be written into two separate segments + ByteBuffer b2 = segments.get(bufferIndex + 1); + if (index + 1 >= segmentSizeInBytes) { + b2.putShort(1, (short) (value >>> 16)); + b2.put(0, (byte) (value >>> 8)); + b1.put(index, (byte) value); + } else if (index + 2 >= segmentSizeInBytes) { + b2.putShort(0, (short) (value >>> 16)); + b1.putShort(index, (short) value); + } else { + // index + 3 >= segmentSizeInBytes + b2.put(0, (byte) (value >>> 24)); + b1.putShort(index + 1, (short) (value >>> 8)); + b1.put(index, (byte) value); + } + } else { + b1.putInt(index, value); + } } @Override public int getInt(long bytePos) { int bufferIndex = (int) (bytePos >> segmentSizePower); int index = (int) (bytePos & indexDivisor); - if (index + 4 > segmentSizeInBytes) - throw new IllegalStateException("Padding required. Currently an int cannot be distributed over two segments. " + bytePos); - ByteBuffer byteBuffer = segments.get(bufferIndex); - return byteBuffer.getInt(index); + ByteBuffer b1 = segments.get(bufferIndex); + if (index + 3 >= segmentSizeInBytes) { + ByteBuffer b2 = segments.get(bufferIndex + 1); + if (index + 1 >= segmentSizeInBytes) + return (b2.getShort(1) & 0xFFFF) << 16 | (b2.get(0) & 0xFF) << 8 | (b1.get(index) & 0xFF); + if (index + 2 >= segmentSizeInBytes) + return (b2.getShort(0) & 0xFFFF) << 16 | (b1.getShort(index) & 0xFFFF); + // index + 3 >= segmentSizeInBytes + return (b2.get(0) & 0xFF) << 24 | (b1.getShort(index + 1) & 0xFFFF) << 8 | (b1.get(index) & 0xFF); + } + return b1.getInt(index); } @Override @@ -275,9 +298,9 @@ public void setShort(long bytePos, short value) { int bufferIndex = (int) (bytePos >>> segmentSizePower); int index = (int) (bytePos & indexDivisor); ByteBuffer byteBuffer = segments.get(bufferIndex); - if (index + 2 > segmentSizeInBytes) { + if (index + 1 >= segmentSizeInBytes) { ByteBuffer byteBufferNext = segments.get(bufferIndex + 1); - // special case if short has to be written into two separate segments + // seldom and special case if short has to be written into two separate segments byteBuffer.put(index, (byte) value); byteBufferNext.put(0, (byte) (value >>> 8)); } else { @@ -290,7 +313,7 @@ public short getShort(long bytePos) { int bufferIndex = (int) (bytePos >>> segmentSizePower); int index = (int) (bytePos & indexDivisor); ByteBuffer byteBuffer = segments.get(bufferIndex); - if (index + 2 > segmentSizeInBytes) { + if (index + 1 >= segmentSizeInBytes) { ByteBuffer byteBufferNext = segments.get(bufferIndex + 1); return (short) ((byteBufferNext.get(0) & 0xFF) << 8 | byteBuffer.get(index) & 0xFF); } diff --git a/core/src/main/java/com/graphhopper/storage/RAMDataAccess.java b/core/src/main/java/com/graphhopper/storage/RAMDataAccess.java index 7fb11d1395c..55c86e1d5e6 100644 --- a/core/src/main/java/com/graphhopper/storage/RAMDataAccess.java +++ b/core/src/main/java/com/graphhopper/storage/RAMDataAccess.java @@ -20,6 +20,9 @@ import java.io.File; import java.io.IOException; import java.io.RandomAccessFile; +import java.lang.invoke.MethodHandles; +import java.lang.invoke.VarHandle; +import java.nio.ByteOrder; import java.util.Arrays; /** @@ -32,6 +35,9 @@ public class RAMDataAccess extends AbstractDataAccess { private byte[][] segments = new byte[0][]; private boolean store; + // we could also use UNSAFE but it is not really faster (see #3005) + private static final VarHandle INT = MethodHandles.byteArrayViewVarHandle(int[].class, ByteOrder.LITTLE_ENDIAN).withInvokeExactBehavior(); + private static final VarHandle SHORT = MethodHandles.byteArrayViewVarHandle(short[].class, ByteOrder.LITTLE_ENDIAN).withInvokeExactBehavior(); RAMDataAccess(String name, String location, boolean store, int segmentSize) { super(name, location, segmentSize); @@ -160,9 +166,23 @@ public final void setInt(long bytePos, int value) { assert segmentSizePower > 0 : "call create or loadExisting before usage!"; int bufferIndex = (int) (bytePos >>> segmentSizePower); int index = (int) (bytePos & indexDivisor); - if (index + 4 > segmentSizeInBytes) - throw new IllegalStateException("Padding required. Currently an int cannot be distributed over two segments. " + bytePos); - bitUtil.fromInt(segments[bufferIndex], value, index); + if (index + 3 >= segmentSizeInBytes) { + // seldom and special case if int has to be written into two separate segments + byte[] b1 = segments[bufferIndex], b2 = segments[bufferIndex + 1]; + if (index + 1 >= segmentSizeInBytes) { + bitUtil.fromUInt3(b2, value >>> 8, 0); + b1[index] = (byte) value; + } else if (index + 2 >= segmentSizeInBytes) { + bitUtil.fromShort(b2, (short) (value >>> 16), 0); + bitUtil.fromShort(b1, (short) value, index); + } else { + // index + 3 >= segmentSizeInBytes + b2[0] = (byte) (value >>> 24); + bitUtil.fromUInt3(b1, value, index); + } + } else { + INT.set(segments[bufferIndex], index, value); + } } @Override @@ -170,9 +190,16 @@ public final int getInt(long bytePos) { assert segments.length > 0 : "call create or loadExisting before usage!"; int bufferIndex = (int) (bytePos >>> segmentSizePower); int index = (int) (bytePos & indexDivisor); - if (index + 4 > segmentSizeInBytes) - throw new IllegalStateException("Padding required. Currently an int cannot be distributed over two segments. " + bytePos); - return bitUtil.toInt(segments[bufferIndex], index); + if (index + 3 >= segmentSizeInBytes) { + byte[] b1 = segments[bufferIndex], b2 = segments[bufferIndex + 1]; + if (index + 1 >= segmentSizeInBytes) + return (b2[2] & 0xFF) << 24 | (b2[1] & 0xFF) << 16 | (b2[0] & 0xFF) << 8 | (b1[index] & 0xFF); + if (index + 2 >= segmentSizeInBytes) + return (b2[1] & 0xFF) << 24 | (b2[0] & 0xFF) << 16 | (b1[index + 1] & 0xFF) << 8 | (b1[index] & 0xFF); + // index + 3 >= segmentSizeInBytes + return (b2[0] & 0xFF) << 24 | (b1[index + 2] & 0xFF) << 16 | (b1[index + 1] & 0xFF) << 8 | (b1[index] & 0xFF); + } + return (int) INT.get(segments[bufferIndex], index); } @Override @@ -180,12 +207,12 @@ public final void setShort(long bytePos, short value) { assert segments.length > 0 : "call create or loadExisting before usage!"; int bufferIndex = (int) (bytePos >>> segmentSizePower); int index = (int) (bytePos & indexDivisor); - if (index + 2 > segmentSizeInBytes) { - // special case if short has to be written into two separate segments + if (index + 1 >= segmentSizeInBytes) { + // seldom and special case if short has to be written into two separate segments segments[bufferIndex][index] = (byte) (value); segments[bufferIndex + 1][0] = (byte) (value >>> 8); } else { - bitUtil.fromShort(segments[bufferIndex], value, index); + SHORT.set(segments[bufferIndex], index, value); } } @@ -194,10 +221,10 @@ public final short getShort(long bytePos) { assert segments.length > 0 : "call create or loadExisting before usage!"; int bufferIndex = (int) (bytePos >>> segmentSizePower); int index = (int) (bytePos & indexDivisor); - if (index + 2 > segmentSizeInBytes) + if (index + 1 >= segmentSizeInBytes) return (short) ((segments[bufferIndex + 1][0] & 0xFF) << 8 | (segments[bufferIndex][index] & 0xFF)); - else - return bitUtil.toShort(segments[bufferIndex], index); + + return (short) SHORT.get(segments[bufferIndex], index); } @Override diff --git a/core/src/main/java/com/graphhopper/storage/TurnCostStorage.java b/core/src/main/java/com/graphhopper/storage/TurnCostStorage.java index 8d694bcf3fd..882fc301d32 100644 --- a/core/src/main/java/com/graphhopper/storage/TurnCostStorage.java +++ b/core/src/main/java/com/graphhopper/storage/TurnCostStorage.java @@ -162,6 +162,21 @@ private long findPointer(int fromEdge, int viaNode, int toEdge) { throw new IllegalStateException("Turn cost list for node: " + viaNode + " is longer than expected, max: " + maxEntries); } + public int getTurnCostsCount() { + return turnCostsCount; + } + + public int getTurnCostsCount(int node) { + int index = baseGraph.getNodeAccess().getTurnCostIndex(node); + int count = 0; + while (index != NO_TURN_ENTRY) { + long pointer = (long) index * BYTES_PER_ENTRY; + index = turnCosts.getInt(pointer + TC_NEXT); + count++; + } + return count; + } + public boolean isClosed() { return turnCosts.isClosed(); } diff --git a/core/src/main/java/com/graphhopper/storage/index/Snap.java b/core/src/main/java/com/graphhopper/storage/index/Snap.java index 7ef64be1a6a..bed20e94ae2 100644 --- a/core/src/main/java/com/graphhopper/storage/index/Snap.java +++ b/core/src/main/java/com/graphhopper/storage/index/Snap.java @@ -160,10 +160,12 @@ public void calcSnappedPoint(DistanceCalc distCalc) { if (considerEqual(crossingPoint.lat, crossingPoint.lon, tmpLat, tmpLon)) { snappedPosition = wayIndex == 0 ? Position.TOWER : Position.PILLAR; snappedPoint = new GHPoint3D(tmpLat, tmpLon, tmpEle); + closestNode = wayIndex == 0 ? closestEdge.getBaseNode() : closestNode; } else if (considerEqual(crossingPoint.lat, crossingPoint.lon, adjLat, adjLon)) { wayIndex++; snappedPosition = wayIndex == fullPL.size() - 1 ? Position.TOWER : Position.PILLAR; snappedPoint = new GHPoint3D(adjLat, adjLon, adjEle); + closestNode = wayIndex == fullPL.size() - 1 ? closestEdge.getAdjNode() : closestNode; } else { snappedPoint = new GHPoint3D(crossingPoint.lat, crossingPoint.lon, (tmpEle + adjEle) / 2); } diff --git a/core/src/main/java/com/graphhopper/util/BitUtil.java b/core/src/main/java/com/graphhopper/util/BitUtil.java index fadc17f68e7..d9c94979f69 100644 --- a/core/src/main/java/com/graphhopper/util/BitUtil.java +++ b/core/src/main/java/com/graphhopper/util/BitUtil.java @@ -88,6 +88,10 @@ public final int toInt(byte[] b, int offset) { | (b[offset + 1] & 0xFF) << 8 | (b[offset] & 0xFF); } + public final int toUInt3(byte[] b, int offset) { + return (b[offset + 2] & 0xFF) << 16 | (b[offset + 1] & 0xFF) << 8 | (b[offset] & 0xFF); + } + public final byte[] fromInt(int value) { byte[] bytes = new byte[4]; fromInt(bytes, value, 0); @@ -120,6 +124,15 @@ public final void fromInt(byte[] bytes, int value, int offset) { bytes[offset] = (byte) (value); } + /** + * Note, currently value with higher bits set (like for a negative value) won't throw an exception at this level. + */ + public final void fromUInt3(byte[] bytes, int value, int offset) { + bytes[offset + 2] = (byte) (value >>> 16); + bytes[offset + 1] = (byte) (value >>> 8); + bytes[offset] = (byte) (value); + } + /** * See the counterpart {@link #fromLong(long)} */ @@ -128,11 +141,11 @@ public final long toLong(byte[] b) { } public final long toLong(int intLow, int intHigh) { - return ((long) intHigh << 32) | (intLow & 0xFFFFFFFFL); + return ((long) intHigh << 32) | (intLow & 0xFFFF_FFFFL); } public final long toLong(byte[] b, int offset) { - return ((long) toInt(b, offset + 4) << 32) | (toInt(b, offset) & 0xFFFFFFFFL); + return ((long) toInt(b, offset + 4) << 32) | (toInt(b, offset) & 0xFFFF_FFFFL); } public final byte[] fromLong(long value) { @@ -179,14 +192,6 @@ public byte[] fromBitString(String str) { return bytes; } - public final String toBitString(IntsRef intsRef) { - StringBuilder str = new StringBuilder(); - for (int ints : intsRef.ints) { - str.append(toBitString(ints, 32)); - } - return str.toString(); - } - /** * Similar to Long.toBinaryString */ @@ -249,7 +254,7 @@ public String toBitString(byte[] bytes) { } public final int getIntLow(long longValue) { - return (int) (longValue & 0xFFFFFFFFL); + return (int) (longValue & 0xFFFF_FFFFL); } public final int getIntHigh(long longValue) { diff --git a/core/src/main/java/com/graphhopper/util/Constants.java b/core/src/main/java/com/graphhopper/util/Constants.java index 87f6f63d990..e4897990c5e 100644 --- a/core/src/main/java/com/graphhopper/util/Constants.java +++ b/core/src/main/java/com/graphhopper/util/Constants.java @@ -59,12 +59,12 @@ public class Constants { private static final int JVM_MINOR_VERSION; public static final int VERSION_NODE = 9; - public static final int VERSION_EDGE = 22; + public static final int VERSION_EDGE = 23; // this should be increased whenever the format of the serialized EncodingManager is changed - public static final int VERSION_EM = 3; + public static final int VERSION_EM = 4; public static final int VERSION_SHORTCUT = 9; public static final int VERSION_NODE_CH = 0; - public static final int VERSION_GEOMETRY = 6; + public static final int VERSION_GEOMETRY = 7; public static final int VERSION_TURN_COSTS = 0; public static final int VERSION_LOCATION_IDX = 5; public static final int VERSION_KV_STORAGE = 2; diff --git a/core/src/main/java/com/graphhopper/util/EdgeIteratorState.java b/core/src/main/java/com/graphhopper/util/EdgeIteratorState.java index 6975bd61ee5..81b53f74266 100644 --- a/core/src/main/java/com/graphhopper/util/EdgeIteratorState.java +++ b/core/src/main/java/com/graphhopper/util/EdgeIteratorState.java @@ -22,7 +22,7 @@ import com.graphhopper.storage.Graph; import com.graphhopper.storage.IntsRef; -import java.util.List; +import java.util.Map; /** * This interface represents an edge and is one possible state of an EdgeIterator. @@ -40,14 +40,40 @@ * @see EdgeExplorer */ public interface EdgeIteratorState { - BooleanEncodedValue UNFAVORED_EDGE = new SimpleBooleanEncodedValue("unfavored"); + BooleanEncodedValue UNFAVORED_EDGE = new BooleanEncodedValue() { + @Override + public int init(InitializerConfig init) { + throw new IllegalStateException("Cannot happen for 'unfavored' BooleanEncodedValue"); + } + + @Override + public boolean getBool(boolean reverse, int edgeId, EdgeIntAccess edgeIntAccess) { + return false; + } + + @Override + public void setBool(boolean reverse, int edgeId, EdgeIntAccess edgeIntAccess, boolean value) { + throw new IllegalStateException("state of 'unfavored' cannot be modified"); + } + + @Override + public boolean isStoreTwoDirections() { + return false; + } + + @Override + public String getName() { + return "unfavored"; + } + }; + /** * This method can be used to fetch the internal reverse state of an edge. */ BooleanEncodedValue REVERSE_STATE = new BooleanEncodedValue() { @Override public int init(InitializerConfig init) { - throw new IllegalStateException("Cannot happen for this BooleanEncodedValue"); + throw new IllegalStateException("Cannot happen for 'reverse' BooleanEncodedValue"); } @Override @@ -62,7 +88,7 @@ public boolean getBool(boolean reverse, int edgeId, EdgeIntAccess edgeIntAccess) @Override public void setBool(boolean reverse, int edgeId, EdgeIntAccess edgeIntAccess, boolean value) { - throw new IllegalStateException("reverse state cannot be modified"); + throw new IllegalStateException("state of 'reverse' cannot be modified"); } @Override @@ -120,8 +146,9 @@ public boolean isStoreTwoDirections() { /** * @param list is a sorted collection of coordinates between the base node and the current adjacent node. Specify - * the list without the adjacent and base node. This method can be called multiple times, but if the - * distance changes, the setDistance method is not called automatically. + * the list without the adjacent and base node. This method can be called multiple times, unless the + * given point list is longer than the first time the method was called. Also keep in + * mind that if the distance changes the setDistance method is not called automatically. */ EdgeIteratorState setWayGeometry(PointList list); @@ -207,14 +234,14 @@ public boolean isStoreTwoDirections() { * But it might be slow and more inefficient on retrieval. Call this setKeyValues method only once per * EdgeIteratorState as it allocates new space everytime this method is called. */ - EdgeIteratorState setKeyValues(List map); + EdgeIteratorState setKeyValues(Map map); /** * This method returns KeyValue pairs for both directions in contrast to {@link #getValue(String)}. * - * @see #setKeyValues(List) + * @see #setKeyValues(Map) */ - List getKeyValues(); + Map getKeyValues(); /** * This method returns the *first* value for the specified key and only if stored for the direction of this diff --git a/core/src/main/java/com/graphhopper/util/GHUtility.java b/core/src/main/java/com/graphhopper/util/GHUtility.java index c4bad9b0230..0639d155b64 100644 --- a/core/src/main/java/com/graphhopper/util/GHUtility.java +++ b/core/src/main/java/com/graphhopper/util/GHUtility.java @@ -32,10 +32,7 @@ import com.graphhopper.routing.util.CustomArea; import com.graphhopper.routing.util.EdgeFilter; import com.graphhopper.routing.weighting.Weighting; -import com.graphhopper.storage.Graph; -import com.graphhopper.storage.NodeAccess; -import com.graphhopper.storage.RoutingCHEdgeIterator; -import com.graphhopper.storage.TurnCostStorage; +import com.graphhopper.storage.*; import com.graphhopper.storage.index.LocationIndex; import com.graphhopper.storage.index.Snap; import com.graphhopper.util.shapes.BBox; @@ -381,6 +378,29 @@ public static int getEdgeFromEdgeKey(int edgeKey) { return edgeKey / 2; } + /** + * @return the common node of two edges + * @throws IllegalArgumentException if one of the edges doesn't exist or is a loop or the edges + * aren't connected at exactly one distinct node + */ + public static int getCommonNode(BaseGraph baseGraph, int edge1, int edge2) { + EdgeIteratorState e1 = baseGraph.getEdgeIteratorState(edge1, Integer.MIN_VALUE); + EdgeIteratorState e2 = baseGraph.getEdgeIteratorState(edge2, Integer.MIN_VALUE); + if (e1.getBaseNode() == e1.getAdjNode()) + throw new IllegalArgumentException("edge1: " + edge1 + " is a loop at node " + e1.getBaseNode()); + if (e2.getBaseNode() == e2.getAdjNode()) + throw new IllegalArgumentException("edge2: " + edge2 + " is a loop at node " + e2.getBaseNode()); + + if ((e1.getBaseNode() == e2.getBaseNode() && e1.getAdjNode() == e2.getAdjNode()) || (e1.getBaseNode() == e2.getAdjNode() && e1.getAdjNode() == e2.getBaseNode())) + throw new IllegalArgumentException("edge1: " + edge1 + " and edge2: " + edge2 + " form a circle"); + else if (e1.getBaseNode() == e2.getBaseNode() || e1.getBaseNode() == e2.getAdjNode()) + return e1.getBaseNode(); + else if (e1.getAdjNode() == e2.getAdjNode() || e1.getAdjNode() == e2.getBaseNode()) + return e1.getAdjNode(); + else + throw new IllegalArgumentException("edge1: " + edge1 + " and edge2: " + edge2 + " aren't connected"); + } + public static void setSpeed(double fwdSpeed, double bwdSpeed, BooleanEncodedValue accessEnc, DecimalEncodedValue speedEnc, EdgeIteratorState... edges) { setSpeed(fwdSpeed, bwdSpeed, accessEnc, speedEnc, Arrays.asList(edges)); } @@ -415,13 +435,18 @@ public static EdgeIteratorState setSpeed(double averageSpeed, boolean fwd, boole return edge; } - public static void updateDistancesFor(Graph g, int node, double lat, double lon) { + public static void updateDistancesFor(Graph g, int node, double... latlonele) { NodeAccess na = g.getNodeAccess(); - na.setNode(node, lat, lon); + if (latlonele.length == 3) + na.setNode(node, latlonele[0], latlonele[1], latlonele[2]); + else if (latlonele.length == 2) { + if (na.is3D()) throw new IllegalArgumentException("graph requires elevation"); + na.setNode(node, latlonele[0], latlonele[1]); + } else + throw new IllegalArgumentException("illegal number of arguments " + latlonele.length); EdgeIterator iter = g.createEdgeExplorer().setBaseNode(node); while (iter.next()) { iter.setDistance(DIST_EARTH.calcDistance(iter.fetchWayGeometry(FetchMode.ALL))); - // System.out.println(node + "->" + adj + ": " + iter.getDistance()); } } diff --git a/core/src/main/java/com/graphhopper/util/details/AbstractPathDetailsBuilder.java b/core/src/main/java/com/graphhopper/util/details/AbstractPathDetailsBuilder.java index ad8f97ee70a..f8f3a5fee4d 100644 --- a/core/src/main/java/com/graphhopper/util/details/AbstractPathDetailsBuilder.java +++ b/core/src/main/java/com/graphhopper/util/details/AbstractPathDetailsBuilder.java @@ -77,7 +77,7 @@ public void endInterval(int lastIndex) { } public Map.Entry> build() { - return new MapEntry(getName(), pathDetails); + return new MapEntry<>(getName(), pathDetails); } @Override @@ -89,4 +89,4 @@ public String getName() { public String toString() { return getName(); } -} \ No newline at end of file +} diff --git a/core/src/main/java/com/graphhopper/util/details/ConstantDetailsBuilder.java b/core/src/main/java/com/graphhopper/util/details/ConstantDetailsBuilder.java index 6c240a778e3..61e976a7288 100644 --- a/core/src/main/java/com/graphhopper/util/details/ConstantDetailsBuilder.java +++ b/core/src/main/java/com/graphhopper/util/details/ConstantDetailsBuilder.java @@ -31,6 +31,7 @@ public class ConstantDetailsBuilder extends AbstractPathDetailsBuilder { private final Object value; private boolean firstEdge = true; + private int lastIndex = -1; public ConstantDetailsBuilder(String name, Object value) { super(name); @@ -51,11 +52,22 @@ public boolean isEdgeDifferentToLastEdge(EdgeIteratorState edge) { return false; } + @Override + public void endInterval(int lastIndex) { + this.lastIndex = lastIndex; + super.endInterval(lastIndex); + } + @Override public Map.Entry> build() { - if (firstEdge) + if (firstEdge) { // #2915 if there was no edge at all we need to add a single entry manually here - return new MapEntry<>(getName(), new ArrayList<>(List.of(new PathDetail(value)))); + // #3007 we need to set the value but also the (empty) interval (first/last) + PathDetail p = new PathDetail(value); + p.setFirst(lastIndex); + p.setLast(lastIndex); + return new MapEntry<>(getName(), new ArrayList<>(List.of(p))); + } return super.build(); } } diff --git a/core/src/main/java/com/graphhopper/util/details/PathDetailsFromEdges.java b/core/src/main/java/com/graphhopper/util/details/PathDetailsFromEdges.java index 8873b7ad927..a900660effb 100644 --- a/core/src/main/java/com/graphhopper/util/details/PathDetailsFromEdges.java +++ b/core/src/main/java/com/graphhopper/util/details/PathDetailsFromEdges.java @@ -59,8 +59,9 @@ public static Map> calcDetails(Path path, EncodedValueL if (!path.isFound() || requestedPathDetails.isEmpty()) return Collections.emptyMap(); HashSet uniquePD = new HashSet<>(requestedPathDetails.size()); - Collection res = requestedPathDetails.stream().filter(pd -> !uniquePD.add(pd)).collect(Collectors.toList()); - if (!res.isEmpty()) throw new IllegalArgumentException("Do not use duplicate path details: " + res); + Collection res = requestedPathDetails.stream().filter(pd -> !uniquePD.add(pd)).toList(); + if (!res.isEmpty()) + throw new IllegalArgumentException("Do not use duplicate path details: " + res); List pathBuilders = pathBuilderFactory.createPathDetailsBuilders(requestedPathDetails, path, evLookup, weighting, graph); if (pathBuilders.isEmpty()) @@ -96,4 +97,4 @@ public void finish() { calc.endInterval(lastIndex); } } -} \ No newline at end of file +} diff --git a/core/src/main/resources/com/graphhopper/custom_models/bike.json b/core/src/main/resources/com/graphhopper/custom_models/bike.json index 8de26dbbbd0..4e38dcbb591 100644 --- a/core/src/main/resources/com/graphhopper/custom_models/bike.json +++ b/core/src/main/resources/com/graphhopper/custom_models/bike.json @@ -12,6 +12,6 @@ ], "speed": [ { "if": "true", "limit_to": "bike_average_speed" }, - { "if": "!bike_access && backward_bike_access && !roundabout", "limit_to": "5" } + { "if": "!bike_access && backward_bike_access", "limit_to": "5" } ] } diff --git a/core/src/main/resources/com/graphhopper/custom_models/bus.json b/core/src/main/resources/com/graphhopper/custom_models/bus.json index 5b29f5831f3..3a1e1e4f9f5 100644 --- a/core/src/main/resources/com/graphhopper/custom_models/bus.json +++ b/core/src/main/resources/com/graphhopper/custom_models/bus.json @@ -2,6 +2,8 @@ // profiles: // - name: bus // custom_model_files: [bus.json] +// +// There is also a hov_access which might be suitable for carpooling and can replace or be combined with bus_access { "distance_influence": 90, diff --git a/core/src/main/resources/com/graphhopper/custom_models/mtb.json b/core/src/main/resources/com/graphhopper/custom_models/mtb.json index fb42e89d6af..6745f55d91f 100644 --- a/core/src/main/resources/com/graphhopper/custom_models/mtb.json +++ b/core/src/main/resources/com/graphhopper/custom_models/mtb.json @@ -12,6 +12,6 @@ ], "speed": [ { "if": "true", "limit_to": "mtb_average_speed" }, - { "if": "!mtb_access && backward_mtb_access && !roundabout", "limit_to": "5" } + { "if": "!mtb_access && backward_mtb_access", "limit_to": "5" } ] } diff --git a/core/src/main/resources/com/graphhopper/custom_models/racingbike.json b/core/src/main/resources/com/graphhopper/custom_models/racingbike.json index 19af214d632..a0e56544cdc 100644 --- a/core/src/main/resources/com/graphhopper/custom_models/racingbike.json +++ b/core/src/main/resources/com/graphhopper/custom_models/racingbike.json @@ -1,17 +1,17 @@ -// to use this custom model you set the following option in the config.yml: +// to use this custom model you need to set the following option in the config.yml // graph.elevation.provider: srtm # enables elevation // profiles: // - name: racingbike // custom_model_files: [racingbike.json, bike_elevation.json] { -"priority": [ -{ "if": "true", "multiply_by": "racingbike_priority" }, -{ "if": "!racingbike_access && (!backward_racingbike_access || roundabout)", "multiply_by": "0" }, -{ "else_if": "!racingbike_access && backward_racingbike_access", "multiply_by": "0.2" } -], -"speed": [ -{ "if": "true", "limit_to": "racingbike_average_speed" }, -{ "if": "!racingbike_access && backward_racingbike_access && !roundabout", "limit_to": "5" } -] + "priority": [ + { "if": "true", "multiply_by": "racingbike_priority" }, + { "if": "!racingbike_access && (!backward_racingbike_access || roundabout)", "multiply_by": "0" }, + { "else_if": "!racingbike_access && backward_racingbike_access", "multiply_by": "0.2" } + ], + "speed": [ + { "if": "true", "limit_to": "racingbike_average_speed" }, + { "if": "!racingbike_access && backward_racingbike_access", "limit_to": "5" } + ] } diff --git a/core/src/test/java/com/graphhopper/GraphHopperTest.java b/core/src/test/java/com/graphhopper/GraphHopperTest.java index f76ac3f21e5..92c03063135 100644 --- a/core/src/test/java/com/graphhopper/GraphHopperTest.java +++ b/core/src/test/java/com/graphhopper/GraphHopperTest.java @@ -32,7 +32,6 @@ import com.graphhopper.routing.util.countryrules.CountryRuleFactory; import com.graphhopper.routing.util.parsers.OSMRoadEnvironmentParser; import com.graphhopper.routing.weighting.Weighting; -import com.graphhopper.search.KVStorage; import com.graphhopper.storage.IntsRef; import com.graphhopper.storage.index.LocationIndexTree; import com.graphhopper.storage.index.Snap; @@ -68,6 +67,7 @@ import static com.graphhopper.util.GHUtility.createRectangle; import static com.graphhopper.util.Parameters.Algorithms.*; import static com.graphhopper.util.Parameters.Curbsides.*; +import static com.graphhopper.util.Parameters.Details.STREET_REF; import static com.graphhopper.util.Parameters.Routing.TIMEOUT_MS; import static com.graphhopper.util.Parameters.Routing.U_TURN_COSTS; import static java.util.Arrays.asList; @@ -1097,24 +1097,22 @@ public void testSRTMWithInstructions() { assertEquals(12, il.size()); assertTrue(il.get(0).getPoints().is3D()); - String str = res.getPoints().toString(); - - assertEquals("(43.730684662577524,7.421283725164733,62.0), (43.7306797,7.4213823,66.0), (43.730949,7.4214948,66.0), " + - "(43.731098,7.4215463,45.0), (43.7312269,7.4215824,45.0), (43.7312991,7.42159,45.0), (43.7313271,7.4214147,45.0), " + - "(43.7312506,7.4213664,45.0), (43.7312546,7.4212741,52.0), (43.7312822,7.4211156,52.0), (43.7313624,7.4211455,52.0), " + - "(43.7313714,7.4211233,52.0), (43.7314858,7.4211734,52.0), (43.7315522,7.4209778,52.0), (43.7315753,7.4208688,52.0), " + - "(43.7316061,7.4208249,52.0), (43.7316404,7.4208503,52.0), (43.7316741,7.4210502,52.0), (43.7316276,7.4214636,45.0), " + - "(43.7316391,7.4215065,45.0), (43.7316664,7.4214904,45.0), (43.7316981,7.4212652,52.0), (43.7317185,7.4211861,52.0), " + - "(43.7319676,7.4206159,19.0), (43.732038,7.4203936,20.0), (43.732173,7.4198886,20.0), (43.7322266,7.4196414,26.0), " + - "(43.732266,7.4194654,26.0), (43.7323236,7.4192656,26.0), (43.7323374,7.4191503,26.0), (43.7323374,7.4190461,26.0), " + - "(43.7323875,7.4189195,26.0), (43.7323444,7.4188579,26.0), (43.731974,7.4181688,29.0), (43.7316421,7.4173042,23.0), " + - "(43.7315686,7.4170356,31.0), (43.7314269,7.4166815,31.0), (43.7312401,7.4163184,49.0), (43.7308286,7.4157613,29.399999618530273), " + - "(43.730662,7.4155599,22.0), (43.7303643,7.4151193,51.0), (43.729579,7.4137274,40.0), (43.7295167,7.4137244,40.0), (43.7294669,7.4137725,40.0), " + - "(43.7285987,7.4149068,23.0), (43.7285167,7.4149272,22.0), (43.7283974,7.4148646,22.0), (43.7285619,7.4151365,23.0), (43.7285774,7.4152444,23.0), " + - "(43.7285863,7.4157656,21.0), (43.7285763,7.4159759,21.0), (43.7285238,7.4161982,20.0), (43.7284592,7.4163655,18.0), (43.72838,7.4165003,18.0), " + - "(43.7281669,7.4168192,18.0), (43.7281442,7.4169449,18.0), (43.7281477,7.4170695,18.0), (43.7281684,7.4172435,14.0), (43.7282784,7.4179606,14.0), " + - "(43.7282757,7.418175,11.0), (43.7282319,7.4183683,11.0), (43.7281482,7.4185473,11.0), (43.7280654,7.4186535,11.0), (43.7279259,7.418748,11.0), " + - "(43.7278398,7.4187697,11.0), (43.727779,7.4187731,11.0), (43.7276825,7.4190072,11.0), (43.72767974015672,7.419198523220426,11.0)", str); + assertEquals(Helper.createPointList3D(43.730684662577524, 7.421283725164733, 62.0, 43.7306797, 7.4213823, 66.0, 43.730949, 7.4214948, 66.0, + 43.731098, 7.4215463, 45.0, 43.7312269, 7.4215824, 45.0, 43.7312991, 7.42159, 45.0, 43.7313271, 7.4214147, 45.0, + 43.7312506, 7.4213664, 45.0, 43.7312546, 7.4212741, 52.0, 43.7312822, 7.4211156, 52.0, 43.7313624, 7.4211455, 52.0, + 43.7313714, 7.4211233, 52.0, 43.7314858, 7.4211734, 52.0, 43.7315522, 7.4209778, 52.0, 43.7315753, 7.4208688, 52.0, + 43.7316061, 7.4208249, 52.0, 43.7316404, 7.4208503, 52.0, 43.7316741, 7.4210502, 52.0, 43.7316276, 7.4214636, 45.0, + 43.7316391, 7.4215065, 45.0, 43.7316664, 7.4214904, 45.0, 43.7316981, 7.4212652, 52.0, 43.7317185, 7.4211861, 52.0, + 43.7319676, 7.4206159, 19.0, 43.732038, 7.4203936, 20.0, 43.732173, 7.4198886, 20.0, 43.7322266, 7.4196414, 26.0, + 43.732266, 7.4194654, 26.0, 43.7323236, 7.4192656, 26.0, 43.7323374, 7.4191503, 26.0, 43.7323374, 7.4190461, 26.0, + 43.7323875, 7.4189195, 26.0, 43.7323444, 7.4188579, 26.0, 43.731974, 7.4181688, 29.0, 43.7316421, 7.4173042, 23.0, + 43.7315686, 7.4170356, 31.0, 43.7314269, 7.4166815, 31.0, 43.7312401, 7.4163184, 49.0, 43.7308286, 7.4157613, 29.4, + 43.730662, 7.4155599, 22.0, 43.7303643, 7.4151193, 51.0, 43.729579, 7.4137274, 40.0, 43.7295167, 7.4137244, 40.0, 43.7294669, 7.4137725, 40.0, + 43.7285987, 7.4149068, 23.0, 43.7285167, 7.4149272, 22.0, 43.7283974, 7.4148646, 22.0, 43.7285619, 7.4151365, 23.0, 43.7285774, 7.4152444, 23.0, + 43.7285863, 7.4157656, 21.0, 43.7285763, 7.4159759, 21.0, 43.7285238, 7.4161982, 20.0, 43.7284592, 7.4163655, 18.0, 43.72838, 7.4165003, 18.0, + 43.7281669, 7.4168192, 18.0, 43.7281442, 7.4169449, 18.0, 43.7281477, 7.4170695, 18.0, 43.7281684, 7.4172435, 14.0, 43.7282784, 7.4179606, 14.0, + 43.7282757, 7.418175, 11.0, 43.7282319, 7.4183683, 11.0, 43.7281482, 7.4185473, 11.0, 43.7280654, 7.4186535, 11.0, 43.7279259, 7.418748, 11.0, + 43.7278398, 7.4187697, 11.0, 43.727779, 7.4187731, 11.0, 43.7276825, 7.4190072, 11.0, 43.72767974015672, 7.419198523220426, 11.0), res.getPoints()); assertEquals(84, res.getAscend(), 1e-1); assertEquals(135, res.getDescend(), 1e-1); @@ -1230,8 +1228,8 @@ public void testSRTMWithLongEdgeSampling() { assertEquals(67.4, arsp.getDescend(), 1e-1); assertEquals(74, arsp.getPoints().size()); - assertEquals(new GHPoint3D(43.73068455771767, 7.421283689825812, 55.82900047302246), arsp.getPoints().get(0)); - assertEquals(new GHPoint3D(43.727679637988224, 7.419198521975086, 12.274499893188477), arsp.getPoints().get(arsp.getPoints().size() - 1)); + assertEquals(new GHPoint3D(43.73068455771767, 7.421283689825812, 55.83), arsp.getPoints().get(0)); + assertEquals(new GHPoint3D(43.727679637988224, 7.419198521975086, 12.27), arsp.getPoints().get(arsp.getPoints().size() - 1)); assertEquals(55.83, arsp.getPoints().get(0).getEle(), 1e-2); assertEquals(57.78, arsp.getPoints().get(1).getEle(), 1e-2); @@ -1892,9 +1890,9 @@ public void testIssue1960() { assertEquals(1995.18, pathLM.getDistance(), 0.1); assertEquals(1995.18, path.getDistance(), 0.1); - assertEquals(149481, pathCH.getTime()); - assertEquals(149481, pathLM.getTime()); - assertEquals(149481, path.getTime()); + assertEquals(149482, pathCH.getTime()); + assertEquals(149482, pathLM.getTime()); + assertEquals(149482, path.getTime()); } @Test @@ -2277,7 +2275,7 @@ public void simplifyWithInstructionsAndPathDetails() { .addPoint(new GHPoint(50.016895, 11.4923)) .addPoint(new GHPoint(50.003464, 11.49157)) .setProfile(profile) - .setPathDetails(Arrays.asList(KVStorage.KeyValue.STREET_REF, "max_speed")); + .setPathDetails(Arrays.asList(STREET_REF, "max_speed")); req.putHint("elevation", true); GHResponse rsp = hopper.route(req); @@ -2319,7 +2317,7 @@ public void simplifyWithInstructionsAndPathDetails() { assertDetail(speeds.get(7), "null [52, 54]"); // check street names - List streetNames = path.getPathDetails().get(KVStorage.KeyValue.STREET_REF); + List streetNames = path.getPathDetails().get(STREET_REF); assertDetail(streetNames.get(0), "KU 11 [0, 4]"); assertDetail(streetNames.get(1), "B 85 [4, 24]"); assertDetail(streetNames.get(2), "B 85 [24, 45]"); @@ -2330,7 +2328,7 @@ public void simplifyWithInstructionsAndPathDetails() { } private void assertInstruction(Instruction instruction, String expectedRef, String expectedInterval, int expectedLength, int expectedPoints) { - assertEquals(expectedRef, instruction.getExtraInfoJSON().get(KVStorage.KeyValue.STREET_REF)); + assertEquals(expectedRef, instruction.getExtraInfoJSON().get(STREET_REF)); assertEquals(expectedInterval, ((ShallowImmutablePointList) instruction.getPoints()).getIntervalString()); assertEquals(expectedLength, instruction.getLength()); assertEquals(expectedPoints, instruction.getPoints().size()); @@ -2591,6 +2589,24 @@ public void testBarriers() { } } + @Test + public void turnRestrictionWithSnapToViaEdge_issue2996() { + final String profile = "profile"; + GraphHopper hopper = new GraphHopper(). + setGraphHopperLocation(GH_LOCATION). + setOSMFile("../map-matching/files/leipzig_germany.osm.pbf"). + setEncodedValuesString("car_access, car_average_speed"). + setProfiles(TestProfiles.accessAndSpeed(profile, "car").setTurnCostsConfig(TurnCostsConfig.car())). + setMinNetworkSize(200); + hopper.importOrLoad(); + // doing a simple left-turn is allowed + GHResponse res = hopper.route(new GHRequest(51.34665, 12.391847, 51.346254, 12.39256).setProfile(profile)); + assertEquals(81, res.getBest().getDistance(), 1); + // if we stop right after the left-turn on the via-edge the turn should still be allowed of course (there should be no detour that avoids the turn) + res = hopper.route(new GHRequest(51.34665, 12.391847, 51.346306, 12.392091).setProfile(profile)); + assertEquals(48, res.getBest().getDistance(), 1); + } + @Test public void germanyCountryRuleAvoidsTracks() { final String profile = "profile"; @@ -2797,4 +2813,65 @@ void testLoadingWithAnotherSpeedFactorWorks() { } } + @ParameterizedTest() + @ValueSource(booleans = {true, false}) + void legDistanceWithDuplicateEndpoint(boolean simplifyResponse) { + // see #3007 + GraphHopper hopper = new GraphHopper(). + setGraphHopperLocation(GH_LOCATION). + setOSMFile(MONACO). + setEncodedValuesString("car_access, car_average_speed"). + setProfiles(TestProfiles.accessAndSpeed("car")). + importOrLoad(); + hopper.getRouterConfig().setSimplifyResponse(simplifyResponse); + GHRequest request = new GHRequest().setProfile("car"); + request.addPoint(new GHPoint(43.732496, 7.427231)); + request.addPoint(new GHPoint(43.732499, 7.426758)); + request.addPoint(new GHPoint(43.732499, 7.426758)); + request.setPathDetails(List.of("leg_distance")); + GHResponse routeRsp = hopper.route(request); + assertEquals(4, routeRsp.getBest().getPoints().size()); + assertEquals(40.080, routeRsp.getBest().getDistance(), 1.e-3); + List p = routeRsp.getBest().getPathDetails().get("leg_distance"); + // there should be two consecutive leg_distance intervals, even though the second is empty: [0,3] and [3,3], see #2915 + assertEquals(2, p.size()); + assertEquals(0, p.get(0).getFirst()); + assertEquals(3, p.get(0).getLast()); + assertEquals(40.080, (double) p.get(0).getValue(), 1.e-3); + assertEquals(3, p.get(1).getFirst()); + assertEquals(3, p.get(1).getLast()); + assertEquals(0.0, (double) p.get(1).getValue(), 1.e-3); + } + + @ParameterizedTest() + @ValueSource(booleans = {true, false}) + void legDistanceWithDuplicateEndpoint_onlyTwoPoints(boolean simplifyResponse) { + // see #3007 + GraphHopper hopper = new GraphHopper(). + setGraphHopperLocation(GH_LOCATION). + setOSMFile(MONACO). + setEncodedValuesString("car_access, car_average_speed"). + setProfiles(TestProfiles.accessAndSpeed("car")). + importOrLoad(); + hopper.getRouterConfig().setSimplifyResponse(simplifyResponse); + GHRequest request = new GHRequest().setProfile("car"); + // special case where the points are so close to each other that the resulting route contains only two points total + request.addPoint(new GHPoint(43.732399, 7.426658)); + request.addPoint(new GHPoint(43.732499, 7.426758)); + request.addPoint(new GHPoint(43.732499, 7.426758)); + request.setPathDetails(List.of("leg_distance")); + GHResponse routeRsp = hopper.route(request); + assertEquals(2, routeRsp.getBest().getPoints().size()); + assertEquals(10.439, routeRsp.getBest().getDistance(), 1.e-3); + List p = routeRsp.getBest().getPathDetails().get("leg_distance"); + // there should be two consecutive leg_distance intervals, even though the second is empty: [0,3] and [3,3], see #2915 + assertEquals(2, p.size()); + assertEquals(0, p.get(0).getFirst()); + assertEquals(1, p.get(0).getLast()); + assertEquals(10.439, (double) p.get(0).getValue(), 1.e-3); + assertEquals(1, p.get(1).getFirst()); + assertEquals(1, p.get(1).getLast()); + assertEquals(0.0, (double) p.get(1).getValue(), 1.e-3); + } + } 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 10237c77591..c3313264c50 100644 --- a/core/src/test/java/com/graphhopper/reader/osm/OSMReaderTest.java +++ b/core/src/test/java/com/graphhopper/reader/osm/OSMReaderTest.java @@ -282,7 +282,7 @@ public void test_edgeDistanceWhenFirstNodeIsMissing_issue2221() { while (iter.next()) { assertEquals(DistanceCalcEarth.DIST_EARTH.calcDistance(iter.fetchWayGeometry(FetchMode.ALL)), iter.getDistance(), 1.e-3); } - assertEquals(35.609, graph.getEdgeIteratorState(0, Integer.MIN_VALUE).getDistance(), 1.e-3); + assertEquals(35.612, graph.getEdgeIteratorState(0, Integer.MIN_VALUE).getDistance(), 1.e-3); assertEquals(75.256, graph.getEdgeIteratorState(1, Integer.MIN_VALUE).getDistance(), 1.e-3); assertEquals(143.332, graph.getEdgeIteratorState(2, Integer.MIN_VALUE).getDistance(), 1.e-3); } diff --git a/core/src/test/java/com/graphhopper/routing/PathTest.java b/core/src/test/java/com/graphhopper/routing/PathTest.java index 7862e571e13..d4d0cf7bfcb 100644 --- a/core/src/test/java/com/graphhopper/routing/PathTest.java +++ b/core/src/test/java/com/graphhopper/routing/PathTest.java @@ -34,8 +34,7 @@ import java.util.*; -import static com.graphhopper.search.KVStorage.KeyValue.STREET_NAME; -import static com.graphhopper.search.KVStorage.KeyValue.createKV; +import static com.graphhopper.search.KVStorage.KValue; import static com.graphhopper.storage.AbstractGraphStorageTester.assertPList; import static com.graphhopper.util.Parameters.Details.*; import static org.junit.jupiter.api.Assertions.*; @@ -108,7 +107,7 @@ public void testWayList() { assertEquals(path.calcPoints().size() - 1, acc); // force minor change for instructions - edge2.setKeyValues(createKV(STREET_NAME, "2")); + edge2.setKeyValues(Map.of(STREET_NAME, new KValue( "2"))); na.setNode(3, 1.0, 1.0); g.edge(1, 3).setDistance(1000).set(carAvSpeedEnc, 10.0, 10.0); @@ -175,16 +174,16 @@ public void testFindInstruction() { EdgeIteratorState edge1 = g.edge(0, 1).setDistance(1000).set(carAvSpeedEnc, 50.0, 50.0); edge1.setWayGeometry(Helper.createPointList()); - edge1.setKeyValues(createKV(STREET_NAME, "Street 1")); + edge1.setKeyValues(Map.of(STREET_NAME, new KValue( "Street 1"))); EdgeIteratorState edge2 = g.edge(1, 2).setDistance(1000).set(carAvSpeedEnc, 50.0, 50.0); edge2.setWayGeometry(Helper.createPointList()); - edge2.setKeyValues(createKV(STREET_NAME, "Street 2")); + edge2.setKeyValues(Map.of(STREET_NAME, new KValue( "Street 2"))); EdgeIteratorState edge3 = g.edge(2, 3).setDistance(1000).set(carAvSpeedEnc, 50.0, 50.0); edge3.setWayGeometry(Helper.createPointList()); - edge3.setKeyValues(createKV(STREET_NAME, "Street 3")); + edge3.setKeyValues(Map.of(STREET_NAME, new KValue( "Street 3"))); EdgeIteratorState edge4 = g.edge(3, 4).setDistance(500).set(carAvSpeedEnc, 50.0, 50.0); edge4.setWayGeometry(Helper.createPointList()); - edge4.setKeyValues(createKV(STREET_NAME, "Street 4")); + edge4.setKeyValues(Map.of(STREET_NAME, new KValue( "Street 4"))); g.edge(1, 5).setDistance(10000).set(carAvSpeedEnc, 50.0, 50.0); g.edge(2, 5).setDistance(10000).set(carAvSpeedEnc, 50.0, 50.0); @@ -534,32 +533,32 @@ public void testCalcInstructionsRoundaboutIssue353() { na.setNode(10, 52.5135, 13.348); na.setNode(11, 52.514, 13.347); - graph.edge(2, 1).set(carAvSpeedEnc, 60, 0).setDistance(5).setKeyValues(createKV(STREET_NAME, "MainStreet 2 1")); - graph.edge(1, 11).set(carAvSpeedEnc, 60, 0).setDistance(5).setKeyValues(createKV(STREET_NAME, "MainStreet 1 11")); + graph.edge(2, 1).set(carAvSpeedEnc, 60, 0).setDistance(5).setKeyValues(Map.of(STREET_NAME, new KValue( "MainStreet 2 1"))); + graph.edge(1, 11).set(carAvSpeedEnc, 60, 0).setDistance(5).setKeyValues(Map.of(STREET_NAME, new KValue( "MainStreet 1 11"))); // roundabout EdgeIteratorState tmpEdge; - tmpEdge = graph.edge(3, 9).set(carAvSpeedEnc, 60, 0).setDistance(2).setKeyValues(createKV(STREET_NAME, "3-9")); + tmpEdge = graph.edge(3, 9).set(carAvSpeedEnc, 60, 0).setDistance(2).setKeyValues(Map.of(STREET_NAME, new KValue( "3-9"))); BooleanEncodedValue carManagerRoundabout = carManager.getBooleanEncodedValue(Roundabout.KEY); tmpEdge.set(carManagerRoundabout, true); - tmpEdge = graph.edge(9, 10).set(carAvSpeedEnc, 60, 0).setDistance(2).setKeyValues(createKV(STREET_NAME, "9-10")); + tmpEdge = graph.edge(9, 10).set(carAvSpeedEnc, 60, 0).setDistance(2).setKeyValues(Map.of(STREET_NAME, new KValue( "9-10"))); tmpEdge.set(carManagerRoundabout, true); - tmpEdge = graph.edge(6, 10).set(carAvSpeedEnc, 60, 0).setDistance(2).setKeyValues(createKV(STREET_NAME, "6-10")); + tmpEdge = graph.edge(6, 10).set(carAvSpeedEnc, 60, 0).setDistance(2).setKeyValues(Map.of(STREET_NAME, new KValue( "6-10"))); tmpEdge.set(carManagerRoundabout, true); - tmpEdge = graph.edge(10, 1).set(carAvSpeedEnc, 60, 0).setDistance(2).setKeyValues(createKV(STREET_NAME, "10-1")); + tmpEdge = graph.edge(10, 1).set(carAvSpeedEnc, 60, 0).setDistance(2).setKeyValues(Map.of(STREET_NAME, new KValue( "10-1"))); tmpEdge.set(carManagerRoundabout, true); - tmpEdge = graph.edge(3, 2).set(carAvSpeedEnc, 60, 0).setDistance(5).setKeyValues(createKV(STREET_NAME, "2-3")); + tmpEdge = graph.edge(3, 2).set(carAvSpeedEnc, 60, 0).setDistance(5).setKeyValues(Map.of(STREET_NAME, new KValue( "2-3"))); tmpEdge.set(carManagerRoundabout, true); - tmpEdge = graph.edge(4, 3).set(carAvSpeedEnc, 60, 0).setDistance(5).setKeyValues(createKV(STREET_NAME, "3-4")); + tmpEdge = graph.edge(4, 3).set(carAvSpeedEnc, 60, 0).setDistance(5).setKeyValues(Map.of(STREET_NAME, new KValue( "3-4"))); tmpEdge.set(carManagerRoundabout, true); - tmpEdge = graph.edge(5, 4).set(carAvSpeedEnc, 60, 0).setDistance(5).setKeyValues(createKV(STREET_NAME, "4-5")); + tmpEdge = graph.edge(5, 4).set(carAvSpeedEnc, 60, 0).setDistance(5).setKeyValues(Map.of(STREET_NAME, new KValue( "4-5"))); tmpEdge.set(carManagerRoundabout, true); - tmpEdge = graph.edge(2, 5).set(carAvSpeedEnc, 60, 0).setDistance(5).setKeyValues(createKV(STREET_NAME, "5-2")); + tmpEdge = graph.edge(2, 5).set(carAvSpeedEnc, 60, 0).setDistance(5).setKeyValues(Map.of(STREET_NAME, new KValue( "5-2"))); tmpEdge.set(carManagerRoundabout, true); - graph.edge(4, 7).set(carAvSpeedEnc, 60, 60).setDistance(5).setKeyValues(createKV(STREET_NAME, "MainStreet 4 7")); - graph.edge(5, 8).set(carAvSpeedEnc, 60, 60).setDistance(5).setKeyValues(createKV(STREET_NAME, "5-8")); - graph.edge(3, 6).set(carAvSpeedEnc, 60, 60).setDistance(5).setKeyValues(createKV(STREET_NAME, "3-6")); + graph.edge(4, 7).set(carAvSpeedEnc, 60, 60).setDistance(5).setKeyValues(Map.of(STREET_NAME, new KValue( "MainStreet 4 7"))); + graph.edge(5, 8).set(carAvSpeedEnc, 60, 60).setDistance(5).setKeyValues(Map.of(STREET_NAME, new KValue( "5-8"))); + graph.edge(3, 6).set(carAvSpeedEnc, 60, 60).setDistance(5).setKeyValues(Map.of(STREET_NAME, new KValue( "3-6"))); Weighting weighting = new SpeedWeighting(carAvSpeedEnc); Path p = new Dijkstra(graph, weighting, TraversalMode.NODE_BASED) @@ -635,8 +634,8 @@ public void testCalcInstructionForForkWithSameName() { na.setNode(3, 48.982611, 13.121012); na.setNode(4, 48.982336, 13.121002); - graph.edge(1, 2).set(carAvSpeedEnc, 60, 60).setDistance(5).setKeyValues(createKV(STREET_NAME, "Regener Weg")); - graph.edge(2, 4).set(carAvSpeedEnc, 60, 60).setDistance(5).setKeyValues(createKV(STREET_NAME, "Regener Weg")); + graph.edge(1, 2).set(carAvSpeedEnc, 60, 60).setDistance(5).setKeyValues(Map.of(STREET_NAME, new KValue( "Regener Weg"))); + graph.edge(2, 4).set(carAvSpeedEnc, 60, 60).setDistance(5).setKeyValues(Map.of(STREET_NAME, new KValue( "Regener Weg"))); graph.edge(2, 3).set(carAvSpeedEnc, 60, 60).setDistance(5); Weighting weighting = new SpeedWeighting(carAvSpeedEnc); @@ -668,8 +667,8 @@ public void testCalcInstructionForMotorwayFork() { EnumEncodedValue roadClassEnc = carManager.getEnumEncodedValue(RoadClass.KEY, RoadClass.class); BooleanEncodedValue roadClassLinkEnc = carManager.getBooleanEncodedValue(RoadClassLink.KEY); - graph.edge(1, 2).set(carAvSpeedEnc, 60, 60).setDistance(5).setKeyValues(createKV(STREET_NAME, "A 8")).set(roadClassEnc, RoadClass.MOTORWAY).set(roadClassLinkEnc, false); - graph.edge(2, 4).set(carAvSpeedEnc, 60, 60).setDistance(5).setKeyValues(createKV(STREET_NAME, "A 8")).set(roadClassEnc, RoadClass.MOTORWAY).set(roadClassLinkEnc, false); + graph.edge(1, 2).set(carAvSpeedEnc, 60, 60).setDistance(5).setKeyValues(Map.of(STREET_NAME, new KValue("A 8"))).set(roadClassEnc, RoadClass.MOTORWAY).set(roadClassLinkEnc, false); + graph.edge(2, 4).set(carAvSpeedEnc, 60, 60).setDistance(5).setKeyValues(Map.of(STREET_NAME, new KValue("A 8"))).set(roadClassEnc, RoadClass.MOTORWAY).set(roadClassLinkEnc, false); graph.edge(2, 3).set(carAvSpeedEnc, 60, 60).setDistance(5).set(roadClassEnc, RoadClass.MOTORWAY).set(roadClassLinkEnc, true); Weighting weighting = new SpeedWeighting(carAvSpeedEnc); @@ -696,9 +695,9 @@ public void testCalcInstructionsEnterMotorway() { na.setNode(3, 48.630558, 9.459851); na.setNode(4, 48.63054, 9.459406); - graph.edge(1, 2).set(carAvSpeedEnc, 60, 0).setDistance(5).setKeyValues(createKV(STREET_NAME, "A 8")); - graph.edge(2, 3).set(carAvSpeedEnc, 60, 0).setDistance(5).setKeyValues(createKV(STREET_NAME, "A 8")); - graph.edge(4, 2).set(carAvSpeedEnc, 60, 0).setDistance(5).setKeyValues(createKV(STREET_NAME, "A 8")); + graph.edge(1, 2).set(carAvSpeedEnc, 60, 0).setDistance(5).setKeyValues(Map.of(STREET_NAME, new KValue( "A 8"))); + graph.edge(2, 3).set(carAvSpeedEnc, 60, 0).setDistance(5).setKeyValues(Map.of(STREET_NAME, new KValue( "A 8"))); + graph.edge(4, 2).set(carAvSpeedEnc, 60, 0).setDistance(5).setKeyValues(Map.of(STREET_NAME, new KValue( "A 8"))); Weighting weighting = new SpeedWeighting(carAvSpeedEnc); Path p = new Dijkstra(graph, weighting, TraversalMode.NODE_BASED) @@ -725,9 +724,9 @@ public void testCalcInstructionsMotorwayJunction() { na.setNode(3, 48.706805, 9.162995); na.setNode(4, 48.706705, 9.16329); - g.edge(1, 2).setDistance(5).set(carAvSpeedEnc, 60, 0).setKeyValues(createKV(STREET_NAME, "A 8")); - g.edge(2, 3).setDistance(5).set(carAvSpeedEnc, 60, 0).setKeyValues(createKV(STREET_NAME, "A 8")); - g.edge(2, 4).setDistance(5).set(carAvSpeedEnc, 60, 0).setKeyValues(createKV(STREET_NAME, "A 8")); + g.edge(1, 2).setDistance(5).set(carAvSpeedEnc, 60, 0).setKeyValues(Map.of(STREET_NAME, new KValue( "A 8"))); + g.edge(2, 3).setDistance(5).set(carAvSpeedEnc, 60, 0).setKeyValues(Map.of(STREET_NAME, new KValue( "A 8"))); + g.edge(2, 4).setDistance(5).set(carAvSpeedEnc, 60, 0).setKeyValues(Map.of(STREET_NAME, new KValue( "A 8"))); Weighting weighting = new SpeedWeighting(carAvSpeedEnc); Path p = new Dijkstra(g, weighting, TraversalMode.NODE_BASED) @@ -755,9 +754,9 @@ public void testCalcInstructionsOntoOneway() { na.setNode(3, -33.824415, 151.188177); na.setNode(4, -33.824437, 151.187925); - g.edge(1, 2).setDistance(5).set(carAvSpeedEnc, 60, 0).setKeyValues(createKV(STREET_NAME, "Pacific Highway")); - g.edge(2, 3).setDistance(5).set(carAvSpeedEnc, 60, 0).setKeyValues(createKV(STREET_NAME, "Pacific Highway")); - g.edge(4, 2).setDistance(5).set(carAvSpeedEnc, 60, 60).setKeyValues(createKV(STREET_NAME, "Greenwich Road")); + g.edge(1, 2).setDistance(5).set(carAvSpeedEnc, 60, 0).setKeyValues(Map.of(STREET_NAME, new KValue( "Pacific Highway"))); + g.edge(2, 3).setDistance(5).set(carAvSpeedEnc, 60, 0).setKeyValues(Map.of(STREET_NAME, new KValue( "Pacific Highway"))); + g.edge(4, 2).setDistance(5).set(carAvSpeedEnc, 60, 60).setKeyValues(Map.of(STREET_NAME, new KValue( "Greenwich Road"))); Weighting weighting = new SpeedWeighting(carAvSpeedEnc); Path p = new Dijkstra(g, weighting, TraversalMode.NODE_BASED) @@ -790,9 +789,9 @@ public void testCalcInstructionIssue1047() { EnumEncodedValue roadClassEnc = carManager.getEnumEncodedValue(RoadClass.KEY, RoadClass.class); BooleanEncodedValue roadClassLinkEnc = carManager.getBooleanEncodedValue(RoadClassLink.KEY); - g.edge(1, 2).set(carAvSpeedEnc, 60, 60).setDistance(5).setKeyValues(createKV(STREET_NAME, "B 156")).set(roadClassEnc, RoadClass.PRIMARY).set(roadClassLinkEnc, false); - g.edge(2, 4).set(carAvSpeedEnc, 60, 60).setDistance(5).setKeyValues(createKV(STREET_NAME, "S 108")).set(roadClassEnc, RoadClass.SECONDARY).set(roadClassLinkEnc, false); - g.edge(2, 3).set(carAvSpeedEnc, 60, 60).setDistance(5).setKeyValues(createKV(STREET_NAME, "B 156")).set(roadClassEnc, RoadClass.PRIMARY).set(roadClassLinkEnc, false); + g.edge(1, 2).set(carAvSpeedEnc, 60, 60).setDistance(5).setKeyValues(Map.of(STREET_NAME, new KValue("B 156"))).set(roadClassEnc, RoadClass.PRIMARY).set(roadClassLinkEnc, false); + g.edge(2, 4).set(carAvSpeedEnc, 60, 60).setDistance(5).setKeyValues(Map.of(STREET_NAME, new KValue("S 108"))).set(roadClassEnc, RoadClass.SECONDARY).set(roadClassLinkEnc, false); + g.edge(2, 3).set(carAvSpeedEnc, 60, 60).setDistance(5).setKeyValues(Map.of(STREET_NAME, new KValue("B 156"))).set(roadClassEnc, RoadClass.PRIMARY).set(roadClassLinkEnc, false); Weighting weighting = new SpeedWeighting(carAvSpeedEnc); Path p = new Dijkstra(g, weighting, TraversalMode.NODE_BASED) @@ -824,9 +823,9 @@ public void testCalcInstructionContinueLeavingStreet() { na.setNode(3, 48.982611, 13.121012); na.setNode(4, 48.982565, 13.121002); - g.edge(1, 2).set(carAvSpeedEnc, 60, 60).setDistance(5).setKeyValues(createKV(STREET_NAME, "Regener Weg")); + g.edge(1, 2).set(carAvSpeedEnc, 60, 60).setDistance(5).setKeyValues(Map.of(STREET_NAME, new KValue( "Regener Weg"))); g.edge(2, 4).set(carAvSpeedEnc, 60, 60).setDistance(5); - g.edge(2, 3).set(carAvSpeedEnc, 60, 60).setDistance(5).setKeyValues(createKV(STREET_NAME, "Regener Weg")); + g.edge(2, 3).set(carAvSpeedEnc, 60, 60).setDistance(5).setKeyValues(Map.of(STREET_NAME, new KValue( "Regener Weg"))); Weighting weighting = new SpeedWeighting(carAvSpeedEnc); Path p = new Dijkstra(g, weighting, TraversalMode.NODE_BASED) @@ -854,9 +853,9 @@ public void testCalcInstructionSlightTurn() { na.setNode(3, 48.412034, 15.599411); na.setNode(4, 48.411927, 15.599197); - g.edge(1, 2).set(carAvSpeedEnc, 60, 60).setDistance(5).setKeyValues(createKV(STREET_NAME, "Stöhrgasse")); + g.edge(1, 2).set(carAvSpeedEnc, 60, 60).setDistance(5).setKeyValues(Map.of(STREET_NAME, new KValue( "Stöhrgasse"))); g.edge(2, 3).set(carAvSpeedEnc, 60, 60).setDistance(5); - g.edge(2, 4).set(carAvSpeedEnc, 60, 60).setDistance(5).setKeyValues(createKV(STREET_NAME, "Stöhrgasse")); + g.edge(2, 4).set(carAvSpeedEnc, 60, 60).setDistance(5).setKeyValues(Map.of(STREET_NAME, new KValue( "Stöhrgasse"))); Weighting weighting = new SpeedWeighting(carAvSpeedEnc); Path p = new Dijkstra(g, weighting, TraversalMode.NODE_BASED) @@ -887,12 +886,12 @@ public void testUTurnLeft() { na.setNode(6, 48.402422, 9.996067); na.setNode(7, 48.402604, 9.994962); - g.edge(1, 2).set(carAvSpeedEnc, 60, 0).setDistance(5).setKeyValues(createKV(STREET_NAME, "Olgastraße")); - g.edge(2, 3).set(carAvSpeedEnc, 60, 0).setDistance(5).setKeyValues(createKV(STREET_NAME, "Olgastraße")); - g.edge(6, 5).set(carAvSpeedEnc, 60, 0).setDistance(5).setKeyValues(createKV(STREET_NAME, "Olgastraße")); - g.edge(5, 4).set(carAvSpeedEnc, 60, 0).setDistance(5).setKeyValues(createKV(STREET_NAME, "Olgastraße")); - g.edge(2, 5).set(carAvSpeedEnc, 60, 60).setDistance(5).setKeyValues(createKV(STREET_NAME, "Neithardtstraße")); - g.edge(5, 7).set(carAvSpeedEnc, 60, 60).setDistance(5).setKeyValues(createKV(STREET_NAME, "Neithardtstraße")); + g.edge(1, 2).set(carAvSpeedEnc, 60, 0).setDistance(5).setKeyValues(Map.of(STREET_NAME, new KValue( "Olgastraße"))); + g.edge(2, 3).set(carAvSpeedEnc, 60, 0).setDistance(5).setKeyValues(Map.of(STREET_NAME, new KValue( "Olgastraße"))); + g.edge(6, 5).set(carAvSpeedEnc, 60, 0).setDistance(5).setKeyValues(Map.of(STREET_NAME, new KValue( "Olgastraße"))); + g.edge(5, 4).set(carAvSpeedEnc, 60, 0).setDistance(5).setKeyValues(Map.of(STREET_NAME, new KValue( "Olgastraße"))); + g.edge(2, 5).set(carAvSpeedEnc, 60, 60).setDistance(5).setKeyValues(Map.of(STREET_NAME, new KValue( "Neithardtstraße"))); + g.edge(5, 7).set(carAvSpeedEnc, 60, 60).setDistance(5).setKeyValues(Map.of(STREET_NAME, new KValue( "Neithardtstraße"))); Weighting weighting = new SpeedWeighting(carAvSpeedEnc); Path p = new Dijkstra(g, weighting, TraversalMode.NODE_BASED) @@ -923,12 +922,12 @@ public void testUTurnRight() { na.setNode(6, -33.885692, 151.181445); na.setNode(7, -33.885692, 151.181445); - g.edge(1, 2).set(carAvSpeedEnc, 60, 0).setDistance(5).setKeyValues(createKV(STREET_NAME, "Parramatta Road")); - g.edge(2, 3).set(carAvSpeedEnc, 60, 0).setDistance(5).setKeyValues(createKV(STREET_NAME, "Parramatta Road")); - g.edge(4, 5).set(carAvSpeedEnc, 60, 0).setDistance(5).setKeyValues(createKV(STREET_NAME, "Parramatta Road")); - g.edge(5, 6).set(carAvSpeedEnc, 60, 0).setDistance(5).setKeyValues(createKV(STREET_NAME, "Parramatta Road")); - g.edge(2, 5).set(carAvSpeedEnc, 60, 60).setDistance(5).setKeyValues(createKV(STREET_NAME, "Larkin Street")); - g.edge(5, 7).set(carAvSpeedEnc, 60, 60).setDistance(5).setKeyValues(createKV(STREET_NAME, "Larkin Street")); + g.edge(1, 2).set(carAvSpeedEnc, 60, 0).setDistance(5).setKeyValues(Map.of(STREET_NAME, new KValue( "Parramatta Road"))); + g.edge(2, 3).set(carAvSpeedEnc, 60, 0).setDistance(5).setKeyValues(Map.of(STREET_NAME, new KValue( "Parramatta Road"))); + g.edge(4, 5).set(carAvSpeedEnc, 60, 0).setDistance(5).setKeyValues(Map.of(STREET_NAME, new KValue( "Parramatta Road"))); + g.edge(5, 6).set(carAvSpeedEnc, 60, 0).setDistance(5).setKeyValues(Map.of(STREET_NAME, new KValue( "Parramatta Road"))); + g.edge(2, 5).set(carAvSpeedEnc, 60, 60).setDistance(5).setKeyValues(Map.of(STREET_NAME, new KValue( "Larkin Street"))); + g.edge(5, 7).set(carAvSpeedEnc, 60, 60).setDistance(5).setKeyValues(Map.of(STREET_NAME, new KValue( "Larkin Street"))); Weighting weighting = new SpeedWeighting(carAvSpeedEnc); Path p = new Dijkstra(g, weighting, TraversalMode.NODE_BASED) @@ -988,9 +987,9 @@ public void testCalcInstructionsForSlightTurnOntoDifferentStreet() { na.setNode(3, 48.764149, 8.678926); na.setNode(4, 48.764085, 8.679183); - g.edge(1, 3).setDistance(5).set(carAvSpeedEnc, 60, 60).setKeyValues(createKV(STREET_NAME, "Talstraße, K 4313")); - g.edge(2, 3).setDistance(5).set(carAvSpeedEnc, 60, 60).setKeyValues(createKV(STREET_NAME, "Calmbacher Straße, K 4312")); - g.edge(3, 4).setDistance(5).set(carAvSpeedEnc, 60, 60).setKeyValues(createKV(STREET_NAME, "Calmbacher Straße, K 4312")); + g.edge(1, 3).setDistance(5).set(carAvSpeedEnc, 60, 60).setKeyValues(Map.of(STREET_NAME, new KValue("Talstraße, new KValue( K 4313"))); + g.edge(2, 3).setDistance(5).set(carAvSpeedEnc, 60, 60).setKeyValues(Map.of(STREET_NAME, new KValue("Calmbacher Straße, new KValue( K 4312"))); + g.edge(3, 4).setDistance(5).set(carAvSpeedEnc, 60, 60).setKeyValues(Map.of(STREET_NAME, new KValue("Calmbacher Straße, new KValue( K 4312"))); Weighting weighting = new SpeedWeighting(carAvSpeedEnc); Path p = new Dijkstra(g, weighting, TraversalMode.NODE_BASED) @@ -1034,11 +1033,11 @@ private Graph generatePathDetailsGraph() { na.setNode(5, 52.516, 13.3452); na.setNode(6, 52.516, 13.344); - graph.edge(1, 2).set(carAvSpeedEnc, 45, 45).setDistance(5).setKeyValues(createKV(STREET_NAME, "1-2")); - graph.edge(4, 5).set(carAvSpeedEnc, 45, 45).setDistance(5).setKeyValues(createKV(STREET_NAME, "4-5")); - graph.edge(2, 3).set(carAvSpeedEnc, 90, 90).setDistance(5).setKeyValues(createKV(STREET_NAME, "2-3")); - graph.edge(3, 4).set(carAvSpeedEnc, 9, 9).setDistance(10).setKeyValues(createKV(STREET_NAME, "3-4")); - graph.edge(5, 6).set(carAvSpeedEnc, 9, 9).setDistance(0.001).setKeyValues(createKV(STREET_NAME, "3-4")); + graph.edge(1, 2).set(carAvSpeedEnc, 45, 45).setDistance(5).setKeyValues(Map.of(STREET_NAME, new KValue( "1-2"))); + graph.edge(4, 5).set(carAvSpeedEnc, 45, 45).setDistance(5).setKeyValues(Map.of(STREET_NAME, new KValue( "4-5"))); + graph.edge(2, 3).set(carAvSpeedEnc, 90, 90).setDistance(5).setKeyValues(Map.of(STREET_NAME, new KValue( "2-3"))); + graph.edge(3, 4).set(carAvSpeedEnc, 9, 9).setDistance(10).setKeyValues(Map.of(STREET_NAME, new KValue( "3-4"))); + graph.edge(5, 6).set(carAvSpeedEnc, 9, 9).setDistance(0.001).setKeyValues(Map.of(STREET_NAME, new KValue( "3-4"))); return graph; } @@ -1084,20 +1083,20 @@ private RoundaboutGraph() { na.setNode(19, 52.515, 13.368); // roundabout - roundaboutEdges.add(g.edge(3, 2).setDistance(5).setKeyValues(createKV(STREET_NAME, "2-3"))); - roundaboutEdges.add(g.edge(4, 3).setDistance(5).setKeyValues(createKV(STREET_NAME, "3-4"))); - roundaboutEdges.add(g.edge(5, 4).setDistance(5).setKeyValues(createKV(STREET_NAME, "4-5"))); - roundaboutEdges.add(g.edge(2, 5).setDistance(5).setKeyValues(createKV(STREET_NAME, "5-2"))); + roundaboutEdges.add(g.edge(3, 2).setDistance(5).setKeyValues(Map.of(STREET_NAME, new KValue( "2-3")))); + roundaboutEdges.add(g.edge(4, 3).setDistance(5).setKeyValues(Map.of(STREET_NAME, new KValue( "3-4")))); + roundaboutEdges.add(g.edge(5, 4).setDistance(5).setKeyValues(Map.of(STREET_NAME, new KValue( "4-5")))); + roundaboutEdges.add(g.edge(2, 5).setDistance(5).setKeyValues(Map.of(STREET_NAME, new KValue( "5-2")))); List bothDir = new ArrayList<>(); List oneDir = new ArrayList<>(roundaboutEdges); - bothDir.add(g.edge(1, 2).setDistance(5).setKeyValues(createKV(STREET_NAME, "MainStreet 1 2"))); - bothDir.add(g.edge(4, 7).setDistance(5).setKeyValues(createKV(STREET_NAME, "MainStreet 4 7"))); - bothDir.add(g.edge(5, 8).setDistance(5).setKeyValues(createKV(STREET_NAME, "5-8"))); + bothDir.add(g.edge(1, 2).setDistance(5).setKeyValues(Map.of(STREET_NAME, new KValue( "MainStreet 1 2")))); + bothDir.add(g.edge(4, 7).setDistance(5).setKeyValues(Map.of(STREET_NAME, new KValue( "MainStreet 4 7")))); + bothDir.add(g.edge(5, 8).setDistance(5).setKeyValues(Map.of(STREET_NAME, new KValue( "5-8")))); - bothDir.add(edge3to6 = g.edge(3, 6).setDistance(5).setKeyValues(createKV(STREET_NAME, "3-6"))); - oneDir.add(edge3to9 = g.edge(3, 9).setDistance(5).setKeyValues(createKV(STREET_NAME, "3-9"))); + bothDir.add(edge3to6 = g.edge(3, 6).setDistance(5).setKeyValues(Map.of(STREET_NAME, new KValue( "3-6")))); + oneDir.add(edge3to9 = g.edge(3, 9).setDistance(5).setKeyValues(Map.of(STREET_NAME, new KValue( "3-9")))); bothDir.add(g.edge(7, 10).setDistance(5)); bothDir.add(g.edge(10, 11).setDistance(5)); diff --git a/core/src/test/java/com/graphhopper/routing/RoutingAlgorithmTest.java b/core/src/test/java/com/graphhopper/routing/RoutingAlgorithmTest.java index f9a0ef429bf..0404d2b520e 100644 --- a/core/src/test/java/com/graphhopper/routing/RoutingAlgorithmTest.java +++ b/core/src/test/java/com/graphhopper/routing/RoutingAlgorithmTest.java @@ -1061,18 +1061,18 @@ private void initEleGraph(Graph graph, double s, DecimalEncodedValue speedEnc) { graph.edge(8, 9).setDistance(10).set(speedEnc, s, 0); graph.edge(9, 8).setDistance(9).set(speedEnc, s, 0); graph.edge(10, 9).setDistance(10).set(speedEnc, s, 0); - updateDistancesFor(graph, 0, 3, 0); - updateDistancesFor(graph, 3, 2.5, 0); - updateDistancesFor(graph, 5, 1, 0); - updateDistancesFor(graph, 8, 0, 0); - updateDistancesFor(graph, 1, 3, 1); - updateDistancesFor(graph, 4, 2, 1); - updateDistancesFor(graph, 6, 1, 1); - updateDistancesFor(graph, 9, 0, 1); - updateDistancesFor(graph, 2, 3, 2); - updateDistancesFor(graph, 11, 2, 2); - updateDistancesFor(graph, 7, 1, 2); - updateDistancesFor(graph, 10, 0, 2); + updateDistancesFor(graph, 0, 3, 0, 0); + updateDistancesFor(graph, 3, 2.5, 0, 0); + updateDistancesFor(graph, 5, 1, 0, 0); + updateDistancesFor(graph, 8, 0, 0, 0); + updateDistancesFor(graph, 1, 3, 1, 0); + updateDistancesFor(graph, 4, 2, 1, 0); + updateDistancesFor(graph, 6, 1, 1, 0); + updateDistancesFor(graph, 9, 0, 1, 0); + updateDistancesFor(graph, 2, 3, 2, 0); + updateDistancesFor(graph, 11, 2, 2, 0); + updateDistancesFor(graph, 7, 1, 2, 0); + updateDistancesFor(graph, 10, 0, 2, 0); } private static String getCHGraphName(Weighting weighting) { diff --git a/core/src/test/java/com/graphhopper/routing/ch/CHPreparationGraphTest.java b/core/src/test/java/com/graphhopper/routing/ch/CHPreparationGraphTest.java index a23638ed5ee..0f538520a9d 100644 --- a/core/src/test/java/com/graphhopper/routing/ch/CHPreparationGraphTest.java +++ b/core/src/test/java/com/graphhopper/routing/ch/CHPreparationGraphTest.java @@ -58,8 +58,8 @@ void basic() { @Test void useLargeEdgeId() { CHPreparationGraph.OrigGraph.Builder builder = new CHPreparationGraph.OrigGraph.Builder(); - int largeEdgeID = Integer.MAX_VALUE >> 2; - assertEquals(536_870_911, largeEdgeID); + int largeEdgeID = Integer.MAX_VALUE >> 1; + assertEquals(1_073_741_823, largeEdgeID); // 0->1 builder.addEdge(0, 1, largeEdgeID, true, false); CHPreparationGraph.OrigGraph g = builder.build(); @@ -72,6 +72,6 @@ void useLargeEdgeId() { IllegalArgumentException e = assertThrows(IllegalArgumentException.class, () -> new CHPreparationGraph.OrigGraph.Builder().addEdge(0, 1, largeEdgeID + 1, true, false) ); - assertTrue(e.getMessage().contains("Maximum edge key exceeded: 1073741824, max: 1073741823"), e.getMessage()); + assertTrue(e.getMessage().contains("Maximum node or edge key exceeded: -2147483648, max: 2147483647"), e.getMessage()); } -} \ No newline at end of file +} diff --git a/core/src/test/java/com/graphhopper/routing/querygraph/QueryGraphTest.java b/core/src/test/java/com/graphhopper/routing/querygraph/QueryGraphTest.java index a884105e9f7..8857cb02c99 100644 --- a/core/src/test/java/com/graphhopper/routing/querygraph/QueryGraphTest.java +++ b/core/src/test/java/com/graphhopper/routing/querygraph/QueryGraphTest.java @@ -40,6 +40,7 @@ import java.util.stream.IntStream; import static com.graphhopper.storage.index.Snap.Position.*; +import static com.graphhopper.util.DistancePlaneProjection.DIST_PLANE; import static com.graphhopper.util.EdgeIteratorState.UNFAVORED_EDGE; import static com.graphhopper.util.GHUtility.updateDistancesFor; import static org.junit.jupiter.api.Assertions.*; @@ -1037,9 +1038,9 @@ public void directedKeyValues() { NodeAccess na = g.getNodeAccess(); na.setNode(0, 1, 0); na.setNode(1, 1, 2.5); - ArrayList kvs = new ArrayList<>(); - kvs.add(new KVStorage.KeyValue("a", "hello", true, false)); - kvs.add(new KVStorage.KeyValue("b", "world", false, true)); + Map kvs = new HashMap<>(); + kvs.put("a", new KVStorage.KValue("hello", null)); + kvs.put("b", new KVStorage.KValue(null, "world")); EdgeIteratorState origEdge = g.edge(0, 1).setDistance(10).set(speedEnc, 60, 60).setKeyValues(kvs); // keyValues List stays the same @@ -1066,6 +1067,51 @@ public void directedKeyValues() { assertNull(edge0ToSnap.getValue("b")); } + @Test + void veryShortEdge() { + EdgeIteratorState e = g.edge(0, 1); + NodeAccess na = g.getNodeAccess(); + na.setNode(0, 40.000_000, 6.000_000); + na.setNode(1, 40.000_000, 6.000_001); + double edgeDist = DIST_PLANE.calcDist(na.getLat(0), na.getLon(0), na.getLat(1), na.getLon(1)); + // the edge is very short + assertEquals(0.085, edgeDist, 1.e-3); + double queryLat = 40.001_000; + double queryLon = 6.000_0009; + double queryTo0 = DIST_PLANE.calcDist(queryLat, queryLon, na.getLat(0), na.getLon(0)); + double queryTo1 = DIST_PLANE.calcDist(queryLat, queryLon, na.getLat(1), na.getLon(1)); + // the query point is relatively far away from the edge + assertEquals(111.1949530, queryTo0, 1.e-7); + assertEquals(111.1949269, queryTo1, 1.e-7); + GHPoint crossingPoint = DIST_PLANE.calcCrossingPointToEdge(queryLat, queryLon, na.getLat(0), na.getLon(0), na.getLat(1), na.getLon(1)); + double distCrossingTo0 = DIST_PLANE.calcDist(crossingPoint.lat, crossingPoint.lon, na.getLat(0), na.getLon(0)); + double distCrossingTo1 = DIST_PLANE.calcDist(crossingPoint.lat, crossingPoint.lon, na.getLat(1), na.getLon(1)); + // ... but the crossing point is very close to both nodes of the edge + assertEquals(0.0766, distCrossingTo0, 1.e-4); + assertEquals(0.0085, distCrossingTo1, 1.e-4); + // ... and closer to node 1 than to node 0 + assertTrue(distCrossingTo1 < distCrossingTo0); + + LocationIndexTree index = new LocationIndexTree(g, new RAMDirectory()); + index.prepareIndex(); + Snap snap = index.findClosest(queryLat, queryLon, EdgeFilter.ALL_EDGES); + // Although this is technically an 'edge-snap', we snap to the tower node, because the **crossing** point + // is so close to a tower node (in our case it is even close to both tower nodes). + assertEquals(TOWER, snap.getSnappedPosition()); + // We do not enforce that the closer of the two tower nodes is chosen. It does not really matter. + // Here it is node 0, because we first try the base node. + int closestNode = snap.getClosestNode(); + assertEquals(0, closestNode); + // ... but what does matter is that the coordinates of the snapped point match the coordinates of the closest node! + // This isn't entirely obvious here, because `index.findClosest` first considers the snap an edge snap and only + // later updates it to a tower snap. See #3009 + assertEquals(na.getLat(closestNode), snap.getSnappedPoint().getLat()); + assertEquals(na.getLon(closestNode), snap.getSnappedPoint().getLon()); + // also the distance should be correct + assertEquals(queryTo0, snap.getQueryDistance()); + assertEquals(0, snap.getWayIndex()); + } + private QueryGraph lookup(Snap res) { return lookup(Collections.singletonList(res)); } diff --git a/core/src/test/java/com/graphhopper/routing/subnetwork/EdgeBasedTarjanSCCTest.java b/core/src/test/java/com/graphhopper/routing/subnetwork/EdgeBasedTarjanSCCTest.java index 5f5c36feafb..4f7cb048ca1 100644 --- a/core/src/test/java/com/graphhopper/routing/subnetwork/EdgeBasedTarjanSCCTest.java +++ b/core/src/test/java/com/graphhopper/routing/subnetwork/EdgeBasedTarjanSCCTest.java @@ -48,7 +48,7 @@ public EdgeBasedTarjanSCCTest() { speedEnc = new DecimalEncodedValueImpl("speed", 5, 5, true); EncodedValue.InitializerConfig evConf = new EncodedValue.InitializerConfig(); speedEnc.init(evConf); - g = new BaseGraph.Builder(evConf.getRequiredInts()).create(); + g = new BaseGraph.Builder(evConf.getRequiredBytes()).create(); fwdAccessFilter = (prev, edge) -> edge.get(speedEnc) > 0; } diff --git a/core/src/test/java/com/graphhopper/routing/subnetwork/TarjanSCCTest.java b/core/src/test/java/com/graphhopper/routing/subnetwork/TarjanSCCTest.java index c26c759896a..6c6f37e9ac3 100644 --- a/core/src/test/java/com/graphhopper/routing/subnetwork/TarjanSCCTest.java +++ b/core/src/test/java/com/graphhopper/routing/subnetwork/TarjanSCCTest.java @@ -43,7 +43,7 @@ public TarjanSCCTest() { speedEnc = new DecimalEncodedValueImpl("speed", 5, 5, true); EncodedValue.InitializerConfig evConf = new EncodedValue.InitializerConfig(); speedEnc.init(evConf); - graph = new BaseGraph.Builder(evConf.getRequiredInts()).create(); + graph = new BaseGraph.Builder(evConf.getRequiredBytes()).create(); edgeFilter = edge -> edge.get(speedEnc) > 0; } diff --git a/core/src/test/java/com/graphhopper/routing/util/CurvatureCalculatorTest.java b/core/src/test/java/com/graphhopper/routing/util/CurvatureCalculatorTest.java index 3405025ac6d..c087d606f14 100644 --- a/core/src/test/java/com/graphhopper/routing/util/CurvatureCalculatorTest.java +++ b/core/src/test/java/com/graphhopper/routing/util/CurvatureCalculatorTest.java @@ -15,12 +15,12 @@ class CurvatureCalculatorTest { @Test public void testCurvature() { CurvatureCalculator calculator = new CurvatureCalculator(em.getDecimalEncodedValue(Curvature.KEY)); - ArrayEdgeIntAccess intAccess = new ArrayEdgeIntAccess(em.getIntsForFlags()); + ArrayEdgeIntAccess intAccess = ArrayEdgeIntAccess.createFromBytes(em.getBytesForFlags()); int edgeId = 0; calculator.handleWayTags(edgeId, intAccess, getStraightWay(), null); double valueStraight = em.getDecimalEncodedValue(Curvature.KEY).getDecimal(false, edgeId, intAccess); - intAccess = new ArrayEdgeIntAccess(em.getIntsForFlags()); + intAccess = ArrayEdgeIntAccess.createFromBytes(em.getBytesForFlags()); calculator.handleWayTags(edgeId, intAccess, getCurvyWay(), null); double valueCurvy = em.getDecimalEncodedValue(Curvature.KEY).getDecimal(false, edgeId, intAccess); @@ -50,4 +50,4 @@ private ReaderWay getCurvyWay() { return way; } -} \ No newline at end of file +} diff --git a/core/src/test/java/com/graphhopper/routing/util/MaxSpeedCalculatorTest.java b/core/src/test/java/com/graphhopper/routing/util/MaxSpeedCalculatorTest.java index e5986186279..d3e9661a5ec 100644 --- a/core/src/test/java/com/graphhopper/routing/util/MaxSpeedCalculatorTest.java +++ b/core/src/test/java/com/graphhopper/routing/util/MaxSpeedCalculatorTest.java @@ -75,7 +75,7 @@ public void internalMaxSpeed() { EdgeIteratorState createEdge(ReaderWay way) { EdgeIteratorState edge = graph.edge(0, 1); - EdgeIntAccess edgeIntAccess = graph.createEdgeIntAccess(); + EdgeIntAccess edgeIntAccess = graph.getEdgeAccess(); parsers.handleWayTags(edge.getEdge(), edgeIntAccess, way, em.createRelationFlags()); return edge; } diff --git a/core/src/test/java/com/graphhopper/routing/util/NameSimilarityEdgeFilterTest.java b/core/src/test/java/com/graphhopper/routing/util/NameSimilarityEdgeFilterTest.java index 32e328c3342..4c954803ed7 100644 --- a/core/src/test/java/com/graphhopper/routing/util/NameSimilarityEdgeFilterTest.java +++ b/core/src/test/java/com/graphhopper/routing/util/NameSimilarityEdgeFilterTest.java @@ -28,8 +28,10 @@ import com.graphhopper.util.shapes.GHPoint; import org.junit.jupiter.api.Test; -import static com.graphhopper.search.KVStorage.KeyValue.STREET_NAME; -import static com.graphhopper.search.KVStorage.KeyValue.createKV; +import java.util.Map; + +import static com.graphhopper.search.KVStorage.KValue; +import static com.graphhopper.util.Parameters.Details.STREET_NAME; import static org.junit.jupiter.api.Assertions.*; /** @@ -105,11 +107,11 @@ public void testDistanceFiltering() { na.setNode(nodeID200, point200mAway.lat, point200mAway.lon); // Check that it matches a street 50m away - EdgeIteratorState edge1 = g.edge(nodeId50, farAwayId).setKeyValues(createKV(STREET_NAME, "Wentworth Street")); + EdgeIteratorState edge1 = g.edge(nodeId50, farAwayId).setKeyValues(Map.of(STREET_NAME, new KValue("Wentworth Street"))); assertTrue(createNameSimilarityEdgeFilter("Wentworth Street").accept(edge1)); // Check that it doesn't match streets 200m away - EdgeIteratorState edge2 = g.edge(nodeID200, farAwayId).setKeyValues(createKV(STREET_NAME, "Wentworth Street")); + EdgeIteratorState edge2 = g.edge(nodeID200, farAwayId).setKeyValues(Map.of(STREET_NAME, new KValue("Wentworth Street"))); assertFalse(createNameSimilarityEdgeFilter("Wentworth Street").accept(edge2)); } @@ -277,16 +279,16 @@ public void curvedWayGeometry_issue2319() { graph.getNodeAccess().setNode(1, 43.842775, -79.264649); EdgeIteratorState doubtfire = graph.edge(0, 1).setWayGeometry(pointList).set(accessEnc, true, true). - set(speedEnc, 60, 60).setKeyValues(createKV(STREET_NAME, "Doubtfire Crescent")); + set(speedEnc, 60, 60).setKeyValues(Map.of(STREET_NAME, new KValue("Doubtfire Crescent"))); EdgeIteratorState golden = graph.edge(0, 1).set(accessEnc, true, true).set(speedEnc, 60, 60). - setKeyValues(createKV(STREET_NAME, "Golden Avenue")); + setKeyValues(Map.of(STREET_NAME, new KValue("Golden Avenue"))); graph.getNodeAccess().setNode(2, 43.841501560244744, -79.26366394602502); graph.getNodeAccess().setNode(3, 43.842247922172724, -79.2605663670726); PointList pointList2 = new PointList(1, false); pointList2.add(43.84191413615452, -79.261912128223); EdgeIteratorState denison = graph.edge(2, 3).setWayGeometry(pointList2).set(accessEnc, true, true). - set(speedEnc, 60, 60).setKeyValues(createKV(STREET_NAME, "Denison Street")); + set(speedEnc, 60, 60).setKeyValues(Map.of(STREET_NAME, new KValue("Denison Street"))); double qlat = 43.842122; double qLon = -79.262162; @@ -310,7 +312,7 @@ private EdgeIteratorState createTestEdgeIterator(String name) { EdgeIteratorState edge = new BaseGraph.Builder(1).create().edge(0, 1) .setWayGeometry(pointList); if (name != null) - edge.setKeyValues(KVStorage.KeyValue.createKV(KVStorage.KeyValue.STREET_NAME, name)); + edge.setKeyValues(Map.of(STREET_NAME, new KValue(name))); return edge; } diff --git a/core/src/test/java/com/graphhopper/routing/util/parsers/AbstractBikeTagParserTester.java b/core/src/test/java/com/graphhopper/routing/util/parsers/AbstractBikeTagParserTester.java index 619f534f4bb..b4d51598a3d 100644 --- a/core/src/test/java/com/graphhopper/routing/util/parsers/AbstractBikeTagParserTester.java +++ b/core/src/test/java/com/graphhopper/routing/util/parsers/AbstractBikeTagParserTester.java @@ -76,7 +76,7 @@ public void setUp() { protected void assertPriority(PriorityCode expectedPrio, ReaderWay way) { IntsRef relFlags = osmParsers.handleRelationTags(new ReaderRelation(0), osmParsers.createRelationFlags()); - ArrayEdgeIntAccess intAccess = new ArrayEdgeIntAccess(encodingManager.getIntsForFlags()); + ArrayEdgeIntAccess intAccess = ArrayEdgeIntAccess.createFromBytes(encodingManager.getBytesForFlags()); int edgeId = 0; osmParsers.handleWayTags(edgeId, intAccess, way, relFlags); assertEquals(PriorityCode.getValue(expectedPrio.getValue()), priorityEnc.getDecimal(false, edgeId, intAccess), 0.01); @@ -88,7 +88,7 @@ protected void assertPriorityAndSpeed(PriorityCode expectedPrio, double expected protected void assertPriorityAndSpeed(PriorityCode expectedPrio, double expectedSpeed, ReaderWay way, ReaderRelation rel) { IntsRef relFlags = osmParsers.handleRelationTags(rel, osmParsers.createRelationFlags()); - ArrayEdgeIntAccess intAccess = new ArrayEdgeIntAccess(encodingManager.getIntsForFlags()); + ArrayEdgeIntAccess intAccess = ArrayEdgeIntAccess.createFromBytes(encodingManager.getBytesForFlags()); int edgeId = 0; osmParsers.handleWayTags(edgeId, intAccess, way, relFlags); assertEquals(PriorityCode.getValue(expectedPrio.getValue()), priorityEnc.getDecimal(false, edgeId, intAccess), 0.01); @@ -98,7 +98,7 @@ protected void assertPriorityAndSpeed(PriorityCode expectedPrio, double expected protected double getSpeedFromFlags(ReaderWay way) { IntsRef relFlags = osmParsers.createRelationFlags(); - ArrayEdgeIntAccess intAccess = new ArrayEdgeIntAccess(encodingManager.getIntsForFlags()); + ArrayEdgeIntAccess intAccess = ArrayEdgeIntAccess.createFromBytes(encodingManager.getBytesForFlags()); int edgeId = 0; osmParsers.handleWayTags(edgeId, intAccess, way, relFlags); return avgSpeedEnc.getDecimal(false, edgeId, intAccess); @@ -249,7 +249,7 @@ public void testRelation() { // two relation tags => we currently cannot store a list, so pick the lower ordinal 'regional' // Example https://www.openstreetmap.org/way/213492914 => two hike 84544, 2768803 and two bike relations 3162932, 5254650 IntsRef relFlags = osmParsers.handleRelationTags(rel2, osmParsers.handleRelationTags(rel, osmParsers.createRelationFlags())); - ArrayEdgeIntAccess intAccess = new ArrayEdgeIntAccess(encodingManager.getIntsForFlags()); + ArrayEdgeIntAccess intAccess = ArrayEdgeIntAccess.createFromBytes(encodingManager.getBytesForFlags()); int edgeId = 0; osmParsers.handleWayTags(edgeId, intAccess, way, relFlags); EnumEncodedValue enc = encodingManager.getEnumEncodedValue(RouteNetwork.key("bike"), RouteNetwork.class); @@ -279,7 +279,7 @@ public void testTramStations() { way = new ReaderWay(1); way.setTag("railway", "platform"); - ArrayEdgeIntAccess intAccess = new ArrayEdgeIntAccess(encodingManager.getIntsForFlags()); + ArrayEdgeIntAccess intAccess = ArrayEdgeIntAccess.createFromBytes(encodingManager.getBytesForFlags()); int edgeId = 0; accessParser.handleWayTags(edgeId, intAccess, way, null); speedParser.handleWayTags(edgeId, intAccess, way, null); @@ -300,7 +300,7 @@ public void testTramStations() { way.setTag("railway", "platform"); way.setTag("bicycle", "no"); - intAccess = new ArrayEdgeIntAccess(encodingManager.getIntsForFlags()); + intAccess = ArrayEdgeIntAccess.createFromBytes(encodingManager.getBytesForFlags()); accessParser.handleWayTags(edgeId, intAccess, way); assertEquals(0.0, avgSpeedEnc.getDecimal(false, edgeId, intAccess)); assertFalse(accessEnc.getBool(false, edgeId, intAccess)); @@ -387,7 +387,7 @@ public void testHandleWayTagsCallsHandlePriority() { ReaderWay osmWay = new ReaderWay(1); osmWay.setTag("highway", "cycleway"); - ArrayEdgeIntAccess intAccess = new ArrayEdgeIntAccess(encodingManager.getIntsForFlags()); + ArrayEdgeIntAccess intAccess = ArrayEdgeIntAccess.createFromBytes(encodingManager.getBytesForFlags()); int edgeId = 0; priorityParser.handleWayTags(edgeId, intAccess, osmWay, null); assertEquals(PriorityCode.getValue(VERY_NICE.getValue()), priorityEnc.getDecimal(false, edgeId, intAccess), 1e-3); diff --git a/core/src/test/java/com/graphhopper/routing/util/parsers/BikeTagParserTest.java b/core/src/test/java/com/graphhopper/routing/util/parsers/BikeTagParserTest.java index 39a9d830553..1799cb315ca 100644 --- a/core/src/test/java/com/graphhopper/routing/util/parsers/BikeTagParserTest.java +++ b/core/src/test/java/com/graphhopper/routing/util/parsers/BikeTagParserTest.java @@ -495,7 +495,7 @@ public void testCalcPriority() { osmRel.setTag("route", "bicycle"); osmRel.setTag("network", "icn"); IntsRef relFlags = osmParsers.handleRelationTags(osmRel, osmParsers.createRelationFlags()); - EdgeIntAccess edgeIntAccess = new ArrayEdgeIntAccess(encodingManager.getIntsForFlags()); + EdgeIntAccess edgeIntAccess = ArrayEdgeIntAccess.createFromBytes(encodingManager.getBytesForFlags()); int edgeId = 0; osmParsers.handleWayTags(edgeId, edgeIntAccess, osmWay, relFlags); assertEquals(RouteNetwork.INTERNATIONAL, encodingManager.getEnumEncodedValue(BikeNetwork.KEY, RouteNetwork.class).getEnum(false, edgeId, edgeIntAccess)); @@ -505,7 +505,7 @@ public void testCalcPriority() { osmRel = new ReaderRelation(1); osmWay = new ReaderWay(1); osmWay.setTag("highway", "track"); - edgeIntAccess = new ArrayEdgeIntAccess(encodingManager.getIntsForFlags()); + edgeIntAccess = ArrayEdgeIntAccess.createFromBytes(encodingManager.getBytesForFlags()); osmParsers.handleWayTags(edgeId, edgeIntAccess, osmWay, osmParsers.createRelationFlags()); assertEquals(RouteNetwork.MISSING, encodingManager.getEnumEncodedValue(BikeNetwork.KEY, RouteNetwork.class).getEnum(false, edgeId, edgeIntAccess)); assertEquals(PriorityCode.getValue(UNCHANGED.getValue()), priorityEnc.getDecimal(false, edgeId, edgeIntAccess), .1); @@ -513,7 +513,7 @@ public void testCalcPriority() { // unknown highway tags will be excluded but priority will be unchanged osmWay = new ReaderWay(1); osmWay.setTag("highway", "whatever"); - edgeIntAccess = new ArrayEdgeIntAccess(encodingManager.getIntsForFlags()); + edgeIntAccess = ArrayEdgeIntAccess.createFromBytes(encodingManager.getBytesForFlags()); osmParsers.handleWayTags(edgeId, edgeIntAccess, osmWay, osmParsers.createRelationFlags()); assertFalse(accessParser.getAccessEnc().getBool(false, edgeId, edgeIntAccess)); assertEquals(RouteNetwork.MISSING, encodingManager.getEnumEncodedValue(BikeNetwork.KEY, RouteNetwork.class).getEnum(false, edgeId, edgeIntAccess)); diff --git a/core/src/test/java/com/graphhopper/routing/util/parsers/CarTagParserTest.java b/core/src/test/java/com/graphhopper/routing/util/parsers/CarTagParserTest.java index 43611cb0738..148b8f1caeb 100644 --- a/core/src/test/java/com/graphhopper/routing/util/parsers/CarTagParserTest.java +++ b/core/src/test/java/com/graphhopper/routing/util/parsers/CarTagParserTest.java @@ -23,15 +23,12 @@ import com.graphhopper.routing.util.EncodingManager; import com.graphhopper.routing.util.PriorityCode; import com.graphhopper.routing.util.WayAccess; -import com.graphhopper.util.Helper; import com.graphhopper.util.PMap; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.ValueSource; -import java.text.DateFormat; import java.util.Arrays; -import java.util.Date; import static org.junit.jupiter.api.Assertions.*; @@ -178,13 +175,13 @@ public void testFordAccess() { public void testOneway() { ReaderWay way = new ReaderWay(1); way.setTag("highway", "primary"); - EdgeIntAccess edgeIntAccess = new ArrayEdgeIntAccess(em.getIntsForFlags()); + EdgeIntAccess edgeIntAccess = ArrayEdgeIntAccess.createFromBytes(em.getBytesForFlags()); int edgeId = 0; parser.handleWayTags(edgeId, edgeIntAccess, way); assertTrue(accessEnc.getBool(false, edgeId, edgeIntAccess)); assertTrue(accessEnc.getBool(true, edgeId, edgeIntAccess)); way.setTag("oneway", "yes"); - edgeIntAccess = new ArrayEdgeIntAccess(em.getIntsForFlags()); + edgeIntAccess = ArrayEdgeIntAccess.createFromBytes(em.getBytesForFlags()); parser.handleWayTags(edgeId, edgeIntAccess, way); assertTrue(accessEnc.getBool(false, edgeId, edgeIntAccess)); assertFalse(accessEnc.getBool(true, edgeId, edgeIntAccess)); @@ -192,7 +189,7 @@ public void testOneway() { way.setTag("highway", "tertiary"); - edgeIntAccess = new ArrayEdgeIntAccess(em.getIntsForFlags()); + edgeIntAccess = ArrayEdgeIntAccess.createFromBytes(em.getBytesForFlags()); parser.handleWayTags(edgeId, edgeIntAccess, way); assertTrue(accessEnc.getBool(false, edgeId, edgeIntAccess)); assertTrue(accessEnc.getBool(true, edgeId, edgeIntAccess)); @@ -201,7 +198,7 @@ public void testOneway() { way.setTag("highway", "tertiary"); way.setTag("vehicle:forward", "no"); - edgeIntAccess = new ArrayEdgeIntAccess(em.getIntsForFlags()); + edgeIntAccess = ArrayEdgeIntAccess.createFromBytes(em.getBytesForFlags()); parser.handleWayTags(edgeId, edgeIntAccess, way); assertFalse(accessEnc.getBool(false, edgeId, edgeIntAccess)); assertTrue(accessEnc.getBool(true, edgeId, edgeIntAccess)); @@ -209,7 +206,7 @@ public void testOneway() { way.setTag("highway", "tertiary"); way.setTag("vehicle:backward", "no"); - edgeIntAccess = new ArrayEdgeIntAccess(em.getIntsForFlags()); + edgeIntAccess = ArrayEdgeIntAccess.createFromBytes(em.getBytesForFlags()); parser.handleWayTags(edgeId, edgeIntAccess, way); assertTrue(accessEnc.getBool(false, edgeId, edgeIntAccess)); assertFalse(accessEnc.getBool(true, edgeId, edgeIntAccess)); @@ -218,7 +215,7 @@ public void testOneway() { // This is no one way way.setTag("highway", "tertiary"); way.setTag("vehicle:backward", "designated"); - edgeIntAccess = new ArrayEdgeIntAccess(em.getIntsForFlags()); + edgeIntAccess = ArrayEdgeIntAccess.createFromBytes(em.getBytesForFlags()); parser.handleWayTags(edgeId, edgeIntAccess, way); assertTrue(accessEnc.getBool(false, edgeId, edgeIntAccess)); assertTrue(accessEnc.getBool(true, edgeId, edgeIntAccess)); @@ -230,26 +227,26 @@ public void shouldBlockPrivate() { ReaderWay way = new ReaderWay(1); way.setTag("highway", "primary"); way.setTag("access", "private"); - EdgeIntAccess edgeIntAccess = new ArrayEdgeIntAccess(em.getIntsForFlags()); + EdgeIntAccess edgeIntAccess = ArrayEdgeIntAccess.createFromBytes(em.getBytesForFlags()); int edgeId = 0; parser.handleWayTags(edgeId, edgeIntAccess, way); assertFalse(accessEnc.getBool(false, edgeId, edgeIntAccess)); final CarAccessParser parser = createParser(em, new PMap("block_private=false")); - edgeIntAccess = new ArrayEdgeIntAccess(em.getIntsForFlags()); + edgeIntAccess = ArrayEdgeIntAccess.createFromBytes(em.getBytesForFlags()); parser.handleWayTags(edgeId, edgeIntAccess, way); assertTrue(parser.getAccessEnc().getBool(false, edgeId, edgeIntAccess)); way.setTag("highway", "primary"); way.setTag("motor_vehicle", "permit"); // currently handled like "private", see #2712 - edgeIntAccess = new ArrayEdgeIntAccess(em.getIntsForFlags()); + edgeIntAccess = ArrayEdgeIntAccess.createFromBytes(em.getBytesForFlags()); parser.handleWayTags(edgeId, edgeIntAccess, way); assertTrue(parser.getAccessEnc().getBool(false, edgeId, edgeIntAccess)); } @Test public void testSetAccess() { - EdgeIntAccess edgeIntAccess = new ArrayEdgeIntAccess(em.getIntsForFlags()); + EdgeIntAccess edgeIntAccess = ArrayEdgeIntAccess.createFromBytes(em.getBytesForFlags()); int edgeId = 0; accessEnc.setBool(false, edgeId, edgeIntAccess, true); accessEnc.setBool(true, edgeId, edgeIntAccess, true); @@ -276,7 +273,7 @@ public void testMaxSpeed() { ReaderWay way = new ReaderWay(1); way.setTag("highway", "trunk"); way.setTag("maxspeed", "500"); - EdgeIntAccess edgeIntAccess = new ArrayEdgeIntAccess(em.getIntsForFlags()); + EdgeIntAccess edgeIntAccess = ArrayEdgeIntAccess.createFromBytes(em.getBytesForFlags()); int edgeId = 0; speedParser.handleWayTags(edgeId, edgeIntAccess, way); assertEquals(140, avSpeedEnc.getDecimal(false, edgeId, edgeIntAccess), 1e-1); @@ -285,7 +282,7 @@ public void testMaxSpeed() { way.setTag("highway", "primary"); way.setTag("maxspeed:backward", "10"); way.setTag("maxspeed:forward", "20"); - edgeIntAccess = new ArrayEdgeIntAccess(em.getIntsForFlags()); + edgeIntAccess = ArrayEdgeIntAccess.createFromBytes(em.getBytesForFlags()); speedParser.handleWayTags(edgeId, edgeIntAccess, way); assertEquals(18, avSpeedEnc.getDecimal(false, edgeId, edgeIntAccess), 1e-1); assertEquals(10, avSpeedEnc.getDecimal(true, edgeId, edgeIntAccess), 1e-1); @@ -293,14 +290,14 @@ public void testMaxSpeed() { way = new ReaderWay(1); way.setTag("highway", "primary"); way.setTag("maxspeed:forward", "20"); - edgeIntAccess = new ArrayEdgeIntAccess(em.getIntsForFlags()); + edgeIntAccess = ArrayEdgeIntAccess.createFromBytes(em.getBytesForFlags()); speedParser.handleWayTags(edgeId, edgeIntAccess, way); assertEquals(18, avSpeedEnc.getDecimal(false, edgeId, edgeIntAccess), 1e-1); way = new ReaderWay(1); way.setTag("highway", "primary"); way.setTag("maxspeed:backward", "20"); - edgeIntAccess = new ArrayEdgeIntAccess(em.getIntsForFlags()); + edgeIntAccess = ArrayEdgeIntAccess.createFromBytes(em.getBytesForFlags()); speedParser.handleWayTags(edgeId, edgeIntAccess, way); assertEquals(66, avSpeedEnc.getDecimal(false, edgeId, edgeIntAccess), 1e-1); assertEquals(18, avSpeedEnc.getDecimal(true, edgeId, edgeIntAccess), 1e-1); @@ -308,14 +305,14 @@ public void testMaxSpeed() { way = new ReaderWay(1); way.setTag("highway", "motorway"); way.setTag("maxspeed", "none"); - edgeIntAccess = new ArrayEdgeIntAccess(em.getIntsForFlags()); + edgeIntAccess = ArrayEdgeIntAccess.createFromBytes(em.getBytesForFlags()); speedParser.handleWayTags(edgeId, edgeIntAccess, way); assertEquals(136, avSpeedEnc.getDecimal(false, edgeId, edgeIntAccess), .1); way = new ReaderWay(1); way.setTag("highway", "motorway_link"); way.setTag("maxspeed", "70 mph"); - edgeIntAccess = new ArrayEdgeIntAccess(em.getIntsForFlags()); + edgeIntAccess = ArrayEdgeIntAccess.createFromBytes(em.getBytesForFlags()); speedParser.handleWayTags(edgeId, edgeIntAccess, way); assertEquals(102, avSpeedEnc.getDecimal(true, edgeId, edgeIntAccess), 1e-1); } @@ -326,7 +323,7 @@ public void testSpeed() { ReaderWay way = new ReaderWay(1); way.setTag("highway", "trunk"); way.setTag("maxspeed", "110"); - EdgeIntAccess edgeIntAccess = new ArrayEdgeIntAccess(em.getIntsForFlags()); + EdgeIntAccess edgeIntAccess = ArrayEdgeIntAccess.createFromBytes(em.getBytesForFlags()); int edgeId = 0; speedParser.handleWayTags(edgeId, edgeIntAccess, way); assertEquals(100, avSpeedEnc.getDecimal(false, edgeId, edgeIntAccess), 1e-1); @@ -334,27 +331,27 @@ public void testSpeed() { way.clearTags(); way.setTag("highway", "residential"); way.setTag("surface", "cobblestone"); - edgeIntAccess = new ArrayEdgeIntAccess(em.getIntsForFlags()); + edgeIntAccess = ArrayEdgeIntAccess.createFromBytes(em.getBytesForFlags()); speedParser.handleWayTags(edgeId, edgeIntAccess, way); assertEquals(30, avSpeedEnc.getDecimal(false, edgeId, edgeIntAccess), 1e-1); way.clearTags(); way.setTag("highway", "track"); - edgeIntAccess = new ArrayEdgeIntAccess(em.getIntsForFlags()); + edgeIntAccess = ArrayEdgeIntAccess.createFromBytes(em.getBytesForFlags()); speedParser.handleWayTags(edgeId, edgeIntAccess, way); assertEquals(16, avSpeedEnc.getDecimal(false, edgeId, edgeIntAccess), 1e-1); way.clearTags(); way.setTag("highway", "track"); way.setTag("tracktype", "grade1"); - edgeIntAccess = new ArrayEdgeIntAccess(em.getIntsForFlags()); + edgeIntAccess = ArrayEdgeIntAccess.createFromBytes(em.getBytesForFlags()); speedParser.handleWayTags(edgeId, edgeIntAccess, way); assertEquals(20, avSpeedEnc.getDecimal(false, edgeId, edgeIntAccess), 1e-1); way.clearTags(); way.setTag("highway", "secondary"); way.setTag("surface", "compacted"); - edgeIntAccess = new ArrayEdgeIntAccess(em.getIntsForFlags()); + edgeIntAccess = ArrayEdgeIntAccess.createFromBytes(em.getBytesForFlags()); speedParser.handleWayTags(edgeId, edgeIntAccess, way); assertEquals(30, avSpeedEnc.getDecimal(false, edgeId, edgeIntAccess), 1e-1); @@ -362,7 +359,7 @@ public void testSpeed() { way.clearTags(); way.setTag("highway", "secondary"); way.setTag("motorroad", "yes"); // motorroad should not influence speed. only access for non-motor vehicles - edgeIntAccess = new ArrayEdgeIntAccess(em.getIntsForFlags()); + edgeIntAccess = ArrayEdgeIntAccess.createFromBytes(em.getBytesForFlags()); speedParser.handleWayTags(edgeId, edgeIntAccess, way); assertEquals(60, avSpeedEnc.getDecimal(false, edgeId, edgeIntAccess), 1e-1); @@ -370,14 +367,14 @@ public void testSpeed() { way.clearTags(); way.setTag("highway", "motorway"); way.setTag("motorroad", "yes"); - edgeIntAccess = new ArrayEdgeIntAccess(em.getIntsForFlags()); + edgeIntAccess = ArrayEdgeIntAccess.createFromBytes(em.getBytesForFlags()); speedParser.handleWayTags(edgeId, edgeIntAccess, way); assertEquals(100, avSpeedEnc.getDecimal(false, edgeId, edgeIntAccess), 1e-1); way.clearTags(); way.setTag("highway", "motorway_link"); way.setTag("motorroad", "yes"); - edgeIntAccess = new ArrayEdgeIntAccess(em.getIntsForFlags()); + edgeIntAccess = ArrayEdgeIntAccess.createFromBytes(em.getBytesForFlags()); speedParser.handleWayTags(edgeId, edgeIntAccess, way); assertEquals(70, avSpeedEnc.getDecimal(false, edgeId, edgeIntAccess), 1e-1); @@ -390,7 +387,7 @@ public void testSpeed() { @Test public void testSetSpeed() { - EdgeIntAccess edgeIntAccess = new ArrayEdgeIntAccess(em.getIntsForFlags()); + EdgeIntAccess edgeIntAccess = ArrayEdgeIntAccess.createFromBytes(em.getBytesForFlags()); int edgeId = 0; avSpeedEnc.setDecimal(false, edgeId, edgeIntAccess, 10); assertEquals(10, avSpeedEnc.getDecimal(false, edgeId, edgeIntAccess), 1e-1); @@ -398,7 +395,7 @@ public void testSetSpeed() { @Test public void testSetSpeed0_issue367_issue1234() { - EdgeIntAccess edgeIntAccess = new ArrayEdgeIntAccess(em.getIntsForFlags()); + EdgeIntAccess edgeIntAccess = ArrayEdgeIntAccess.createFromBytes(em.getBytesForFlags()); int edgeId = 0; accessEnc.setBool(false, edgeId, edgeIntAccess, true); accessEnc.setBool(true, edgeId, edgeIntAccess, true); @@ -424,7 +421,7 @@ public void testSetSpeed0_issue367_issue1234() { @Test public void testRoundabout() { - EdgeIntAccess edgeIntAccess = new ArrayEdgeIntAccess(em.getIntsForFlags()); + EdgeIntAccess edgeIntAccess = ArrayEdgeIntAccess.createFromBytes(em.getBytesForFlags()); int edgeId = 0; accessEnc.setBool(false, edgeId, edgeIntAccess, true); accessEnc.setBool(true, edgeId, edgeIntAccess, true); @@ -440,7 +437,7 @@ public void testRoundabout() { ReaderWay way = new ReaderWay(1); way.setTag("highway", "motorway"); - edgeIntAccess = new ArrayEdgeIntAccess(em.getIntsForFlags()); + edgeIntAccess = ArrayEdgeIntAccess.createFromBytes(em.getBytesForFlags()); parser.handleWayTags(edgeId, edgeIntAccess, way); assertTrue(accessEnc.getBool(false, edgeId, edgeIntAccess)); assertTrue(accessEnc.getBool(true, edgeId, edgeIntAccess)); @@ -592,7 +589,7 @@ public void testMaxValue() { ReaderWay way = new ReaderWay(1); way.setTag("highway", "motorway_link"); way.setTag("maxspeed", "60 mph"); - EdgeIntAccess edgeIntAccess = new ArrayEdgeIntAccess(em.getIntsForFlags()); + EdgeIntAccess edgeIntAccess = ArrayEdgeIntAccess.createFromBytes(em.getBytesForFlags()); int edgeId = 0; speedParser.handleWayTags(edgeId, edgeIntAccess, way); @@ -605,7 +602,7 @@ public void testMaxValue() { way = new ReaderWay(2); way.setTag("highway", "motorway_link"); way.setTag("maxspeed", "70 mph"); - edgeIntAccess = new ArrayEdgeIntAccess(em.getIntsForFlags()); + edgeIntAccess = ArrayEdgeIntAccess.createFromBytes(em.getBytesForFlags()); speedParser.handleWayTags(edgeId, edgeIntAccess, way); assertEquals(101.5, smallFactorSpeedEnc.getDecimal(false, edgeId, edgeIntAccess), .1); } @@ -632,7 +629,7 @@ public void testCombination() { BikeAccessParser bikeParser = new BikeAccessParser(em, new PMap()); assertEquals(WayAccess.CAN_SKIP, parser.getAccess(way)); assertNotEquals(WayAccess.CAN_SKIP, bikeParser.getAccess(way)); - EdgeIntAccess edgeIntAccess = new ArrayEdgeIntAccess(em.getIntsForFlags()); + EdgeIntAccess edgeIntAccess = ArrayEdgeIntAccess.createFromBytes(em.getBytesForFlags()); int edgeId = 0; parser.handleWayTags(edgeId, edgeIntAccess, way); bikeParser.handleWayTags(edgeId, edgeIntAccess, way); @@ -657,7 +654,7 @@ public void testIssue_1256() { way.setTag("route", "ferry"); way.setTag("edge_distance", 257.0); - EdgeIntAccess edgeIntAccess = new ArrayEdgeIntAccess(em.getIntsForFlags()); + EdgeIntAccess edgeIntAccess = ArrayEdgeIntAccess.createFromBytes(em.getBytesForFlags()); int edgeId = 0; speedParser.handleWayTags(edgeId, edgeIntAccess, way); assertEquals(2, speedParser.getAverageSpeedEnc().getDecimal(false, edgeId, edgeIntAccess), .1); @@ -669,7 +666,7 @@ public void testIssue_1256() { .add(lowFactorSpeedEnc) .add(FerrySpeed.create()) .build(); - edgeIntAccess = new ArrayEdgeIntAccess(lowFactorEm.getIntsForFlags()); + edgeIntAccess = ArrayEdgeIntAccess.createFromBytes(lowFactorEm.getBytesForFlags()); new CarAverageSpeedParser(lowFactorEm).handleWayTags(edgeId, edgeIntAccess, way); assertEquals(1, lowFactorSpeedEnc.getDecimal(false, edgeId, edgeIntAccess), .1); } @@ -735,29 +732,29 @@ void footway_etc_not_allowed_despite_vehicle_yes(String vehicle) { void nonHighwaysFallbackSpeed_issue2845() { ReaderWay way = new ReaderWay(1); way.setTag("man_made", "pier"); - EdgeIntAccess edgeIntAccess = new ArrayEdgeIntAccess(em.getIntsForFlags()); + EdgeIntAccess edgeIntAccess = ArrayEdgeIntAccess.createFromBytes(em.getBytesForFlags()); speedParser.handleWayTags(0, edgeIntAccess, way); assertEquals(10, avSpeedEnc.getDecimal(false, 0, edgeIntAccess), 1e-1); way.clearTags(); way.setTag("railway", "platform"); - speedParser.handleWayTags(0, edgeIntAccess = new ArrayEdgeIntAccess(em.getIntsForFlags()), way); + speedParser.handleWayTags(0, edgeIntAccess = ArrayEdgeIntAccess.createFromBytes(em.getBytesForFlags()), way); assertEquals(10, avSpeedEnc.getDecimal(false, 0, edgeIntAccess), 1e-1); way.clearTags(); way.setTag("route", "ski"); - speedParser.handleWayTags(0, edgeIntAccess = new ArrayEdgeIntAccess(em.getIntsForFlags()), way); + speedParser.handleWayTags(0, edgeIntAccess = ArrayEdgeIntAccess.createFromBytes(em.getBytesForFlags()), way); assertEquals(10, avSpeedEnc.getDecimal(false, 0, edgeIntAccess), 1e-1); way.clearTags(); way.setTag("highway", "abandoned"); - speedParser.handleWayTags(0, edgeIntAccess = new ArrayEdgeIntAccess(em.getIntsForFlags()), way); + speedParser.handleWayTags(0, edgeIntAccess = ArrayEdgeIntAccess.createFromBytes(em.getBytesForFlags()), way); assertEquals(10, avSpeedEnc.getDecimal(false, 0, edgeIntAccess), 1e-1); way.clearTags(); way.setTag("highway", "construction"); way.setTag("maxspeed", "100"); - speedParser.handleWayTags(0, edgeIntAccess = new ArrayEdgeIntAccess(em.getIntsForFlags()), way); + speedParser.handleWayTags(0, edgeIntAccess = ArrayEdgeIntAccess.createFromBytes(em.getBytesForFlags()), way); // unknown highways can be quite fast in combination with maxspeed!? assertEquals(90, avSpeedEnc.getDecimal(false, 0, edgeIntAccess), 1e-1); } diff --git a/core/src/test/java/com/graphhopper/routing/util/parsers/FootTagParserTest.java b/core/src/test/java/com/graphhopper/routing/util/parsers/FootTagParserTest.java index 1787db97d8d..0215df6d0a7 100644 --- a/core/src/test/java/com/graphhopper/routing/util/parsers/FootTagParserTest.java +++ b/core/src/test/java/com/graphhopper/routing/util/parsers/FootTagParserTest.java @@ -27,9 +27,7 @@ import com.graphhopper.util.*; import org.junit.jupiter.api.Test; -import java.text.DateFormat; import java.util.Collections; -import java.util.Date; import java.util.HashMap; import java.util.Map; @@ -63,7 +61,7 @@ public FootTagParserTest() { @Test public void testGetSpeed() { - EdgeIntAccess edgeIntAccess = new ArrayEdgeIntAccess(encodingManager.getIntsForFlags()); + EdgeIntAccess edgeIntAccess = ArrayEdgeIntAccess.createFromBytes(encodingManager.getBytesForFlags()); int edgeId = 0; footAccessEnc.setBool(false, edgeId, edgeIntAccess, true); footAccessEnc.setBool(true, edgeId, edgeIntAccess, true); @@ -75,13 +73,13 @@ public void testGetSpeed() { public void testSteps() { ReaderWay way = new ReaderWay(1); way.setTag("highway", "service"); - EdgeIntAccess edgeIntAccess = new ArrayEdgeIntAccess(encodingManager.getIntsForFlags()); + EdgeIntAccess edgeIntAccess = ArrayEdgeIntAccess.createFromBytes(encodingManager.getBytesForFlags()); int edgeId = 0; speedParser.handleWayTags(edgeId, edgeIntAccess, way); assertEquals(MEAN_SPEED, footAvgSpeedEnc.getDecimal(false, edgeId, edgeIntAccess), 1e-1); way.setTag("highway", "steps"); - edgeIntAccess = new ArrayEdgeIntAccess(encodingManager.getIntsForFlags()); + edgeIntAccess = ArrayEdgeIntAccess.createFromBytes(encodingManager.getBytesForFlags()); speedParser.handleWayTags(edgeId, edgeIntAccess, way); assertTrue(MEAN_SPEED > footAvgSpeedEnc.getDecimal(false, edgeId, edgeIntAccess)); } @@ -101,7 +99,7 @@ public void testCombined() { assertTrue(edge.get(carAccessEnc)); assertFalse(edge.getReverse(carAccessEnc)); - EdgeIntAccess edgeIntAccess = new ArrayEdgeIntAccess(encodingManager.getIntsForFlags()); + EdgeIntAccess edgeIntAccess = ArrayEdgeIntAccess.createFromBytes(encodingManager.getBytesForFlags()); int edgeId = 0; footAvgSpeedEnc.setDecimal(false, edgeId, edgeIntAccess, 10); footAccessEnc.setBool(false, edgeId, edgeIntAccess, true); @@ -230,7 +228,7 @@ public void testAccess() { public void testRailPlatformIssue366() { ReaderWay way = new ReaderWay(1); way.setTag("railway", "platform"); - EdgeIntAccess edgeIntAccess = new ArrayEdgeIntAccess(encodingManager.getIntsForFlags()); + EdgeIntAccess edgeIntAccess = ArrayEdgeIntAccess.createFromBytes(encodingManager.getBytesForFlags()); int edgeId = 0; accessParser.handleWayTags(edgeId, edgeIntAccess, way); speedParser.handleWayTags(edgeId, edgeIntAccess, way); @@ -240,7 +238,7 @@ public void testRailPlatformIssue366() { way.clearTags(); way.setTag("highway", "track"); way.setTag("railway", "platform"); - edgeIntAccess = new ArrayEdgeIntAccess(encodingManager.getIntsForFlags()); + edgeIntAccess = ArrayEdgeIntAccess.createFromBytes(encodingManager.getBytesForFlags()); accessParser.handleWayTags(edgeId, edgeIntAccess, way); speedParser.handleWayTags(edgeId, edgeIntAccess, way); assertTrue(footAccessEnc.getBool(false, edgeId, edgeIntAccess)); @@ -249,7 +247,7 @@ public void testRailPlatformIssue366() { way.clearTags(); // only tram, no highway => no access way.setTag("railway", "tram"); - edgeIntAccess = new ArrayEdgeIntAccess(encodingManager.getIntsForFlags()); + edgeIntAccess = ArrayEdgeIntAccess.createFromBytes(encodingManager.getBytesForFlags()); accessParser.handleWayTags(edgeId, edgeIntAccess, way); speedParser.handleWayTags(edgeId, edgeIntAccess, way); assertFalse(footAccessEnc.getBool(false, edgeId, edgeIntAccess)); @@ -260,7 +258,7 @@ public void testRailPlatformIssue366() { public void testPier() { ReaderWay way = new ReaderWay(1); way.setTag("man_made", "pier"); - EdgeIntAccess edgeIntAccess = new ArrayEdgeIntAccess(encodingManager.getIntsForFlags()); + EdgeIntAccess edgeIntAccess = ArrayEdgeIntAccess.createFromBytes(encodingManager.getBytesForFlags()); int edgeId = 0; accessParser.handleWayTags(edgeId, edgeIntAccess, way); speedParser.handleWayTags(edgeId, edgeIntAccess, way); @@ -273,13 +271,13 @@ public void testOneway() { ReaderWay way = new ReaderWay(1); way.setTag("highway", "path"); way.setTag("foot:forward", "yes"); - EdgeIntAccess edgeIntAccess = new ArrayEdgeIntAccess(encodingManager.getIntsForFlags()); + EdgeIntAccess edgeIntAccess = ArrayEdgeIntAccess.createFromBytes(encodingManager.getBytesForFlags()); int edgeId = 0; accessParser.handleWayTags(edgeId, edgeIntAccess, way, null); assertTrue(footAccessEnc.getBool(false, edgeId, edgeIntAccess)); assertFalse(footAccessEnc.getBool(true, edgeId, edgeIntAccess)); - edgeIntAccess = new ArrayEdgeIntAccess(encodingManager.getIntsForFlags()); + edgeIntAccess = ArrayEdgeIntAccess.createFromBytes(encodingManager.getBytesForFlags()); way.clearTags(); way.setTag("highway", "path"); way.setTag("foot:backward", "yes"); @@ -292,7 +290,7 @@ public void testOneway() { public void testMixSpeedAndSafe() { ReaderWay way = new ReaderWay(1); way.setTag("highway", "motorway"); - EdgeIntAccess edgeIntAccess = new ArrayEdgeIntAccess(encodingManager.getIntsForFlags()); + EdgeIntAccess edgeIntAccess = ArrayEdgeIntAccess.createFromBytes(encodingManager.getBytesForFlags()); int edgeId = 0; accessParser.handleWayTags(edgeId, edgeIntAccess, way); assertFalse(footAccessEnc.getBool(false, edgeId, edgeIntAccess)); @@ -300,7 +298,7 @@ public void testMixSpeedAndSafe() { assertEquals(0, footAvgSpeedEnc.getDecimal(false, edgeId, edgeIntAccess)); way.setTag("sidewalk", "yes"); - edgeIntAccess = new ArrayEdgeIntAccess(encodingManager.getIntsForFlags()); + edgeIntAccess = ArrayEdgeIntAccess.createFromBytes(encodingManager.getBytesForFlags()); accessParser.handleWayTags(edgeId, edgeIntAccess, way); speedParser.handleWayTags(edgeId, edgeIntAccess, way); assertTrue(footAccessEnc.getBool(false, edgeId, edgeIntAccess)); @@ -309,7 +307,7 @@ public void testMixSpeedAndSafe() { way.clearTags(); way.setTag("highway", "track"); - edgeIntAccess = new ArrayEdgeIntAccess(encodingManager.getIntsForFlags()); + edgeIntAccess = ArrayEdgeIntAccess.createFromBytes(encodingManager.getBytesForFlags()); accessParser.handleWayTags(edgeId, edgeIntAccess, way); speedParser.handleWayTags(edgeId, edgeIntAccess, way); assertTrue(footAccessEnc.getBool(false, edgeId, edgeIntAccess)); @@ -386,14 +384,14 @@ public void testSlowHiking() { ReaderWay way = new ReaderWay(1); way.setTag("highway", "track"); way.setTag("sac_scale", "hiking"); - EdgeIntAccess edgeIntAccess = new ArrayEdgeIntAccess(encodingManager.getIntsForFlags()); + EdgeIntAccess edgeIntAccess = ArrayEdgeIntAccess.createFromBytes(encodingManager.getBytesForFlags()); int edgeId = 0; speedParser.handleWayTags(edgeId, edgeIntAccess, way); assertEquals(MEAN_SPEED, footAvgSpeedEnc.getDecimal(false, edgeId, edgeIntAccess), 1e-1); way.setTag("highway", "track"); way.setTag("sac_scale", "mountain_hiking"); - edgeIntAccess = new ArrayEdgeIntAccess(encodingManager.getIntsForFlags()); + edgeIntAccess = ArrayEdgeIntAccess.createFromBytes(encodingManager.getBytesForFlags()); speedParser.handleWayTags(edgeId, edgeIntAccess, way); assertEquals(SLOW_SPEED, footAvgSpeedEnc.getDecimal(false, edgeId, edgeIntAccess), 1e-1); } @@ -401,7 +399,7 @@ public void testSlowHiking() { @Test public void testReadBarrierNodesFromWay() { int edgeId = 0; - EdgeIntAccess edgeIntAccess = new ArrayEdgeIntAccess(encodingManager.getIntsForFlags()); + EdgeIntAccess edgeIntAccess = ArrayEdgeIntAccess.createFromBytes(encodingManager.getBytesForFlags()); ReaderWay way = new ReaderWay(1); way.setTag("highway", "secondary"); way.setTag("gh:barrier_edge", true); diff --git a/core/src/test/java/com/graphhopper/routing/util/parsers/HikeCustomModelTest.java b/core/src/test/java/com/graphhopper/routing/util/parsers/HikeCustomModelTest.java index 92e3520187c..47424201514 100644 --- a/core/src/test/java/com/graphhopper/routing/util/parsers/HikeCustomModelTest.java +++ b/core/src/test/java/com/graphhopper/routing/util/parsers/HikeCustomModelTest.java @@ -1,7 +1,6 @@ package com.graphhopper.routing.util.parsers; import com.graphhopper.reader.ReaderWay; -import com.graphhopper.reader.osm.conditional.DateRangeParser; import com.graphhopper.routing.ev.*; import com.graphhopper.routing.util.EncodingManager; import com.graphhopper.routing.util.OSMParsers; @@ -46,7 +45,7 @@ public void setup() { EdgeIteratorState createEdge(ReaderWay way) { BaseGraph graph = new BaseGraph.Builder(em).create(); EdgeIteratorState edge = graph.edge(0, 1); - EdgeIntAccess edgeIntAccess = graph.createEdgeIntAccess(); + EdgeIntAccess edgeIntAccess = graph.getEdgeAccess(); parsers.handleWayTags(edge.getEdge(), edgeIntAccess, way, em.createRelationFlags()); return edge; } diff --git a/core/src/test/java/com/graphhopper/routing/util/parsers/ModeAccessParserTest.java b/core/src/test/java/com/graphhopper/routing/util/parsers/ModeAccessParserTest.java index 28e595031b9..3270027a08b 100644 --- a/core/src/test/java/com/graphhopper/routing/util/parsers/ModeAccessParserTest.java +++ b/core/src/test/java/com/graphhopper/routing/util/parsers/ModeAccessParserTest.java @@ -24,7 +24,7 @@ class ModeAccessParserTest { public void testAccess() { ReaderWay way = new ReaderWay(1); way.setTag("highway", "primary"); - EdgeIntAccess edgeIntAccess = new ArrayEdgeIntAccess(em.getIntsForFlags()); + EdgeIntAccess edgeIntAccess = ArrayEdgeIntAccess.createFromBytes(em.getBytesForFlags()); int edgeId = 0; parser.handleWayTags(edgeId, edgeIntAccess, way, null); assertTrue(busAccessEnc.getBool(false, edgeId, edgeIntAccess)); @@ -36,7 +36,7 @@ public void testPrivate() { ReaderWay way = new ReaderWay(1); way.setTag("highway", "primary"); way.setTag("access", "private"); - EdgeIntAccess edgeIntAccess = new ArrayEdgeIntAccess(em.getIntsForFlags()); + EdgeIntAccess edgeIntAccess = ArrayEdgeIntAccess.createFromBytes(em.getBytesForFlags()); int edgeId = 0; parser.handleWayTags(edgeId, edgeIntAccess, way, null); assertTrue(busAccessEnc.getBool(false, edgeId, edgeIntAccess)); @@ -50,7 +50,7 @@ public void testOneway() { way.setTag("oneway", "yes"); int edgeId = 0; - EdgeIntAccess edgeIntAccess = new ArrayEdgeIntAccess(em.getIntsForFlags()); + EdgeIntAccess edgeIntAccess = ArrayEdgeIntAccess.createFromBytes(em.getBytesForFlags()); parser.handleWayTags(edgeId, edgeIntAccess, way, null); assertTrue(busAccessEnc.getBool(false, edgeId, edgeIntAccess)); assertFalse(busAccessEnc.getBool(true, edgeId, edgeIntAccess)); @@ -58,7 +58,7 @@ public void testOneway() { way.clearTags(); way.setTag("highway", "tertiary"); way.setTag("vehicle:forward", "no"); - edgeIntAccess = new ArrayEdgeIntAccess(em.getIntsForFlags()); + edgeIntAccess = ArrayEdgeIntAccess.createFromBytes(em.getBytesForFlags()); parser.handleWayTags(edgeId, edgeIntAccess, way, null); assertFalse(busAccessEnc.getBool(false, edgeId, edgeIntAccess)); assertTrue(busAccessEnc.getBool(true, edgeId, edgeIntAccess)); @@ -66,13 +66,13 @@ public void testOneway() { way.clearTags(); way.setTag("highway", "tertiary"); way.setTag("vehicle:backward", "no"); - edgeIntAccess = new ArrayEdgeIntAccess(em.getIntsForFlags()); + edgeIntAccess = ArrayEdgeIntAccess.createFromBytes(em.getBytesForFlags()); parser.handleWayTags(edgeId, edgeIntAccess, way, null); assertTrue(busAccessEnc.getBool(false, edgeId, edgeIntAccess)); assertFalse(busAccessEnc.getBool(true, edgeId, edgeIntAccess)); way.setTag("bus:backward", "yes"); - edgeIntAccess = new ArrayEdgeIntAccess(em.getIntsForFlags()); + edgeIntAccess = ArrayEdgeIntAccess.createFromBytes(em.getBytesForFlags()); parser.handleWayTags(edgeId, edgeIntAccess, way, null); assertTrue(busAccessEnc.getBool(false, edgeId, edgeIntAccess)); assertTrue(busAccessEnc.getBool(true, edgeId, edgeIntAccess)); @@ -81,7 +81,7 @@ public void testOneway() { way.setTag("highway", "tertiary"); way.setTag("vehicle:backward", "yes"); way.setTag("bus:backward", "no"); - edgeIntAccess = new ArrayEdgeIntAccess(em.getIntsForFlags()); + edgeIntAccess = ArrayEdgeIntAccess.createFromBytes(em.getBytesForFlags()); parser.handleWayTags(edgeId, edgeIntAccess, way, null); assertTrue(busAccessEnc.getBool(false, edgeId, edgeIntAccess)); assertFalse(busAccessEnc.getBool(true, edgeId, edgeIntAccess)); diff --git a/core/src/test/java/com/graphhopper/routing/util/parsers/OSMMaxWeightParserTest.java b/core/src/test/java/com/graphhopper/routing/util/parsers/OSMMaxWeightParserTest.java index 5091cca6e87..847c8c2b2eb 100644 --- a/core/src/test/java/com/graphhopper/routing/util/parsers/OSMMaxWeightParserTest.java +++ b/core/src/test/java/com/graphhopper/routing/util/parsers/OSMMaxWeightParserTest.java @@ -33,9 +33,9 @@ public void testSimpleTags() { // if value is beyond the maximum then do not use infinity instead fallback to more restrictive maximum edgeIntAccess = new ArrayEdgeIntAccess(1); - readerWay.setTag("maxweight", "50"); + readerWay.setTag("maxweight", "54"); parser.handleWayTags(edgeId, edgeIntAccess, readerWay, relFlags); - assertEquals(25.4, mwEnc.getDecimal(false, edgeId, edgeIntAccess), .01); + assertEquals(51, mwEnc.getDecimal(false, edgeId, edgeIntAccess), .01); } @Test @@ -62,4 +62,4 @@ public void testConditionalTags() { parser.handleWayTags(edgeId, edgeIntAccess, readerWay, relFlags); assertEquals(6, mwEnc.getDecimal(false, edgeId, edgeIntAccess), .01); } -} \ No newline at end of file +} diff --git a/core/src/test/java/com/graphhopper/routing/util/parsers/RacingBikeTagParserTest.java b/core/src/test/java/com/graphhopper/routing/util/parsers/RacingBikeTagParserTest.java index b24ea9105ae..d61357530d2 100644 --- a/core/src/test/java/com/graphhopper/routing/util/parsers/RacingBikeTagParserTest.java +++ b/core/src/test/java/com/graphhopper/routing/util/parsers/RacingBikeTagParserTest.java @@ -117,7 +117,7 @@ public void testSacScale() { @Test public void testGetSpeed() { - EdgeIntAccess edgeIntAccess = new ArrayEdgeIntAccess(encodingManager.getIntsForFlags()); + EdgeIntAccess edgeIntAccess = ArrayEdgeIntAccess.createFromBytes(encodingManager.getBytesForFlags()); int edgeId = 0; avgSpeedEnc.setDecimal(false, edgeId, edgeIntAccess, 10); ReaderWay way = new ReaderWay(1); @@ -293,7 +293,7 @@ public void testPriority_avoidanceOfHighMaxSpeed() { private void assertPriorityAndSpeed(EncodingManager encodingManager, DecimalEncodedValue priorityEnc, DecimalEncodedValue speedEnc, List parsers, PriorityCode expectedPrio, double expectedSpeed, ReaderWay way) { - EdgeIntAccess edgeIntAccess = new ArrayEdgeIntAccess(encodingManager.getIntsForFlags()); + EdgeIntAccess edgeIntAccess = ArrayEdgeIntAccess.createFromBytes(encodingManager.getBytesForFlags()); int edgeId = 0; for (TagParser p : parsers) p.handleWayTags(edgeId, edgeIntAccess, way, null); assertEquals(PriorityCode.getValue(expectedPrio.getValue()), priorityEnc.getDecimal(false, edgeId, edgeIntAccess), 0.01); diff --git a/core/src/test/java/com/graphhopper/routing/util/parsers/RestrictionSetterTest.java b/core/src/test/java/com/graphhopper/routing/util/parsers/RestrictionSetterTest.java index 40574f287fd..a3c729a6663 100644 --- a/core/src/test/java/com/graphhopper/routing/util/parsers/RestrictionSetterTest.java +++ b/core/src/test/java/com/graphhopper/routing/util/parsers/RestrictionSetterTest.java @@ -6,15 +6,23 @@ import com.graphhopper.reader.osm.RestrictionType; import com.graphhopper.routing.Dijkstra; import com.graphhopper.routing.ev.*; +import com.graphhopper.routing.querygraph.QueryGraph; +import com.graphhopper.routing.util.EdgeFilter; import com.graphhopper.routing.util.EncodingManager; import com.graphhopper.routing.util.TraversalMode; import com.graphhopper.routing.weighting.SpeedWeighting; import com.graphhopper.routing.weighting.TurnCostProvider; import com.graphhopper.storage.BaseGraph; +import com.graphhopper.storage.Graph; +import com.graphhopper.storage.NodeAccess; +import com.graphhopper.storage.index.LocationIndex; +import com.graphhopper.storage.index.LocationIndexTree; +import com.graphhopper.storage.index.Snap; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import java.util.Arrays; +import java.util.List; import static org.junit.jupiter.api.Assertions.*; @@ -330,6 +338,148 @@ void viaWay_only_twoRestrictionsSharingSameVia_different_directions() { assertEquals(nodes(5, 1, 2, 4), calcPath(5, 4, turnRestrictionEnc)); } + @Test + void viaWayAndNode() { + // 4-0-1-2 + // | + // 3 + int e0_1 = edge(0, 1); + int e0_4 = edge(0, 4); + int e1_2 = edge(1, 2); + int e1_3 = edge(1, 3); + BooleanEncodedValue turnRestrictionEnc = createTurnRestrictionEnc("car"); + assertEquals(nodes(0, 1, 3), calcPath(0, 3, turnRestrictionEnc)); + assertEquals(nodes(4, 0, 1, 2), calcPath(4, 2, turnRestrictionEnc)); + r.setRestrictions(List.of( + new Pair<>(GraphRestriction.way(e0_4, e0_1, e1_2, nodes(0, 1)), RestrictionType.NO), + new Pair<>(GraphRestriction.node(e0_1, 1, e1_3), RestrictionType.NO) + ), turnRestrictionEnc); + assertEquals(NO_PATH, calcPath(4, 2, turnRestrictionEnc)); + assertEquals(NO_PATH, calcPath(0, 3, turnRestrictionEnc)); + } + + @Test + void snapToViaWay() { + // 6 0 + // | | + // 1-2-x-3-4 + // | | + // 5 7 + int e1_2 = edge(1, 2); + int e2_3 = edge(2, 3); + int e3_4 = edge(3, 4); + int e0_3 = edge(0, 3); + int e2_5 = edge(2, 5); + int e2_6 = edge(2, 6); + int e3_7 = edge(3, 7); + NodeAccess na = graph.getNodeAccess(); + na.setNode(0, 40.03, 5.03); + na.setNode(1, 40.02, 5.01); + na.setNode(2, 40.02, 5.02); + na.setNode(3, 40.02, 5.03); + na.setNode(4, 40.02, 5.04); + na.setNode(5, 40.01, 5.02); + na.setNode(6, 40.03, 5.02); + na.setNode(7, 40.01, 5.03); + BooleanEncodedValue turnRestrictionEnc = createTurnRestrictionEnc("car"); + + assertEquals(nodes(1, 2, 3, 0), calcPath(1, 0, turnRestrictionEnc)); + assertEquals(nodes(1, 2, 3, 4), calcPath(1, 4, turnRestrictionEnc)); + assertEquals(nodes(5, 2, 3, 0), calcPath(5, 0, turnRestrictionEnc)); + assertEquals(nodes(6, 2, 3), calcPath(6, 3, turnRestrictionEnc)); + assertEquals(nodes(2, 3, 7), calcPath(2, 7, turnRestrictionEnc)); + r.setRestrictions(List.of( + new Pair<>(GraphRestriction.way(e1_2, e2_3, e0_3, nodes(2, 3)), RestrictionType.NO), + new Pair<>(GraphRestriction.node(e2_6, 2, e2_3), RestrictionType.NO), + new Pair<>(GraphRestriction.node(e2_3, 3, e3_7), RestrictionType.NO) + ), turnRestrictionEnc); + assertEquals(NO_PATH, calcPath(1, 0, turnRestrictionEnc)); + assertEquals(nodes(1, 2, 3, 4), calcPath(1, 4, turnRestrictionEnc)); + assertEquals(nodes(5, 2, 3, 0), calcPath(5, 0, turnRestrictionEnc)); + assertEquals(NO_PATH, calcPath(6, 3, turnRestrictionEnc)); + assertEquals(NO_PATH, calcPath(2, 7, turnRestrictionEnc)); + + // Now we try to route to and from a virtual node x. The problem here is that the 1-2-3-0 + // restriction forces paths coming from 1 onto an artificial edge (2-3)' (denying turns onto + // 2-3 coming from 1), so if we just snapped to the original edge 2-3 we wouldn't find a path! + // But if we snapped to the artificial edge we wouldn't find a path if we came from node 5. + // If x was our starting point we wouldn't be able to go to 0 either. + LocationIndex locationIndex = new LocationIndexTree(graph, graph.getDirectory()).prepareIndex(); + Snap snap = locationIndex.findClosest(40.02, 5.025, EdgeFilter.ALL_EDGES); + QueryGraph queryGraph = QueryGraph.create(graph, snap); + final int x = 8; + // due to the virtual node the 1-2-3-0 path is now possible + assertEquals(nodes(1, 2, x, 3, 0), calcPath(queryGraph, 1, 0, turnRestrictionEnc)); + assertEquals(nodes(1, 2, 3, 4), calcPath(queryGraph, 1, 4, turnRestrictionEnc)); + assertEquals(nodes(1, 2, x), calcPath(queryGraph, 1, x, turnRestrictionEnc)); + assertEquals(nodes(5, 2, x), calcPath(queryGraph, 5, x, turnRestrictionEnc)); + assertEquals(nodes(x, 3, 0), calcPath(queryGraph, x, 0, turnRestrictionEnc)); + assertEquals(nodes(x, 3, 4), calcPath(queryGraph, x, 4, turnRestrictionEnc)); + // the 6-2-3 and 2-3-7 restrictions are still enforced, despite the virtual node + assertEquals(NO_PATH, calcPath(queryGraph, 6, 3, turnRestrictionEnc)); + assertEquals(NO_PATH, calcPath(queryGraph, 2, 7, turnRestrictionEnc)); + } + + @Test + void snapToViaWay_twoVirtualNodes() { + // 1-2-x-3-y-4-z-5-6 + int e1_2 = edge(1, 2); + int e2_3 = edge(2, 3); + int e3_4 = edge(3, 4); + int e4_5 = edge(4, 5); + int e5_6 = edge(5, 6); + NodeAccess na = graph.getNodeAccess(); + na.setNode(1, 40.02, 5.01); + na.setNode(2, 40.02, 5.02); + na.setNode(3, 40.02, 5.03); + na.setNode(4, 40.02, 5.04); + na.setNode(5, 40.02, 5.05); + na.setNode(6, 40.02, 5.06); + BooleanEncodedValue turnRestrictionEnc = createTurnRestrictionEnc("car"); + + assertEquals(nodes(1, 2, 3, 4), calcPath(1, 4, turnRestrictionEnc)); + assertEquals(nodes(2, 3, 4), calcPath(2, 4, turnRestrictionEnc)); + assertEquals(nodes(2, 3, 4, 5), calcPath(2, 5, turnRestrictionEnc)); + assertEquals(nodes(3, 4, 5), calcPath(3, 5, turnRestrictionEnc)); + assertEquals(nodes(3, 4, 5, 6), calcPath(3, 6, turnRestrictionEnc)); + assertEquals(nodes(4, 5, 6), calcPath(4, 6, turnRestrictionEnc)); + r.setRestrictions(List.of( + new Pair<>(GraphRestriction.way(e1_2, e2_3, e3_4, nodes(2, 3)), RestrictionType.NO), + new Pair<>(GraphRestriction.way(e2_3, e3_4, e4_5, nodes(3, 4)), RestrictionType.NO), + new Pair<>(GraphRestriction.way(e3_4, e4_5, e5_6, nodes(4, 5)), RestrictionType.NO) + ), turnRestrictionEnc); + assertEquals(NO_PATH, calcPath(1, 4, turnRestrictionEnc)); + assertEquals(nodes(2, 3, 4), calcPath(2, 4, turnRestrictionEnc)); + assertEquals(NO_PATH, calcPath(2, 5, turnRestrictionEnc)); + assertEquals(nodes(3, 4, 5), calcPath(3, 5, turnRestrictionEnc)); + assertEquals(NO_PATH, calcPath(3, 6, turnRestrictionEnc)); + assertEquals(nodes(4, 5, 6), calcPath(4, 6, turnRestrictionEnc)); + + // three virtual notes + LocationIndex locationIndex = new LocationIndexTree(graph, graph.getDirectory()).prepareIndex(); + Snap snapX = locationIndex.findClosest(40.02, 5.025, EdgeFilter.ALL_EDGES); + Snap snapY = locationIndex.findClosest(40.02, 5.035, EdgeFilter.ALL_EDGES); + Snap snapZ = locationIndex.findClosest(40.02, 5.045, EdgeFilter.ALL_EDGES); + QueryGraph queryGraph = QueryGraph.create(graph, List.of(snapX, snapY, snapZ)); + final int x = 8; + final int y = 7; + final int z = 9; + assertEquals(x, snapX.getClosestNode()); + assertEquals(y, snapY.getClosestNode()); + assertEquals(z, snapZ.getClosestNode()); + assertEquals(nodes(1, 2, x, 3, 4), calcPath(queryGraph, 1, 4, turnRestrictionEnc)); + assertEquals(nodes(2, x, 3, 4), calcPath(queryGraph, 2, 4, turnRestrictionEnc)); + assertEquals(nodes(2, x, 3, y, 4, 5), calcPath(queryGraph, 2, 5, turnRestrictionEnc)); + assertEquals(nodes(3, y, 4, 5), calcPath(queryGraph, 3, 5, turnRestrictionEnc)); + assertEquals(nodes(3, y, 4, z, 5, 6), calcPath(queryGraph, 3, 6, turnRestrictionEnc)); + assertEquals(nodes(4, z, 5, 6), calcPath(queryGraph, 4, 6, turnRestrictionEnc)); + // turning between the virtual nodes is still possible + assertEquals(nodes(x, 3, y), calcPath(queryGraph, x, y, turnRestrictionEnc)); + assertEquals(nodes(y, 3, x), calcPath(queryGraph, y, x, turnRestrictionEnc)); + assertEquals(nodes(y, 4, z), calcPath(queryGraph, y, z, turnRestrictionEnc)); + assertEquals(nodes(z, 4, y), calcPath(queryGraph, z, y, turnRestrictionEnc)); + } + private static BooleanEncodedValue createTurnRestrictionEnc(String name) { BooleanEncodedValue turnRestrictionEnc = TurnRestriction.create(name); turnRestrictionEnc.init(new EncodedValue.InitializerConfig()); @@ -337,7 +487,11 @@ private static BooleanEncodedValue createTurnRestrictionEnc(String name) { } private IntArrayList calcPath(int from, int to, BooleanEncodedValue turnRestrictionEnc) { - return new IntArrayList(new Dijkstra(graph, new SpeedWeighting(speedEnc, new TurnCostProvider() { + return calcPath(this.graph, from, to, turnRestrictionEnc); + } + + private IntArrayList calcPath(Graph graph, int from, int to, BooleanEncodedValue turnRestrictionEnc) { + return new IntArrayList(new Dijkstra(graph, graph.wrapWeighting(new SpeedWeighting(speedEnc, new TurnCostProvider() { @Override public double calcTurnWeight(int inEdge, int viaNode, int outEdge) { if (inEdge == outEdge) return Double.POSITIVE_INFINITY; @@ -348,7 +502,7 @@ public double calcTurnWeight(int inEdge, int viaNode, int outEdge) { public long calcTurnMillis(int inEdge, int viaNode, int outEdge) { return Double.isInfinite(calcTurnWeight(inEdge, viaNode, outEdge)) ? Long.MAX_VALUE : 0L; } - }), TraversalMode.EDGE_BASED).calcPath(from, to).calcNodes()); + })), TraversalMode.EDGE_BASED).calcPath(from, to).calcNodes()); } private IntArrayList nodes(int... nodes) { diff --git a/core/src/test/java/com/graphhopper/routing/util/parsers/TagParsingTest.java b/core/src/test/java/com/graphhopper/routing/util/parsers/TagParsingTest.java index ef659085d2f..0a309b6b57e 100644 --- a/core/src/test/java/com/graphhopper/routing/util/parsers/TagParsingTest.java +++ b/core/src/test/java/com/graphhopper/routing/util/parsers/TagParsingTest.java @@ -75,7 +75,7 @@ public void handleWayTags(int edgeId, EdgeIntAccess intAccess, ReaderWay way, In osmRel.setTag("network", "lcn"); IntsRef relFlags = osmParsers.createRelationFlags(); relFlags = osmParsers.handleRelationTags(osmRel, relFlags); - EdgeIntAccess edgeIntAccess = new ArrayEdgeIntAccess(em.getIntsForFlags()); + EdgeIntAccess edgeIntAccess = ArrayEdgeIntAccess.createFromBytes(em.getBytesForFlags()); int edgeId = 0; osmParsers.handleWayTags(edgeId, edgeIntAccess, osmWay, relFlags); assertEquals(RouteNetwork.LOCAL, bikeNetworkEnc.getEnum(false, edgeId, edgeIntAccess)); @@ -117,7 +117,7 @@ public void testMixBikeTypesAndRelationCombination() { osmRel.setTag("network", "rcn"); IntsRef relFlags = osmParsers.createRelationFlags(); relFlags = osmParsers.handleRelationTags(osmRel, relFlags); - EdgeIntAccess edgeIntAccess = new ArrayEdgeIntAccess(em.getIntsForFlags()); + EdgeIntAccess edgeIntAccess = ArrayEdgeIntAccess.createFromBytes(em.getBytesForFlags()); int edgeId = 0; osmParsers.handleWayTags(edgeId, edgeIntAccess, osmWay, relFlags); // bike: uninfluenced speed for grade but via network => NICE @@ -152,7 +152,7 @@ public void testSharedEncodedValues() { new MountainBikeAccessParser(manager, new PMap()) ); - final ArrayEdgeIntAccess intAccess = new ArrayEdgeIntAccess(manager.getIntsForFlags()); + final ArrayEdgeIntAccess intAccess = ArrayEdgeIntAccess.createFromBytes(manager.getBytesForFlags()); int edgeId = 0; IntsRef relFlags = manager.createRelationFlags(); ReaderWay way = new ReaderWay(1); @@ -164,7 +164,6 @@ public void testSharedEncodedValues() { for (BooleanEncodedValue accessEnc : accessEncs) assertTrue(accessEnc.getBool(false, edgeId, intAccess)); - final IntsRef edgeFlags2 = manager.createEdgeFlags(); way.clearTags(); way.setTag("highway", "tertiary"); way.setTag("junction", "circular"); diff --git a/core/src/test/java/com/graphhopper/search/KVStorageTest.java b/core/src/test/java/com/graphhopper/search/KVStorageTest.java index 8c3820ac013..47180b75551 100644 --- a/core/src/test/java/com/graphhopper/search/KVStorageTest.java +++ b/core/src/test/java/com/graphhopper/search/KVStorageTest.java @@ -1,9 +1,8 @@ package com.graphhopper.search; import com.carrotsearch.hppc.LongArrayList; -import com.graphhopper.search.KVStorage.KeyValue; +import com.graphhopper.search.KVStorage.KValue; import com.graphhopper.storage.RAMDirectory; -import com.graphhopper.util.BitUtil; import com.graphhopper.util.Helper; import org.junit.jupiter.api.RepeatedTest; import org.junit.jupiter.api.Test; @@ -11,7 +10,6 @@ import java.io.File; import java.util.*; -import static com.graphhopper.search.KVStorage.KeyValue.createKV; import static com.graphhopper.search.KVStorage.MAX_UNIQUE_KEYS; import static com.graphhopper.search.KVStorage.cutString; import static com.graphhopper.util.Helper.UTF_CS; @@ -25,12 +23,12 @@ private KVStorage create() { return new KVStorage(new RAMDirectory(), true).create(1000); } - List createList(Object... keyValues) { + Map createMap(Object... keyValues) { if (keyValues.length % 2 != 0) throw new IllegalArgumentException("Cannot create list from " + Arrays.toString(keyValues)); - List map = new ArrayList<>(); + Map map = new LinkedHashMap<>(); for (int i = 0; i < keyValues.length; i += 2) { - map.add(new KeyValue((String) keyValues[i], keyValues[i + 1])); + map.put((String) keyValues[i], new KValue(keyValues[i + 1])); } return map; } @@ -38,7 +36,7 @@ List createList(Object... keyValues) { @Test public void putSame() { KVStorage index = create(); - long aPointer = index.add(createList("a", "same name", "b", "same name")); + long aPointer = index.add(createMap("a", "same name", "b", "same name")); assertNull(index.get(aPointer, "", false)); assertEquals("same name", index.get(aPointer, "a", false)); @@ -46,14 +44,14 @@ public void putSame() { assertNull(index.get(aPointer, "c", false)); index = create(); - aPointer = index.add(createList("a", "a name", "b", "same name")); + aPointer = index.add(createMap("a", "a name", "b", "same name")); assertEquals("a name", index.get(aPointer, "a", false)); } @Test public void putAB() { KVStorage index = create(); - long aPointer = index.add(createList("a", "a name", "b", "b name")); + long aPointer = index.add(createMap("a", "a name", "b", "b name")); assertNull(index.get(aPointer, "", false)); assertEquals("a name", index.get(aPointer, "a", false)); @@ -63,15 +61,16 @@ public void putAB() { @Test public void getForwardBackward() { KVStorage index = create(); - List list = new ArrayList<>(); - list.add(new KeyValue("keyA", "FORWARD", true, false)); - list.add(new KeyValue("keyB", "BACKWARD", false, true)); - list.add(new KeyValue("keyC", "BOTH", true, true)); - long aPointer = index.add(list); + Map map = new LinkedHashMap<>(); + map.put("keyA", new KValue("FORWARD", null)); + map.put("keyB", new KValue(null, "BACKWARD")); + map.put("keyC", new KValue("BOTH")); + map.put("keyD", new KValue("BOTH1", "BOTH2")); + long aPointer = index.add(map); assertNull(index.get(aPointer, "", false)); - List deserializedList = index.getAll(aPointer); - assertEquals(list, deserializedList); + Map deserializedList = index.getAll(aPointer); + assertEquals(map, deserializedList); assertEquals("FORWARD", index.get(aPointer, "keyA", false)); assertNull(index.get(aPointer, "keyA", true)); @@ -81,20 +80,23 @@ public void getForwardBackward() { assertEquals("BOTH", index.get(aPointer, "keyC", false)); assertEquals("BOTH", index.get(aPointer, "keyC", true)); + + assertEquals("BOTH1", index.get(aPointer, "keyD", false)); + assertEquals("BOTH2", index.get(aPointer, "keyD", true)); } @Test public void putEmpty() { KVStorage index = create(); - assertEquals(1, index.add(createList("", ""))); + assertEquals(1, index.add(createMap("", ""))); // cannot store null (in its first version we accepted null once it was clear which type the value has, but this is inconsequential) - assertThrows(IllegalArgumentException.class, () -> assertEquals(5, index.add(createList("", null)))); - assertThrows(IllegalArgumentException.class, () -> index.add(createList("blup", null))); - assertThrows(IllegalArgumentException.class, () -> index.add(createList(null, null))); + assertThrows(IllegalArgumentException.class, () -> assertEquals(5, index.add(createMap("", null)))); + assertThrows(IllegalArgumentException.class, () -> index.add(createMap("blup", null))); + assertThrows(IllegalArgumentException.class, () -> index.add(createMap(null, null))); assertNull(index.get(0, "", false)); - assertEquals(5, index.add(createList("else", "else"))); + assertEquals(5, index.add(createMap("else", "else"))); } @Test @@ -103,7 +105,7 @@ public void putMany() { long aPointer = 0, tmpPointer = 0; for (int i = 0; i < 10000; i++) { - aPointer = index.add(createList("a", "a name " + i, "b", "b name " + i, "c", "c name " + i)); + aPointer = index.add(createMap("a", "a name " + i, "b", "b name " + i, "c", "c name " + i)); if (i == 567) tmpPointer = aPointer; } @@ -121,10 +123,10 @@ public void putManyKeys() { KVStorage index = create(); // one key is already stored => empty key for (int i = 1; i < MAX_UNIQUE_KEYS; i++) { - index.add(createList("a" + i, "a name")); + index.add(createMap("a" + i, "a name")); } try { - index.add(createList("new", "a name")); + index.add(createMap("new", "a name")); fail(); } catch (IllegalArgumentException ex) { } @@ -138,14 +140,14 @@ public void testNoErrorOnLargeStringValue() { str += "ß"; } assertEquals(254, str.getBytes(Helper.UTF_CS).length); - long result = index.add(createList("", str)); + long result = index.add(createMap("", str)); assertEquals(127, ((String) index.get(result, "", false)).length()); } @Test public void testTooLongStringValueError() { KVStorage index = create(); - assertThrows(IllegalArgumentException.class, () -> index.add(createList("", "Бухарестская улица (http://ru.wikipedia.org/wiki" + + assertThrows(IllegalArgumentException.class, () -> index.add(createMap("", "Бухарестская улица (http://ru.wikipedia.org/wiki" + "/%D0%91%D1%83%D1%85%D0%B0%D1%80%D0%B5%D1%81%D1%82%D1%81%D0%BA%D0%B0%D1%8F_%D1%83%D0%BB%D0%B8%D1%86%D0%B0_(%D0%A1%D0%B0%D0%BD%D0%BA%D1%82-%D0%9F%D0%B5%D1%82%D0%B5%D1%80%D0%B1%D1%83%D1%80%D0%B3))"))); String str = "sdfsdfds"; @@ -153,7 +155,7 @@ public void testTooLongStringValueError() { str += "Б"; } final String finalStr = str; - assertThrows(IllegalArgumentException.class, () -> index.add(createList("", finalStr))); + assertThrows(IllegalArgumentException.class, () -> index.add(createMap("", finalStr))); } @Test @@ -165,23 +167,23 @@ public void testNoErrorOnLargestByteArray() { bytes[i] = (byte) (i % 255); copy[i] = bytes[i]; } - long result = index.add(createKV("myval", bytes)); + long result = index.add(Map.of("myval", new KValue(bytes))); bytes = (byte[]) index.get(result, "myval", false); assertArrayEquals(copy, bytes); final byte[] biggerByteArray = Arrays.copyOf(bytes, 256); - IllegalArgumentException e = assertThrows(IllegalArgumentException.class, () -> index.add(createKV("myval2", biggerByteArray))); + IllegalArgumentException e = assertThrows(IllegalArgumentException.class, () -> index.add(Map.of("myval2", new KValue(biggerByteArray)))); assertTrue(e.getMessage().contains("bytes.length cannot be > 255")); } @Test public void testIntLongDoubleFloat() { KVStorage index = create(); - long intres = index.add(createKV("intres", 4)); - long doubleres = index.add(createKV("doubleres", 4d)); - long floatres = index.add(createKV("floatres", 4f)); - long longres = index.add(createKV("longres", 4L)); - long after4Inserts = index.add(createKV("somenext", 0)); + long intres = index.add(Map.of("intres", new KValue(4))); + long doubleres = index.add(Map.of("doubleres", new KValue(4d))); + long floatres = index.add(Map.of("floatres", new KValue(4f))); + long longres = index.add(Map.of("longres", new KValue(4L))); + long after4Inserts = index.add(Map.of("somenext", new KValue(0))); // initial point is 1, then twice plus 1 + (2+4) and twice plus 1 + (2+8) assertEquals(1 + 36, after4Inserts); @@ -195,23 +197,23 @@ public void testIntLongDoubleFloat() { @Test public void testIntLongDoubleFloat2() { KVStorage index = create(); - List list = new ArrayList<>(); - list.add(new KeyValue("int", 4)); - list.add(new KeyValue("long", 4L)); - list.add(new KeyValue("double", 4d)); - list.add(new KeyValue("float", 4f)); - long allInOne = index.add(list); + Map map = new LinkedHashMap<>(); + map.put("int", new KValue(4)); + map.put("long", new KValue(4L)); + map.put("double", new KValue(4d)); + map.put("float", new KValue(4f)); + long allInOne = index.add(map); - long afterMapInsert = index.add(createKV("somenext", 0)); + long afterMapInsert = index.add(Map.of("somenext", new KValue(0))); // 1 + 1 + (2+4) + (2+8) + (2+8) + (2+4) assertEquals(1 + 1 + 32, afterMapInsert); - List resMap = index.getAll(allInOne); - assertEquals(4, resMap.get(0).value); - assertEquals(4L, resMap.get(1).value); - assertEquals(4d, resMap.get(2).value); - assertEquals(4f, resMap.get(3).value); + Map resMap = index.getAll(allInOne); + assertEquals(4, resMap.get("int").getFwd()); + assertEquals(4L, resMap.get("long").getFwd()); + assertEquals(4d, resMap.get("double").getFwd()); + assertEquals(4f, resMap.get("float").getFwd()); } @Test @@ -219,7 +221,7 @@ public void testFlush() { Helper.removeDir(new File(location)); KVStorage index = new KVStorage(new RAMDirectory(location, true).create(), true); - long pointer = index.add(createList("", "test")); + long pointer = index.add(createMap("", "test")); index.flush(); index.close(); @@ -227,7 +229,7 @@ public void testFlush() { assertTrue(index.loadExisting()); assertEquals("test", index.get(pointer, "", false)); // make sure bytePointer is correctly set after loadExisting - long newPointer = index.add(createList("", "testing")); + long newPointer = index.add(createMap("", "testing")); assertEquals(pointer + 1 + 3 + "test".getBytes().length, newPointer, newPointer + ">" + pointer); index.close(); @@ -239,9 +241,9 @@ public void testLoadKeys() { Helper.removeDir(new File(location)); KVStorage index = new KVStorage(new RAMDirectory(location, true).create(), true).create(1000); - long pointerA = index.add(createList("c", "test value")); + long pointerA = index.add(createMap("c", "test value")); assertEquals(2, index.getKeys().size()); - long pointerB = index.add(createList("a", "value", "b", "another value")); + long pointerB = index.add(createMap("a", "value", "b", "another value")); // empty string is always the first key assertEquals("[, c, a, b]", index.getKeys().toString()); index.flush(); @@ -256,7 +258,7 @@ public void testLoadKeys() { assertNull(index.get(pointerB, "", false)); assertEquals("value", index.get(pointerB, "a", false)); assertEquals("another value", index.get(pointerB, "b", false)); - assertEquals("[a=value (true|true), b=another value (true|true)]", index.getAll(pointerB).toString()); + assertEquals("{a=value, b=another value}", index.getAll(pointerB).toString()); index.close(); Helper.removeDir(new File(location)); @@ -265,8 +267,8 @@ public void testLoadKeys() { @Test public void testEmptyKey() { KVStorage index = create(); - long pointerA = index.add(createList("", "test value")); - long pointerB = index.add(createList("a", "value", "b", "another value")); + long pointerA = index.add(createMap("", "test value")); + long pointerB = index.add(createMap("a", "value", "b", "another value")); assertEquals("test value", index.get(pointerA, "", false)); assertNull(index.get(pointerA, "a", false)); @@ -275,24 +277,36 @@ public void testEmptyKey() { assertNull(index.get(pointerB, "", false)); } + @Test + public void testDifferentValuePerDirection() { + Map map = new LinkedHashMap<>(); + map.put("test", new KValue("forw", "back")); + + KVStorage index = create(); + long pointerA = index.add(map); + + assertEquals("forw", index.get(pointerA, "test", false)); + assertEquals("back", index.get(pointerA, "test", true)); + } + @Test public void testSameByteArray() { KVStorage index = create(); - long pointerA = index.add(createList("mykey", new byte[]{1, 2, 3, 4})); - long pointerB = index.add(createList("mykey", new byte[]{1, 2, 3, 4})); + long pointerA = index.add(createMap("mykey", new byte[]{1, 2, 3, 4})); + long pointerB = index.add(createMap("mykey", new byte[]{1, 2, 3, 4})); assertEquals(pointerA, pointerB); byte[] sameRef = new byte[]{1, 2, 3, 4}; - pointerA = index.add(createList("mykey", sameRef)); - pointerB = index.add(createList("mykey", sameRef)); + pointerA = index.add(createMap("mykey", sameRef)); + pointerB = index.add(createMap("mykey", sameRef)); assertEquals(pointerA, pointerB); } @Test public void testUnknownValueClass() { KVStorage index = create(); - IllegalArgumentException ex = assertThrows(IllegalArgumentException.class, () -> index.add(createList("mykey", new Object()))); + IllegalArgumentException ex = assertThrows(IllegalArgumentException.class, () -> index.add(createMap("mykey", new Object()))); assertTrue(ex.getMessage().contains("The Class of a value was Object, currently supported"), ex.getMessage()); } @@ -303,12 +317,12 @@ public void testRandom() { KVStorage index = new KVStorage(new RAMDirectory(location, true).create(), true).create(1000); Random random = new Random(seed); List keys = createRandomStringList(random, "_key", 100); - List values = createRandomList(random, 500); + List values = createRandomMap(random, 500); int size = 10000; LongArrayList pointers = new LongArrayList(size); for (int i = 0; i < size; i++) { - List list = createRandomList(random, keys, values); + Map list = createRandomMap(random, keys, values); long pointer = index.add(list); try { assertEquals(list.size(), index.getAll(pointer).size(), "" + i); @@ -319,11 +333,11 @@ public void testRandom() { } for (int i = 0; i < size; i++) { - List list = index.getAll(pointers.get(i)); - assertTrue(list.size() > 0, i + " " + list); - for (KeyValue entry : list) { - Object value = index.get(pointers.get(i), entry.key, false); - assertEquals(entry.value, value, i + " " + list); + Map map = index.getAll(pointers.get(i)); + assertFalse(map.isEmpty(), i + " " + map); + for (Map.Entry entry : map.entrySet()) { + Object value = index.get(pointers.get(i), entry.getKey(), false); + assertEquals(entry.getValue().getFwd(), value, i + " " + map); } } index.flush(); @@ -332,11 +346,11 @@ public void testRandom() { index = new KVStorage(new RAMDirectory(location, true).create(), true); assertTrue(index.loadExisting()); for (int i = 0; i < size; i++) { - List list = index.getAll(pointers.get(i)); - assertTrue(list.size() > 0, i + " " + list); - for (KeyValue entry : list) { - Object value = index.get(pointers.get(i), entry.key, false); - assertEquals(entry.value, value, i + " " + list); + Map map = index.getAll(pointers.get(i)); + assertFalse(map.isEmpty(), i + " " + map); + for (Map.Entry entry : map.entrySet()) { + Object value = index.get(pointers.get(i), entry.getKey(), false); + assertEquals(entry.getValue().getFwd(), value, i + " " + map); } } index.close(); @@ -345,7 +359,7 @@ public void testRandom() { } } - private List createRandomList(Random random, int size) { + private List createRandomMap(Random random, int size) { List list = new ArrayList<>(); for (int i = 0; i < size; i++) { list.add(random.nextInt(size * 5)); @@ -361,16 +375,16 @@ private List createRandomStringList(Random random, String postfix, int s return list; } - private List createRandomList(Random random, List keys, List values) { + private Map createRandomMap(Random random, List keys, List values) { int count = random.nextInt(10) + 2; Set avoidDuplicates = new HashSet<>(); // otherwise index.get returns potentially wrong value - List list = new ArrayList<>(); + Map list = new LinkedHashMap<>(); for (int i = 0; i < count; i++) { String key = keys.get(random.nextInt(keys.size())); if (!avoidDuplicates.add(key)) continue; Object o = values.get(random.nextInt(values.size())); - list.add(new KeyValue(key, key.endsWith("_s") ? o + "_s" : o)); + list.put(key, new KValue(key.endsWith("_s") ? o + "_s" : o)); } return list; } diff --git a/core/src/test/java/com/graphhopper/storage/AbstractGraphStorageTester.java b/core/src/test/java/com/graphhopper/storage/AbstractGraphStorageTester.java index f580cfde7ad..fa728021363 100644 --- a/core/src/test/java/com/graphhopper/storage/AbstractGraphStorageTester.java +++ b/core/src/test/java/com/graphhopper/storage/AbstractGraphStorageTester.java @@ -28,9 +28,10 @@ import org.junit.jupiter.api.Test; import java.io.File; +import java.util.Map; -import static com.graphhopper.search.KVStorage.KeyValue.STREET_NAME; -import static com.graphhopper.search.KVStorage.KeyValue.createKV; +import static com.graphhopper.search.KVStorage.KValue; +import static com.graphhopper.util.Parameters.Details.STREET_NAME; import static org.junit.jupiter.api.Assertions.*; /** @@ -265,7 +266,7 @@ public void testUpdateUnidirectional() { public void testCopyProperties() { graph = createGHStorage(); EdgeIteratorState edge = graph.edge(1, 3).setDistance(10).set(carAccessEnc, true, false). - setKeyValues(createKV(STREET_NAME, "testing")).setWayGeometry(Helper.createPointList(1, 2)); + setKeyValues(Map.of(STREET_NAME, new KValue("testing"))).setWayGeometry(Helper.createPointList(1, 2)); EdgeIteratorState newEdge = graph.edge(1, 3).setDistance(10).set(carAccessEnc, true, false); newEdge.copyPropertiesFrom(edge); @@ -638,10 +639,10 @@ public void testGetAllEdges() { public void testKVStorage() { graph = createGHStorage(); EdgeIteratorState iter1 = graph.edge(0, 1).setDistance(10).set(carAccessEnc, true, true); - iter1.setKeyValues(createKV(STREET_NAME, "named street1")); + iter1.setKeyValues(Map.of(STREET_NAME, new KValue("named street1"))); EdgeIteratorState iter2 = graph.edge(0, 1).setDistance(10).set(carAccessEnc, true, true); - iter2.setKeyValues(createKV(STREET_NAME, "named street2")); + iter2.setKeyValues(Map.of(STREET_NAME, new KValue("named street2"))); assertEquals(graph.getEdgeIteratorState(iter1.getEdge(), iter1.getAdjNode()).getName(), "named street1"); assertEquals(graph.getEdgeIteratorState(iter2.getEdge(), iter2.getAdjNode()).getName(), "named street2"); @@ -664,8 +665,7 @@ public void test8AndMoreBytesForEdgeFlags() { IntsRef intsRef = manager.createEdgeFlags(); intsRef.ints[0] = Integer.MAX_VALUE / 3; edge.setFlags(intsRef); - // System.out.println(BitUtil.LITTLE.toBitString(Long.MAX_VALUE / 3) + "\n" + BitUtil.LITTLE.toBitString(edge.getFlags())); - assertEquals(Integer.MAX_VALUE / 3, edge.getFlags().ints[0]); + assertEquals(Integer.MAX_VALUE / 3, intsRef.ints[0]); graph.close(); graph = new BaseGraph.Builder(manager).create(); @@ -725,21 +725,21 @@ public void testDontGrowOnUpdate() { na.setNode(2, 12, 12, 0.4); EdgeIteratorState iter2 = graph.edge(0, 1).setDistance(100).set(carAccessEnc, true, true); - final BaseGraph baseGraph = (BaseGraph) graph.getBaseGraph(); - assertEquals(4, baseGraph.getMaxGeoRef()); + final BaseGraph baseGraph = graph.getBaseGraph(); + assertEquals(1, baseGraph.getMaxGeoRef()); iter2.setWayGeometry(Helper.createPointList3D(1, 2, 3, 3, 4, 5, 5, 6, 7, 7, 8, 9)); - assertEquals(4 + (1 + 12), baseGraph.getMaxGeoRef()); + assertEquals(1 + 3 + 4 * 11, baseGraph.getMaxGeoRef()); iter2.setWayGeometry(Helper.createPointList3D(1, 2, 3, 3, 4, 5, 5, 6, 7)); - assertEquals(4 + (1 + 12), baseGraph.getMaxGeoRef()); + assertEquals(1 + 3 + 4 * 11, baseGraph.getMaxGeoRef()); iter2.setWayGeometry(Helper.createPointList3D(1, 2, 3, 3, 4, 5)); - assertEquals(4 + (1 + 12), baseGraph.getMaxGeoRef()); + assertEquals(1 + 3 + 4 * 11, baseGraph.getMaxGeoRef()); iter2.setWayGeometry(Helper.createPointList3D(1, 2, 3)); - assertEquals(4 + (1 + 12), baseGraph.getMaxGeoRef()); - iter2.setWayGeometry(Helper.createPointList3D(1.5, 1, 0, 2, 3, 0)); - assertEquals(4 + (1 + 12) + (1 + 6), baseGraph.getMaxGeoRef()); + assertEquals(1 + 3 + 4 * 11, baseGraph.getMaxGeoRef()); + assertThrows(IllegalStateException.class, () -> iter2.setWayGeometry(Helper.createPointList3D(1.5, 1, 0, 2, 3, 0))); + assertEquals(1 + 3 + 4 * 11, baseGraph.getMaxGeoRef()); EdgeIteratorState iter1 = graph.edge(0, 2).setDistance(200).set(carAccessEnc, true, true); iter1.setWayGeometry(Helper.createPointList3D(3.5, 4.5, 0, 5, 6, 0)); - assertEquals(4 + (1 + 12) + (1 + 6) + (1 + 6), baseGraph.getMaxGeoRef()); + assertEquals(1 + 3 + 4 * 11 + (3 + 2 * 11), baseGraph.getMaxGeoRef()); } @Test diff --git a/core/src/test/java/com/graphhopper/storage/BaseGraphTest.java b/core/src/test/java/com/graphhopper/storage/BaseGraphTest.java index 06b71845e97..84dcf86da55 100644 --- a/core/src/test/java/com/graphhopper/storage/BaseGraphTest.java +++ b/core/src/test/java/com/graphhopper/storage/BaseGraphTest.java @@ -17,19 +17,22 @@ */ package com.graphhopper.storage; +import com.carrotsearch.hppc.IntArrayList; import com.graphhopper.routing.ev.EnumEncodedValue; import com.graphhopper.routing.ev.RoadClass; -import com.graphhopper.search.KVStorage.KeyValue; +import com.graphhopper.search.KVStorage.KValue; import com.graphhopper.util.*; import com.graphhopper.util.shapes.BBox; import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; -import java.util.ArrayList; -import java.util.List; +import java.util.LinkedHashMap; +import java.util.Map; -import static com.graphhopper.search.KVStorage.KeyValue.STREET_NAME; import static com.graphhopper.util.EdgeIteratorState.REVERSE_STATE; import static com.graphhopper.util.FetchMode.*; +import static com.graphhopper.util.Parameters.Details.STREET_NAME; import static org.junit.jupiter.api.Assertions.*; /** @@ -70,14 +73,15 @@ public void testSave_and_fileFormat() { graph.edge(9, 11).setDistance(200).set(carAccessEnc, true, true); graph.edge(1, 2).setDistance(120).set(carAccessEnc, true, false); - iter1.setKeyValues(KeyValue.createKV(STREET_NAME, "named street1")); - iter2.setKeyValues(KeyValue.createKV(STREET_NAME, "named street2")); + iter1.setKeyValues(Map.of(STREET_NAME, new KValue("named street1"))); + iter2.setKeyValues(Map.of(STREET_NAME, new KValue("named street2"))); - List list = new ArrayList<>(); - list.add(new KeyValue("keyA", "FORWARD", true, false)); - list.add(new KeyValue("keyB", "BACKWARD", false, true)); - list.add(new KeyValue("keyC", "BOTH", true, true)); - iter3.setKeyValues(list); + Map map = new LinkedHashMap<>(); + map.put("keyA", new KValue("FORWARD", null)); + map.put("keyB", new KValue(null, "BACKWARD")); + map.put("keyC", new KValue("BOTH")); + map.put("keyD", new KValue("BOTH2", "BOTH2")); + iter3.setKeyValues(map); checkGraph(graph); graph.flush(); @@ -92,8 +96,8 @@ public void testSave_and_fileFormat() { assertEquals("named street1", graph.getEdgeIteratorState(iter1.getEdge(), iter1.getAdjNode()).getName()); assertEquals("named street2", graph.getEdgeIteratorState(iter2.getEdge(), iter2.getAdjNode()).getName()); iter3 = graph.getEdgeIteratorState(iter3.getEdge(), iter3.getAdjNode()); - assertEquals(list, iter3.getKeyValues()); - assertEquals(list, iter3.detach(true).getKeyValues()); + assertEquals(map, iter3.getKeyValues()); + assertEquals(map, iter3.detach(true).getKeyValues()); assertEquals("FORWARD", iter3.getValue("keyA")); assertNull(iter3.getValue("keyB")); @@ -101,6 +105,7 @@ public void testSave_and_fileFormat() { assertNull(iter3.detach(true).getValue("keyA")); assertEquals("BACKWARD", iter3.detach(true).getValue("keyB")); assertEquals("BOTH", iter3.detach(true).getValue("keyC")); + assertEquals("BOTH2", iter3.getValue("keyD")); GHUtility.setSpeed(60, true, true, carAccessEnc, carSpeedEnc, graph.edge(3, 4).setDistance(123)). setWayGeometry(Helper.createPointList3D(4.4, 5.5, 0, 6.6, 7.7, 0)); @@ -271,7 +276,7 @@ public void outOfBounds() { public void setGetFlagsRaw() { BaseGraph graph = new BaseGraph.Builder(1).create(); EdgeIteratorState edge = graph.edge(0, 1); - IntsRef flags = new IntsRef(graph.getIntsForFlags()); + IntsRef flags = encodingManager.createEdgeFlags(); flags.ints[0] = 10; edge.setFlags(flags); assertEquals(10, edge.getFlags().ints[0]); @@ -289,4 +294,117 @@ public void setGetFlags() { edge.set(rcEnc, RoadClass.CORRIDOR); assertEquals(RoadClass.CORRIDOR, edge.get(rcEnc)); } + + @Test + public void copyEdge() { + BaseGraph graph = createGHStorage(); + EnumEncodedValue rcEnc = encodingManager.getEnumEncodedValue(RoadClass.KEY, RoadClass.class); + EdgeIteratorState edge1 = graph.edge(3, 5).set(rcEnc, RoadClass.LIVING_STREET); + EdgeIteratorState edge2 = graph.edge(3, 5).set(rcEnc, RoadClass.MOTORWAY); + EdgeIteratorState edge3 = graph.copyEdge(edge1.getEdge(), true); + EdgeIteratorState edge4 = graph.copyEdge(edge1.getEdge(), false); + assertEquals(RoadClass.LIVING_STREET, edge1.get(rcEnc)); + assertEquals(RoadClass.MOTORWAY, edge2.get(rcEnc)); + assertEquals(edge1.get(rcEnc), edge3.get(rcEnc)); + assertEquals(edge1.get(rcEnc), edge4.get(rcEnc)); + graph.forEdgeAndCopiesOfEdge(graph.createEdgeExplorer(), edge1, e -> e.set(rcEnc, RoadClass.FOOTWAY)); + assertEquals(RoadClass.FOOTWAY, edge1.get(rcEnc)); + assertEquals(RoadClass.FOOTWAY, edge3.get(rcEnc)); + // edge4 was not changed because it was copied with reuseGeometry=false + assertEquals(RoadClass.LIVING_STREET, edge4.get(rcEnc)); + } + + @ParameterizedTest + @ValueSource(booleans = {true, false}) + public void copyEdge_multiple(boolean withGeometries) { + BaseGraph graph = createGHStorage(); + EnumEncodedValue rcEnc = encodingManager.getEnumEncodedValue(RoadClass.KEY, RoadClass.class); + EdgeIteratorState edge1 = graph.edge(1, 2).set(rcEnc, RoadClass.FOOTWAY); + EdgeIteratorState edge2 = graph.edge(1, 3).set(rcEnc, RoadClass.MOTORWAY); + EdgeIteratorState edge3 = graph.edge(1, 4).set(rcEnc, RoadClass.CYCLEWAY); + if (withGeometries) { + edge1.setWayGeometry(Helper.createPointList(1.5, 1, 0, 2, 3, 0)); + edge2.setWayGeometry(Helper.createPointList(1.5, 1, 1, 2, 3, 5)); + edge3.setWayGeometry(Helper.createPointList(1.5, 1, 2, 2, 3, 6)); + } + EdgeIteratorState edge4 = graph.copyEdge(edge1.getEdge(), true); + EdgeIteratorState edge5 = graph.copyEdge(edge3.getEdge(), true); + EdgeIteratorState edge6 = graph.copyEdge(edge3.getEdge(), true); + EdgeExplorer explorer = graph.createEdgeExplorer(); + graph.forEdgeAndCopiesOfEdge(explorer, edge1, e -> e.set(rcEnc, RoadClass.PATH)); + assertEquals(RoadClass.PATH, edge1.get(rcEnc)); + assertEquals(RoadClass.CYCLEWAY, edge3.get(rcEnc)); + assertEquals(RoadClass.PATH, edge4.get(rcEnc)); + assertEquals(RoadClass.CYCLEWAY, edge5.get(rcEnc)); + assertEquals(RoadClass.CYCLEWAY, edge6.get(rcEnc)); + graph.forEdgeAndCopiesOfEdge(explorer, edge6, e -> e.set(rcEnc, RoadClass.OTHER)); + assertEquals(RoadClass.PATH, edge1.get(rcEnc)); + assertEquals(RoadClass.OTHER, edge3.get(rcEnc)); + assertEquals(RoadClass.PATH, edge4.get(rcEnc)); + assertEquals(RoadClass.OTHER, edge5.get(rcEnc)); + assertEquals(RoadClass.OTHER, edge6.get(rcEnc)); + } + + @ParameterizedTest + @ValueSource(booleans = {true, false}) + public void forEdgeAndCopiesOfEdge_noCopyNoGeo(boolean withGeometries) { + BaseGraph graph = createGHStorage(); + EdgeIteratorState edge1 = graph.edge(0, 1); + EdgeIteratorState edge2 = graph.edge(1, 2); + if (withGeometries) { + edge1.setWayGeometry(Helper.createPointList(1.5, 1, 0, 2, 3, 0)); + edge2.setWayGeometry(Helper.createPointList(3, 0, 4, 5, 4.5, 5.5)); + } + IntArrayList edges = new IntArrayList(); + graph.forEdgeAndCopiesOfEdge(graph.createEdgeExplorer(), edge2, e -> { + edges.add(e.getEdge()); + }); + assertEquals(IntArrayList.from(edge2.getEdge()), edges); + + edges.clear(); + EdgeIteratorState edge3 = graph.copyEdge(edge2.getEdge(), true); + graph.forEdgeAndCopiesOfEdge(graph.createEdgeExplorer(), edge2, e -> { + edges.add(e.getEdge()); + }); + assertEquals(IntArrayList.from(edge3.getEdge(), edge2.getEdge()), edges); + } + + @Test + public void copyEdge_changeGeometry() { + BaseGraph graph = createGHStorage(); + EnumEncodedValue rcEnc = encodingManager.getEnumEncodedValue(RoadClass.KEY, RoadClass.class); + EdgeIteratorState edge1 = graph.edge(1, 2).set(rcEnc, RoadClass.FOOTWAY); + EdgeIteratorState edge2 = graph.edge(1, 3).set(rcEnc, RoadClass.FOOTWAY).setWayGeometry(Helper.createPointList(0, 1, 2, 3)); + EdgeIteratorState edge3 = graph.edge(1, 4).set(rcEnc, RoadClass.FOOTWAY).setWayGeometry(Helper.createPointList(4, 5, 6, 7)); + EdgeIteratorState edge4 = graph.copyEdge(edge1.getEdge(), true); + EdgeIteratorState edge5 = graph.copyEdge(edge3.getEdge(), true); + + // after copying an edge we can no longer change the geometry + assertThrows(IllegalStateException.class, () -> graph.getEdgeIteratorState(edge1.getEdge(), Integer.MIN_VALUE).setWayGeometry(Helper.createPointList(1.5, 1, 5, 4))); + // after setting the geometry once we can change it again + graph.getEdgeIteratorState(edge2.getEdge(), Integer.MIN_VALUE).setWayGeometry(Helper.createPointList(2, 3, 4, 5)); + // ... but not if it is longer than before + IllegalStateException e = assertThrows(IllegalStateException.class, () -> graph.getEdgeIteratorState(edge2.getEdge(), Integer.MIN_VALUE).setWayGeometry(Helper.createPointList(2, 3, 4, 5, 6, 7))); + assertTrue(e.getMessage().contains("This edge already has a way geometry so it cannot be changed to a bigger geometry"), e.getMessage()); + // it's the same for edges with geometry that were copied: + graph.getEdgeIteratorState(edge3.getEdge(), Integer.MIN_VALUE).setWayGeometry(Helper.createPointList(6, 7, 8, 9)); + e = assertThrows(IllegalStateException.class, () -> graph.getEdgeIteratorState(edge3.getEdge(), Integer.MIN_VALUE).setWayGeometry(Helper.createPointList(0, 1, 6, 7, 8, 9))); + assertTrue(e.getMessage().contains("This edge already has a way geometry so it cannot be changed to a bigger geometry"), e.getMessage()); + } + + @Test + public void testGeoRef() { + BaseGraph graph = createGHStorage(); + BaseGraphNodesAndEdges ne = graph.getStore(); + ne.setGeoRef(0, 123); + assertEquals(123, ne.getGeoRef(0)); + ne.setGeoRef(0, -123); + assertEquals(-123, ne.getGeoRef(0)); + ne.setGeoRef(0, 1L << 38); + assertEquals(1L << 38, ne.getGeoRef(0)); + + // 1000_0000 0000_0000 0000_0000 0000_0000 0000_0000 + assertThrows(IllegalArgumentException.class, () -> ne.setGeoRef(0, 1L << 39)); + graph.close(); + } } diff --git a/core/src/test/java/com/graphhopper/storage/BaseGraphWithTurnCostsTest.java b/core/src/test/java/com/graphhopper/storage/BaseGraphWithTurnCostsTest.java index 68df0eec0bb..b649f369ada 100644 --- a/core/src/test/java/com/graphhopper/storage/BaseGraphWithTurnCostsTest.java +++ b/core/src/test/java/com/graphhopper/storage/BaseGraphWithTurnCostsTest.java @@ -21,14 +21,15 @@ import com.graphhopper.routing.ev.RoadClass; import com.graphhopper.routing.ev.TurnCost; import com.graphhopper.routing.util.EncodingManager; -import com.graphhopper.search.KVStorage.KeyValue; +import com.graphhopper.search.KVStorage; import com.graphhopper.util.EdgeIteratorState; import com.graphhopper.util.Helper; import org.junit.jupiter.api.Test; +import java.util.Map; import java.util.Random; -import static com.graphhopper.search.KVStorage.KeyValue.STREET_NAME; +import static com.graphhopper.util.Parameters.Details.STREET_NAME; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertTrue; @@ -81,8 +82,8 @@ public void testSave_and_fileFormat() { setTurnCost(iter2.getEdge(), 0, iter1.getEdge(), 666); setTurnCost(iter1.getEdge(), 1, iter2.getEdge(), 815); - iter1.setKeyValues(KeyValue.createKV(STREET_NAME, "named street1")); - iter2.setKeyValues(KeyValue.createKV(STREET_NAME, "named street2")); + iter1.setKeyValues(Map.of(STREET_NAME, new KVStorage.KValue( "named street1"))); + iter2.setKeyValues(Map.of(STREET_NAME, new KVStorage.KValue( "named street2"))); checkGraph(graph); graph.flush(); diff --git a/core/src/test/java/com/graphhopper/storage/DataAccessTest.java b/core/src/test/java/com/graphhopper/storage/DataAccessTest.java index caee43a03b0..68e2550f82f 100644 --- a/core/src/test/java/com/graphhopper/storage/DataAccessTest.java +++ b/core/src/test/java/com/graphhopper/storage/DataAccessTest.java @@ -24,6 +24,7 @@ import org.junit.jupiter.api.Test; import java.io.File; +import java.util.Random; import static org.junit.jupiter.api.Assertions.*; @@ -255,4 +256,30 @@ public void testSet_Get_Short_Long() { } da.close(); } + + @Test + public void testPadding() { + DataAccess da = createDataAccess(name); + da.create(10); + da.ensureCapacity(12_800); + assertEquals(100, da.getSegments()); + int val = Integer.MAX_VALUE / 2; + for (int i = 0; i < 10_000; i++) { + da.setInt(i, val * i); + assertEquals(val * i, da.getInt(i), "idx " + i); + da.setInt(i, -val * i); + assertEquals(-val * i, da.getInt(i), "idx " + i); + } + + Random rand = new Random(0); + for (int i = 0; i < 10_000; i++) { + val = 1 << rand.nextInt(32) + rand.nextInt(); + da.setInt(i, val); + assertEquals(val, da.getInt(i), "idx " + i); + da.setInt(i, -val); + assertEquals(-val, da.getInt(i), "idx " + i); + } + + da.close(); + } } diff --git a/core/src/test/java/com/graphhopper/storage/TurnCostStorageTest.java b/core/src/test/java/com/graphhopper/storage/TurnCostStorageTest.java index 89bea6fe82b..5468f321c87 100644 --- a/core/src/test/java/com/graphhopper/storage/TurnCostStorageTest.java +++ b/core/src/test/java/com/graphhopper/storage/TurnCostStorageTest.java @@ -28,6 +28,7 @@ import java.util.HashSet; import java.util.List; import java.util.Set; +import java.util.stream.IntStream; import static com.graphhopper.util.GHUtility.getEdge; import static org.junit.jupiter.api.Assertions.assertEquals; @@ -92,6 +93,8 @@ public void testMultipleTurnCosts() { turnCostStorage.set(bikeEnc, edge31, 1, edge10, Double.POSITIVE_INFINITY); turnCostStorage.set(bikeEnc, edge02, 2, edge24, Double.POSITIVE_INFINITY); + assertEquals(turnCostStorage.getTurnCostsCount(), IntStream.range(0, g.getNodes()).map(turnCostStorage::getTurnCostsCount).sum()); + assertEquals(Double.POSITIVE_INFINITY, turnCostStorage.get(carEnc, edge42, 2, edge23), 0); assertEquals(Double.POSITIVE_INFINITY, turnCostStorage.get(bikeEnc, edge42, 2, edge23), 0); diff --git a/core/src/test/java/com/graphhopper/storage/index/SnapTest.java b/core/src/test/java/com/graphhopper/storage/index/SnapTest.java new file mode 100644 index 00000000000..5075106b067 --- /dev/null +++ b/core/src/test/java/com/graphhopper/storage/index/SnapTest.java @@ -0,0 +1,63 @@ +/* + * 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.index; + +import com.graphhopper.storage.BaseGraph; +import com.graphhopper.storage.NodeAccess; +import com.graphhopper.util.EdgeIteratorState; +import com.graphhopper.util.shapes.GHPoint; +import org.junit.jupiter.api.Test; + +import static com.graphhopper.util.DistancePlaneProjection.DIST_PLANE; +import static org.junit.jupiter.api.Assertions.*; + +class SnapTest { + + @Test + void snapToCloseTower() { + // see #3009 + BaseGraph graph = new BaseGraph.Builder(1).create(); + EdgeIteratorState edge = graph.edge(0, 1); + NodeAccess na = graph.getNodeAccess(); + na.setNode(0, 40.000_000, 6.000_000); + na.setNode(1, 40.000_000, 6.000_101); + double queryLat = 40.001_000; + double queryLon = 6.000_1009; + Snap snap = new Snap(queryLat, queryLon); + snap.setClosestEdge(edge); + // We set the base node to the closest node, even though the crossing point is closer to + // the adj node. Not sure if LocationIndexTree can really produce this situation. + snap.setClosestNode(edge.getBaseNode()); + snap.setWayIndex(0); + snap.setSnappedPosition(Snap.Position.EDGE); + // the crossing point is very close to the adj node + GHPoint crossingPoint = DIST_PLANE.calcCrossingPointToEdge(queryLat, queryLon, + na.getLat(edge.getBaseNode()), na.getLon(edge.getBaseNode()), na.getLat(edge.getAdjNode()), na.getLon(edge.getAdjNode())); + double distCrossingTo0 = DIST_PLANE.calcDist(crossingPoint.lat, crossingPoint.lon, na.getLat(edge.getBaseNode()), na.getLon(edge.getBaseNode())); + double distCrossingTo1 = DIST_PLANE.calcDist(crossingPoint.lat, crossingPoint.lon, na.getLat(edge.getAdjNode()), na.getLon(edge.getAdjNode())); + assertEquals(8.594, distCrossingTo0, 1.e-3); + assertEquals(0.008, distCrossingTo1, 1.e-3); + // the snapped point snaps to the adj tower node, so the coordinates must the same + snap.calcSnappedPoint(DIST_PLANE); + assertEquals(na.getLat(snap.getClosestNode()), snap.getSnappedPoint().getLat()); + assertEquals(na.getLon(snap.getClosestNode()), snap.getSnappedPoint().getLon()); + assertEquals(edge.getAdjNode(), snap.getClosestNode()); + } + +} diff --git a/core/src/test/java/com/graphhopper/util/BitUtilLittleTest.java b/core/src/test/java/com/graphhopper/util/BitUtilLittleTest.java deleted file mode 100644 index 71cadf3f09a..00000000000 --- a/core/src/test/java/com/graphhopper/util/BitUtilLittleTest.java +++ /dev/null @@ -1,89 +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.util; - -import org.junit.jupiter.api.Test; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertTrue; - -/** - * @author Peter Karich - */ -public class BitUtilLittleTest extends AbstractBitUtilTester { - @Override - BitUtil getBitUtil() { - return BitUtil.LITTLE; - } - - @Test - public void testToBitString() { - assertEquals("0010101010101010101010101010101010101010101010101010101010101010", bitUtil.toBitString(Long.MAX_VALUE / 3)); - assertEquals("0111111111111111111111111111111111111111111111111111111111111111", bitUtil.toBitString(Long.MAX_VALUE)); - - assertEquals("00101010101010101010101010101010", bitUtil.toBitString(bitUtil.fromInt(Integer.MAX_VALUE / 3))); - - assertEquals("10000000000000000000000000000000", bitUtil.toBitString(1L << 63, 32)); - assertEquals("00000000000000000000000000000001", bitUtil.toBitString((1L << 32), 32)); - } - - @Test - public void testFromBitString() { - String str = "001110110"; - assertEquals(str + "0000000", bitUtil.toBitString(bitUtil.fromBitString(str))); - - str = "01011110010111000000111111000111"; - assertEquals(str, bitUtil.toBitString(bitUtil.fromBitString(str))); - - str = "0101111001011100000011111100011"; - assertEquals(str + "0", bitUtil.toBitString(bitUtil.fromBitString(str))); - } - - @Test - public void testCountBitValue() { - assertEquals(1, BitUtil.countBitValue(1)); - assertEquals(2, BitUtil.countBitValue(2)); - assertEquals(2, BitUtil.countBitValue(3)); - assertEquals(3, BitUtil.countBitValue(4)); - assertEquals(3, BitUtil.countBitValue(7)); - assertEquals(4, BitUtil.countBitValue(8)); - assertEquals(5, BitUtil.countBitValue(20)); - } - - @Test - public void testUnsignedConversions() { - long l = Integer.toUnsignedLong(-1); - assertEquals(4294967295L, l); - assertEquals(-1, BitUtil.toSignedInt(l)); - - int intVal = Integer.MAX_VALUE; - long maxInt = intVal; - assertEquals(intVal, BitUtil.toSignedInt(maxInt)); - - intVal++; - maxInt = Integer.toUnsignedLong(intVal); - assertEquals(intVal, BitUtil.toSignedInt(maxInt)); - - intVal++; - maxInt = Integer.toUnsignedLong(intVal); - assertEquals(intVal, BitUtil.toSignedInt(maxInt)); - - assertEquals(0xFFFFffffL, (1L << 32) - 1); - assertTrue(0xFFFFffffL > 0L); - } -} diff --git a/core/src/test/java/com/graphhopper/util/AbstractBitUtilTester.java b/core/src/test/java/com/graphhopper/util/BitUtilTest.java similarity index 55% rename from core/src/test/java/com/graphhopper/util/AbstractBitUtilTester.java rename to core/src/test/java/com/graphhopper/util/BitUtilTest.java index 7aa05a41a93..7c32afb952c 100644 --- a/core/src/test/java/com/graphhopper/util/AbstractBitUtilTester.java +++ b/core/src/test/java/com/graphhopper/util/BitUtilTest.java @@ -20,14 +20,69 @@ import org.junit.jupiter.api.Test; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; /** * @author Peter Karich */ -public abstract class AbstractBitUtilTester { - protected BitUtil bitUtil = getBitUtil(); +public class BitUtilTest { + private static final BitUtil bitUtil = BitUtil.LITTLE; - abstract BitUtil getBitUtil(); + @Test + public void testToBitString() { + assertEquals("0010101010101010101010101010101010101010101010101010101010101010", bitUtil.toBitString(Long.MAX_VALUE / 3)); + assertEquals("0111111111111111111111111111111111111111111111111111111111111111", bitUtil.toBitString(Long.MAX_VALUE)); + + assertEquals("00101010101010101010101010101010", bitUtil.toBitString(bitUtil.fromInt(Integer.MAX_VALUE / 3))); + + assertEquals("10000000000000000000000000000000", bitUtil.toBitString(1L << 63, 32)); + assertEquals("00000000000000000000000000000001", bitUtil.toBitString((1L << 32), 32)); + } + + @Test + public void testFromBitString() { + String str = "001110110"; + assertEquals(str + "0000000", bitUtil.toBitString(bitUtil.fromBitString(str))); + + str = "01011110010111000000111111000111"; + assertEquals(str, bitUtil.toBitString(bitUtil.fromBitString(str))); + + str = "0101111001011100000011111100011"; + assertEquals(str + "0", bitUtil.toBitString(bitUtil.fromBitString(str))); + } + + @Test + public void testCountBitValue() { + assertEquals(1, BitUtil.countBitValue(1)); + assertEquals(2, BitUtil.countBitValue(2)); + assertEquals(2, BitUtil.countBitValue(3)); + assertEquals(3, BitUtil.countBitValue(4)); + assertEquals(3, BitUtil.countBitValue(7)); + assertEquals(4, BitUtil.countBitValue(8)); + assertEquals(5, BitUtil.countBitValue(20)); + } + + @Test + public void testUnsignedConversions() { + long l = Integer.toUnsignedLong(-1); + assertEquals(4294967295L, l); + assertEquals(-1, BitUtil.toSignedInt(l)); + + int intVal = Integer.MAX_VALUE; + long maxInt = intVal; + assertEquals(intVal, BitUtil.toSignedInt(maxInt)); + + intVal++; + maxInt = Integer.toUnsignedLong(intVal); + assertEquals(intVal, BitUtil.toSignedInt(maxInt)); + + intVal++; + maxInt = Integer.toUnsignedLong(intVal); + assertEquals(intVal, BitUtil.toSignedInt(maxInt)); + + assertEquals(0xFFFFffffL, (1L << 32) - 1); + assertTrue(0xFFFFffffL > 0L); + } @Test public void testToFloat() { @@ -98,4 +153,14 @@ public void testToLastBitString() { assertEquals("011", bitUtil.toLastBitString(3L, 3)); } + @Test + public void testUInt3() { + byte[] bytes = new byte[3]; + bitUtil.fromUInt3(bytes, 12345678, 0); + assertEquals(12345678, bitUtil.toUInt3(bytes, 0)); + + bytes = new byte[3]; + bitUtil.fromUInt3(bytes, -12345678, 0); + assertEquals(-12345678 & 0x00FF_FFFF, bitUtil.toUInt3(bytes, 0)); + } } diff --git a/core/src/test/java/com/graphhopper/util/DepthFirstSearchTest.java b/core/src/test/java/com/graphhopper/util/DepthFirstSearchTest.java index de60e0f1610..d90b500aa24 100644 --- a/core/src/test/java/com/graphhopper/util/DepthFirstSearchTest.java +++ b/core/src/test/java/com/graphhopper/util/DepthFirstSearchTest.java @@ -67,7 +67,7 @@ public boolean goFurther(int v) { BooleanEncodedValue accessEnc = new SimpleBooleanEncodedValue("access", true); EncodedValue.InitializerConfig evConf = new EncodedValue.InitializerConfig(); accessEnc.init(evConf); - BaseGraph g = new BaseGraph.Builder(evConf.getRequiredInts()).create(); + BaseGraph g = new BaseGraph.Builder(evConf.getRequiredBytes()).create(); g.edge(1, 2).setDistance(1).set(accessEnc, true, false); g.edge(1, 5).setDistance(1).set(accessEnc, true, false); g.edge(1, 4).setDistance(1).set(accessEnc, true, false); @@ -103,7 +103,7 @@ public boolean goFurther(int v) { BooleanEncodedValue accessEnc = new SimpleBooleanEncodedValue("access", true); EncodedValue.InitializerConfig evConf = new EncodedValue.InitializerConfig(); accessEnc.init(evConf); - BaseGraph g = new BaseGraph.Builder(evConf.getRequiredInts()).create(); + BaseGraph g = new BaseGraph.Builder(evConf.getRequiredBytes()).create(); g.edge(1, 2).setDistance(1).set(accessEnc, true, false); g.edge(1, 4).setDistance(1).set(accessEnc, true, true); g.edge(1, 3).setDistance(1).set(accessEnc, true, false); diff --git a/core/src/test/java/com/graphhopper/util/InstructionListTest.java b/core/src/test/java/com/graphhopper/util/InstructionListTest.java index 28263f8d1d3..755a2359e4b 100644 --- a/core/src/test/java/com/graphhopper/util/InstructionListTest.java +++ b/core/src/test/java/com/graphhopper/util/InstructionListTest.java @@ -35,13 +35,10 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; -import java.util.Locale; +import java.util.*; -import static com.graphhopper.search.KVStorage.KeyValue.STREET_NAME; -import static com.graphhopper.search.KVStorage.KeyValue.createKV; +import static com.graphhopper.search.KVStorage.KValue; +import static com.graphhopper.util.Parameters.Details.STREET_NAME; import static org.junit.jupiter.api.Assertions.*; /** @@ -93,30 +90,30 @@ Graph createTestGraph() { na.setNode(6, 1.0, 1.0); na.setNode(7, 1.0, 1.1); na.setNode(8, 1.0, 1.2); - g.edge(0, 1).setDistance(10000).set(speedEnc, 60, 60).setKeyValues(createKV(STREET_NAME, "0-1")); - g.edge(1, 2).setDistance(11000).set(speedEnc, 60, 60).setKeyValues(createKV(STREET_NAME, "1-2")); + g.edge(0, 1).setDistance(10000).set(speedEnc, 60, 60).setKeyValues(Map.of(STREET_NAME, new KValue("0-1"))); + g.edge(1, 2).setDistance(11000).set(speedEnc, 60, 60).setKeyValues(Map.of(STREET_NAME, new KValue("1-2"))); g.edge(0, 3).setDistance(11000).set(speedEnc, 60, 60); - g.edge(1, 4).setDistance(10000).set(speedEnc, 60, 60).setKeyValues(createKV(STREET_NAME, "1-4")); - g.edge(2, 5).setDistance(11000).set(speedEnc, 60, 60).setKeyValues(createKV(STREET_NAME, "5-2")); + g.edge(1, 4).setDistance(10000).set(speedEnc, 60, 60).setKeyValues(Map.of(STREET_NAME, new KValue("1-4"))); + g.edge(2, 5).setDistance(11000).set(speedEnc, 60, 60).setKeyValues(Map.of(STREET_NAME, new KValue("5-2"))); - g.edge(3, 6).setDistance(11000).set(speedEnc, 60, 60).setKeyValues(createKV(STREET_NAME, "3-6")); - g.edge(4, 7).setDistance(10000).set(speedEnc, 60, 60).setKeyValues(createKV(STREET_NAME, "4-7")); - g.edge(5, 8).setDistance(10000).set(speedEnc, 60, 60).setKeyValues(createKV(STREET_NAME, "5-8")); + g.edge(3, 6).setDistance(11000).set(speedEnc, 60, 60).setKeyValues(Map.of(STREET_NAME, new KValue("3-6"))); + g.edge(4, 7).setDistance(10000).set(speedEnc, 60, 60).setKeyValues(Map.of(STREET_NAME, new KValue("4-7"))); + g.edge(5, 8).setDistance(10000).set(speedEnc, 60, 60).setKeyValues(Map.of(STREET_NAME, new KValue("5-8"))); - g.edge(6, 7).setDistance(11000).set(speedEnc, 60, 60).setKeyValues(createKV(STREET_NAME, "6-7")); + g.edge(6, 7).setDistance(11000).set(speedEnc, 60, 60).setKeyValues(Map.of(STREET_NAME, new KValue("6-7"))); EdgeIteratorState iter = g.edge(7, 8).setDistance(10000).set(speedEnc, 60, 60); PointList list = new PointList(); list.add(1.0, 1.15); list.add(1.0, 1.16); iter.setWayGeometry(list); - iter.setKeyValues(createKV(STREET_NAME, "7-8")); + iter.setKeyValues(Map.of(STREET_NAME, new KValue("7-8"))); // missing edge name g.edge(9, 10).setDistance(10000).set(speedEnc, 60, 60); EdgeIteratorState iter2 = g.edge(8, 9).setDistance(20000).set(speedEnc, 60, 60); list.clear(); list.add(1.0, 1.3); - iter2.setKeyValues(createKV(STREET_NAME, "8-9")); + iter2.setKeyValues(Map.of(STREET_NAME, new KValue("8-9"))); iter2.setWayGeometry(list); return g; } @@ -185,11 +182,11 @@ public void testWayList2() { na.setNode(3, 10.0, 10.08); na.setNode(4, 10.1, 10.10); na.setNode(5, 10.2, 10.13); - g.edge(3, 4).setDistance(100).set(speedEnc, 60, 60).setKeyValues(createKV(STREET_NAME, "3-4")); - g.edge(4, 5).setDistance(100).set(speedEnc, 60, 60).setKeyValues(createKV(STREET_NAME, "4-5")); + g.edge(3, 4).setDistance(100).set(speedEnc, 60, 60).setKeyValues(Map.of(STREET_NAME, new KValue("3-4"))); + g.edge(4, 5).setDistance(100).set(speedEnc, 60, 60).setKeyValues(Map.of(STREET_NAME, new KValue("4-5"))); EdgeIteratorState iter = g.edge(2, 4).setDistance(100).set(speedEnc, 60, 60); - iter.setKeyValues(createKV(STREET_NAME, "2-4")); + iter.setKeyValues(Map.of(STREET_NAME, new KValue("2-4"))); PointList list = new PointList(); list.add(10.20, 10.05); iter.setWayGeometry(list); @@ -224,11 +221,11 @@ public void testNoInstructionIfSameStreet() { na.setNode(3, 10.0, 10.05); na.setNode(4, 10.1, 10.10); na.setNode(5, 10.2, 10.15); - g.edge(3, 4).setDistance(100).set(speedEnc, 60, 60).setKeyValues(createKV(STREET_NAME, "street")); - g.edge(4, 5).setDistance(100).set(speedEnc, 60, 60).setKeyValues(createKV(STREET_NAME, "4-5")); + g.edge(3, 4).setDistance(100).set(speedEnc, 60, 60).setKeyValues(Map.of(STREET_NAME, new KValue("street"))); + g.edge(4, 5).setDistance(100).set(speedEnc, 60, 60).setKeyValues(Map.of(STREET_NAME, new KValue("4-5"))); EdgeIteratorState iter = g.edge(2, 4).setDistance(100).set(speedEnc, 60, 60); - iter.setKeyValues(createKV(STREET_NAME, "street")); + iter.setKeyValues(Map.of(STREET_NAME, new KValue("street"))); PointList list = new PointList(); list.add(10.20, 10.05); iter.setWayGeometry(list); @@ -324,9 +321,9 @@ public void testNoInstructionIfSlightTurnAndAlternativeIsSharp3() { g.edge(2, 3).setDistance(20).set(speedEnc, 18, 18); g.edge(2, 4).setDistance(20).set(speedEnc, 4, 4); - g.edge(1, 2).set(rcEV, RoadClass.RESIDENTIAL).setKeyValues(createKV(STREET_NAME, "pfarr")); - g.edge(2, 3).set(rcEV, RoadClass.RESIDENTIAL).setKeyValues(createKV(STREET_NAME, "pfarr")); - g.edge(2, 4).set(rcEV, RoadClass.PEDESTRIAN).setKeyValues(createKV(STREET_NAME, "markt")); + g.edge(1, 2).set(rcEV, RoadClass.RESIDENTIAL).setKeyValues(Map.of(STREET_NAME, new KValue("pfarr"))); + g.edge(2, 3).set(rcEV, RoadClass.RESIDENTIAL).setKeyValues(Map.of(STREET_NAME, new KValue("pfarr"))); + g.edge(2, 4).set(rcEV, RoadClass.PEDESTRIAN).setKeyValues(Map.of(STREET_NAME, new KValue("markt"))); Weighting weighting = new SpeedWeighting(speedEnc); Path p = new Dijkstra(g, weighting, tMode).calcPath(1, 3); @@ -399,13 +396,13 @@ public void testInstructionIfSlightTurn() { // default is priority=0 so set it to 1 g.edge(1, 2).setDistance(20).set(speedEnc, 5). - setKeyValues(createKV(STREET_NAME, "myroad")); + setKeyValues(Map.of(STREET_NAME, new KValue("myroad"))); g.edge(2, 3).setDistance(20).set(speedEnc, 5). - setKeyValues(createKV(STREET_NAME, "myroad")); + setKeyValues(Map.of(STREET_NAME, new KValue("myroad"))); PointList pointList = new PointList(); pointList.add(43.729627, 7.41749); g.edge(2, 4).setDistance(20).set(speedEnc, 5). - setKeyValues(createKV(STREET_NAME, "myroad")).setWayGeometry(pointList); + setKeyValues(Map.of(STREET_NAME, new KValue("myroad"))).setWayGeometry(pointList); Weighting weighting = new SpeedWeighting(speedEnc); Path p = new Dijkstra(g, weighting, tMode).calcPath(4, 3); @@ -495,12 +492,12 @@ public void testFind() { na.setNode(6, 15.1, 10.1); na.setNode(7, 15.1, 9.8); - g.edge(1, 2).setDistance(10000).set(speedEnc, 60, 60).setKeyValues(createKV(STREET_NAME, "1-2")); - g.edge(2, 3).setDistance(10000).set(speedEnc, 60, 60).setKeyValues(createKV(STREET_NAME, "2-3")); - g.edge(2, 6).setDistance(10000).set(speedEnc, 60, 60).setKeyValues(createKV(STREET_NAME, "2-6")); - g.edge(3, 4).setDistance(10000).set(speedEnc, 60, 60).setKeyValues(createKV(STREET_NAME, "3-4")).setWayGeometry(waypoint); - g.edge(3, 7).setDistance(10000).set(speedEnc, 60, 60).setKeyValues(createKV(STREET_NAME, "3-7")); - g.edge(4, 5).setDistance(10000).set(speedEnc, 60, 60).setKeyValues(createKV(STREET_NAME, "4-5")); + g.edge(1, 2).setDistance(10000).set(speedEnc, 60, 60).setKeyValues(Map.of(STREET_NAME, new KValue("1-2"))); + g.edge(2, 3).setDistance(10000).set(speedEnc, 60, 60).setKeyValues(Map.of(STREET_NAME, new KValue("2-3"))); + g.edge(2, 6).setDistance(10000).set(speedEnc, 60, 60).setKeyValues(Map.of(STREET_NAME, new KValue("2-6"))); + g.edge(3, 4).setDistance(10000).set(speedEnc, 60, 60).setKeyValues(Map.of(STREET_NAME, new KValue("3-4"))).setWayGeometry(waypoint); + g.edge(3, 7).setDistance(10000).set(speedEnc, 60, 60).setKeyValues(Map.of(STREET_NAME, new KValue("3-7"))); + g.edge(4, 5).setDistance(10000).set(speedEnc, 60, 60).setKeyValues(Map.of(STREET_NAME, new KValue("4-5"))); Weighting weighting = new SpeedWeighting(speedEnc); Path p = new Dijkstra(g, weighting, tMode).calcPath(1, 5); @@ -544,11 +541,11 @@ public void testSplitWays() { PointList list = new PointList(); list.add(43.62549, -79.714292); - g.edge(1, 2).setKeyValues(createKV(STREET_NAME, "main")).setWayGeometry(list). - setDistance(110).set(roadsSpeedEnc, 50, 50).set(lanesEnc, 2); - g.edge(2, 3).setKeyValues(createKV(STREET_NAME, "main")). - setDistance(110).set(roadsSpeedEnc, 50, 50).set(lanesEnc, 3); - g.edge(2, 4).setKeyValues(createKV(STREET_NAME, "main")). + g.edge(1, 2).setKeyValues(Map.of(STREET_NAME, new KValue("main"))).setWayGeometry(list). + setDistance(110).set(roadsSpeedEnc, 50, 0).set(lanesEnc, 2); + g.edge(2, 3).setKeyValues(Map.of(STREET_NAME, new KValue("main"))). + setDistance(110).set(roadsSpeedEnc, 50, 0).set(lanesEnc, 3); + g.edge(2, 4).setKeyValues(Map.of(STREET_NAME, new KValue("main"))). setDistance(80).set(roadsSpeedEnc, 50, 50).set(lanesEnc, 5); Weighting weighting = new SpeedWeighting(roadsSpeedEnc); @@ -556,6 +553,48 @@ public void testSplitWays() { InstructionList wayList = InstructionsFromEdges.calcInstructions(p, g, weighting, tmpEM, usTR); List tmpList = getTurnDescriptions(wayList); assertEquals(Arrays.asList("continue onto main", "arrive at destination"), tmpList); + + // Other roads should not influence instructions. Example: https://www.openstreetmap.org/node/392106581 + na.setNode(5, 43.625666,-79.714048); + g.edge(2, 5).setDistance(80).set(roadsSpeedEnc, 50, 50).set(lanesEnc, 5); + + p = new Dijkstra(g, weighting, tMode).calcPath(1, 4); + wayList = InstructionsFromEdges.calcInstructions(p, g, weighting, tmpEM, usTR); + tmpList = getTurnDescriptions(wayList); + assertEquals(Arrays.asList("continue onto main", "arrive at destination"), tmpList); + } + + @Test + public void testNotSplitWays() { + DecimalEncodedValue roadsSpeedEnc = new DecimalEncodedValueImpl("speed", 7, 2, true); + EncodingManager tmpEM = EncodingManager.start().add(roadsSpeedEnc). + add(RoadClass.create()).add(Roundabout.create()).add(RoadClassLink.create()). + add(MaxSpeed.create()).add(Lanes.create()).build(); + IntEncodedValue lanesEnc = tmpEM.getIntEncodedValue(Lanes.KEY); + BaseGraph g = new BaseGraph.Builder(tmpEM).create(); + // real world example: https://graphhopper.com/maps/?point=51.425484%2C14.223298&point=51.42523%2C14.222864&profile=car + // 3 + // | + // 1-2-4 + + NodeAccess na = g.getNodeAccess(); + na.setNode(1, 51.42523, 14.222864); + na.setNode(2, 51.425256, 14.22325); + na.setNode(3, 51.425397, 14.223266); + na.setNode(4, 51.425273, 14.223427); + + g.edge(1, 2).setKeyValues(Map.of(STREET_NAME, new KValue("dresdener"))). + setDistance(110).set(roadsSpeedEnc, 50, 50).set(lanesEnc, 2); + g.edge(2, 3).setKeyValues(Map.of(STREET_NAME, new KValue("dresdener"))). + setDistance(110).set(roadsSpeedEnc, 50, 50).set(lanesEnc, 3); + g.edge(2, 4).setKeyValues(Map.of(STREET_NAME, new KValue("main"))). + setDistance(80).set(roadsSpeedEnc, 50, 50).set(lanesEnc, 5); + + Weighting weighting = new SpeedWeighting(roadsSpeedEnc); + Path p = new Dijkstra(g, weighting, tMode).calcPath(3, 1); + InstructionList wayList = InstructionsFromEdges.calcInstructions(p, g, weighting, tmpEM, usTR); + List tmpList = getTurnDescriptions(wayList); + assertEquals(Arrays.asList("continue onto dresdener", "turn right onto dresdener", "arrive at destination"), tmpList); } private void compare(List> expected, List> actual) { diff --git a/core/src/test/java/com/graphhopper/util/PathSimplificationTest.java b/core/src/test/java/com/graphhopper/util/PathSimplificationTest.java index b3316851bec..173726f4794 100644 --- a/core/src/test/java/com/graphhopper/util/PathSimplificationTest.java +++ b/core/src/test/java/com/graphhopper/util/PathSimplificationTest.java @@ -35,9 +35,9 @@ import java.util.*; -import static com.graphhopper.search.KVStorage.KeyValue.STREET_NAME; -import static com.graphhopper.search.KVStorage.KeyValue.createKV; +import static com.graphhopper.search.KVStorage.KValue; import static com.graphhopper.util.Parameters.Details.AVERAGE_SPEED; +import static com.graphhopper.util.Parameters.Details.STREET_NAME; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertTrue; @@ -75,24 +75,24 @@ public void testScenario() { na.setNode(7, 1.0, 1.1); na.setNode(8, 1.0, 1.2); - g.edge(0, 1).set(speedEnc, 9).setDistance(10000).setKeyValues(createKV(STREET_NAME, "0-1")); - g.edge(1, 2).set(speedEnc, 9).setDistance(11000).setKeyValues(createKV(STREET_NAME, "1-2")); + g.edge(0, 1).set(speedEnc, 9).setDistance(10000).setKeyValues(Map.of(STREET_NAME, new KValue("0-1"))); + g.edge(1, 2).set(speedEnc, 9).setDistance(11000).setKeyValues(Map.of(STREET_NAME, new KValue("1-2"))); g.edge(0, 3).set(speedEnc, 18).setDistance(11000); - g.edge(1, 4).set(speedEnc, 18).setDistance(10000).setKeyValues(createKV(STREET_NAME, "1-4")); - g.edge(2, 5).set(speedEnc, 18).setDistance(11000).setKeyValues(createKV(STREET_NAME, "5-2")); + g.edge(1, 4).set(speedEnc, 18).setDistance(10000).setKeyValues(Map.of(STREET_NAME, new KValue("1-4"))); + g.edge(2, 5).set(speedEnc, 18).setDistance(11000).setKeyValues(Map.of(STREET_NAME, new KValue("5-2"))); - g.edge(3, 6).set(speedEnc, 27).setDistance(11000).setKeyValues(createKV(STREET_NAME, "3-6")); - g.edge(4, 7).set(speedEnc, 27).setDistance(10000).setKeyValues(createKV(STREET_NAME, "4-7")); - g.edge(5, 8).set(speedEnc, 27).setDistance(10000).setKeyValues(createKV(STREET_NAME, "5-8")); + g.edge(3, 6).set(speedEnc, 27).setDistance(11000).setKeyValues(Map.of(STREET_NAME, new KValue("3-6"))); + g.edge(4, 7).set(speedEnc, 27).setDistance(10000).setKeyValues(Map.of(STREET_NAME, new KValue("4-7"))); + g.edge(5, 8).set(speedEnc, 27).setDistance(10000).setKeyValues(Map.of(STREET_NAME, new KValue("5-8"))); - g.edge(6, 7).setDistance(11000).set(speedEnc, 36).setKeyValues(createKV(STREET_NAME, "6-7")); + g.edge(6, 7).setDistance(11000).set(speedEnc, 36).setKeyValues(Map.of(STREET_NAME, new KValue("6-7"))); EdgeIteratorState tmpEdge = g.edge(7, 8).set(speedEnc, 36).setDistance(10000); PointList list = new PointList(); list.add(1.0, 1.15); list.add(1.0, 1.16); tmpEdge.setWayGeometry(list); - tmpEdge.setKeyValues(createKV(STREET_NAME, "7-8")); + tmpEdge.setKeyValues(Map.of(STREET_NAME, new KValue("7-8"))); // missing edge name g.edge(9, 10).set(speedEnc, 45).setDistance(10000); @@ -102,7 +102,7 @@ public void testScenario() { list.add(1.0, 1.3001); list.add(1.0, 1.3002); list.add(1.0, 1.3003); - tmpEdge.setKeyValues(createKV(STREET_NAME, "8-9")); + tmpEdge.setKeyValues(Map.of(STREET_NAME, new KValue("8-9"))); tmpEdge.setWayGeometry(list); // Path is: [0 0-1, 3 1-4, 6 4-7, 9 7-8, 11 8-9, 10 9-10] diff --git a/example/pom.xml b/example/pom.xml index a68f84f8d70..1eeeacc0300 100644 --- a/example/pom.xml +++ b/example/pom.xml @@ -5,14 +5,14 @@ 4.0.0 graphhopper-example - 9.0-SNAPSHOT + 10.0-SNAPSHOT jar GraphHopper Example com.graphhopper graphhopper-parent - 9.0-SNAPSHOT + 10.0-SNAPSHOT diff --git a/example/src/main/java/com/graphhopper/example/LocationIndexExample.java b/example/src/main/java/com/graphhopper/example/LocationIndexExample.java index eb8ac266b90..b4f4aa12c3c 100644 --- a/example/src/main/java/com/graphhopper/example/LocationIndexExample.java +++ b/example/src/main/java/com/graphhopper/example/LocationIndexExample.java @@ -4,6 +4,7 @@ import com.graphhopper.config.Profile; import com.graphhopper.routing.util.EdgeFilter; import com.graphhopper.search.KVStorage; +import com.graphhopper.search.KVStorage.KValue; import com.graphhopper.storage.BaseGraph; import com.graphhopper.storage.index.LocationIndex; import com.graphhopper.storage.index.LocationIndexTree; @@ -11,6 +12,8 @@ import com.graphhopper.util.EdgeIteratorState; import com.graphhopper.util.GHUtility; +import java.util.Map; + public class LocationIndexExample { public static void main(String[] args) { String relDir = args.length == 1 ? args[0] : ""; @@ -36,8 +39,8 @@ public static void graphhopperLocationIndex(String relDir) { public static void lowLevelLocationIndex() { // If you don't use the GraphHopper class you have to use the low level API: - BaseGraph graph = new BaseGraph.Builder(1).create(); - graph.edge(0, 1).setKeyValues(KVStorage.KeyValue.createKV("name", "test edge")); + BaseGraph graph = new BaseGraph.Builder(4).create(); + graph.edge(0, 1).setKeyValues(Map.of("name", new KValue( "test edge"))); graph.getNodeAccess().setNode(0, 12, 42); graph.getNodeAccess().setNode(1, 12.01, 42.01); diff --git a/map-matching/pom.xml b/map-matching/pom.xml index 07e445177d7..2714ab5e136 100644 --- a/map-matching/pom.xml +++ b/map-matching/pom.xml @@ -3,14 +3,14 @@ 4.0.0 com.graphhopper graphhopper-map-matching - 9.0-SNAPSHOT + 10.0-SNAPSHOT jar GraphHopper Map Matching com.graphhopper graphhopper-parent - 9.0-SNAPSHOT + 10.0-SNAPSHOT diff --git a/navigation/pom.xml b/navigation/pom.xml index 034ead2da21..ed117bac47e 100644 --- a/navigation/pom.xml +++ b/navigation/pom.xml @@ -5,14 +5,14 @@ 4.0.0 graphhopper-nav - 9.0-SNAPSHOT + 10.0-SNAPSHOT jar GraphHopper Navigation com.graphhopper graphhopper-parent - 9.0-SNAPSHOT + 10.0-SNAPSHOT diff --git a/pom.xml b/pom.xml index 111bd9941a5..81cf8d9ba7c 100644 --- a/pom.xml +++ b/pom.xml @@ -6,7 +6,7 @@ com.graphhopper graphhopper-parent GraphHopper Parent Project - 9.0-SNAPSHOT + 10.0-SNAPSHOT pom https://www.graphhopper.com 2012 diff --git a/reader-gtfs/pom.xml b/reader-gtfs/pom.xml index 7d430a51dfe..6d72967a165 100644 --- a/reader-gtfs/pom.xml +++ b/reader-gtfs/pom.xml @@ -10,7 +10,7 @@ com.graphhopper graphhopper-parent - 9.0-SNAPSHOT + 10.0-SNAPSHOT diff --git a/reader-gtfs/src/main/java/com/graphhopper/gtfs/analysis/PtGraphAsAdjacencyList.java b/reader-gtfs/src/main/java/com/graphhopper/gtfs/analysis/PtGraphAsAdjacencyList.java index b9670f3bf5c..b893d4d7e2b 100644 --- a/reader-gtfs/src/main/java/com/graphhopper/gtfs/analysis/PtGraphAsAdjacencyList.java +++ b/reader-gtfs/src/main/java/com/graphhopper/gtfs/analysis/PtGraphAsAdjacencyList.java @@ -11,7 +11,7 @@ import com.graphhopper.util.shapes.BBox; import java.util.Iterator; -import java.util.List; +import java.util.Map; class PtGraphAsAdjacencyList implements Graph { private final PtGraph ptGraph; @@ -304,12 +304,12 @@ public String getName() { } @Override - public EdgeIteratorState setKeyValues(List list) { + public EdgeIteratorState setKeyValues(Map map) { throw new RuntimeException(); } @Override - public List getKeyValues() { + public Map getKeyValues() { throw new RuntimeException(); } diff --git a/reader-gtfs/src/test/java/com/graphhopper/GraphHopperMultimodalIT.java b/reader-gtfs/src/test/java/com/graphhopper/GraphHopperMultimodalIT.java index fe37fb73e9d..e92e590bd22 100644 --- a/reader-gtfs/src/test/java/com/graphhopper/GraphHopperMultimodalIT.java +++ b/reader-gtfs/src/test/java/com/graphhopper/GraphHopperMultimodalIT.java @@ -44,6 +44,7 @@ import java.util.List; import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.within; import static org.assertj.core.api.Assumptions.assumeThat; public class GraphHopperMultimodalIT { @@ -110,7 +111,7 @@ public void testDepartureTimeOfAccessLegInProfileQuery() { assertThat(firstTransitSolution.getLegs().get(0).getArrivalTime().toInstant()) .isEqualTo(firstTransitSolution.getLegs().get(1).getDepartureTime().toInstant()); assertThat(firstTransitSolution.getLegs().get(2).getArrivalTime().toInstant().atZone(zoneId).toLocalTime()) - .isEqualTo(LocalTime.parse("06:52:02.741")); + .isEqualTo(LocalTime.parse("06:52:02.740")); // I like walking exactly as I like riding a bus (per travel time unit) // Now we get a walk solution which arrives earlier than the transit solutions. @@ -148,14 +149,14 @@ public void testDepartureTimeOfAccessLeg() { assertThat(firstTransitSolution.getLegs().get(0).getArrivalTime().toInstant()) .isEqualTo(firstTransitSolution.getLegs().get(1).getDepartureTime().toInstant()); assertThat(firstTransitSolution.getLegs().get(2).getArrivalTime().toInstant().atZone(zoneId).toLocalTime()) - .isEqualTo(LocalTime.parse("06:52:02.741")); + .isEqualTo(LocalTime.parse("06:52:02.740")); - double EXPECTED_TOTAL_WALKING_DISTANCE = 497.1043138676106; + double EXPECTED_TOTAL_WALKING_DISTANCE = 497.1; assertThat(firstTransitSolution.getLegs().get(0).distance + firstTransitSolution.getLegs().get(2).distance) - .isEqualTo(EXPECTED_TOTAL_WALKING_DISTANCE); + .isEqualTo(EXPECTED_TOTAL_WALKING_DISTANCE, within(.1)); List distances = firstTransitSolution.getPathDetails().get("distance"); assertThat(distances.stream().mapToDouble(d -> (double) d.getValue()).sum()) - .isEqualTo(EXPECTED_TOTAL_WALKING_DISTANCE); // Also total walking distance -- PathDetails only cover access/egress for now + .isEqualTo(EXPECTED_TOTAL_WALKING_DISTANCE, within(.1)); // Also total walking distance -- PathDetails only cover access/egress for now assertThat(distances.get(0).getFirst()).isEqualTo(0); // PathDetails start and end with PointList assertThat(distances.get(distances.size() - 1).getLast()).isEqualTo(12); @@ -173,7 +174,7 @@ public void testDepartureTimeOfAccessLeg() { // In principle, this would dominate the transit solution, since it's faster, but // walking gets a penalty. assertThat(walkSolution.getLegs().get(0).getArrivalTime().toInstant().atZone(zoneId).toLocalTime()) - .isEqualTo(LocalTime.parse("06:51:10.361")); + .isEqualTo(LocalTime.parse("06:51:10.365")); assertThat(walkSolution.getLegs().size()).isEqualTo(1); assertThat(walkSolution.getNumChanges()).isEqualTo(-1); diff --git a/tools/pom.xml b/tools/pom.xml index dd9594171ac..096b64b5e8c 100644 --- a/tools/pom.xml +++ b/tools/pom.xml @@ -10,7 +10,7 @@ com.graphhopper graphhopper-parent - 9.0-SNAPSHOT + 10.0-SNAPSHOT package diff --git a/tools/src/main/java/com/graphhopper/tools/GraphSpeedMeasurement.java b/tools/src/main/java/com/graphhopper/tools/GraphSpeedMeasurement.java index f24b20a0410..b1eb28a7d7b 100644 --- a/tools/src/main/java/com/graphhopper/tools/GraphSpeedMeasurement.java +++ b/tools/src/main/java/com/graphhopper/tools/GraphSpeedMeasurement.java @@ -90,7 +90,7 @@ public static void main(String[] strs) { } return (int) sum; }); - result.add(String.format("bits: %d, ints: %d, took: %.2fms, checksum: %d", speedBits, em.getIntsForFlags(), t.getSum(), t.getDummySum())); + result.add(String.format("bits: %d, bytes: %d, took: %.2fms, checksum: %d", speedBits, em.getBytesForFlags(), t.getSum(), t.getDummySum())); System.out.println(result.get(result.size() - 1)); } System.out.println(); diff --git a/tools/src/main/java/com/graphhopper/tools/Measurement.java b/tools/src/main/java/com/graphhopper/tools/Measurement.java index 81e54156cea..b6025d67b5c 100644 --- a/tools/src/main/java/com/graphhopper/tools/Measurement.java +++ b/tools/src/main/java/com/graphhopper/tools/Measurement.java @@ -92,8 +92,8 @@ void start(PMap args) throws IOException { boolean cleanGraph = args.getBool("measurement.clean", false); stopOnError = args.getBool("measurement.stop_on_error", false); String summaryLocation = args.getString("measurement.summaryfile", ""); - final String timeStamp = new SimpleDateFormat("yyyy-MM-dd_HH:mm:ss").format(new Date()); - put("measurement.timestamp", timeStamp); + final String timestamp = new SimpleDateFormat("yyyy-MM-dd_HH:mm:ss").format(new Date()); + put("measurement.timestamp", timestamp); String propFolder = args.getString("measurement.folder", ""); if (!propFolder.isEmpty()) { Files.createDirectories(Paths.get(propFolder)); @@ -103,9 +103,9 @@ void start(PMap args) throws IOException { if (useJson) { // if we start from IDE or otherwise jar was not built using maven the git commit id will be unknown String id = Constants.GIT_INFO != null ? Constants.GIT_INFO.getCommitHash().substring(0, 8) : "unknown"; - propFilename = "measurement_" + id + "_" + timeStamp + ".json"; + propFilename = "measurement_" + id + "_" + timestamp + ".json"; } else { - propFilename = "measurement_" + timeStamp + ".properties"; + propFilename = "measurement_" + timestamp + ".properties"; } } final String propLocation = Paths.get(propFolder).resolve(propFilename).toString(); diff --git a/web-api/pom.xml b/web-api/pom.xml index 75fa69107b5..a66afdbebf9 100644 --- a/web-api/pom.xml +++ b/web-api/pom.xml @@ -5,14 +5,14 @@ 4.0.0 graphhopper-web-api jar - 9.0-SNAPSHOT + 10.0-SNAPSHOT GraphHopper Web API JSON Representation of the API classes com.graphhopper graphhopper-parent - 9.0-SNAPSHOT + 10.0-SNAPSHOT diff --git a/web-api/src/main/java/com/graphhopper/ResponsePath.java b/web-api/src/main/java/com/graphhopper/ResponsePath.java index 8638efc73c7..2e8b2897483 100644 --- a/web-api/src/main/java/com/graphhopper/ResponsePath.java +++ b/web-api/src/main/java/com/graphhopper/ResponsePath.java @@ -267,8 +267,9 @@ public void addPathDetails(Map> details) { } for (Map.Entry> detailEntry : details.entrySet()) { String key = detailEntry.getKey(); - if (this.pathDetails.containsKey(key)) { - this.pathDetails.get(key).addAll(detailEntry.getValue()); + List pathDetails = this.pathDetails.get(key); + if (pathDetails != null) { + pathDetails.addAll(detailEntry.getValue()); } else { this.pathDetails.put(key, detailEntry.getValue()); } diff --git a/web-api/src/main/java/com/graphhopper/jackson/ResponsePathSerializer.java b/web-api/src/main/java/com/graphhopper/jackson/ResponsePathSerializer.java index 9b1144dd360..baebecd4577 100644 --- a/web-api/src/main/java/com/graphhopper/jackson/ResponsePathSerializer.java +++ b/web-api/src/main/java/com/graphhopper/jackson/ResponsePathSerializer.java @@ -81,7 +81,7 @@ private static void encodeNumber(StringBuilder sb, int num) { sb.append((char) (num)); } - public record Info(List copyrights, long took, String roadDataTimeStamp) { + public record Info(List copyrights, long took, String roadDataTimestamp) { } public static ObjectNode jsonObject(GHResponse ghRsp, Info info, boolean enableInstructions, diff --git a/web-api/src/main/java/com/graphhopper/util/Helper.java b/web-api/src/main/java/com/graphhopper/util/Helper.java index 1c009987c2d..8322bfb1ff7 100644 --- a/web-api/src/main/java/com/graphhopper/util/Helper.java +++ b/web-api/src/main/java/com/graphhopper/util/Helper.java @@ -37,8 +37,9 @@ public class Helper { public static final long MB = 1L << 20; // we keep the first seven decimal places of lat/lon coordinates. this corresponds to ~1cm precision ('pointing to waldo on a page') private static final float DEGREE_FACTOR = 10_000_000; - // milli meter is a bit extreme but we have integers + // milli meter is a bit extreme but we have 3 bytes private static final float ELE_FACTOR = 1000f; + private static final int MAX_ELE_UINT = (int) ((10_000 + 1000) * ELE_FACTOR); private Helper() { } @@ -257,7 +258,7 @@ public static int degreeToInt(double deg) { return Integer.MAX_VALUE; if (deg <= -Double.MAX_VALUE) return -Integer.MAX_VALUE; - return (int) (deg * DEGREE_FACTOR); + return (int) Math.round(deg * DEGREE_FACTOR); } /** @@ -276,20 +277,21 @@ public static double intToDegree(int storedInt) { /** * Converts elevation value (in meters) into integer for storage. */ - public static int eleToInt(double ele) { - if (ele >= Integer.MAX_VALUE) - return Integer.MAX_VALUE; - return (int) (ele * ELE_FACTOR); + public static int eleToUInt(double ele) { + if (Double.isNaN(ele)) throw new IllegalArgumentException("elevation cannot be NaN"); + if (ele < -1000) return -1000; + if (ele >= Integer.MAX_VALUE / ELE_FACTOR - 1000) return MAX_ELE_UINT; + return (int) Math.round((ele + 1000) * ELE_FACTOR); // enough for smallest value is -414m } /** * Converts the integer value retrieved from storage into elevation (in meters). Do not expect * more precision than meters although it currently is! */ - public static double intToEle(int integEle) { - if (integEle == Integer.MAX_VALUE) + public static double uIntToEle(int integEle) { + if (integEle >= MAX_ELE_UINT) return Double.MAX_VALUE; - return integEle / ELE_FACTOR; + return integEle / ELE_FACTOR - 1000; } public static String nf(long no) { diff --git a/web-api/src/main/java/com/graphhopper/util/JsonFeature.java b/web-api/src/main/java/com/graphhopper/util/JsonFeature.java index 9615827656d..aff83786c2b 100644 --- a/web-api/src/main/java/com/graphhopper/util/JsonFeature.java +++ b/web-api/src/main/java/com/graphhopper/util/JsonFeature.java @@ -91,7 +91,7 @@ public void setProperties(Map properties) { @Override public String toString() { - return "id:" + getId(); + return "id:" + getId() + " with " + getGeometry().getCoordinates().length + " points: " + getGeometry(); } public static boolean isValidId(String name) { diff --git a/web-api/src/main/java/com/graphhopper/util/PointList.java b/web-api/src/main/java/com/graphhopper/util/PointList.java index 1823a5ee40d..68d9ce166ed 100644 --- a/web-api/src/main/java/com/graphhopper/util/PointList.java +++ b/web-api/src/main/java/com/graphhopper/util/PointList.java @@ -401,7 +401,7 @@ public boolean equals(Object obj) { if (!equalsEps(this.getLon(i), other.getLon(i))) return false; - if (this.is3D() && !equalsEps(this.getEle(i), other.getEle(i))) + if (this.is3D() && !equalsEps(this.getEle(i), other.getEle(i), 0.01)) return false; } return true; diff --git a/web-api/src/main/java/com/graphhopper/util/shapes/GHPoint3D.java b/web-api/src/main/java/com/graphhopper/util/shapes/GHPoint3D.java index a301731bb2f..80b206f28ed 100644 --- a/web-api/src/main/java/com/graphhopper/util/shapes/GHPoint3D.java +++ b/web-api/src/main/java/com/graphhopper/util/shapes/GHPoint3D.java @@ -53,7 +53,7 @@ public boolean equals(Object obj) { return NumHelper.equalsEps(lat, other.lat) && NumHelper.equalsEps(lon, other.lon); else return NumHelper.equalsEps(lat, other.lat) && NumHelper.equalsEps(lon, other.lon) - && NumHelper.equalsEps(ele, other.ele); + && NumHelper.equalsEps(ele, other.ele, 0.01); } @Override diff --git a/web-api/src/test/java/com/graphhopper/util/HelperTest.java b/web-api/src/test/java/com/graphhopper/util/HelperTest.java index 408116fc531..3417dc53d45 100644 --- a/web-api/src/test/java/com/graphhopper/util/HelperTest.java +++ b/web-api/src/test/java/com/graphhopper/util/HelperTest.java @@ -31,6 +31,19 @@ */ public class HelperTest { + @Test + public void testElevation() { + assertEquals(9034.1, Helper.uIntToEle(Helper.eleToUInt(9034.1)), .1); + assertEquals(1234.5, Helper.uIntToEle(Helper.eleToUInt(1234.5)), .1); + assertEquals(0, Helper.uIntToEle(Helper.eleToUInt(0)), .1); + assertEquals(-432.3, Helper.uIntToEle(Helper.eleToUInt(-432.3)), .1); + + assertEquals(Double.MAX_VALUE, Helper.uIntToEle(Helper.eleToUInt(11_000))); + assertEquals(Double.MAX_VALUE, Helper.uIntToEle(Helper.eleToUInt(Double.MAX_VALUE))); + + assertThrows(IllegalArgumentException.class, () -> Helper.eleToUInt(Double.NaN)); + } + @Test public void testGetLocale() { assertEquals(Locale.GERMAN, Helper.getLocale("de")); @@ -92,4 +105,22 @@ public void testIssue2609() { bytes[0] = -25; assertEquals(3, new String(bytes, 0, 1, UTF_CS).getBytes(UTF_CS).length); } + + @Test + void degreeToInt() { + int storedInt = 444_494_395; + double lat = Helper.intToDegree(storedInt); + assertEquals(44.4494395, lat); + assertEquals(storedInt, Helper.degreeToInt(lat)); + } + + @Test + void eleToInt() { + int storedInt = 1145636; + double ele = Helper.uIntToEle(storedInt); + // converting to double is imprecise + assertEquals(145.635986, ele, 1.e-6); + // ... but converting back to int should yield the same value we started with! + assertEquals(storedInt, Helper.eleToUInt(ele)); + } } diff --git a/web-bundle/pom.xml b/web-bundle/pom.xml index aa2922481ae..76dead1c6c1 100644 --- a/web-bundle/pom.xml +++ b/web-bundle/pom.xml @@ -5,9 +5,10 @@ 4.0.0 graphhopper-web-bundle jar - 9.0-SNAPSHOT + 10.0-SNAPSHOT - 0.0.0-6fdf096cf324bcf6761713132fc84a8d1bb276fe + 0.0.0-651952a42e591f0cf2f08fcf89bf1c973567597c + GraphHopper Dropwizard Bundle Use the GraphHopper routing engine as a web-service @@ -15,7 +16,7 @@ com.graphhopper graphhopper-parent - 9.0-SNAPSHOT + 10.0-SNAPSHOT @@ -140,8 +141,8 @@ install-node-and-npm - v16.17.0 - 8.15.0 + v20.14.0 + 10.7.0 diff --git a/web-bundle/src/main/java/com/graphhopper/http/GraphHopperManaged.java b/web-bundle/src/main/java/com/graphhopper/http/GraphHopperManaged.java index f1ec72e30f7..b3a23a093c0 100644 --- a/web-bundle/src/main/java/com/graphhopper/http/GraphHopperManaged.java +++ b/web-bundle/src/main/java/com/graphhopper/http/GraphHopperManaged.java @@ -42,10 +42,10 @@ public GraphHopperManaged(GraphHopperConfig configuration) { @Override public void start() { graphHopper.importOrLoad(); - logger.info("loaded graph at:{}, data_reader_file:{}, encoded values:{}, {} ints for edge flags, {}", + logger.info("loaded graph at:{}, data_reader_file:{}, encoded values:{}, {} bytes for edge flags, {}", graphHopper.getGraphHopperLocation(), graphHopper.getOSMFile(), graphHopper.getEncodingManager().toEncodedValuesAsString(), - graphHopper.getEncodingManager().getIntsForFlags(), + graphHopper.getEncodingManager().getBytesForFlags(), graphHopper.getBaseGraph().toDetailsString()); } diff --git a/web-bundle/src/main/java/com/graphhopper/resources/MVTResource.java b/web-bundle/src/main/java/com/graphhopper/resources/MVTResource.java index 2e816951887..f8a1ad40816 100644 --- a/web-bundle/src/main/java/com/graphhopper/resources/MVTResource.java +++ b/web-bundle/src/main/java/com/graphhopper/resources/MVTResource.java @@ -3,6 +3,7 @@ import com.graphhopper.GraphHopper; import com.graphhopper.routing.ev.*; import com.graphhopper.routing.util.EncodingManager; +import com.graphhopper.search.KVStorage; import com.graphhopper.storage.NodeAccess; import com.graphhopper.storage.index.LocationIndexTree; import com.graphhopper.util.EdgeIteratorState; @@ -118,9 +119,9 @@ public Response doGetXyz( edgeCounter.incrementAndGet(); Map map = new LinkedHashMap<>(); - edge.getKeyValues().forEach( - entry -> map.put(entry.key, entry.value) - ); + for (Map.Entry e : edge.getKeyValues().entrySet()) { + map.put(e.getKey(), e.getValue().toString()); + } map.put("edge_id", edge.getEdge()); map.put("edge_key", edge.getEdgeKey()); map.put("base_node", edge.getBaseNode()); diff --git a/web-bundle/src/test/java/com/graphhopper/gpx/GpxConversionsTest.java b/web-bundle/src/test/java/com/graphhopper/gpx/GpxConversionsTest.java index 1462071cf27..d1278d6f861 100644 --- a/web-bundle/src/test/java/com/graphhopper/gpx/GpxConversionsTest.java +++ b/web-bundle/src/test/java/com/graphhopper/gpx/GpxConversionsTest.java @@ -43,9 +43,10 @@ import java.util.Date; import java.util.List; import java.util.Locale; +import java.util.Map; -import static com.graphhopper.search.KVStorage.KeyValue.STREET_NAME; -import static com.graphhopper.search.KVStorage.KeyValue.createKV; +import static com.graphhopper.search.KVStorage.KValue; +import static com.graphhopper.util.Parameters.Details.STREET_NAME; import static org.junit.jupiter.api.Assertions.*; public class GpxConversionsTest { @@ -78,12 +79,12 @@ public void testInstructionsWithTimeAndPlace() { na.setNode(6, 15.1, 10.1); na.setNode(7, 15.1, 9.8); - g.edge(1, 2).set(speedEnc, 63).setDistance(7000).setKeyValues(createKV(STREET_NAME, "1-2")); - g.edge(2, 3).set(speedEnc, 72).setDistance(8000).setKeyValues(createKV(STREET_NAME, "2-3")); - g.edge(2, 6).set(speedEnc, 9).setDistance(10000).setKeyValues(createKV(STREET_NAME, "2-6")); - g.edge(3, 4).set(speedEnc, 81).setDistance(9000).setKeyValues(createKV(STREET_NAME, "3-4")); - g.edge(3, 7).set(speedEnc, 9).setDistance(10000).setKeyValues(createKV(STREET_NAME, "3-7")); - g.edge(4, 5).set(speedEnc, 90).setDistance(10000).setKeyValues(createKV(STREET_NAME, "4-5")); + g.edge(1, 2).set(speedEnc, 63).setDistance(7000).setKeyValues(Map.of(STREET_NAME, new KValue("1-2"))); + g.edge(2, 3).set(speedEnc, 72).setDistance(8000).setKeyValues(Map.of(STREET_NAME, new KValue("2-3"))); + g.edge(2, 6).set(speedEnc, 9).setDistance(10000).setKeyValues(Map.of(STREET_NAME, new KValue("2-6"))); + g.edge(3, 4).set(speedEnc, 81).setDistance(9000).setKeyValues(Map.of(STREET_NAME, new KValue("3-4"))); + g.edge(3, 7).set(speedEnc, 9).setDistance(10000).setKeyValues(Map.of(STREET_NAME, new KValue("3-7"))); + g.edge(4, 5).set(speedEnc, 90).setDistance(10000).setKeyValues(Map.of(STREET_NAME, new KValue("4-5"))); Weighting weighting = new SpeedWeighting(speedEnc); Path p = new Dijkstra(g, weighting, TraversalMode.NODE_BASED).calcPath(1, 5); diff --git a/web/pom.xml b/web/pom.xml index 6ac8fc286ae..9921345d583 100644 --- a/web/pom.xml +++ b/web/pom.xml @@ -5,14 +5,14 @@ 4.0.0 graphhopper-web jar - 9.0-SNAPSHOT + 10.0-SNAPSHOT GraphHopper Web Use the GraphHopper routing engine as a web-service com.graphhopper graphhopper-parent - 9.0-SNAPSHOT + 10.0-SNAPSHOT package diff --git a/web/src/test/java/com/graphhopper/application/resources/ExtendedJsonResponseTest.java b/web/src/test/java/com/graphhopper/application/resources/ExtendedJsonResponseTest.java index cce7fabd6d3..3fbeef17da2 100644 --- a/web/src/test/java/com/graphhopper/application/resources/ExtendedJsonResponseTest.java +++ b/web/src/test/java/com/graphhopper/application/resources/ExtendedJsonResponseTest.java @@ -36,7 +36,9 @@ import java.util.ArrayList; import java.util.List; +import java.util.Map; +import static com.graphhopper.util.Parameters.Details.STREET_NAME; import static org.junit.jupiter.api.Assertions.assertEquals; public class ExtendedJsonResponseTest { @@ -88,7 +90,7 @@ private EdgeIteratorState getEdgeIterator() { pointList.add(-3.4445, -38.9990); pointList.add(-3.5550, -38.7990); return new VirtualEdgeIteratorState(0, 0, 0, 1, 10, new IntsRef(1), - KVStorage.KeyValue.createKV(KVStorage.KeyValue.STREET_NAME, "test of iterator"), pointList, false); + Map.of(STREET_NAME, new KVStorage.KValue("test of iterator")), pointList, false); } }