package edu.colostate.vchill.plot;
import edu.colostate.vchill.ViewUtil;
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.map.MapInstruction;
import edu.colostate.vchill.map.MapInstruction.Shape;
import javax.imageio.ImageIO;
import java.awt.*;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.io.UnsupportedEncodingException;
import java.net.URL;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;
/**
* This class is the plotting method for PPI data of the Chill format.
*
* @author Justin Carlson
* @author Jochen Deyke
* @author jpont
* @author Michael Rausch
* @version 2010-08-02
* @created April 25, 2003
*/
public class ViewPlotMethodPPI extends ViewPlotMethod {
protected static final double piOverTwo = Math.PI / 2;
protected static final double threePiOverTwo = 3 * piOverTwo;
private double centerX = 0;
private double centerY = 0;
/**
* Sole constructor
*/
public ViewPlotMethodPPI(final String type) {
super(type);
this.Mappable = true;
}
/**
* This method will plot the two lines perpendicular to
* each other that intersect the midpoint.
*
* @param g the graphics context to use to draw the grid to.
* Used in this manner to allow easy switching of where the
* information is actually drawn.
*/
@Override
public void plotGrid(final Graphics g) {
if (g == null) return;
g.setColor(Color.WHITE);
int maxDistance = getPixelsFromKm(Math.max(config.getPlotRange(), 150)); //maximum range should be determined by data: gatewidth * numgates
//Plot the Vertical line down the center of the plotting area.
g.drawLine(this.width / 2 + offsetX,
offsetY - maxDistance,
this.width / 2 + offsetX,
this.height + offsetY + maxDistance);
//Plot the Horizontal line across the middle of the plotting area.
g.drawLine(offsetX - maxDistance,
this.height / 2 + offsetY,
this.width + offsetX + maxDistance,
this.height / 2 + offsetY);
//Plot diagonals
g.drawLine(this.width + offsetX + maxDistance,
offsetY - maxDistance,
offsetX - maxDistance,
this.height + offsetY + maxDistance);
g.drawLine(offsetX - maxDistance,
offsetY - maxDistance,
this.width + offsetX + maxDistance,
this.height + offsetY + maxDistance);
plotRangeRings(g);
}
/**
* Draws the circles that are used to show expanding distance
* in a PPI plot. The rings are drawn outwards from the center
* at intervals determined by the config.getGridSpacing() method
* in the superclass. This value can be set by the user.
*
* @param g Graphics that should be drawn to
*/
private void plotRangeRings(final Graphics g) {
int topLeftX = 1;
int topLeftY = 1;
int centerX = this.width / 2;
int centerY = this.height / 2;
int currXOffset = 0;
int currYOffset = 0;
int currDistance = 0;
//While we have not plotted out to a spcified maximum distance
//continue to draw the grid. NOTE: 0 must not be allowed as a
//incr for the distance, or -1. Must be checked in the set
//method or this infinite loops.
while (currDistance <= Math.max(config.getPlotRange(), 150)) { //maximum range should be determined by data: gatewidth * numgates
//This is the ammounts that must be increased pixels wise for the
//increase in kilometers.
currXOffset = getPixelsFromKm(currDistance);
currYOffset = getPixelsFromKm(currDistance);
//The circle coordinates that are determine by midpoint
//calculations. This should be changed to use helper functions.
topLeftX = centerX - currXOffset;
topLeftY = centerY - currYOffset;
//Draw the circle part of this grid.
g.drawOval(topLeftX + offsetX,
topLeftY + offsetY,
2 * currXOffset,
2 * currYOffset
);
//This will draw the "distance" that each ring is currently
//considered to be at.
g.drawString(String.valueOf(currDistance),
centerX + offsetX + currXOffset + 1,
centerY + offsetY + 11);
g.drawString(String.valueOf(currDistance),
centerX + offsetX - currXOffset + 1,
centerY + offsetY + 11);
//Get the ever increasing distance to plot by adding on another
//distance interval.
currDistance += config.getGridSpacing();
}
}
private Image plotEPSG4326Overlay() {
System.out.println("Using EPSG:4326 for overlay");
double BBnorth, BBsouth, BBeast, BBwest;
Image image = null;
double[] NWLatLong = ViewUtil.getDegrees(getKmFromPixels(-getCenterX()), getKmFromPixels(getCenterY()));
double[] SELatLong = ViewUtil.getDegrees(getKmFromPixels(-getCenterX() + this.width), getKmFromPixels(getCenterY() - this.height));
BBwest = NWLatLong[0];
BBeast = SELatLong[0];
BBnorth = NWLatLong[1];
BBsouth = SELatLong[1];
try {
// Read from a URL
URL url = new URL("http://wms.chill.colostate.edu/cgi-bin/mapserv?REQUEST=GetMap&VERSION=1.1.1&SRS=epsg:4326&SERVICE=WMS&map=/var/www/html/maps/test.map&BBOX=" + BBwest + "," + BBsouth + "," + BBeast + "," + BBnorth + "&WIDTH=" + (this.width) + "&HEIGHT=" + (this.height) + "&FORMAT=image/png;%20mode=24bit&LAYERS=" + msConfig.getUserMapOverlayLayers());
image = ImageIO.read(url);
} catch (Exception e) {
System.out.println("Something went very wrong");
}
return image;
}
private Image plotAUTO42003Overlay() {
System.out.println("Using AUTO:42003 for overlay");
double[] centerLatLong = ViewUtil.getDegrees(getKmFromPixels(-getCenterX() + this.width / 2), getKmFromPixels(getCenterY() - this.height / 2));
Image image = null;
try {
// Read from a URL
URL url = new URL("http://wms.chill.colostate.edu/cgi-bin/mapserv?REQUEST=GetMap&VERSION=1.1.1&SRS=AUTO:42003,9001," + centerLatLong[0] + "," + centerLatLong[1] + "&SERVICE=WMS&map=/var/www/html/maps/test.map&BBOX=" + getKmFromPixels(-this.width / 2) * 1000 + "," + getKmFromPixels(-this.height / 2) * 1000 + "," + getKmFromPixels(this.width / 2) * 1000 + "," + getKmFromPixels(this.height / 2) * 1000 + "&WIDTH=" + (this.width) + "&HEIGHT=" + (this.height) + "&FORMAT=image/png;%20mode=24bit&LAYERS=" + msConfig.getUserMapOverlayLayers());
image = ImageIO.read(url);
} catch (Exception e) {
System.out.println("Something went very wrong");
}
return image;
}
@Override
public void plotMapServerOverlay(final Graphics g) {
//NeedToPlotMap = true;
if (msConfig.getUserMapOverlayLayers() == "") {
System.out.println("No layers to display");
return;
}
if (msConfig.AUTO42003IsEnabled() == true)
g.drawImage(plotAUTO42003Overlay(), 0, 0, null);
else if (msConfig.EPSG4326IsEnabled() == true)
g.drawImage(plotEPSG4326Overlay(), 0, 0, null);
else
System.out.println("No supported map projection method is available");
}
@Override
public void plotMap(final Graphics g) {
if (g == null) return;
if (vc.getMap() == null) return;
g.setColor(Color.WHITE);
Font oldFont = g.getFont();
g.setFont(oldFont.deriveFont(9f));
int xOffset = this.width / 2 + offsetX;
int yOffset = this.height / 2 + offsetY;
MapInstruction prevInstr = null;
int radius;
double[] oldkm = null;
for (MapInstruction instr : vc.getMap()) {
double[] km = ViewUtil.getKm(instr.getX(), instr.getY(), true);
if (prevInstr != null && prevInstr.getType() == Shape.CIRCLE) { //special case
radius = getPixelsFromKm(instr.getX()); //radius doesn't need adjustment
g.drawOval(getPixelsFromKm(oldkm[0] + this.centerX) - radius + xOffset,
getPixelsFromKm(0 - oldkm[1] + this.centerY) - radius + yOffset,
2 * radius,
2 * radius);
prevInstr = instr;
} else switch (instr.getType()) { //normal cases
case CIRCLE: //handled in special case above
case START_LINE: //handled by CONT_LINE
break;
case CONT_LINE:
if (prevInstr == null || oldkm == null) return;
g.drawLine(getPixelsFromKm(km[0] + this.centerX) + xOffset,
getPixelsFromKm(0 - km[1] + this.centerY) + yOffset,
getPixelsFromKm(oldkm[0] + this.centerX) + xOffset,
getPixelsFromKm(0 - oldkm[1] + this.centerY) + yOffset);
break;
case POINT:
radius = 3; //pixels
g.drawLine(getPixelsFromKm(km[0] + this.centerX) - radius + xOffset,
getPixelsFromKm(0 - km[1] + this.centerY) + yOffset,
getPixelsFromKm(km[0] + this.centerX) + radius + xOffset,
getPixelsFromKm(0 - km[1] + this.centerY) + yOffset);
g.drawLine(getPixelsFromKm(km[0] + this.centerX) + xOffset,
getPixelsFromKm(0 - km[1] + this.centerY) - radius + yOffset,
getPixelsFromKm(km[0] + this.centerX) + xOffset,
getPixelsFromKm(0 - km[1] + this.centerY) + radius + yOffset);
break;
default:
throw new Error("Don't know how to handle map shape " + instr.getType());
}
String comment = instr.getComment();
if (comment.length() > 0 && !comment.equals("!") && !comment.startsWith("#")) {
g.drawString(comment,
getPixelsFromKm(km[0] + this.centerX) + xOffset + 1,
getPixelsFromKm(0 - km[1] + this.centerY) + yOffset + 10);
}
oldkm = km;
prevInstr = instr;
}
g.setFont(oldFont);
}
@Override
public void plotClickPoint(final Graphics g) {
g.setColor(Color.WHITE);
double clickX = Math.sin(Math.toRadians(clickAz)) * clickRng;
double clickY = Math.cos(Math.toRadians(clickAz)) * clickRng;
int x = getPixelsFromKm(clickX) + this.width / 2 + offsetX;
int y = -getPixelsFromKm(clickY) + this.height / 2 + offsetY;
int radius = 10; //pixels
g.drawLine(x - radius, y - radius,
x + radius, y + radius);
g.drawLine(x + radius, y - radius,
x - radius, y + radius);
//Double the thickness of the lines
x = x + 1;
g.drawLine(x - radius, y - radius,
x + radius, y + radius);
g.drawLine(x + radius, y - radius,
x - radius, y + radius);
x = x + 1;
g.drawLine(x - radius, y - radius,
x + radius, y + radius);
g.drawLine(x + radius, y - radius,
x - radius, y + radius);
}
@Override
protected void plotAircraft(final ChillTrackInfo loc) {
addAircraftPoint(loc.ident, new Point(
getOriginX() + getPixelsFromKm(loc.xKm),
getOriginY() - getPixelsFromKm(loc.yKm)));
System.out.println(loc.toString());
}
@Override
protected void plotAircraft(final ChillOldExtTrackInfo coeti) {
addAircraftPoint(coeti.trackID, new Point(
getOriginX() + getPixelsFromKm(coeti.posX),
getOriginY() - getPixelsFromKm(coeti.posY)));
System.out.println(coeti.toString());
}
@Override
protected void plotAircraft(final ChillNewExtTrackInfo cneti) {
addAircraftPoint(cneti.trackID, new Point(
getOriginX() + getPixelsFromKm(cneti.posX),
getOriginY() - getPixelsFromKm(cneti.posY)));
System.out.println(cneti.toString());
}
@Override
protected double getStartAngle(final Ray currRay) {
return Math.toRadians(currRay.getStartAzimuth());
}
@Override
protected double getEndAngle(final Ray currRay) {
return Math.toRadians(currRay.getEndAzimuth());
}
@Override
protected int getX(final Angle angle, final double offset) {
return getCenterX() + (int) (offset * angle.getSin());
}
@Override
protected int getY(final Angle angle, final double offset, final int xPos) {
return getCenterY() - (int) (offset * angle.getCos());
}
@Override
protected boolean outOfRange(final int[] xVals, final int[] yVals) {
assert xVals.length == yVals.length;
double maxDistance = config.getPlotRange();
for (int i = 0; i < xVals.length; ++i) {
if (getRangeInKm(xVals[i], yVals[i]) > maxDistance) break;
return false;
}
return true;
}
@Override
protected boolean outOfRange(final double startAngle, final double endAngle) {
if (getCenterY() < 0) {
if ((startAngle < piOverTwo || startAngle > threePiOverTwo) &&
(endAngle < piOverTwo || endAngle > threePiOverTwo)) return true;
} else if (getCenterY() > this.height) {
if ((startAngle > piOverTwo && startAngle < threePiOverTwo) &&
(endAngle > piOverTwo && endAngle < threePiOverTwo)) return true;
}
if (getCenterX() < 0) {
if (startAngle > Math.PI && endAngle > Math.PI) return true;
} else if (getCenterX() > this.width) {
if (startAngle < Math.PI && endAngle < Math.PI) return true;
}
return false;
}
@Override
public int getOriginX() {
return getCenterX();
}
@Override
public int getOriginY() {
return getCenterY();
}
/**
* This method translates a kilometer distance into the logical number
* of pixels determined by the current zoom factor and pixel weight.
*
* @param distance The kilometer value to find a pixel distance for.
* @return The number of pixels for this km distance.
*/
@Override
protected int getPixelsFromKm(final double distance) {
return (int) ((Math.min(this.width, this.height) * distance * 1e3) / (config.getPlotRange() * 2048));
}
/**
* This method takes a distance in pixels, and translates it into a
* logical kilometer distance.
*
* @param numPixels The distance in pixels to be converted.
* @return The Kilometers that this pixel value represents.
*/
@Override
protected double getKmFromPixels(final int numPixels) {
return (config.getPlotRange() * numPixels * 2048) / (1e3 * Math.min(this.width, this.height));
}
@Override
public double getRangeInKm(final int x, final int y) {
double deltaX = x - getOriginX();
double deltaY = y - getOriginY();
double distX = deltaX * deltaX;
double distY = deltaY * deltaY;
return getKmFromPixels((int) Math.sqrt(distX + distY));
}
@Override
public double getKmEast(final int x, final int y) {
return getKmFromPixels(x - getOriginX());
}
@Override
public int getPixelsX(final double kmEast, final double kmNorth) {
return getPixelsFromKm(kmEast);
}
@Override
public double getKmNorth(final int x, final int y) {
return getKmFromPixels(getOriginY() - y);
}
@Override
public double getAzimuthDegrees(final int x, final int y) {
double n = -getKmNorth(x, y);
double e = getKmEast(x, y);
return Math.toDegrees(Math.atan(n / e)) + (e < 0 ? 270 : 90);
}
@Override
public double getElevationDegrees(final int x, final int y) {
double azimuth = getAzimuthDegrees(x, y);
Ray ray = vc.getRayAtAz(this.type, azimuth);
if (ray == null)
return this.radarElevation;
else
return ray.getStartElevation();
}
@Override
public int getPixelsY(final double kmEast, final double kmNorth) {
return getPixelsFromKm(kmNorth);
}
@Override
public double getElevationInKm(final int x, final int y) {
double range = getRangeInKm(x, y);
double elevation = Math.toRadians(getRadarElevation());
return (Math.tan(elevation) * range);
}
@Override
public double getElevationInKm(final double elevation, final double range) {
return ViewUtil.getKmElevation(elevation, range);
}
@Override
public String getPlotMode() {
return "PPI";
}
private double[] getNSEWDegrees() {
double n = ViewUtil.getDegrees(0, (0 - this.getCenterY() > 0 ? -1 : 1) *
this.getRangeInKm(this.getCenterX(), 0))[1];
double s = ViewUtil.getDegrees(0, (this.height - this.getCenterY() > 0 ? -1 : 1) *
this.getRangeInKm(this.getCenterX(), this.height))[1];
double e = ViewUtil.getDegrees((this.width - this.getCenterX() > 0 ? 1 : -1) *
this.getRangeInKm(this.width, this.getCenterY()), 0)[0];
double w = ViewUtil.getDegrees((0 - this.getCenterX() > 0 ? 1 : -1) *
this.getRangeInKm(0, this.getCenterY()), 0)[0];
return new double[]{n, s, e, w};
}
@Override
public boolean isExportable() {
return true;
}
@Override
public void export(final ZipOutputStream zip) throws IOException {
//write kml
zip.putNextEntry(new ZipEntry("vchillppi.kml"));
OutputStreamWriter out;
try {
out = new OutputStreamWriter(zip, "UTF-8");
} catch (UnsupportedEncodingException uee) {
out = new OutputStreamWriter(zip);
}
double[] nsew = this.getNSEWDegrees();
out.write("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" +
"<kml xmlns=\"http://earth.google.com/kml/2.1\">\n" +
"<Folder>\n" +
" <TimeSpan>\n" +
" <begin>" + df.format(this.getBeginDaT()) + "</begin>\n" +
" <end>" + df.format(this.getDateAndTime()) + "</end>\n" +
" </TimeSpan>\n" +
" <name>" + this.getRadarName() + " Radar</name>\n" +
" <Placemark>\n" +
" <description><![CDATA[Created with <a href=\"http://chill.colostate.edu/java/\">Java VCHILL</a>]]></description>\n" +
" <name>" + this.getRadarName() + " Radar Facility</name>\n" +
" <LookAt>\n" +
" <longitude>" + lm.getLongitude() + "</longitude>\n" +
" <latitude>" + lm.getLatitude() + "</latitude>\n" +
" <range>500.0</range>\n" +
" <tilt>0</tilt>\n" +
" <heading>3</heading>\n" +
" </LookAt>\n" +
" <Point>\n" +
" <coordinates>" + lm.getLongitude() + "," + lm.getLatitude() + ",0</coordinates>\n" +
" </Point>\n" +
" </Placemark>\n" +
" <ScreenOverlay>\n" +
" <name>Legend</name>\n" +
" <Icon>\n" +
" <href>vchillbar.png</href>\n" +
" </Icon>\n" +
" <overlayXY x=\"0\" y=\"1\" xunits=\"fraction\" yunits=\"fraction\"/>\n" +
" <screenXY x=\"0\" y=\"1\" xunits=\"fraction\" yunits=\"fraction\"/>\n" +
" </ScreenOverlay>\n" +
" <GroundOverlay>\n" +
" <name>Radar data</name>\n" +
" <color>9effffff</color>\n" +
" <drawOrder>1</drawOrder>\n" +
" <Icon>\n" +
" <href>vchillimg.png</href>\n" +
//" <viewBoundScale>0.75</viewBoundScale>\n" +
" </Icon>\n" +
" <LatLonBox>\n" +
" <north>" + nsew[0] + "</north>\n" +
" <south>" + nsew[1] + "</south>\n" +
" <east>" + nsew[2] + "</east>\n" +
" <west>" + nsew[3] + "</west>\n" +
" <rotation>0</rotation>\n" +
" </LatLonBox>\n" +
" </GroundOverlay> \n" +
"</Folder>\n" +
"</kml>\n");
out.flush();
zip.closeEntry();
}
}