/**
* File : SpatialIndex.java
* Created on : 16 Apr 2008
*/
package iamrescue.belief.spatial;
import iamrescue.agent.SimulationTimer;
import iamrescue.belief.IAMWorldModel;
import iamrescue.util.SpatialUtils;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import javolution.util.FastMap;
import javolution.util.FastSet;
import org.apache.log4j.Logger;
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.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;
import com.infomatiq.jsi.IntProcedure;
import com.infomatiq.jsi.Rectangle;
import com.infomatiq.jsi.rtree.RTree;
import com.vividsolutions.jts.geom.Envelope;
import com.vividsolutions.jts.geom.Geometry;
import com.vividsolutions.jts.geom.LineString;
import com.vividsolutions.jts.geom.Point;
import com.vividsolutions.jts.geom.Polygon;
/**
* @author ss2
*
*/
public class SpatialIndex implements ISpatialIndex, EntityListener,
WorldModelListener<StandardEntity> {
/**
* Currently uses three R-trees for different types of objects
*/
private RTree movingTree;
private RTree roadsAndNodesTree;
private RTree blockadeTree;
private RTree buildingTree;
// private final static GeometryFactory factory = new GeometryFactory();
private Map<EntityID, Envelope> insertedEnvelopes;
private IAMWorldModel worldModel = null;
private Map<EntityID, Geometry> geometries;
private Map<EntityID, Integer> lastChanged = new FastMap<EntityID, Integer>();
private static final Logger LOGGER = Logger.getLogger(SpatialIndex.class
.getCanonicalName());
public SpatialIndex(IAMWorldModel worldModel) {
this.worldModel = worldModel;
movingTree = new RTree();
roadsAndNodesTree = new RTree();
buildingTree = new RTree();
blockadeTree = new RTree();
Properties defaultProperties = new Properties();
defaultProperties.setProperty("MaxNodeEntries", "10");
defaultProperties.setProperty("MinNodeEntries", "2");
movingTree.init(defaultProperties);
roadsAndNodesTree.init(defaultProperties);
buildingTree.init(defaultProperties);
blockadeTree.init(defaultProperties);
insertedEnvelopes = new FastMap<EntityID, Envelope>();
geometries = new FastMap<EntityID, Geometry>();
Collection<StandardEntity> allObjects = worldModel.getAllEntities();
for (StandardEntity object : allObjects) {
addObject(object);
if (object instanceof Area) {
geometries.put(object.getID(), SpatialUtils.createGeometry(
object, worldModel));
}
}
worldModel.addWorldModelListener(this);
}
private void addObject(StandardEntity object) {
if (SpatialUtils.isSpatialObject(object)) {
Envelope env = SpatialUtils.createBoundingEnvelope(object,
worldModel);
if (env != null) {
selectQuadtree(object).add(convert(env),
object.getID().getValue());
assert !insertedEnvelopes.containsKey(object.getID());
insertedEnvelopes.put(object.getID(), env);
}
// Also register, even if position is null, so that updates are
// received.
object.addEntityListener(this);
/*
* Collection<RescueObjectProperty> properties = object
* .getSupportedProperties(); for (RescueObjectProperty prop :
* spatialProperties) { if (properties.contains(prop)) { try {
*
* } catch (PropertyNotSupportedException e) {
* log.error("This exception should not have occurred", e); } } }
*/
}
}
private Rectangle convert(Envelope env) {
return new Rectangle((float) env.getMinX(), (float) env.getMinY(),
(float) env.getMaxX(), (float) env.getMaxY());
}
private void removeObject(StandardEntity object) {
if (SpatialUtils.isSpatialObject(object)) {
Envelope env = insertedEnvelopes.remove(object.getID());
geometries.remove(object.getID());
if (env != null) {
selectQuadtree(object).delete(convert(env),
object.getID().getValue());
}
// Also remove listener(s)!
object.removeEntityListener(this);
lastChanged.remove(object.getID());
}
}
private RTree selectQuadtree(StandardEntity object) {
if (object instanceof Road) {
return roadsAndNodesTree;
} else if (object instanceof Building) {
return buildingTree;
} else if (object instanceof Human) {
return movingTree;
} else if (object instanceof Blockade) {
return blockadeTree;
} else {
LOGGER.error("Cannot find quadtree for object " + object);
return null;
}
}
public Collection<StandardEntity> query(SpatialQuery query) {
long startTime = 0;
if (LOGGER.isTraceEnabled()) {
LOGGER.trace("Starting query " + query);
startTime = System.nanoTime();
}
Geometry geometry = query.getGeometry();
Geometry envelope = geometry.getEnvelope();
int distance = query.getDistance();
Envelope rectangle;
if (envelope instanceof Polygon) {
Polygon polyRectangle = (Polygon) envelope;
rectangle = new Envelope(polyRectangle.getCoordinates()[0].x
- distance, polyRectangle.getCoordinates()[1].x + distance,
polyRectangle.getCoordinates()[1].y - distance,
polyRectangle.getCoordinates()[2].y + distance);
} else if (envelope instanceof Point) {
Point pointRectangle = (Point) envelope;
if (pointRectangle.isEmpty()) {
throw new IllegalArgumentException("Invalid shape: " + geometry
+ ". Must represent either a point or an area.");
}
rectangle = new Envelope(pointRectangle.getX() - distance,
pointRectangle.getX() + distance, pointRectangle.getY()
- distance, pointRectangle.getY() + distance);
} else if (envelope instanceof LineString) {
LineString line = (LineString) envelope;
double minX = line.getStartPoint().getX();
double maxX = line.getStartPoint().getX();
double minY = line.getStartPoint().getY();
double maxY = line.getStartPoint().getY();
for (int i = 1; i < line.getNumPoints(); i++) {
Point p = line.getPointN(i);
if (p.getX() < minX) {
minX = p.getX();
}
if (p.getX() > maxX) {
maxX = p.getX();
}
if (p.getY() < minY) {
minY = p.getY();
}
if (p.getY() > maxY) {
maxY = p.getY();
}
}
rectangle = new Envelope(minX - distance, maxX + distance, minY
- distance, maxY + distance);
} else {
throw new IllegalArgumentException("Invalid shape: " + geometry
+ ". Must represent either a point or an area.");
}
ArrayList<StandardEntity> list = new ArrayList<StandardEntity>();
// Now query all relevant trees
Class<? extends StandardEntity> queryClass = query.getQueryClass();
SpatialObjectVisitor visitor = new SpatialObjectVisitor(query, list,
geometries, worldModel);
MyIntProcedure prc = new MyIntProcedure(visitor, worldModel);
Rectangle r = convert(rectangle);
if (queryClass.isAssignableFrom(Road.class)
|| Road.class.isAssignableFrom(queryClass)) {
roadsAndNodesTree.intersects(r, prc);
}
if (queryClass.isAssignableFrom(Human.class)
|| Human.class.isAssignableFrom(queryClass)) {
movingTree.intersects(r, prc);
}
if (queryClass.isAssignableFrom(Building.class)
|| Building.class.isAssignableFrom(queryClass)) {
buildingTree.intersects(r, prc);
}
if (queryClass.isAssignableFrom(Blockade.class)
|| Blockade.class.isAssignableFrom(queryClass)) {
blockadeTree.intersects(r, prc);
}
if (LOGGER.isTraceEnabled()) {
long time = System.nanoTime() - startTime;
LOGGER.trace("Completed query in " + time + "ns : " + list);
}
return list;
}
private static class MyIntProcedure implements IntProcedure {
private SpatialObjectVisitor visitor;
private IAMWorldModel worldModel;
public MyIntProcedure(SpatialObjectVisitor visitor,
IAMWorldModel worldModel) {
this.visitor = visitor;
this.worldModel = worldModel;
}
/*
* (non-Javadoc)
*
* @see com.infomatiq.jsi.IntProcedure#execute(int)
*/
public boolean execute(int id) {
visitor.visitItem(worldModel.getEntity(new EntityID(id)));
return true;
}
}
public void propertyChanged(Entity e, Property p, Object oldValue,
Object newValue) {
if (p.getURN().equals(StandardPropertyURN.POSITION.toString())
|| p.getURN().equals(StandardPropertyURN.APEXES.toString())
|| p.getURN().equals(StandardPropertyURN.EDGES.toString())
|| p.getURN().equals(StandardPropertyURN.X.toString())
|| p.getURN().equals(StandardPropertyURN.Y.toString())) {
StandardEntity se = (StandardEntity) e;
removeObject(se);
addObject(se);
}
}
public void entityAdded(WorldModel<? extends StandardEntity> model,
StandardEntity e) {
addObject(e);
}
public void entityRemoved(WorldModel<? extends StandardEntity> model,
StandardEntity e) {
if (LOGGER.isTraceEnabled()) {
LOGGER.debug("Removing " + e);
LOGGER.debug(blockadeTree.size() + " blockades before.");
}
removeObject(e);
if (LOGGER.isTraceEnabled()) {
LOGGER.debug(blockadeTree.size() + " blockades after.");
}
}
}