/* * 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 . * */ package de.tud.kom.p2psim.impl.topology.movement.local; 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; import com.graphhopper.GHRequest; import com.graphhopper.GHResponse; import com.graphhopper.GraphHopper; import com.graphhopper.routing.util.EdgeFilter; import com.graphhopper.routing.util.EncodingManager; import com.graphhopper.storage.index.LocationIndex; import com.graphhopper.storage.index.QueryResult; import com.graphhopper.util.DistanceCalc2D; import com.graphhopper.util.EdgeIteratorState; import com.graphhopper.util.PointList; import com.graphhopper.util.shapes.GHPoint; import de.tud.kom.p2psim.api.topology.Topology; import de.tud.kom.p2psim.api.topology.movement.SimLocationActuator; import de.tud.kom.p2psim.impl.topology.PositionVector; import de.tud.kom.p2psim.impl.topology.movement.modularosm.GPSCalculation; import de.tud.kom.p2psim.impl.topology.movement.modularosm.RouteSensorComponent; import de.tud.kom.p2psim.impl.util.Either; import de.tud.kom.p2psim.impl.util.Left; import de.tudarmstadt.maki.simonstrator.api.Binder; import de.tudarmstadt.maki.simonstrator.api.Monitor; import de.tudarmstadt.maki.simonstrator.api.Monitor.Level; import de.tudarmstadt.maki.simonstrator.api.component.ComponentNotAvailableException; /** * This movement strategy uses the data from osm and navigates the nodes throught streets to the destination * * 13.03.2017 Clemens Krug: Fixed a bug. When the GraphHopper routing had errors the nodes would move to the * top right corner and not, as intended, straight to their destination. * * @author Martin Hellwig * @version 1.0, 07.07.2015 */ public class RealWorldStreetsMovement extends AbstractLocalMovementStrategy { private static PositionVector worldDimensions; private GraphHopper hopper; private LocationIndex index; private boolean init = false; private static HashMap currentRoutes = new HashMap<>(); private Map routeSensorComponents = new LinkedHashMap<>(); private String osmFileLocation; //use pbf-format, because osm-format causes problems (xml-problems) private String graphFolderFiles; private String movementType; //car, bike or foot private String defaultMovement; private String navigationalType; //fastest, 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 private boolean uniqueFolders; /** * 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; public RealWorldStreetsMovement() { worldDimensions = Binder.getComponentOrNull(Topology.class) .getWorldDimensions(); latLower = GPSCalculation.getLatLower(); latUpper = GPSCalculation.getLatUpper(); lonLeft = GPSCalculation.getLonLeft(); lonRight = GPSCalculation.getLonRight(); } private void init() { hopper = new GraphHopper().forServer(); hopper.setOSMFile(osmFileLocation); // where to store graphhopper files? 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); } hopper.setEncodingManager(new EncodingManager(movementType)); hopper.importOrLoad(); index = hopper.getLocationIndex(); init = true; } @Override public Either nextPosition( SimLocationActuator comp, PositionVector destination) { return nextPosition(comp, destination, defaultMovement); } public Either nextPosition(SimLocationActuator comp, PositionVector destination, String movementType) { if (movementType == null || movementType.equals("") || !this.movementType.contains(movementType)) { throw new AssertionError("Invalid movement type: " + movementType); } 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."); } } PositionVector newPosition = null; if (destination .distanceTo(comp.getRealPosition()) > getMovementSpeed(comp)) { //if not set already for this node or new destination is different than last one RouteImpl trajectory = currentRoutes.get(comp); if(trajectory == null || destination.distanceTo(trajectory.getDestination()) > 1.0) { 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); GHResponse rsp = hopper.route(req); //If the requested point is not in the map data, simple return the destination as next point if(rsp.hasErrors()) { Monitor.log(this.getClass(), Monitor.Level.ERROR, "Routing request for Host %s with starting point (" + "%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()); PointList pointList = new PointList(); pointList.add(new GHPoint(destinationPosition[0], destinationPosition[1])); trajectory = new RouteImpl(comp.getRealPosition(), destination, pointList); currentRoutes.put(comp, trajectory); } else { PointList pointList = rsp.getBest().getPoints(); if (isCalculateRouteSegments()) { /* * Obtain segment IDs along the route. */ trajectory = new RouteImpl(comp.getRealPosition(), destination, pointList, routeSensor.getSegmentListeners(), calculateSegments(pointList)); } else { trajectory = new RouteImpl(comp.getRealPosition(), destination, pointList); } currentRoutes.put(comp, trajectory); } routeSensor.setNewRoute(trajectory); } newPosition = trajectory.updateCurrentLocation(comp, getMovementSpeed(comp), tolerance); } return new Left(newPosition); } /** * Calculate the route segments along the given point list. * * @return */ private List calculateSegments(PointList pointList) { int ptIdx = 0; int prevEdgeId = -1; List 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 * * @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]; gps_coordinates[0] = latLower + (latUpper - latLower) * (worldDimensions.getY() - y)/worldDimensions.getY(); 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 */ public static PositionVector transformGPSWindowToOwnWorld(double lat, double lon) { double x = worldDimensions.getX() * (lon - lonLeft)/(lonRight - lonLeft); double y = worldDimensions.getY() - worldDimensions.getY() * (lat - latLower)/(latUpper - latLower); x = Math.max(0, x); x = Math.min(worldDimensions.getX(), x); y = Math.max(0, y); y = Math.min(worldDimensions.getY(), y); return new PositionVector(x, y); } /** * 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 getMovementPoints(SimLocationActuator ms) { List positions = new LinkedList<>(); PointList pointList = currentRoutes.get(ms).getPointList(); 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) { return currentRoutes.get(ms).getPointList().calcDistance(new DistanceCalc2D()); } public void setOsmFileLocation(String osmFileLocation) { this.osmFileLocation = osmFileLocation; } public void setGraphFolderFiles(String graphFolderFiles) { this.graphFolderFiles = graphFolderFiles; } public void setMovementType(String movementType) { this.movementType = movementType; defaultMovement = movementType.split(",")[0]; } public void setNavigationalType(String navigationalType) { this.navigationalType = navigationalType; } public void setWaypointTolerance(double tolerance) { this.tolerance = tolerance; } /** * 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...) * * @param uniqueFolders */ public void setCreateUniqueFolders(boolean uniqueFolders) { this.uniqueFolders = uniqueFolders; } }