package atmsdriver.model; import atmsdriver.model.LoopDetector.DOTCOLOR; import java.util.List; import org.w3c.dom.Document; import org.w3c.dom.Element; /** * A Station (LDS or Loop Detector Station) represents a group of lane detectors * across all lanes at a particular point on a highway. A station is * identified by its highway number and postmile. A station has an associated * direction used to establish which direction is Main and which is Opposite. * The MLTotVol and OppTotVol for a station can be dynamically updated. * A station has other attributes: lineNum, ldsID, drop, and location which are * used by the FEP. A station can be compared to other stations by its postmile. * * @author John A. Torres * @version 9/10/2017 */ public class Station implements Comparable { /* Static Station meta data */ final public int lineNum; final public int ldsID; // double check final public int drop; final public String location; final public List loops; final public int routeNumber; final public double postmile; final public DIRECTION direction; /* Dynamic Station data */ private int MLTotVol; private int OppTotVol; /* Constructor */ public Station(int lineNum, int ldsID, int drop, String location, List loops, int hwy, DIRECTION direction, double postmile) { this.lineNum = lineNum; this.ldsID = ldsID; this.drop = drop; this.loops = loops; this.location = location; this.postmile = postmile; this.direction = direction; this.routeNumber = hwy; this.MLTotVol = getMLTotVol(); this.OppTotVol = getOPPTotVol(); } /** * Calculates the total ML Volume. * * @return total ML volume. */ private int getMLTotVol() { int mlTotVol = 0; for(LoopDetector loop : loops) { if(loop.loopLocation.startsWith("ML")) { mlTotVol += loop.vol; } } return mlTotVol; } /** * Calculates the total OPP Volume * * @return total OPP volume. */ private int getOPPTotVol() { int oppTotVol = 0; for(LoopDetector loop : loops) { if(loop.loopLocation.startsWith("OS")) { oppTotVol += loop.vol; } } return oppTotVol; } /** * Returns the station metadata in condensed form. This is just a quick * script function to make a proper highway metadata configuration file, so * that we can read the network faster. * * @return station metadata */ public String getStationMeta() { StringBuilder build = new StringBuilder(); build.append(Integer.toString(this.ldsID)); build.append(" "); build.append(Integer.toString(this.drop)); build.append(" "); build.append(Integer.toString(this.routeNumber)); build.append(" "); build.append(this.direction.getLetter()); build.append(" "); build.append(Double.toString(this.postmile)); build.append(" "); build.append(Integer.toString(loops.size())); build.append(" "); build.append(this.location); build.append("\n"); for (LoopDetector loop : loops) { build.append(loop.getLoopMeta()); } return build.toString(); } /** * Compare this Station to another by postmile. Note: This might be better * as a Comparator since it checks only one field. */ @Override public int compareTo(Object otherStation) { // check for identity if (this == otherStation) { return 0; } // check that Object is of type Station, if not throw exception if (!(otherStation instanceof Station)) { throw new ClassCastException("A Station object expected."); } // get difference of values double otherStationPostmile = ((Station) otherStation).postmile; double val = this.postmile - otherStationPostmile; // set appropriate comparable return value int retval = 0; if (val > 0) { retval = 1; } else if (val < 0) { retval = -1; } return retval; } /** Determine which lane fields to update based on given direction * and update all the loop detectors with the given color. * @param direction desired highway direction * @param dotColor desired dot color */ public void updateByDirection(DIRECTION direction, DOTCOLOR dotColor) { String laneDir = "OS"; if(direction.equals(this.direction)) { laneDir = "ML"; } outputUpdateMessage(dotColor, laneDir); for(LoopDetector loop : loops) { if(loop.loopLocation.startsWith(laneDir)) { // UPDATE LOOP WITH VALUES int speed = 0; loop.updateLoop(dotColor.volume(), dotColor.occupancy(), speed); } } this.MLTotVol = getMLTotVol(); this.OppTotVol = getOPPTotVol(); } /** * Output for updateByDirection. Logs the update to the console. * * @param dotcolor * @param OPP_ML */ private void outputUpdateMessage(DOTCOLOR dotcolor, String OPP_ML) { System.out.printf("Updating %-3.3s %-5.5s %-3.3s lanes\t %-12.12s " + "at postmile %-6.6s to %-7.7s\n", Integer.toString(this.routeNumber), this.direction.name(), OPP_ML, this.location, Double.toString(this.postmile), dotcolor.name()); } /** * XML tags used for toXML() method. */ private static enum XML_TAGS { STATION("Station"), LDS_ID("LDS_ID"), LINE_NUM("Line_Num"), DROP("Drop"), LOOPS("Loops"), LOCATION("Location"), POST_MILE("Post_Mile"), DIRECTION("Direction"), FREEWAY("Freeway"), ML_TOT_VOL("ML_Tot_Vol"), OPP_TOT_VOL("Opp_Tot_Vol"); String tag; private XML_TAGS(String n) { tag = n; } } /** * Returns the Station data in XMLFormat. * * @param currElem The current XML element */ public void toXML(Element currElem) { Document theDoc = currElem.getOwnerDocument(); Element stationElement = theDoc.createElement(XML_TAGS.STATION.tag); currElem.appendChild(stationElement); Element ldsIDElement = theDoc.createElement(XML_TAGS.LDS_ID.tag); ldsIDElement.appendChild(theDoc.createTextNode(String.valueOf(this.ldsID))); stationElement.appendChild(ldsIDElement); Element lineNumElement = theDoc.createElement(XML_TAGS.LINE_NUM.tag); lineNumElement.appendChild(theDoc.createTextNode(String.valueOf(this.lineNum))); stationElement.appendChild(lineNumElement); Element dropElement = theDoc.createElement(XML_TAGS.DROP.tag); dropElement.appendChild(theDoc.createTextNode(String.valueOf(this.drop))); stationElement.appendChild(dropElement); Element locationElement = theDoc.createElement(XML_TAGS.LOCATION.tag); locationElement.appendChild(theDoc.createTextNode(this.location)); stationElement.appendChild(locationElement); Element postMileElement = theDoc.createElement(XML_TAGS.POST_MILE.tag); postMileElement.appendChild(theDoc.createTextNode(String.valueOf(this.postmile))); stationElement.appendChild(postMileElement); Element directionElement = theDoc.createElement(XML_TAGS.DIRECTION.tag); directionElement.appendChild(theDoc.createTextNode("" + this.direction.getLetter())); stationElement.appendChild(directionElement); Element freewayElement = theDoc.createElement(XML_TAGS.FREEWAY.tag); freewayElement.appendChild(theDoc.createTextNode(String.valueOf(this.routeNumber))); stationElement.appendChild(freewayElement); Element mlElement = theDoc.createElement(XML_TAGS.ML_TOT_VOL.tag); mlElement.appendChild(theDoc.createTextNode(String.valueOf(this.MLTotVol))); stationElement.appendChild(mlElement); Element oppElement = theDoc.createElement(XML_TAGS.OPP_TOT_VOL.tag); oppElement.appendChild(theDoc.createTextNode(String.valueOf(this.OppTotVol))); stationElement.appendChild(oppElement); Element loopsElement = theDoc.createElement(XML_TAGS.LOOPS.tag); stationElement.appendChild(loopsElement); for (LoopDetector loop : loops) { loop.toXML(loopsElement); } } /** * Enum for freeway direction. * * @author John A. Torres * @version 9/10/2017 */ public static enum DIRECTION { NORTH, SOUTH, EAST, WEST; // All the first letters of the values, in order. private static String allLetters = "NSEW"; /** * Return the first letter of this enum. * * @return String first letter of this enum. */ public String getLetter() { return this.toString().substring(0, 1); } /** * Returns a direction given its first character. * * @param letter the first character of a direction * @return direction corresponding to letter * @pre letter must be one of allLetters */ public static DIRECTION toDirection(String letter) { return values()[allLetters.indexOf(letter.charAt(0))]; } } }