21
21
import com .carrotsearch .hppc .BitSetIterator ;
22
22
import com .carrotsearch .hppc .IntArrayList ;
23
23
import com .carrotsearch .hppc .IntIndexedContainer ;
24
+ import com .carrotsearch .hppc .cursors .IntCursor ;
24
25
import com .graphhopper .routing .ev .BooleanEncodedValue ;
25
26
import com .graphhopper .routing .util .DefaultEdgeFilter ;
26
- import com .graphhopper .routing .util . FlagEncoder ;
27
+ import com .graphhopper .routing .weighting . TurnCostProvider ;
27
28
import com .graphhopper .storage .GraphHopperStorage ;
28
- import com .graphhopper .util .EdgeExplorer ;
29
- import com .graphhopper .util .EdgeIterator ;
30
- import com .graphhopper .util .Helper ;
31
- import com .graphhopper .util .StopWatch ;
29
+ import com .graphhopper .util .*;
32
30
import org .slf4j .Logger ;
33
31
import org .slf4j .LoggerFactory ;
34
32
38
36
/**
39
37
* Removes nodes/edges which are not part of the 'main' network(s). I.e. mostly nodes with no edges at all but
40
38
* also small subnetworks which could be bugs in OSM data or 'islands' or indicate otherwise disconnected areas
41
- * e.g. via barriers or one way problems - see #86.
42
- * <p>
39
+ * e.g. via barriers or one way problems - see #86. Subnetworks are removed by disabling access to the corresponding
40
+ * edges for a given access encoded value. It is important to search for strongly connected components here (i.e.
41
+ * consider that the graph is directed). For example, small areas like parking lots are sometimes connected to the whole
42
+ * network through a single one-way road (a mapping error) and have to be removed because otherwise the routing fails
43
+ * when starting from such a parking lot.
43
44
*
44
45
* @author Peter Karich
45
46
* @author easbar
46
47
*/
47
48
public class PrepareRoutingSubnetworks {
48
49
private final Logger logger = LoggerFactory .getLogger (getClass ());
49
50
private final GraphHopperStorage ghStorage ;
50
- private final List <FlagEncoder > encoders ;
51
- private final List <BooleanEncodedValue > accessEncList ;
51
+ private final List <PrepareJob > prepareJobs ;
52
52
private int minNetworkSize = 200 ;
53
- private int subnetworks = -1 ;
54
53
55
- public PrepareRoutingSubnetworks (GraphHopperStorage ghStorage , List <FlagEncoder > encoders ) {
54
+ public PrepareRoutingSubnetworks (GraphHopperStorage ghStorage , List <PrepareJob > prepareJobs ) {
56
55
this .ghStorage = ghStorage ;
57
- this .encoders = encoders ;
58
- this .accessEncList = new ArrayList <>();
59
- for (FlagEncoder flagEncoder : encoders ) {
60
- accessEncList .add (flagEncoder .getAccessEnc ());
61
- }
56
+ this .prepareJobs = prepareJobs ;
62
57
}
63
58
59
+ /**
60
+ * The subnetwork removal removes components with less than {@link #minNetworkSize} nodes from the graph if it is
61
+ * run node-based. For edge-based subnetwork removal it removes components with less than 2*{@link #minNetworkSize}
62
+ * (directed) edges.
63
+ */
64
64
public PrepareRoutingSubnetworks setMinNetworkSize (int minNetworkSize ) {
65
65
this .minNetworkSize = minNetworkSize ;
66
66
return this ;
@@ -73,15 +73,16 @@ public void doWork() {
73
73
}
74
74
StopWatch sw = new StopWatch ().start ();
75
75
logger .info ("Start removing subnetworks (prepare.min_network_size:" + minNetworkSize + ") " + Helper .getMemInfo ());
76
+ logger .info ("Subnetwork removal jobs: " + prepareJobs );
76
77
logger .info ("Graph nodes: " + Helper .nf (ghStorage .getNodes ()));
77
78
logger .info ("Graph edges: " + Helper .nf (ghStorage .getEdges ()));
78
- for (FlagEncoder encoder : encoders ) {
79
- logger .info ("--- vehicle: '" + encoder . toString () + "'" );
80
- removeSmallSubNetworks (encoder . getAccessEnc () );
79
+ for (PrepareJob job : prepareJobs ) {
80
+ logger .info ("--- vehicle: '" + job . name + "'" );
81
+ removeSmallSubNetworks (job . accessEnc , job . turnCostProvider );
81
82
}
82
83
markNodesRemovedIfUnreachable ();
83
84
optimize ();
84
- logger .info ("Finished finding and removing subnetworks for " + encoders .size () + " vehicles, took: " + sw .stop ().getSeconds () + "s, " + Helper .getMemInfo ());
85
+ logger .info ("Finished finding and removing subnetworks for " + prepareJobs .size () + " vehicles, took: " + sw .stop ().getSeconds () + "s, " + Helper .getMemInfo ());
85
86
}
86
87
87
88
private void optimize () {
@@ -90,21 +91,21 @@ private void optimize() {
90
91
logger .info ("Optimized storage after subnetwork removal, took: " + sw .stop ().getSeconds () + "s," + Helper .getMemInfo ());
91
92
}
92
93
93
- public int getMaxSubnetworks () {
94
- return subnetworks ;
95
- }
96
-
97
94
/**
98
- * Removes components with less than {@link #minNetworkSize} nodes from the graph by disabling access to the nodes
99
- * of the removed components (for the given access encoded value). It is important to search for strongly connected
100
- * components here (i.e. consider that the graph is directed). For example, small areas like parking lots are
101
- * sometimes connected to the whole network through a single one-way road. This is clearly a (mapping) error - but
102
- * it causes the routing to fail when starting from the parking lot (and there is no way out from it).
103
- * The biggest component is always kept regardless of its size.
95
+ * The biggest component is always kept regardless of its size. For edge-based routing with turn restrictions the
96
+ * subnetwork search has to consider the turn restrictions as well to make sure components that are not reachable
97
+ * due to turn restrictions are also removed.
104
98
*
105
99
* @return number of removed edges
106
100
*/
107
- int removeSmallSubNetworks (BooleanEncodedValue accessEnc ) {
101
+ int removeSmallSubNetworks (BooleanEncodedValue accessEnc , TurnCostProvider turnCostProvider ) {
102
+ if (turnCostProvider == null )
103
+ return removeSmallSubNetworksNodeBased (accessEnc );
104
+ else
105
+ return removeSmallSubNetworksEdgeBased (accessEnc , turnCostProvider );
106
+ }
107
+
108
+ private int removeSmallSubNetworksNodeBased (BooleanEncodedValue accessEnc ) {
108
109
// partition graph into strongly connected components using Tarjan's algorithm
109
110
StopWatch sw = new StopWatch ().start ();
110
111
TarjanSCC tarjan = new TarjanSCC (ghStorage , accessEnc , false );
@@ -115,7 +116,7 @@ int removeSmallSubNetworks(BooleanEncodedValue accessEnc) {
115
116
logger .info ("Found " + ccs .getTotalComponents () + " subnetworks (" + numSingleNodeComponents + " single nodes and "
116
117
+ components .size () + " components with more than one node, total nodes: " + ccs .getNodes () + "), took: " + sw .stop ().getSeconds () + "s" );
117
118
118
- // remove all small networks except the biggest (even when its smaller than the given min_network_size)
119
+ // remove all small networks, but keep the biggest (even when its smaller than the given min_network_size)
119
120
sw = new StopWatch ().start ();
120
121
int removedComponents = 0 ;
121
122
int removedEdges = 0 ;
@@ -151,17 +152,14 @@ int removeSmallSubNetworks(BooleanEncodedValue accessEnc) {
151
152
throw new IllegalStateException ("Too many total edges were removed: " + removedEdges + " out of " + ghStorage .getEdges () + "\n " +
152
153
"The maximum number of removed edges is: " + allowedRemoved );
153
154
154
- subnetworks = ccs .getTotalComponents () - removedComponents ;
155
155
logger .info ("Removed " + removedComponents + " subnetworks (biggest removed: " + biggestRemoved + " nodes) -> " +
156
- subnetworks + " subnetwork(s) left (smallest: " + smallestRemaining + ", biggest: " + ccs .getBiggestComponent ().size () + " nodes)"
156
+ ( ccs . getTotalComponents () - removedComponents ) + " subnetwork(s) left (smallest: " + smallestRemaining + ", biggest: " + ccs .getBiggestComponent ().size () + " nodes)"
157
157
+ ", total removed edges: " + removedEdges + ", took: " + sw .stop ().getSeconds () + "s" );
158
158
return removedEdges ;
159
159
}
160
160
161
161
/**
162
162
* Makes all edges of the given component (the given set of node ids) inaccessible for the given access encoded value.
163
- * So far we are not removing the edges entirely from the graph (we could probably do this for edges that are blocked
164
- * for *all* vehicles similar to {@link #markNodesRemovedIfUnreachable})
165
163
*/
166
164
int blockEdgesForComponent (EdgeExplorer explorer , BooleanEncodedValue accessEnc , IntIndexedContainer component ) {
167
165
int removedEdges = 0 ;
@@ -183,8 +181,83 @@ private int blockEdgesForNode(EdgeExplorer explorer, BooleanEncodedValue accessE
183
181
return removedEdges ;
184
182
}
185
183
184
+ private int removeSmallSubNetworksEdgeBased (BooleanEncodedValue accessEnc , TurnCostProvider turnCostProvider ) {
185
+ // partition graph into strongly connected components using Tarjan's algorithm
186
+ StopWatch sw = new StopWatch ().start ();
187
+ EdgeBasedTarjanSCC tarjan = new EdgeBasedTarjanSCC (ghStorage , accessEnc , turnCostProvider , false );
188
+ EdgeBasedTarjanSCC .ConnectedComponents ccs = tarjan .findComponents ();
189
+ List <IntArrayList > components = ccs .getComponents ();
190
+ BitSet singleEdgeComponents = ccs .getSingleEdgeComponents ();
191
+ long numSingleEdgeComponents = singleEdgeComponents .cardinality ();
192
+ logger .info ("Found " + ccs .getTotalComponents () + " subnetworks (" + numSingleEdgeComponents + " single edges and "
193
+ + components .size () + " components with more than one edge, total nodes: " + ccs .getEdgeKeys () + "), took: " + sw .stop ().getSeconds () + "s" );
194
+
195
+ // n edge-keys roughly equal n/2 edges and components with n/2 edges approximately have n/2 nodes
196
+ // we could actually count the nodes to make this more consistent, but is it really needed?
197
+ final int minNetworkSizeEdges = 2 * minNetworkSize ;
198
+
199
+ // remove all small networks, but keep the biggest (even when its smaller than the given min_network_size)
200
+ sw = new StopWatch ().start ();
201
+ int removedComponents = 0 ;
202
+ int removedEdgeKeys = 0 ;
203
+ int smallestRemaining = ccs .getBiggestComponent ().size ();
204
+ int biggestRemoved = 0 ;
205
+
206
+ for (IntArrayList component : components ) {
207
+ if (component == ccs .getBiggestComponent ())
208
+ continue ;
209
+
210
+ if (component .size () < minNetworkSizeEdges ) {
211
+ for (IntCursor cursor : component ) {
212
+ removedEdgeKeys += removeEdgeWithKey (cursor .value , accessEnc );
213
+ }
214
+ removedComponents ++;
215
+ biggestRemoved = Math .max (biggestRemoved , component .size ());
216
+ } else {
217
+ smallestRemaining = Math .min (smallestRemaining , component .size ());
218
+ }
219
+ }
220
+
221
+ if (minNetworkSizeEdges > 0 ) {
222
+ BitSetIterator iter = singleEdgeComponents .iterator ();
223
+ for (int edgeKey = iter .nextSetBit (); edgeKey >= 0 ; edgeKey = iter .nextSetBit ()) {
224
+ removedEdgeKeys += removeEdgeWithKey (edgeKey , accessEnc );
225
+ removedComponents ++;
226
+ biggestRemoved = Math .max (biggestRemoved , 1 );
227
+ }
228
+ } else if (numSingleEdgeComponents > 0 ) {
229
+ smallestRemaining = Math .min (smallestRemaining , 1 );
230
+ }
231
+
232
+ int allowedRemoved = ghStorage .getEdges () / 2 ;
233
+ if (removedEdgeKeys / 2 > allowedRemoved )
234
+ throw new IllegalStateException ("Too many total (directed) edges were removed: " + removedEdgeKeys + " out of " + (2 * ghStorage .getEdges ()) + "\n " +
235
+ "The maximum number of removed edges is: " + (2 * allowedRemoved ));
236
+
237
+ logger .info ("Removed " + removedComponents + " subnetworks (biggest removed: " + biggestRemoved + " edges) -> " +
238
+ (ccs .getTotalComponents () - removedComponents ) + " subnetwork(s) left (smallest: " + smallestRemaining + ", biggest: " + ccs .getBiggestComponent ().size () + " edges)"
239
+ + ", total removed edges: " + removedEdgeKeys + ", took: " + sw .stop ().getSeconds () + "s" );
240
+ return removedEdgeKeys ;
241
+ }
242
+
243
+ private int removeEdgeWithKey (int edgeKey , BooleanEncodedValue accessEnc ) {
244
+ int edgeId = EdgeBasedTarjanSCC .getEdgeFromKey (edgeKey );
245
+ EdgeIteratorState edge = ghStorage .getEdgeIteratorState (edgeId , Integer .MIN_VALUE );
246
+ if (edgeKey % 2 == 0 && edge .get (accessEnc )) {
247
+ edge .set (accessEnc , false );
248
+ return 1 ;
249
+ }
250
+ if (edgeKey % 2 != 0 && edge .getReverse (accessEnc )) {
251
+ edge .setReverse (accessEnc , false );
252
+ return 1 ;
253
+ }
254
+ return 0 ;
255
+ }
256
+
186
257
/**
187
- * Removes nodes if all edges are not accessible. I.e. removes zero degree nodes.
258
+ * Removes nodes if all edges are not accessible. I.e. removes zero degree nodes. Note that so far we are not
259
+ * removing any edges entirely from the graph (we could probably do this for edges that are blocked for *all*
260
+ * vehicles.
188
261
*/
189
262
void markNodesRemovedIfUnreachable () {
190
263
EdgeExplorer edgeExplorer = ghStorage .createEdgeExplorer ();
@@ -207,6 +280,10 @@ boolean detectNodeRemovedForAllEncoders(EdgeExplorer edgeExplorerAllEdges, int n
207
280
// we could implement a 'fast check' for several previously marked removed nodes via GHBitSet
208
281
// removedNodesPerVehicle. The problem is that we would need long-indices but BitSet only supports int (due to nodeIndex*numberOfEncoders)
209
282
283
+ List <BooleanEncodedValue > accessEncList = new ArrayList <>();
284
+ for (PrepareJob job : prepareJobs ) {
285
+ accessEncList .add (job .accessEnc );
286
+ }
210
287
// if no edges are reachable return true
211
288
EdgeIterator iter = edgeExplorerAllEdges .setBaseNode (nodeIndex );
212
289
while (iter .next ()) {
@@ -219,4 +296,21 @@ boolean detectNodeRemovedForAllEncoders(EdgeExplorer edgeExplorerAllEdges, int n
219
296
220
297
return true ;
221
298
}
299
+
300
+ public static class PrepareJob {
301
+ private final String name ;
302
+ private final BooleanEncodedValue accessEnc ;
303
+ private final TurnCostProvider turnCostProvider ;
304
+
305
+ public PrepareJob (String name , BooleanEncodedValue accessEnc , TurnCostProvider turnCostProvider ) {
306
+ this .name = name ;
307
+ this .accessEnc = accessEnc ;
308
+ this .turnCostProvider = turnCostProvider ;
309
+ }
310
+
311
+ @ Override
312
+ public String toString () {
313
+ return name + "|" + (turnCostProvider == null ? "node-based" : "edge-based" );
314
+ }
315
+ }
222
316
}
0 commit comments