package maps.convert.legacy2gml;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import maps.gml.GMLMap;
import maps.gml.GMLNode;
import maps.gml.GMLRoad;
import maps.legacy.LegacyBuilding;
import maps.legacy.LegacyMap;
import maps.legacy.LegacyNode;
import maps.legacy.LegacyObject;
import maps.legacy.LegacyRoad;
import rescuecore2.misc.geometry.GeometryTools2D;
import rescuecore2.misc.geometry.Line2D;
import rescuecore2.misc.geometry.Point2D;
import rescuecore2.misc.geometry.Vector2D;
/**
Container for node information during conversion.
*/
public class NodeInfo {
private LegacyNode node;
private Point2D centre;
private List<GMLNode> apexes;
private GMLRoad road;
/**
Construct a NodeInfo object.
@param node The LegacyNode to store info about.
*/
public NodeInfo(LegacyNode node) {
this.node = node;
centre = new Point2D(node.getX(), node.getY());
}
/**
Get the LegacyNode.
@return The LegacyNode.
*/
public LegacyNode getNode() {
return node;
}
/**
Get the node location.
@return The node location.
*/
public Point2D getLocation() {
return centre;
}
/**
Get the generated GMLRoad.
@return The generated road or null if this node did not generate a road segment.
*/
public GMLRoad getRoad() {
return road;
}
/**
Process the node and create GMLRoad objects if required.
@param legacy The legacy map.
@param gml The GML map.
@param roadInfo A map from road ID to RoadInfo.
@param buildingInfo A map from building ID to BuildingInfo.
*/
public void process(LegacyMap legacy, GMLMap gml, Map<Integer, RoadInfo> roadInfo, Map<Integer, BuildingInfo> buildingInfo) {
apexes = new ArrayList<GMLNode>();
List<EdgeAspect> edges = new ArrayList<EdgeAspect>();
for (int id : node.getEdges()) {
LegacyRoad lRoad = legacy.getRoad(id);
if (lRoad != null && roadInfo.containsKey(id)) {
edges.add(new RoadAspect(lRoad, node, legacy, roadInfo.get(id)));
continue;
}
LegacyBuilding lBuilding = legacy.getBuilding(id);
if (lBuilding != null && buildingInfo.containsKey(id)) {
edges.add(new BuildingAspect(lBuilding, node, legacy, buildingInfo.get(id)));
}
}
if (edges.size() == 1) {
EdgeAspect aspect = edges.get(0);
findRoadEdges(aspect, centre);
}
else {
// Sort the roads
CounterClockwiseSort sort = new CounterClockwiseSort(centre);
Collections.sort(edges, sort);
// Now build the apex list
Iterator<EdgeAspect> it = edges.iterator();
EdgeAspect first = it.next();
EdgeAspect prev = first;
EdgeAspect next;
while (it.hasNext()) {
next = it.next();
Point2D[] newApexes = findIncomingEdgeIntersection(prev, next, centre);
for (Point2D apex : newApexes) {
apexes.add(gml.createNode(apex.getX(), apex.getY()));
}
prev = next;
}
Point2D[] newApexes = findIncomingEdgeIntersection(prev, first, centre);
for (Point2D apex : newApexes) {
apexes.add(gml.createNode(apex.getX(), apex.getY()));
}
}
if (apexes.size() > 2) {
road = gml.createRoadFromNodes(apexes);
}
}
/**
Process two incoming roads and find the intersection of the right edge of the first road and the left edge of the second road.
@param first The road endpoint to check the right edge of.
@param second The road endpoint to check the left edge of.
@param centrePoint The centre of the intersection.
@return The intersection of the two roads.
*/
private Point2D[] findIncomingEdgeIntersection(EdgeAspect first, EdgeAspect second, Point2D centrePoint) {
LegacyObject firstNode = first.getFarNode();
LegacyObject secondNode = second.getFarNode();
Point2D firstPoint = new Point2D(firstNode.getX(), firstNode.getY());
Point2D secondPoint = new Point2D(secondNode.getX(), secondNode.getY());
// Find the intersection of the incoming road edges
Vector2D firstVector = centrePoint.minus(firstPoint);
Vector2D secondVector = centrePoint.minus(secondPoint);
Vector2D firstNormal = firstVector.getNormal().normalised().scale(-first.getRoadWidth() / 2.0);
Vector2D secondNormal = secondVector.getNormal().normalised().scale(second.getRoadWidth() / 2.0);
Point2D start1Point = firstPoint.plus(firstNormal);
Point2D start2Point = secondPoint.plus(secondNormal);
Line2D line1 = new Line2D(start1Point, firstVector);
Line2D line2 = new Line2D(start2Point, secondVector);
Point2D intersection = GeometryTools2D.getIntersectionPoint(line1, line2);
if (intersection == null) {
// Lines are parallel
// This means the normals are parallel, so we can just add a normal to the centre point to generate an intersection point
intersection = centrePoint.plus(firstNormal);
}
double maxWidth = Math.max(first.getRoadWidth(), second.getRoadWidth());
double distance = GeometryTools2D.getDistance(centrePoint, intersection);
Vector2D intersectionVector = intersection.minus(centrePoint).normalised();
double dp1 = firstVector.normalised().dot(intersectionVector);
double dp2 = secondVector.normalised().dot(intersectionVector);
if (distance > maxWidth && dp1 > 0 && dp2 > 0) {
//Cap spikes on acute angles
Vector2D cutoffVector = intersectionVector.getNormal().scale(maxWidth);
Point2D cutoffStart = centrePoint.plus(intersectionVector.scale(maxWidth)).plus(cutoffVector);
// CHECKSTYLE:OFF:MagicNumber
Line2D cutoffLine = new Line2D(cutoffStart, cutoffVector.scale(-2.0));
// CHECKSTYLE:ON:MagicNumber
Point2D end1 = GeometryTools2D.getIntersectionPoint(line1, cutoffLine);
Point2D end2 = GeometryTools2D.getIntersectionPoint(line2, cutoffLine);
first.setRightEnd(end1);
second.setLeftEnd(end2);
return new Point2D[] {end1, end2};
}
else if (distance > maxWidth && (dp1 > 0 || dp2 > 0)) {
// Prevent too distant intersections on obtuse angles
// Those usually happen on intersections between roads with (very) different widths.
// For now, just use the intersection that would have occured between two roads
// of the averages width
double avgWidth = (first.getRoadWidth() + second.getRoadWidth()) / 2.0;
firstNormal = firstVector.getNormal().normalised().scale(-avgWidth / 2.0);
secondNormal = secondVector.getNormal().normalised().scale(avgWidth / 2.0);
start1Point = firstPoint.plus(firstNormal);
start2Point = secondPoint.plus(secondNormal);
line1 = new Line2D(start1Point, firstVector);
line2 = new Line2D(start2Point, secondVector);
intersection = GeometryTools2D.getIntersectionPoint(line1, line2);
first.setRightEnd(intersection);
second.setLeftEnd(intersection);
}
else if (distance > maxWidth) {
// Inner acute angle: just cut off at twice the max. road width.
intersection = centrePoint.plus(intersectionVector.scale(maxWidth * 2.0));
first.setRightEnd(intersection);
second.setLeftEnd(intersection);
}
else {
first.setRightEnd(intersection);
second.setLeftEnd(intersection);
}
return new Point2D[] {intersection};
}
private void findRoadEdges(EdgeAspect aspect, Point2D centrePoint) {
LegacyObject farNode = aspect.getFarNode();
Point2D roadPoint = new Point2D(farNode.getX(), farNode.getY());
Vector2D vector = centrePoint.minus(roadPoint);
Vector2D leftNormal = vector.getNormal().normalised().scale(aspect.getRoadWidth() / 2.0);
Vector2D rightNormal = leftNormal.scale(-1);
Point2D left = centrePoint.plus(leftNormal);
Point2D right = centrePoint.plus(rightNormal);
aspect.setLeftEnd(left);
aspect.setRightEnd(right);
}
private interface EdgeAspect {
int getRoadWidth();
LegacyObject getFarNode();
void setLeftEnd(Point2D p);
void setRightEnd(Point2D p);
}
private static class RoadAspect implements EdgeAspect {
private boolean forward;
private LegacyNode farNode;
private RoadInfo info;
private int width;
RoadAspect(LegacyRoad road, LegacyNode intersection, LegacyMap map, RoadInfo info) {
forward = intersection.getID() == road.getTail();
farNode = map.getNode(forward ? road.getHead() : road.getTail());
width = road.getWidth();
this.info = info;
}
public int getRoadWidth() {
return width;
}
public LegacyObject getFarNode() {
return farNode;
}
public void setLeftEnd(Point2D p) {
if (forward) {
info.setHeadLeft(p);
}
else {
info.setTailRight(p);
}
}
public void setRightEnd(Point2D p) {
if (forward) {
info.setHeadRight(p);
}
else {
info.setTailLeft(p);
}
}
}
private static class BuildingAspect implements EdgeAspect {
private LegacyBuilding building;
private BuildingInfo info;
BuildingAspect(LegacyBuilding building, LegacyNode intersection, LegacyMap map, BuildingInfo info) {
this.building = building;
this.info = info;
}
public int getRoadWidth() {
return BuildingInfo.ENTRANCE_SIZE;
}
public LegacyObject getFarNode() {
return building;
}
public void setLeftEnd(Point2D p) {
info.setRoadLeft(p);
}
public void setRightEnd(Point2D p) {
info.setRoadRight(p);
}
}
private static class CounterClockwiseSort implements Comparator<EdgeAspect> {
private Point2D centre;
/**
Construct a CounterClockwiseSort with a reference point.
@param centre The reference point.
*/
public CounterClockwiseSort(Point2D centre) {
this.centre = centre;
}
@Override
public int compare(EdgeAspect first, EdgeAspect second) {
double d1 = score(first);
double d2 = score(second);
if (d1 < d2) {
return 1;
}
else if (d1 > d2) {
return -1;
}
else {
return 0;
}
}
/**
Compute the score for a RoadAspect - the amount of clockwiseness from 12 o'clock.
@param aspect The RoadAspect.
@return The amount of clockwiseness. This will be in the range [0..4) with 0 representing 12 o'clock, 1 representing 3 o'clock and so on.
*/
public double score(EdgeAspect aspect) {
LegacyObject node = aspect.getFarNode();
Point2D point = new Point2D(node.getX(), node.getY());
Vector2D v = point.minus(centre);
double sin = v.getX() / v.getLength();
double cos = v.getY() / v.getLength();
if (Double.isNaN(sin) || Double.isNaN(cos)) {
System.out.println(v);
System.out.println(v.getLength());
}
return convert(sin, cos);
}
// CHECKSTYLE:OFF:MagicNumber
private double convert(double sin, double cos) {
if (sin >= 0 && cos >= 0) {
return sin;
}
if (sin >= 0 && cos < 0) {
return 2 - sin;
}
if (sin < 0 && cos < 0) {
return 2 - sin;
}
if (sin < 0 && cos >= 0) {
return 4 + sin;
}
throw new IllegalArgumentException("This should be impossible! What's going on? sin=" + sin + ", cos=" + cos);
}
// CHECKSTYLE:ON:MagicNumber
}
}