package trafficdefinition;
import classes.Pair;
import classes.Project;
import classes.TypeSelection;
import enums.MotionRestriction;
import helpers.DrawingHelper;
import helpers.TimeConverter;
import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
import shapes.Ellipse2DExt;
import shapes.Line2DExt;
import shapes.Point2DExt;
import shapes.Rectangle2DExt;
import simulation.VehicleType;
/**
* Class representing a flow of vehicles from one area to another. The area flow is drawn on screen using an ellipsoid for the starting and ending area, connected with an arrow. Vehicles will be emitted at random points in time between beginTime and endTime.
*/
public class AreaFlow extends TrafficDefinitionElement implements Serializable {
private static final long serialVersionUID = 1L;
/**
* Used during the graphical creation of the area flow.
*/
public int constructionPhase = 0;
/**
* The time that vehicles will start being emitted
*/
private int beginTime = 0;
/**
* The time that vehicles will end being emitted
*/
private int endTime = 100;
/**
* The number of vehicles to emit
*/
private int numberOfVehicles = 100;
/**
* The area flow's color. All created vehicles and routes will have this
* color.
*/
private Color color = new Color((float) Math.random(), (float) Math.random(), (float) Math.random());
/**
* Arrow used for drawing the direction of flow.
*/
private Line2DExt arrow;
/**
* The ellipsoid defining the area from which vehicles will be leaving
*/
private Ellipse2DExt startArea;
/**
* The ellipsoid defining the area to which vehicles will be arriving
*/
private Ellipse2DExt endArea;
/**
* The center of the source area.
*/
private Point2DExt startAreaCenter;
/**
* The horizontal radius of the source area.
*/
private double startAreaRadiusX;
/**
* The vertical radius of the source area.
*/
private double startAreaRadiusY;
/**
* The center of the destination area
*/
private Point2DExt endAreaCenter;
/**
* The horizontal radius of the destination area.
*/
private double endAreaRadiusX;
/**
* The vertical radius of the destination area.
*/
private double endAreaRadiusY;
/**
* The selection of the vehicle types that will be created for this area flow.
* @uml.property name="vehicleSelection"
* @uml.associationEnd
*/
private TypeSelection<VehicleType> vehicleSelection;
/**
* Default constructor. Assigns a random name to the area flow and selects
* the default vehicle type.
*
*/
public AreaFlow() {
super();
name = "Area Flow " + String.valueOf(increment++);
startArea = new Ellipse2DExt();
endArea = new Ellipse2DExt();
arrow = new Line2DExt();
// Assign a possibility of 1 to the default vehicle type
vehicleSelection = new TypeSelection<VehicleType>();
vehicleSelection.add(new Pair<VehicleType, Float>(Project.getCurrentlyLoadedProject().getDefaultVehicleType(), 1.f));
}
/**
* Class constructor that creates a new area flow as a copy of the specified
* area flow
*
* @param source
*/
private AreaFlow(AreaFlow source) {
super(source);
constructionPhase = source.constructionPhase;
beginTime = source.beginTime;
endTime = source.endTime;
numberOfVehicles = source.numberOfVehicles;
color = new Color(source.color.getRGB());
startArea = new Ellipse2DExt(source.startArea);
startAreaCenter = new Point2DExt(source.startAreaCenter);
startAreaRadiusX = source.startAreaRadiusX;
startAreaRadiusY = source.startAreaRadiusY;
endArea = new Ellipse2DExt(source.endArea);
endAreaCenter = new Point2DExt(source.endAreaCenter);
endAreaRadiusX = source.endAreaRadiusX;
endAreaRadiusY = source.endAreaRadiusY;
arrow = new Line2DExt(source.arrow);
vehicleSelection = new TypeSelection<VehicleType>(source.vehicleSelection);
}
@Override
public void Draw(Graphics2D g, double zoomFactor, boolean isDeleted, boolean isSelected) {
// Save the previous color to restore it later
Color prev = g.getColor();
if (!isSelected) {
g.setColor(new Color(color.getRed(), color.getGreen(), color.getBlue(), 100));
}
if (isSelected) {
g.setColor(new Color(color.getRed(), color.getGreen(), color.getBlue(), 200));
}
// Draw the source area
if (startArea != null) {
g.fill(startArea);
}
// Draw the destination area
if (endArea != null) {
g.fill(endArea);
}
// Draw the direction arrow
if (arrow != null) {
g.setColor(Color.BLACK);
DrawingHelper.drawArrowHead(g, arrow.x1, arrow.y1, arrow.x2, arrow.y2, 1.f);
// TODO draw with thicker line
g.draw(arrow);
}
// Draw handles
super.Draw(g, zoomFactor, isDeleted, isSelected);
g.setColor(prev);
}
/**
* Creates the handles that enable UI manipulation of the area flow
*/
public void createHandles() {
List<Handle> handles = new ArrayList<Handle>();
Handle startAreaCenterHandle = new Handle(startAreaCenter, "StartAreaCenter", MotionRestriction.None);
Handle startAreaXRadiusHandle = new Handle(new Point2D.Double(startAreaCenter.x + startAreaRadiusX, startAreaCenter.y), "StartAreaRadiusX", MotionRestriction.Horizontal);
Handle startAreaYRadiusHandle = new Handle(new Point2D.Double(startAreaCenter.x, startAreaCenter.y + startAreaRadiusY), "StartAreaRadiusY", MotionRestriction.Vertical);
Handle endAreaCenterHandle = new Handle(endAreaCenter, "EndAreaCenter", MotionRestriction.None);
Handle endAreaXRadiusHandle = new Handle(new Point2D.Double(endAreaCenter.x + endAreaRadiusX, endAreaCenter.y), "EndAreaRadiusX", MotionRestriction.Horizontal);
Handle endAreaYRadiusHandle = new Handle(new Point2D.Double(endAreaCenter.x, endAreaCenter.y + endAreaRadiusY), "EndAreaRadiusY", MotionRestriction.Vertical);
handles.add(startAreaCenterHandle);
handles.add(startAreaXRadiusHandle);
handles.add(startAreaYRadiusHandle);
handles.add(endAreaCenterHandle);
handles.add(endAreaXRadiusHandle);
handles.add(endAreaYRadiusHandle);
// Assign motion listeners to the handles
super.addHandles(handles);
}
public Rectangle2DExt getBounds() {
Rectangle2D bounds = startArea.getBounds2D();
bounds.add(endArea.getBounds2D());
return new Rectangle2DExt(bounds.getCenterX(),bounds.getCenterY(),bounds.getWidth(),bounds.getHeight());
}
/**
* @return the beginTime
* @uml.property name="beginTime"
*/
public int getBeginTime() {
return beginTime;
}
/**
* @return the color
* @uml.property name="color"
*/
public Color getColor() {
return color;
}
/**
* @return the endArea
* @uml.property name="endArea"
*/
public Ellipse2DExt getEndArea() {
return endArea;
}
/**
* @return the endTime
* @uml.property name="endTime"
*/
public int getEndTime() {
return endTime;
}
/**
* @return the numberOfVehicles
* @uml.property name="numberOfVehicles"
*/
public int getNumberOfVehicles() {
return numberOfVehicles;
}
/**
* @return the startArea
* @uml.property name="startArea"
*/
public Ellipse2DExt getStartArea() {
return startArea;
}
@Override
public String getToolTip() {
return "<html>" + "<b>Area flow</b> " + name + "<br>" + "<b>Number of vehicles:</b> " + String.valueOf(numberOfVehicles) + "<br>" + "<b>Start time:</b> " + TimeConverter.toString(beginTime) + "<br>" + "<b>End time:</b> " + TimeConverter.toString(endTime) + "</html>";
}
/**
* @return the vehicleSelection
* @uml.property name="vehicleSelection"
*/
public TypeSelection<VehicleType> getVehicleSelection() {
return vehicleSelection;
}
public void handleLocationChanged(Handle h) {
if (h.getName().equals("StartAreaCenter")) {
startAreaCenter = new Point2DExt(h.getLocation());
// Move the other handles with it
handles.get("StartAreaRadiusX").setUnrestrictedLocation(new Point2D.Double(startAreaCenter.x + startAreaRadiusX, startAreaCenter.y));
handles.get("StartAreaRadiusY").setUnrestrictedLocation(new Point2D.Double(startAreaCenter.x, startAreaCenter.y + startAreaRadiusY));
} else if (h.getName().equals("StartAreaRadiusX")) {
startAreaRadiusX = h.getLocation().x - startAreaCenter.x;
} else if (h.getName().equals("StartAreaRadiusY")) {
startAreaRadiusY = h.getLocation().y - startAreaCenter.y;
} else if (h.getName().equals("EndAreaCenter")) {
endAreaCenter = new Point2DExt(h.getLocation());
// Move the other handles with it
handles.get("EndAreaRadiusX").setUnrestrictedLocation(new Point2D.Double(endAreaCenter.x + endAreaRadiusX, endAreaCenter.y));
handles.get("EndAreaRadiusY").setUnrestrictedLocation(new Point2D.Double(endAreaCenter.x, endAreaCenter.y + endAreaRadiusY));
} else if (h.getName().equals("EndAreaRadiusX")) {
endAreaRadiusX = h.getLocation().x - endAreaCenter.x;
} else if (h.getName().equals("EndAreaRadiusY")) {
endAreaRadiusY = h.getLocation().y - endAreaCenter.y;
}
// Recreate the shapes
startArea.setFrameFromCenter(startAreaCenter, new Point2D.Double(startAreaCenter.x + startAreaRadiusX, startAreaCenter.y + startAreaRadiusY));
endArea.setFrameFromCenter(endAreaCenter, new Point2D.Double(endAreaCenter.x + endAreaRadiusX, endAreaCenter.y + endAreaRadiusY));
arrow.setLine(startAreaCenter, endAreaCenter);
}
@Override
public boolean Hit(Point p) {
if (arrow.getBounds2D().contains(p.x, p.y)) {
if (arrow.contains(p.x, p.y)) {
return true;
}
}
if (startArea.getBounds2D().contains(p.x, p.y)) {
if (startArea.contains(p.x, p.y)) {
return true;
}
}
if (endArea.getBounds2D().contains(p.x, p.y)) {
if (endArea.contains(p.x, p.y)) {
return true;
}
}
return false;
}
@Override
public boolean Hit(Rectangle transformedRectangle) {
return (arrow.intersects(transformedRectangle) || startArea.intersects(transformedRectangle) || endArea.intersects(transformedRectangle));
}
/**
* @param beginTime the beginTime to set
* @uml.property name="beginTime"
*/
public void setBeginTime(int beginTime) {
this.beginTime = beginTime;
}
/**
* @param color the color to set
* @uml.property name="color"
*/
public void setColor(Color color) {
this.color = color;
}
public void setEndAreaCenter(Point2D.Double p) {
endAreaCenter = new Point2DExt(p.x, p.y);
arrow.setLine(startAreaCenter, endAreaCenter);
}
public void setEndAreaRadius(Point2D.Double p) {
endAreaRadiusX = p.x - endAreaCenter.x;
endAreaRadiusY = p.y - endAreaCenter.y;
endArea.setFrameFromCenter(endAreaCenter, new Point2D.Double(endAreaCenter.x + endAreaRadiusX, endAreaCenter.y + endAreaRadiusY));
}
/**
* @param endTime the endTime to set
* @uml.property name="endTime"
*/
public void setEndTime(int endTime) {
this.endTime = endTime;
}
/**
* @param numberOfVehicles the numberOfVehicles to set
* @uml.property name="numberOfVehicles"
*/
public void setNumberOfVehicles(int numberOfVehicles) {
this.numberOfVehicles = numberOfVehicles;
}
public void setStartAreaCenter(Point2D.Double p) {
startAreaCenter = new Point2DExt(p.x, p.y);
}
public void setStartAreaRadius(Point2D.Double p) {
startAreaRadiusX = p.x - startAreaCenter.x;
startAreaRadiusY = p.y - startAreaCenter.y;
startArea.setFrameFromCenter(startAreaCenter, new Point2D.Double(startAreaCenter.x + startAreaRadiusX, startAreaCenter.y + startAreaRadiusY));
}
/**
* @param vehicleSelection the vehicleSelection to set
* @uml.property name="vehicleSelection"
*/
public void setVehicleSelection(TypeSelection<VehicleType> vehicleSelection) {
this.vehicleSelection = vehicleSelection;
}
@Override
public TrafficDefinitionElement elementClone() {
return new AreaFlow(this);
}
@Override
public String toXML() {
StringBuilder sb=new StringBuilder();
sb.append("<areaFlow name=\""+name+"\" numberOfVehicles=\""+String.valueOf(numberOfVehicles)+"\" startingTime=\""+String.valueOf(beginTime)+"\" endingTime=\""+String.valueOf(endTime)+"\">\n");
sb.append(tdl.XMLHelpers.vehicleSelectionToXML("vehicleSelection", vehicleSelection));
sb.append("<sourceAreaShape>");
sb.append(tdl.XMLHelpers.shapeToXML(startArea));
sb.append("</sourceAreaShape>\n");
sb.append("<destinationAreaShape>");
sb.append(tdl.XMLHelpers.shapeToXML(endArea));
sb.append("</destinationAreaShape>\n");
sb.append("</areaFlow>\n");
return sb.toString();
}
}