/* * <copyright> * Copyright 2015 BBN Technologies * </copyright> */ package com.bbn.openmap.omGraphics.rule; import java.util.Collections; import java.util.LinkedList; import java.util.List; import java.util.Properties; import java.util.Vector; import com.bbn.openmap.OMComponent; import com.bbn.openmap.omGraphics.OMGraphic; import com.bbn.openmap.omGraphics.OMGraphicList; import com.bbn.openmap.omGraphics.OMTextLabeler; import com.bbn.openmap.proj.Projection; import com.bbn.openmap.util.PropUtils; /** * The RuleHandler manages a set of Rules and will evaluate OMGraphics against * them for a current projection. * <p> * For List rules, something like this: * * <pre> * # Rule marker names specified in space-separated list * neroads.rules=rule0 rule1 * # global scale settings can be used so work is only performed within scale range of minScale/maxScale * neroads.maxScale=1000000f * * # rule0 definition: * # CLASS_RTE is a DBF column name * neroads.rule0.key=CLASS_RTE * # operation, if key value is less than 2 * neroads.rule0.op=lt * neroads.rule0.val=2 * # If rule is met, then actions can be performed: * # Column names can be added together in a label by specifying them in a space-separated list * neroads.rule0.label=PREFIX PRETYPE NAME TYPE SUFFIX * # Labels can have scale limits imposed, so they don't appear if map scale is * # greater than maxScale or less than minScale * neroads.rule0.label.maxScale=1000000 * # Visibility can be controlled with respect to scale as well * neroads.rule0.render.maxScale=1000000 * # Rendering attributes can be specified. * neroads.rule0.lineColor=FFFA73 * neroads.rule0.lineWidth=4 * neroads.rule0.mattingColor=55AAAAAA * * # rule1 definition: * neroads.rule1.key=CLASS_RTE * neroads.rule1.op=all * neroads.rule1.label=PREFIX PRETYPE NAME TYPE SUFFIX * neroads.rule1.label.maxScale=200000 * neroads.rule1.render.maxScale=500000 * neroads.rule1.lineColor=FFFFFF * neroads.rule1.lineWidth=3 * neroads.rule1.mattingColor=55AAAAAA * * # The render attribute is assumed to be true. You can hide OMGraphics by setting it to false. * </pre> * * @author dietrick */ public abstract class RuleHandler<T> extends OMComponent { List<Rule> rules; /** * Create a Rule object that knows how to interpret properties to create the * proper indices into the record List. */ public abstract Rule createRule(); /** * Return a record Map for a particular OMGraphic, like a properties table. * * @param omg OMGraphic being queried * @return Map of objects as attributes for the OMGraphic. */ public abstract T getRecordDataForOMGraphic(OMGraphic omg); public void setProperties(String prefix, Properties props) { super.setProperties(prefix, props); prefix = PropUtils.getScopedPropertyPrefix(prefix); String rulesString = props.getProperty(prefix + Rule.RuleListProperty); Vector<String> keysV = PropUtils.parseSpacedMarkers(rulesString); if (keysV != null && !keysV.isEmpty()) { List<Rule> rules = Collections.synchronizedList(new LinkedList<Rule>()); for (String ruleMarker : keysV) { Rule rule = createRule(); rule.setProperties(prefix + ruleMarker, props); rules.add(rule); } setRules(rules); } } public Properties getProperties(Properties props) { props = super.getProperties(props); String prefix = PropUtils.getScopedPropertyPrefix(this); StringBuffer ruleList = new StringBuffer(); int createdRuleNum = 1; for (Rule rule : getRules()) { String rulePrefix = rule.getPropertyPrefix(); // For rules created programmatically without a prefix, need to // create one. if (rulePrefix == null) { rulePrefix = "createdRulePrefix" + (createdRuleNum++); rule.setPropertyPrefix(prefix + rulePrefix); } if (rulePrefix.startsWith(prefix)) { rulePrefix = rulePrefix.substring(prefix.length()); if (rulePrefix.startsWith(".")) { rulePrefix = rulePrefix.substring(1); } } ruleList.append(rulePrefix).append(" "); rule.getProperties(props); } if (ruleList.length() > 0) { props.put(prefix + Rule.RuleListProperty, ruleList.toString()); } return props; } public void setRules(List<Rule> rules) { this.rules = rules; } public void addRule(Rule rule) { if (rule != null) { getRules().add(rule); } } public boolean removeRule(Rule rule) { if (rule != null) { return getRules().remove(rule); } return false; } public void clearRules() { getRules().clear(); } public List<Rule> getRules() { if (rules == null) { rules = Collections.synchronizedList(new LinkedList<Rule>()); } return rules; } /** * Used to help prevent consecutive repeat label values. */ protected String lastLabel; /** * This is the main call that a layer would use to modify/update an * OMGraphic based on dbf file contents. Tries to retrieve the index from * the attributes of the OMGraphic, and then checks the index and OMGraphic * to see how/if it should be rendered, as determined by the rules. * * @param omg the OMGraphic in question * @param labelList an OMGraphicList to add the label to, so it gets * rendered on top. * @param proj the current map projection, for scale appropriateness * determinations. * @return OMGraphic if it should be displayed, null if it shouldn't. */ public OMGraphic evaluate(OMGraphic omg, OMGraphicList labelList, Projection proj) { // Just check for rules first - if no rules defined, don't bother // reading the attributes. List<Rule> rules = getRules(); if (rules.isEmpty()) { return omg; } T record = getRecordDataForOMGraphic(omg); if (record == null) { return omg; } OMGraphic passedEval = null; for (Rule rule : rules) { passedEval = rule.evaluate(record, omg, proj); if (passedEval != null) { /** * Let's do some stuff with a label to minimize the number of * labels that might show up. */ Object labelObj = omg.getAttribute(OMGraphic.LABEL); if (labelObj instanceof OMTextLabeler) { String curLabel = ((OMTextLabeler) labelObj).getData(); if (lastLabel == null || (lastLabel != null && !lastLabel.equalsIgnoreCase(curLabel))) { labelList.add((OMTextLabeler) labelObj); } else { // The Rule adds the label to the OMGraphic, we'll // remove it so it doesn't get rendered underneath omg.removeAttribute(OMGraphic.LABEL); } lastLabel = curLabel; } break; } } // Might be null return passedEval; } }