// ********************************************************************** // // <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/VPFFeatureGraphicWarehouse.java,v $ // $RCSfile: VPFFeatureGraphicWarehouse.java,v $ // $Revision: 1.9 $ // $Date: 2009/01/21 01:24:41 $ // $Author: dietrick $ // // ********************************************************************** package com.bbn.openmap.layer.vpf; import java.awt.BasicStroke; import java.awt.Component; import java.awt.Graphics2D; import java.awt.Image; import java.awt.image.BufferedImage; import java.awt.image.ImageObserver; import java.io.IOException; import java.net.MalformedURLException; import java.util.ArrayList; import java.util.Collections; import java.util.Hashtable; import java.util.List; import java.util.Properties; import java.util.Vector; import java.util.logging.Level; import java.util.logging.Logger; import com.bbn.openmap.Environment; import com.bbn.openmap.I18n; import com.bbn.openmap.PropertyConsumer; import com.bbn.openmap.dataAccess.cgm.CGM; import com.bbn.openmap.dataAccess.cgm.CGMDisplay; import com.bbn.openmap.io.CSVFile; import com.bbn.openmap.io.FormatException; import com.bbn.openmap.omGraphics.OMColor; 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.OMRasterObject; import com.bbn.openmap.omGraphics.OMScalingIcon; import com.bbn.openmap.omGraphics.OMText; import com.bbn.openmap.proj.Projection; import com.bbn.openmap.proj.coords.LatLonPoint; import com.bbn.openmap.util.ComponentFactory; import com.bbn.openmap.util.DataBounds; import com.bbn.openmap.util.Debug; import com.bbn.openmap.util.PropUtils; /** * Implement a graphic factory that builds OMGraphics from VPF. Designed to work * closely with the VPFFeatureLayer, using GeoSymAttExpression objects to figure * out how features are rendered. Uses two files to help manage features. The * first file is a symbol lookup file that ties FACC codes and attribute * settings with a set of CGM files. This file should cover entries for a * particular data set. The second file is a priority file, that lists the order * that features should be rendered, by feature type, facc code and attribute * settings. If you want to change which features are displayed, or the order in * which they are displayed, this is the file to modify. * <p> * * Both of these files are CSV files, and their fields are important. The lookup * file is of the format: * * <pre> * facc,type,symbol,conditions * AK160,A,0804, * AL005,A,0081 0734, * AL015,P,0002,bfc=81ANDsta=0or2or3or6or11 * AL015,P,0010,bfc=7ANDhwt=0or2or3or4or7or22 * AL015,P,0011,bfc=7ANDhwt=11or14or15or16or20or21 * </pre> * * Note that the conditions field can be empty.The first field is the 5 * character FACC code, the second field is the type (P, A, L) and the third * field is the CGM file name. * <P> * * The priority file is similar: * * <pre> * priority,type,facc,conditions,description * 0,Area,BA040, * 0,Area,BE010,cvl=99999 * 0,Area,BE010,idsm=0 AND cvl>=msdcand<>99999 * 0,Area,BE010,idsm=0 AND cvl>=ssdcand<msdc * </pre> * * The priority field really isn't important, the order of the overall file is. * * @see com.bbn.openmap.omGraphics.OMGraphic */ public class VPFAutoFeatureGraphicWarehouse implements VPFFeatureWarehouse, PropertyConsumer { protected static Logger logger = Logger.getLogger("com.bbn.openmap.layer.vpf.VPFAutoFeatureGraphicWarehouse"); public final static String CGM_DIR_PROPERTY = "cgmDirectory"; public final static String SYMBOL_LOOKUP_FILE_PROPERTY = "faccLookupFile"; public final static String PRIORITY_FILE_PROPERTY = "priorityFile"; public final static String FEATURE_INFO_HANDLER_PROPERTY = "featureInfoHandler"; public final static String FACC_DEBUG_PROPERTY = "debug"; public final static String ICON_SIZE_PROPERTY = "iconSize"; public final static int DEFAULT_ICON_SIZE = 20; protected List<FeaturePriorityHolder> priorities; protected Hashtable<String, List<FeaturePriorityHolder>> faccLookup; protected String priorityFilePath; protected String faccLookupFilePath; protected String geoSymDirectory; protected VPFFeatureInfoHandler featInfoHandler; protected int iconSize = DEFAULT_ICON_SIZE; protected String[] compositeFeatureFaccs = new String[] { "BC010", "BC020", "BC040", "BC070" }; /** * If set, the warehouse will limit visibility to specified facc and print * out decision making process. */ protected String debugFacc = null; /** * Set which library to use. If null, all applicable libraries in database * will be searched. */ private List<String> useLibrary = null; /** * The property prefix for scoping properties. */ protected String prefix; public final static String EV_ISDM = "isdm"; public final static String EV_IDSM = "idsm"; public final static String EV_SSDC = "ssdc"; public final static String EV_MSDC = "msdc"; public final static String EV_MSSC = "mssc"; /** * Interactive Shallow Display Mode:ECDIS defines the display mode of * shallow water areas (shallow depth zones) to be one of two symbology * scenarios. The attribute values are 1 and 0, which toggle the shallow * display mode to be on or off respectively. When ISDM is set on (1), the * display of all depth zones shallower than the defined values of the Ships * Safety Depth Contour (SSDC) are overprinted with a lattice pattern. This * mode can be initiated in the four- or two-depth zone display modes (not * including the drying line), defined by the Interactive Display Selection * Mode, (IDSM). The shallow display mode is made available due to viewing * limitations of the shallow depth zones in night displays. */ protected double isdm = 0; /** * Interactive Display Selection Mode:ECDIS defines the display of depth * zones to be divided into two or four depth areas. This variable allows * for the mariner to interactively set either display mode. The two-zone * mode uses only the ships safety depth contour (SSDC) as a zone separator, * whereas the four-zone mode further divides zones based on the mariner * selected deep and shallow contours (MSDC, MSSC). Attribute values are 0 * and 1 meaning four- and two-zone modes respectively. */ protected double idsm = 0; /** * Ship's Safety Depth Contour: The ships safety depth contour represents a * safe contour based on the draft of the ship. This value must be entered * by the mariner using an application interface. This interface must ensure * that if a contour value does not exist within the data, that a next * deeper value is specified as the SSDC. This checking must be dynamic as * one traverses tile boundaries within the data.. */ protected double ssdc = 5; /** * Mariner Specified Deep Contour: The four-zone display mode requires the * establishment of a deep contour that must be specified by the mariner * through application inquiry. A default value may be implemented at 30m * according to the ISO Color and Symbol Specification directives. */ protected double msdc = 30; /** * Mariner Specified Shallow Contour - The four-zone display mode requires * the establishment of a shallow contour that must be specified by the * mariner through application inquiry. A default value may be implemented * at 2m according to the ISO Color and Symbol Specification directives. */ protected double mssc = 1; /** * */ public VPFAutoFeatureGraphicWarehouse() { } /** * The warehouse is initialized the first time features are fetched. */ protected void init() { CSVFile priorityFile; CSVFile symbolLookupFile; try { symbolLookupFile = new CSVFile(faccLookupFilePath); symbolLookupFile.setHeadersExist(true); symbolLookupFile.loadData(true); priorityFile = new CSVFile(priorityFilePath); priorityFile.setHeadersExist(true); priorityFile.loadData(true); faccLookup = new Hashtable<String, List<FeaturePriorityHolder>>(); Hashtable<String, FeaturePriorityHolder.Compound> composites = new Hashtable<String, FeaturePriorityHolder.Compound>(); // Build up the priority holder list - this keeps the features in // proper rendering order, according to the priority csv file. // composite features are just held once at the first location they // are // found. int numPriorities = priorityFile.getNumberOfRecords(); priorities = new ArrayList<FeaturePriorityHolder>(); for (Vector<Object> row : priorityFile) { String lineCheck = null; String type = null; String facc = null; String conditions = null; try { lineCheck = row.get(0).toString(); if (lineCheck.startsWith("#")) { continue; } type = row.get(1).toString(); facc = row.get(2).toString(); conditions = row.get(3).toString(); } catch (ArrayIndexOutOfBoundsException aioobe) { logger.warning("Bad entry in priority file: " + lineCheck + "," + type + "," + facc + "," + conditions); continue; } // If the debugFacc is defined, just add that particular facc // type if (debugFacc != null && !debugFacc.equals(facc)) { continue; } // Now we need to check if the facc is a composite symbol, like // a // buoy. boolean composite = false; if (type.charAt(0) == CoverageTable.UPOINT_FEATURETYPE) { for (String compFacc : compositeFeatureFaccs) { if (compFacc.equals(facc)) { composite = true; break; } } } FeaturePriorityHolder.Basic ph = new FeaturePriorityHolder.Basic(type, facc, conditions, this); ph.setCGMPath(geoSymDirectory, ".cgm"); // If it is a composite, we need to add it to a // FeaturePriorityHolder.Compound object, so that all the little // parts will be added to any symbols, based on how the parts // conditions match up to the feature entry in the attribute // table. if (composite) { FeaturePriorityHolder.Compound compound = composites.get(facc); if (compound == null) { compound = new FeaturePriorityHolder.Compound(type, facc, this); composites.put(facc, compound); priorities.add(compound); // faccLookup doesn't have this facc if we're here... List<FeaturePriorityHolder> list = new ArrayList<FeaturePriorityHolder>(); faccLookup.put(facc, list); list.add(compound); } compound.addPart(ph); } else { priorities.add(ph); List<FeaturePriorityHolder> list = faccLookup.get(facc); if (list == null) { list = new ArrayList<FeaturePriorityHolder>(); faccLookup.put(facc, list); } list.add(ph); } } // The priority file doesn't know anything about the symbol that is // going to be used for the different conditions in the feature // holders. Need to disperse the symbol information to the feature // holders. // OK, time killer - loop through int numSymbols = symbolLookupFile.getNumberOfRecords(); int foundRecords = 0; for (Vector<Object> row : symbolLookupFile) { int numArgs = row.size(); String facc = row.get(0).toString(); if (numArgs != 7) { logger.warning("Problem with facc entry, not correct number of args in csv file:" + facc); continue; } char type = getType(row.get(1).toString()); String symbolCode = row.get(2).toString(); String conditions = row.get(3).toString().trim(); String size = row.get(4).toString().trim(); String xoff = row.get(5).toString().trim(); String yoff = row.get(6).toString().trim(); if (conditions.length() > 0) { conditions = conditions.replace(" ", ""); } List<FeaturePriorityHolder> faccList = faccLookup.get(facc); if (faccList != null) { boolean found = false; for (FeaturePriorityHolder ph : faccList) { if (ph.matches(facc, type, conditions, symbolCode, size, xoff, yoff)) { found = true; foundRecords++; break; } } if (!found) { // This really shouldn't be triggered, if it is, // something // happened to the data files. But that might be // intentional, // to keep some feature types off the map. if (logger.isLoggable(Level.FINE)) { logger.fine("didn't find matching PriorityHolder for " + facc + "|" + type + "|" + symbolCode + "|" + conditions); } } } else { // Since we've turned off loading faccs for everything but // the // debug facc, of course other things will complain. if (debugFacc == null) { if (logger.isLoggable(Level.FINE)) { logger.fine("can't find faccLookup for " + facc + " for" + type + "|" + symbolCode + "|" + conditions); } } } } if (logger.isLoggable(Level.FINE)) { logger.fine("matched up " + foundRecords + " of " + numSymbols + " symbols, " + numPriorities + " priority entries"); } } catch (MalformedURLException e) { // TODO Auto-generated catch block e.printStackTrace(); } } public static char getType(String type) { if (type == null) { logger.warning("unknown type!"); } else { switch (type.charAt(0)) { case 'P': return CoverageTable.UPOINT_FEATURETYPE; case 'A': return CoverageTable.AREA_FEATURETYPE; case 'L': return CoverageTable.EDGE_FEATURETYPE; default: } } return CoverageTable.SKIP_FEATURETYPE; } /** * Set the VPF libraries to use, by name. If null, all libraries will be * searched. Null is default. */ public void setUseLibraries(List<String> libNames) { useLibrary = libNames; } /** * Get a list of VPF library names that should be used, specified at * configuration. */ 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 the specified string entry (Good for * specifying sets of like-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; } /** * Create an OMPoly for an area described by the facevec. */ public OMGraphic createArea(CoverageTable covtable, AreaTable areatable, List<Object> facevec, LatLonPoint ll1, LatLonPoint ll2, double dpplat, double dpplon, String featureType, int primID) { List<CoordFloatString> ipts = new ArrayList<CoordFloatString>(); int totalSize = 0; try { totalSize = areatable.computeEdgePoints(facevec, ipts); } catch (FormatException f) { Debug.output("FormatException in computeEdgePoints: " + f); return null; } if (totalSize == 0) { return null; } OMPoly py = LayerGraphicWarehouseSupport.createAreaOMPoly(ipts, totalSize, ll1, ll2, dpplat, dpplon, covtable.doAntarcticaWorkaround); py.setIsPolygon(true); return py; } protected String info = null; /** * */ public OMGraphic createEdge(CoverageTable c, EdgeTable edgetable, List<Object> edgevec, LatLonPoint ll1, LatLonPoint ll2, double dpplat, double dpplon, CoordFloatString coords, String featureType, int primID) { OMPoly py = LayerGraphicWarehouseSupport.createEdgeOMPoly(coords, ll1, ll2, dpplat, dpplon); py.setFillPaint(OMColor.clear); py.setIsPolygon(false); return py; } /** * */ public OMGraphic createText(CoverageTable c, TextTable texttable, List<Object> textvec, double latitude, double longitude, String text, String featureType, int primID) { OMText txt = LayerGraphicWarehouseSupport.createOMText(text, latitude, longitude); return txt; } /** * Method called by the VPF reader code to construct a node feature. */ public OMGraphic createNode(CoverageTable c, NodeTable t, List<Object> nodeprim, double latitude, double longitude, boolean isEntityNode, String featureType, int primID) { // OMPoint pt = new OMPoint.Image(latitude, longitude); OMScalingIcon pt = new OMScalingIcon(latitude, longitude, (Image) null); pt.setBaseScale(500000); pt.setMinScale(500000); pt.setMaxScale(2000000); return pt; } public boolean needToFetchTileContents(String libraryName, String currentFeature, TileDirectory currentTile) { return true; } /** * This is where the magic happens. * * @param lst LibrarySelectionTable that lets the warehouse know where the * data is and what's in it. * @param ll1 upper left coordinate of the desired area. * @param ll2 lower right coordinate of the desired area. * @param proj the projection for the area, used to generate OMGraphics * added to the list. * @param omgList the list to add OMGraphics to. One will be created and * returned if this is null. * @return the OMGraphicList with OMGraphics for features over desired area. * @throws FormatException */ public OMGraphicList getFeatures(LibrarySelectionTable lst, LatLonPoint ll1, LatLonPoint ll2, Projection proj, OMGraphicList omgList) throws FormatException { if (priorities == null) { init(); } // handle Dateline if (ll1.getX() > ll2.getX()) { omgList = getFeatures(lst, ll1, new LatLonPoint.Double(ll2.getY(), 180 - .00001),// 180-epsilon proj, omgList); omgList = getFeatures(lst, new LatLonPoint.Double(ll1.getY(), -180f), ll2, proj, omgList); return omgList; } if (omgList == null) { omgList = new OMGraphicList(); } omgList.setTraverseMode(OMGraphicList.LAST_ADDED_ON_TOP); int screenheight = proj.getHeight(); int screenwidth = proj.getWidth(); double dpplat = Math.abs((ll1.getY() - ll2.getY()) / screenheight); double dpplon = Math.abs((ll1.getX() - ll2.getX()) / screenwidth); /* BoundingCircle screenBounds = new GeoSegment.Impl(new Geo[] { new Geo(ll1.getLatitude(), ll1.getLongitude()), new Geo(ll2.getLatitude(), ll2.getLongitude()) }).getBoundingCircle(); */ DataBounds screenBounds = new DataBounds(ll1, ll2); for (String libraryName : lst.getLibraryNames()) { if (!checkLibraryForUsage(libraryName)) { continue; } if (logger.isLoggable(Level.FINE)) { logger.fine("reading library: " + libraryName); } CoverageAttributeTable cat = lst.getCAT(libraryName); if (cat == null) { if (logger.isLoggable(Level.FINE)) { logger.fine("no CoverageAttributeTable for " + libraryName + ", skipping..."); } continue; } // Do a quick bounds check, so we can just skip the tiles for this // CAT // if nothing is on the map. DataBounds bounds = cat.getBounds(); if (bounds != null) { /* Point2D min = bounds.getMin(); Point2D max = bounds.getMax(); BoundingCircle catCircle = new GeoSegment.Impl(new Geo[] { new Geo(min.getY(), min.getX()), new Geo(max.getY(), max.getX()) }).getBoundingCircle(); */ if (!screenBounds.intersects(bounds)) { logger.fine("CoverageAttributeTable for " + libraryName + " not on map, skipping..."); continue; } } for (String covname : cat.getCoverageNames()) { if (logger.isLoggable(Level.FINER)) { logger.finer("for coverage: " + covname + ", coverage topology level: " + cat.getCoverageTopologyLevel(covname)); } CoverageTable coverageTable = cat.getCoverageTable(covname); coverageTable.getFeatures(this, ll1, ll2, dpplat, dpplon, omgList); } } // Go through PriorityHolders and build up OMGraphicList, in order for // rendering to map. Moved this from inside the for loop above, so that // feature order is preserved across libraries. for (FeaturePriorityHolder ph : priorities) { OMGraphicList list = ph.getList(); if (list != null) { if (logger.isLoggable(Level.FINE)) { logger.fine("Adding features from " + ph.toString() + ": " + list.size() + " features"); } list.generate(proj); omgList.addAll(list); list.setVisible(debugFacc == null || ph.getDebugFacc() != null); // Now that the OMGraphics are part of the main list, clean them // out for the next request. Doing it here saves from having to // do another loop through at the beginning of getFeatures. ph.resetList(); } } logger.fine("returning from prepare ************"); return omgList; } /** * Return true, this is a NOOP for this warehouse. */ public boolean drawEdgeFeatures() { return true; } /** * Return true, this is a NOOP for this warehouse. */ public boolean drawTextFeatures() { return true; } /** * Return true, this is a NOOP for this warehouse. */ public boolean drawAreaFeatures() { return true; } /** * Return true, this is a NOOP for this warehouse. */ public boolean drawEPointFeatures() { return true; } /** * Return true, this is a NOOP for this warehouse. */ public boolean drawCPointFeatures() { return true; } /** * @param rightSide the string pulled out of the VPF data for attribute * comparisons. * @return value greater than 0 for valid strings. */ public double getExternalAttribute(String rightSide) { double ret = -1; if (rightSide != null) { if (rightSide.equals(EV_IDSM)) { ret = idsm; } else if (rightSide.equals(EV_ISDM)) { ret = isdm; } else if (rightSide.equals(EV_MSDC)) { ret = msdc; } else if (rightSide.equals(EV_MSSC)) { ret = mssc; } else if (rightSide.equals(EV_SSDC)) { ret = ssdc; } } return ret; } /** * Given an OMGraphic that is going to be added to the map, use the * FeatureClassInfo to gather attribute information from the fcirow * contents. * * @param omg The OMGraphic representing a feature. * @param fci The Description of the columns of the fcirow. * @param fcirow The attributes for the feature. */ public void handleInformationForOMGraphic(OMGraphic omg, FeatureClassInfo fci, List<Object> fcirow) { if (featInfoHandler != null) { featInfoHandler.updateInfoForOMGraphic(omg, fci, fcirow); } } /* * (non-Javadoc) * * @see com.bbn.openmap.PropertyConsumer#setProperties(java.util.Properties) */ public void setProperties(Properties setList) { setProperties(null, setList); } /** * Set properties of the warehouse. * * @param prefix the prefix to use for looking up properties. * @param props the properties file to look at. */ public void setProperties(String prefix, Properties props) { setPropertyPrefix(prefix); prefix = PropUtils.getScopedPropertyPrefix(prefix); faccLookupFilePath = props.getProperty(prefix + SYMBOL_LOOKUP_FILE_PROPERTY, faccLookupFilePath); priorityFilePath = props.getProperty(prefix + PRIORITY_FILE_PROPERTY, priorityFilePath); debugFacc = props.getProperty(prefix + FACC_DEBUG_PROPERTY, debugFacc); geoSymDirectory = props.getProperty(prefix + CGM_DIR_PROPERTY, geoSymDirectory); iconSize = PropUtils.intFromProperties(props, prefix + ICON_SIZE_PROPERTY, iconSize); String fihString = props.getProperty(prefix + FEATURE_INFO_HANDLER_PROPERTY); if (fihString != null) { Object obj = ComponentFactory.create(fihString, prefix, props); if (obj instanceof VPFFeatureInfoHandler) { featInfoHandler = (VPFFeatureInfoHandler) obj; } } isdm = PropUtils.doubleFromProperties(props, prefix + EV_ISDM, isdm); idsm = PropUtils.doubleFromProperties(props, prefix + EV_IDSM, idsm); msdc = PropUtils.doubleFromProperties(props, prefix + EV_MSDC, msdc); mssc = PropUtils.doubleFromProperties(props, prefix + EV_MSSC, mssc); ssdc = PropUtils.doubleFromProperties(props, prefix + EV_SSDC, ssdc); } /* * (non-Javadoc) * * @see com.bbn.openmap.PropertyConsumer#getProperties(java.util.Properties) */ public Properties getProperties(Properties getList) { if (getList == null) { getList = new Properties(); } String prefix = PropUtils.getScopedPropertyPrefix(this); getList.put(prefix + SYMBOL_LOOKUP_FILE_PROPERTY, faccLookupFilePath); getList.put(prefix + PRIORITY_FILE_PROPERTY, priorityFilePath); getList.put(prefix + CGM_DIR_PROPERTY, geoSymDirectory); if (featInfoHandler != null) { getList.put(prefix + FEATURE_INFO_HANDLER_PROPERTY, featInfoHandler.getClass().getName()); if (featInfoHandler instanceof PropertyConsumer) { ((PropertyConsumer) featInfoHandler).getProperties(getList); } } getList.put(prefix + ICON_SIZE_PROPERTY, Integer.toString(iconSize)); if (debugFacc != null && debugFacc.length() > 0) { getList.put(prefix + FACC_DEBUG_PROPERTY, debugFacc); } getList.put(prefix + EV_ISDM, Double.toString(isdm)); getList.put(prefix + EV_IDSM, Double.toString(idsm)); getList.put(prefix + EV_MSDC, Double.toString(msdc)); getList.put(prefix + EV_MSSC, Double.toString(mssc)); getList.put(prefix + EV_SSDC, Double.toString(ssdc)); return getList; } /* * (non-Javadoc) * * @see * com.bbn.openmap.PropertyConsumer#getPropertyInfo(java.util.Properties) */ public Properties getPropertyInfo(Properties list) { if (list == null) { list = new Properties(); } I18n i18n = Environment.getI18n(); PropUtils.setI18NPropertyInfo(i18n, list, VPFAutoFeatureGraphicWarehouse.class, SYMBOL_LOOKUP_FILE_PROPERTY, "Symbol Lookup File", "The path to the file containing symbol lookup information", "com.bbn.openmap.util.propertyEditor.FilePropertyEditor"); PropUtils.setI18NPropertyInfo(i18n, list, VPFAutoFeatureGraphicWarehouse.class, PRIORITY_FILE_PROPERTY, "Priority File", "The path to the file containing feature type and order to use for display", "com.bbn.openmap.util.propertyEditor.FilePropertyEditor"); PropUtils.setI18NPropertyInfo(i18n, list, VPFAutoFeatureGraphicWarehouse.class, CGM_DIR_PROPERTY, "CGM Directory Path", "The path to the directory containing GeoSym CGM files", "com.bbn.openmap.util.propertyEditor.DirectoryPropertyEditor"); PropUtils.setI18NPropertyInfo(i18n, list, VPFAutoFeatureGraphicWarehouse.class, FACC_DEBUG_PROPERTY, "FACC Debug", "A FACC code to use to debug problems with data set", null); PropUtils.setI18NPropertyInfo(i18n, list, VPFAutoFeatureGraphicWarehouse.class, ICON_SIZE_PROPERTY, "Icon Size", "The pixel size of icons for point features", null); return list; } /* * (non-Javadoc) * * @see com.bbn.openmap.PropertyConsumer#setPropertyPrefix(java.lang.String) */ public void setPropertyPrefix(String prefix) { this.prefix = prefix; } /* * (non-Javadoc) * * @see com.bbn.openmap.PropertyConsumer#getPropertyPrefix() */ public String getPropertyPrefix() { return prefix; } /* * (non-Javadoc) * * @see com.bbn.openmap.layer.vpf.VPFWarehouse#resetForCAT() */ public void resetForCAT() { // NOOP } public int getIconSize() { return iconSize; } public void setIconSize(int iconSize) { this.iconSize = iconSize; } /* * (non-Javadoc) * * @see * com.bbn.openmap.layer.vpf.VPFWarehouse#getGUI(com.bbn.openmap.layer.vpf * .LibrarySelectionTable) */ public Component getGUI(LibrarySelectionTable lst) { // TODO Auto-generated method stub return null; } /* * (non-Javadoc) NOOP * * @see com.bbn.openmap.layer.vpf.VPFWarehouse#getFeatures() */ public List<String> getFeatures() { return Collections.emptyList(); } /** * Set the object used to manage attribute formatting and display for * features on the map. * * @return VPFFeatureInfoHandler being used. */ public VPFFeatureInfoHandler getFeatInfoHandler() { return featInfoHandler; } public void setFeatInfoHandler(VPFFeatureInfoHandler featInfoHandler) { this.featInfoHandler = featInfoHandler; } /** * * A FeaturePriorityHolder represents a rendering order slot in a list of * feature types to be rendered. It is responsible for evaluating attributes * of a VPF feature and determining if a particular feature matches what * this priority holder represents. It can then provide an OMGraphicList for * those features that match its attribute conditions. * * @author dietrick */ protected static abstract class FeaturePriorityHolder { /** * The type of the feature, i.e. point, line, area */ protected char type; /** * The feature code FACC for this kind of feature. */ protected String facc; /** * The OMGraphicList containing all the matching feature OMGraphics. */ protected OMGraphicList list; /** * The dimension of icons created for point OMGraphics. */ protected int dim = DEFAULT_ICON_SIZE; protected float sizePercent = 1f; protected float xoffPercent = 0f; protected float yoffPercent = 0f; /** * A handle to any debug FACC code listed by the warehouse, so that a * specific type of feature can be singled out for debugging. */ protected String debugFacc = null; protected FeaturePriorityHolder(String type, String facc, VPFAutoFeatureGraphicWarehouse warehouse) { this.type = getType(type); this.facc = facc; this.dim = warehouse.getIconSize(); if (warehouse.debugFacc != null && warehouse.debugFacc.equals(facc)) { debugFacc = warehouse.debugFacc; } } public OMGraphicList getList() { if (debugFacc != null && list != null) { if (logger.isLoggable(Level.FINE)) { logger.fine(list.getDescription()); } } return list; } public String getFacc() { return facc; } String getDebugFacc() { return debugFacc; } public void resetList() { if (list != null) { list.clear(); } } public void updateLocation(String size, String xoff, String yoff) { sizePercent = getValue(size, 1f); xoffPercent = getValue(xoff, 0f); yoffPercent = getValue(yoff, 0f); } protected float getValue(String s, float def) { float ret = def; if (s != null) { try { ret = Float.parseFloat(s); } catch (NumberFormatException nfe) { } } return ret; } /** * Used to match feature entries with PriorityHolder. * * @param facc * @param fci * @param row * @return true if feature entry matches PriorityHolder conditions. */ public abstract boolean matches(String facc, FeatureClassInfo fci, List<Object> row); /** * Used to match symbol codes with PriorityHolder during initialization * of PriorityHolders. * * @param facc * @param type * @param conditions * @param size percent of dim setting to use for size of symbol (0-1f) * @param xoff percent off center of dim setting to use for x origin of * symbol (0 is centered, positive is right) * @param yoff percent off center of dim setting to use for x origin of * symbol (0 is centered, positive is down) * @return true of feature entry matches PriorityHolder conditions. */ public abstract boolean matches(String facc, char type, String conditions, String symbolFileName, String size, String xoff, String yoff); protected abstract void add(OMGraphic omg); protected static class Basic extends FeaturePriorityHolder { protected GeoSymAttExpression expression; protected String conditions; protected String symbolParentDir; protected String symbolExt; protected String[] cgmTitle; protected CGMDisplay[] cgmDisplay; protected BufferedImage icon; protected Basic(String type, String facc, String cond, VPFAutoFeatureGraphicWarehouse warehouse) { super(type, facc, warehouse); if (cond != null && cond.trim().length() > 0) { this.conditions = cond.replace(" ", ""); expression = new GeoSymAttExpression(this.conditions, warehouse); } } public String toString() { return type + "|" + facc + "|" + conditions; } /** * Needs to be called before matches is called in init(). */ public void setCGMPath(String parent, String append) { symbolParentDir = parent; symbolExt = append; } public Image getIcon() { if (icon == null) { try { if (debugFacc != null) { logger.info("initializing cgm for " + toString()); } if (cgmTitle == null) { logger.fine("no title for " + toString()); } else { cgmDisplay = new CGMDisplay[cgmTitle.length]; for (int i = 0; i < cgmTitle.length; i++) { CGM cgm = new CGM(cgmTitle[i]); if (debugFacc != null) { logger.info(" using " + cgmTitle[i]); } cgmDisplay[i] = new CGMDisplay(cgm); // Rendering the icon will load cgmDisplay with // cgm // parameters // (fill paint, line paint, etc); icon = cgmDisplay[i].getBufferedImage((int) (dim * sizePercent), (int) (dim * sizePercent)); } } } catch (IOException ioe) { logger.fine("Couldn't load CGM files: " + cgmTitle[0] + "; first of " + cgmTitle.length); } } return icon; } /** * Used to match feature entries with PriorityHolder. * * @param facc * @param fci * @param row * @return true of feature matches conditions of PriorityHolder. */ public boolean matches(String facc, FeatureClassInfo fci, List<Object> row) { boolean ret = false; char type = fci.getFeatureType(); if (type == CoverageTable.EPOINT_FEATURETYPE || type == CoverageTable.CPOINT_FEATURETYPE) { type = CoverageTable.UPOINT_FEATURETYPE; } if (facc.equals(this.facc) && this.type == type) { if (expression != null) { ret = expression.evaluate(fci, row); } else { ret = true; } } return ret; } /** * Used to match symbol codes with PriorityHolder. * * @param facc * @param type * @param conditions * @return true if feature matches conditions of PriorityHolder. */ public boolean matches(String facc, char type, String conditions, String symbolFileName, String size, String xoff, String yoff) { boolean basicMatch = this.facc.equals(facc) && type == this.type; boolean conditionMatch = ((this.conditions == null || this.conditions.trim().length() == 0) && (conditions == null || conditions.trim() .length() == 0)) || (this.conditions != null && this.conditions.equals(conditions)); boolean ret = basicMatch && conditionMatch; if (ret) { Vector<String> names = PropUtils.parseSpacedMarkers(symbolFileName); cgmTitle = new String[names.size()]; for (int i = 0; i < names.size(); i++) { cgmTitle[i] = symbolParentDir + "/" + names.get(i) + symbolExt; updateLocation(size, xoff, yoff); } } return ret; } public void add(OMGraphic omg) { if (list == null) { list = new OMGraphicList(); } // Also makes sure cgmDisplay is initialized Image icon = getIcon(); if (cgmDisplay != null) { if (omg instanceof OMPoint.Image) { ((OMPoint.Image) omg).setImage(icon); } else if (omg instanceof OMRasterObject) { ((OMRasterObject) omg).setImage(icon); } else if (omg instanceof OMPoly) { OMPoly omp = (OMPoly) omg; if (!omp.isPolygon()) { // This check is necessary of the cgms are not found if (cgmDisplay[0] != null) { omp.setLinePaint(cgmDisplay[0].getLineColor()); omp.setStroke(new BasicStroke(1)); omp.setFillPaint(OMColor.clear); } } else { if (cgmDisplay.length == 1 && cgmDisplay[0] != null) { omp.setFillPaint(cgmDisplay[0].getFillColor()); omp.setLinePaint(cgmDisplay[0].getFillColor()); } else if (cgmDisplay.length > 1 && cgmDisplay[1] != null) { omp.setFillPaint(cgmDisplay[1].getFillColor()); omp.setLinePaint(cgmDisplay[1].getFillColor()); } } } } list.add(omg); } /* * (non-Javadoc) * * @see com.bbn.openmap.layer.vpf.VPFAutoFeatureGraphicWarehouse. * PriorityHolder #getConditions() */ public String getConditions() { return conditions; } } /** * A Compound FeaturePriorityHolder is used for buoys and other features * that have parts added to their representation based on their feature * attributes. It contains a list of Basic FeaturePriorityHolders, and * each one adds its touch to the resulting OMGraphic as needed. * * @author dietrick */ protected static class Compound extends FeaturePriorityHolder implements ImageObserver { protected List<FeaturePriorityHolder.Basic> parts = new ArrayList<FeaturePriorityHolder.Basic>(); protected BufferedImage icon; protected Compound(String type, String facc, VPFAutoFeatureGraphicWarehouse warehouse) { super(type, facc, warehouse); } public String toString() { return "Compound: " + type + "|" + facc; } public void addPart(FeaturePriorityHolder.Basic part) { parts.add(part); } /** * Used to match features with PriorityHolder. We need to do a * little more work here, to build up an image that matches the all * of the attributes set on this feature. So if the feature matches * at the first level, walk through the parts and draw on top of it. * * @param facc * @param fci * @param row * @return true if feature matches conditions of PriorityHolder. */ public boolean matches(String facc, FeatureClassInfo fci, List<Object> row) { boolean ret = false; int partCount = 0; char type = fci.getFeatureType(); if (type == CoverageTable.EPOINT_FEATURETYPE || type == CoverageTable.CPOINT_FEATURETYPE) { type = CoverageTable.UPOINT_FEATURETYPE; } /** * Building up the current image based on the attributes. Give * each part a chance to evaluate whether it should be rendered * into the image or not. */ if (facc.equals(this.facc) && this.type == type) { BufferedImage image = new BufferedImage(dim, dim, BufferedImage.TYPE_INT_ARGB); Graphics2D g = (Graphics2D) image.getGraphics(); for (FeaturePriorityHolder.Basic part : parts) { if (part.expression != null) { boolean partRet = part.expression.evaluate(fci, row); if (partRet) { Image im = part.getIcon(); g.drawImage(im, (int) (part.xoffPercent * dim), (int) (part.yoffPercent * dim), this); ret = true; partCount++; } } else { Image im = part.getIcon(); g.drawImage(im, (int) (part.xoffPercent * dim), (int) (part.yoffPercent * dim), this); ret = true; partCount++; } } icon = image; } return ret; } /** * Used to match symbol codes with PriorityHolder. * * @param facc * @param type * @param conditions * @return true if feature matches conditions of PriorityHolder. */ public boolean matches(String facc, char type, String conditions, String symbolFileName, String size, String xoff, String yoff) { boolean basicMatch = this.facc.equals(facc) && type == this.type; boolean conditionMatch = false; /** * We need to step through each part so that each part can find * it's symbol. */ for (FeaturePriorityHolder.Basic part : parts) { conditionMatch = part.matches(facc, type, conditions, symbolFileName, size, xoff, yoff); if (conditionMatch) { break; } } return basicMatch && conditionMatch; } public void add(OMGraphic omg) { if (list == null) { list = new OMGraphicList(); } if (icon != null) { if (omg instanceof OMPoint.Image) { ((OMPoint.Image) omg).setImage(icon); list.add(omg); } else if (omg instanceof OMRasterObject) { ((OMRasterObject) omg).setImage(icon); list.add(omg); } } } /* * (non-Javadoc) * * @see java.awt.image.ImageObserver#imageUpdate(java.awt.Image, * int, int, int, int, int) */ public boolean imageUpdate(Image img, int infoflags, int x, int y, int width, int height) { return false;// all set } } } }