source: tmcsimulator/trunk/src/atmsdriver/model/Highways.java @ 398

Revision 398, 24.8 KB checked in by jdalbey, 7 years ago (diff)

Fix defect 118, add test case that reveals defect.

Line 
1package atmsdriver.model;
2
3import atmsdriver.trafficeventseditor.TrafficLaneEvent;
4import atmsdriver.model.LoopDetector.DOTCOLOR;
5import atmsdriver.model.Station.DIRECTION;
6import java.io.File;
7import java.io.FileInputStream;
8import java.io.FileNotFoundException;
9import java.io.IOException;
10import java.io.PrintWriter;
11import java.io.StringWriter;
12import java.io.Writer;
13import java.net.Socket;
14import java.util.ArrayList;
15import java.util.Collections;
16import java.util.HashMap;
17import java.util.List;
18import java.util.Map;
19import java.util.Scanner;
20import java.util.Set;
21import java.util.logging.Level;
22import java.util.logging.Logger;
23import javax.xml.parsers.DocumentBuilder;
24import javax.xml.parsers.DocumentBuilderFactory;
25import javax.xml.transform.OutputKeys;
26import javax.xml.transform.Transformer;
27import javax.xml.transform.TransformerFactory;
28import javax.xml.transform.dom.DOMSource;
29import javax.xml.transform.stream.StreamResult;
30import org.w3c.dom.Document;
31import org.w3c.dom.Element;
32import tmcsim.common.SimulationException;
33
34/**
35 * The Highways class aggregates all Highway instances within a geographic
36 * region, and all of the FEPLines within an electronic detector network, in the
37 * same geographic region. An instance of Highways.java comprises the underlying
38 * model for the ATMSDriver application.
39 *
40 * Highways uses method writeToFEP() to communicate with the FEP Simulator. It
41 * creates a socket client which sends the FEP Simulator a highways status
42 * message over the socket. This message is sent in the format required by the
43 * FEP Simulator.
44 *
45 *
46 * @author John A. Torres
47 */
48final public class Highways
49{
50
51    final private String FEPHostName;
52    final private int FEPPortNum;
53   
54    final private List<FEPLine> lines;
55    final public List<Highway> highways;
56
57    public Highways(String highwaysMapFileName, String FEPHostName, int FEPPortNum)
58    {
59        // load FEP Lines
60        lines = loadLines(highwaysMapFileName);
61        // build highways data structure
62        this.highways = buildHighways();
63
64        // write to FEP host and port number
65        this.FEPHostName = FEPHostName;
66        this.FEPPortNum = FEPPortNum;
67    }
68
69    private ArrayList<Highway> buildHighways()
70    {
71        System.out.println("Building highways...");
72        // The list of highways to return
73        ArrayList<Highway> highways = new ArrayList<Highway>();
74       
75        // map of hwy number to its list of stations
76        Map<Integer, ArrayList<Station>> highwayMap = new HashMap<>();
77       
78        // iterate through FEPLines and get data to add to the above map
79        for (FEPLine line : lines)
80        {
81            // grab all stations from the current FEPLine
82            ArrayList<Station> lineStations = (ArrayList<Station>) line.stations;
83            // iterate through each station in the list of stations
84            for (Station station : lineStations)
85            {
86                Integer hwyNum = station.routeNumber;
87                // if the map does not contain an entry for the highway, create
88                // a new entry (key/value pair) for the highway and instantiate
89                // the empty list of stations
90                if (!highwayMap.containsKey(hwyNum))
91                {
92                    ArrayList<Station> stnList = new ArrayList<>();
93                    stnList.add(station);
94                    highwayMap.put(hwyNum, stnList);
95                } 
96                // if the map does have an entry for the highway, add the current
97                // station to its list of stations
98                else
99                {
100                    highwayMap.get(hwyNum).add(station);
101                }
102            }
103        }
104       
105        // get the set of highway numbers
106        Set<Integer> hwyKeys = highwayMap.keySet();
107        // get the highway number and associated stations and create a new hwy
108        // and add the hwy to this.highways
109        for (Integer hwyKey : hwyKeys)
110        {
111            ArrayList<Station> hwyStations = highwayMap.get(hwyKey);
112            Collections.sort(hwyStations);
113            System.out.println("Loaded highway " + hwyKey + " with " +
114                    hwyStations.size() + " stations.");
115            highways.add(new Highway(hwyKey,
116                    hwyStations));
117        }
118        System.out.println("");
119        return highways;
120    }
121
122    /** Search for a station with the given attributes
123     *
124     * @param routeNumber
125     * @param direction
126     * @param postmile
127     * @return the desired station, or null if not found.
128     */
129    public Station findStation(Integer routeNumber, Station.DIRECTION direction,
130            Double postmile)
131    {
132        // Get the highway by route number
133        Highway highway = getHighwayByRouteNumber(routeNumber);
134        if (highway == null)
135        {
136            Logger.getLogger(Highways.class.getName()).log(Level.SEVERE, 
137                    "Highway "+routeNumber+" not found in findStation()", "");
138            return null;
139        }
140        //Search the stations on this highway for a match
141        for (Station station : highway.stations)
142        {
143            if (station.matches(direction, postmile))
144            {
145                return station;
146            }
147        }
148        return null;
149    }
150    /**
151     * Applies specified color to the specified highway stretch. Route number
152     * and direction specify the highway. Postmile and range specify the stretch
153     * of specified highway.
154     * The purpose of this method is to modify the highway state to represent
155     * traffic conditions created by the Traffic Manager.  These conditions
156     * originate in the traffic events file which specifies traffic congestion arising
157     * during the simulation.  NOTE: Since these events describe congestion,
158     * the direction that the color should be applied is the REVERSE of the
159     * regular flow of traffic. 
160     * So a request to apply red to 55 S from 8.5 for 2 miles means
161     * the stretch 8.5 -> 10.5 because sobo traffic normally flows toward
162     * decreasing postmiles and we want to do the reverse.
163     * @param routeNumber highway route number
164     * @param direction highway direction (for normal traffic flow)
165     * @param postmile origin postmile value
166     * @param range range from origin postmile
167     * @param dotColor the color to be applied to specified highway stretch
168     */
169    public void applyColorToHighwayStretch(Integer routeNumber, Station.DIRECTION direction,
170            Double postmile, Double range, LoopDetector.DOTCOLOR dotColor)
171    {
172        System.out.println("Applying " + dotColor.name() + " dots to highway "
173                + routeNumber + " " + direction.name() + " at postmile "
174                + postmile + " with a range of " + range + " miles...");
175
176        // Get the highway by route number
177        Highway highway = getHighwayByRouteNumber(routeNumber);
178        // This check added to fix defect #118. Handles situation when the
179        // events file specifies a highway that doesn't exist in the network.
180        if (highway == null)
181        {
182            Logger.getLogger(Highways.class.getName()).log(Level.SEVERE, 
183                    "Highway "+routeNumber+" not found trying to applyColor", "");
184            return;
185        }
186        // start value for highway section, and end value for highway section
187        // by postmile
188        Double startPost;
189        Double endPost;
190        double epsilon = 0.001;
191
192        // postmiles increase from s to n and w to e
193        // S or W directions backup in a positive postmile direction
194        if (direction.equals(Station.DIRECTION.SOUTH) || direction.equals(Station.DIRECTION.WEST))
195        {
196            // add range value to startPost to get
197            // the end postmile value of the highway section
198            startPost = postmile;
199            endPost = postmile + range;
200            // iterate through the stations, if within the specified highway
201            // stretch, update the station by direction and apply dot color
202            for (Station station : highway.stations)
203            {
204                if (station.postmile >= startPost && station.postmile <= endPost)
205                {
206                    station.updateByDirection(direction, dotColor);
207                }
208            }
209        } 
210        // N or E directions backup in a negative postmile direction
211        else
212        {
213            // subtract range value from startPost
214            // to get the end postmile value of the highway section
215            startPost = postmile;
216            endPost = postmile - range;
217
218            // iterate through the stations, if within the specified highway
219            // section, update the station by direction and apply dot color
220            for (Station station : highway.stations)
221            {
222                if (station.postmile <= startPost && station.postmile >= endPost)
223                {
224                    station.updateByDirection(direction, dotColor);
225                }
226            }
227        }
228        System.out.println("");
229    }
230   
231    /**
232     * Loads all FEPLines from the specified highways map file.
233     *
234     * @param highwaysMapFileName
235     * @return List of FEPLines
236     */
237    private ArrayList<FEPLine> loadLines(String highwaysMapFileName)
238    {
239        ArrayList<FEPLine> lines = new ArrayList<>();
240        try
241        {
242            Scanner sc = new Scanner(new File(highwaysMapFileName));
243            // first line of file contains number of FEP Lines
244            String firstLine = sc.nextLine();
245            Scanner linesc = new Scanner(firstLine);
246            int numLines = linesc.nextInt();
247            linesc.close();
248            // FOR each FEP Line
249            for (int i = 0; i < numLines; i++)
250            {
251                lines.add(loadLine(sc));
252            }
253            sc.close();
254
255        } catch (FileNotFoundException ex)
256        {
257            Logger.getLogger(Highways.class.getName()).log(Level.SEVERE, null, ex);
258        }
259        return lines;
260    }
261   
262    /**
263     * Load all the stations for a single FEP Line from the highways map file.
264     *
265     * @param sc scanner at the current FEPLine line
266     * @return FEPLine
267     */
268    private FEPLine loadLine(Scanner sc)
269    {
270        String line = sc.nextLine();
271        Scanner scline = new Scanner(line);
272        // Get the attributes of this FEP Line
273        int lineNum = scline.nextInt();
274        int count = scline.nextInt();
275        int numStations = scline.nextInt();
276       
277        // initialze stations array
278        ArrayList<Station> stations = new ArrayList<>();
279        // Read all the stations for thie FEP Line
280        for (int i = 0; i < numStations; i++)
281        {
282            stations.add(loadStation(sc, lineNum));
283        }
284
285        return new FEPLine(lineNum, stations, count);
286    }
287   
288    /**
289     * Loads a single Station from the highways map file
290     * @param sc scanner at the current station line
291     * @param lineNum the FEPLine number for the station
292     * @return Station
293     */
294    private Station loadStation(Scanner sc, int lineNum)
295    {
296        String line = sc.nextLine();
297        Scanner scline = new Scanner(line);
298       
299        int ldsID = scline.nextInt();
300        int drop = scline.nextInt();
301        int fwy = scline.nextInt();
302        DIRECTION dir = DIRECTION.toDirection(scline.next());
303        double postmile = scline.nextDouble();
304        int numLoops = scline.nextInt();
305        String location = getStationLoc(line);
306        ArrayList<LoopDetector> loops = new ArrayList<>();
307        for (int i = 0; i < numLoops; i++)
308        {
309            loops.add(loadLoop(sc));
310        }
311
312        return new Station(lineNum, ldsID, drop, location, loops, fwy, dir, postmile);
313    }
314   
315    /**
316     * Loads a single loop from the highways map file
317     *
318     * @param sc scanner at the current loop line
319     * @return LoopDetector
320     */
321    private LoopDetector loadLoop(Scanner sc)
322    {
323        String line = sc.nextLine();
324        Scanner scline = new Scanner(line);
325
326        int loopID = scline.nextInt();
327        String loopLocID = scline.next();
328        String loopLoc = scline.next();
329        scline.close();
330        return new LoopDetector(loopID, loopLocID, loopLoc);
331    }
332
333    /**
334     * Scans the LoopDetector line and grabs the String location from the line.
335     *
336     * @param line the line containing the location
337     * @return A String loop location.
338     */
339    private String getLoopLoc(String line)
340    {
341        Scanner sc = new Scanner(line);
342        sc.nextInt();
343
344     // GRABS FROM CURRENT TO END OF LINE
345        sc.useDelimiter("\\z");
346        String loc = sc.next().trim();
347        sc.close();
348        return loc;
349    }
350
351    /**
352     * Scans the Station line and grabs the String location from the line.
353     *
354     * @param line the line containing the location
355     * @return A String station location.
356     */
357    private String getStationLoc(String line)
358    {
359        Scanner scline = new Scanner(line);
360        scline.nextInt();
361        scline.nextInt();
362        scline.nextInt();
363        scline.next();
364        scline.nextDouble();
365        scline.nextInt();
366
367        // GRABS FROM CURRENT TO END OF LINE
368        scline.useDelimiter("\\z");
369        String loc = scline.next().trim();
370        scline.close();
371        return loc;
372    }
373   
374    /**
375     * Creates a socket client that writes the Highways data to the FEP Simulator.
376     *
377     * @throws SimulationException
378     */
379    public void writeToFEP() throws SimulationException
380    {
381        try
382        {
383            // Create the socket to the FEP Simulator
384            Socket sock = new Socket(FEPHostName, FEPPortNum);
385            PrintWriter out = new PrintWriter(sock.getOutputStream(), true);
386           
387            // Print the number of bytes the highways data message contains
388            System.out.println("Highways sending " + this.toCondensedFormat(false).toCharArray().length + 1 + "bytes to FEPSIM.");
389            String outMsg = this.toCondensedFormat(false);
390            // Write the highways data over the socket
391            out.println(outMsg);
392           
393            // close the socket
394            sock.close();
395        } catch (java.net.ConnectException ex)
396        {
397            //Logger.getLogger(Highways.class.getName()).log(Level.SEVERE, null, ex);
398            System.out.println("writeToFEP() can't connect, no data sent to FEP.");
399            throw new SimulationException(SimulationException.BINDING);
400        } catch (IOException ex)
401        {
402            //Logger.getLogger(Highways.class.getName()).log(Level.SEVERE, null, ex);
403            System.out.println("Highway Model failed writing to FEPSim.");
404            throw new SimulationException(SimulationException.BINDING);
405        }
406    }
407   
408    /** Returns a string of highways data. If MetaDataOnly is true, you get a full
409     *  dump of the highways meta data, which does not include dynamic loop values,
410     *  and does include the string location names. If MetaDataOnly is false,
411     *  dynamic loop values are included, and unnecessary information like string
412     *  location values are not included.
413     *
414     *  The FEPSimulator takes in the toCondensedFormat() output, with a MetaDataOnly
415     *  value of false, over the socket.
416     *
417     *  The MetaDataOnly flag should be used to get a full dump of the highways
418     *  information. This was used to get the highways_fullmap.txt output.
419     *
420     * @param MetaDataOnly Whether you want meta data, or a full dump for FEPSim
421     * @return String, highways data in condensed format
422     *
423     * Example toCondensedFormat(MetaDataOnly = false) output:
424     *
425     * 43                       // "number of lines"
426     * 32 0 13                  // "line id" "count num" "number of stations"
427     * 1210831 1 5 S 0.9 8      // "station id" "drop num" "route num"...
428     *                          //      ..."direction" "postmile" "number of loops"
429     * 1210832  0.0 0  ML_1     // "loop id" "occ" "vol"
430     * 1210833  0.0 0  ML_2     // ..
431     * 1210834  0.0 0  ML_3     // ..
432     * 1210835  0.0 0  ML_4     // ..
433     * 1210836  0.0 0  PASSAGE  // ..
434     * 1210837  0.0 0  DEMAND   // ..
435     * 1210838  0.0 0  QUEUE    // ..
436     * 1210839  0.0 0  RAMP_OFF // ..
437     * ...
438     *
439     * Example toCondensedFormat(MetaDataOnly = true) output:
440     *
441     * 43                           // "number of lines"
442     * 32 0 13                      // "line id" "count num" "number of stations"
443     * 1210831 1 5 S 0.9 8 CALAFIA  // "station id" "drop num" "route num"...
444     *                              //      ..."direction" "postmile"...
445     *                              //      ..."number of loops" "string location"
446     * 1210832 ML_1                 // "loop id" "loop location"
447     * 1210833 ML_2                 // "            "
448     * 1210834 ML_3                 // "            "
449     * 1210835 ML_4                 // "            "
450     * 1210836 PASSAGE              // "            "
451     * 1210837 DEMAND               // "            "
452     * 1210838 QUEUE                // "            "
453     * 1210839 RAMP_OFF             // "            "
454     * ...
455     */
456    public String toCondensedFormat(boolean MetaDataOnly)
457    {
458        // first line: number of FEPLines
459        StringBuilder build = new StringBuilder();
460        build.append(lines.size());
461        build.append("\n");
462        // append each fep line to the string
463        for(FEPLine line : lines)
464        {
465            build.append(line.toCondensedFormat(MetaDataOnly));
466        }
467        // return the full condensed format string
468        return build.toString();
469    }
470   
471    /**
472     * Returns the Highways model data in XML format.
473     * Probably obsolete, since we aren't using exchange.xml any longer.
474     * @return highways data in XML format
475     */
476    public String toXML()
477    {
478        String xml = null;
479        try
480        {
481            DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
482            DocumentBuilder builder = factory.newDocumentBuilder();
483            Document theDoc = builder.newDocument();
484
485            Element networkElement = theDoc.createElement(XML_TAGS.NETWORK.tag);
486            theDoc.appendChild(networkElement);
487
488            for (FEPLine line : lines)
489            {
490                line.toXML(networkElement);
491            }
492
493            Transformer tf = TransformerFactory.newInstance().newTransformer();
494
495            tf.setOutputProperty(OutputKeys.ENCODING, "UTF-8");
496            tf.setOutputProperty(OutputKeys.INDENT, "yes");
497            tf.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "2");
498
499            Writer out = new StringWriter();
500            tf.transform(new DOMSource(theDoc), new StreamResult(out));
501            xml = out.toString();
502            out.close();
503        } catch (Exception ex)
504        {
505            Logger.getLogger(Highways.class.getName()).log(Level.SEVERE, null, ex);
506        }
507        return xml;
508
509    }
510
511    /**
512     * Returns a highway by given highway number.
513     *
514     * @param routeNum
515     * @return Highway with specified route number, or null if no highway with
516     *          the specified route num
517     */
518    public Highway getHighwayByRouteNumber(Integer routeNum)
519    {
520        Highway returnHwy = null;
521        // search through highways and check routeNums
522        for (Highway hwy : highways)
523        {
524            if (hwy.routeNumber.equals(routeNum))
525            {
526                returnHwy = hwy;
527                break;
528            }
529        }
530        return returnHwy;
531    }
532
533    /** Return a string representation of the Highways */
534    public String toString()
535    {
536        StringBuilder result = new StringBuilder();
537        for (Highway hwy: highways)
538        {
539            // Consider each route direction
540            for (DIRECTION dir: hwy.availDirs)
541            {
542                String rowLabel = ""+String.format("%3s ",hwy.routeNumber)+dir.getLetter()+' ';
543                StringBuilder lineout = new StringBuilder();
544                // Examine every station on this highway and direction
545                for (Station stat: hwy.stations)
546                {
547                    if (stat.direction.equals(dir))
548                    {
549                    //lineout.append("" + dir.getLetter() + stat.postmile);
550                    lineout.append(stat.getColor());
551                    //lineout.append("  ");
552                    }
553                    else 
554                    {
555                        lineout.append(".");
556                    }
557                }
558                // See if there were stations for this direction
559                String checkMe = lineout.toString().trim();
560                // if any stations were colored, output the line
561                if (checkMe.length() > 1)
562                {
563                    result.append(rowLabel);
564                    result.append(lineout + "\n");
565                }
566            }
567        }
568        result.append("\n");
569        return result.toString();
570    }
571    /** Return a json representation of the Highways, readable by Google Maps */
572    public String toJson()
573    {
574        // TODO: move loading this file to init method so it doesn't get
575        // called every time.
576        PostmileCoords pmList = new PostmileCoords();
577        FileInputStream fis = null;
578        try
579        {
580            fis = new FileInputStream("config/vds_data/postmile_coordinates.txt");
581        }
582        catch (FileNotFoundException ex)
583        {
584            Logger.getLogger(Highways.class.getName()).log(Level.SEVERE, null, ex);
585        }
586        Scanner s = new Scanner(fis).useDelimiter("\\A");
587        pmList.load(s);
588       
589        Collections.sort(highways);  // Sort the highways for easier inspection
590        String header = "{\n" +
591        "  \"type\": \"FeatureCollection\",\n" +
592        "  \"features\": [";
593        StringBuilder result = new StringBuilder();
594        result.append(header);
595        for (Highway hwy: highways)
596        {
597            // Examine every station on this highway
598            StringBuilder lineout = new StringBuilder();
599            Collections.sort(hwy.stations, new StationComparator());
600            for (Station stat: hwy.stations)
601            {
602                String pmID = "" + hwy.routeNumber + " " 
603                        + stat.direction.getLetter() + " " 
604                        + stat.postmile;
605                PostmileCoords.Postmile currentPM = pmList.find(pmID);
606                if (currentPM == null)
607                { 
608                Logger.getLogger(Highways.class.getName()).log(Level.INFO, 
609                        "Postmile Coords lookup couldn't find Station: "+pmID,
610                        " ");
611                }
612                if (currentPM != null)
613                {   
614                    //lineout.append("" + dir.getLetter() + stat.postmile);
615                    //lineout.append(stat.getColorByDirection(dir));
616                    String outString = currentPM.toJson();
617                    // replace the color code with the color name
618                    String colorName=stat.getColorName();
619                    outString = outString.replace("desiredcolor",colorName);
620                    lineout.append(outString);
621                    lineout.append("  ");
622                }
623            }
624            //result.append(rowLabel);
625            result.append(lineout + "\n");
626
627        }
628        // remove last trailing comma
629        result.replace(result.lastIndexOf(","), result.lastIndexOf(",") + 1, " "  );
630
631        result.append("  ]\n" +  "}");
632        return result.toString();
633    }
634   
635    /**
636     * Generates the route number list, used for user input validation.
637     * @return list of route numbers.
638     */
639    public List<Integer> getAllRouteNums()
640    {
641        ArrayList<Integer> routeNums = new ArrayList<>();
642        // add the route number for each highway to the list
643        for(Highway hwy : highways)
644        {
645            routeNums.add(hwy.routeNumber);
646        }
647        return routeNums;
648    }
649   
650    /**
651     * XML tags used in writeToXML()
652     */
653    private static enum XML_TAGS
654    {
655
656        NETWORK("Network");
657
658        String tag;
659
660        private XML_TAGS(String n)
661        {
662            tag = n;
663        }
664    }
665   
666    public void reset()
667    {
668        for(FEPLine line : lines)
669        {
670            for(Station stn : line.stations)
671            {
672                for(LoopDetector ld : stn.loops)
673                {
674                    ld.occ = 0;
675                    ld.vol = 0;
676                }
677            }
678        }
679    }
680   
681    public void applyTrafficLaneEvent(TrafficLaneEvent event)
682    {
683        Integer routeNum = event.routeNum;
684        Highway hwy = getHighwayByRouteNumber(routeNum);
685        for(Station stn: hwy.stations)
686        {
687            if(stn.equals(event.station))
688            {
689                for(LoopDetector ld : stn.loops)
690                {
691                    if(ld.equals(event.loopDetector))
692                    {
693                        ld.occ = event.color.occupancy();
694                        ld.vol = event.color.volume();
695                        break;
696                    }
697                }
698                break;
699            }
700        }
701    }
702}
Note: See TracBrowser for help on using the repository browser.