package tmcsim.highwaymodel; import atmsdriver.trafficeventseditor.TrafficLaneEvent; import tmcsim.highwaymodel.Station.DIRECTION; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; 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; /** * Highways represents California state highways supervised by CalTrans. * The highways have sensors to detect traffic flow. In the simulation * the traffic flow is artificially created and Highways maintains the * current levels of traffic flow throughout the network. * * Highways builds an internal representation of the highway network * from VDS data (obtained from PeMS) The current state of the network * is output to a json file for use by CPTMS. * * @author John A. Torres, jdalbey */ final public class Highways { final public List highways; public Highways(String highwaysMapFileName) { // build highways data structure this.highways = buildHighwayNetwork(highwaysMapFileName); } private ArrayList buildHighwayNetwork(String highwaysMapFileName) { // NOTE: Could this method be streamlined? Is it necessary to have the // second data structure? System.out.println("Building highways..."); // The list of highways to return ArrayList highways = new ArrayList(); // map of hwy number to its list of stations Map> highwayMap = new HashMap<>(); // Open the postmile file and scan each line File file = new File(highwaysMapFileName); try { Scanner scanner = new Scanner(file); while (scanner.hasNext()) { Station station = loadStation(scanner); 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); } } }catch (FileNotFoundException e) { e.printStackTrace(); } // 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 + " with " + hwyStations.size() + " stations."); highways.add(new Highway(hwyKey, hwyStations)); } System.out.println(""); return highways; } /** Search for a station with the given attributes * * @param routeNumber * @param direction * @param postmile * @return the desired station, or null if not found. */ public Station findStation(Integer routeNumber, Station.DIRECTION direction, Double postmile) { // Get the highway by route number Highway highway = getHighwayByRouteNumber(routeNumber); if (highway == null) { Logger.getLogger(Highways.class.getName()).log(Level.SEVERE, "Highway "+routeNumber+" not found in findStation()", ""); return null; } //Search the stations on this highway for a match for (Station station : highway.stations) { if (station.matches(direction, postmile)) { return station; } } return null; } /** * Applies specified color to the specified highway stretch. Route number * and direction specify the highway. Postmile and range specify the stretch * of specified highway. * The purpose of this method is to modify the highway state to represent * traffic conditions created by the Traffic Manager. These conditions * originate in the traffic events file which specifies traffic congestion arising * during the simulation. NOTE: Since these events describe congestion, * the direction that the color should be applied is the REVERSE of the * regular flow of traffic. * So a request to apply red to 55 S from 8.5 for 2 miles means * the stretch 8.5 -> 10.5 because sobo traffic normally flows toward * decreasing postmiles and we want to do the reverse. * @param routeNumber highway route number * @param direction highway direction (for normal traffic flow) * @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); // This check added to fix defect #118. Handles situation when the // events file specifies a highway that doesn't exist in the network. if (highway == null) { Logger.getLogger(Highways.class.getName()).log(Level.SEVERE, "Highway "+routeNumber+" not found trying to applyColor", ""); return; } // start value for highway section, and end value for highway section // by postmile Double startPost; Double endPost; double epsilon = 0.001; // postmiles increase from s to n and w to e // S or W directions backup in a positive postmile direction 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); } } } // N or E directions backup in a negative postmile direction 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 a single Station from the postmile coordinates file * @param sc scanner at the current station line * @return Station */ private Station loadStation(Scanner sc) { String line = sc.nextLine(); Scanner scline = new Scanner(line); scline.useDelimiter(","); String route = scline.next(); // FWY DIR POSTMILE scline.next(); // skip lat scline.next(); // skip long String description = scline.next(); // the description of the location Scanner rteScan = new Scanner(route); int fwy = rteScan.nextInt(); DIRECTION dir = DIRECTION.toDirection(rteScan.next()); double postmile = rteScan.nextDouble(); ArrayList loops = new ArrayList<>(); // For now we'll use just a dummy loop detector LoopDetector detector = new LoopDetector(1,"ML","ML_1"); loops.add(detector); return new Station(11, 123, 99, description, loops, fwy, dir, postmile); } /** * 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 - for debugging */ public String toString() { StringBuilder result = new StringBuilder(); for (Highway hwy: highways) { // Consider each route direction for (DIRECTION dir: hwy.availDirs) { String rowLabel = ""+String.format("%3s ",hwy.routeNumber)+dir.getLetter()+' '; StringBuilder lineout = new StringBuilder(); // Examine every station on this highway and direction for (Station station: hwy.stations) { if (station.direction.equals(dir)) { //lineout.append("" + dir.getLetter() + stat.postmile); lineout.append(station.getColor().symbol()); //lineout.append(" "); } else { 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(); } /** Return a json representation of the Highways, readable by Google Maps */ public String toJson() { // TODO: move loading this file to init method so it doesn't get // called every time. PostmileCoords pmList = new PostmileCoords(); FileInputStream fis = null; try { fis = new FileInputStream("config/vds_data/postmile_coordinates.txt"); } catch (FileNotFoundException ex) { Logger.getLogger(Highways.class.getName()).log(Level.SEVERE, null, ex); } Scanner s = new Scanner(fis).useDelimiter("\\A"); pmList.load(s); Collections.sort(highways); // Sort the highways for easier inspection String header = "{\n" + " \"type\": \"FeatureCollection\",\n" + " \"features\": ["; StringBuilder result = new StringBuilder(); result.append(header); for (Highway hwy: highways) { // Examine every station on this highway StringBuilder lineout = new StringBuilder(); Collections.sort(hwy.stations, new StationComparator()); for (Station stat: hwy.stations) { String pmID = "" + hwy.routeNumber + " " + stat.direction.getLetter() + " " + stat.postmile; PostmileCoords.Postmile currentPM = pmList.find(pmID); if (currentPM == null) { Logger.getLogger(Highways.class.getName()).log(Level.INFO, "Postmile Coords lookup couldn't find Station: "+pmID, " "); } if (currentPM != null) { //lineout.append("" + dir.getLetter() + stat.postmile); //lineout.append(stat.getColorByDirection(dir)); String outString = currentPM.toJson(); // replace the color code with the color name String colorName=stat.getColor().htmlColor(); outString = outString.replace("desiredcolor",colorName); lineout.append(outString); lineout.append(" "); } } //result.append(rowLabel); result.append(lineout + "\n"); } // remove last trailing comma result.replace(result.lastIndexOf(","), result.lastIndexOf(",") + 1, " " ); 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; } } /** Reset all the traffic levels to free flowing (green) */ public void reset() { for(Highway hwy: highways) { for(Station stn : hwy.stations) { for(LoopDetector ld : stn.loops) { ld.setAttributes(LoopDetector.DOTCOLOR.GREEN); } } } } 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; } } } }