source: tmcsimulator/trunk/src/tmcsim/highwaymodel/Station.java @ 422

Revision 422, 12.3 KB checked in by jdalbey, 7 years ago (diff)

Remove ATMS functionality. Reworked and simplified the Highway model to use only VDS data from PeMS. Updated all unit tests.

Line 
1package tmcsim.highwaymodel;
2
3import tmcsim.highwaymodel.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            // Set the values for all lanes at this station
197            for (LoopDetector loop : loops)
198            {
199                // Set loop detector attributes given the desired color
200                loop.setAttributes(dotColor);
201            }
202
203            this.MLTotVol = getMLTotVol();
204            this.OppTotVol = getOPPTotVol();
205        }
206    }
207    /**
208     * Compute the color for the lanes in a given direction.
209     * @return DOTCOLOR of this station's traffic flow
210     */
211    public DOTCOLOR getColor()
212    {
213        /* For now just use the color of the first lane.
214         * TODO: Average the color in ALL the lanes  */
215
216        String laneDir = "";
217       
218        // FOR FUTURE USE we will need to examine all detectors for this station
219        // and perform an average
220        // for (LoopDetector loop : loops)
221        {
222            // for now, Return color according to loop volume of first lane
223            if (loops.get(0).vol == 1)
224            {
225                return DOTCOLOR.RED;
226            }
227            if (loops.get(0).vol == 3)
228            {
229                return DOTCOLOR.YELLOW;
230            }
231            if (loops.get(0).vol == 0)
232            {
233                return DOTCOLOR.GREEN;
234            }
235        }
236       
237        // Default case for invalid data
238        return DOTCOLOR.GREEN;
239    }
240
241    /**
242     * Output for updateByDirection. Logs the update to the console.
243     *
244     * @param dotcolor
245     * @param OPP_ML
246     */
247    private void outputUpdateMessage(DOTCOLOR dotcolor, String OPP_ML)
248    {
249        System.out.printf("Updating %-3.3s %-5.5s %-3.3s lanes\t %-12.12s "
250                + "at postmile %-6.6s to %-7.7s\n",
251                Integer.toString(this.routeNumber), this.direction.name(),
252                OPP_ML, this.location, Double.toString(this.postmile),
253                dotcolor.name());
254    }
255
256    /**
257     * XML tags used for toXML() method.
258     */
259    private static enum XML_TAGS
260    {
261
262        STATION("Station"),
263        LDS_ID("LDS_ID"),
264        LINE_NUM("Line_Num"),
265        DROP("Drop"),
266        LOOPS("Loops"),
267        LOCATION("Location"),
268        POST_MILE("Post_Mile"),
269        DIRECTION("Direction"),
270        FREEWAY("Freeway"),
271        ML_TOT_VOL("ML_Tot_Vol"),
272        OPP_TOT_VOL("Opp_Tot_Vol");
273
274        String tag;
275
276        private XML_TAGS(String n)
277        {
278            tag = n;
279        }
280    }
281
282    /**
283     * Returns the Station data in XMLFormat.
284     *
285     * @param currElem The current XML <Station> element
286     */
287    public void toXML(Element currElem)
288    {
289        Document theDoc = currElem.getOwnerDocument();
290
291        Element stationElement = theDoc.createElement(XML_TAGS.STATION.tag);
292        currElem.appendChild(stationElement);
293
294        Element ldsIDElement = theDoc.createElement(XML_TAGS.LDS_ID.tag);
295        ldsIDElement.appendChild(theDoc.createTextNode(String.valueOf(this.vdsID)));
296        stationElement.appendChild(ldsIDElement);
297
298        Element lineNumElement = theDoc.createElement(XML_TAGS.LINE_NUM.tag);
299        lineNumElement.appendChild(theDoc.createTextNode(String.valueOf(this.lineID)));
300        stationElement.appendChild(lineNumElement);
301
302        Element dropElement = theDoc.createElement(XML_TAGS.DROP.tag);
303        dropElement.appendChild(theDoc.createTextNode(String.valueOf(this.drop)));
304        stationElement.appendChild(dropElement);
305
306        Element locationElement = theDoc.createElement(XML_TAGS.LOCATION.tag);
307        locationElement.appendChild(theDoc.createTextNode(this.location));
308        stationElement.appendChild(locationElement);
309
310        Element postMileElement = theDoc.createElement(XML_TAGS.POST_MILE.tag);
311        postMileElement.appendChild(theDoc.createTextNode(String.valueOf(this.postmile)));
312        stationElement.appendChild(postMileElement);
313
314        Element directionElement = theDoc.createElement(XML_TAGS.DIRECTION.tag);
315        directionElement.appendChild(theDoc.createTextNode("" + this.direction.getLetter()));
316        stationElement.appendChild(directionElement);
317
318        Element freewayElement = theDoc.createElement(XML_TAGS.FREEWAY.tag);
319        freewayElement.appendChild(theDoc.createTextNode(String.valueOf(this.routeNumber)));
320        stationElement.appendChild(freewayElement);
321
322        Element mlElement = theDoc.createElement(XML_TAGS.ML_TOT_VOL.tag);
323        mlElement.appendChild(theDoc.createTextNode(String.valueOf(this.MLTotVol)));
324        stationElement.appendChild(mlElement);
325
326        Element oppElement = theDoc.createElement(XML_TAGS.OPP_TOT_VOL.tag);
327        oppElement.appendChild(theDoc.createTextNode(String.valueOf(this.OppTotVol)));
328        stationElement.appendChild(oppElement);
329
330        Element loopsElement = theDoc.createElement(XML_TAGS.LOOPS.tag);
331        stationElement.appendChild(loopsElement);
332
333        for (LoopDetector loop : loops)
334        {
335            loop.toXML(loopsElement);
336        }
337    }
338
339    /**
340     * Enum for freeway direction.
341     *
342     * @author John A. Torres
343     * @version 9/10/2017
344     */
345    public static enum DIRECTION
346    {
347        NORTH,
348        SOUTH,
349        EAST,
350        WEST;
351
352        // All the first letters of the values, in order.
353        private static String allLetters = "NSEW";
354
355        /**
356         * Return the first letter of this enum.
357         *
358         * @return String first letter of this enum.
359         */
360        public String getLetter()
361        {
362            return this.toString().substring(0, 1);
363        }
364       
365        public DIRECTION getOpposite()
366        {
367            switch (this)
368            {
369                case NORTH:
370                    return SOUTH;
371                case SOUTH:
372                    return NORTH;
373                case EAST:
374                    return WEST;
375                case WEST:
376                    return EAST;
377            }
378            return null;
379        }
380
381        /**
382         * Returns a direction given its first character.
383         *
384         * @param letter the first character of a direction
385         * @return direction corresponding to letter
386         * @pre letter must be one of allLetters
387         */
388        public static DIRECTION toDirection(String letter)
389        {
390            if(letter.indexOf(letter.charAt(0)) == -1)
391            {
392                return null;
393            }
394            return values()[allLetters.indexOf(letter.charAt(0))];
395        }
396    }
397   
398    @Override
399    public String toString()
400    {
401        return Integer.toString(this.vdsID)+this.getColor();
402    }
403}
Note: See TracBrowser for help on using the repository browser.