/* * 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.modularosm; import java.util.LinkedHashMap; import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import java.util.Random; import java.util.Set; import java.util.Vector; import de.tud.kom.p2psim.api.scenario.ConfigurationException; import de.tud.kom.p2psim.api.topology.Topology; import de.tud.kom.p2psim.api.topology.movement.MovementModel; import de.tud.kom.p2psim.api.topology.movement.SimLocationActuator; import de.tud.kom.p2psim.api.topology.movement.local.LocalMovementStrategy; import de.tud.kom.p2psim.api.topology.placement.PlacementModel; import de.tud.kom.p2psim.impl.simengine.Simulator; import de.tud.kom.p2psim.impl.topology.PositionVector; import de.tud.kom.p2psim.impl.topology.TopologyFactory; import de.tud.kom.p2psim.impl.topology.movement.modularosm.attraction.IAttractionGenerator; import de.tud.kom.p2psim.impl.topology.movement.modularosm.mapvisualization.IMapVisualization; import de.tud.kom.p2psim.impl.topology.movement.modularosm.transition.ITransitionStrategy; import de.tud.kom.p2psim.impl.topology.movement.modularosm.transition.ITransitionStrategy.AttractionAssignmentListener; import de.tud.kom.p2psim.impl.topology.views.VisualizationTopologyView.VisualizationInjector; import de.tud.kom.p2psim.impl.util.Either; import de.tudarmstadt.maki.simonstrator.api.Binder; import de.tudarmstadt.maki.simonstrator.api.Event; import de.tudarmstadt.maki.simonstrator.api.EventHandler; import de.tudarmstadt.maki.simonstrator.api.Randoms; import de.tudarmstadt.maki.simonstrator.api.Time; import de.tudarmstadt.maki.simonstrator.api.component.sensor.location.AttractionPoint; /** * Modular Movement Model uses different models/strategies to create a movement * model. In this implementation, it has 3 different models/strategies. *

* M0: AttractionGenerator -> Generates the {@link AttractionPoint}s and place * them on the map. The {@link AttractionPoint}s can't be moved, because they * are static POIs from real-world data! *

* M1: A general {@link MovementModel} is not used, because we use static * attraction points. *

* M2: The {@link ITransitionStrategy}! It takes the Hosts, which should be moved * around, but calculates only the assignment to the {@link AttractionPoint}s. * It doesn't move the Hosts! It will be only assignment a new AttractionPoint! * *

* M3: The {@link LocalMovementStrategy} is responsible for the movement of the * Hosts. It moves the hosts to the assigned AttractionPoint, and if the * AttractionPoint has moved, then will be followed. The * {@link LocalMovementStrategy} will be called from the * {@link ModularMovementModel} to do a Movement! *

* This class contains all three components and manage the data exchange. * Additionally it contains an periodic operation, which handle the movement of * all hosts. This mean, that it will be call the {@link LocalMovementStrategy} * with the destination. Please take care, that the handling of the movement of * the AttractionPoints will be handled by the movement model in M1!
* Further it contains an offset for every Host, which will be added to the * destination point (AttractionPoint), so that not all hosts, which are * assigned to one {@link AttractionPoint}, lies on the same point.
* * CHANGELOG * * - 04.01.2017 Clemens Krug: Added the possibility to configure the model * visualisation via XML. If not specified, the visualisation will use the * {@link ModularMovementModelViz}, just as before. Thus there shouldn't be any problems * with older code. * * @author Martin Hellwig, Christoph Muenker * @version 1.0, 07.07.2015 */ public class ModularMovementModel implements MovementModel, EventHandler, AttractionAssignmentListener { private final int EVENT_MOVE = 1; private final int EVENT_INIT = 2; protected PositionVector worldDimensions; protected ITransitionStrategy transition; protected IAttractionGenerator attractionGenerator; protected LocalMovementStrategy localMovementStrategy; protected IMapVisualization mapVisualization; private ModularMovementModelViz modelVisualisation; protected Set moveableHosts = new LinkedHashSet(); private Map currentTarget = new LinkedHashMap<>(); private Map routeSensorComponents = new LinkedHashMap<>(); private boolean initialized = false; private long timeBetweenMoveOperation = Simulator.SECOND_UNIT; private Random rand; public ModularMovementModel() { this.worldDimensions = Binder.getComponentOrNull(Topology.class) .getWorldDimensions(); this.rand = Randoms.getRandom(ModularMovementModel.class); // scheduling initalization! Event.scheduleImmediately(this, null, EVENT_INIT); } /** * This Method will be not called from the Components. So we call this * manually! */ public void initialize() { if (!initialized) { if (modelVisualisation == null) { modelVisualisation = new ModularMovementModelViz(this); } VisualizationInjector.injectComponent(modelVisualisation); if (mapVisualization != null) { VisualizationInjector.injectComponent(mapVisualization); } checkConfiguration(); // setWayPointModel localMovementStrategy.setObstacleModel(Binder .getComponentOrNull(Topology.class).getObstacleModel()); localMovementStrategy.setWaypointModel(Binder .getComponentOrNull(Topology.class).getWaypointModel()); /* * Scale depending on calculation interval, if interval != 1 Second. */ localMovementStrategy .setScaleFactor(timeBetweenMoveOperation / (double) Time.SECOND); List attractionPoints = attractionGenerator .getAttractionPoints(); transition.setAttractionPoints(attractionPoints); transition.addAttractionAssignmentListener(this); // This adds the mobile hosts (smartphones/users) to the transition // strategy for (SimLocationActuator ms : moveableHosts) { transition.addComponent(ms); } setTimeBetweenMoveOperations(timeBetweenMoveOperation); // initial move move(); initialized = true; } } /** * This default implementation relies on {@link PlacementModel}s to be * configured in the {@link TopologyFactory} */ @Override public void placeComponent(SimLocationActuator actuator) { // not supported } @Override public void changeTargetLocation(SimLocationActuator actuator, AttractionPoint ap) { transition.updateTargetAttractionPoint(actuator, ap); } @Override public AttractionPoint getTargetLocation(SimLocationActuator actuator) { return transition.getAssignment(actuator); } @Override public Set getAllAttractionPoints() throws UnsupportedOperationException { return transition.getAllAttractionPoints(); } private void checkConfiguration() { if (localMovementStrategy == null) { throw new ConfigurationException( "LocalMovementStrategy is missing in ModularMovementModel!"); } if (transition == null) { throw new ConfigurationException( "TransitionStrategy is missing in ModularMovementModel!"); } if (attractionGenerator == null) { throw new ConfigurationException( "AttractionGenerator is missing in ModularMovementModel!"); } } @Override public void addComponent(SimLocationActuator comp) { moveableHosts.add(comp); if (!routeSensorComponents.containsKey(comp)) { routeSensorComponents.put(comp, new RouteSensorComponent(comp)); } } public Set getAllLocationActuators() { return moveableHosts; } @Override public void setTimeBetweenMoveOperations(long time) { if (time > 0) { this.timeBetweenMoveOperation = time; } else { throw new ConfigurationException( "time is negative for the Move Operations"); } } @Override public void updatedAttractionAssignment(SimLocationActuator component, AttractionPoint newAssignment) { /* * Use this method to calculate the offset and target location for a * host. */ PositionVector attractionCenter = (PositionVector) newAssignment; PositionVector destination = null; /* * Even if an AP does not have a radius, we slightly offset */ double apRadius = Math.max(newAssignment.getRadius(), 25.0); int tries = 0; do { destination = new PositionVector(attractionCenter); // Gaussian with std = 1 --> >99% of nodes PositionVector offset = new PositionVector( rand.nextGaussian() * apRadius / 3, rand.nextGaussian() * apRadius / 3); destination.add(offset); // Check constraints if (destination.getX() < 0.0 || destination.getX() > Binder .getComponentOrNull(Topology.class) .getWorldDimensions().getX() || destination.getY() < 0.0 || destination.getY() > Binder .getComponentOrNull(Topology.class) .getWorldDimensions().getY()) { destination = null; if (tries > 100) { throw new AssertionError("Unable to find a valid target destination within <100 tries."); } } tries++; } while (destination == null); currentTarget.put(component, destination); } protected void move() { for (SimLocationActuator component : moveableHosts) { assert currentTarget.containsKey(component); doLocalMovement(component, currentTarget.get(component)); } Event.scheduleWithDelay(timeBetweenMoveOperation, this, null, EVENT_MOVE); } /** * * Ask the local movement strategy for the next position. It may return the * next position or a boolean with true to notify the movement model that it * can't get any closer to the current way point. * * @param ms * @param destination */ protected void doLocalMovement(SimLocationActuator ms, PositionVector destination) { Either either = localMovementStrategy .nextPosition(ms, destination); if (either.hasLeft()) { ms.updateCurrentLocation(either.getLeft()); /* * Check for negative or out of bound coordinates! */ assert ms.getRealPosition().getX() >= 0.0 && ms.getRealPosition().getX() <= Binder .getComponentOrNull(Topology.class) .getWorldDimensions().getX(); assert ms.getRealPosition().getY() >= 0.0 && ms.getRealPosition().getY() <= Binder .getComponentOrNull(Topology.class) .getWorldDimensions().getY(); } else { transition.reachedAttractionPoint(ms); } } public void setIAttractionGenerator( IAttractionGenerator attractionGenerator) { this.attractionGenerator = attractionGenerator; } public void setLocalMovementStrategy( LocalMovementStrategy localMovementStrategy) { this.localMovementStrategy = localMovementStrategy; } public void setITransitionStrategy(ITransitionStrategy transition) { this.transition = transition; } public void setIMapVisualization(IMapVisualization mapVisualization) { this.mapVisualization = mapVisualization; } public void setModelVisualisation(ModularMovementModelViz modelVis) { modelVisualisation = modelVis; modelVisualisation.setMovementModel(this); } @Override public void eventOccurred(Object content, int type) { if (type == EVENT_INIT) { initialize(); } else if (type == EVENT_MOVE) { move(); } } /** * Only for visualization! * * @return */ public List getAttractionPoints() { return new Vector(transition.getAllAttractionPoints()); } }