package atmsdriver.model; import atmsdriver.batchbuilder.TrafficLaneEvent; import atmsdriver.model.LoopDetector.DOTCOLOR; import atmsdriver.model.Station.DIRECTION; import java.io.File; import java.io.FileNotFoundException; import java.io.IOException; import java.io.PrintWriter; import java.io.StringWriter; import java.io.Writer; import java.net.Socket; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Scanner; import java.util.Set; import java.util.logging.Level; import java.util.logging.Logger; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.transform.OutputKeys; import javax.xml.transform.Transformer; import javax.xml.transform.TransformerFactory; import javax.xml.transform.dom.DOMSource; import javax.xml.transform.stream.StreamResult; import org.w3c.dom.Document; import org.w3c.dom.Element; import tmcsim.common.SimulationException; /** * The Highways class aggregates all Highway instances within a geographic * region, and all of the FEPLines within an electronic detector network, in the * same geographic region. An instance of Highways.java comprises the underlying * model for the ATMSDriver application. * * Highways uses method writeToFEP() to communicate with the FEP Simulator. It * creates a socket client which sends the FEP Simulator a highways status * message over the socket. This message is sent in the format required by the * FEP Simulator. * * Currently, there is no driving logic within the highways class. It is only * done through an instance of the ConsoleDriver.java class. Eventually, it will * receive incident data (from exchange.xml or better yet, directly from the * TMCSimulator itself) and drive the highways using resident logic. * * @author John A. Torres */ final public class Highways { final private String FEPHostName; final private int FEPPortNum; final private List lines; final public List highways; public Highways(String highwaysMapFileName, String FEPHostName, int FEPPortNum) { // load FEP Lines lines = loadLines(highwaysMapFileName); // configure and load highways this.highways = configureHighways(); // write to FEP host and port number this.FEPHostName = FEPHostName; this.FEPPortNum = FEPPortNum; } private ArrayList configureHighways() { System.out.println("Loading highways..."); // The list of highways to return ArrayList highways = new ArrayList(); // map of hwy number to its list of stations Map> highwayMap = new HashMap<>(); // iterate through FEPLines and get data to add to the above map for (FEPLine line : lines) { // grab all stations from the current FEPLine ArrayList lineStations = (ArrayList) line.stations; // iterate through each station in the list of stations for (Station station : lineStations) { Integer hwyNum = station.routeNumber; // if the map does not contain an entry for the highway, create // a new entry (key/value pair) for the highway and instantiate // the empty list of stations if (!highwayMap.containsKey(hwyNum)) { ArrayList stnList = new ArrayList<>(); stnList.add(station); highwayMap.put(hwyNum, stnList); } // if the map does have an entry for the highway, add the current // station to its list of stations else { highwayMap.get(hwyNum).add(station); } } } // get the set of highway numbers Set hwyKeys = highwayMap.keySet(); // get the highway number and associated stations and create a new hwy // and add the hwy to this.highways for (Integer hwyKey : hwyKeys) { ArrayList hwyStations = highwayMap.get(hwyKey); Collections.sort(hwyStations); System.out.println("Loaded highway " + hwyKey + "..."); highways.add(new Highway(hwyKey, hwyStations)); } System.out.println(""); return highways; } /** * Applies specified color to the specified highway stretch. Route number * and direction specify the highway. Postmile and range specify the stretch * of specified highway. Dot color is the color to be applied to the * stretch. * * @param routeNumber highway route number * @param direction highway direction * @param postmile origin postmile value * @param range range from origin postmile * @param dotColor the color to be applied to specified highway stretch */ public void applyColorToHighwayStretch(Integer routeNumber, Station.DIRECTION direction, Double postmile, Double range, LoopDetector.DOTCOLOR dotColor) { System.out.println("Applying " + dotColor.name() + " dots to highway " + routeNumber + " " + direction.name() + " at postmile " + postmile + " with a range of " + range + " miles..."); // Get the highway by route number Highway highway = getHighwayByRouteNumber(routeNumber); // start value for highway section, and end value for highway section // by postmile Double startPost; Double endPost; // postmiles increase from s to n and w to e // if the direction is south or west if (direction.equals(Station.DIRECTION.SOUTH) || direction.equals(Station.DIRECTION.WEST)) { // add range value to startPost to get // the end postmile value of the highway section startPost = postmile; endPost = postmile + range; // iterate through the stations, if within the specified highway // stretch, update the station by direction and apply dot color for (Station station : highway.stations) { if (station.postmile >= startPost && station.postmile <= endPost) { station.updateByDirection(direction, dotColor); } } } // if the direction is north or east else { // subtract range value from startPost // to get the end postmile value of the highway section startPost = postmile; endPost = postmile - range; // iterate through the stations, if within the specified highway // section, update the station by direction and apply dot color for (Station station : highway.stations) { if (station.postmile <= startPost && station.postmile >= endPost) { station.updateByDirection(direction, dotColor); } } } System.out.println(""); } /** * Loads all FEPLines from the specified highways map file. * * @param highwaysMapFileName * @return List of FEPLines */ private ArrayList loadLines(String highwaysMapFileName) { ArrayList lines = new ArrayList<>(); try { Scanner sc = new Scanner(new File(highwaysMapFileName)); // first line of file contains number of FEP Lines String firstLine = sc.nextLine(); Scanner linesc = new Scanner(firstLine); int numLines = linesc.nextInt(); linesc.close(); // FOR each FEP Line for (int i = 0; i < numLines; i++) { lines.add(loadLine(sc)); } sc.close(); } catch (FileNotFoundException ex) { Logger.getLogger(Highways.class.getName()).log(Level.SEVERE, null, ex); } return lines; } /** * Loads a single FEP Line from the highways map file. * * @param sc scanner at the current FEPLine line * @return FEPLine */ private FEPLine loadLine(Scanner sc) { String line = sc.nextLine(); Scanner scline = new Scanner(line); // Get the attributes of this FEP Line int lineNum = scline.nextInt(); int count = scline.nextInt(); int numStations = scline.nextInt(); // initialze stations array ArrayList stations = new ArrayList<>(); // Read all the stations for thie FEP Line for (int i = 0; i < numStations; i++) { stations.add(loadStation(sc, lineNum)); } return new FEPLine(lineNum, stations, count); } /** * Loads a single Station from the highways map file * @param sc scanner at the current station line * @param lineNum the FEPLine number for the station * @return Station */ private Station loadStation(Scanner sc, int lineNum) { String line = sc.nextLine(); Scanner scline = new Scanner(line); int ldsID = scline.nextInt(); int drop = scline.nextInt(); int fwy = scline.nextInt(); DIRECTION dir = DIRECTION.toDirection(scline.next()); double postmile = scline.nextDouble(); int numLoops = scline.nextInt(); String location = getStationLoc(line); ArrayList loops = new ArrayList<>(); for (int i = 0; i < numLoops; i++) { loops.add(loadLoop(sc)); } return new Station(lineNum, ldsID, drop, location, loops, fwy, dir, postmile); } /** * Loads a single loop from the highways map file * * @param sc scanner at the current loop line * @return LoopDetector */ private LoopDetector loadLoop(Scanner sc) { String line = sc.nextLine(); Scanner scline = new Scanner(line); int loopID = scline.nextInt(); String loopLocID = scline.next(); String loopLoc = scline.next(); scline.close(); return new LoopDetector(loopID, loopLocID, loopLoc); } /** * Scans the LoopDetector line and grabs the String location from the line. * * @param line the line containing the location * @return A String loop location. */ private String getLoopLoc(String line) { Scanner sc = new Scanner(line); sc.nextInt(); // GRABS FROM CURRENT TO END OF LINE sc.useDelimiter("\\z"); String loc = sc.next().trim(); sc.close(); return loc; } /** * Scans the Station line and grabs the String location from the line. * * @param line the line containing the location * @return A String station location. */ private String getStationLoc(String line) { Scanner scline = new Scanner(line); scline.nextInt(); scline.nextInt(); scline.nextInt(); scline.next(); scline.nextDouble(); scline.nextInt(); // GRABS FROM CURRENT TO END OF LINE scline.useDelimiter("\\z"); String loc = scline.next().trim(); scline.close(); return loc; } /** * Creates a socket client that writes the Highways data to the FEP Simulator. * * @throws SimulationException */ public void writeToFEP() throws SimulationException { try { // Create the socket to the FEP Simulator Socket sock = new Socket(FEPHostName, FEPPortNum); PrintWriter out = new PrintWriter(sock.getOutputStream(), true); // Print the number of bytes the highways data message contains System.out.println("BYTES: " + this.toCondensedFormat(false).toCharArray().length + 1); // Write the highways data over the socket out.println(this.toCondensedFormat(false)); // close the socket sock.close(); } catch (java.net.ConnectException ex) { //Logger.getLogger(Highways.class.getName()).log(Level.SEVERE, null, ex); System.out.println("writeToFEP() can't connect, no data sent to FEP."); throw new SimulationException(SimulationException.BINDING); } catch (IOException ex) { //Logger.getLogger(Highways.class.getName()).log(Level.SEVERE, null, ex); System.out.println("Highway Model failed writing to FEPSim."); throw new SimulationException(SimulationException.BINDING); } } /** Returns a string of highways data. If MetaDataOnly is true, you get a full * dump of the highways meta data, which does not include dynamic loop values, * and does include the string location names. If MetaDataOnly is false, * dynamic loop values are included, and unnecessary information like string * location values are included. * * The FEPSimulator takes in the toCondensedFormat() output, with a MetaDataOnly * value of false, over the socket. * * The MetaDataOnly flag should be used to get a full dump of the highways * information. This was used to get the highways_fullmap.txt output. * * @param MetaDataOnly Whether you want meta data, or a full dump for FEPSim * @return String, highways data in condensed format * * Example toCondensedFormat(MetaDataOnly = false) output: * * 43 // "number of lines" * 32 0 13 // "line id" "count num" "number of stations" * 1210831 1 5 S 0.9 8 // "station id" "drop num" "route num"... * // ..."direction" "postmile" "number of loops" * 1210832 0.0 0 ML_1 // "loop id" "occ" "vol" * 1210833 0.0 0 ML_2 // .. * 1210834 0.0 0 ML_3 // .. * 1210835 0.0 0 ML_4 // .. * 1210836 0.0 0 PASSAGE // .. * 1210837 0.0 0 DEMAND // .. * 1210838 0.0 0 QUEUE // .. * 1210839 0.0 0 RAMP_OFF // .. * ... * * Example toCondensedFormat(MetaDataOnly = true) output: * * 43 // "number of lines" * 32 0 13 // "line id" "count num" "number of stations" * 1210831 1 5 S 0.9 8 CALAFIA // "station id" "drop num" "route num"... * // ..."direction" "postmile"... * // ..."number of loops" "string location" * 1210832 ML_1 // "loop id" "loop location" * 1210833 ML_2 // " " * 1210834 ML_3 // " " * 1210835 ML_4 // " " * 1210836 PASSAGE // " " * 1210837 DEMAND // " " * 1210838 QUEUE // " " * 1210839 RAMP_OFF // " " * ... */ public String toCondensedFormat(boolean MetaDataOnly) { // first line: number of FEPLines StringBuilder build = new StringBuilder(); build.append(lines.size()); build.append("\n"); // append each fep line to the string for(FEPLine line : lines) { build.append(line.toCondensedFormat(MetaDataOnly)); } // return the full condensed format string return build.toString(); } /** * Returns the Highways model data in XML format. * * @return highways data in XML format */ public String toXML() { String xml = null; try { DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); DocumentBuilder builder = factory.newDocumentBuilder(); Document theDoc = builder.newDocument(); Element networkElement = theDoc.createElement(XML_TAGS.NETWORK.tag); theDoc.appendChild(networkElement); for (FEPLine line : lines) { line.toXML(networkElement); } Transformer tf = TransformerFactory.newInstance().newTransformer(); tf.setOutputProperty(OutputKeys.ENCODING, "UTF-8"); tf.setOutputProperty(OutputKeys.INDENT, "yes"); tf.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "2"); Writer out = new StringWriter(); tf.transform(new DOMSource(theDoc), new StreamResult(out)); xml = out.toString(); out.close(); } catch (Exception ex) { Logger.getLogger(Highways.class.getName()).log(Level.SEVERE, null, ex); } return xml; } /** * Returns a highway by given highway number. * * @param routeNum * @return Highway with specified route number, or null if no highway with * the specified route num */ public Highway getHighwayByRouteNumber(Integer routeNum) { Highway returnHwy = null; // search through highways and check routeNums for (Highway hwy : highways) { if (hwy.routeNumber.equals(routeNum)) { returnHwy = hwy; break; } } return returnHwy; } /** Return a string representation of the Highways */ public String toString() { StringBuilder result = new StringBuilder(); for (Highway hwy: highways) { // Consider each route direction for (DIRECTION dir: DIRECTION.values()) { String rowLabel = ""+String.format("%3s ",hwy.routeNumber)+dir.getLetter()+' '; StringBuilder lineout = new StringBuilder(); // Examine every station on this highway and direction for (Station stat: hwy.stations) { //lineout.append("" + dir.getLetter() + stat.postmile); lineout.append(stat.getColorByDirection(dir)); //lineout.append(" "); } // See if there were stations for this direction String checkMe = lineout.toString().trim(); // if any stations were colored, output the line if (checkMe.length() > 1) { result.append(rowLabel); result.append(lineout + "\n"); } } } result.append("\n"); return result.toString(); } /** * Generates the route number list, used for user input validation. * @return list of route numbers. */ public List getAllRouteNums() { ArrayList routeNums = new ArrayList<>(); // add the route number for each highway to the list for(Highway hwy : highways) { routeNums.add(hwy.routeNumber); } return routeNums; } /** * XML tags used in writeToXML() */ private static enum XML_TAGS { NETWORK("Network"); String tag; private XML_TAGS(String n) { tag = n; } } public void reset() { for(FEPLine line : lines) { for(Station stn : line.stations) { for(LoopDetector ld : stn.loops) { ld.occ = 0; ld.vol = 0; } } } } public void applyTrafficLaneEvent(TrafficLaneEvent event) { Integer routeNum = event.routeNum; Highway hwy = getHighwayByRouteNumber(routeNum); for(Station stn: hwy.stations) { if(stn.equals(event.station)) { for(LoopDetector ld : stn.loops) { if(ld.equals(event.loopDetector)) { ld.occ = event.color.occupancy(); ld.vol = event.color.volume(); break; } } break; } } } }