source: tmcsimulator/trunk/src/atmsdriver/model/Station.java @ 343

Revision 343, 13.5 KB checked in by jdalbey, 7 years ago (diff)

Fix defect #117. Update HighwaysTest?.java for new highway model.

Line 
1package atmsdriver.model;
2
3import atmsdriver.model.LoopDetector.DOTCOLOR;
4import java.util.ArrayList;
5import java.util.List;
6import org.w3c.dom.Document;
7import org.w3c.dom.Element;
8
9/**
10 * A Station (VDS or Vehicle Detector Station) represents a group of lane detectors
11 * across all lanes in one direction at a particular point on a highway. A station is identified
12 * by its highway number and postmile. A station has an associated direction
13 * used to establish which direction is Main and which is Opposite. The MLTotVol
14 * and OppTotVol for a station can be dynamically updated. A station has other
15 * attributes: lineNum, vdsID, drop, and location which are used by the FEP. A
16 * station can be compared to other stations by its postmile.
17 *
18 * @author John A. Torres, jdalbey
19 * @version 9/10/2017, 3/22/2019
20 */
21public final class Station implements Comparable
22{
23
24    /* Static Station meta data */
25    final public int lineID;
26    final public int vdsID; // double check
27    final public int drop;
28    final public String location;
29    final public List<LoopDetector> loops;
30    final public int routeNumber;
31    final public double postmile;
32    final public DIRECTION direction;
33
34    /* Dynamic Station data */
35    private int MLTotVol;
36    private int OppTotVol;
37
38    /* Constructor */
39    public Station(int lineID, int vdsID, int drop,
40            String location, List<LoopDetector> loops, int hwy,
41            DIRECTION direction, double postmile)
42    {
43        this.lineID = lineID;
44        this.vdsID = vdsID;
45        this.drop = drop;
46        this.loops = loops;
47        this.location = location;
48        this.postmile = postmile;
49        this.direction = direction;
50        this.routeNumber = hwy;
51
52        this.MLTotVol = getMLTotVol();
53        this.OppTotVol = getOPPTotVol();
54    }
55
56    /**
57     * Calculates the total ML Volume.
58     *
59     * @return total ML volume.
60     */
61    private int getMLTotVol()
62    {
63        int mlTotVol = 0;
64        for (LoopDetector loop : loops)
65        {
66            if (loop.loopLocation.startsWith("ML"))
67            {
68                mlTotVol += loop.vol;
69            }
70        }
71        return mlTotVol;
72    }
73
74    /**
75     * Calculates the total OPP Volume
76     *
77     * @return total OPP volume.
78     */
79    private int getOPPTotVol()
80    {
81        int oppTotVol = 0;
82        for (LoopDetector loop : loops)
83        {
84            if (loop.loopLocation.startsWith("OS"))
85            {
86                oppTotVol += loop.vol;
87            }
88        }
89        return oppTotVol;
90    }
91
92    /**
93     * Returns a string of highways data. If MetaDataOnly is true, you get a
94     * full dump of the highways meta data, which does not include dynamic loop
95     * values, and does include the string location names. If MetaDataOnly is
96     * false, dynamic loop values are included, and unnecessary information like
97     * string location values are included.
98     *
99     * The FEPSimulator takes in the toCondensedFormat() output, with a
100     * MetaDataOnly value of false, over the socket.
101     *
102     * The MetaDataOnly flag should be used to get a full dump of the highways
103     * information. This was used to get the highways_fullmap.txt output.
104     *
105     * @param MetaDataOnly Whether you want meta data, or a full dump for FEPSim
106     * @return String, highways data in condensed format
107     */
108    public String toCondensedFormat(boolean MetaDataOnly)
109    {
110        StringBuilder build = new StringBuilder();
111        build.append(Integer.toString(this.vdsID));
112        build.append(" ");
113        build.append(Integer.toString(this.drop));
114        build.append(" ");
115        build.append(Integer.toString(this.routeNumber));
116        build.append(" ");
117        build.append(this.direction.getLetter());
118        build.append(" ");
119        build.append(Double.toString(this.postmile));
120        build.append(" ");
121        build.append(Integer.toString(loops.size()));
122        build.append(" ");
123        if (MetaDataOnly)
124        {
125            build.append(this.location);
126        }
127        build.append("\n");
128        for (LoopDetector loop : loops)
129        {
130            build.append(loop.toCondensedFormat(MetaDataOnly));
131        }
132        return build.toString();
133    }
134
135    /**
136     * Compare this Station to another by postmile. Note: This might be better
137     * as a Comparator since it checks only one field.
138     */
139    @Override
140    public int compareTo(Object otherStation)
141    {
142        // check for identity
143        if (this == otherStation)
144        {
145            return 0;
146        }
147        // check that Object is of type Station, if not throw exception
148        if (!(otherStation instanceof Station))
149        {
150            throw new ClassCastException("A Station object expected.");
151        }
152
153        // get difference of values
154        double otherStationPostmile = ((Station) otherStation).postmile;
155        double val = this.postmile - otherStationPostmile;
156
157        // set appropriate comparable return value
158        int retval = 0;
159        if (val > 0)
160        {
161            retval = 1;
162        }
163        else if (val < 0)
164        {
165            retval = -1;
166        }
167
168        return retval;
169    }
170
171    /**
172     * See if this station matches the specified attributes.
173     * @param dir
174     * @param postmile
175     * @return true if this station's attributes match the given ones.
176     */
177    public boolean matches(DIRECTION dir, double postmile)
178    {
179        double val = this.postmile - postmile;
180        return (Math.abs(val) < 0.01) && this.direction.equals(dir);
181    }
182    /**
183     * Determine which lane fields to update based on given direction and update
184     * all the loop detectors with the given color.
185     *
186     * @param direction desired highway direction
187     * @param dotColor desired dot color
188     */
189    public void updateByDirection(DIRECTION direction, DOTCOLOR dotColor)
190    {
191        // Is this station going in the desired direction?
192        if (direction.equals(this.direction))
193        {
194            outputUpdateMessage(dotColor, direction.toString());
195
196            for (LoopDetector loop : loops)
197            {
198                // THIS DECISION ISN'T NEEDED after JD's BuildHighwayFile pgm
199                // creates a highway map based on VDS instead of Controller (LDS).
200    //            if (loop.loopLocation.startsWith(laneDir))
201    //            {
202                    // UPDATE LOOP WITH VALUES
203                    loop.updateLoop(dotColor.volume(), dotColor.occupancy());
204    //            }
205            }
206
207            this.MLTotVol = getMLTotVol();
208            this.OppTotVol = getOPPTotVol();
209        }
210    }
211    /**
212     * Return the color for the lanes in a given direction.
213     * @return character representing color of this station's traffic flow
214     * '@' = red, '+' = yellow, '-' = green
215     */
216    public char getColor()
217    {
218        /* For now just use the color of the first lane.
219         * TODO: Average the color in ALL the lanes for the given direction */
220
221        String laneDir = "";
222       
223            // THIS DECISION ISN'T NEEDED after JD's BuildHighwayFile pgm
224            // creates a highway map based on VDS instead of Controller (LDS).
225//        if (direction.equals(this.direction))
226//        {
227//            laneDir = "ML";
228//        }
229//        else if (direction.equals(this.direction.getOpposite()))
230//        {
231//            laneDir = "OS";
232//        }
233        // Search lanes to find specified direction
234        for (LoopDetector loop : loops)
235        {
236            // THIS DECISION ISN'T NEEDED after JD's BuildHighwayFile pgm
237            // creates a highway map based on VDS instead of Controller (LDS).
238//            if (loop.loopLocation.substring(0,2).equals(laneDir))
239//            {
240                // Return color according to loop volume of first matching lane
241                if (loop.vol == 1)
242                {
243                    return '@';
244                }
245                if (loop.vol == 3)
246                {
247                    return '+';
248                }
249                if (loop.vol == 0)
250                {
251                    return '-';
252                }
253//            }
254        }
255        // Default case for when the route is not on this direction
256        return ' ';
257    }
258    /**
259     * Return the color name for the traffic volume of this station.
260     */
261    public String getColorName()
262    {
263        String colorName = "";
264        switch (this.getColor())
265        {
266            case '@': colorName = "red";break;
267            case '+': colorName = "yellow"; break;
268            case '-': colorName = "lime";break;
269            case ' ': colorName = "lime";break;
270        }
271        return colorName;
272    }
273    /**
274     * Output for updateByDirection. Logs the update to the console.
275     *
276     * @param dotcolor
277     * @param OPP_ML
278     */
279    private void outputUpdateMessage(DOTCOLOR dotcolor, String OPP_ML)
280    {
281        System.out.printf("Updating %-3.3s %-5.5s %-3.3s lanes\t %-12.12s "
282                + "at postmile %-6.6s to %-7.7s\n",
283                Integer.toString(this.routeNumber), this.direction.name(),
284                OPP_ML, this.location, Double.toString(this.postmile),
285                dotcolor.name());
286    }
287
288    /**
289     * XML tags used for toXML() method.
290     */
291    private static enum XML_TAGS
292    {
293
294        STATION("Station"),
295        LDS_ID("LDS_ID"),
296        LINE_NUM("Line_Num"),
297        DROP("Drop"),
298        LOOPS("Loops"),
299        LOCATION("Location"),
300        POST_MILE("Post_Mile"),
301        DIRECTION("Direction"),
302        FREEWAY("Freeway"),
303        ML_TOT_VOL("ML_Tot_Vol"),
304        OPP_TOT_VOL("Opp_Tot_Vol");
305
306        String tag;
307
308        private XML_TAGS(String n)
309        {
310            tag = n;
311        }
312    }
313
314    /**
315     * Returns the Station data in XMLFormat.
316     *
317     * @param currElem The current XML <Station> element
318     */
319    public void toXML(Element currElem)
320    {
321        Document theDoc = currElem.getOwnerDocument();
322
323        Element stationElement = theDoc.createElement(XML_TAGS.STATION.tag);
324        currElem.appendChild(stationElement);
325
326        Element ldsIDElement = theDoc.createElement(XML_TAGS.LDS_ID.tag);
327        ldsIDElement.appendChild(theDoc.createTextNode(String.valueOf(this.vdsID)));
328        stationElement.appendChild(ldsIDElement);
329
330        Element lineNumElement = theDoc.createElement(XML_TAGS.LINE_NUM.tag);
331        lineNumElement.appendChild(theDoc.createTextNode(String.valueOf(this.lineID)));
332        stationElement.appendChild(lineNumElement);
333
334        Element dropElement = theDoc.createElement(XML_TAGS.DROP.tag);
335        dropElement.appendChild(theDoc.createTextNode(String.valueOf(this.drop)));
336        stationElement.appendChild(dropElement);
337
338        Element locationElement = theDoc.createElement(XML_TAGS.LOCATION.tag);
339        locationElement.appendChild(theDoc.createTextNode(this.location));
340        stationElement.appendChild(locationElement);
341
342        Element postMileElement = theDoc.createElement(XML_TAGS.POST_MILE.tag);
343        postMileElement.appendChild(theDoc.createTextNode(String.valueOf(this.postmile)));
344        stationElement.appendChild(postMileElement);
345
346        Element directionElement = theDoc.createElement(XML_TAGS.DIRECTION.tag);
347        directionElement.appendChild(theDoc.createTextNode("" + this.direction.getLetter()));
348        stationElement.appendChild(directionElement);
349
350        Element freewayElement = theDoc.createElement(XML_TAGS.FREEWAY.tag);
351        freewayElement.appendChild(theDoc.createTextNode(String.valueOf(this.routeNumber)));
352        stationElement.appendChild(freewayElement);
353
354        Element mlElement = theDoc.createElement(XML_TAGS.ML_TOT_VOL.tag);
355        mlElement.appendChild(theDoc.createTextNode(String.valueOf(this.MLTotVol)));
356        stationElement.appendChild(mlElement);
357
358        Element oppElement = theDoc.createElement(XML_TAGS.OPP_TOT_VOL.tag);
359        oppElement.appendChild(theDoc.createTextNode(String.valueOf(this.OppTotVol)));
360        stationElement.appendChild(oppElement);
361
362        Element loopsElement = theDoc.createElement(XML_TAGS.LOOPS.tag);
363        stationElement.appendChild(loopsElement);
364
365        for (LoopDetector loop : loops)
366        {
367            loop.toXML(loopsElement);
368        }
369    }
370
371    /**
372     * Enum for freeway direction.
373     *
374     * @author John A. Torres
375     * @version 9/10/2017
376     */
377    public static enum DIRECTION
378    {
379        NORTH,
380        SOUTH,
381        EAST,
382        WEST;
383
384        // All the first letters of the values, in order.
385        private static String allLetters = "NSEW";
386
387        /**
388         * Return the first letter of this enum.
389         *
390         * @return String first letter of this enum.
391         */
392        public String getLetter()
393        {
394            return this.toString().substring(0, 1);
395        }
396       
397        public DIRECTION getOpposite()
398        {
399            switch (this)
400            {
401                case NORTH:
402                    return SOUTH;
403                case SOUTH:
404                    return NORTH;
405                case EAST:
406                    return WEST;
407                case WEST:
408                    return EAST;
409            }
410            return null;
411        }
412
413        /**
414         * Returns a direction given its first character.
415         *
416         * @param letter the first character of a direction
417         * @return direction corresponding to letter
418         * @pre letter must be one of allLetters
419         */
420        public static DIRECTION toDirection(String letter)
421        {
422            if(letter.indexOf(letter.charAt(0)) == -1)
423            {
424                return null;
425            }
426            return values()[allLetters.indexOf(letter.charAt(0))];
427        }
428    }
429   
430    @Override
431    public String toString()
432    {
433        return Integer.toString(this.vdsID)+this.getColor();
434    }
435}
Note: See TracBrowser for help on using the repository browser.