//********************************************************************** // //<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; } }