package edu.colostate.vchill.plot; import edu.colostate.vchill.*; 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.color.XMLControl; import edu.colostate.vchill.data.Ray; import edu.colostate.vchill.gui.MapServerConfig; import edu.colostate.vchill.gui.MapServerConfigWindow; import javax.imageio.ImageIO; import java.awt.*; import java.io.IOException; import java.net.URL; import java.text.NumberFormat; import java.text.SimpleDateFormat; import java.util.*; import java.util.List; import java.util.zip.ZipOutputStream; //Rausch /** * This is the super class for the different types of plotting modes. The * methods and fields are both common to the lower level plots. NOTE: Mouse * click distance finds must be moved to this level instead of the * ViewPlotWindow. * * @author Justin Carlson * @author Jochen Deyke * @author jpont * @version 2010-07-07 * @created April 25, 2003 */ public abstract class ViewPlotMethod { protected Boolean NeedToPlotMap = false; protected boolean Mappable = false; protected final static MapServerConfig msConfig = MapServerConfig.getInstance(); protected final static Config config = Config.getInstance(); protected final static ScaleManager sm = ScaleManager.getInstance(); protected final static ViewControl vc = ViewControl.getInstance(); protected static final LocationManager lm = LocationManager.getInstance(); private final static NumberFormat nf = config.getNumberFormat(); protected static final SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'"); /** * Color management; stores the current color map */ private final XMLControl colorControl = vc.getColorControl(); /** * The elevation that this data set was collected at */ protected double radarElevation = 0; /** * The azimuth that this data set was collected at */ protected double radarAzimuth = 0; /** * The distance in km to the first gate */ protected double startRange = 0; protected String type = null; protected long beginTime = -1; protected long currentTime = 0; protected int height; protected int width; protected int offsetX, offsetY; protected String radarId; /** * Number of meters in one gate. Default to 150, found from Ray data. */ protected double metersPerGate = 150; /** * Number of gates in one ray. Found from Ray data. */ protected int numGates = 0; /** * Location the user clicked on. Used to highlight that point. */ protected double clickAz, clickEl, clickRng; /** * stores Points of where aircraft were */ protected Map<String, LimitedList<Point>> aircraftInfo; protected void clearAircraftInfo() { this.aircraftInfo.clear(); } public ViewPlotMethod(final String type) { df.setTimeZone(TimeZone.getTimeZone("UTC")); this.type = type; this.aircraftInfo = new HashMap<String, LimitedList<Point>>(); } public void setType(final String type) { this.type = type; } public String getType() { return this.type; } public List<String> getAnnotationString() { return this.getAnnotationString(sm.getScale(this.type).isUnfoldable() && config.isUnfoldingEnabled() ? 2 : 1); } private List<String> getAnnotationString(double scaleMultiplier) { int maxnum = (int) (16.0 * this.height / 512.0); //number of entries in scale int num = getColors().size(); if (num > maxnum) { int i = 2; while (num / i > maxnum) ++i; num = num / (i - 1); } ArrayList<String> vals = new ArrayList<String>(num + 1); double max = sm.getScale(this.type).getMax() * scaleMultiplier; double min = sm.getScale(this.type).getMin() * scaleMultiplier; double step = (max - min) / (double) num; double value = max; for (int i = 0; i < num + 1; ++i) { vals.add(ViewUtil.format(value)); value -= step; } return Collections.unmodifiableList(vals); } public void setNewPlot() { this.beginTime = -1; } public double getRadarElevation() { return this.radarElevation; } public double getRadarAzimuth() { return this.radarAzimuth; } public double getStartRange() { return this.startRange; } /** * Gets the center of the current plot and grid. * * @return The x center of the grid that should be plotted in. */ public int getCenterX() { return this.width / 2 + this.offsetX; } /** * Get the center of the plot and grid * * @return The calculated center due to various offsets. */ public int getCenterY() { return this.height / 2 + this.offsetY; } /** * Gets the step at which to sample data points. This is also important * in determining the distance weight of each pixel and how often to * plot the grid intervals. * * @return How far to advance in the data (number of gates) per pixel */ public double getPlotStepSize() { return (2048 * config.getPlotRange()) / (metersPerGate * Math.min(this.height, this.width)); } /** * This is a method to mimic a JPanel in case this class will later * extend JPanel. It alters the height and width of the area to plot in * * @param width The new width of the plot * @param height The new height of the plot */ public void setPreferredSize(final int width, final int height) { this.width = width; this.height = height; } public void setPlottingOffset(final int xOffset, final int yOffset) { //System.out.println("offset changed to " + xOffset + ", " + yOffset); this.offsetX = xOffset; this.offsetY = yOffset; } /** * Fills the buffer or area refered to by the Graphics2D Object with a black sqaure. * * @param g The Graphics2D reference to what is to be cleared. */ public void clearScreen(final Graphics g) { if (g == null) return; g.setColor(Color.BLACK); g.fillRect(0, 0, this.width, this.height); } protected void plotAircraft(final ChillTrackInfo loc) { } protected void plotAircraft(final ChillOldExtTrackInfo coeti) { } protected void plotAircraft(final ChillNewExtTrackInfo cneti) { } protected abstract int getPixelsFromKm(final double distance); protected abstract double getKmFromPixels(final int distance); //angles (in radians) protected abstract double getStartAngle(final Ray rayCurr); protected abstract double getEndAngle(final Ray rayCurr); //coordinates protected abstract int getX(final Angle angle, final double offset); protected abstract int getY(final Angle angle, final double offset, final int xPos); protected boolean outOfRange(final int[] xVals, final int[] yVals) { return false; } protected boolean outOfRange(final double startAngle, final double endAngle) { return false; } /** * Caches the sin and cos for an angle (in radians) for quicker calculations */ static class Angle { private double angle = Double.NaN; //radians private double sin; private double cos; public double getAngle() { return this.angle; } public double getSin() { return this.sin; } public double getCos() { return this.cos; } /** * @param angle in radians */ public void setAngle(final double angle) { if (this.angle != angle) { this.angle = angle; this.sin = Math.sin(angle); this.cos = Math.cos(angle); } } } private Angle startAngle = new Angle(); private Angle endAngle = new Angle(); // Rausch private Image plotEPSG4326Underlay() { System.out.println("Using EPSG:4326 for underlay"); 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.getUserMapUnderlayLayers()); //URL url = new URL("http://sedac.ciesin.columbia.edu/geoserver/gwc/service/wms?REQUEST=GetMap&VERSION=1.1.1&SRS=epsg:4326&SERVICE=WMS&&BBOX=" + round(BBwest) + "," + round(BBsouth) + "," + round(BBeast) + "," + round(BBnorth) + "&WIDTH=" + (this.width) + "&HEIGHT=" + (this.height) + "&FORMAT=image/png;&LAYERS=" + msConfig.getUserMapUnderlayLayers()); image = ImageIO.read(url); } catch (Exception e) { System.out.println("Something went very wrong VIew Plot Method: Line 300:" + e); } return image; } private Image plotAUTO42003Underlay() { System.out.println("Using AUTO:42003 for underlay"); double[] centerLatLong = ViewUtil.getDegrees(getKmFromPixels(-getCenterX() + this.width / 2), getKmFromPixels(getCenterY() - this.height / 2)); // System.out.println("Lat and Lon is:"+ centerLatLong[1] + " " + centerLatLong[0]); 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.getUserMapUnderlayLayers()); // URL url = new URL("http://sedac.ciesin.columbia.edu/geoserver/gwc/service/wms?REQUEST=GetMap&VERSION=1.1.1&SRS=AUTO:42003,9001," + centerLatLong[0] + "," + centerLatLong[1] + "&SERVICE=WMS&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.getUserMapUnderlayLayers()); // System.err.println("URL is :"+url); image = ImageIO.read(url); } catch (Exception e) { System.out.println("Something went very wrong: ViewPlotMethod Line 327"); } return image; } public void plotMapServerUnderlay(Graphics g) { if (NeedToPlotMap == true) { NeedToPlotMap = false; } else { return; } if (msConfig.getUserMapUnderlayLayers() == "") { //System.out.println("No layers to display"); return; } NeedToPlotMap = false; if (msConfig.AUTO42003IsEnabled() == true) g.drawImage(plotAUTO42003Underlay(), 0, 0, null); else if (msConfig.EPSG4326IsEnabled() == true) g.drawImage(plotEPSG4326Underlay(), 0, 0, null); } /** * Translates a (set of) rays into sets of x, y endpoints and a color value and draws the result onto the specified Graphics * * @param prevRay The previous ray * @param currRay The data that is to be translated. * @param nextRay The next ray * @param threshRay The threshold ray corresponding to the current ray * @param g the Graphics context to plot to */ public void plotData(final Ray prevRay, final Ray currRay, final Ray nextRay, final Ray threshRay, final Graphics g) { if (currRay == null) throw new IllegalArgumentException("Error: PlotMethod.plotData(): null for data"); double[] values = currRay.getData(); radarId = currRay.getRadarId(); radarAzimuth = currRay.getStartAzimuth(); radarElevation = currRay.getStartElevation(); metersPerGate = currRay.getGateWidth() * 1e3; numGates = currRay.getNumberOfGates(); setTime(currRay.getDate()); //double start = getStartAngle(currRay); double end = getEndAngle(currRay); double start = prevRay == null ? getStartAngle(currRay) : getEndAngle(prevRay); if (Math.abs(start - end) > 0.25) start = getStartAngle(currRay); //radians //double end = nextRay == null ? getEndAngle(currRay) : getStartAngle(nextRay); if (outOfRange(start, end)) return; //do this here so the sin and cos won't be calculated on offscreen angles startAngle.setAngle(start); endAngle.setAngle(end); //This is the step size for the plotting, or how often we should sample data points. double plotStepSize = getPlotStepSize(); startRange = currRay.getStartRange() * 1e-6; //mm -> km double pixelOffset = 0; double gateOffset = startRange / (metersPerGate / 1000.0F); if (gateOffset < 0) { gateOffset = Math.abs(gateOffset); } else { gateOffset = 0; pixelOffset = getPixelsFromKm(startRange); //km -> px } double prevValue = Double.NaN; //While there is more data, and less data than can be handled, //continue doing translation of the radar data. Also, note that //the plotStepSize is related to the current zoom value and //represents how often to sample the data points. int[] xVals = new int[4]; int[] yVals = new int[4]; List<Color> colors = this.getColors(); ViewPlotDataFilter filter = new ViewPlotDataFilter(prevRay, currRay, nextRay, threshRay, this.type); for (double i = gateOffset; i < numGates; i += plotStepSize) { int k = (int) i; //The x,y coordinates of one endpoint of the line/gate data. xVals[0] = getX(startAngle, pixelOffset); yVals[0] = getY(startAngle, pixelOffset, xVals[0]); //The other endpoint of the line. xVals[1] = getX(endAngle, pixelOffset); yVals[1] = getY(endAngle, pixelOffset, xVals[1]); //optimization - draw consecutive same-colored blobs at once //smoothing causes same value to possibly != same color // if (!config.isSmoothingEnabled()) while (values[(int)i] == values[k]) { // if (i + plotStepSize >= numGates) break; // i += plotStepSize; // ++offset; // } ++pixelOffset; xVals[2] = getX(endAngle, pixelOffset); yVals[2] = getY(endAngle, pixelOffset, xVals[2]); xVals[3] = getX(startAngle, pixelOffset); yVals[3] = getY(startAngle, pixelOffset, xVals[3]); if (xVals[0] < 0 && xVals[1] < 0 && xVals[2] < 0 && xVals[3] < 0) continue; if (yVals[0] < 0 && yVals[1] < 0 && yVals[2] < 0 && yVals[3] < 0) continue; if (xVals[0] > this.width && xVals[1] > this.width && xVals[2] > this.width && xVals[3] > this.width) continue; if (yVals[0] > this.height && yVals[1] > this.height && yVals[2] > this.height && yVals[3] > this.height) continue; //if (outOfRange(xVals, yVals)) continue; //check for > max plot range //apply filters double filteredValue = filter.applyFilters(i, plotStepSize, getElevationInKm(avg(xVals), avg(yVals)), prevValue); Color colorValue = this.getColorValue(filteredValue, colors); prevValue = filteredValue; //Finally set that value in the data so that it can be assigned a color //(might make more sense to just assign a color now). // Rausch if (colorValue != Color.BLACK) { int alphaTransparency = MapServerConfigWindow.getOpacity(); colorValue = new Color(colorValue.getRed(), colorValue.getGreen(), colorValue.getBlue(), alphaTransparency); g.setColor(colorValue); g.drawPolygon(xVals, yVals, 4); g.fillPolygon(xVals, yVals, 4); } } //End for loop startAngle = endAngle; endAngle = new Angle(); } /** * The Method that is to be used to plot the grid for this specific data type. * * @param g A Graphics2D ref to what the grid should be plotted into. */ public abstract void plotGrid(Graphics g); public abstract void plotClickPoint(Graphics g); public void setClickPoint(final double azimuth, final double elevation, final double range) { this.clickAz = azimuth; this.clickEl = elevation; this.clickRng = range; } public double getClickAz() { return this.clickAz; } public double getClickEl() { return this.clickEl; } public double getClickRng() { return this.clickRng; } public void plotMap(Graphics g) { } public void plotMapServerOverlay(Graphics g) { } public abstract int getOriginX(); public abstract int getOriginY(); public abstract double getRangeInKm(int x, int y); public abstract String getPlotMode(); public abstract double getKmEast(int x, int y); public abstract double getKmNorth(int x, int y); public abstract double getElevationInKm(int x, int y); public abstract int getPixelsX(double kmEast, double kmNorth); public abstract int getPixelsY(double kmEast, double kmNorth); public abstract double getAzimuthDegrees(int x, int y); public abstract double getElevationDegrees(int x, int y); /** * Gets the desired ray number from the specified * x, y location. It returns -1 if it can't find the ray. */ public int getRayNumFromXY(int x, int y) { return -1; } /** * The maximum number of rays displayable. It returns * -1 if it can't determine the number of rays. */ public int getMaxDisplayableRays() { return -1; } /** * @param elevation elevation angle * @param range distance from the radar (in km) */ public double getElevationInKm(double elevation, double range) { return Math.tan(Math.toRadians(elevation)) * range; } public long getDateAndTime() { return this.currentTime; } public long getBeginDaT() { return this.beginTime; } public double getMetersPerGate() { return this.metersPerGate; } public int getNumberOfGates() { return this.numGates; } public String getRadarName() { return this.radarId; } /** * Converts a data value into a color * * @param val the value to convert */ protected Color getColorValue(final double val) { return this.getColorValue(val, this.getColors()); } /** * Converts a data value into a color * * @param val the value to convert * @param colors the current colortable */ protected Color getColorValue(final double val, final List<Color> colors) { if (Double.isNaN(val)) return Color.BLACK; //missing if (colors == null) return Color.BLACK; //no colortable? ChillMomentFieldScale scale = sm.getScale(this.type); double min = scale.getMin(); if (val < min) return scale.shouldLowerBoundBeClipped() ? Color.BLACK : colors.get(0); double max = scale.getMax(); if (val >= max) return colors.get(colors.size() - 1); return colors.get((int) ((val - min) / (max - min) * (colors.size()))); } protected List<Color> getColors() { return this.colorControl.getType(this.type); } protected void setTime(final long time) { if (this.beginTime < 0) this.beginTime = time; this.currentTime = time; } public Map<String, LimitedList<Point>> getAircraftInfo() { return this.aircraftInfo; } protected void addAircraftPoint(final String name, final Point loc) { LimitedList<Point> list = this.aircraftInfo.get(name); if (list == null) this.aircraftInfo.put(name, list = new LimitedList<Point>(20)); list.add(loc); } private int avg(final int[] nums) { int total = 0; for (int i = 0; i < nums.length; ++i) total += nums[i]; return total / nums.length; } public boolean isExportable() { return false; } /** * Write plot-type-specific information to zip stream for export */ public void export(final ZipOutputStream zip) throws IOException { } }