// **********************************************************************
//
// <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/dataAccess/dted/DTEDFrameCacheHandler.java,v $
// $RCSfile: DTEDFrameCacheHandler.java,v $
// $Revision: 1.6 $
// $Date: 2006/01/13 22:05:14 $
// $Author: dietrick $
//
// **********************************************************************
package com.bbn.openmap.dataAccess.dted;
import java.awt.BorderLayout;
import java.awt.CardLayout;
import java.awt.Component;
import java.awt.event.ItemEvent;
import java.awt.event.ItemListener;
import java.awt.geom.Point2D;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.ArrayList;
import java.util.List;
import java.util.Properties;
import java.util.Vector;
import javax.swing.JComboBox;
import javax.swing.JLabel;
import javax.swing.JPanel;
import com.bbn.openmap.PropertyConsumer;
import com.bbn.openmap.omGraphics.OMGraphic;
import com.bbn.openmap.omGraphics.OMGraphicList;
import com.bbn.openmap.omGraphics.OMGrid;
import com.bbn.openmap.omGraphics.grid.GeneratorLoader;
import com.bbn.openmap.omGraphics.grid.OMGridGenerator;
import com.bbn.openmap.omGraphics.grid.SinkGenerator;
import com.bbn.openmap.proj.Projection;
import com.bbn.openmap.util.ComponentFactory;
import com.bbn.openmap.util.Debug;
import com.bbn.openmap.util.PropUtils;
import com.bbn.openmap.util.cacheHandler.CacheHandler;
import com.bbn.openmap.util.cacheHandler.CacheObject;
/**
* The DTEDFrameCacheHandler is a cache for objects being rendered on the map as
* a result of reading in DTED data. It communicates with a DTEDFrameCache to
* get OMGrid data from the actual DTED data files, and then sets
* OMGridGenerators on those OMGrids to create representations of the DTED.
* <P>
*
* The DTEDFrameCacheHandler uses GeneratorLoaders to create OMGridGenerators
* for its OMGrids. The GeneratorLoaders provide a GUI for controlling those
* OMGridGenerator parameters. The list of GeneratorLoaders can be set via
* Properties. In general, properties being set for a DTEDFrameCacheHandler:
* <P>
*
* <pre>
*
*
* markerName.generators=greys colors
* markerName.greys.class=com.bbn.openmap.omGraphics.grid.SlopeGeneratorLoader
* markerName.greys.prettyName=Slope Shading
* markerName.greys.colorsClass=com.bbn.openmap.omGraphics.grid.GreyscaleSlopeColors
* markerName.colors.class=com.bbn.openmap.omGraphics.grid.SlopeGeneratorLoader
* markerName.colors.prettyName=Elevation Shading
* markerName.colors.colorsClass=com.bbn.openmap.omGraphics.grid.ColoredShadingColors
*
*
* </pre>
*
* The only properties that are required for the DTEDFrameCacheHandler are the
* generators property, and then the .class properties for the generator loader
* class names. All of the other generator loader properties are sent to the
* generator loader for interpretation.
* <p>
*
* The markerName is generally provided by the parent component of the
* DTEDFrameCacheHandler, like the DTEDFrameCacheLayer.
*/
public class DTEDFrameCacheHandler extends CacheHandler implements DTEDConstants, PropertyConsumer,
PropertyChangeListener {
public final static String GeneratorLoadersProperty = "generators";
/** The real frame cache. */
protected DTEDFrameCache frameCache;
// Setting up the screen...
protected double frameUp, frameDown, frameLeft, frameRight;
// Returning the images...
protected boolean firstImageReturned = true;
protected double frameLon = 0.0;
protected double frameLat = 0.0;
protected boolean newframe = false;
protected int dtedLevel = LEVEL_0;
/**
* The active GeneratorLoader providing OMGridGenerators to the OMGrids.
*/
protected GeneratorLoader activeGeneratorLoader = null;
/**
* The list of GeneratorLoaders.
*/
protected List<GeneratorLoader> generatorLoaders = new ArrayList<GeneratorLoader>();
/**
* The DTEDFrameCache must be set at some point.
*/
protected DTEDFrameCacheHandler() {
this(null);
}
/**
* Create a handler for the DTEDFrameCache.
*/
public DTEDFrameCacheHandler(DTEDFrameCache dfc) {
setFrameCache(dfc);
}
/**
* Set the DTEDFrameCache.
*/
public void setFrameCache(DTEDFrameCache dfc) {
frameCache = dfc;
resetCache();
}
/**
* Get the DTEDFrameCache.
*/
public DTEDFrameCache getFrameCache() {
return frameCache;
}
/**
* Get an elevation at a point. Always uses the cache to load the frame and
* get the data. DTED data is in meters.
*/
public int getElevation(float lat, float lon) {
if (frameCache != null) {
return frameCache.getElevation(lat, lon);
} else {
return DTEDFrameCache.NO_DATA;
}
}
/**
* Set the DTED level to get from the DTEDFrameCache.
*/
public void setDtedLevel(int level) {
dtedLevel = level;
}
/**
* Get the DTED level being retrieved from the DTEDFrameCache.
*/
public int getDtedLevel() {
return dtedLevel;
}
/**
* Set the active GeneratorLoader based on a pretty name from one of the
* loaders.
*/
public void setActiveGeneratorLoader(String active) {
for (GeneratorLoader gl : generatorLoaders) {
if (active.equals(gl.getPrettyName()) && gl != activeGeneratorLoader) {
activeGeneratorLoader = gl;
resetCache();
}
}
}
/**
* Get a new OMGridGenerator from the active GeneratorLoader.
*/
public OMGridGenerator getGenerator() {
if (activeGeneratorLoader != null) {
return activeGeneratorLoader.getGenerator();
} else if (generatorLoaders != null && generatorLoaders.size() > 0) {
activeGeneratorLoader = generatorLoaders.get(0);
return activeGeneratorLoader.getGenerator();
} else {
return new SinkGenerator();
}
}
/**
* GUI Panel holding the GeneratorLoader GUIs.
*/
final JPanel cards = new JPanel(new CardLayout());
/**
* Get the GUI for the GeneratorLoaders.
*/
public Component getGUI() {
JPanel pane = new JPanel(new BorderLayout());
int numLoaders = generatorLoaders.size();
String comboBoxItems[] = new String[numLoaders];
int count = 0;
for (GeneratorLoader gl : generatorLoaders) {
String prettyName = gl.getPrettyName();
comboBoxItems[count++] = prettyName;
Component glGui = gl.getGUI();
if (glGui == null) {
glGui = new JLabel("No options available.");
}
cards.add(glGui, prettyName);
}
JComboBox cb = new JComboBox(comboBoxItems);
cb.setEditable(false);
cb.addItemListener(new ItemListener() {
public void itemStateChanged(ItemEvent evt) {
CardLayout cl = (CardLayout) (cards.getLayout());
String active = (String) evt.getItem();
cl.show(cards, active);
setActiveGeneratorLoader(active);
}
});
// Put the JComboBox in a JPanel to get a nicer look.
JPanel comboBoxPane = new JPanel(); // use FlowLayout
comboBoxPane.add(cb);
pane.add(comboBoxPane, BorderLayout.NORTH);
pane.add(cards, BorderLayout.CENTER);
return pane;
}
/**
* The call to the cache that lets you choose what kind of information is
* returned. This function also figures out what part of the earth is
* covered on the screen, and creates auxillary cache handlers as needed.
*
* @param proj The projection of the screen (CADRG).
* @return List of rasters to display.
*/
public OMGraphicList getRectangle(Projection proj) {
double[] lat = new double[3];
double[] lon = new double[3];
// This next bit of mumbo jumbo is to handle the equator and
// dateline: Worst case, crossing both, treat each area
// separately, so it is the same as handling four requests for
// data - above and below the equator, and left and right of
// the dateline. Normal case, there is only one box. Two
// boxes if crossing only one of the boundaries.
int xa = 2;
int ya = 2;
int lat_minus = 2;
int lon_minus = 2;
// Set up checks for equator and dateline
Point2D ll1 = proj.getUpperLeft();
Point2D ll2 = proj.getLowerRight();
lat[0] = ll1.getY();
lon[0] = ll1.getX();
lat[1] = ll2.getY();
lon[1] = ll2.getX();
lat[2] = ll2.getY();
lon[2] = ll2.getX();
if (lon[0] > 0 && lon[2] < 0) {
lon[1] = -179.999; // put a little breather on the
// dateline
lon_minus = 1;
}
if (lat[0] > 0 && lat[2] < 0) {
lat[1] = -0.0001; // put a little breather on the
// equator
lat_minus = 1;
}
if (Debug.debugging("dteddetail")) {
Debug.output("For :");
Debug.output("lat[0] " + lat[0]);
Debug.output("lon[0] " + lon[0]);
Debug.output("lat[1] " + lat[1]);
Debug.output("lon[1] " + lon[1]);
Debug.output("lat[2] " + lat[2]);
Debug.output("lon[2] " + lon[2]);
Debug.output("lat_minus = " + lat_minus);
Debug.output("lon_minus = " + lon_minus);
}
/*
* Look at all the paths if needed. Worst case, there are four boxes on
* the screen. Best case, there is one. The things that create boxes and
* dictates how large they are are the equator and the dateline. When
* the screen straddles one or both of these lat/lon lines, lon_minus
* and lat_minus get adjusted, causing two or four different calls to
* the tochandler to get the data above/below the equator, and
* left/right of the dateline. Plus, each path gets checked until the
* required boxes are filled.
*/
if (Debug.debugging("dted")) {
Debug.output("--- DTEDFrameCacheHandler: getting images: ---");
}
setProjection(proj, lat[ya - lat_minus], lon[xa - lon_minus], lat[ya], lon[xa]);
OMGraphicList list = loadListFromHandler(null);
// Dateline split
if (lon_minus == 1) {
setProjection(proj, lat[ya - lat_minus], lon[0], lat[ya], -1f * lon[1]); // -1
// to
// make
// it
// 180
list = loadListFromHandler(list);
}
// Equator Split
if (lat_minus == 1) {
setProjection(proj, lat[0], lon[xa - lon_minus], -1f * lat[1], // flip
// breather
lon[xa]);
list = loadListFromHandler(list);
}
// Both!!
if (lon_minus == 1 && lat_minus == 1) {
setProjection(proj, lat[0], lon[0], -1f * lat[1],// flip
// breather
-1f * lon[1]);// -1 to make it 180, not -180
list = loadListFromHandler(list);
}
if (Debug.debugging("dted")) {
Debug.output("--- DTEDFrameCacheHandler: finished getting images ---");
}
return list;
}
/**
* Method that pings the cache for images based on the projection that has
* been set on it. If the cache returns null from getNextImage(), it's done.
* Method creates and returns a graphics list if the one passed in is null,
* otherwise it returns the one passed in.
*/
protected OMGraphicList loadListFromHandler(OMGraphicList graphics) {
if (graphics == null) {
graphics = new OMGraphicList();
}
OMGraphic image = getNextImage();
while (image != null) {
graphics.add(image);
image = getNextImage();
}
return graphics;
}
/**
* The method to call to let the cache handler know what the projection
* looks like so it can figure out which frames (and subframes) will be
* needed.
*
* @param proj the projection of the screen.
*/
public void setProjection(Projection proj) {
Point2D ul = proj.getUpperLeft();
Point2D lr = proj.getLowerRight();
setProjection(proj, ul.getY(), ul.getX(), lr.getY(), lr.getX());
}
/**
* The method to call to let the cache handler know what the projection
* looks like so it can figure out which frames (and subframes) will be
* needed. Should be called when the CacheHandler is dealing with just a
* part of the map, such as when the map covers the dateline or equator.
*
* @param proj the projection of the screen.
* @param lat1 latitude of the upper left corner of the window, in decimal
* degrees.
* @param lon1 longitude of the upper left corner of the window, in decimal
* degrees.
* @param lat2 latitude of the lower right corner of the window, in decimal
* degrees.
* @param lon2 longitude of the lower right corner of the window, in decimal
* degrees.
*/
public void setProjection(Projection proj, double lat1, double lon1, double lat2, double lon2) {
firstImageReturned = true;
// upper lat of top frame of the screen
// lower lat of bottom frame of the screen
// left lon of left frame of the screen
// upper lon of right frame of the screen
frameUp = Math.floor(lat1);
frameDown = Math.floor(lat2);
frameLeft = Math.floor(lon1);
frameRight = Math.ceil(lon2);
if (Debug.debugging("dted"))
Debug.output("frameUp = " + frameUp + ", frameDown = " + frameDown + ", frameLeft = "
+ frameLeft + ", frameRight = " + frameRight);
}
/**
* Returns the next OMGraphic image. When setProjection() is called, the
* cache sets the projection parameters it needs, and also resets this
* popping mechanism. When this mechanism is reset, you can keep calling
* this method to get another subframe image. When it returns a null value,
* it is done. It will automatically skip over window frames it doesn't
* have, and return the next one it does have. It traverses from the top
* left to right frames, and top to bottom for each column of frames. It
* handles all the subframes for a frame at one time.
*
* @return OMGraphic image.
*/
public OMGraphic getNextImage() {
if (Debug.debugging("dted"))
Debug.output("--- DTEDFrameCacheHandler: getNextImage:");
while (true) {
if (firstImageReturned == true) {
frameLon = frameLeft;
frameLat = frameDown;
newframe = true;
firstImageReturned = false;
} else if (frameLon < frameRight) {
// update statics to look for next frame
if (frameLat < frameUp) {
frameLat++;
} else {
frameLat = frameDown;
frameLon++;
}
newframe = true;
} else { // bounds exceeded, all done
return (OMGraphic) null;
}
if (newframe && frameLon < frameRight) {
if (Debug.debugging("dted")) {
Debug.output(" gni: Getting new frame Lat = " + frameLat + " Lon = " + frameLon);
}
OMGraphic omg = get(frameLat, frameLon, dtedLevel);
if (omg != null) {
return omg;
}
}
}
}
/**
* Return an OMGraphic for the Dted Frame, given A lat, lon and dted level.
*
* @param lat latitude of point
* @param lon longitude of point
* @param level the dted level wanted (0, 1, 2)
* @return OMGraphic, most likely an OMGrid.
*/
public OMGraphic get(double lat, double lon, int level) {
// First, put together a key from the above info, and then
// look for it in the local cache. If it's not there, then go
// to the DTEDFrameCache.
String key = new String(lat + ":" + lon + ":" + level);
CacheObject ret = searchCache(key);
if (ret != null) {
if (Debug.debugging("dted")) {
Debug.output("DTEDFrameCacheHandler.get(): retrieving frame from cache (" + lat
+ ":" + lon + ":" + level + ")");
}
return (OMGraphic) ret.obj;
}
ret = load(key, lat, lon, level);
if (ret == null) {
return null;
}
replaceLeastUsed(ret);
if (Debug.debugging("dted")) {
Debug.output("DTEDFrameCacheHandler.get(): loading new frame into cache (" + lat + ":"
+ lon + ":" + level + ")");
}
return (OMGraphic) ret.obj;
}
/**
* Load a dted frame into the cache, based on the path of the frame as a
* key. Implements abstract CacheHandler method.
*
* @param key key to remember raster created for DTED frame.
* @return DTED frame, hidden as a CacheObject.
*/
public CacheObject load(String key, double lat, double lon, int level) {
if (frameCache != null) {
DTEDFrame frame = frameCache.get(lat, lon, level);
if (frame != null) {
OMGrid omgrid = frame.getOMGrid();
// Need to create a unique generator for each OMGrid.
omgrid.setGenerator(getGenerator());
return new DTEDCacheObject(key, omgrid);
}
}
return null;
}
public CacheObject load(Object key) {
// Do nothing, because this implementation doesn't use it.
// The get() method has been overridden to c all the other
// load method with addition needed information to better call
// the DTEDFrameCache.
return null;
}
/**
* A private class that makes sure that cached frames get disposed properly.
*/
private static class DTEDCacheObject extends CacheObject {
/**
* Construct a DTEDCacheObject, just calls superclass constructor
*
* @param id passed to superclass
* @param obj passed to superclass
*/
public DTEDCacheObject(String id, OMGraphic omg) {
super(id, omg);
}
}
// //// PropertyConsumer Interface Methods
/**
* Token uniquely identifying this component in the application properties.
*/
protected String propertyPrefix = null;
/**
* Sets the properties for the OMComponent.
*
* @param props the <code>Properties</code> object.
*/
public void setProperties(java.util.Properties props) {
setProperties(getPropertyPrefix(), props);
}
/**
* Sets the properties for the OMComponent.
*
* @param prefix the token to prefix the property names
* @param props the <code>Properties</code> object
*/
public void setProperties(String prefix, java.util.Properties props) {
setPropertyPrefix(prefix);
String realPrefix = PropUtils.getScopedPropertyPrefix(prefix);
String generatorList = props.getProperty(realPrefix + GeneratorLoadersProperty);
if (generatorList != null) {
Vector<String> generatorMarkers = PropUtils.parseSpacedMarkers(generatorList);
for (String gmString : generatorMarkers) {
String loaderPrefix = realPrefix + gmString;
String loaderClassnameProperty = loaderPrefix + ".class";
String classname = props.getProperty(loaderClassnameProperty);
try {
GeneratorLoader loader = (GeneratorLoader) ComponentFactory.create(classname);
loader.setProperties(loaderPrefix, props);
generatorLoaders.add(loader);
loader.addPropertyChangeListener(this);
// Initialize
if (activeGeneratorLoader == null) {
activeGeneratorLoader = loader;
}
} catch (ClassCastException cce) {
Debug.output("DTEDFrameCacheHandler created " + classname
+ ", but it's not a GeneratorLoader");
} catch (NullPointerException npe) {
Debug.error("DTEDFrameCacheHandler: problem creating generator loader: "
+ classname + " from " + loaderClassnameProperty);
}
}
}
}
/**
* PropertyConsumer method, to fill in a Properties object, reflecting the
* current values of the OMComponent. If the component has a propertyPrefix
* set, the property keys should have that prefix plus a separating '.'
* prepended to each property key it uses for configuration.
*
* @param props a Properties object to load the PropertyConsumer properties
* into. If props equals null, then a new Properties object should be
* created.
* @return Properties object containing PropertyConsumer property values. If
* getList was not null, this should equal getList. Otherwise, it
* should be the Properties object created by the PropertyConsumer.
*/
public Properties getProperties(Properties props) {
if (props == null) {
props = new Properties();
}
String prefix = PropUtils.getScopedPropertyPrefix(this);
StringBuffer sb = new StringBuffer();
for (GeneratorLoader gl : generatorLoaders) {
String pref = gl.getPropertyPrefix();
props.put(pref + ".class", gl.getClass().getName());
gl.getProperties(props);
int index = pref.indexOf(prefix);
if (index != -1) {
pref = pref.substring(index + prefix.length());
}
sb.append(pref).append(" ");
}
props.put(prefix + GeneratorLoadersProperty, sb.toString());
return props;
}
/**
* Method to fill in a Properties object with values reflecting the
* properties able to be set on this PropertyConsumer. The key for each
* property should be the raw property name (without a prefix) with a value
* that is a String that describes what the property key represents, along
* with any other information about the property that would be helpful
* (range, default value, etc.). For Layer, this method should at least
* return the 'prettyName' property.
*
* @param list a Properties object to load the PropertyConsumer properties
* into. If getList equals null, then a new Properties object should
* be created.
* @return Properties object containing PropertyConsumer property values. If
* getList was not null, this should equal getList. Otherwise, it
* should be the Properties object created by the PropertyConsumer.
*/
public Properties getPropertyInfo(Properties list) {
if (list == null) {
list = new Properties();
}
// Not sure how to set up an inspector to create child classes
// yet.
return list;
}
/**
* Set the property key prefix that should be used by the PropertyConsumer.
* The prefix, along with a '.', should be prepended to the property keys
* known by the PropertyConsumer.
*
* @param prefix the prefix String.
*/
public void setPropertyPrefix(String prefix) {
propertyPrefix = prefix;
}
/**
* Get the property key prefix that is being used to prepend to the property
* keys for Properties lookups.
*
* @return the property prefix string
*/
public String getPropertyPrefix() {
return propertyPrefix;
}
/**
* The DTEDFrameCacheHandler needs to sign up as a PropertyChangeListener so
* if anything on the GeneratorLoader GUI changes, it knows to dump the
* current representations so they can be rebuild with the current GUI
* settings.
*/
public void propertyChange(PropertyChangeEvent pce) {
clear();
}
public List<GeneratorLoader> getGeneratorLoaders() {
if (generatorLoaders == null) {
generatorLoaders = new ArrayList<GeneratorLoader>();
}
return generatorLoaders;
}
public void setGeneratorLoaders(List<GeneratorLoader> generatorLoaders) {
this.generatorLoaders = generatorLoaders;
}
public void clearGeneratorLoaders() {
getGeneratorLoaders().clear();
}
public void addGeneratorLoader(GeneratorLoader gl) {
getGeneratorLoaders().add(gl);
}
public boolean removeGeneratorLoader(GeneratorLoader gl) {
return getGeneratorLoaders().remove(gl);
}
}