// ********************************************************************** // // <copyright> // // BBN Technologies // 10 Moulton Street // Cambridge, MA 02138 // (617) 873-8000 // // Copyright (C) BBNT Solutions LLC. All rights reserved. // // </copyright> // ********************************************************************** // // $Source: /cvs/distapps/openmap/src/openmap/com/bbn/openmap/layer/terrain/ProfileGenerator.java,v $ // $RCSfile: ProfileGenerator.java,v $ // $Revision: 1.11 $ // $Date: 2009/01/21 01:24:42 $ // $Author: dietrick $ // // ********************************************************************** package com.bbn.openmap.layer.terrain; import java.awt.Color; import java.awt.FontMetrics; import java.awt.Point; import java.awt.event.ComponentListener; import java.awt.event.MouseEvent; import java.util.Vector; import com.bbn.openmap.dataAccess.dted.DTEDFrameCache; import com.bbn.openmap.image.AcmeGifFormatter; import com.bbn.openmap.omGraphics.OMGraphic; import com.bbn.openmap.omGraphics.OMGraphicList; import com.bbn.openmap.omGraphics.OMPoly; import com.bbn.openmap.proj.GeoProj; import com.bbn.openmap.proj.GreatCircle; import com.bbn.openmap.proj.Projection; import com.bbn.openmap.proj.coords.LatLonPoint; import com.bbn.openmap.util.Debug; import com.bbn.openmap.util.stateMachine.State; /** * This tool lets the user draw a line on the map, and then presents * the profile of the path in a GIF picture. The line can be drawn in * a series if clicks, or the mouse button can be held down as the * mouse is dragged around. The lines are drawn as great circle lines, * which represent the straight geographical line between clicks. * * <P> * The profile tool uses the ProfileStateMachine, and the Profile * States, to keep track of the proper actions and reactions of user * input. */ public class ProfileGenerator implements TerrainTool { /** The color of the line that is drawn on the screen. */ Color toolColor = new Color(255, 0, 0); /** The state machine for user gestures. */ protected ProfileStateMachine stateMachine; /** The layer that the tool is serving. */ protected TerrainLayer layer; /** The list of graphics to draw. Contains the drawn line. */ protected OMGraphicList graphics = new OMGraphicList(); /** * Array of LatLonPoints. The points are the clicked points, and * the points in between, on a great circle. Have to figure these * points out, and not rely only on the poly line points, because * we need to get the elevations for all the points for the * profile. */ public Vector<LatLonPoint> coords; /** * These are the raw x-y points of the gestures, for the great * circle line points, too. These are used to construct the * profile image. An array of java.awt.Points. */ public Vector<Point> xypoints; /** * The line drawn on the screen representing the profile line * path. */ public OMPoly profileLine; /** * General gesture tracking, Used to track the last place of * interest on the screen for the creation of hte profile. */ MouseEvent lastMouse; /** * A copy of the most current projection to use to update the * drawn line. */ Projection proj; public ProfileGenerator(TerrainLayer tLayer) { layer = tLayer; init(); } public synchronized OMGraphicList getGraphics() { profileLine.setLocation(setLLPoints(), OMGraphic.RADIANS); profileLine.generate(proj); return graphics; } /** * Create the line object, the state machine, and the vectors used * to keep track of the line being drawn. */ public void init() { lastMouse = null; coords = new Vector<LatLonPoint>(); xypoints = new Vector<Point>(); profileLine = new OMPoly(setLLPoints(), OMGraphic.RADIANS, OMGraphic.LINETYPE_GREATCIRCLE); profileLine.setLinePaint(toolColor); graphics.add(profileLine); // System.loadLibrary("com_bbn_openmap_terrain_ProfileGenerator"); stateMachine = new ProfileStateMachine(this); } /** * Clears the line from the screen, and resets the state machine. */ public void reset() { coords.removeAllElements(); xypoints.removeAllElements(); profileLine.setLocation(setLLPoints(), OMGraphic.RADIANS); stateMachine.reset(); layer.repaint(); lastMouse = null; } public void setScreenParameters(Projection p) { proj = p; graphics.generate(p); } /** * Returns a set of lat lon points that represent the line as it * was drawn. The lat lon points are in an array of floats, that * alternate, lat, lon, etc. */ public double[] setLLPoints() { double[] points; int num_points = coords.size(); if (num_points <= 1) { points = new double[4]; if (num_points == 0) { points[0] = 0f; points[1] = -6f; } else { points[0] = ((LatLonPoint) coords.elementAt(0)).getRadLat(); points[1] = ((LatLonPoint) coords.elementAt(0)).getRadLon(); } points[2] = points[0]; points[3] = points[1]; } else { points = new double[coords.size() * 2]; for (int i = 0; i < coords.size(); i++) { points[i * 2] = (float)((LatLonPoint) coords.elementAt(i)).getRadLat(); points[(i * 2) + 1] = (float)((LatLonPoint) coords.elementAt(i)).getRadLon(); } } return points; } /** * Returns the current state of the state machine. */ public State getState() { return stateMachine.getState(); } /** * Creates the line points for the path drawn on the screen, and * collects the elevation values for those points. Makes the call * to write the new gif file to disk. */ public void createProfileImage() { Debug.message("terrain", "ProfileGenerator:createProfileImage(): Creating image"); if (layer == null || layer.frameCache == null) { Debug.error("ProfileGenerator: can't access the DTED data through the terrain layer."); return; } // Set the final line, as it was drawn. profileLine.setLocation(setLLPoints(), OMGraphic.RADIANS); int total_distance = 0; int[] distances = new int[xypoints.size()]; Point tmpPoint1, tmpPoint2; distances[0] = 0; for (int j = 1; j < xypoints.size(); j++) { tmpPoint1 = (Point) xypoints.elementAt(j); tmpPoint2 = (Point) xypoints.elementAt(j - 1); // Needed for the GIF, the number of pixels (distance) // between points of the line. The distances array is the // distance between this point and the next in the xy // point array. distances[j] = TerrainLayer.numPixelsBetween(tmpPoint1.x, tmpPoint1.y, tmpPoint2.x, tmpPoint2.y); total_distance += distances[j]; } int tmp = 0; int max = 0; int[] heights = new int[xypoints.size()]; // Go through the points and get the heights for (int i = 0; i < heights.length; i++) { LatLonPoint llp = ((LatLonPoint) coords.elementAt(i)); // Ask the cache for the elevation tmp = layer.frameCache.getElevation(llp.getLatitude(), llp.getLongitude()); if (tmp == DTEDFrameCache.NO_DATA) tmp = -1; if (tmp > max) max = tmp; heights[i] = tmp; } // get the picture drawn and written createGIFFile(total_distance, max, distances, heights); } /** * Create the image and write it the location. * * @param distance total length of line, in pixels * @param max highest point, in meters of all the heights in the * line. * @param post_dist array of pixel distances between the points * @param post_height the array of heights */ protected void createGIFFile(int distance, int max, int[] post_dist, int[] post_height) { int box_height_buffer = 20; int gif_height_buffer = 20; int gif_width_buffer = 20; int text_width = 100; int box_height = max + (box_height_buffer * 2); int box_width = distance; int gif_height = box_height + (gif_height_buffer * 2); int gif_width = box_width + (gif_width_buffer * 2) + text_width; AcmeGifFormatter formatter = new AcmeGifFormatter(); java.awt.Graphics graphics = formatter.getGraphics(gif_width, gif_height); // Color gray10 = new Color(25, 25, 25); Color gray50 = new Color(128, 128, 128); // Color gray75 = new Color(191, 191, 191); Color gray90 = new Color(230, 230, 230); Debug.message("terrain", "ProfileGenerator gif creation: drawing boundaries"); /* Fill in the generic colors */ graphics.setColor(gray90); graphics.fillRect(0, 0, gif_width, gif_height); graphics.setColor(gray50); graphics.fillRect(gif_width_buffer, gif_height_buffer, box_width, box_height); Debug.message("terrain", "ProfileGenerator gif creation: drawing edges"); // outside edge graphics.setColor(Color.black); graphics.drawRect(0, 0, gif_width - 1, gif_height - 1); // inside edge graphics.drawRect(gif_width_buffer, gif_height_buffer, box_width, box_height); graphics.setColor(Color.yellow); // 0 height line graphics.drawLine(gif_width_buffer + 1, gif_height_buffer + box_height - box_height_buffer, gif_width_buffer + box_width - 1, gif_height_buffer + box_height - box_height_buffer); // These are the horizontal reference lines in the image. graphics.setColor(Color.black); FontMetrics f = graphics.getFontMetrics(); Debug.message("terrain", "ProfileGenerator gif creation: drawing level lines"); for (int i = 1; i < 9; i++) { graphics.drawLine(gif_width_buffer, gif_height_buffer + box_height - box_height_buffer - (max * i / 8), gif_width_buffer + box_width + 5, gif_height_buffer + box_height - box_height_buffer - (max * i / 8)); int meters = max * i / 8; int feet = (int) (meters * 3.2); String lineLabel = meters + "m / " + feet + "ft"; // byte[] lineLabelBytes = lineLabel.getBytes(); graphics.drawString(lineLabel, gif_width_buffer + box_width + 10, gif_height_buffer + box_height - box_height_buffer - (max * i / 8) + (f.getAscent() / 2)); } // int last_x = gif_width_buffer + 1; // int last_height = gif_height_buffer + box_height - box_height_buffer // - post_height[0]; int total_distance = 0; Debug.message("terrain", "ProfileGenerator gif creation: drawing profile"); graphics.setColor(Color.red); for (int i = 1; i < post_height.length; i++) { graphics.drawLine(gif_width_buffer + total_distance, gif_height_buffer + box_height - box_height_buffer - post_height[i - 1], gif_width_buffer + post_dist[i] + total_distance, gif_height_buffer + box_height - box_height_buffer - post_height[i]); total_distance += post_dist[i]; } javax.swing.ImageIcon ii = new javax.swing.ImageIcon(formatter.getBufferedImage()); javax.swing.JFrame jf = com.bbn.openmap.util.PaletteHelper.getPaletteWindow(new javax.swing.JLabel(ii), "Path Profile", (ComponentListener) null); jf.setVisible(true); // byte[] imageBytes = formatter.getImageBytes(); // String tmppath = null; // try { // String tmpDir = Environment.get(Environment.TmpDir); // if (tmpDir != null) { // tmppath = tmpDir + File.separator + "openmap-" + // Environment.timestamp() + ".gif"; // FileOutputStream fs = new FileOutputStream(tmppath); // fs.write(imageBytes); // fs.close(); // close the streams // String url = "file://" + tmppath; // layer.fireRequestURL(url); // } else { // Debug.error("ProfileGenerator: can't create image file, // because the openmap.TempDirectory was not set."); // } // } catch (IOException e) { // Debug.error("ProfileGenerator: Cannot write to temp file:" // + // Environment.get("line.separator") + // "\"" + tmppath + "\""); // } // String imageString = new String(imageBytes); // layer.fireRequestBrowserContent(imageString); } /** * Used to keep track of another point for the line, as determined * by the state machine. * * @param event Mouse event that supplies the location */ protected void addProfileEvent(MouseEvent event) { LatLonPoint llp = proj.inverse(event.getX(), event.getY(), new LatLonPoint.Double()); if (lastMouse != null) { // Check for proximity of the click, since a double // click means the end of the line. if ((Math.abs(lastMouse.getX() - event.getX()) > MAX_SPACE_BETWEEN_PIXELS) || (Math.abs(lastMouse.getY() - event.getY()) > MAX_SPACE_BETWEEN_PIXELS)) { // The line may need to be broken up into smaller // segments in order for it to be a true straight // line, to figure out the segments. The interior // points are added to the vector. addGreatCirclePoints(lastMouse, event); // Now add the end point to the vector coords.addElement(llp); // The xy points don't need the interior points, the // line gets these points and figures them out for // itself. This may be redundant is some way. xypoints.addElement(event.getPoint()); } } else { coords.addElement(llp); xypoints.addElement(event.getPoint()); } lastMouse = event; // Reset the line to have all the new points profileLine.setLocation(setLLPoints(), OMGraphic.RADIANS); profileLine.generate(proj); } /** * Figure out the internal points to create a great circle line * between two points on the screen. The interior points are added * to the coords array, but not to the xy points array. * * @param beginning the starting mouse event * @param ending the ending mouse event */ protected void addGreatCirclePoints(MouseEvent beginning, MouseEvent ending) { LatLonPoint beg = proj.inverse(beginning.getX(), beginning.getY(), new LatLonPoint.Double()); LatLonPoint end = proj.inverse(ending.getX(), ending.getY(), new LatLonPoint.Double()); int num_points = (TerrainLayer.numPixelsBetween(beginning.getX(), beginning.getY(), ending.getX(), ending.getY()) - 2) / MAX_SPACE_BETWEEN_PIXELS; float[] radPoints = GreatCircle.greatCircle((float)beg.getRadLat(), (float)beg.getRadLon(), (float)end.getRadLat(), (float)end.getRadLon(), num_points, true); boolean geoProj = proj instanceof GeoProj; for (int i = 0; i < radPoints.length; i++) { coords.addElement(new LatLonPoint.Double(radPoints[i], radPoints[i + 1], true)); Point pt = new Point(); if (geoProj) { ((GeoProj)proj).forward(radPoints[i], radPoints[i + 1], pt, true); } else { proj.forward(Math.toDegrees(radPoints[i]), Math.toDegrees(radPoints[i + 1]), pt); } xypoints.addElement(pt); // System.out.println("addCGPoints: point " + i + " lat=" // + // RadianPoint.radToDeg(radPoints[i].lat) + ", lon=" + // RadianPoint.radToDeg(radPoints[i].lon) + ", x=" + // (short)pt.x + ", y=" + (short)pt.y); i++; } } }