RealWorldStreetsMovement.java 15.7 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
29
30
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.UUID;

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

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

63
64
/**
 * This movement strategy uses the data from osm and navigates the nodes throught streets to the destination
Clemens Krug's avatar
Clemens Krug committed
65
 *
66
 * 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
67
 * top right corner and not, as intended, straight to their destination.
68
69
70
71
72
73
 * 
 * @author Martin Hellwig
 * @version 1.0, 07.07.2015
 */
public class RealWorldStreetsMovement extends AbstractLocalMovementStrategy {
	
74
	private static PositionVector worldDimensions;
75
	private GraphHopper hopper;
76
	private LocationIndex index;
77
78
	private boolean init = false;
	
79
80
81
82
	private static HashMap<SimLocationActuator, RouteImpl> currentRoutes = new HashMap<>();

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

83
84
85
	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
86
	private String defaultMovement;
87
	private String navigationalType; //fastest,
88
	private String blockedAreas; // default null
89
90
91
92
	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
93
	private boolean uniqueFolders;
94

95
96
97
98
99
100
	/**
	 * 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;

101
102
103
104
105
106
	@XMLConfigurableConstructor({"blockedAreas"})
	public RealWorldStreetsMovement(String blockedAreas) {
		this();
		this.setBlockedAreas(blockedAreas);
	}
	
107
	public RealWorldStreetsMovement() {
108
		worldDimensions = Binder.getComponentOrNull(Topology.class)
109
				.getWorldDimensions();
110
111
		latLower = GPSCalculation.getLatLower();
		latUpper = GPSCalculation.getLatUpper();
112
113
		lonLeft = GPSCalculation.getLonLeft();
		lonRight = GPSCalculation.getLonRight();
114
115
116
	}
	
	private void init() {
117
118
119
		hopper = new GraphHopperOSM().forServer();
		hopper.setDataReaderFile(osmFileLocation);
		
120
		// where to store graphhopper files?
121
122
123
124
125
126
127
128
129
130
		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);
		}
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
		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 
		if(blockedAreas != null && !blockedAreas.isEmpty()) {		
			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();
		}	
		
149
		index = hopper.getLocationIndex();
150
		init = true;
151
	}	
152

153
154
155
	@Override
	public Either<PositionVector, Boolean> nextPosition(
			SimLocationActuator comp, PositionVector destination) {
Clemens Krug's avatar
Clemens Krug committed
156
        return nextPosition(comp, destination, defaultMovement);
157
158
    }

159
	public Either<PositionVector, Boolean> nextPosition(SimLocationActuator comp,
160
			PositionVector destination, String movementType) {
Clemens Krug's avatar
Clemens Krug committed
161

162
163
		if (movementType == null || movementType.equals("")
				|| !this.movementType.contains(movementType)) {
Clemens Krug's avatar
Clemens Krug committed
164
			throw new AssertionError("Invalid movement type: " + movementType);
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
		}

		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
183

184
		PositionVector newPosition = null;
185
186
		if (destination
				.distanceTo(comp.getRealPosition()) > getMovementSpeed(comp)) {
187
			//if not set already for this node or new destination is different than last one
188
			RouteImpl trajectory = currentRoutes.get(comp);
189
			if(trajectory == null || destination.distanceTo(trajectory.getDestination()) > 1.0) {
190
191
192
193
194
195
				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);
196
197
198
199
200
201
202
203
204
				
				// 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);
				}				
				
				GHResponse rsp = hopper.route(req);				
				
205
206
				//If the requested point is not in the map data, simple return the destination as next point
				if(rsp.hasErrors()) {
207
					Monitor.log(this.getClass(), Monitor.Level.ERROR, "Routing request for Host %s with starting point (" +
Clemens Krug's avatar
Clemens Krug committed
208
209
							"%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());
210
					
211
					PointList pointList = new PointList();
Clemens Krug's avatar
Clemens Krug committed
212
					pointList.add(new GHPoint(destinationPosition[0], destinationPosition[1]));
213
214
215
					trajectory = new RouteImpl(comp.getRealPosition(), destination, pointList);
					currentRoutes.put(comp, trajectory);

216
217
				} else {
					PointList pointList = rsp.getBest().getPoints();
218
219
220
221
222
223
224

					if (isCalculateRouteSegments()) {
						/*
						 * Obtain segment IDs along the route.
						 */
						trajectory = new RouteImpl(comp.getRealPosition(),
								destination, pointList,
Björn Richerzhagen's avatar
Björn Richerzhagen committed
225
								routeSensor.getSegmentListeners(), routeSensor,
226
								calculateSegments(rsp.getBest()));
227
228
229
230
231
					} else {
						trajectory = new RouteImpl(comp.getRealPosition(),
								destination, pointList);
					}
					currentRoutes.put(comp, trajectory);
232
				}
233
				routeSensor.setNewRoute(trajectory);
234
			}
235
			newPosition = trajectory.updateCurrentLocation(comp, getMovementSpeed(comp), tolerance);
236
237
238
239

			if (trajectory.reachedDestination()) {
				routeSensor.reachedDestination();
			}
240
241
242
243
		}
		return new Left<PositionVector, Boolean>(newPosition);
	}
	
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
	/**
	 * 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();
259
260
261
			if (name.isEmpty()) {				
				name = Integer.toString(instr.getPoints().get(0).hashCode());
				//name = Integer.toString(instr.getPoints().toGHPoint(0).hashCode());  @deprecated for old GraphHopper version
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
			}
			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;
	}
	
284
	/**
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
	 * 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
	 * 
324
325
326
327
328
329
	 * @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];
330
		gps_coordinates[0] = latLower + (latUpper - latLower) * (worldDimensions.getY() - y)/worldDimensions.getY();
331
332
333
334
335
336
337
338
339
340
		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
	 */
341
	public static PositionVector transformGPSWindowToOwnWorld(double lat, double lon) {
342
		double x = worldDimensions.getX() * (lon - lonLeft)/(lonRight - lonLeft);
343
		double y = worldDimensions.getY() - worldDimensions.getY() * (lat - latLower)/(latUpper - latLower);
344
345
346
347
		x = Math.max(0, x);
		x = Math.min(worldDimensions.getX(), x);
		y = Math.max(0, y);
		y = Math.min(worldDimensions.getY(), y);
348
349
		return new PositionVector(x, y);
	}
Clemens Krug's avatar
Clemens Krug committed
350
351
352
353
354
355
356
357
358
359

	/**
	 * 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<>();
360
		PointList pointList = currentRoutes.get(ms).getPointList();
Clemens Krug's avatar
Clemens Krug committed
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400

		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)
	{
401
		return currentRoutes.get(ms).getPointList().calcDistance(new DistanceCalc2D());
Clemens Krug's avatar
Clemens Krug committed
402
	}
403
404
405
406
407
408
409
410
411
412
413
	
	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
414
		defaultMovement = movementType.split(",")[0];
415
416
417
418
419
	}

	public void setNavigationalType(String navigationalType) {
		this.navigationalType = navigationalType;
	}
420
	
421
422
	public void setWaypointTolerance(double tolerance) {
		this.tolerance = tolerance;
423
	}
424

425
426
427
428
429
	/**
	 * 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...)
430
	 *
431
432
433
434
435
	 * @param uniqueFolders
	 */
	public void setCreateUniqueFolders(boolean uniqueFolders) {
		this.uniqueFolders = uniqueFolders;
	}
436
437
438
439
440
441
	
	public void setBlockedAreas(String blockedAreas) {
		if(!blockedAreas.isEmpty()) {
			this.blockedAreas = blockedAreas;
		}
	}
442
}