// **********************************************************************
//
// <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/shape/areas/AreaHandler.java,v $
// $RCSfile: AreaHandler.java,v $
// $Revision: 1.4.2.8 $
// $Date: 2009/03/03 04:59:13 $
// $Author: dietrick $
//
// **********************************************************************
package com.bbn.openmap.layer.shape.areas;
import java.awt.Color;
import java.io.IOException;
import java.io.InputStream;
import java.io.InterruptedIOException;
import java.net.URL;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.Properties;
import java.util.StringTokenizer;
import java.util.Vector;
import com.bbn.openmap.PropertyConsumer;
import com.bbn.openmap.dataAccess.shape.DbfTableModel;
import com.bbn.openmap.dataAccess.shape.ShapeConstants;
import com.bbn.openmap.dataAccess.shape.input.DbfInputStream;
import com.bbn.openmap.io.FormatException;
import com.bbn.openmap.layer.shape.CSVShapeInfoFile;
import com.bbn.openmap.layer.shape.ShapeLayer;
import com.bbn.openmap.layer.shape.SpatialIndex;
import com.bbn.openmap.layer.shape.SpatialIndex.Entry;
import com.bbn.openmap.omGraphics.DrawingAttributes;
import com.bbn.openmap.omGraphics.OMGeometryList;
import com.bbn.openmap.omGraphics.OMGraphic;
import com.bbn.openmap.omGraphics.OMGraphicList;
import com.bbn.openmap.proj.ProjMath;
import com.bbn.openmap.proj.Projection;
import com.bbn.openmap.proj.coords.GeoCoordTransformation;
import com.bbn.openmap.util.ComponentFactory;
import com.bbn.openmap.util.Debug;
import com.bbn.openmap.util.PropUtils;
/**
* An object to organize graphics in a shapefile and their corresponding
* attributes in OpenMap. A properties object can determine how areas/graphics
* are to be colored, or you can grab the graphics directly and color them
* yourself. It's called AreaHandler because it was originally intended to be a
* management tool for political boundary areas, but it should work for all
* shapefiles, really. This object uses a CSV file created from the DBF file
* that usually accompanies the shapefile. Also, this class does inflict a
* startup burden on the map. Because all the organizational effort occurs in
* setProperties(), it occurs even if the handler isn't used in an active Layer.
* <P>
* Here is a sample of what this thing is looking for by way of properties:
* <P>
*
* <pre>
*
*
* layer.class=com.bbn.openmap.layer.shape.areas.AreaShapeLayer
* layer.prettyName=Layer Name
* layer.shapeFile=/usr/local/data/shape/shapefile.shp
* layer.spatialIndex=/usr/local/data/shape/shapefile.ssx
*
* # Now, provide a data file that says what the shapes in the .shp
* # file are. You can use the DBF file:
* layer.dbfFile=/usr/local/data/shape/shapefile.dbf
* # OR a csv file, created yourself or from the .dbf file. There
* # should be the same number of entries in the .csv file that are in
* # the .shp file.
* layer.csvFile=/usr/local/data/shape/shapefile.csv
* # An attribute to tell the AreaHandler to skip over the first row
* # of the csv file if it contains descriptive column header names.
* layer.csvFileHasHeader=true
*
* # Default DrawingAttributes properties for everything not defined
* # specifically:
* layer.lineColor=ff000000
* layer.fillColor=ffff00ff
*
* # Now add any other attributes accepted by the DrawingAttributes
* # object, with the prefix as stated above, i.e. layer.lineColor)
* #
* # The first column index is 0, not 1.
* #
* # The key index specifies which column in the csv file contains
* # unique area names that are listed in the areas list here in the
* # properties. In this case, it's the column that contains MA in one
* # of its rows.
* layer.keyIndex=4
*
* # The name index is the column in the csv file that contains what
* # should be displayed in the application when a shape is chosen - the
* # object's proper name.
* layer.nameIndex=4
* layer.areas=MA RI
* layer.areas.MA.fillColor=ffff0000
* layer.areas.MA.lineColor=ff00ff00
* layer.areas.RI.fillColor=ffff0000
* layer.areas.RI.lineColor=ff00ff00
*
*
* </pre>
*
* <P>
*/
public class AreaHandler implements PropertyConsumer {
/**
* The known political areas, based on the list of OMGraphics each entry
* contains.
*/
protected Hashtable politicalAreas;
/** The property that lists special colored areas. */
public static final String areasProperty = "areas";
/**
* A property that sets an image URL to use for point objects. Only one
* image for all point objects.
*/
public static final String pointImageURLProperty = "pointImageURL";
/**
* The property that specifies an index location for the area search key for
* a shape graphic in the database file. Default is 1. The contents of this
* column should match the area key used to specify the drawingattributes of
* that particular object as listed in these properties.
*/
public static final String keyIndexProperty = "keyIndex";
/**
* The property that specifies an index location for the area name for a
* shape graphic in the database file. Default is 0.
*/
public static final String nameIndexProperty = "nameIndex";
/**
* The resource name, URL or file name of the serialized graphics file.
*/
public static final String CacheFileProperty = "cacheFile";
/**
* The name of the property that holds the name of the CSV file with the
* area attributes, like the name and the abbreviation (or search Key).
*/
public final static String csvFileProperty = "csvFile";
/** Set if the CSVFile has a header record. Default is true. */
public final static String csvHeaderProperty = "csvFileHasHeader";
/**
* The name of the property that holds the name of the DBF file with the
* area attributes, like the name and the abbreviation (or search Key).
*/
public final static String dbfFileProperty = "dbfFile";
/**
* The list of areas that have special coloring needs. Used to write the
* properties back out.
*/
protected Vector areasItems = new Vector();
/**
* The index of the column that holds the name of the area. This name will
* be used for display in the GUI for a particular map object.
*/
protected int nameIndex = 0;
/**
* The index of the column that holds the search key of the area. This is
* the field that is the key to use for the Hashtable holding all the area
* descriptions, and should be unique for each named area.
*/
protected int keyIndex = 1;
/** The URL location of the cached graphics file. */
protected URL cacheURL = null;
/** The graphics list */
protected OMGraphicList omgraphics = null;
/**
* Default draw parameters of the graphics that don't have something
* specific set for it.
*/
protected DrawingAttributes drawingAttributes;
/** The location of the CSV attribute file. */
protected CSVShapeInfoFile infoFile = null;
/** The DBF attribute file table model. */
protected DbfTableModel dbfModel = null;
/**
* Flag that specifies that the first line consists of header information,
* and should not be mapped to a graphic.
*/
protected boolean csvHasHeader = true;
protected Properties originalProperties = null;
protected String originalPrefix = null;
protected SpatialIndex spatialIndex = null;
protected GeoCoordTransformation coordTransform = null;
// public AreaHandler() {}
/**
* Construct an AreaHandler. Needs an external SpatialIndex, and default
* DrawingAttributes.
*/
public AreaHandler(SpatialIndex si, DrawingAttributes da) {
setDrawingAttributes(da);
setSpatialIndex(si);
}
public void setDrawingAttributes(DrawingAttributes da) {
drawingAttributes = da;
}
public DrawingAttributes getDrawingAttributes() {
return drawingAttributes;
}
public void setSpatialIndex(SpatialIndex si) {
spatialIndex = si;
}
public SpatialIndex getSpatialIndex() {
return spatialIndex;
}
public Hashtable getPoliticalAreas() {
return politicalAreas;
}
public void setProperties(Properties props) {
setProperties(null, props);
}
/**
* Initializes this object from the given properties
*
* @param props the <code>Properties</code> holding settings for this object
*/
public void setProperties(String prefix, Properties props) {
if (Debug.debugging("areas")) {
Debug.output("AreaHandler: setting properties");
}
setPropertyPrefix(prefix);
originalProperties = props;
// These will get initialized when someone asks for it.
// Otherwise, it delays the startup of the map.
politicalAreas = null;
String realPrefix = PropUtils.getScopedPropertyPrefix(prefix);
String transClassName = props.getProperty(realPrefix
+ ShapeLayer.TransformProperty);
if (transClassName != null) {
try {
coordTransform = (GeoCoordTransformation) ComponentFactory.create(transClassName,
realPrefix + ShapeLayer.TransformProperty,
props);
} catch (ClassCastException cce) {
}
}
}
/** PropertyConsumer method. */
public Properties getProperties(Properties props) {
if (props == null) {
props = new Properties();
}
if (coordTransform != null
&& coordTransform instanceof PropertyConsumer) {
((PropertyConsumer) coordTransform).getProperties(props);
}
return props;
}
/** PropertyConsumer method. */
public Properties getPropertyInfo(Properties props) {
if (props == null) {
props = new Properties();
}
return props;
}
/** PropertyConsumer method. */
public void setPropertyPrefix(String pre) {
originalPrefix = pre;
}
/** PropertyConsumer method. */
public String getPropertyPrefix() {
return originalPrefix;
}
/**
* Go through the properties, loading the shapefile, information file and
* attributes files, and resolve how everything should be drawn. Might take
* awhile if the files are large. Called from getRectangle, which is called
* when the AreaShapeLayer is added to the map.
*
* @param prefix property file entry header name
* @param props the properties to look through.
*/
public void initialize(String prefix, Properties props) {
if (props == null) {
Debug.error("AreaHandler: initialize received bad input:\n\tprefix: "
+ prefix
+ "\n\tproperties: "
+ (props == null ? "null" : "OK"));
politicalAreas = null;
return;
}
prefix = PropUtils.getScopedPropertyPrefix(prefix);
politicalAreas = new Hashtable();
// OK, Get the graphics. We are not expecting that all the
// graphics in the file are not too much to handle. Also, we
// test for the serialized graphics file first, and if it
// isn't designated, then look for a shapefile and spatial
// index file to create an OMGraphicsList.
String cacheFile = props.getProperty(prefix + CacheFileProperty);
// First find the resource, if not, then try as a file-URL...
try {
cacheURL = PropUtils.getResourceOrFileOrURL(this, cacheFile);
if (cacheURL != null) {
omgraphics = readCachedGraphics(cacheURL);
} else {
// We'll use the spatial index set from the
// ShapeLayer.
// Now, get the attribute file
String dbfFile = props.getProperty(prefix + dbfFileProperty);
URL dbfFileURL = null;
if (dbfFile != null) {
dbfFileURL = PropUtils.getResourceOrFileOrURL(this, dbfFile);
}
if (dbfFileURL != null) {
InputStream is = dbfFileURL.openStream();
dbfModel = new DbfTableModel(new DbfInputStream(is));
}
if (dbfModel == null) {
String csvFile = props.getProperty(prefix + csvFileProperty);
URL infofileURL = null;
if (csvFile != null) {
infofileURL = PropUtils.getResourceOrFileOrURL(this,
csvFile);
}
// Read them in.
if (infofileURL != null) {
infoFile = new CSVShapeInfoFile(csvFile);
infoFile.setHeadersExist(PropUtils.booleanFromProperties(props,
prefix + csvHeaderProperty,
true));
infoFile.loadData(true);
}
}
}
} catch (java.net.MalformedURLException murle) {
omgraphics = new OMGraphicList();
} catch (java.io.IOException ioe) {
omgraphics = new OMGraphicList();
} catch (Exception exc) {
omgraphics = new OMGraphicList();
}
// This is handled properly yet. The PoliticalArea should be
// updated to handle URLs for area points, and have different
// icons for different areas.
// String defaultPointImageURLString =
// props.getProperty(prefix + pointImageURLProperty);
// Now, match the attributes to the graphics. Find the
// indexes of the name and the search key. Also figure out
// which areas have special coloring needs.
keyIndex = PropUtils.intFromProperties(props,
prefix + keyIndexProperty,
keyIndex);
nameIndex = PropUtils.intFromProperties(props, prefix
+ nameIndexProperty, nameIndex);
String areas = props.getProperty(prefix + areasProperty);
if (areas == null)
areas = "";
StringTokenizer tokenizer = new StringTokenizer(areas, " ");
// All this uses the properties to set the individual colors
// of any area
String currentArea;
while (tokenizer.hasMoreTokens()) {
currentArea = tokenizer.nextToken();
PoliticalArea newParams = new PoliticalArea(currentArea);
if (Debug.debugging("areas")) {
Debug.output("AreaHandler: setting SPECIALIZED attributes for \""
+ newParams.id + "\"");
}
areasItems.addElement(currentArea);
newParams.drawingAttributes = new DrawingAttributes(prefix
+ areasProperty + "." + currentArea, props);
politicalAreas.put(newParams.id.toUpperCase().intern(), newParams);
}
if (Debug.debugging("areas")) {
Debug.output("AreaHandler: finished initialization");
}
}
/**
* Read a cache of OMGraphics
*/
public OMGraphicList readCachedGraphics(URL url) throws java.io.IOException {
if (Debug.debugging("areas")) {
Debug.output("Reading cached graphics");
}
OMGraphicList omgraphics = new OMGraphicList();
if (url != null) {
omgraphics.readGraphics(url);
}
return omgraphics;
}
/**
* Get all the graphics from the shapefile, colored appropriately.
*/
public OMGraphicList getGraphics() {
if (omgraphics == null) {
omgraphics = new OMGraphicList();
try {
spatialIndex.getOMGraphics(-180,
-90,
180,
90,
omgraphics,
drawingAttributes,
(Projection) null,
coordTransform);
updateDrawingParameters(omgraphics);
} catch (IOException ioe) {
Debug.error(ioe.getMessage());
} catch (FormatException fe) {
Debug.error(fe.getMessage());
}
}
return omgraphics;
}
protected void updateDrawingParameters(OMGraphicList omgl) {
if (omgl != null) {
for (Iterator it = omgl.iterator(); it.hasNext();) {
OMGraphic omg = (OMGraphic) it.next();
Integer recNum = (Integer) omg.getAttribute(ShapeConstants.SHAPE_INDEX_ATTRIBUTE);
if (recNum != null) {
getDrawParams(recNum.intValue()).setTo(omg);
omg.putAttribute(OMGraphic.INFOLINE, getName(recNum));
}
}
}
}
/**
* Get the graphics for a particular lat/lon area.
*
* @param ulLat upper left latitude, in decimal degrees.
* @param ulLon upper left longitude, in decimal degrees.
* @param lrLat lower right latitude, in decimal degrees.
* @param lrLon lower right longitude, in decimal degrees.
* @return OMGraphicList
*/
public OMGraphicList getGraphics(float ulLat, float ulLon, float lrLat,
float lrLon) {
return getGraphics(ulLat, ulLon, lrLat, lrLon, (Projection) null);
}
/**
* Get the graphics for a particular lat/lon area.
*
* @param ulLat upper left latitude, in decimal degrees.
* @param ulLon upper left longitude, in decimal degrees.
* @param lrLat lower right latitude, in decimal degrees.
* @param lrLon lower right longitude, in decimal degrees.
* @param proj the current map projection.
* @return OMGraphicList
*/
public OMGraphicList getGraphics(float ulLat, float ulLon, float lrLat,
float lrLon, Projection proj) {
if (cacheURL != null) {
return omgraphics;
}
if (spatialIndex == null) {
return new OMGraphicList();
}
if (politicalAreas == null) {
initialize(originalPrefix, originalProperties);
}
OMGraphicList list = new OMGraphicList();
// check for dateline anomaly on the screen. we check for
// ulLon >= lrLon, but we need to be careful of the check for
// equality because of floating point arguments...
if (ProjMath.isCrossingDateline(ulLon, lrLon, proj.getScale())) {
if (Debug.debugging("areas")) {
Debug.output("AreaHander.getGraphics(): Dateline is on screen");
}
double ymin = (double) Math.min(ulLat, lrLat);
double ymax = (double) Math.max(ulLat, lrLat);
try {
list = spatialIndex.getOMGraphics(ulLon,
ymin,
180.0d,
ymax,
list,
drawingAttributes,
proj,
coordTransform);
list = spatialIndex.getOMGraphics(-180.0d,
ymin,
lrLon,
ymax,
list,
drawingAttributes,
proj,
coordTransform);
} catch (InterruptedIOException iioe) {
// This means that the thread has been interrupted,
// probably due to a projection change. Not a big
// deal, just return, don't do any more work, and let
// the next thread solve all problems.
list = null;
} catch (IOException ex) {
ex.printStackTrace();
} catch (FormatException fe) {
fe.printStackTrace();
}
} else {
double xmin = (double) Math.min(ulLon, lrLon);
double xmax = (double) Math.max(ulLon, lrLon);
double ymin = (double) Math.min(ulLat, lrLat);
double ymax = (double) Math.max(ulLat, lrLat);
try {
list = spatialIndex.getOMGraphics(xmin,
ymin,
xmax,
ymax,
list,
drawingAttributes,
proj,
coordTransform);
} catch (InterruptedIOException iioe) {
// This means that the thread has been interrupted,
// probably due to a projection change. Not a big
// deal, just return, don't do any more work, and let
// the next thread solve all problems.
list = null;
} catch (java.io.IOException ex) {
ex.printStackTrace();
} catch (FormatException fe) {
fe.printStackTrace();
}
}
updateDrawingParameters(list);
return list;
}
/**
* Return the graphic name, given the infofile vector on the graphic. The
* AreaHandler knows which one is the name. Returns an empty string if
* something goes wrong.
*/
public String getName(Vector vector) {
try {
String string = (String) vector.elementAt(nameIndex);
return string;
} catch (ClassCastException cce) {
}
return "";
}
/**
* Get the name of the object at record number. The record number is the
* shapefile record number, which is one greater than the index number.
* Returns an empty string if something goes wrong.
*/
public String getName(Integer integer) {
try {
if (infoFile != null) {
Vector vector = infoFile.getRecord(integer.intValue() - 1);
if (vector != null) {
return (String) vector.elementAt(nameIndex);
}
} else if (dbfModel != null) {
Object obj = dbfModel.getValueAt(integer.intValue() - 1,
nameIndex);
if (obj != null) {
if (obj instanceof String) {
return (String) obj;
} else {
return obj.toString();
}
}
}
} catch (ClassCastException cce) {
}
return "";
}
/**
* Given the shapefile record number, find the drawing parameters that
* should be used for the shape. Note, this recordNumber is the shapefile
* record number, which starts at one. All our indexes start at 0, so this
* is taken into account here. Don't make the adjustment elsewhere. Returns
* the default coloring if the key for the drawing parameters isn't found.
*/
public DrawingAttributes getDrawParamsFromCSV(int recordNumber) {
if (infoFile == null) {
return drawingAttributes;
}
// OFF BY ONE!!! The shape record numbers
// assigned to the records start with 1, while
// everything else we do starts with 0...
Vector info = infoFile.getRecord(recordNumber - 1);
if (info == null) {
if (Debug.debugging("areas")) {
Debug.output("AreaHandler.getDrawParameters: record "
+ recordNumber + " has no info");
}
return drawingAttributes;
}
Object keyObj = info.elementAt(keyIndex);
String key = null;
PoliticalArea pa = null;
if (keyObj != null) {
key = createStringFromKeyObject(keyObj);
pa = (PoliticalArea) politicalAreas.get(key);
}
if (pa == null) {
if (Debug.debugging("areas")) {
Debug.output("AreaHandler.getDrawParameters: record "
+ recordNumber + " has key \"" + key
+ "\" and DEFAULT attributes");
}
return drawingAttributes;
} else {
// Only bother with this the first time around.
if (pa.name == null) {
String name = (String) info.elementAt(nameIndex);
if (name != null) {
pa.name = name;
} else {
pa.name = "";
}
}
if (Debug.debugging("areas")) {
Debug.output("AreaHandler.getDrawParameters: record "
+ recordNumber + " has key \"" + key
+ "\" and SPECIALIZED attributes");
}
return pa.drawingAttributes;
}
}
/**
* Given the shapefile record number, find the drawing parameters from the
* DBF model that should be used for the shape. Returns the default coloring
* if the key for the drawing parameters isn't found.
*/
public DrawingAttributes getDrawParamsFromDBF(int recordNumber) {
if (dbfModel == null) {
return drawingAttributes;
}
// OFF BY ONE!!! The shape record numbers
// assigned to the records start with 1, while
// everything else we do starts with 0...
// Vector info = infoFile.getRecord(recordNumber-1);
if (dbfModel == null || dbfModel.getRowCount() < recordNumber) {
if (Debug.debugging("areas")) {
Debug.output("AreaHandler.getDrawParameters: record "
+ recordNumber + " has no info");
}
return drawingAttributes;
}
Object keyObj = dbfModel.getValueAt(recordNumber - 1, keyIndex);
String key = null;
PoliticalArea pa = null;
if (keyObj != null) {
key = createStringFromKeyObject(keyObj);
pa = (PoliticalArea) politicalAreas.get(key);
}
if (pa == null) {
if (Debug.debugging("areas")) {
Debug.output("AreaHandler.getDrawParameters: record "
+ recordNumber + " has key \"" + key
+ "\" and DEFAULT attributes");
}
return drawingAttributes;
} else {
// Only bother with this the first time around.
if (pa.name == null) {
// String name = (String) info.elementAt(nameIndex);
String name = (String) dbfModel.getValueAt(recordNumber - 1,
nameIndex);
if (name != null) {
pa.name = name;
} else {
pa.name = "";
}
}
if (Debug.debugging("areas")) {
Debug.output("AreaHandler.getDrawParameters: record "
+ recordNumber + " has key \"" + key
+ "\" and SPECIALIZED attributes");
}
return pa.drawingAttributes;
}
}
/**
* OK, we can't assume that we are assigning a string as a key, you might
* want to key in on a specific attribute that is a number, like the country
* coloring code that ESRI has in the country file. We're going to assume
* that if the number has an integer value, it shouldn't have decimal
* places. That is, a 1.0 will be truncated to 1, because that makes more
* sense in a data file where you are using a key as a factor. If the double
* value doesn't match the integer value, though, we'll assume that's what
* was meant and leave it alone.
* <p>
*/
protected String createStringFromKeyObject(Object keyObj) {
String key = null;
if (keyObj instanceof String) {
key = ((String) keyObj).toUpperCase().intern();
} else if (keyObj instanceof Number) {
Number keyNum = (Number) keyObj;
if (keyNum.doubleValue() == (double) keyNum.intValue()) {
// Strips off empty decimal places, for sure
key = Integer.toString(keyNum.intValue()).intern();
} else {
key = Double.toString(keyNum.doubleValue()).intern();
}
} else {
try {
key = keyObj.toString().toUpperCase().intern();
} catch (Exception e) {
Debug.error("AreaHandler.createStringFromKeyObject: bad key object:"
+ keyObj);
}
}
return key;
}
/**
* Given the shapefile record number, find the drawing parameters that
* should be used for the shape. Returns the default coloring if the key for
* the drawing parameters isn't found.
*/
public DrawingAttributes getDrawParams(int recordNumber) {
if (dbfModel != null)
return getDrawParamsFromDBF(recordNumber);
else
return getDrawParamsFromCSV(recordNumber);
}
/**
* This function takes an OMGraphicList and loads each one with the array
* representing the records in the dbf file. Each graphics stores the
* graphic in its object slot.
*/
public void loadDbfModelIntoGraphics(OMGraphicList list) {
if (list != null && dbfModel.getRowCount() > 0) {
int numgraphics = list.size();
for (int i = 0; i < numgraphics; i++) {
try {
OMGraphic omg = list.getOMGraphicAt(i);
Integer recnum = (Integer) (omg.getAttribute(ShapeConstants.SHAPE_INDEX_ATTRIBUTE));
// OFF BY ONE!!! The shape record numbers
// assigned to the records start with 1, while
// everything else we do starts with 0...
Object inforec = dbfModel.getRecord(recnum.intValue() - 1);
omg.putAttribute(ShapeConstants.SHAPE_DBF_INFO_ATTRIBUTE,
inforec);
} catch (ClassCastException cce) {
if (Debug.debugging("shape")) {
cce.printStackTrace();
}
} catch (NullPointerException npe) {
npe.printStackTrace();
}
}
}
}
/**
* Find a PoliticalArea named by the search key. If the shapefile is large,
* the first query will take a little extra time on the first query to read
* in the files.
*
* @param area_key the lookup key, of which the index for the column was
* designated in the properties file.
*/
public PoliticalArea findPoliticalArea(String area_key) {
// Right now, this method gathers all the graphics in the
// shape file, groups them, and then returns the PoliticalArea
// for the key. In the future, it would be nice to have the
// option to actually search through the data file, find the
// indexes of the graphics that go to the area, and assemble a
// temporary list to pass back.
if (politicalAreas == null) {
Debug.message("areas",
"AreaHandler: initializing graphic attributes");
initialize(originalPrefix, originalProperties);
if (omgraphics == null) {
omgraphics = getGraphics();
if (dbfModel != null)
loadDbfModelIntoGraphics(omgraphics);
else
infoFile.loadIntoGraphics(omgraphics);
}
politicalAreas = determinePoliticalAreas(omgraphics);
Debug.message("areas", "AreaHandler: completed initialization");
}
if (politicalAreas != null) {
String key = area_key.toUpperCase().intern(); // Just to
// be sure.
return (PoliticalArea) politicalAreas.get(key);
} else {
Debug.error("AreaHandler: initialization failed for "
+ originalPrefix + "\n\tNo data will be displayed");
return null;
}
}
/**
* Find the graphics that are represented by an search key. If the shapefile
* is large, the first query will take a little extra time on the first
* query to read in the files.
*
* @param area_key the lookup key, of which the index for the column was
* designated in the properties file.
*/
public OMGeometryList findGraphics(String area_key) {
PoliticalArea area = findPoliticalArea(area_key);
if (area == null) {
return null;
} else {
return area.getGeometry();
}
}
/**
* DeterminePoliticalAreas goes over a list of omgraphics, and spits out a
* hashtable that holds PoliticalArea objects for every area key.
*
* @param graphicList the list of graphics. The top level graphic entries on
* the list represent areas.
*/
public Hashtable determinePoliticalAreas(OMGraphicList graphicList) {
if (Debug.debugging("areas")) {
Debug.output("AreaHandler: Determining political areas from OMGraphicList");
}
Hashtable poli_areas = new Hashtable();
return determinePoliticalAreas(graphicList, poli_areas);
}
/**
* DeterminePoliticalAreas goes over a list of omgraphics, and spits out a
* hashtable that holds PoliticalArea objects for every area key. When an ID
* is found in the graphics, it is checked in the hashtable for like
* graphics, and added to that PoliticalArea if found. If not found, a new
* PoliticalArea is created and placed in the Hashtable. This will duplicate
* graphics if you call it more than once for the same graphic list.
*
* @param graphicList the list of graphics. The top level graphic entries on
* the list represent areas.
*/
public Hashtable determinePoliticalAreas(OMGraphicList graphicList,
Hashtable poli_areas) {
// Simple case. No graphics means an empty list of regions.
String name = null;
String key = null;
if (graphicList != null) {
int size = graphicList.size();
for (int i = 0; i < size; i++) {
OMGraphic graphic = graphicList.getOMGraphicAt(i);
// below should be a vector like [ "Massachusetts",
// "MA" ];
Object obj = graphic.getAttribute(ShapeConstants.SHAPE_DBF_INFO_ATTRIBUTE);
if (obj == null) {
if (Debug.debugging("areas")) {
Debug.error("AreaHandler: No attributes for graphic #"
+ i);
}
continue;
}
if (obj instanceof Vector) {
Vector pair = (Vector) obj;
name = (String) pair.elementAt(nameIndex);
key = ((String) pair.elementAt(keyIndex)).toUpperCase()
.intern();
if (Debug.debugging("areas")) {
Debug.output("AreaHandler: looking at " + name + ", "
+ key);
}
} else if (obj instanceof String) {
// Assume that the key is stored here, I guess.
key = (String) obj;
if (Debug.debugging("areas")) {
Debug.output("AreaHandler: String app object, looking at "
+ key);
}
} else {
if (Debug.debugging("areas")) {
Debug.output("AreaHandler: Unidentified app object type "
+ obj);
}
}
// Get the political area object for this keyiation.
PoliticalArea area = (PoliticalArea) poli_areas.get(key);
if (area == null) { // key is not in table
area = new PoliticalArea(name, key);
poli_areas.put(key, area); // add it to the table
// AreaDrawParams adp =
// (AreaDrawParams)drawingParams.get(key);
// if (adp != null) {
// area.setDrawingAttributes(adp.drawingAttributes);
// }
}
// Add the graphic to the list for this political
// area.
area.addGraphic(graphic);
}
if (Debug.debugging("areas")) {
Debug.output("AreaHandler: Finished determinePoliticalAreas: "
+ poli_areas.size() + " areas defined.");
}
}
return poli_areas;
}
protected Color getColor(String colorString) {
Color result = Color.black;
try {
result = PropUtils.parseColor(colorString);
} catch (NumberFormatException nfe) {
result = GetColorFromString(colorString);
}
return result;
}
/**
* This function would return a Color object for string such as red,
* green,.. (all that are available from java.awt.color class). It can also
* return a specific color represented by HEX or Octal number like
* 0xffeeffee
*/
protected Color GetColorFromString(String token) {
String tokstring = (String) token;
Color result = Color.black;
if (Debug.debugging("areas")) {
Debug.output("AreaHandler: GetColorFromString(" + tokstring + ")");
}
// Thank the heavens for Emacs macros!
if (tokstring.equals("black"))
result = Color.black;
else if (tokstring.equals("blue"))
result = Color.blue;
else if (tokstring.equals("cyan"))
result = Color.cyan;
else if (tokstring.equals("darkGray"))
result = Color.darkGray;
else if (tokstring.equals("gray"))
result = Color.gray;
else if (tokstring.equals("green"))
result = Color.green;
else if (tokstring.equals("lightGray"))
result = Color.lightGray;
else if (tokstring.equals("magenta"))
result = Color.magenta;
else if (tokstring.equals("orange"))
result = Color.orange;
else if (tokstring.equals("pink"))
result = Color.pink;
else if (tokstring.equals("red"))
result = Color.red;
else if (tokstring.equals("white"))
result = Color.white;
else if (tokstring.equals("yellow"))
result = Color.yellow;
else
// decode a hex color string.
result = Color.decode(tokstring);
if (Debug.debugging("areas")) {
Debug.output("AreaHandler.GetColorFromToken returns (" + result
+ ")");
}
return result;
}
public GeoCoordTransformation getCoordTransform() {
return coordTransform;
}
public void setCoordTransform(GeoCoordTransformation dataTransform) {
this.coordTransform = dataTransform;
}
/**
* This main function basically reads in the data sources (the shape file
* and the csv information file, and creates a serialized graphics file that
* will act like a cache later.
*/
public static void main(String[] argv) {
String propertiesFile = null;
String prefix = null;
String outputFile = null;
Debug.init();
if (argv.length < 6)
printUsage();
for (int i = 0; i < argv.length; i++) {
if (argv[i].equalsIgnoreCase("-props")) {
propertiesFile = argv[++i];
} else if (argv[i].equalsIgnoreCase("-prefix")) {
prefix = argv[++i];
} else if (argv[i].equalsIgnoreCase("-file")) {
outputFile = argv[++i];
}
}
if (propertiesFile == null || prefix == null || outputFile == null) {
printUsage();
}
try {
Properties properties = new Properties();
// Read in the properties.
URL propertiesURL = new URL(propertiesFile);
InputStream is = propertiesURL.openStream();
properties.load(is);
// Let's make a file
ShapeLayer sl = new ShapeLayer();
sl.setProperties(prefix, properties);
AreaHandler ah = new AreaHandler(sl.getSpatialIndex(), sl.getDrawingAttributes());
// Set the properties in the handler.
ah.setProperties(prefix, properties);
// Write the saved graphics.
ah.getGraphics().writeGraphics(outputFile);
} catch (java.net.MalformedURLException murle) {
Debug.error("Bad URL for properties file : " + propertiesFile);
printUsage();
} catch (java.io.IOException ioe) {
Debug.error("IOException creating cached graphics file: "
+ outputFile);
printUsage();
}
}
public static void printUsage() {
Debug.output("Usage: java com.bbn.openmap.layer.shape.areas.AreaHandler -props <URL to properties file> -prefix <handler property prefix> -file <path to output file>");
System.exit(-1);
}
}