package maps.validate;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import maps.gml.GMLDirectedEdge;
import maps.gml.GMLMap;
import maps.gml.GMLRoad;
import maps.gml.GMLShape;
import com.vividsolutions.jts.geom.Coordinate;
import com.vividsolutions.jts.geom.Geometry;
import com.vividsolutions.jts.geom.LineString;
import com.vividsolutions.jts.operation.linemerge.LineSequencer;
/**
* Validator to check if the shapes of the map are traversable.
*
* For all shaped we check if they can be entered via their entrances.
*
* For road we also check if each entrance can reach the other ones via this
* road.
*
*/
public class GMLTraversabilityValidator implements MapValidator<GMLMap> {
private static final double MIN_ROAD_WIDTH = 1.0;
private static final double SHAPE_PADDING = 0.01;
@Override
public Collection<ValidationError> validate(GMLMap map) {
Collection<ValidationError> errors = new ArrayList<ValidationError>();
for (GMLShape shape : map.getRoads()) {
ValidationError error = checkTraversability(shape, MIN_ROAD_WIDTH);
if (error != null) {
errors.add(error);
}
}
for (GMLShape shape : map.getBuildings()) {
ValidationError error = checkTraversability(shape, MIN_ROAD_WIDTH);
if (error != null) {
errors.add(error);
}
}
return errors;
}
/**
* Check if this shape can be traversed by an agent of width
* <tt>minWidth</tt>.
*
* @param shape
* @param agentWidth
* @return
*/
private ValidationError checkTraversability(GMLShape shape, double minWidth) {
// To check for traversability, we shrink the non-traversable edges
// of the shape by the radius of the agent.
// We then check, if all entrance edges are part of the same part
// of the resulting polygon
try {
Geometry polygon = JTSTools.shapeToPolygon(shape);
if (!polygon.isValid()) {
return new ValidationError(shape.getID(), "invalid shape");
}
Geometry boundary = impassableLines(shape);
Geometry buffer = boundary.buffer(((double) minWidth) / 2);
Geometry result = polygon.difference(buffer);
// make sure the intersection tests succeed
result = result.buffer(SHAPE_PADDING);
Coordinate centroid = JTSTools.pointToCoordinate(shape.getCentroid());
// Build list of adjacent entrance edges
List<GMLDirectedEdge> edges = shape.getEdges();
List<List<GMLDirectedEdge>> entrances = new ArrayList<List<GMLDirectedEdge>>();
List<GMLDirectedEdge> entrance = new ArrayList<GMLDirectedEdge>();
for (GMLDirectedEdge e : edges) {
if (shape.hasNeighbour(e)) {
entrance.add(e);
//Check if we have a line of sight to the centroid
LineString edge = JTSTools.edgeToLine(e);
Coordinate edgeCenter = edge.getCentroid().getCoordinate();
Coordinate[] coords = new Coordinate[]{centroid, edgeCenter};
LineString lineOfSight = JTSTools.getFactory().createLineString(coords);
if (lineOfSight.intersects(boundary)) {
String message = "Edge " + e.getEdge().getID()
+ " has no line of sight to shape center.";
return new ValidationError(shape.getID(), message);
}
}
else {
if (!entrance.isEmpty()) {
entrances.add(entrance);
}
entrance = new ArrayList<GMLDirectedEdge>();
}
}
if (!entrance.isEmpty()) {
// Merge first and last sequences if neccessary
if (shape.hasNeighbour(edges.get(0)) && !entrances.isEmpty()) {
entrances.get(0).addAll(entrance);
}
else {
entrances.add(entrance);
}
}
// Check in which part of the polygon the entrances lie
GMLDirectedEdge firstEdge = null;
int firstPolygon = -1;
for (List<GMLDirectedEdge> etr : entrances) {
int polyIndex = -1;
for (GMLDirectedEdge e : etr) {
polyIndex = findPolygonPartOfEdge(e, result);
if (polyIndex != -1) {
break;
}
}
if (polyIndex == -1) {
// Entrance edge no longer in polygon
String message = "Edge is too narrow to pass through.";
return new ValidationError(etr.get(0).getEdge().getID(),
message);
}
if (firstEdge == null) {
firstEdge = etr.get(0);
firstPolygon = polyIndex;
}
else if (firstPolygon != polyIndex
&& (shape instanceof GMLRoad)) {
// Only check traversability for roads
String message = "Can't reach edge "
+ firstEdge.getEdge().getID() + " from "
+ etr.get(0).getEdge().getID();
return new ValidationError(shape.getID(), message);
}
}
return null;
}
catch (ValidationException e) {
return e.getError();
}
}
/**
* Find the index of the subgeometry the given edge is part of. Return -1 if
* the edge is not contained in the geometry at all.
* @param edge
* @param geom
* @return
*/
private static int findPolygonPartOfEdge(GMLDirectedEdge edge, Geometry geom) {
for (int i = 0; i < geom.getNumGeometries(); i++) {
if (edgePartOfPolygon(edge, geom.getGeometryN(i))) {
return i;
}
}
return -1;
}
/**
* Checks if an edge is part (i.e intersects) of a given polygon.
* @param edge
* @param polygon
* @return
*/
private static boolean edgePartOfPolygon(GMLDirectedEdge edge,
Geometry polygon) {
// No idea if this works...
return polygon.intersects(JTSTools.edgeToLine(edge));
}
/**
* Return a LineString or MultiLineString of the impassable edges of a
* shape.
* @param shape
* @return
*/
private static Geometry impassableLines(GMLShape shape) {
LineSequencer seq = new LineSequencer();
for (GMLDirectedEdge e : shape.getEdges()) {
if (!shape.hasNeighbour(e)) {
Coordinate[] coord = new Coordinate[2];
coord[0] = JTSTools.nodeToCoordinate(e.getStartNode());
coord[1] = JTSTools.nodeToCoordinate(e.getEndNode());
seq.add(JTSTools.getFactory().createLineString(coord));
}
}
return seq.getSequencedLineStrings();
}
}