package edu.colostate.vchill.plot;
import edu.colostate.vchill.LimitedList;
import edu.colostate.vchill.Loader;
import edu.colostate.vchill.ViewUtil;
import edu.colostate.vchill.chill.ChillMomentFieldScale;
import edu.colostate.vchill.chill.ChillNewExtTrackInfo;
import edu.colostate.vchill.chill.ChillOldExtTrackInfo;
import edu.colostate.vchill.chill.ChillTrackInfo;
import edu.colostate.vchill.data.Ray;
import edu.colostate.vchill.gui.*;
import javax.imageio.ImageIO;
import javax.swing.*;
import java.awt.*;
import java.awt.event.ComponentAdapter;
import java.awt.event.ComponentEvent;
import java.awt.image.BufferedImage;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.List;
import java.util.Map;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;
/**
* This window type displays data as colored graphics.
*
* @author Justin Carlson
* @author Jochen Deyke
* @author jpont
* @version 2010-08-02
* @created April 25, 2003
*/
public class ViewPlotWindow extends ViewWindow {
/**
*
*/
private static final long serialVersionUID = -275737332070996909L;
private boolean plotting; //are we currently plotting?
private ViewSplitPane containAll;
private int prevWidth;
private int prevHeight;
private int plotWidth;
private int plotHeight;
//The Current plotting methods to be used based on the data
//that is being received.
private final static MapServerConfig msConfig = MapServerConfig.getInstance();
private ViewPlotMethod plotMethod;
private boolean modeOverriden = false;
//Used to plot information with a graphics, this includes
//things like user mouse clicks, type of plots etc.
private ViewPlotInformation plotInformation;
//used to set colors correctly for the various plot types.
//Used to draw data onto
private BufferedImage dataBuffer;
private BufferedImage aircraftBuffer;
private BufferedImage overlayBuffer;
private BufferedImage annotationBuffer;
private boolean overlayReplotNeeded = true;
private Map<String, LimitedList<Point>> aircraftInfo;
private volatile int dragOffsetX = 0;
private volatile int dragOffsetY = 0;
private volatile int dragRectStartX = 0;
private volatile int dragRectStartY = 0;
private volatile int dragRectEndX = 0;
private volatile int dragRectEndY = 0;
/**
* Constructor for the ViewPlotWindow object
*
* @param type The datatype to display (eg Z, V, ...)
*/
public ViewPlotWindow(final String type) {
super();
this.type = type;
//Set the layout to add components to the left as they are added.
plotMethod = new ViewPlotMethodPPI(this.type);
this.aircraftInfo = plotMethod.getAircraftInfo();
plotInformation = new ViewPlotInformation();
dataBuffer = new BufferedImage(1, 1, BufferedImage.TYPE_INT_ARGB);
this.aircraftBuffer = new BufferedImage(1, 1, BufferedImage.TYPE_INT_ARGB);
this.overlayBuffer = new BufferedImage(1, 1, BufferedImage.TYPE_INT_ARGB);
this.annotationBuffer = new BufferedImage(1, 1, BufferedImage.TYPE_INT_ARGB);
//Swing setup.
setCursor(Cursor.getPredefinedCursor(Cursor.CROSSHAIR_CURSOR));
setBackground(Color.BLACK);
setForeground(Color.BLACK);
setDoubleBuffered(false);
//setType(type);
this.prevWidth = getWidth();
this.prevHeight = getHeight();
this.addComponentListener(new ComponentAdapter() {
@Override
public void componentResized(final ComponentEvent ce) {
wm.setPlotWindowWidth(parent.getWidth());
wm.setPlotWindowHeight(parent.getHeight());
wm.resizePlots();
vc.rePlot();
}
});
}
public void setColors() {
this.setColors(this.plotMethod.getColors());
}
public void setColors(final List<Color> colors) {
plotInformation.setColors(colors);
rePlotDrawingArea();
}
/**
* Changes the type of data that will be plotted
*
* @param type The new type value
*/
@Override
public void setType(final String type) {
super.setType(type);
plotMethod.setType(type);
this.setColors();
}
private void resetInformation() {
plotInformation.setRadarName(plotMethod.getRadarName());
ChillMomentFieldScale scale = sm.getScale(type);
if (scale == null) return; //no longer available
plotInformation.setType(type
+ (scale.isGradientable() ? config.getGradientType().suffix :
(scale.isUnfoldable() ? (config.isUnfoldingEnabled() ? "UnF" : "") :
""))); //append string according to filter used
plotInformation.setDataKey(scale.fieldDescription);
plotInformation.setAnnotationString(plotMethod.getAnnotationString());
plotInformation.setElevation(ViewUtil.format(plotMethod.getRadarElevation()));
plotInformation.setAzimuth(ViewUtil.format(plotMethod.getRadarAzimuth()));
plotInformation.setDateAndTime(plotMethod.getDateAndTime());
plotInformation.setGateInfo(plotMethod.getNumberOfGates() + "x" +
((int) plotMethod.getMetersPerGate()) + "m");
}
//Which plotting Method should be used, this needs to be changed
//to something much simpler.
/**
* Changes the plotting method
*
* @param mode The new plotting mode (PPI, RHI, etc)
* @param overrideMode Whether the new mode was explicitly specified
*/
public void setMode(final String mode, final boolean overrideMode) {
if (modeOverriden && !overrideMode) return; //ignore if override is on
if (mode == null) return; //can't plot null mode
if (mode.equals(plotMethod.getPlotMode()) && !overrideMode) return; //same mode
if (mode.equals("auto")) {
modeOverriden = false;
return;
}
modeOverriden = overrideMode; //won't turn off due to return above
if (mode.equals("RHI")) {
plotMethod = new ViewPlotMethodRHI(type);
} else if (mode.equals("PPI")) {
plotMethod = new ViewPlotMethodPPI(type);
} else if (mode.equals("MAN")) {
plotMethod = new ViewPlotMethodTH(type);
} else {
System.err.println("Unknown mode: " + mode + " (plotting as TH)");
plotMethod = new ViewPlotMethodTH(type);
}
parent.setFrameIcon(new ImageIcon(Loader.getResource("icons/sweep" + plotMethod.getPlotMode() + ".png")));
this.aircraftInfo = plotMethod.getAircraftInfo();
setupPlottingSizes();
overlayReplotNeeded = true;
clearScreen();
rePlotDrawingArea();
}
@Override
public void setParent(final JInternalFrame parent) {
super.setParent(parent);
this.containAll = new ViewSplitPane(this, plotInformation);
this.containAll.addPropertyChangeListener(ViewSplitPane.DIVIDER_LOCATION_PROPERTY, new PropertyChangeListener() {
public void propertyChange(final PropertyChangeEvent evt) {
wm.setPlotDividerLocation(((ViewSplitPane) evt.getSource()).getDividerLocation());
wm.resetPlotDividerLocation();
}
});
this.containAll.setOneTouchExpandable(true);
parent.setContentPane(this.containAll);
parent.setFrameIcon(new ImageIcon(Loader.getResource("icons/sweep" + plotMethod.getPlotMode() + ".png")));
}
public String getPlotMode() {
return modeOverriden ? plotMethod.getPlotMode() : "<auto>";
}
public void rePlotDrawingArea() {
resetInformation();
parent.repaint(parent.getVisibleRect());
}
public void clearScreen() {
Graphics g = getGraphics();
plotMethod.clearScreen(g);
Graphics2D gBuff = dataBuffer.createGraphics();
plotMethod.clearScreen(gBuff);
repaint();
}
public void clearAircraftInfo() {
plotMethod.clearAircraftInfo();
}
/**
* This method will reset where everything is drawing, and also how
* much area the various operations will be taking. This is to allow
* the window to be dynamically resized and still have the center
* remain consistent.
*/
private void setupPlottingSizes() {
plotWidth = /*config.isAnnotationEnabled() ? getWidth() * 4 / 5 : */getWidth();
plotHeight = getHeight();
//Set the size of the plotting area.
plotMethod.setPreferredSize(plotWidth, plotHeight);
if (plotWidth == this.prevWidth && plotHeight == this.prevHeight) return;
if (getWidth() <= 0 || getHeight() <= 0) return;
this.prevWidth = plotWidth;
this.prevHeight = plotHeight;
dataBuffer = new BufferedImage(plotWidth, plotHeight, BufferedImage.TYPE_INT_ARGB);
this.aircraftBuffer = new BufferedImage(plotWidth, plotHeight, BufferedImage.TYPE_INT_ARGB);
Graphics2D acg = this.aircraftBuffer.createGraphics();
acg.setColor(new Color(1f, 1f, 1f, 1f));
this.overlayBuffer = new BufferedImage(plotWidth, plotHeight, BufferedImage.TYPE_INT_ARGB);
this.annotationBuffer = new BufferedImage(plotWidth, plotHeight, BufferedImage.TYPE_INT_ARGB);
Graphics2D olg = this.overlayBuffer.createGraphics();
olg.setColor(new Color(1f, 1f, 1f, 1f));
overlayReplotNeeded = true;
}
@Override
public void paintComponent(final Graphics g) {
super.paintComponent(g);
if (g == null) return;
g.drawImage(dataBuffer, dragOffsetX, dragOffsetY, this);
this.clearAircraftBuffer();
this.plotAircraft();
if (overlayReplotNeeded) {
clearOverlayBuffer();
plotOverlay();
clearAnnotationBuffer();
}
if (ViewPaintPanel.GreasePencilAnnotationEnabled) {
if (image == null) {
image = createImage(getSize().width, getSize().height);
Graphics2D graphics2D = annotationBuffer.createGraphics();
graphics2D = (Graphics2D) image.getGraphics();
graphics2D.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
clearAnnotationBuffer();
}
//clear();
//g.drawImage(image, 0, 0, null);
}
g.drawImage(this.overlayBuffer, dragOffsetX, dragOffsetY, null);
g.drawImage(this.aircraftBuffer, dragOffsetX, dragOffsetY, null);
if (config.isClickPointEnabled() && dragOffsetX == 0 && dragOffsetY == 0) plotMethod.plotClickPoint(g);
if (dragRectStartX != 0 || dragRectStartY != 0 || dragRectEndX != 0 || dragRectEndY != 0)
g.draw3DRect(dragRectStartX, dragRectStartY, dragRectEndX - dragRectStartX, dragRectEndY - dragRectStartY, false);
g.drawImage(annotationBuffer, dragOffsetX, dragOffsetY, null);
}
public void replotOverlay() {
overlayReplotNeeded = true;
}
private void clearOverlayBuffer() {
Graphics2D olg = overlayBuffer.createGraphics();
olg.setBackground(new Color(0f, 0f, 0f, 0f));
olg.clearRect(0, 0, overlayBuffer.getWidth(), overlayBuffer.getHeight());
}
private void plotOverlay() {
if (!overlayReplotNeeded) return;
overlayReplotNeeded = false;
Graphics2D overlay = overlayBuffer.createGraphics();
plotMethod.NeedToPlotMap = true;
if (msConfig.plottedUnderlayOnce() == false) {
if (this.plotMethod.Mappable) {
plotMethod.plotMapServerUnderlay(overlay);
}
}
if (config.isGridEnabled()) plotMethod.plotGrid(overlay);
if (config.isMapEnabled()) {
plotMethod.plotMap(overlay);
}
//plotMethod.plotMapServerOverlay(overlay);
if (this.plotMethod.Mappable) {
plotMethod.plotMapServerOverlay(overlay);
}
}
private void clearAircraftBuffer() {
Graphics2D acg = this.aircraftBuffer.createGraphics();
acg.setBackground(new Color(0f, 0f, 0f, 0f));
acg.clearRect(0, 0, aircraftBuffer.getWidth(), aircraftBuffer.getHeight());
}
/**
* Add an aircraft location to the list to be plotted
*/
public void plotAircraft(final ChillTrackInfo loc) {
this.plotMethod.plotAircraft(loc);
}
/**
* Add an aircraft location to the list to be plotted
*/
public void plotAircraft(final ChillOldExtTrackInfo coeti) {
this.plotMethod.plotAircraft(coeti);
}
/**
* Add an aircraft location to the list to be plotted
*/
public void plotAircraft(final ChillNewExtTrackInfo cneti) {
this.plotMethod.plotAircraft(cneti);
}
/**
* Draw aircraft locations in list to buffer
*/
private void plotAircraft() {
Graphics2D acg = this.aircraftBuffer.createGraphics();
for (String name : this.aircraftInfo.keySet()) {
for (Point p : this.aircraftInfo.get(name)) {
if (name.hashCode() % 2 == 0) drawRhombus(acg, (int) p.getX(), (int) p.getY());
else drawSquare(acg, (int) p.getX(), (int) p.getY());
}
}
}
private static void drawRhombus(final Graphics g, final int x, final int y) {
g.drawLine(x - 3, y, x, y - 3);
g.drawLine(x, y - 3, x + 3, y);
g.drawLine(x + 3, y, x, y + 3);
g.drawLine(x, y + 3, x - 3, y);
}
private static void drawSquare(final Graphics g, final int x, final int y) {
g.drawLine(x - 3, y - 3, x + 3, y - 3);
g.drawLine(x - 3, y + 3, x + 3, y + 3);
g.drawLine(x - 3, y - 3, x - 3, y + 3);
g.drawLine(x + 3, y - 3, x + 3, y + 3);
}
public void plot(final Ray prevRay, final Ray currRay,
final Ray nextRay, final Ray threshRay) {
msConfig.plottedOnce(true);
if (currRay == null) return; //can't plot without data...
setMode(currRay.getMode(), false);
//This translation to a specific type may require updates to be made
//that determine data translation and values. Inform the data hash
//of these qaulifiers.
Graphics g = dataBuffer.getGraphics();
if (this.plotMethod.Mappable)
plotMethod.plotMapServerUnderlay(g);
//Send the object which holds the data type into the translation method
//in order to get useful data for plotting.
plotMethod.plotData(prevRay, currRay, nextRay, threshRay, g);
/*
plotInformation.setDataInfo(ViewUtil.format(vc.getDataValue(
plotMethod.getPlotMode(), type, plotMethod.getClickAz(), plotMethod.getClickEl(),
(int)((plotMethod.getClickRng() - plotMethod.getStartRange()) / (plotMethod.getMetersPerGate() / 1000.0)))));
*/
}
@Override
public BufferedImage getBufferedImage() {
return this.containAll.getBufferedImage();
}
/**
* Converts a left mouse click into a km-based ClickPoint instruction
*
* @param x X coordinate of where the mouse was clicked
* @param y Y coordinate of where the mouse was clicked
*/
public void markData(final int x, final int y) {
double az = this.plotMethod.getAzimuthDegrees(x, y);
double el = this.plotMethod.getElevationDegrees(x, y);
double rng = this.plotMethod.getRangeInKm(x, y);
if (rng > 0) wm.setClickRay(az, el, rng, x, y);
else wm.setClickRay(0, 0, 0, x, y);
}
/**
* Converts a middle mouse click into a km-based recentering instruction
*
* @param x X coordinate of where the mouse was clicked
* @param y Y coordinate of where the mouse was clicked
*/
public void recenter(final int x, final int y) {
config.setCenterX((x - plotMethod.getCenterX() > 0 ? 1 : -1) *
plotMethod.getRangeInKm(x, plotMethod.getCenterY()));
config.setCenterY((y - plotMethod.getCenterY() > 0 ? -1 : 1) *
plotMethod.getRangeInKm(plotMethod.getCenterX(), y));
wm.setCenterInKm();
repaint();
}
/**
* Updates the display with information about the ClickPoint
*
* @param azimuth the azimuth angle (in degrees) of the radar
* @param elevation the elevation angle (in degrees) of the radar
* @param range the distance (in km) from the radar
* @param x X coordinate of where the mouse was clicked
* @param y Y coordinate of where the mouse was clicked
*/
public void setClickRay(final double azimuth, final double elevation, final double range, final int x, final int y) {
plotMethod.setClickPoint(azimuth, elevation, range);
double elKm = ViewUtil.getKmElevation(elevation, range);
double elKft = ViewUtil.getKFtFromKm(elKm);
plotInformation.setClickAzimuth(ViewUtil.format(azimuth));
plotInformation.setClickElevation(ViewUtil.format(elevation));
plotInformation.setClickHeight(ViewUtil.format(elKm), ViewUtil.format(elKft));
plotInformation.setClickRange(ViewUtil.format(range));
Double dataValue = vc.getDataValue(
plotMethod.getPlotMode(), type, azimuth, elevation,
(int) Math.round((range - plotMethod.getStartRange()) / (plotMethod.getMetersPerGate() / 1000.0)),
plotMethod, x, y);
plotInformation.setDataInfo(Double.isNaN(dataValue) ? "N/A" : ViewUtil.format(dataValue));
rePlotDrawingArea();
}
/**
* Sets the center of the plot to the coordinates set in ViewControlConfig.
* The coordinates are in km north/east of the radar.
*/
public void setCenterInKm() {
double kmEast = config.getCenterX();
double kmNorth = config.getCenterY();
int x = 0 - plotMethod.getPixelsX(kmEast, kmNorth);
int y = plotMethod.getPixelsY(kmEast, kmNorth);
plotMethod.setPlottingOffset(x, y);
this.replotOverlay();
}
public void rePlot() {
clearScreen();
vc.rePlot();
}
@Override
public synchronized void setPlotting(final boolean plotting) {
super.setPlotting(plotting);
if (plotting) plotMethod.setNewPlot();
}
public void updateSizes() {
super.setSizes(wm.getPlotWindowWidth(), wm.getPlotWindowHeight());
setupPlottingSizes();
}
public void updateDividerLocation() {
this.containAll.setDividerLocation(wm.getPlotDividerLocation());
}
public void setDragOffset(final int dragOffsetX, final int dragOffsetY) {
this.dragOffsetX = dragOffsetX;
this.dragOffsetY = dragOffsetY;
repaint();
}
public void resetDragOffset() {
this.recenter(this.getWidth() / 2 - this.dragOffsetX, this.getHeight() / 2 - this.dragOffsetY);
this.dragOffsetX = 0;
this.dragOffsetY = 0;
rePlot();
}
public void setDragRect(final int startX, final int startY, final int endX, final int endY) {
this.dragRectStartX = startX;
this.dragRectStartY = startY;
this.dragRectEndX = endX;
this.dragRectEndY = endY;
repaint();
}
public void resetDragRect() {
int x = (this.dragRectStartX + this.dragRectEndX) / 2;
int y = (this.dragRectStartY + this.dragRectEndY) / 2;
int size = Math.max(this.dragRectEndX - this.dragRectStartX, this.dragRectEndY - this.dragRectStartY);
config.setCenterX((x - this.plotMethod.getCenterX() > 0 ? 1 : -1) *
plotMethod.getRangeInKm(x, this.plotMethod.getCenterY()));
config.setCenterY((y - plotMethod.getCenterY() > 0 ? -1 : 1) *
plotMethod.getRangeInKm(this.plotMethod.getCenterX(), y));
config.setPlotRange(this.plotMethod.getKmFromPixels(size) / 2);
ViewRemotePanel.getInstance().update();
this.dragRectStartX = 0;
this.dragRectStartY = 0;
this.dragRectEndX = 0;
this.dragRectEndY = 0;
wm.setCenterInKm();
rePlot();
}
/**
* @return Transparentified background version of data buffer
*/
private BufferedImage getDataBuffer() {
BufferedImage result = new BufferedImage(this.dataBuffer.getWidth(), this.dataBuffer.getHeight(), this.dataBuffer.getType());
for (int w = 0; w < result.getWidth(); ++w)
for (int h = 0; h < result.getHeight(); ++h) {
int rgb = this.dataBuffer.getRGB(w, h);
if (rgb == Color.BLACK.getRGB()) rgb = 0x00000000; //transparent black
result.setRGB(w, h, rgb);
}
return result;
}
private BufferedImage getColorbar() {
BufferedImage buff = new BufferedImage(plotInformation.getWidth(), plotInformation.getHeight() * 64 / 100, BufferedImage.TYPE_INT_ARGB);
plotInformation.paintComponent(buff.createGraphics());
return buff;
}
@Override
public String getStyle() {
return "Plot";
}
/**
* Creates a temporary kmz file for use with external display programs
*
* @param filename path/name to save to or null for a temporary, automatically deleted file
* @return the name of the file written to
*/
public String export(final String filename) throws IOException {
if (!this.isExportable()) return null;
File zipfile = filename == null ? File.createTempFile("vchill", ".kmz") : new File(filename);
ZipOutputStream zip = new ZipOutputStream(new FileOutputStream(zipfile));
//write image
zip.putNextEntry(new ZipEntry("vchillimg.png"));
ImageIO.write(this.getDataBuffer(), "png", zip);
zip.closeEntry();
//write colorbar
zip.putNextEntry(new ZipEntry("vchillbar.png"));
ImageIO.write(this.getColorbar(), "png", zip);
zip.closeEntry();
//write kml
this.plotMethod.export(zip);
zip.close();
if (filename == null) zipfile.deleteOnExit();
return zipfile.getAbsolutePath();
}
public boolean isExportable() {
return this.plotMethod.isExportable();
}
Image image;
public void updateAnnotationLayer(int currentX, int currentY, int oldX, int oldY) {
Graphics2D graphics2D = annotationBuffer.createGraphics();
graphics2D.setPaint(ViewPaintPanel.getPaintColor());
graphics2D.setStroke(new BasicStroke(ViewPaintPanel.getPenSize()));
if (graphics2D != null)
//graphics2D.fillOval(currentX, currentY, ViewPaintPanel.getPenSize(), ViewPaintPanel.getPenSize());
graphics2D.drawLine(oldX, oldY, currentX, currentY);
repaint();
}
public void updateTextAnnotationLayer(int currentX, int currentY) {
Graphics2D graphics2D = annotationBuffer.createGraphics();
graphics2D.setPaint(ViewPaintPanel.getPaintColor());
if (graphics2D != null)
if (ViewPaintPanel.PPIDisplayString.getText() != null)
graphics2D.drawString(ViewPaintPanel.PPIDisplayString.getText(), currentX, currentY);
repaint();
}
public void clearAnnotationBuffer() {
Graphics2D graphics2D = annotationBuffer.createGraphics();
graphics2D.setBackground(new Color(0f, 0f, 0f, 0f));
graphics2D.clearRect(0, 0, getSize().width, getSize().height);
repaint();
}
}