diff --git a/config-example.yml b/config-example.yml index a7c0e7a0141..5b80b313aef 100644 --- a/config-example.yml +++ b/config-example.yml @@ -91,6 +91,7 @@ graphhopper: # More are: surface,smoothness,max_width,max_height,max_weight,max_weight_except,hgv,max_axle_load,max_length, # hazmat,hazmat_tunnel,hazmat_water,lanes,osm_way_id,toll,track_type,mtb_rating,hike_rating,horse_rating, # country,curvature,average_slope,max_slope,car_temporal_access,bike_temporal_access,foot_temporal_access + # noisy_road_nearby graph.encoded_values: car_access, car_average_speed #### Speed, hybrid and flexible mode #### diff --git a/core/src/main/java/com/graphhopper/GraphHopper.java b/core/src/main/java/com/graphhopper/GraphHopper.java index 56c742094a3..e3ee4eb9999 100644 --- a/core/src/main/java/com/graphhopper/GraphHopper.java +++ b/core/src/main/java/com/graphhopper/GraphHopper.java @@ -914,7 +914,8 @@ protected void importOSM() { } logger.info("start creating graph from " + osmFile); - OSMReader reader = new OSMReader(baseGraph.getBaseGraph(), osmParsers, osmReaderConfig).setFile(_getOSMFile()). + OSMReader reader = new OSMReader(baseGraph.getBaseGraph(), encodingManager, osmParsers, osmReaderConfig) + .setFile(_getOSMFile()). setAreaIndex(areaIndex). setElevationProvider(eleProvider). setCountryRuleFactory(countryRuleFactory); diff --git a/core/src/main/java/com/graphhopper/reader/osm/OSMNoisyRoad.java b/core/src/main/java/com/graphhopper/reader/osm/OSMNoisyRoad.java new file mode 100644 index 00000000000..0559456e648 --- /dev/null +++ b/core/src/main/java/com/graphhopper/reader/osm/OSMNoisyRoad.java @@ -0,0 +1,55 @@ +/* + * 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.reader.osm; + +import com.graphhopper.routing.util.NoiseIndex; +import org.locationtech.jts.geom.GeometryFactory; +import org.locationtech.jts.geom.LineString; +import org.locationtech.jts.geom.impl.PackedCoordinateSequence; + +import java.util.Arrays; + +public class OSMNoisyRoad implements NoiseIndex.Road { + private static final GeometryFactory GEOMETRY_FACTORY = new GeometryFactory(); + final PackedCoordinateSequence.Float road; + + public OSMNoisyRoad(int numNodes) { + float[] coords = new float[numNodes * 2]; + Arrays.fill(coords, Float.NaN); + road = new PackedCoordinateSequence.Float(coords, 2, 0); + } + + public void setCoordinate(int index, double lat, double lon) { + // todonow: precision/double->float cast? + road.setX(index, (float) lon); + road.setY(index, (float) lat); + } + + public boolean isValid() { + float[] coords = road.getRawCoordinates(); + for (float f : coords) + if (Float.isNaN(f)) + return false; + return coords.length >= 2 ; + } + + public LineString getRoad() { + return GEOMETRY_FACTORY.createLineString(this.road); + } +} diff --git a/core/src/main/java/com/graphhopper/reader/osm/OSMNoisyRoadData.java b/core/src/main/java/com/graphhopper/reader/osm/OSMNoisyRoadData.java new file mode 100644 index 00000000000..7f1a10ebdb2 --- /dev/null +++ b/core/src/main/java/com/graphhopper/reader/osm/OSMNoisyRoadData.java @@ -0,0 +1,113 @@ +/* + * 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.reader.osm; + +import com.carrotsearch.hppc.IntArrayList; +import com.carrotsearch.hppc.LongArrayList; +import com.carrotsearch.hppc.cursors.LongCursor; +import com.graphhopper.coll.GHLongLongBTree; +import com.graphhopper.coll.LongLongMap; +import com.graphhopper.reader.ReaderNode; +import com.graphhopper.reader.ReaderWay; +import com.graphhopper.storage.Directory; +import com.graphhopper.util.BitUtil; + +import java.util.ArrayList; +import java.util.List; + +public class OSMNoisyRoadData { + + private final List osmNoisyRoads; + private final LongToTwoIntsMap osmNoisyRoadsNodeIndicesByOSMNodeIds; + + public OSMNoisyRoadData() { + osmNoisyRoads = new ArrayList<>(); + osmNoisyRoadsNodeIndicesByOSMNodeIds = new LongToTwoIntsMap(); + } + + public void addOSMNoisyRoadWithoutCoordinates(LongArrayList osmNodeIds) { + osmNoisyRoads.add(new OSMNoisyRoad(osmNodeIds.size())); + for (LongCursor node : osmNodeIds) + osmNoisyRoadsNodeIndicesByOSMNodeIds.put(node.value, osmNoisyRoads.size() - 1, node.index); + } + + public void fillOSMNoisyRoadNodeCoordinates(ReaderNode node) { + long index = osmNoisyRoadsNodeIndicesByOSMNodeIds.get(node.getId()); + if (index >= 0) { + int osmNoisyRoadIndex = BitUtil.LITTLE.getIntLow(index); + int nodeIndex = BitUtil.LITTLE.getIntHigh(index); + OSMNoisyRoad osmNoisyRoad = osmNoisyRoads.get(osmNoisyRoadIndex); + // Note that we set the coordinates only for one particular node for one particular + // osm area, even though the same osm node might be used in multiple such areas. We will + // fix the next time we get to see the osm area ways. + osmNoisyRoad.setCoordinate(nodeIndex, node.getLat(), node.getLon()); + } + } + + public void clear(){ + osmNoisyRoadsNodeIndicesByOSMNodeIds.clear(); + } + + public void fixOSMNoisyRoads(int osmNoisyWayIndex, ReaderWay way) { + // The problem we solve here is that some osm nodes are used by multiple noisy ways. + // At the very least this is the case for the first and last node of the ways, + // but there are also many junctions that share a common node. Since we only store one + // index into the coordinate array of the noisy roads, the coordinates for some noisy roads + // nodes won't be set. Therefore, we need to make up for this here where we get to see the + // ways a second time. If this wasn't the case we could read the noisy ways&nodes in pass1 + // instead of pass0 which would allow us to skip nodes and ways in pass0 (faster import). + OSMNoisyRoad actual = osmNoisyRoads.get(osmNoisyWayIndex); + for (LongCursor node : way.getNodes()) { + long index = osmNoisyRoadsNodeIndicesByOSMNodeIds.get(node.value); + int osmNoisyRoadIndex = BitUtil.LITTLE.getIntLow(index); + int nodeIndex = BitUtil.LITTLE.getIntHigh(index); + OSMNoisyRoad osmNoisyRoad = osmNoisyRoads.get(osmNoisyRoadIndex); + actual.road.setX(node.index, osmNoisyRoad.road.getX(nodeIndex)); + actual.road.setY(node.index, osmNoisyRoad.road.getY(nodeIndex)); + } + } + + public List getOsmNoisyRoads() { + return osmNoisyRoads; + } + + public static class LongToTwoIntsMap { + private final LongLongMap internalIdsByKey = new GHLongLongBTree(200, 4, -1); + private final IntArrayList vals1 = new IntArrayList(); + private final IntArrayList vals2 = new IntArrayList(); + + public void put(long key, int val1, int val2) { + vals1.add(val1); + vals2.add(val2); + internalIdsByKey.put(key, vals1.size() - 1); + } + + public long get(long key) { + long id = internalIdsByKey.get(key); + if (id < 0) return -1; + return BitUtil.LITTLE.toLong(vals1.get((int)id), vals2.get((int)id)); + } + + public void clear() { + internalIdsByKey.clear(); + vals1.release(); + vals2.release(); + } + } +} 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 56d6d016207..9c836da376e 100644 --- a/core/src/main/java/com/graphhopper/reader/osm/OSMReader.java +++ b/core/src/main/java/com/graphhopper/reader/osm/OSMReader.java @@ -31,11 +31,9 @@ import com.graphhopper.routing.OSMReaderConfig; import com.graphhopper.routing.ev.Country; import com.graphhopper.routing.ev.EdgeIntAccess; +import com.graphhopper.routing.ev.NoisyRoadNearby; import com.graphhopper.routing.ev.State; -import com.graphhopper.routing.util.AreaIndex; -import com.graphhopper.routing.util.CustomArea; -import com.graphhopper.routing.util.FerrySpeedCalculator; -import com.graphhopper.routing.util.OSMParsers; +import com.graphhopper.routing.util.*; import com.graphhopper.routing.util.countryrules.CountryRule; import com.graphhopper.routing.util.countryrules.CountryRuleFactory; import com.graphhopper.routing.util.parsers.RestrictionSetter; @@ -99,12 +97,19 @@ public class OSMReader { private WayToEdgesMap restrictedWaysToEdgesMap = new WayToEdgesMap(); private List restrictionRelations = new ArrayList<>(); - public OSMReader(BaseGraph baseGraph, OSMParsers osmParsers, OSMReaderConfig config) { + private final OSMNoisyRoadData osmNoisyRoadData; + private NoiseIndex osmNoisyRoadNoiseIndex; + private final EncodingManager encodingManager; + final Set noisyHighwayValues = new HashSet<>(Arrays.asList("motorway", + "motorway_link", "trunk", "trunk_link", "primary", "primary_link", "secondary", "secondary_link")); + + public OSMReader(BaseGraph baseGraph, EncodingManager encodingManager, OSMParsers osmParsers, OSMReaderConfig config) { this.baseGraph = baseGraph; this.edgeIntAccess = baseGraph.getEdgeAccess(); this.config = config; this.nodeAccess = baseGraph.getNodeAccess(); this.osmParsers = osmParsers; + this.encodingManager = encodingManager; this.restrictionSetter = new RestrictionSetter(baseGraph, osmParsers.getRestrictionTagParsers().stream().map(RestrictionTagParser::getTurnRestrictionEnc).toList()); simplifyAlgo.setMaxDistance(config.getMaxWayPointDistance()); @@ -115,6 +120,7 @@ public OSMReader(BaseGraph baseGraph, OSMParsers osmParsers, OSMReaderConfig con if (tempRelFlags.length != 2) // we use a long to store relation flags currently, so the relation flags ints ref must have length 2 throw new IllegalArgumentException("OSMReader cannot use relation flags with != 2 integers"); + osmNoisyRoadData = new OSMNoisyRoadData(); } /** @@ -162,7 +168,7 @@ public void readGraph() throws IOException { if (!baseGraph.isInitialized()) throw new IllegalStateException("BaseGraph must be initialize before we can read OSM"); - WaySegmentParser waySegmentParser = new WaySegmentParser.Builder(baseGraph.getNodeAccess(), baseGraph.getDirectory()) + WaySegmentParser.Builder waySegmentParserBuilder = new WaySegmentParser.Builder(baseGraph.getNodeAccess(), baseGraph.getDirectory()) .setElevationProvider(this::getElevation) .setWayFilter(this::acceptWay) .setSplitNodeFilter(this::isBarrierNode) @@ -170,8 +176,19 @@ public void readGraph() throws IOException { .setRelationPreprocessor(this::preprocessRelations) .setRelationProcessor(this::processRelation) .setEdgeHandler(this::addEdge) - .setWorkerThreads(config.getWorkerThreads()) - .build(); + .setWorkerThreads(config.getWorkerThreads()); + if (encodingManager.hasEncodedValue(NoisyRoadNearby.KEY)) { + waySegmentParserBuilder + // todonow: we could move the pass1 way handling and the pass2 node handling of the standard way segment parser to pass0/1, + // then we could skip nodes in pass2 to speed up the import + .setPass0WayPreHook(this::handleOSMNoisyRoads) + .setPass1NodePreHook(osmNoisyRoadData::fillOSMNoisyRoadNodeCoordinates) + .setPass1WayPreHook(this::handleOSMNoisyRoadsAgain) + .setPass1FinishHook(osmNoisyRoadData::clear) + .setPass2AfterNodesHook(this::buildOSMNoisyRoadIndex); + + } + WaySegmentParser waySegmentParser = waySegmentParserBuilder.build(); waySegmentParser.readOSM(osmFile); osmDataDate = waySegmentParser.getTimestamp(); if (baseGraph.getNodes() == 0) @@ -190,6 +207,31 @@ public Date getDataDate() { return osmDataDate; } + void handleOSMNoisyRoads(ReaderWay way) { + if (way.hasTag("highway", noisyHighwayValues)) + osmNoisyRoadData.addOSMNoisyRoadWithoutCoordinates(way.getNodes()); + } + + int osmNoisyWayIndex = -1; + void handleOSMNoisyRoadsAgain(ReaderWay way) { + if (way.hasTag("highway",noisyHighwayValues)) { + osmNoisyWayIndex++; + osmNoisyRoadData.fixOSMNoisyRoads(osmNoisyWayIndex, way); + } + } + + void buildOSMNoisyRoadIndex() { + List validRoads = osmNoisyRoadData.getOsmNoisyRoads().stream().filter(r -> { + if (!r.isValid()) { + OSM_WARNING_LOGGER.warn("invalid noisy road: " + r); + return false; + } + return true; + }).collect(Collectors.toList()); + osmNoisyRoadData.getOsmNoisyRoads().clear(); + osmNoisyRoadNoiseIndex = new NoiseIndex<>(validRoads); + } + protected double getElevation(ReaderNode node) { double ele = eleProvider.getEle(node); return Double.isNaN(ele) ? config.getDefaultElevation() : ele; @@ -246,20 +288,13 @@ protected void setArtificialWayTags(PointList pointList, ReaderWay way, double d way.removeTag("country_rule"); way.removeTag("custom_areas"); + if (osmNoisyRoadNoiseIndex != null) { + way.setTag("noisy_road_nearby", osmNoisyRoadNoiseIndex.query(pointList.getMiddleLat(), pointList.getMiddleLon())); + } + List customAreas; if (areaIndex != null) { - double middleLat; - double middleLon; - if (pointList.size() > 2) { - middleLat = pointList.getLat(pointList.size() / 2); - middleLon = pointList.getLon(pointList.size() / 2); - } else { - double firstLat = pointList.getLat(0), firstLon = pointList.getLon(0); - double lastLat = pointList.getLat(pointList.size() - 1), lastLon = pointList.getLon(pointList.size() - 1); - middleLat = (firstLat + lastLat) / 2; - middleLon = (firstLon + lastLon) / 2; - } - customAreas = areaIndex.query(middleLat, middleLon); + customAreas = areaIndex.query(pointList.getMiddleLat(), pointList.getMiddleLon()); } else { customAreas = emptyList(); } 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 fe7ee35f411..40342a123c2 100644 --- a/core/src/main/java/com/graphhopper/reader/osm/WaySegmentParser.java +++ b/core/src/main/java/com/graphhopper/reader/osm/WaySegmentParser.java @@ -63,7 +63,19 @@ public class WaySegmentParser { private static final Set INCLUDE_IF_NODE_TAGS = new HashSet<>(Arrays.asList("barrier", "highway", "railway", "crossing", "ford")); private ToDoubleFunction elevationProvider = node -> 0d; + private Consumer pass0WayPreHook = way -> { + }; + private Consumer pass1NodePreHook = node -> { + }; + private Consumer pass1WayPreHook = way -> { + }; + private Runnable pass1FinishHook = () -> { + }; private Predicate wayFilter = way -> true; + private Consumer pass2NodePreHook = node -> { + }; + private Runnable pass2AfterNodesHook = () -> { + }; private Predicate splitNodeFilter = node -> false; private WayPreprocessor wayPreprocessor = (way, coordinateSupplier, nodeTagSupplier) -> { }; @@ -90,9 +102,15 @@ public void readOSM(File osmFile) { throw new IllegalStateException("You can only run way segment parser once"); LOGGER.info("Start reading OSM file: '" + osmFile + "'"); + LOGGER.info("pass0 - start"); + StopWatch sw0 = StopWatch.started(); + // todonow: skip options + readOSM(osmFile, new Pass0Handler(), new SkipOptions(true, false, true)); + LOGGER.info("pass0 - finished, took: {}", sw0.stop().getTimeString()); + LOGGER.info("pass1 - start"); StopWatch sw1 = StopWatch.started(); - readOSM(osmFile, new Pass1Handler(), new SkipOptions(true, false, false)); + readOSM(osmFile, new Pass1Handler(), new SkipOptions(false, false, false)); LOGGER.info("pass1 - finished, took: {}", sw1.stop().getTimeString()); long nodes = nodeData.getNodeCount(); @@ -107,9 +125,10 @@ public void readOSM(File osmFile) { nodeData.release(); LOGGER.info("Finished reading OSM file." + + " pass0: " + (int) sw0.getSeconds() + "s, " + " pass1: " + (int) sw1.getSeconds() + "s, " + " pass2: " + (int) sw2.getSeconds() + "s, " + - " total: " + (int) (sw1.getSeconds() + sw2.getSeconds()) + "s"); + " total: " + (int) (sw0.getSeconds() + sw1.getSeconds() + sw2.getSeconds()) + "s"); } /** @@ -119,6 +138,13 @@ public Date getTimestamp() { return timestamp; } + private class Pass0Handler implements ReaderElementHandler { + @Override + public void handleWay(ReaderWay way) { + pass0WayPreHook.accept(way); + } + } + private class Pass1Handler implements ReaderElementHandler { private boolean handledWays; private boolean handledRelations; @@ -126,6 +152,11 @@ private class Pass1Handler implements ReaderElementHandler { private long acceptedWays = 0; private long relationsCounter = 0; + @Override + public void handleNode(ReaderNode node) { + pass1NodePreHook.accept(node); + } + @Override public void handleWay(ReaderWay way) { if (!handledWays) { @@ -139,6 +170,8 @@ public void handleWay(ReaderWay way) { LOGGER.info("pass1 - processed ways: " + nf(wayCounter) + ", accepted ways: " + nf(acceptedWays) + ", way nodes: " + nf(nodeData.getNodeCount()) + ", " + Helper.getMemInfo()); + pass1WayPreHook.accept(way); + if (!wayFilter.test(way)) return; acceptedWays++; @@ -173,6 +206,7 @@ public void handleFileHeader(OSMFileHeader fileHeader) throws ParseException { @Override public void onFinish() { + pass1FinishHook.run(); LOGGER.info("pass1 - finished, processed ways: " + nf(wayCounter) + ", accepted ways: " + nf(acceptedWays) + ", way nodes: " + nf(nodeData.getNodeCount()) + ", relations: " + nf(relationsCounter) + ", " + Helper.getMemInfo()); @@ -203,6 +237,8 @@ public void handleNode(ReaderNode node) { LOGGER.info("pass2 - processed nodes: " + nf(nodeCounter) + ", accepted nodes: " + nf(acceptedNodes) + ", " + Helper.getMemInfo()); + pass2NodePreHook.accept(node); + long nodeType = nodeData.addCoordinatesIfMapped(node.getId(), node.getLat(), node.getLon(), () -> elevationProvider.applyAsDouble(node)); if (nodeType == EMPTY_NODE) return; @@ -235,6 +271,7 @@ public void handleNode(ReaderNode node) { @Override public void handleWay(ReaderWay way) { if (!handledWays) { + pass2AfterNodesHook.run(); LOGGER.info("pass2 - start reading OSM ways"); handledWays = true; } @@ -427,6 +464,26 @@ public Builder setElevationProvider(ToDoubleFunction elevationProvid return this; } + public Builder setPass0WayPreHook(Consumer pass0WayPreHook) { + waySegmentParser.pass0WayPreHook = pass0WayPreHook; + return this; + } + + public Builder setPass1NodePreHook(Consumer pass1NodePreHook) { + waySegmentParser.pass1NodePreHook = pass1NodePreHook; + return this; + } + + public Builder setPass1WayPreHook(Consumer pass1WayPreHook) { + waySegmentParser.pass1WayPreHook = pass1WayPreHook; + return this; + } + + public Builder setPass1FinishHook(Runnable pass1FinishHook) { + waySegmentParser.pass1FinishHook = pass1FinishHook; + return this; + } + /** * @param wayFilter return true for OSM ways that should be considered and false otherwise */ @@ -435,6 +492,16 @@ public Builder setWayFilter(Predicate wayFilter) { return this; } + public Builder setPass2NodePreHook(Consumer pass2NodePreHook) { + waySegmentParser.pass2NodePreHook = pass2NodePreHook; + return this; + } + + public Builder setPass2AfterNodesHook(Runnable pass2AfterNodesHook) { + waySegmentParser.pass2AfterNodesHook = pass2AfterNodesHook; + return this; + } + /** * @param splitNodeFilter return true if the given OSM node should be duplicated to create an artificial edge */ diff --git a/core/src/main/java/com/graphhopper/routing/ev/DefaultImportRegistry.java b/core/src/main/java/com/graphhopper/routing/ev/DefaultImportRegistry.java index 0ff15f83a61..57e6f8ca186 100644 --- a/core/src/main/java/com/graphhopper/routing/ev/DefaultImportRegistry.java +++ b/core/src/main/java/com/graphhopper/routing/ev/DefaultImportRegistry.java @@ -192,6 +192,11 @@ else if (Curvature.KEY.equals(name)) (lookup, props) -> new CurvatureCalculator( lookup.getDecimalEncodedValue(Curvature.KEY)) ); + else if (NoisyRoadNearby.KEY.equals(name)) + return ImportUnit.create(name, props -> NoisyRoadNearby.create(), + (lookup, props) -> new NoisyRoadNearbyCalculator( + lookup.getBooleanEncodedValue(NoisyRoadNearby.KEY)) + ); else if (AverageSlope.KEY.equals(name)) return ImportUnit.create(name, props -> AverageSlope.create(), null, "slope_calculator"); else if (MaxSlope.KEY.equals(name)) diff --git a/core/src/main/java/com/graphhopper/routing/ev/NoisyRoadNearby.java b/core/src/main/java/com/graphhopper/routing/ev/NoisyRoadNearby.java new file mode 100644 index 00000000000..66e398585a5 --- /dev/null +++ b/core/src/main/java/com/graphhopper/routing/ev/NoisyRoadNearby.java @@ -0,0 +1,27 @@ +/* + * Licensed to GraphHopper GmbH under one or more contributor + * license agreements. See the NOTICE file distributed with this work for + * additional information regarding copyright ownership. + * + * GraphHopper GmbH licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.graphhopper.routing.ev; + +public class NoisyRoadNearby { + public static final String KEY = "noisy_road_nearby"; + + public static BooleanEncodedValue create() { + return new SimpleBooleanEncodedValue(KEY, false); + } +} diff --git a/core/src/main/java/com/graphhopper/routing/util/NoiseIndex.java b/core/src/main/java/com/graphhopper/routing/util/NoiseIndex.java new file mode 100644 index 00000000000..a56bc33dc92 --- /dev/null +++ b/core/src/main/java/com/graphhopper/routing/util/NoiseIndex.java @@ -0,0 +1,93 @@ +/* + * Licensed to GraphHopper GmbH under one or more contributor + * license agreements. See the NOTICE file distributed with this work for + * additional information regarding copyright ownership. + * + * GraphHopper GmbH licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.graphhopper.routing.util; + +import com.graphhopper.util.DistanceCalc; +import com.graphhopper.util.DistanceCalcEarth; +import com.graphhopper.util.shapes.BBox; +import org.locationtech.jts.geom.*; +import org.locationtech.jts.geom.prep.PreparedGeometry; +import org.locationtech.jts.geom.prep.PreparedGeometryFactory; +import org.locationtech.jts.index.strtree.STRtree; + +import java.util.List; + +public class NoiseIndex { + GeometryFactory gf; + private final STRtree index; + private final DistanceCalc dc; + + public interface Road { + default LineString getRoad() { return null; } + } + + public NoiseIndex(List roads) { + gf = new GeometryFactory(); + index = new STRtree(); + dc = new DistanceCalcEarth(); + PreparedGeometryFactory pgf = new PreparedGeometryFactory(); + for (T road : roads) { + LineString lineString = road.getRoad(); + IndexedNoisyRoad indexedNoisyRoad = new IndexedNoisyRoad<>(road, pgf.create(lineString)); + index.insert(lineString.getEnvelopeInternal(), indexedNoisyRoad); + } + index.build(); + } + + public boolean query(double lat, double lon) { + if (Double.isNaN(lat) || Double.isNaN(lon)) + return false; + BBox bbox = dc.createBBox(lat, lon, 15); + Envelope searchEnv = new Envelope(bbox.minLon, bbox.maxLon, bbox.minLat, bbox.maxLat); + List> result = index.query(searchEnv); + + if (!result.isEmpty()) { + Coordinate[] coordinates = new Coordinate[]{ + new Coordinate(bbox.minLon, bbox.minLat), + new Coordinate(bbox.minLon, bbox.maxLat), + new Coordinate(bbox.maxLon, bbox.maxLat), + new Coordinate(bbox.maxLon, bbox.minLat), + new Coordinate(bbox.minLon, bbox.minLat) // Closing the loop + }; + LinearRing linearRing = gf.createLinearRing(coordinates); + Polygon rectangle = gf.createPolygon(linearRing, null); + return !result.stream() + .filter(c -> c.intersects(rectangle)) + .map(c -> c.road) + .toList().isEmpty(); + } else { + return false; + } + } + + private static class IndexedNoisyRoad { + final T road; + final PreparedGeometry preparedGeometry; + + IndexedNoisyRoad(T road, PreparedGeometry preparedGeometry) { + this.road = road; + this.preparedGeometry = preparedGeometry; + } + + boolean intersects(Geometry geometry) { + return preparedGeometry.intersects(geometry); + } + + } +} diff --git a/core/src/main/java/com/graphhopper/routing/util/NoisyRoadNearbyCalculator.java b/core/src/main/java/com/graphhopper/routing/util/NoisyRoadNearbyCalculator.java new file mode 100644 index 00000000000..017aca98820 --- /dev/null +++ b/core/src/main/java/com/graphhopper/routing/util/NoisyRoadNearbyCalculator.java @@ -0,0 +1,41 @@ +/* + * Licensed to GraphHopper GmbH under one or more contributor + * license agreements. See the NOTICE file distributed with this work for + * additional information regarding copyright ownership. + * + * GraphHopper GmbH licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.graphhopper.routing.util; + +import com.graphhopper.reader.ReaderWay; +import com.graphhopper.routing.ev.BooleanEncodedValue; +import com.graphhopper.routing.ev.EdgeIntAccess; +import com.graphhopper.routing.util.parsers.TagParser; +import com.graphhopper.storage.IntsRef; + +public class NoisyRoadNearbyCalculator implements TagParser { + + private final BooleanEncodedValue noiseLevelEnc; + + public NoisyRoadNearbyCalculator(BooleanEncodedValue noiseLevelEnc) { + this.noiseLevelEnc = noiseLevelEnc; + } + + @Override + public void handleWayTags(int edgeId, EdgeIntAccess edgeIntAccess, ReaderWay way, IntsRef relationFlags) { + noiseLevelEnc.setBool(false, edgeId, edgeIntAccess, way.getTag("noisy_road_nearby", false) ); + way.removeTag("noisy_road_nearby"); + } +} + diff --git a/core/src/main/resources/com/graphhopper/custom_models/noisy_road_nearby.json b/core/src/main/resources/com/graphhopper/custom_models/noisy_road_nearby.json new file mode 100644 index 00000000000..feb29af506f --- /dev/null +++ b/core/src/main/resources/com/graphhopper/custom_models/noisy_road_nearby.json @@ -0,0 +1,10 @@ +// avoid noisy segments + +{ + "priority": [ + { + "if": "noisy_road_nearby", + "multiply_by": "0.9" + } + ] +} 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 c3313264c50..c853b0eba32 100644 --- a/core/src/test/java/com/graphhopper/reader/osm/OSMReaderTest.java +++ b/core/src/test/java/com/graphhopper/reader/osm/OSMReaderTest.java @@ -938,7 +938,7 @@ public void testCountries() throws IOException { OSMParsers osmParsers = new OSMParsers(); osmParsers.addWayTagParser(new OSMRoadAccessParser(roadAccessEnc, OSMRoadAccessParser.toOSMRestrictions(CAR))); BaseGraph graph = new BaseGraph.Builder(em).create(); - OSMReader reader = new OSMReader(graph, osmParsers, new OSMReaderConfig()); + OSMReader reader = new OSMReader(graph, em, osmParsers, new OSMReaderConfig()); reader.setCountryRuleFactory(new CountryRuleFactory()); reader.setAreaIndex(createCountryIndex()); // there are two edges, both with highway=track, one in Berlin, one in Paris @@ -971,7 +971,7 @@ public void testCurvedWayAlongBorder() throws IOException { OSMParsers osmParsers = new OSMParsers() .addWayTagParser(new CountryParser(countryEnc)); BaseGraph graph = new BaseGraph.Builder(em).create(); - OSMReader reader = new OSMReader(graph, osmParsers, new OSMReaderConfig()); + OSMReader reader = new OSMReader(graph, em, osmParsers, new OSMReaderConfig()); reader.setCountryRuleFactory(new CountryRuleFactory()); reader.setAreaIndex(createCountryIndex()); reader.setFile(new File(getClass().getResource("test-osm12.xml").getFile())); diff --git a/core/src/test/java/com/graphhopper/routing/TrafficChangeWithNodeOrderingReusingTest.java b/core/src/test/java/com/graphhopper/routing/TrafficChangeWithNodeOrderingReusingTest.java index e62f5bb5639..f28f6b7d4a7 100644 --- a/core/src/test/java/com/graphhopper/routing/TrafficChangeWithNodeOrderingReusingTest.java +++ b/core/src/test/java/com/graphhopper/routing/TrafficChangeWithNodeOrderingReusingTest.java @@ -103,7 +103,7 @@ public void testPerformanceForRandomTrafficChange(Fixture f) throws IOException LOGGER.info("Running performance test, max deviation percentage: " + f.maxDeviationPercentage); // read osm - OSMReader reader = new OSMReader(f.graph, f.osmParsers, new OSMReaderConfig()); + OSMReader reader = new OSMReader(f.graph, f.em, f.osmParsers, new OSMReaderConfig()); reader.setFile(new File(OSM_FILE)); reader.readGraph(); f.graph.freeze(); 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 68d9ce166ed..b4252a74305 100644 --- a/web-api/src/main/java/com/graphhopper/util/PointList.java +++ b/web-api/src/main/java/com/graphhopper/util/PointList.java @@ -273,6 +273,26 @@ public double getLon(int index) { return longitudes[index]; } + public double getMiddleLat() { + double middleLat; + if (latitudes.length > 2) { + middleLat = latitudes[latitudes.length / 2]; + } else { + middleLat = (latitudes[0] + latitudes[latitudes.length - 1]) / 2; + } + return middleLat; + } + + public double getMiddleLon() { + double middleLon; + if (longitudes.length > 2) { + middleLon = longitudes[longitudes.length / 2]; + } else { + middleLon = (longitudes[0] + longitudes[longitudes.length - 1]) / 2; + } + return middleLon; + } + @Override public double getEle(int index) { if (index >= size)