//**********************************************************************
//
//<copyright>
//
//BBN Technologies
//10 Moulton Street
//Cambridge, MA 02138
//(617) 873-8000
//
//Copyright (C) BBNT Solutions LLC. All rights reserved.
//
//</copyright>
//**********************************************************************
//
//$Source:
///cvs/darwars/ambush/aar/src/com/bbn/ambush/mission/MissionHandler.java,v
//$
//$RCSfile: MissionHandler.java,v $
//$Revision: 1.10 $
//$Date: 2004/10/21 20:08:31 $
//$Author: dietrick $
//
//**********************************************************************
package com.bbn.openmap.layer.event;
import java.net.MalformedURLException;
import java.net.URL;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.Properties;
import java.util.Vector;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.swing.ImageIcon;
import javax.swing.SwingConstants;
import com.bbn.openmap.OMComponent;
import com.bbn.openmap.event.OMEvent;
import com.bbn.openmap.io.CSVFile;
import com.bbn.openmap.layer.location.LocationHandler;
import com.bbn.openmap.omGraphics.DrawingAttributes;
import com.bbn.openmap.omGraphics.OMGraphic;
import com.bbn.openmap.omGraphics.time.TemporalOMGraphic;
import com.bbn.openmap.omGraphics.time.TemporalOMGraphicList;
import com.bbn.openmap.omGraphics.time.TemporalOMPoint;
import com.bbn.openmap.omGraphics.time.TemporalOMScalingIcon;
import com.bbn.openmap.omGraphics.time.TemporalPoint;
import com.bbn.openmap.proj.coords.LatLonPoint;
import com.bbn.openmap.time.TimeBounds;
import com.bbn.openmap.util.DataBounds;
import com.bbn.openmap.util.PropUtils;
/**
* A data importer for the EventLayer. The location file should contain information about objects that will be moving on
* the map. The activity file will contain information about where and when the objects moved. Sample properties:
*
* <pre>
* eventLayer.class=com.bbn.openmap.layer.time.EventLayer
* eventLayer.importer=com.bbn.openmap.layer.time.CSVEventImporter
* eventLayer.prettyName=Test Event
* eventLayer.locationFile=org-list.csv
* eventLayer.locationFileHasHeader=true
* eventLayer.nameIndex=0
* eventLayer.iconIndex=5
* eventLayer.activityFile=org-activities.csv
* eventLayer.activityFileHasHeader=true
* eventLayer.activityNameIndex=1
* eventLayer.latIndex=9
* eventLayer.lonIndex=10
* eventLayer.timeFormat=d-MMM-yyyy HH:mm
* eventLayer.timeIndex=7
* # If no icon defined, used for location markers edge.
* eventLayer.lineColor=aaaaaa33
* # If no icon defined, used for location markers fill.
* eventLayer.fillColor=aaaaaa33
* </pre>
*
* @author dietrick
*/
public class CSVEventImporter extends OMComponent implements EventImporter {
public static Logger logger = Logger.getLogger("com.bbn.openmap.layer.event.CSVEventImporter");
/** locationFile */
public final static String LocationFileProperty = "locationFile";
/** locationFileHasHeader */
public final static String LocationHeaderProperty = "locationFileHasHeader";
/** iconIndex */
public final static String IconIndexProperty = "iconIndex";
/** nameIndex */
public final static String NameIndexProperty = "nameIndex";
/** activityFile */
public final static String ActivityFileProperty = "activityFile";
/** activityNameIndex */
public final static String ActivityNameIndexProperty = "activityNameIndex";
/** activityFileHasHeader */
public final static String ActivityHeaderProperty = "activityFileHasHeader";
/** latIndex */
public final static String LatIndexProperty = "latIndex";
/** lonIndex */
public final static String LonIndexProperty = "lonIndex";
/** timeIndex */
public final static String TimeIndexProperty = "timeIndex";
/** eastIsNeg */
public final static String EastIsNegProperty = "eastIsNeg";
/** showNames */
public final static String ShowNamesProperty = LocationHandler.ShowNamesProperty;
/** defaultURL */
public final static String DefaultIconURLProperty = "defaultURL";
/** timeFormat */
public final static String TimeFormatProperty = "timeFormat";
/**
* TimeFormat default is similar to IETF standard date syntax:
* "Sat, 12 Aug 1995 13:30:00 GMT" represented by (EEE, d MMM yyyy HH:mm:ss
* z), except for the local timezone.
*/
protected SimpleDateFormat timeFormat = new SimpleDateFormat("EEE, d MMM yyyy HH:mm:ss z");
protected String locationFile;
protected boolean locationHeader = true;
protected int nameIndex;
protected int iconIndex;
protected String activityFile;
protected boolean activityHeader = true;
protected int activityNameIndex;
protected int latIndex;
protected int lonIndex;
protected int timeIndex;
protected boolean eastIsNeg = false;
protected int orientation = SwingConstants.HORIZONTAL;
/** Icon URL for points to use as default. May be null. */
protected String defaultIconURL;
protected boolean showNames = false;
protected DrawingAttributes drawingAttributes = DrawingAttributes.getDefaultClone();
public CSVEventImporter() {
}
/**
* Read the data files and construct the TemporalOMGraphics. You also need
* to create TimeBounds, keep track of the time stamps from the data source,
* and set the new TimeBounds on the EventLayer before returning from this
* method. If you want to set the DataBounds on the layer, in order for the
* view menu to have a selection for the area of interest, fetch the
* DataBounds object and set it accordingly while you are in this method.
* <p>
* Read the data files and construct the TemporalOMGraphics. There are four
* things you need to do in this method.
* <ul>
* <li>Create an TemporalOMGraphicList, add TemporalOMGraphics, return it.
* <li>Set a new TimeBounds object on the callback EventLayer when all the
* timestamp range is known.
* <li>Add OMEvents to the callback.events list, one for each TemporalPoint
* created.
* <li>Add locations to callback's DataBounds (callback.getDataBounds()).
* </ul>
*/
public synchronized TemporalOMGraphicList createData(EventLayer callback) {
TemporalOMGraphicList list = new TemporalOMGraphicList();
Hashtable<String, TemporalOMGraphic> library = new Hashtable<String, TemporalOMGraphic>();
Hashtable<String, ImageIcon> iconLibrary = new Hashtable<String, ImageIcon>();
// BOTH IMPORTANT
DataBounds dataBounds = callback.getDataBounds();
TimeBounds timeBounds = new TimeBounds();
// Create TemporalOMGraphics, to associate events to
if (locationFile != null && nameIndex != -1) {
logger.fine("Reading location file...");
try {
CSVFile locations = new CSVFile(locationFile);
locations.loadData();
Iterator<Vector<Object>> records = locations.iterator();
while (records.hasNext()) {
String name = null;
String iconName = null;
ImageIcon icon = null;
Vector<?> record = records.next();
if (record.isEmpty()) {
continue;
}
name = (String) record.elementAt(nameIndex);
if (iconIndex != -1) {
iconName = (String) record.elementAt(iconIndex);
icon = iconLibrary.get(iconName);
if (icon == null) {
URL icURL = PropUtils.getResourceOrFileOrURL(iconName);
if (icURL != null) {
icon = new ImageIcon(icURL);
if (icon != null) {
iconLibrary.put(iconName, icon);
}
}
}
}
if (name != null) {
TemporalOMGraphic location;
if (icon == null) {
location = new TemporalOMPoint(name, OMGraphic.RENDERTYPE_LATLON, true);
} else {
location = new TemporalOMScalingIcon(name, OMGraphic.RENDERTYPE_LATLON, true, icon, 4000000);
}
// location.setShowName(showNames);
drawingAttributes.setTo(location);
library.put(name.intern(), location);
list.add(location);
} else {
logger.warning("no name to use to create location: "
+ name);
}
}
} catch (MalformedURLException murle) {
logger.warning("problem finding the location file: "
+ locationFile);
return list;
} catch (ArrayIndexOutOfBoundsException aioobe) {
logger.warning("problem with parsing location file: "
+ locationFile);
if (logger.isLoggable(Level.FINE)) {
logger.fine("The problem is with one of the indexes into the file: \n"
+ aioobe.getMessage());
aioobe.printStackTrace();
}
} catch (NullPointerException npe) {
logger.warning("null pointer exception, most likely a problem finding the organization data file");
}
} else {
logger.warning("Location file (" + locationFile
+ ") not configured.");
return list;
}
// OK, got the TemporalOMGraphics built up, need to fill up the
// events
//
if (activityFile != null && activityNameIndex != -1 && latIndex != -1
&& lonIndex != -1 && timeIndex != -1) {
logger.fine("Reading activity file...");
try {
CSVFile activities = new CSVFile(activityFile);
activities.loadData(); // numbers as strings == false
Iterator<Vector<Object>> records = activities.iterator();
while (records.hasNext()) {
String name = null;
float lat;
float lon;
Vector<?> record = records.next();
if (record.isEmpty()) {
continue;
}
name = record.elementAt(activityNameIndex)
.toString()
.intern();
try {
lat = ((Double) record.elementAt(latIndex)).floatValue();
lon = ((Double) record.elementAt(lonIndex)).floatValue();
// parse time from string, ending up with
// milliseconds from time epoch.
String timeString = (String) record.elementAt(timeIndex);
Date timeDate = timeFormat.parse(timeString);
long time = timeDate.getTime();
// BOTH IMPORTANT
timeBounds.addTimeToBounds(time);
dataBounds.add((double) lon, (double) lat);
if (name != null) {
TemporalOMGraphic point = library.get(name);
if (point != null) {
LatLonPoint location = new LatLonPoint.Double(lat, lon);
TemporalPoint ts = new TemporalPoint(location, time);
point.addTimeStamp(ts);
OMEvent event = new OMEvent(ts, name
+ " moving", time, location);
// IMPORTANT
callback.events.add(event);
} else {
logger.warning("ScenarioPoint not found for "
+ name + ", entry: " + record);
}
} else {
logger.warning("no name to use to create activity point: "
+ name);
}
} catch (ClassCastException cce) {
Object obj0 = record.elementAt(activityNameIndex);
Object obj1 = record.elementAt(latIndex);
Object obj2 = record.elementAt(lonIndex);
Object obj3 = record.elementAt(timeIndex);
logger.warning("Problem with indexes in activity file for "
+ obj0
+ " ("
+ obj0.getClass().getName()
+ ")"
+ ":\n\tlat index = "
+ latIndex
+ ", value = "
+ obj1
+ " ("
+ obj1.getClass().getName()
+ ")\n\t lon index = "
+ lonIndex
+ ", value = "
+ obj2
+ " ("
+ obj2.getClass().getName()
+ ")\n\t time index = "
+ timeIndex
+ ", value = "
+ obj3
+ " ("
+ obj3.getClass().getName() + ")");
} catch (ParseException pe) {
logger.fine("Problem with time format. "
+ pe.getMessage());
}
}
} catch (MalformedURLException murle) {
logger.warning("problem with activity file: " + activityFile);
return list;
} catch (NullPointerException npe) {
logger.warning("null pointer exception, most likely a problem finding the activites data file");
}
} else {
logger.warning("Activity file (" + activityFile
+ ") not configured.");
return list;
}
logger.fine("Reading files OK");
// IMPORTANT!
callback.setTimeBounds(timeBounds);
// Time will get updated automatically when the TimeEvent gets sent from
// the clock.
return list;
}
/**
* The properties and prefix are managed and decoded here, for the standard uses of the EventLayer.
*
* @param prefix string prefix used in the properties file for this layer.
* @param properties the properties set in the properties file.
*/
public void setProperties(String prefix, Properties properties) {
super.setProperties(prefix, properties);
drawingAttributes.setProperties(prefix, properties);
prefix = PropUtils.getScopedPropertyPrefix(prefix);
locationFile = properties.getProperty(prefix + LocationFileProperty);
iconIndex = PropUtils.intFromProperties(properties, prefix
+ IconIndexProperty, -1);
nameIndex = PropUtils.intFromProperties(properties, prefix
+ NameIndexProperty, -1);
activityNameIndex = PropUtils.intFromProperties(properties, prefix
+ ActivityNameIndexProperty, -1);
activityFile = properties.getProperty(prefix + ActivityFileProperty);
latIndex = PropUtils.intFromProperties(properties, prefix
+ LatIndexProperty, -1);
lonIndex = PropUtils.intFromProperties(properties, prefix
+ LonIndexProperty, -1);
timeIndex = PropUtils.intFromProperties(properties, prefix
+ TimeIndexProperty, -1);
eastIsNeg = PropUtils.booleanFromProperties(properties, prefix
+ EastIsNegProperty, eastIsNeg);
showNames = PropUtils.booleanFromProperties(properties, prefix
+ ShowNamesProperty, showNames);
defaultIconURL = properties.getProperty(prefix + DefaultIconURLProperty);
locationHeader = PropUtils.booleanFromProperties(properties, prefix
+ LocationHeaderProperty, false);
activityHeader = PropUtils.booleanFromProperties(properties, prefix
+ ActivityHeaderProperty, false);
String timeFormatString = properties.getProperty(prefix
+ TimeFormatProperty,
((SimpleDateFormat) timeFormat).toPattern());
timeFormat = new SimpleDateFormat(timeFormatString);
if (logger.isLoggable(Level.FINE)) {
logger.fine("EventLayer indexes:" + "\n\tlocation file: "
+ locationFile + "\n\tlocation file has header: "
+ locationHeader + "\n\tnameIndex = " + nameIndex
+ "\n\ticonIndex = " + iconIndex + "\n\tactivity file: "
+ activityFile + "\n\tactivity file has header: "
+ activityHeader + "\n\tlatIndex = " + latIndex
+ "\n\tlonIndex = " + lonIndex + "\n\ttimeIndex = "
+ timeIndex);
}
}
/**
* PropertyConsumer method, to fill in a Properties object, reflecting the current values of the layer. If the layer
* 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.
* @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) {
props = super.getProperties(props);
drawingAttributes.getProperties(props);
String prefix = PropUtils.getScopedPropertyPrefix(this);
props.put(prefix + LocationFileProperty, PropUtils.unnull(locationFile));
props.put(prefix + LocationHeaderProperty,
new Boolean(locationHeader).toString());
props.put(prefix + NameIndexProperty,
(nameIndex != -1 ? Integer.toString(nameIndex) : ""));
props.put(prefix + ActivityFileProperty, PropUtils.unnull(activityFile));
props.put(prefix + ActivityHeaderProperty,
new Boolean(activityHeader).toString());
props.put(prefix + ActivityNameIndexProperty,
(activityNameIndex != -1 ? Integer.toString(activityNameIndex)
: ""));
props.put(prefix + EastIsNegProperty, new Boolean(eastIsNeg).toString());
props.put(prefix + ShowNamesProperty, new Boolean(showNames).toString());
props.put(prefix + LatIndexProperty,
(latIndex != -1 ? Integer.toString(latIndex) : ""));
props.put(prefix + LonIndexProperty,
(lonIndex != -1 ? Integer.toString(lonIndex) : ""));
props.put(prefix + TimeIndexProperty,
(timeIndex != -1 ? Integer.toString(timeIndex) : ""));
props.put(prefix + IconIndexProperty,
(iconIndex != -1 ? Integer.toString(iconIndex) : ""));
props.put(prefix + DefaultIconURLProperty,
PropUtils.unnull(defaultIconURL));
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.). This method takes care of the basic
* LocationHandler parameters, so any LocationHandlers that extend the
* AbstractLocationHandler should call this method, too, before adding any
* specific properties.
*
* @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) {
list = super.getPropertyInfo(list);
list.put(LocationFileProperty,
"URL of file containing location information.");
list.put(LocationFileProperty + ScopedEditorProperty,
"com.bbn.openmap.util.propertyEditor.FUPropertyEditor");
list.put(LocationHeaderProperty,
"Location file has a header row to be ignored.");
list.put(LocationHeaderProperty + ScopedEditorProperty,
"com.bbn.openmap.util.propertyEditor.YesNoPropertyEditor");
list.put(ActivityFileProperty,
"URL of file containing scenario activity information.");
list.put(ActivityFileProperty + ScopedEditorProperty,
"com.bbn.openmap.util.propertyEditor.FUPropertyEditor");
list.put(ActivityHeaderProperty,
"Activity file has a header row to be ignored.");
list.put(ActivityHeaderProperty + ScopedEditorProperty,
"com.bbn.openmap.util.propertyEditor.YesNoPropertyEditor");
list.put(EastIsNegProperty,
"Flag to note that negative latitude are over the eastern hemisphere.");
list.put(EastIsNegProperty + ScopedEditorProperty,
"com.bbn.openmap.util.propertyEditor.YesNoPropertyEditor");
list.put(ShowNamesProperty,
"Flag to note that locations should display their names.");
list.put(ShowNamesProperty + ScopedEditorProperty,
"com.bbn.openmap.util.propertyEditor.YesNoPropertyEditor");
list.put(NameIndexProperty,
"The column index, in the location file, of the location label text.");
list.put(ActivityNameIndexProperty,
"The column index, in the activity file, of the location label text.");
list.put(LatIndexProperty,
"The column index, in the activity file, of the latitudes.");
list.put(LonIndexProperty,
"The column index, in the activity file, of the longitudes.");
list.put(TimeIndexProperty,
"The column index, in the activity file, of the time of the activity.");
list.put(IconIndexProperty,
"The column index, in the location file, of the icon for locations (optional).");
list.put(DefaultIconURLProperty,
"The URL of an image file to use as a default for the location markers (optional).");
drawingAttributes.getPropertyInfo(list);
return list;
}
}