// **********************************************************************
//
// <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/vpf/LayerGraphicWarehouseSupport.java,v $
// $Revision: 1.13 $ $Date: 2009/01/21 01:24:41 $ $Author: dietrick $
// **********************************************************************
package com.bbn.openmap.layer.vpf;
import java.awt.Component;
import java.util.List;
import java.util.Properties;
import java.util.StringTokenizer;
import java.util.logging.Logger;
import com.bbn.openmap.omGraphics.DrawingAttributes;
import com.bbn.openmap.omGraphics.OMGraphic;
import com.bbn.openmap.omGraphics.OMGraphicList;
import com.bbn.openmap.omGraphics.OMPoint;
import com.bbn.openmap.omGraphics.OMPoly;
import com.bbn.openmap.omGraphics.OMText;
import com.bbn.openmap.proj.ProjMath;
import com.bbn.openmap.proj.coords.LatLonPoint;
import com.bbn.openmap.util.Debug;
import com.bbn.openmap.util.FanCompress;
import com.bbn.openmap.util.PropUtils;
/**
* Implement a graphic factory that builds OMGraphics.
*
* @see com.bbn.openmap.omGraphics.OMGraphic
*/
public abstract class LayerGraphicWarehouseSupport
implements VPFGraphicWarehouse {
public static Logger logger = Logger.getLogger("com.bbn.openmap.layer.vpf.VPFGraphicWarehouse");
protected DrawingAttributes drawingAttributes;
/** HACK around antarctica display problem. */
final transient protected static float antarcticaThreshold = ProjMath.degToRad(-89.9f);
/** hang on to the graphics that we build */
protected OMGraphicList graphics;
/** remember if we draw edge features */
private boolean drawEdgeFeatures;
/** remember if we draw text features */
private boolean drawTextFeatures;
/** remember if we draw area features */
private boolean drawAreaFeatures;
/** remember if we draw entity point features */
private boolean drawEPointFeatures;
/** remember if we draw connected point features */
private boolean drawCPointFeatures;
/**
* thinning variables. note that thinning is meant to be done offline, so
* this is not optimized...
*/
private static boolean doThinning = false;
private static double fan_eps = 0.01f;
/**
* Construct an object, initializes graphiclist
*/
public LayerGraphicWarehouseSupport() {
initDrawingAttributes();
graphics = new OMGraphicList();
graphics.setTraverseMode(OMGraphicList.LAST_ADDED_ON_TOP);
}
/**
* Called from super class constructor.
*
*/
protected void initDrawingAttributes() {
drawingAttributes = new DrawingAttributes();
}
/**
* Get the current graphics list.
*
* @return the OMGraphicList.
*/
public synchronized OMGraphicList getGraphics() {
return getGraphics(graphics);
}
/**
* Add the area, edge, text and point sublists to the provided list.
*/
protected synchronized OMGraphicList getGraphics(OMGraphicList addToList) {
if (areaSubList != null) {
addToList.add(areaSubList);
}
if (edgeSubList != null) {
addToList.add(edgeSubList);
}
if (pointSubList != null) {
addToList.add(pointSubList);
}
if (textSubList != null) {
addToList.add(textSubList);
}
return addToList;
}
/**
* Get the DrawingAttributes used for the coverage type.
*/
public DrawingAttributes getDrawingAttributes() {
return drawingAttributes;
}
/**
* Set the drawing attributes for the coverage type.
*/
public void setDrawingAttributes(DrawingAttributes da) {
drawingAttributes = da;
}
/**
* Lets the warehouse know that a different CoverageAttributeTable will be
* using it. Default action is to do nothing.
*/
public void resetForCAT() {
}
/**
* Set which library to use. If null, all applicable libraries in database
* will be searched.
*/
private List<String> useLibrary = null;
/**
* Set the VPF library names to use. If null, all libraries will be searched.
* Null is default.
*/
public void setUseLibraries(List<String> lib) {
useLibrary = lib;
}
/**
* Get the VPF library names to use.
*/
public List<String> getUseLibraries() {
return useLibrary;
}
/**
* Utility method to check if the specified library name has been set by the
* configuration as one to use.
*
* @param libName the library name to test
* @return true if the useLibrary list has not been set, is empty, or if the
* provided name starts with a useList entry on it (good for sets of
* libraries).
*/
public boolean checkLibraryForUsage(String libName) {
boolean useLibrary = true;
List<String> libraryNames = getUseLibraries();
if (libraryNames != null && !libraryNames.isEmpty()) {
useLibrary = false;
for (String libraryName : libraryNames) {
if (libName.startsWith(libraryName)) {
useLibrary = true;
break;
}
}
}
return useLibrary;
}
/**
* Return the GUI for certain warehouse attributes. By default, return the
* GUI for the DrawingAttributes object being used for rendering attributes
* of the graphics.
*
* @param lst LibrarySelectionTable to use to get information about the data,
* if needed. Not needed here.
*/
public Component getGUI(LibrarySelectionTable lst) {
if (drawingAttributes != null) {
return drawingAttributes.getGUI();
} else {
return null;
}
}
protected OMGraphicList areaSubList;
protected OMGraphicList edgeSubList;
protected OMGraphicList textSubList;
protected OMGraphicList pointSubList;
/**
* Clears the contained list of graphics.
*/
public void clear() {
graphics.clear();
if (areaSubList != null) {
areaSubList.clear();
areaSubList = null;
}
if (edgeSubList != null) {
edgeSubList.clear();
edgeSubList = null;
}
if (textSubList != null) {
textSubList.clear();
textSubList = null;
}
if (pointSubList != null) {
pointSubList.clear();
pointSubList = null;
}
}
protected void addArea(OMGraphic area) {
if (areaSubList == null) {
areaSubList = new OMGraphicList();
}
areaSubList.add(area);
}
protected void addEdge(OMGraphic edge) {
if (edgeSubList == null) {
edgeSubList = new OMGraphicList();
}
edgeSubList.add(edge);
}
protected void addText(OMGraphic text) {
if (textSubList == null) {
textSubList = new OMGraphicList();
}
textSubList.add(text);
}
protected void addPoint(OMGraphic point) {
if (pointSubList == null) {
pointSubList = new OMGraphicList();
}
pointSubList.add(point);
}
/**
* set if we draw edge features
*
* @param newvalue <code>true</code> for drawing, false otherwise
*/
public void setEdgeFeatures(boolean newvalue) {
drawEdgeFeatures = newvalue;
}
/**
* Return true if we may draw some edge features.
*/
public boolean drawEdgeFeatures() {
return drawEdgeFeatures;
}
/**
* set if we draw text features
*
* @param newvalue <code>true</code> for drawing, false otherwise
*/
public void setTextFeatures(boolean newvalue) {
drawTextFeatures = newvalue;
}
/**
* Return true if we may draw some text features.
*/
public boolean drawTextFeatures() {
return drawTextFeatures;
}
/**
* set if we draw area features
*
* @param newvalue <code>true</code> for drawing, false otherwise
*/
public void setAreaFeatures(boolean newvalue) {
drawAreaFeatures = newvalue;
}
/**
* Return true if we may draw some area features.
*/
public boolean drawAreaFeatures() {
return drawAreaFeatures;
}
/**
* set if we draw entity point features
*
* @param newvalue <code>true</code> for drawing, false otherwise
*/
public void setEPointFeatures(boolean newvalue) {
drawEPointFeatures = newvalue;
}
/**
* Return true if we may draw some entity point features.
*/
public boolean drawEPointFeatures() {
return drawEPointFeatures;
}
/**
* set if we draw connected point features
*
* @param newvalue <code>true</code> for drawing, false otherwise
*/
public void setCPointFeatures(boolean newvalue) {
drawCPointFeatures = newvalue;
}
/**
* Return true if we may draw some connected point features.
*/
public boolean drawCPointFeatures() {
return drawCPointFeatures;
}
/**
* 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) {
// If someone gives us a list of features, we need to make
// sure thats
// what we use.
setAreaFeatures(false);
setEdgeFeatures(false);
setTextFeatures(false);
setEPointFeatures(false);
setCPointFeatures(false);
StringTokenizer t = new StringTokenizer(features);
while (t.hasMoreTokens()) {
String token = t.nextToken();
if (token.equalsIgnoreCase(VPFUtil.Area)) {
setAreaFeatures(true);
} else if (token.equalsIgnoreCase(VPFUtil.Edge)) {
setEdgeFeatures(true);
} else if (token.equalsIgnoreCase(VPFUtil.EPoint)) {
setEPointFeatures(true);
} else if (token.equalsIgnoreCase(VPFUtil.CPoint)) {
setCPointFeatures(true);
} else if (token.equalsIgnoreCase(VPFUtil.Text)) {
setTextFeatures(true);
} else {
Debug.output("LayerGraphicsWarehouseSupport: ignoring feature: " + token);
}
}
}
public String getFeatureString() {
StringBuffer features = new StringBuffer();
if (drawAreaFeatures)
features.append(VPFUtil.Area.toLowerCase()).append(" ");
if (drawEdgeFeatures)
features.append(VPFUtil.Edge.toLowerCase()).append(" ");
if (drawEPointFeatures)
features.append(VPFUtil.EPoint.toLowerCase()).append(" ");
if (drawCPointFeatures)
features.append(VPFUtil.CPoint.toLowerCase()).append(" ");
if (drawTextFeatures)
features.append(VPFUtil.Text.toLowerCase()).append(" ");
return features.toString();
}
/**
* set drawing attribute properties
*
* @param prefix the prefix for our properties
* @param props the Properties object we use to look up values
*/
public void setProperties(String prefix, Properties props) {
String features;
String realPrefix = PropUtils.getScopedPropertyPrefix(prefix);
features = props.getProperty(realPrefix + VPFLayer.featureTypesProperty);
drawingAttributes.setProperties(prefix, props);
if (features != null) {
setFeatures(features);
}
}
/**
* set drawing attribute properties
*
* @param props the Properties object.
*/
public Properties getProperties(Properties props) {
if (props == null) {
props = new Properties();
}
String realPrefix = PropUtils.getScopedPropertyPrefix(drawingAttributes);
props.put(realPrefix + VPFLayer.featureTypesProperty, getFeatureString());
drawingAttributes.getProperties(props);
return props;
}
/**
* create a filled polygon
*
* @param ipts a list of CoordFloatString objects
* @param totalSize the total number of points
* @param dpplat threshold for latitude thinning (passed to warehouse)
* @param dpplon threshold for longitude thinngin (passed to warehouse)
* @param ll1 upperleft of selection region (passed to warehouse)
* @param ll2 lowerright of selection region (passed to warehouse)
* @param doAntarcticaWorkaround hack for funny DCW antarctica data (passed
* to warehouse)
*/
public static OMPoly createAreaOMPoly(List<CoordFloatString> ipts, int totalSize, LatLonPoint ll1, LatLonPoint ll2,
double dpplat, double dpplon, boolean doAntarcticaWorkaround) {
// thin the data
// if (doThinning) {
// totalSize = doThinning(ipts);
// }
// *2 for pairs
double[] llpts = new double[totalSize * 2];
// only do it if we're in the vicinity
if (doAntarcticaWorkaround) {
doAntarcticaWorkaround = (ll2.getLatitude() < -62f);
}
int npts = 0;
for (CoordFloatString cfs : ipts) {
int cfscnt = cfs.tcount;
int cfssz = cfs.tsize;
double cfsvals[] = cfs.vals;
if (cfscnt > 0) { // normal
for (int i = 0; i < cfscnt; i++) {
llpts[npts++] = ProjMath.degToRad(cfsvals[i * cfssz + 1]);// lat
llpts[npts++] = ProjMath.degToRad(cfsvals[i * cfssz]);// lon
}
} else { // reverse
cfscnt *= -1;
for (int i = cfscnt - 1; i >= 0; i--) {
llpts[npts++] = ProjMath.degToRad(cfsvals[i * cfssz + 1]);// lat
llpts[npts++] = ProjMath.degToRad(cfsvals[i * cfssz]);// lon
}
}
}
// HACK: we will rewrite the data for the Antarctica polygon
// so that
// it will display "correctly" in the cylindrical projections.
// only check if bottom edge of screen below a certain
// latitude
if (doAntarcticaWorkaround) {
double[] newllpts = new double[llpts.length];
for (int i = 0; i < newllpts.length; i += 2) {
newllpts[i] = llpts[i];
newllpts[i + 1] = llpts[i + 1];
if (newllpts[i] < antarcticaThreshold) {
Debug.message("vpf", "AreaTable.generateOMPoly(): Antarctica!");
// HACK: we're assuming data is going from west to
// east,
// so we wrap the other way
newllpts[i++] = ProjMath.degToRad(-89.99f);
newllpts[i++] = ProjMath.degToRad(179.99f);
newllpts[i++] = ProjMath.degToRad(-89.99f);
newllpts[i++] = ProjMath.degToRad(90f);
newllpts[i++] = ProjMath.degToRad(-89.99f);
newllpts[i++] = ProjMath.degToRad(0f);
newllpts[i++] = ProjMath.degToRad(-89.99f);
newllpts[i++] = ProjMath.degToRad(-90f);
newllpts[i++] = ProjMath.degToRad(-89.99f);
newllpts[i++] = ProjMath.degToRad(-179.99f);
// HACK: advance to western hemisphere where we
// pick up the real data again
while (llpts[i + 1] > 0) {// lon
newllpts[i++] = ProjMath.degToRad(-89.99f);
newllpts[i++] = ProjMath.degToRad(-179.99f);
}
i -= 2;
}
}
llpts = newllpts;
}
// create polygon - change to OMPoly for jdk 1.1.x compliance.
OMPoly py = new OMPoly(llpts, OMGraphic.RADIANS, OMGraphic.LINETYPE_STRAIGHT);
return py;
}
/**
* Create an OMPoly corresponding to a VPF edge feature
*
* @param coords the coordinates to use for the poly
* @param ll1 upper left, used for clipping
* @param ll2 lower right, used for clipping
* @param dpplat used for latitude thinning
* @param dpplon used for longitude thinning
*/
public static OMPoly createEdgeOMPoly(CoordFloatString coords, LatLonPoint ll1, LatLonPoint ll2, double dpplat, double dpplon) {
// thin the data
// if (doThinning) {
// List ipts = new ArrayList(1);
// ipts.add(coords);
// doThinning(ipts);
// }
double[] llpts = coords.vals; // NOTE: lon,lat order!
// handle larger tuples (do extra O(n) loop to extract only
// lon/lats.
if (coords.tsize > 2) {// assume 3
/*
* if (Debug.debugging("vpf")) {
* Debug.output("EdgeTable.drawTile: big tuple size: " + coords.tsize);
* }
*/
double[] newllpts = new double[coords.tcount * 2];// *2 for
// pairs
int len = newllpts.length;
for (int i = 0, j = 0; i < len; i += 2, j += 3) {
newllpts[i] = ProjMath.degToRad(llpts[j + 1]);// lat
newllpts[i + 1] = ProjMath.degToRad(llpts[j]);// lon
}
llpts = newllpts;
} else {
double lon;
int len = llpts.length;
for (int i = 0; i < len; i += 2) {
lon = ProjMath.degToRad(llpts[i]);
llpts[i] = ProjMath.degToRad(llpts[i + 1]);// lat
llpts[i + 1] = lon;// lon
}
}
// create polyline - change to OMPoly for jdk 1.1.x
// compliance.
OMPoly py = new OMPoly(llpts, OMGraphic.RADIANS, OMGraphic.LINETYPE_STRAIGHT);
return py;
}
/**
* Set doThinning.
*
* @param value boolean
*/
public static void setDoThinning(boolean value) {
doThinning = value;
}
/**
* Are we thinning?.
*
* @return boolean
*/
public static boolean isDoThinning() {
return doThinning;
}
/**
* Set fan compression epsilon.
*
* @param value double
*/
public static void setFanEpsilon(double value) {
fan_eps = value;
}
/**
* Get fan compression epsilon.
*
* @return double
*/
public static double getFanEpsilon() {
return fan_eps;
}
/**
* do fan compression of raw edge points
*/
protected static int doThinning(List<Object> ipts) {
int size = ipts.size();
int totalSize = 0;
for (int j = 0; j < size; j++) {
// get poly
CoordFloatString cfs = (CoordFloatString) ipts.get(j);
int cfscnt = cfs.tcount;
int cfssz = cfs.tsize;
double[] cfsvals = cfs.vals;
int npts = 0;
// handle reverse
boolean rev = (cfscnt < 0);
if (rev) {
cfscnt = -cfscnt;
}
// copy points
double[] llpts = new double[cfscnt << 1];
for (int i = 0; i < cfscnt; i++) {
llpts[npts++] = cfsvals[i * cfssz];// lon
llpts[npts++] = cfsvals[i * cfssz + 1];// lat
}
// thin points
FanCompress.FloatCompress fan = new FanCompress.FloatCompress(llpts);
FanCompress.fan_compress(fan, fan_eps);
// install new points
cfs.vals = fan.getArray(); // install thinned 2-tuple
// array
cfs.tcount = cfs.vals.length >>> 1;// num pairs
cfs.tsize = 2;// HACK lossy...
totalSize += cfs.tcount;
if (rev) {
cfs.tcount *= -1;
}
}
return totalSize;
}
/**
* Create an OMText object corresponding to a VPF text feature
*
* @param text the text
* @param latitude the latitude of where to place the text
* @param longitude the longitude of where to place the text
*/
public static OMText createOMText(String text, double latitude, double longitude) {
OMText txt = new OMText(latitude, longitude, text, OMText.JUSTIFY_LEFT);
return txt;
}
/**
* Create an OMPoint object corresponding to a VPF node feature
*
* @param latitude the latitude of where to place the text
* @param longitude the longitude of where to place the text
*/
public static OMPoint createOMPoint(double latitude, double longitude) {
return new OMPoint(latitude, longitude);
}
}