RealWorldStreetsMovement.java 18 KB
Newer Older
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
/*
 * Copyright (c) 2005-2010 KOM – Multimedia Communications Lab
 *
 * This file is part of PeerfactSim.KOM.
 * 
 * PeerfactSim.KOM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * any later version.
 * 
 * PeerfactSim.KOM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with PeerfactSim.KOM.  If not, see <http://www.gnu.org/licenses/>.
 *
 */

package de.tud.kom.p2psim.impl.topology.movement.local;

23
24
25
26
27
28
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Locale;
import java.util.Map;
29
import java.util.Random;
30
31
import java.util.UUID;

32
33
34
import com.graphhopper.GHRequest;
import com.graphhopper.GHResponse;
import com.graphhopper.GraphHopper;
35
import com.graphhopper.PathWrapper;
36
import com.graphhopper.reader.osm.GraphHopperOSM;
37
import com.graphhopper.routing.util.EdgeFilter;
38
import com.graphhopper.routing.util.EncodingManager;
39
import com.graphhopper.routing.util.TraversalMode;
40
import com.graphhopper.routing.weighting.BlockAreaWeighting;
41
42
import com.graphhopper.storage.index.LocationIndex;
import com.graphhopper.storage.index.QueryResult;
Clemens Krug's avatar
Clemens Krug committed
43
import com.graphhopper.util.DistanceCalc2D;
44
import com.graphhopper.util.EdgeIteratorState;
45
46
import com.graphhopper.util.Instruction;
import com.graphhopper.util.InstructionList;
47
import com.graphhopper.util.Parameters;
48
import com.graphhopper.util.Parameters.Routing;
49
import com.graphhopper.util.PointList;
50
import com.graphhopper.util.shapes.GHPoint;
51

52
import de.tud.kom.p2psim.api.topology.Topology;
53
import de.tud.kom.p2psim.api.topology.movement.SimLocationActuator;
54
import de.tud.kom.p2psim.impl.topology.movement.modularosm.GPSCalculation;
55
import de.tud.kom.p2psim.impl.topology.movement.modularosm.ModularMovementModelViz;
56
import de.tud.kom.p2psim.impl.topology.movement.modularosm.RouteSensorComponent;
57
import de.tud.kom.p2psim.impl.topology.movement.modularosm.attraction.RandomAttractionGenerator;
58
import de.tud.kom.p2psim.impl.topology.util.PositionVector;
59
60
import de.tud.kom.p2psim.impl.util.Either;
import de.tud.kom.p2psim.impl.util.Left;
61
import de.tudarmstadt.maki.simonstrator.api.Binder;
62
import de.tudarmstadt.maki.simonstrator.api.Monitor;
63
import de.tudarmstadt.maki.simonstrator.api.Randoms;
64
import de.tudarmstadt.maki.simonstrator.api.Monitor.Level;
65
import de.tudarmstadt.maki.simonstrator.api.component.ComponentNotAvailableException;
66
import de.tudarmstadt.maki.simonstrator.api.util.XMLConfigurableConstructor;
Clemens Krug's avatar
Clemens Krug committed
67

68
69
/**
 * This movement strategy uses the data from osm and navigates the nodes throught streets to the destination
Clemens Krug's avatar
Clemens Krug committed
70
 *
71
 * 13.03.2017 Clemens Krug: Fixed a bug. When the GraphHopper routing had errors the nodes would move to the
Clemens Krug's avatar
Clemens Krug committed
72
 * top right corner and not, as intended, straight to their destination.
73
74
75
 * 
 * @author Martin Hellwig
 * @version 1.0, 07.07.2015
76
77
78
79
80
81
82
83
84
 * 
 * - added a possibility to allow the usage of alternative routes
 * - set the probability for this alternatives
 * - allow a dynamic reload of graphhopper files when switching routing algorithms
 * - allow to define blocked areas, that are not used for routing
 * 
 * @author Julian Zobel
 * @version 02.04.2020
 *  
85
86
87
 */
public class RealWorldStreetsMovement extends AbstractLocalMovementStrategy {
	
88
89
	private Random random = Randoms.getRandom(RealWorldStreetsMovement.class);
	
90
	private static PositionVector worldDimensions;
91
	private GraphHopper hopper;
92
	private LocationIndex index;
93
94
	private boolean init = false;
	
95
96
97
98
	private static HashMap<SimLocationActuator, RouteImpl> currentRoutes = new HashMap<>();

	private Map<SimLocationActuator, RouteSensorComponent> routeSensorComponents = new LinkedHashMap<>();

99
100
101
	private String osmFileLocation; //use pbf-format, because osm-format causes problems (xml-problems)
	private String graphFolderFiles;
	private String movementType; //car, bike or foot
Clemens Krug's avatar
Clemens Krug committed
102
	private String defaultMovement;
103
	private String navigationalType; //fastest,
104
	private String blockedAreas; // default null
105
106
107
108
	private static double latLower; //Values from -90 to 90; always smaller than latUpper
	private static double latUpper; //Values from -90 to 90
	private static double lonLeft; //Values from -180 to 180; Always smaller than lonRight
	private static double lonRight; //Values from -180 to 180
109
	private boolean uniqueFolders;
110
111
112
	
	private boolean allowAlternativeRoutes = false;
	private double probabilityForAlternativeRoute = 0.0;
113

114
115
116
117
118
119
	/**
	 * Tolerance in meters (if the node reached a waypoint up to "tolerance"
	 * meters, it will select the next waypoint in the path.
	 */
	private double tolerance = 1;

120
121
122
123
124
125
	@XMLConfigurableConstructor({"blockedAreas"})
	public RealWorldStreetsMovement(String blockedAreas) {
		this();
		this.setBlockedAreas(blockedAreas);
	}
	
126
	public RealWorldStreetsMovement() {
127
		worldDimensions = Binder.getComponentOrNull(Topology.class)
128
				.getWorldDimensions();
129
130
		latLower = GPSCalculation.getLatLower();
		latUpper = GPSCalculation.getLatUpper();
131
132
		lonLeft = GPSCalculation.getLonLeft();
		lonRight = GPSCalculation.getLonRight();
133
134
135
	}
	
	private void init() {
136
137
138
		hopper = new GraphHopperOSM().forServer();
		hopper.setDataReaderFile(osmFileLocation);
		
139
		// where to store graphhopper files?
140
141
142
143
144
145
146
147
148
149
		if (uniqueFolders) {
			Monitor.log(RealWorldStreetsMovement.class, Level.WARN,
					"Using per-simulation unique folders for GraphHopper temporary data in %s. Remember to delete them to prevent your disk from filling up.",
					graphFolderFiles);
			hopper.setGraphHopperLocation(graphFolderFiles + "/"
					+ UUID.randomUUID().toString());
		} else {
			hopper.setGraphHopperLocation(graphFolderFiles + "/"
					+ osmFileLocation.hashCode() + movementType);
		}
150
151
152
153
		hopper.setEncodingManager(EncodingManager.create(movementType));
				
		// if we have blocked areas defined, the CH in Graphhopper needs to be disabled!
		// this also requires a new import 
154
		if((blockedAreas != null && !blockedAreas.isEmpty()) || allowAlternativeRoutes) {		
155
156
157
158
159
160
161
162
163
164
165
166
167
			hopper.setCHEnabled(false);
		}
		
		// try to load a cached import. If the cached import was with a different CH_ENABLED setting then now specified, this will fail.
		// in this case, clean cache and re-import.
		try {
			hopper.importOrLoad();
		} catch (Exception e) {
			System.err.println("[RealWorldStreetMovement] GraphHopper graph file must be imported again!");
			hopper.clean();
			hopper.importOrLoad();
		}	
		
168
		index = hopper.getLocationIndex();
169
		init = true;
170
	}	
171

172
173
174
	@Override
	public Either<PositionVector, Boolean> nextPosition(
			SimLocationActuator comp, PositionVector destination) {
Clemens Krug's avatar
Clemens Krug committed
175
        return nextPosition(comp, destination, defaultMovement);
176
177
    }

178
	public Either<PositionVector, Boolean> nextPosition(SimLocationActuator comp,
179
			PositionVector destination, String movementType) {
Clemens Krug's avatar
Clemens Krug committed
180

181
182
		if (movementType == null || movementType.equals("")
				|| !this.movementType.contains(movementType)) {
Clemens Krug's avatar
Clemens Krug committed
183
			throw new AssertionError("Invalid movement type: " + movementType);
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
		}

		if (!init) {
			init();
		}

		RouteSensorComponent routeSensor = routeSensorComponents.get(comp);
		if (routeSensor == null) {
			try {
				routeSensorComponents.put(comp, comp.getHost()
						.getComponent(RouteSensorComponent.class));
				routeSensor = routeSensorComponents.get(comp);
			} catch (ComponentNotAvailableException e) {
				throw new AssertionError(
						"The movement model needs to provide RouteSensorComponents.");
			}
		}

Clemens Krug's avatar
Clemens Krug committed
202

203
		PositionVector newPosition = null;
204
205
		if (destination
				.distanceTo(comp.getRealPosition()) > getMovementSpeed(comp)) {
206
			//if not set already for this node or new destination is different than last one
207
			RouteImpl trajectory = currentRoutes.get(comp);
208
			if(trajectory == null || destination.distanceTo(trajectory.getDestination()) > 1.0) {
209
210
211
212
213
214
				double[] startPosition = transformOwnWorldWindowToGPS(comp.getRealPosition().getX(), comp.getRealPosition().getY());
				double[] destinationPosition = transformOwnWorldWindowToGPS(destination.getX(), destination.getY());
				GHRequest req = new GHRequest(startPosition[0], startPosition[1], destinationPosition[0], destinationPosition[1]).
					    setWeighting(navigationalType).
					    setVehicle(movementType).
					    setLocale(Locale.GERMANY);
215
216
217
218
219
				
				// blocks one or multiple given areas from being used by the routing
				// https://github.com/graphhopper/graphhopper/blob/master/docs/web/api-doc.md#flexible				
				if(blockedAreas != null && !blockedAreas.isEmpty()) {		
					req.getHints().put(Routing.BLOCK_AREA, blockedAreas);
220
				}		
221
				if(allowAlternativeRoutes) {
222
223
224
225
					// see: https://discuss.graphhopper.com/t/alternative-routes/424/11
					req.setAlgorithm(Parameters.Algorithms.ALT_ROUTE);			
					req.getHints().put(Parameters.Algorithms.AltRoute.MAX_WEIGHT, "2.0"); // how much longer can alternatives be?
					req.getHints().put(Parameters.Algorithms.AltRoute.MAX_PATHS, "5"); // how much alternatives should be returned at max?					
226
				}
227
				
228
229
230
				
				GHResponse rsp = hopper.route(req);				
				
231
232
				//If the requested point is not in the map data, simple return the destination as next point
				if(rsp.hasErrors()) {
233
					Monitor.log(this.getClass(), Monitor.Level.ERROR, "Routing request for Host %s with starting point (" +
Clemens Krug's avatar
Clemens Krug committed
234
235
							"%f,%f), destination (%f,%f) and type %s failed with error: %s.", comp.getHost().getId().valueAsString(),startPosition[0], startPosition[1],
							destinationPosition[0], destinationPosition[1], movementType, rsp.getErrors());
236
					
237
					PointList pointList = new PointList();
Clemens Krug's avatar
Clemens Krug committed
238
					pointList.add(new GHPoint(destinationPosition[0], destinationPosition[1]));
239
240
241
					trajectory = new RouteImpl(comp.getRealPosition(), destination, pointList);
					currentRoutes.put(comp, trajectory);

242
				} else {
243
					
244
					PointList pointList = rsp.getBest().getPoints();
245
					
246
247
248
					// alternatives for routing are allowed for distances bigger than 50
					if(allowAlternativeRoutes && rsp.hasAlternatives() && rsp.getBest().getDistance() > 50) {
						// alternative route is taken with a certain chance
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
						if(random.nextDouble() <= probabilityForAlternativeRoute) {							
							List<PathWrapper> paths = rsp.getAll();		
						
							// remove the best, we do not want this!				
							paths.remove(rsp.getBest()); 		
							
							PathWrapper choice;							
							if(paths.size() == 1) {
								choice = paths.get(0);
							}
							else {
								choice = paths.get(random.nextInt(paths.size() - 1) + 1);
							}
							
							pointList = choice.getPoints();
264
						}	
265
					}							
266
267
268
269
270
271
272

					if (isCalculateRouteSegments()) {
						/*
						 * Obtain segment IDs along the route.
						 */
						trajectory = new RouteImpl(comp.getRealPosition(),
								destination, pointList,
Björn Richerzhagen's avatar
Björn Richerzhagen committed
273
								routeSensor.getSegmentListeners(), routeSensor,
274
								calculateSegments(pointList));
275
276
277
278
279
					} else {
						trajectory = new RouteImpl(comp.getRealPosition(),
								destination, pointList);
					}
					currentRoutes.put(comp, trajectory);
280
				}
281
				routeSensor.setNewRoute(trajectory);
282
			}
283
			newPosition = trajectory.updateCurrentLocation(comp, getMovementSpeed(comp), tolerance);
284
285
286
287

			if (trajectory.reachedDestination()) {
				routeSensor.reachedDestination();
			}
288
289
290
291
		}
		return new Left<PositionVector, Boolean>(newPosition);
	}
	
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
	/**
	 * Calculate RouteSegments using the PathWrapper
	 * @param pathWrapper
	 * @return
	 */
	private List<RouteSegmentImpl> calculateSegments(PathWrapper pathWrapper) {
		InstructionList instructions = pathWrapper.getInstructions();
		int ptIdx = 0;
		List<RouteSegmentImpl> segments = new LinkedList<>();
		RouteSegmentImpl lastSegment = null;
		
		for (Instruction instr : instructions) {
			RouteSegmentImpl segment = null;
			PointList points = instr.getPoints();
			String name = instr.getName();
307
308
309
			if (name.isEmpty()) {				
				name = Integer.toString(instr.getPoints().get(0).hashCode());
				//name = Integer.toString(instr.getPoints().toGHPoint(0).hashCode());  @deprecated for old GraphHopper version
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
			}
			for (GHPoint point : points) {
				if (segment == null) {
					if (lastSegment != null) {
						lastSegment.addRouteLocation(transformGPSWindowToOwnWorld(
								point.getLat(), point.getLon()));
					}
					segment = new RouteSegmentImpl(name, ptIdx, transformGPSWindowToOwnWorld(point.getLat(),
						point.getLon()));
				} else {
					segment.addRouteLocation(transformGPSWindowToOwnWorld(
							point.getLat(), point.getLon()));
				}
				ptIdx++;
			}
			lastSegment = segment;
			segments.add(segment);
		}
		
		return segments;
	}
	
332
	/**
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
	 * Calculate the route segments along the given point list.
	 * 
	 * @return
	 */
	private List<RouteSegmentImpl> calculateSegments(PointList pointList) {
		int ptIdx = 0;
		int prevEdgeId = -1;
		List<RouteSegmentImpl> segments = new LinkedList<>();
		RouteSegmentImpl lastSegment = null;
		for (GHPoint point : pointList) {
			QueryResult qr = index.findClosest(point.getLat(), point.getLon(),
					EdgeFilter.ALL_EDGES);
			EdgeIteratorState nearEdge = qr.getClosestEdge();
			if (nearEdge != null) {
				int edgeId = nearEdge.getEdge();
				if (edgeId != prevEdgeId) {
					if (lastSegment != null) {
						lastSegment.addRouteLocation(
								transformGPSWindowToOwnWorld(point.getLat(),
										point.getLon()));
					}
					lastSegment = new RouteSegmentImpl(Integer.toString(edgeId),
							ptIdx, transformGPSWindowToOwnWorld(point.getLat(),
									point.getLon()));
					segments.add(lastSegment);
				} else {
					lastSegment.addRouteLocation(transformGPSWindowToOwnWorld(
							point.getLat(), point.getLon()));
				}
			}
			ptIdx++;
		}
		return segments;
	}

	/**
	 * Projects the world coordinates in the given gps window to the
	 * gps-coordinates
	 * 
372
373
374
375
376
377
	 * @param x
	 * @param y
	 * @return The projected position in gps-coordinates (lat, long)
	 */
	private double[] transformOwnWorldWindowToGPS(double x, double y) {
		double[] gps_coordinates = new double[2];
378
		gps_coordinates[0] = latLower + (latUpper - latLower) * (worldDimensions.getY() - y)/worldDimensions.getY();
379
380
381
382
383
384
385
386
387
388
		gps_coordinates[1] = lonLeft + (lonRight - lonLeft) * x/worldDimensions.getX();
		return gps_coordinates;
	}
	
	/**
	 * Projects the gps coordinates in the given gps window to the world-coordinates given in world-dimensions
	 * @param lat
	 * @param lon
	 * @return The projected position in world-dimensions
	 */
389
	public static PositionVector transformGPSWindowToOwnWorld(double lat, double lon) {
390
		double x = worldDimensions.getX() * (lon - lonLeft)/(lonRight - lonLeft);
391
		double y = worldDimensions.getY() - worldDimensions.getY() * (lat - latLower)/(latUpper - latLower);
392
393
394
395
		x = Math.max(0, x);
		x = Math.min(worldDimensions.getX(), x);
		y = Math.max(0, y);
		y = Math.min(worldDimensions.getY(), y);
396
397
		return new PositionVector(x, y);
	}
Clemens Krug's avatar
Clemens Krug committed
398
399
400
401
402
403
404
405
406
407

	/**
	 * Returns a list of points representing the current route of the component. Points are
	 * in x / y values of the own world.
	 * @param ms the component
	 * @return list of movement points.
	 */
	public List<PositionVector> getMovementPoints(SimLocationActuator ms)
	{
		List<PositionVector> positions = new LinkedList<>();
408
		PointList pointList = currentRoutes.get(ms).getPointList();
Clemens Krug's avatar
Clemens Krug committed
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448

		pointList.forEach(p -> positions.add(new PositionVector(transformGPSWindowToOwnWorld(p.getLat(), p.getLon()))));

		return positions;
	}

	/**
	 * Calculates the length of a route in meters.
	 * @param start Starting position in own world coordinates (x / y)
	 * @param destination Destination on own world coordinates (x / y)
	 * @return the length of the route in meters.
	 */
	public double calculateRouteLength(PositionVector start, PositionVector destination)
	{
		PointList pointList;
			double[] startPosition = transformOwnWorldWindowToGPS(start.getX(), start.getY());
			double[] destinationPosition = transformOwnWorldWindowToGPS(destination.getX(), destination.getY());
			GHRequest req = new GHRequest(startPosition[0], startPosition[1], destinationPosition[0], destinationPosition[1]).
					setWeighting(navigationalType).
					setVehicle(movementType).
					setLocale(Locale.GERMANY);
			GHResponse rsp = hopper.route(req);

			//If the requested point is not in the map data, return -1
			if(rsp.hasErrors()) {
				return -1;
			}
			else {
				pointList = rsp.getBest().getPoints();
				return pointList.calcDistance(new DistanceCalc2D());
			}
	}

	/**
	 * Calculates the length of the current route of the SimLocationActuator.
	 * @param ms the component
	 * @return the length of the current route
	 */
	public double getCurrentRouteLength(SimLocationActuator ms)
	{
449
		return currentRoutes.get(ms).getPointList().calcDistance(new DistanceCalc2D());
Clemens Krug's avatar
Clemens Krug committed
450
	}
451
452
453
454
455
456
457
458
459
460
461
	
	public void setOsmFileLocation(String osmFileLocation) {
		this.osmFileLocation = osmFileLocation;
	}

	public void setGraphFolderFiles(String graphFolderFiles) {
		this.graphFolderFiles = graphFolderFiles;
	}

	public void setMovementType(String movementType) {
		this.movementType = movementType;
Clemens Krug's avatar
Clemens Krug committed
462
		defaultMovement = movementType.split(",")[0];
463
464
465
466
467
	}

	public void setNavigationalType(String navigationalType) {
		this.navigationalType = navigationalType;
	}
468
	
469
470
	public void setWaypointTolerance(double tolerance) {
		this.tolerance = tolerance;
471
	}
472

473
474
475
476
477
	/**
	 * For large batch simulations, we need to prevent same-time access to
	 * garphhopper temp data. Therefore, this flag creates unique folders for
	 * each run (which, obviously, wastes a lot of space and comp-resources and
	 * should not be used in standalone, single-threaded demo mode...)
478
	 *
479
480
481
482
483
	 * @param uniqueFolders
	 */
	public void setCreateUniqueFolders(boolean uniqueFolders) {
		this.uniqueFolders = uniqueFolders;
	}
484
485
486
487
	
	public void setBlockedAreas(String blockedAreas) {
		if(!blockedAreas.isEmpty()) {
			this.blockedAreas = blockedAreas;
488
489
490
		}		
	}
	
491
492
493
494
	public String getBlockedAreas() {
		return this.blockedAreas;
	}
	
495
496
497
498
499
500
	public void setAllowAlternativeRoutes(boolean allowAlternativeRoutes) {
		this.allowAlternativeRoutes = allowAlternativeRoutes;
	}
	
	public void setProbabilityForAlternativeRoute(double p) {
		this.probabilityForAlternativeRoute = p;
501
	}
502
	 
503
}