/*
* File name: TrajectoryPlugin.java
* Java version: 6.0
* Author(s): Lukas König
* File created: 01.10.2010 (in Budapest, airport)
*
* (c) This file and the EAS (Easy Agent Simulation) framework containing it
* is protected by Creative Commons by-nc-sa license. Any altered or
* further developed versions of this file have to meet the agreements
* stated by the license conditions.
*
* In a nutshell
* -------------
* You are free:
* - to Share -- to copy, distribute and transmit the work
* - to Remix -- to adapt the work
*
* Under the following conditions:
* - Attribution -- You must attribute the work in the manner specified by the
* author or licensor (but not in any way that suggests that they endorse
* you or your use of the work).
* - Noncommercial -- You may not use this work for commercial purposes.
* - Share Alike -- If you alter, transform, or build upon this work, you may
* distribute the resulting work only under the same or a similar license to
* this one.
*
* + Detailed license conditions (Germany):
* http://creativecommons.org/licenses/by-nc-sa/3.0/de/
* + Detailed license conditions (unported):
* http://creativecommons.org/licenses/by-nc-sa/3.0/deed.en
*
* This header must be placed in the beginning of any version of this file.
*/
package eas.plugins.standard.visualization;
import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.TreeMap;
import javax.imageio.ImageIO;
import eas.math.geometry.Rectangle2D;
import eas.math.geometry.Vector2D;
import eas.miscellaneous.StaticMethods;
import eas.plugins.AbstractDefaultPlugin;
import eas.simulation.Wink;
import eas.simulation.agent.AbstractAgent;
import eas.simulation.event.EASEvent;
import eas.simulation.spatial.sim2D.standardAgents.AbstractAgent2D;
import eas.simulation.spatial.sim2D.standardEnvironments.AbstractEnvironment2D;
import eas.startSetup.GlobalVariables;
import eas.startSetup.ParCollection;
import eas.startSetup.SingleParameter;
import eas.startSetup.parameterDatatypes.ArrayListVec2D;
import eas.startSetup.parameterDatatypes.Datatypes;
/**
* @author Lukas König
*/
public class AllroundTrajectoryPlugin extends AbstractDefaultPlugin<AbstractEnvironment2D<AbstractAgent2D<?>>> {
private static final long serialVersionUID = 9017869109522644959L;
@Override
public List<String> getRequiredPlugins() {
return null;
}
@Override
public List<SingleParameter> getParameters() {
LinkedList<SingleParameter> list = new LinkedList<SingleParameter>();
list.add(new SingleParameter(
"liveTrajectories?",
Datatypes.BOOLEAN,
true,
"Paints trajectories on screen during simulation if true.",
this.id().toUpperCase()));
list.add(new SingleParameter(
"fileNameTrajectories",
Datatypes.STRING,
"trajectories.png",
"Paints trajectories in png file with this filename ('.png' "
+ "is added automatically!). 'null' for no file creation.",
this.id().toUpperCase()));
ArrayListVec2D a = new ArrayListVec2D(1);
a.add(new Vector2D(-1, 0));
ArrayListVec2D b = new ArrayListVec2D(1);
b.add(new Vector2D(-1, Double.MAX_VALUE));
list.add(new SingleParameter(
"startTrajectories",
Datatypes.VECTOR2D_ARR,
a,
"If live painting is turned OFF: start trajectory painting "
+ "for any of the given tuples (x, y) by interpreting x as "
+ "agent id and y as time step. x = -1 means all agents.",
this.id().toUpperCase()));
list.add(new SingleParameter(
"endTrajectories",
Datatypes.VECTOR2D_ARR,
b,
"If live painting is turned OFF: stop trajectory painting "
+ "for any of the given tuples (x, y) by interpreting x as "
+ "agent id and y as time step. x = -1 means all agents.",
this.id().toUpperCase()));
list.add(new SingleParameter(
"captureUpperLeftCoordinate",
Datatypes.VECTOR2D,
new Vector2D(0, 0),
"The upper left corner of the simulation field that should"
+ "be captured.",
this.id().toUpperCase()));
list.add(new SingleParameter(
"captureLowerRightCoordinate",
Datatypes.VECTOR2D,
new Vector2D(100, 100),
"The lower right corner of the simulation field that should"
+ "be captured.",
this.id().toUpperCase()));
list.add(new SingleParameter(
"autoScale?",
Datatypes.BOOLEAN,
true,
"Scales automatically to field view size. Environment must "
+ "support this! (Can be useless "
+ "if environment zooms out etc. as agent trajectories may "
+ "disappear behind the borders of the image.)",
this.id().toUpperCase()));
list.add(new SingleParameter(
"saveInterval",
Datatypes.DOUBLE,
1000d,
"Interval stating how often the trajectory image is saved.",
this.id().toUpperCase(),
this.getClass()));
return list;
}
private static double saveInterval = 0;
public static void setSaveInterval(double saveInterval) {
if (saveInterval != AllroundTrajectoryPlugin.saveInterval) {
GlobalVariables.getPrematureParameters().logDebug(
"<" + new AllroundTrajectoryPlugin().id()
+ "> Value of 'saveInterval' changed from "
+ AllroundTrajectoryPlugin.saveInterval
+ " to "
+ saveInterval);
AllroundTrajectoryPlugin.saveInterval = saveInterval;
}
}
@Override
public String id() {
return AbstractDefaultPlugin.ALLROUND_PLUGIN_PREFIX + "-trajectoryPlugin2D";
}
private transient BufferedImage trajectoryImage;
private transient Graphics2D trajectoryImageGraphics;
private int fieldWidth;
private int fieldHeight;
private Vector2D upperLeft;
private Vector2D lowerRight;
private boolean autoscale;
/**
* Last position of currently active agents. Position <code>null</code>
* means that there is no known last position available.
*/
private HashMap<Integer, Vector2D> activeAgents;
/**
* (x, y): Start trajectories of agents y at time steps x.
* Keys are sorted.
*/
private TreeMap<Double, LinkedList<Integer>> beginAgents;
/**
* (x, y): End trajectories of agents y at time steps x.
* Keys are sorted.
*/
private TreeMap<Double, LinkedList<Integer>> endAgents;
private LiveWindow liveWindow = null;
/**
* TODO: Unchecked conversion.
*/
@Override
public void runBeforeSimulation(AbstractEnvironment2D<AbstractAgent2D<?>> env, ParCollection params) {
env.addVisualizer(this);
this.createImage(env, params);
boolean showLiveWindow = params.getParValueBoolean("liveTrajectories?");
this.activeAgents = new HashMap<Integer, Vector2D>();
// Create lists of time steps to activate or deactivate trajectories.
ArrayListVec2D begin = params.getParValueArrayListVector2D(
"startTrajectories");
ArrayListVec2D end = params.getParValueArrayListVector2D(
"endTrajectories");
this.beginAgents = new TreeMap<Double, LinkedList<Integer>>();
this.endAgents = new TreeMap<Double, LinkedList<Integer>>();
for (Vector2D v : begin) {
if (this.beginAgents.get(v.y) == null) {
this.beginAgents.put(v.y, new LinkedList<Integer>());
}
this.beginAgents.get(v.y).add((int) v.x);
}
for (Vector2D v : end) {
if (this.endAgents.get(v.y) == null) {
this.endAgents.put(v.y, new LinkedList<Integer>());
}
this.endAgents.get(v.y).add((int) v.x);
}
if (showLiveWindow) {
this.liveWindow = new LiveWindow(
"Live Trajectory View",
env,
params,
this.trajectoryImage.getWidth() + 25,
this.trajectoryImage.getHeight() + 25,
true);
this.liveWindow.metaInfVis.img = this.trajectoryImage;
}
}
private void createImage(AbstractEnvironment2D<AbstractAgent2D<?>> env, ParCollection params) {
BufferedImage fieldView;
this.autoscale = params.getParValueBoolean("autoScale?");
boolean perfectFit = env.isPerfectFit();
env.getSimTime().neglectTicks(this);
env.getSimTime().requestAllNotifications(this);
// Rectangle2D oldZoom = env.getZoomBox();
if (this.autoscale) {
if (env.getCurrentViewBox() != null) {
// env.setZoomBox(null);
this.upperLeft = new Vector2D(env.getCurrentViewBox().upperLeftCorner());
this.lowerRight = new Vector2D(env.getCurrentViewBox().lowerRightCorner());
// env.setZoomBox(new Rectangle2D(
// env.getCurrentViewBox().liObEcke(),
// env.getCurrentViewBox().rechtUntEcke()));
} else {
this.upperLeft = params
.getParValueVector2D("captureUpperLeftCoordinate");
this.lowerRight = params
.getParValueVector2D("captureLowerRightCoordinate");
// env.setZoomBox(new Rectangle2D(this.upperLeft, this.lowerRight));
StaticMethods.logWarning(
this.id() + ": Automatic Scaling turned off.",
params);
}
} else {
this.upperLeft = params
.getParValueVector2D("captureUpperLeftCoordinate");
this.lowerRight = params
.getParValueVector2D("captureLowerRightCoordinate");
// env.setZoomBox(new Rectangle2D(this.upperLeft, this.lowerRight));
}
boolean isShowIDs = env.isShowIDs();
env.setShowIDs(false);
fieldView = env.generateEnvironmentView(
700,
700,
0,
null,
new Rectangle2D(this.upperLeft, this.lowerRight));
env.setShowIDs(isShowIDs);
// env.setZoomBox(oldZoom);
env.setPerfectFit(perfectFit);
this.fieldWidth = fieldView.getWidth();
this.fieldHeight = fieldView.getHeight();
this.trajectoryImage = new BufferedImage(
this.fieldWidth,
this.fieldHeight,
BufferedImage.TYPE_INT_RGB);
this.trajectoryImageGraphics = this.trajectoryImage.createGraphics();
this.trajectoryImageGraphics.setBackground(Color.white);
this.trajectoryImageGraphics.fillRect(0, 0, this.fieldWidth, this.fieldHeight);
this.trajectoryImageGraphics.drawImage(fieldView, 0, 0, null);
this.trajectoryImageGraphics.setStroke(
new BasicStroke(3));
}
private void saveImage(AbstractEnvironment2D<AbstractAgent2D<?>> env, ParCollection params) {
String filename =
params.getStdDirectory()
+ File.separatorChar
+ params.getParValueString("fileName-Trajectories")
+ "-"
+ this.beginTrajectory
+ "-"
+ this.endTrajectory
+ ".png";
if (!params.getParValueString("fileName-Trajectories").equals("null")) {
try {
ImageIO.write(this.trajectoryImage, "png", new File(filename));
StaticMethods.logInfo(
"Trajectory image saved: " + filename,
params);
} catch (IOException e) {
StaticMethods.logError(
"Trajectory image not saved: " + filename,
params);
}
}
for (int agent : this.activeAgents.keySet()) {
this.activeAgents.put(agent, null);
}
this.createImage(env, params);
}
@Override
public void runAfterSimulation(AbstractEnvironment2D<AbstractAgent2D<?>> env, ParCollection params) {
this.endTrajectory = "end";
this.saveImage(env, params);
}
private double sofar = 0;
private String beginTrajectory = "0";
private String endTrajectory = "end";
@Override
public synchronized void runDuringSimulation(AbstractEnvironment2D<AbstractAgent2D<?>> env, Wink simZyk,
ParCollection params) {
// Live view.
if (this.liveWindow != null) {
this.liveWindow.metaInfVis.img = this.trajectoryImage;
this.liveWindow.metaInfVis.repaint();
}
// Save image?
if (simZyk.getCurrentTime() - this.sofar >= saveInterval) {
this.endTrajectory = (simZyk.getCurrentTime() + "").replace('.', '_');
this.sofar = simZyk.getCurrentTime();
this.saveImage(env, params);
this.beginTrajectory
= (simZyk.getCurrentTime() + "").replace('.', '_');
}
// Aktiviere Agenten.
HashSet<Double> removeTimeStepsBegin = new HashSet<Double>();
HashSet<Double> removeTimeStepsEnd = new HashSet<Double>();
for (double d : this.beginAgents.keySet()) {
if (d > simZyk.getCurrentTime()) {
break;
}
for (int agent : this.beginAgents.get(d)) {
if (agent == -1) {
for (AbstractAgent<?> ag : env.getAgents()) {
this.activeAgents.put(ag.id(), null);
}
} else {
this.activeAgents.put(agent, null);
}
}
removeTimeStepsBegin.add(d);
}
for (double d : removeTimeStepsBegin) {
this.beginAgents.remove(d);
}
// Zeichne Trajektorien.
// Rectangle2D oldZoom = env.getZoomBox();
// double width = env.getScreenWidth();
// double height = env.getScreenHeight();
// env.setZoomBox(new Rectangle2D(this.upperLeft, this.lowerRight));
// env.setScreenWidth(700);
// env.setScreenHeight(700);
for (int i : this.activeAgents.keySet()) {
Vector2D pos;
try {
pos = env.getPositionInVisualization(
i,
700,
700,
0,
null,
new Rectangle2D(this.upperLeft, this.lowerRight));
} catch (Exception e) {
pos = env.getPositionInVisualization(i);
}
if (pos != null) {
Vector2D oldPos = this.activeAgents.get(i);
if (oldPos != null) {
this.trajectoryImageGraphics.setColor(
new Color(
(i * 13) % 199,
(i * 137) % 199,
(i * 227) % 199));
this.trajectoryImageGraphics.drawLine(
(int) oldPos.x,
(int) oldPos.y,
(int) pos.x,
(int) pos.y);
}
this.activeAgents.put(i, pos);
}
}
// env.setZoomBox(oldZoom);
// env.setScreenWidth(width);
// env.setScreenHeight(height);
// Deaktiviere Agenten.
for (double d : this.endAgents.keySet()) {
if (d > simZyk.getCurrentTime()) {
break;
}
for (int agent : this.endAgents.get(d)) {
if (agent == -1) {
for (AbstractAgent<?> ag : env.getAgents()) {
this.activeAgents.remove(ag.id());
}
} else {
this.activeAgents.remove(agent);
}
}
removeTimeStepsEnd.add(d);
}
for (double d : removeTimeStepsEnd) {
this.endAgents.remove(d);
}
}
@Override
public void handleEvent(EASEvent e, AbstractEnvironment2D<AbstractAgent2D<?>> env,
Wink lastTimeStep, ParCollection params) {
// this.runDuringSim(env, lastTimeStep, params);
}
@Override
public List<String> getSupportedPlugins() {
return null;
}
@Override
public void onSimulationResumed(AbstractEnvironment2D<AbstractAgent2D<?>> env, Wink resumeTime,
ParCollection params) {
// env.requestRedraw();
super.onSimulationResumed(env, resumeTime, params);
this.createImage(env, params);
if (params.getParValueBoolean("liveTrajectories?")) {
this.liveWindow.setVisible(true);
}
}
}