package iamrescue.routing;
import iamrescue.agent.AbstractIAMAgent;
import iamrescue.agent.ISimulationTimer;
import iamrescue.agent.ITimeStepListener;
import iamrescue.belief.IAMWorldModel;
import iamrescue.belief.entities.RoutingInfoBlockade;
import iamrescue.execution.command.IPath;
import iamrescue.routing.WorldModelConverter.SimpleGraphNode;
import iamrescue.routing.WorldModelConverter.WorldModelArea;
import iamrescue.routing.costs.IRoutingCostFunction;
import iamrescue.routing.dijkstra.PathSolution;
import iamrescue.routing.dijkstra.SimpleGraph;
import iamrescue.routing.queries.IRoutingLocation;
import iamrescue.routing.queries.IRoutingQuery;
import iamrescue.routing.queries.QueryFactory;
import iamrescue.routing.queries.RoutingLocation;
import iamrescue.routing.queries.RoutingQuery;
import iamrescue.util.PositionXY;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.Map.Entry;
import javolution.util.FastList;
import javolution.util.FastMap;
import javolution.util.FastSet;
import org.apache.log4j.Logger;
import rescuecore2.misc.Pair;
import rescuecore2.standard.entities.Area;
import rescuecore2.standard.entities.Blockade;
import rescuecore2.standard.entities.Building;
import rescuecore2.standard.entities.Human;
import rescuecore2.standard.entities.Road;
import rescuecore2.standard.entities.StandardEntity;
import rescuecore2.standard.entities.StandardEntityURN;
import rescuecore2.standard.entities.StandardPropertyURN;
import rescuecore2.worldmodel.Entity;
import rescuecore2.worldmodel.EntityID;
import rescuecore2.worldmodel.EntityListener;
import rescuecore2.worldmodel.Property;
import rescuecore2.worldmodel.WorldModel;
import rescuecore2.worldmodel.WorldModelListener;
public abstract class AbstractRoutingModule implements IRoutingModule,
EntityListener, WorldModelListener<StandardEntity>, ITimeStepListener {
private final Map<Integer, PositionXY> EMPTY_MAP = new FastMap<Integer, PositionXY>();
private static final int[] EMPTY_INT_ARRAY = new int[0];
protected IAMWorldModel worldModel;
protected IRoutingCostFunction routingCostFunction;
protected WorldModelConverter converter;
protected SimpleGraph graph;
private Set<EntityID> changedEntities = new FastSet<EntityID>();
private static final Logger LOGGER = Logger
.getLogger(AbstractRoutingModule.class);
public AbstractRoutingModule(IAMWorldModel worldModel,
IRoutingCostFunction roadCostFunction, ISimulationTimer timer) {
timer.addTimeStepListener(this);
this.worldModel = worldModel;
this.routingCostFunction = roadCostFunction;
// Listen to all entities for changes
for (StandardEntity se : worldModel.getEntitiesOfType(
StandardEntityURN.BUILDING, StandardEntityURN.ROAD,
StandardEntityURN.FIRE_STATION,
StandardEntityURN.AMBULANCE_CENTRE,
StandardEntityURN.POLICE_OFFICE, StandardEntityURN.REFUGE,
StandardEntityURN.BLOCKADE)) {
se.addEntityListener(this);
}
worldModel.addWorldModelListener(this);
createGraph();
// System.out.println("Size of graph is " + graph.getNodes().size());
}
/*
* public AbstractRoutingModule(IAMWorldModel worldModel,
* IRoutingCostFunction roadCostFunction, WorldModelConverter converter,
* SimpleGraph graph, ISimulationTimer timer) {
*
* timer.addTimeStepListener(this); this.worldModel = worldModel;
* this.routingCostFunction = roadCostFunction;
*
* // Listen to all entities for changes for (StandardEntity se :
* worldModel.getEntitiesOfType( StandardEntityURN.BUILDING,
* StandardEntityURN.ROAD, StandardEntityURN.FIRE_STATION,
* StandardEntityURN.AMBULANCE_CENTRE, StandardEntityURN.POLICE_OFFICE,
* StandardEntityURN.REFUGE, StandardEntityURN.BLOCKADE)) {
* se.addEntityListener(this); }
*
* worldModel.addWorldModelListener(this);
*
* if (converter != null && graph != null) { this.converter = converter;
* this.graph = graph; } else { createGraph(); } }
*/
/**
* @return the worldModel
*/
public IAMWorldModel getWorldModel() {
return worldModel;
}
@Override
public void entityAdded(WorldModel<? extends StandardEntity> model,
StandardEntity e) {
if (e instanceof Blockade) {
e.addEntityListener(this);
}
}
@Override
public void entityRemoved(WorldModel<? extends StandardEntity> model,
StandardEntity e) {
if (e instanceof Blockade) {
e.removeEntityListener(this);
}
}
private void createGraph() {
this.converter = new WorldModelConverter(worldModel,
routingCostFunction);
this.graph = converter.getGraph();
}
/**
* @return the graph
*/
public SimpleGraph getGraph() {
return graph;
}
/**
* @return the converter
*/
public WorldModelConverter getConverter() {
return converter;
}
/*
* (non-Javadoc)
*
* @see routing.IRoutingModule#areConnected(rescuecore2.worldmodel.EntityID,
* rescuecore2.worldmodel.EntityID)
*/
@Override
public boolean areConnected(EntityID from, EntityID to) {
Entity fromEntity = worldModel.getEntity(from);
Entity toEntity = worldModel.getEntity(to);
while (fromEntity instanceof Human) {
fromEntity = worldModel.getEntity(((Human) fromEntity)
.getPosition());
}
while (fromEntity instanceof Blockade) {
fromEntity = worldModel.getEntity(((Blockade) fromEntity)
.getPosition());
}
while (toEntity instanceof Human) {
toEntity = worldModel.getEntity(((Human) toEntity).getPosition());
}
while (toEntity instanceof Blockade) {
toEntity = worldModel
.getEntity(((Blockade) toEntity).getPosition());
}
return converter.onSameComponent(fromEntity.getID().getValue(),
toEntity.getID().getValue());
}
@Override
public IPath findShortestPath(IRoutingQuery query) {
long start = System.nanoTime();
if (LOGGER.isTraceEnabled()) {
LOGGER.trace("Running routing query: " + query);
}
EntityID from = query.getStartLocation().getID();
Set<EntityID> possibleStartPositions = FastSet.newInstance();
possibleStartPositions.add(from);
StandardEntity position = worldModel.getEntity(from);
while (position instanceof Human) {
Human human = (Human) position;
if (!human.isPositionDefined()) {
throw new NullPointerException("Start location " + human
+ " does not have its position defined.");
}
EntityID positionFrom = human.getPosition();
possibleStartPositions.add(positionFrom);
position = worldModel.getEntity(positionFrom);
}
while (position instanceof Blockade) {
Blockade block = (Blockade) position;
if (!block.isPositionDefined()) {
throw new NullPointerException("Start location " + block
+ " does not have its position defined.");
}
EntityID positionFrom = block.getPosition();
possibleStartPositions.add(positionFrom);
position = worldModel.getEntity(positionFrom);
}
List<Integer> nodes = new ArrayList<Integer>();
List<Double> costs = new ArrayList<Double>();
Map<Integer, Double> nodeCosts = FastMap.newInstance();
Map<Integer, IRoutingLocation> targetMap = FastMap.newInstance();
for (IRoutingLocation point : query.getDestinationLocations()) {
boolean done = false;
EntityID id = point.getID();
do {
if (possibleStartPositions.contains(id)) {
// Already at destination!
List<PositionXY> positions = new ArrayList<PositionXY>();
positions.add(findRepresentativePoint(query
.getStartLocation()));
positions.add(findRepresentativePoint(point));
return new Path(Collections.singletonList(getArea(point)),
positions);
}
StandardEntity entity = worldModel.getEntity(id);
if (entity instanceof Human) {
Human h = (Human) entity;
if (!h.isPositionDefined()) {
throw new NullPointerException("Human " + h
+ " does not have its position defined.");
}
id = h.getPosition();
} else if (entity instanceof Blockade) {
Blockade b = (Blockade) entity;
if (!b.isPositionDefined()) {
throw new NullPointerException("Blockade " + b
+ " does not have its position defined.");
}
id = b.getPosition();
} else {
done = true;
}
} while (!done);
Pair<List<Integer>, List<Double>> costsForThis = computeSearchNodes(point);
List<Integer> theseNodes = costsForThis.first();
List<Double> theseCosts = costsForThis.second();
for (int i = 0; i < theseNodes.size(); i++) {
int node = theseNodes.get(i);
double cost = theseCosts.get(i);
Double existingCost = nodeCosts.get(node);
if (existingCost == null || existingCost > cost) {
nodeCosts.put(node, cost);
targetMap.put(node, point);
}
}
}
for (Entry<Integer, Double> target : nodeCosts.entrySet()) {
nodes.add(target.getKey());
costs.add(target.getValue());
}
IPath path = findShortestPath(query.getStartLocation(), nodes, costs,
targetMap);
/*
* if (!nodes.contains(path.getLocations().get(
* path.getLocations().size() - 1).getValue())) {
* System.out.println("oops"); path =
* findShortestPath(query.getStartLocation(), nodes, costs, targetMap);
* }
*/
if (LOGGER.isTraceEnabled()) {
LOGGER.trace("Completed query in " + (System.nanoTime() - start)
+ "ns");
LOGGER.trace("Path: " + path);
}
return path;
}
/*
* (non-Javadoc)
*
* @see newrouting.IRoutingModule#findShortestPath(java.util.List)
*/
@Override
public List<IPath> findShortestPath(List<IRoutingQuery> queries) {
List<IPath> solutions = new ArrayList<IPath>(queries.size());
for (IRoutingQuery query : queries) {
solutions.add(findShortestPath(query));
}
return solutions;
}
/*
* (non-Javadoc)
*
* @see
* newrouting.IRoutingModule#findShortestPath(rescuecore2.worldmodel.EntityID
* , java.util.Collection)
*/
@Override
public IPath findShortestPath(EntityID from,
Collection<EntityID> possibleDestinations) {
return findShortestPath(QueryFactory.createQuery(from,
possibleDestinations));
}
/*
* (non-Javadoc)
*
* @see
* newrouting.IRoutingModule#findShortestPath(rescuecore2.worldmodel.EntityID
* , util.PositionXY, java.util.Collection)
*/
@Override
public IPath findShortestPath(EntityID from, PositionXY exactPosition,
Collection<EntityID> possibleDestinations) {
return findShortestPath(QueryFactory.createQuery(from, exactPosition,
possibleDestinations));
}
private EntityID getArea(IRoutingLocation position) {
StandardEntity entity = worldModel.getEntity(position.getID());
while (entity instanceof Human) {
entity = ((Human) entity).getPosition(worldModel);
}
while (entity instanceof Blockade) {
entity = worldModel.getEntity(((Blockade) entity).getPosition());
}
return entity.getID();
}
public IPath findShortestPath(EntityID from, EntityID destination) {
return findShortestPath(QueryFactory.createQuery(from, destination));
}
public IPath findShortestPath(Entity from, Entity destination) {
return findShortestPath(QueryFactory.createQuery(from, destination));
}
public IPath findShortestPath(Entity from,
Collection<? extends Entity> destinations) {
return findShortestPath(QueryFactory.createQuery(from, destinations));
}
/*
* (non-Javadoc)
*
* @see
* newrouting.IRoutingModule#findShortestPath(rescuecore2.worldmodel.EntityID
* , util.PositionXY, rescuecore2.worldmodel.EntityID, util.PositionXY)
*/
@Override
public IPath findShortestPath(EntityID from, PositionXY fromPosition,
EntityID destination, PositionXY destinationPosition) {
return findShortestPath(new RoutingQuery(new RoutingLocation(from,
fromPosition), new RoutingLocation(destination,
destinationPosition)));
}
/**
* Finds search nodes related to a point.
*
* @param point
* The point
* @return Pair of routing nodes and respective costs.
*/
protected Pair<List<Integer>, List<Double>> computeSearchNodes(
IRoutingLocation location) {
List<Integer> nodes = new ArrayList<Integer>();
List<Double> costs = new ArrayList<Double>();
StandardEntity entity = worldModel.getEntity(location.getID());
PositionXY position = (location.hasPositionDefined()) ? location
.getPositionXY() : null;
if (entity instanceof Human) {
do {
Human human = (Human) entity;
if (position == null) {
position = new PositionXY(human.getLocation(worldModel));
}
entity = worldModel.getEntity(human.getPosition());
} while (entity instanceof Human);
} else if (entity instanceof Blockade) {
Blockade blockade = (Blockade) entity;
if (position == null) {
position = new PositionXY(blockade.getLocation(worldModel));
}
entity = worldModel.getEntity(blockade.getPosition());
}
WorldModelArea wmArea = converter.getWorldModelArea(entity.getID()
.getValue());
// Get all nodes
Set<Integer> simpleNodes = wmArea.getSimpleNeighbours();
for (int neighbour : simpleNodes) {
double cost = 0;
if (position != null) {
cost = routingCostFunction.getTravelCost((Area) entity,
position, converter.getSimpleGraphNode(neighbour)
.getRepresentativePoint());
}
nodes.add(neighbour);
costs.add(cost);
}
return new Pair<List<Integer>, List<Double>>(nodes, costs);
}
private IPath findShortestPath(IRoutingLocation from,
List<Integer> simpleTargets, List<Double> simpleCosts,
Map<Integer, IRoutingLocation> finalTargets) {
AbstractIAMAgent.stopIfInterrupted();
IRoutingAlgorithm algorithm = obtainSolver(from);
PathSolution solution = algorithm.getShortestPath(simpleTargets,
simpleCosts);
// Additionally check if direct move is possible.
/*
* EntityID id = from.getID();
*
* converter.get
*
* PositionXY fromPosition; if (from.hasPositionDefined()) {
* fromPosition = from.getPositionXY(); } else { fromPosition = new
* PositionXY(worldModel.getEntity(id).getLocation( worldModel)); }
*
* for (Entry<Integer, IRoutingLocation> finalTarget : finalTargets
* .entrySet()) {
*
* }
*/
if (solution.getCost() == Double.POSITIVE_INFINITY) {
return Path.INVALID_PATH;
} else {
Path path = createPath(from, solution, finalTargets);
path.setCost(solution.getCost());
return path;
}
}
private PositionXY findRepresentativePoint(IRoutingLocation location) {
if (location.hasPositionDefined()) {
return location.getPositionXY();
} else {
return new PositionXY(worldModel.getEntity(location.getID())
.getLocation(worldModel));
}
}
/**
* @param solution
* @param finalTargets
* @return
*/
private Path createPath(IRoutingLocation from, PathSolution solution,
Map<Integer, IRoutingLocation> finalTargets) {
// ids : simple node IDs
int[] ids = solution.getPathIDs();
if (ids.length == 0) {
// Empty!
return new Path(new ArrayList<EntityID>(),
new ArrayList<PositionXY>());
}
// World IDs
List<EntityID> entities = new ArrayList<EntityID>();
List<PositionXY> xyPositions = new FastList<PositionXY>();
EntityID lastID = getArea(from);
entities.add(lastID);
SimpleGraphNode node = converter.getSimpleGraphNode(ids[0]);
// if (!from.equals(node.getRepresentativePoint())) {
// }
// Note that first two positions could be the same if starting exactly
// on edge.
// This is required to ensure consistent behaviour in
// ABstractRoutingCOstFunction.
xyPositions.add(findRepresentativePoint(from));
xyPositions.add(node.getRepresentativePoint());
int thisOne = node.getOtherNeighbour(lastID.getValue());
entities.add(new EntityID(thisOne));
int lastEntity = thisOne;
// String debug = converter.getSimpleGraphNode(ids[0]) + "_";
for (int i = 1; i < ids.length; i++) {
// debug += converter.getSimpleGraphNode(ids[i]) + "_";
SimpleGraphNode next = converter.getSimpleGraphNode(ids[i]);
int neighbour1 = next.getAreaNeighbour1();
int neighbour2 = next.getAreaNeighbour2();
int counter = 0;
boolean repeat;
do {
repeat = false;
if (lastEntity == neighbour1) {
if (lastEntity == neighbour2) {
// Ignore this - we're traversing an interior routing
// node.
break;
} else {
lastEntity = neighbour2;
entities.add(new EntityID(lastEntity));
xyPositions.add(next.getRepresentativePoint());
}
} else if (lastEntity == neighbour2) {
lastEntity = neighbour1;
entities.add(new EntityID(lastEntity));
xyPositions.add(next.getRepresentativePoint());
} else {
// Border case - this happens when moving between edge nodes
// repeatedly within the same shape
counter++;
// Need to go back 2 nodes in this case.
if (entities.size() <= 1) {
LOGGER.error("Warning: entities is too small. From:"
+ from + ", solution:"
+ Arrays.toString(solution.getPathIDs())
+ ", entities:" + entities + ", next:" + next);
return Path.INVALID_PATH;
}
entities.remove(entities.size() - 1);
xyPositions.remove(xyPositions.size() - 1);
lastEntity = entities.get(entities.size() - 1).getValue();
if (LOGGER.isTraceEnabled()) {
LOGGER.trace("Retracing path due to interior routing.");
}
repeat = true;
}
} while (counter == 1 && repeat);
if (counter == 2) {
LOGGER.error("Something bad happened. "
+ "Counter should not be > 1.");
}
// lastEntity = next.getOtherNeighbour(lastEntity);
}
IRoutingLocation last = finalTargets.get(lastEntity);
if (last != null) {
xyPositions.add(findRepresentativePoint(last));
} else {
// By default, route to centre of next entity
xyPositions.add(new PositionXY(worldModel.getEntity(
new EntityID(lastEntity)).getLocation(worldModel)));
}
return new Path(entities, xyPositions);
}
/**
* This method is called to get a solver (might be from cache).
*
* @param from
* starting point.
* @return
*/
protected abstract IRoutingAlgorithm obtainSolver(IRoutingLocation from);
/**
* This one creates a *new* solver.
*
* @param graph
* @param sources
* @param costs
* @return
*/
// protected abstract IRoutingAlgorithm createSolver(SimpleGraph graph,
// List<Integer> sources, List<Double> costs);
/*
* (non-Javadoc)
*
* @see routing.IRoutingModule#getRoadCostFunction()
*/
@Override
public IRoutingCostFunction getRoutingCostFunction() {
return routingCostFunction;
}
/*
* (non-Javadoc)
*
* @see iamrescue.agent.ITimeStepListener#notifyTimeStepStarted(int)
*/
@Override
public void notifyTimeStepStarted(int timeStep) {
Set<Area> changed = new FastSet<Area>();
for (EntityID id : changedEntities) {
Entity e = worldModel.getEntity(id);
if (e instanceof Area) {
changed.add((Area) e);
} else if (e instanceof Blockade) {
if (((Blockade) e).isPositionDefined()) {
// Get previous known position
EntityID position = ((Blockade) e).getPosition();
Area area = (Area) worldModel.getEntity(position);
/*
* if (area == null) { System.out.println("Warning: " +
* ((StandardEntity) e).getFullDescription()); }
*/
changed.add(area);
}
}
}
boolean oneChanged = false;
for (Area area : changed) {
boolean thisChanged = converter.recomputeArea(area);
if (thisChanged && !oneChanged) {
oneChanged = true;
}
}
if (oneChanged) {
graphChanged();
}
}
/*
* (non-Javadoc)
*
* @see
* rescuecore2.worldmodel.EntityListener#propertyChanged(rescuecore2.worldmodel
* .Entity, rescuecore2.worldmodel.Property)
*/
public void propertyChanged(Entity e, Property p, Object oldValue,
Object newValue) {
// System.out.println(e);
// changedEntities.add(e.getID());
if (e instanceof Road) {
if (p.getURN().equals(StandardPropertyURN.BLOCKADES.toString())) {
boolean added = false;
if (newValue instanceof List) {
// If empty list, always add (bug? - undefined is initially
// [])
if (((List) newValue).size() == 0) {
changedEntities.add(e.getID());
added = true;
}
}
if (!added
&& !IAMWorldModel.checkIfPropertyValuesEqual(oldValue,
newValue)) {
changedEntities.add(e.getID());
}
}
} else if (e instanceof Blockade) {
if (p.getURN().equals(StandardPropertyURN.APEXES.toString())) {
// System.out.println(p + ":" + oldValue + " -> " + newValue);
if (!IAMWorldModel.checkIfPropertyValuesEqual(oldValue,
newValue)) {
// System.out.println("Different!");
changedEntities.add(e.getID());
}
} else if (p.getURN().equals(RoutingInfoBlockade.BLOCK_INFO_URN)) {
if (!IAMWorldModel.checkIfPropertyValuesEqual(oldValue,
newValue)) {
changedEntities.add(e.getID());
// Also remember that we need to check communicated info
Blockade b = (Blockade) e;
// if (p.isDefined()) {
// LOGGER.warn(b.getFullDescription() + ", " + p + ", "
// + oldValue + ", " + newValue);
// worldModel.getBlockCache().setUseCommunicatedInfo(b,
// true);
// }
}
}
} else if (e instanceof Building) {
if (p.getURN().equals(StandardPropertyURN.FIERYNESS.toString())) {
// System.out.println(p + ":" + oldValue + " -> " + newValue);
if (!IAMWorldModel.checkIfPropertyValuesEqual(oldValue,
newValue)) {
// System.out.println("Different!");
changedEntities.add(e.getID());
}
}
}
}
public void forceRecompute(Area area) {
converter.recomputeArea(area);
graphChanged();
}
protected abstract void graphChanged();
@Override
public SimpleGraph getRoutingGraph() {
return graph;
}
}