/* ********************************************************************** * * BBNT Solutions LLC, A part of GTE * 10 Moulton St. * Cambridge, MA 02138 * (617) 873-2000 * * Copyright (C) 1998-2000 * This software is subject to copyright protection under the laws of * the United States and other countries. * * ********************************************************************** * * $Source: /net/bitburg/u4/distapps/rcs/submissions/layerEnabledNotification/VPFLayer.java,v $ * $Revision: 1.1 $ * $Date: 2000/12/22 01:51:12 $ * $Author: dietrick $ * * ********************************************************************** */ package com.bbn.openmap.layer.vpf; import java.awt.*; import java.awt.event.*; import java.io.File; import java.io.InputStream; import java.io.IOException; import java.io.Serializable; import java.util.Hashtable; import java.util.StringTokenizer; import javax.swing.*; import com.bbn.openmap.LatLonPoint; import com.bbn.openmap.Layer; import com.bbn.openmap.event.*; import com.bbn.openmap.layer.util.DrawingAttributes; import com.bbn.openmap.layer.util.LayerUtils; import com.bbn.openmap.omGraphics.*; import com.bbn.openmap.proj.Projection; import com.bbn.openmap.util.Debug; import com.bbn.openmap.util.PaletteHelper; import com.bbn.openmap.util.SwingWorker; /** * Implement an OpenMap Layer for display of NIMA data sources in the * VPF (Mil-Std 2407) format.

* The properties needed to configure this layer to display VPF data include * some "magic" strings specific to the VPF database you are trying to * display. {@link com.bbn.openmap.layer.vpf.DescribeDB DescribeDB} is * a utility to help you figure out what those strings are. *

 *#-----------------------------
 *# Properties for a VMAP political layer
 *#-----------------------------
 *# Mandatory properties
 *# Mandatory for all layers
 *vmapPol.class= com.bbn.openmap.layer.vpf.VPFLayer
 *vmapPol.prettyName= Political Boundaries from VMAP
 *#Mandatory for VPF - a ';' separated list of paths, each of these
 *#directories should have a "lat" or "lat." file in them.
 *vmapPol.vpfPath= e:/VMAPLV0
 *#
 *# Choose either .defaultLayer or .coverageType
 *#
 *# .defaultLayer results in the layer looking up the remainder of the
 *# properties in the defaultVPFLayers.properties files.
 *vmapPol.defaultLayer= vmapPolitical
 *#
 *# .coverageType continues in this property file - chose the VMAP bnd
 *# (Boundary) coverage
 *vmapPol.coverageType= bnd
 *# Select if we want edges (polylines), areas (filled polygons) or text
 *# This is a space-separated list of "edge" "area" and "text"
 *vmapPol.featureTypes= edge area
 *#For DCW, the remaining 3 properties are ignored
 *#Select the text featureclasses we'd like to display.  Since we didn't
 *#select text above, this is ignored
 *vmapPol.text= 
 *#Select the edge featureclasses we'd like to display. In this case,
 *#draw political boundaries and coastline, but skip anything else.
 *vmapPol.edge=polbndl coastl
 *#Select the area featureclasses we'd like to display. In this case,
 *#draw politcal areas, but skip anything else.
 *vmapPol.area=polbnda
 *#Selectable drawing attributes - for default values, they don't need to
 *#be included.  A hex ARGB color looks like FF000000 for black.
 *vmapPol.lineColor=hex ARGB color value - Black is default.
 *vmapPol.fillColor=hex ARGB color value - Clear is default.
 *vmapPol.lineWidth=float value, 1f is the default, 10f is the max.
//RMS>>
 * # Minumum scale to display images. Larger numbers mean smaller scale, 
 * # and are more zoomed out.
 *vmapPol.min.scale=20000000
//RMS<<
 * 
 *#------------------------------------
 *# End of properties for a VMAP political layer
 *#------------------------------------
 *
 *### Now a VMAP Coastline layer
 *vmapCoast.class=com.bbn.openmap.layer.vpf.VPFLayer
 *vmapCoast.prettyName=VMAP Coastline Layer
 *vmapCoast.vpfPath=/u5/vmap/vmaplv0
 *## a predefined layer from the VPF predefined layer set found in
 *## com/bbn/openmap/layer/vpf/defaultVPFLayers.properties
 *vmapCoast.defaultLayer=vmapCoastline
 *
 *### Now a DCW Politcal layer
 *# Basic political boundaries with DCW
 *dcwPolitical.class=com.bbn.openmap.leyer.vpf.VPFLayer
 *dcwPolitical.prettyName=DCW Political Boundaries
 *dcwPolitical.vpfPath=path to data
 *dcwPolitical.coverageType=po
 *dcwPolitical.featureTypes=edge area
 * 
*/ public class VPFLayer extends Layer implements ProjectionListener, ActionListener, ComponentListener, Serializable { /** property extension used to set the VPF root directory */ public static final String pathProperty = ".vpfPath"; /** property extension used to set the desired coverage type. e.g. po, hyd */ public static final String coverageTypeProperty = ".coverageType"; /** property extension used to set the desired feature types. * e.g. line area text */ public static final String featureTypesProperty = ".featureTypes"; /** property extension used to specify a default property set */ public static final String defaultLayerProperty = ".defaultLayer"; //RMS>> /** Property extension used to specify a minimum scale to display the data contained in this layer. The data is displayed if the current projections scale is equal to or less than the minScale for this layer. */ public static final String minScaleProperty = "min.scale"; protected long minScale = 20000000; //RMS<< /** command for the palette redraw button. */ public static final String redrawCommand = "redraw"; /** the object that knows all the nitty-gritty vpf stuff */ transient LibrarySelectionTable lst; /** some graphics */ transient OMGraphicList graphics; /** some more graphics */ transient OMGraphicList omglist; /** our own little graphics factory */ private transient LayerGraphicWarehouseSupport warehouse; /** the worker thread we spin off to collect the data */ private transient DcwWorker currentWorker = null; /** used to communicate with our worker thread*/ protected transient boolean cancelled = false; /** * If you need it, use the accessor! * @see #setProjection * @see #getProjection */ private transient Projection projection; /** the path to the root VPF directory */ protected String[] dataPaths = null; /** the coverage type that we display */ protected String coverageType = "po"; /** * Construct a VPF layer. */ public VPFLayer () { addComponentListener(this); } /** * Construct a VPFLayer, and sets its name. * @param name the name of the layer. */ public VPFLayer (String name) { this(); setName(name); } /** * Sets the arguments.

* * @param args String arguments. * * This overrides the same call in Layer. We parse the argument list * looking for: DCWType DCWPath Area/Edges

* */ public void setArgs(String args) { // @HACK: not actually dynamic since they're coming from the OT. dynamicArgs = args; StringTokenizer t = new StringTokenizer(dynamicArgs); if (!t.hasMoreTokens()) return; setDataTypes(t.nextToken()); if (!t.hasMoreTokens()) return; setPath(t.nextToken()); if (!t.hasMoreTokens()) return; setAreasEnabled(t.nextToken().equalsIgnoreCase(VPFUtil.Area)); } /** * Sets the arguments.

* * @param args String arguments. * * This overrides the same call in Layer. We parse the argument list * looking for: DCWType DCWPath Area/Edges

* */ public void setArgs(String[] argv) { Debug.output("VPFLayer.setArgs(argv)"); Debug.output("Setting path to: " + argv[0]); setPath(argv[0]); } /** * Sets the features (lines, areas, text, points) that get displayed. * @param features a whitespace-separated list of features to display. */ public void setFeatures(String features) { warehouse.setFeatures(features); } /** * Another way to set the parameters of the DcwLayer. * @see #pathProperty * @see #coverageTypeProperty * @see #featureTypesProperty */ public void setProperties(String prefix, java.util.Properties props) { super.setProperties(prefix, props); String path[] = LayerUtils.initPathsFromProperties(props, prefix + pathProperty); if (path != null) { setPath(path); } //RMS>> Code moved from DTEDLayer to set minScale String realPrefix; if (prefix != null){ realPrefix = prefix + "."; } else { realPrefix = ""; } minScale = (long) LayerUtils.intFromProperties(props, realPrefix + minScaleProperty, 20000000); if (minScale < 100){ minScale = 20000000; Debug.error("VPFLayer: min.scale unreasonable, setting to 20M"); } //RMS<< String defaultProperty = props.getProperty(prefix + defaultLayerProperty); if (defaultProperty != null) { prefix = defaultProperty; props = getDefaultProperties(); } String coverage = props.getProperty(prefix + coverageTypeProperty); if (coverage != null) { setDataTypes(coverage); } try { initLST(); if (lst.getDatabaseName().equals("DCW")) { warehouse = new VPFLayerDCWWarehouse(); } else { warehouse = new VPFLayerGraphicWarehouse(); } warehouse.setProperties(prefix, props); } catch (IllegalArgumentException iae){ Debug.error("VPFLayer.setProperties: Illegal Argument Exception.\n\nPerhaps a file not found. Check to make sure that the paths to the VPF data directories are the parents of \"lat\" or \"lat.\" files. \n\n" + iae); } } /** Where we store our default properties once we've loaded them. */ private static java.util.Properties defaultProps; /** * Return our default properties for vpf land. */ public static java.util.Properties getDefaultProperties() { if (defaultProps == null) { try { InputStream in = VPFLayer.class.getResourceAsStream("defaultVPFlayers.properties"); //use a temporary so other threads won't see an //empty properties file java.util.Properties tmp = new java.util.Properties(); if (in != null) { tmp.load(in); in.close(); } else { Debug.error("VPFLayer: can't load default properties file"); //just use an empty properties file } defaultProps = tmp; } catch (IOException io) { Debug.error("VPFLayer: can't load default properties: " + io); defaultProps = new java.util.Properties(); } } return defaultProps; } /** * Set the projection we're using. * @param p the projection. */ protected synchronized void setProjection(Projection p) { projection = p; } /** * Get the current projection. */ protected synchronized Projection getProjection() { return projection; } /** * Set the data path to a single place. */ public void setPath(String newPath) { dataPaths = new String[]{newPath}; } /** * Set the data path to multiple places. */ public void setPath(String[] newPaths) { dataPaths = newPaths; } /** * Returns the list of paths we use to look for data. * @return the list of paths. Don't modify the array! */ public String[] getPath() { return dataPaths; } /** * Set the coveragetype of the layer. * @param dataTypes the coveragetype to display. */ public void setDataTypes (String dataTypes) { coverageType = dataTypes; } /** * Get the current coverage type. * @return the current coverage type. */ public String getDataTypes () { return coverageType; } /** * Enable/Disable the display of areas. */ public void setAreasEnabled (boolean value) { warehouse.setAreaFeatures(value); } /** * Find out if areas are enabled. */ public boolean getAreasEnabled () { return warehouse.drawAreaFeatures(); } /** * Enable/Disable the display of edges. */ public void setEdgesEnabled (boolean value) { warehouse.setEdgeFeatures(value); } /** * Find out if edges are enabled. */ public boolean getEdgesEnabled () { return warehouse.drawEdgeFeatures(); } /** * Enable/Disable the display of text. */ public void setTextEnabled (boolean value) { warehouse.setTextFeatures(value); } /** * Find out if text is enabled. */ public boolean getTextEnabled () { return warehouse.drawTextFeatures(); } /** * Get the DrawingAttributes used for the coverage type. */ public DrawingAttributes getDrawingAttributes(){ return warehouse.getDrawingAttributes(); } /** * Set the drawing attributes for the coverage type. */ public void setDrawingAttributes(DrawingAttributes da){ warehouse.setDrawingAttributes(da); } /** * Should only be called by Swing to render the layer. */ public void paint (Graphics g) { if (graphics == null) return; graphics.render(g); } /** * Start fetching graphics to render. This will invoke a * swing repaint when its completed. */ protected synchronized void computeLayer() { if (getProjection() == null) return; //RMS>> // Only display this layers data when zoomed in more than // the minimum scale set in the properties file if ( getProjection().getScale() > minScale ) { // Grey out the Layers menu item fireEnabledUpdate(LayerEnabledEvent.LAYER_DISABLED); return; } fireEnabledUpdate(LayerEnabledEvent.LAYER_ENABLED); //RMS<< if (isVisible()) { if (getOMGraphics() != null) { // If we already have graphics, just slap 'em up there. repaint(); } else if (currentWorker == null) { // No graphics. If we're not already computing graphics, // then compute new graphics. currentWorker = new DcwWorker(); Debug.message("vpfdetail", "VPFLayer.computeLayer: Executing..."); currentWorker.execute(); Debug.message("vpfdetail", "VPFLayer.computeLayer: Done executing."); } else { // We're already computing. Set the cancel flag and // return. The DcwWorker management code will fire // up a new computation when the current worker stops. cancelled = true; } } } /** * Implementing the ProjectionPainter interface. */ public synchronized void renderDataForProjection(Projection proj, java.awt.Graphics g){ if (proj == null){ Debug.error("VPFLayer.renderDataForProjection: null projection!"); return; } else if (!proj.equals(projection)){ setProjection(proj.makeClone()); setOMGraphics(getRectangle()); } paint(g); } /** * Handle a new projection. */ public void projectionChanged (ProjectionEvent e) { if (Debug.debugging("basic")) { Debug.output(getName()+"|DcwLayer: projectionChanged()"); } Projection oldProjection = getProjection(); Projection newProjection = e.getProjection(); // newProjection.equals(null) ==> false // so this next call is safe even if oldProjection is null if (newProjection.equals(oldProjection)) { repaint(); return; } // Clear the layer setOMGraphics(null); // Install the new projection setProjection((Projection) newProjection.makeClone()); // Create new graphics computeLayer(); } /** * Get the graphics from the last fetch. */ protected synchronized OMGraphicList getOMGraphics () { return graphics; } /** * Set the omgraphiclist to get returned. */ protected synchronized void setOMGraphics (OMGraphicList aGraphicList) { graphics = aGraphicList; } /** * Called by the worker thread when it was done. If the current * worker was cancelled, a new one gets started. */ protected synchronized void workerComplete (DcwWorker worker) { if (!cancelled) { currentWorker = null; setOMGraphics((OMGraphicList)worker.get()); repaint(); } else{ cancelled = false; currentWorker = new DcwWorker(); currentWorker.execute(); } } /** * A thread class used to fetch VPF data, so that the GUI * doesn't lock while all the work is done. */ class DcwWorker extends SwingWorker { public DcwWorker () { super(); } /** * Compute the value to be returned by the get method. */ public Object construct() { fireStatusUpdate(LayerStatusEvent.START_WORKING); try { return getRectangle(); } catch (OutOfMemoryError e) { graphics = null; omglist.clear(); String msg = getName()+"|VPFLayer.DcwWorker.construct(): " + e; System.err.println(msg); e.printStackTrace(); fireRequestMessage( new InfoDisplayEvent(this, msg)); fireStatusUpdate(LayerStatusEvent.FINISH_WORKING); return null; } } /** * Called on the event dispatching thread (not on the worker thread) * after the construct method has returned. */ public void finished() { fireStatusUpdate(LayerStatusEvent.FINISH_WORKING); workerComplete(this); } } /** * initialize the library selection table. */ private void initLST() { try { if (lst == null) lst = new LibrarySelectionTable(dataPaths); } catch (com.bbn.openmap.util.FormatException f) { throw new java.lang.IllegalArgumentException(f.getMessage()); } } public OMGraphicList getRectangle() { if (lst == null) { try { initLST(); } catch (IllegalArgumentException iae){ Debug.error("VPFLayer.getRectangle: Illegal Argument Exception.\n\nPerhaps a file not found. Check to make sure that the paths to the VPF data directories are the parents of \"lat\" or \"lat.\" files. \n\n" + iae); return null; } } if (warehouse == null){ Debug.error("VPFLayer.getRectangle: warehouse not initialized - should be OK on restart..."); return null; } Projection p = getProjection(); LatLonPoint upperleft = p.getUpperLeft(); LatLonPoint lowerright = p.getLowerRight(); if (Debug.debugging("vpfdetail")){ Debug.output("VPFLayer.getRectangle: " + coverageType + " " + dynamicArgs); } omglist = null; // free up previous list for gc warehouse.clear(); // int edgecount[] = new int[] { 0 , 0 }; // int textcount[] = new int[] { 0 , 0 }; // int areacount[] = new int[] { 0 , 0 }; // Check both dynamic args and palette values when // deciding what to draw. if (Debug.debugging("vpf")) { Debug.output("VPFLayer.getRectangle(): " + "calling drawTile with boundaries: " + upperleft + " " + lowerright); } long start = System.currentTimeMillis(); lst.drawTile((int)p.getScale(), p.getWidth(), p.getHeight(), coverageType, warehouse, upperleft, lowerright); long stop = System.currentTimeMillis(); // if (Debug.debugging("vpfdetail")) { // Debug.output("Returned " + edgecount[0] + // " polys with " + edgecount[1] + " points\n" + // "Returned " + textcount[0] + // " texts with " + textcount[1] + " points\n" + // "Returned " + areacount[0] + // " areas with " + areacount[1] + " points"); // } if (Debug.debugging("vpf")) { Debug.output("VPFLayer.getRectangle(): read time: " + ((stop-start)/1000d) + " seconds"); } omglist = warehouse.getGraphics(); // Don't forget to project. start = System.currentTimeMillis(); omglist.project(p); stop = System.currentTimeMillis(); if (Debug.debugging("vpf")) { Debug.output("VPFLayer.getRectangle(): proj time: " + ((stop-start)/1000d) + " seconds"); } return omglist; } private transient Box box; /** * Gets the palette associated with the layer. *

* @return Component or null */ public Component getGUI() { if (box == null) { box = Box.createVerticalBox(); JPanel pal = null; ActionListener al = new ActionListener() { public void actionPerformed(ActionEvent e) { int index = Integer.parseInt( e.getActionCommand(), 10); switch (index) { case 0: warehouse.setEdgeFeatures(!warehouse.drawEdgeFeatures()); break; case 1: warehouse.setTextFeatures(!warehouse.drawTextFeatures()); break; case 2: warehouse.setAreaFeatures(!warehouse.drawAreaFeatures()); break; default: throw new RuntimeException("argh!"); } } }; pal = PaletteHelper.createCheckbox( getName(), new String[] { VPFUtil.Edges, VPFUtil.Text, VPFUtil.Area }, new boolean[] { warehouse.drawEdgeFeatures(), warehouse.drawTextFeatures(), warehouse.drawAreaFeatures() }, al ); box.add(pal); JPanel pal2 = new JPanel(); JButton redraw = new JButton("Redraw Layer"); redraw.setActionCommand(redrawCommand); redraw.addActionListener(this); pal2.add(redraw); box.add(pal2); } return box; } public void actionPerformed (ActionEvent e) { String cmd = e.getActionCommand(); if (cmd == redrawCommand) { setOMGraphics(null); computeLayer(); } } //---------------------------------------------------------------------- // Component Listener implementation //---------------------------------------------------------------------- /** * Empty. */ public void componentResized(ComponentEvent e) {} /** * Empty. */ public void componentMoved(ComponentEvent e) {} /** * When component gets shown, compute the graticule. * * @param e a component event */ public void componentShown(ComponentEvent e) { computeLayer(); } /** * Empty. */ public void componentHidden(ComponentEvent e) {} }