source: tmcsimulator/trunk/src/cptms/cptms.html @ 311

Revision 311, 34.5 KB checked in by jdalbey, 7 years ago (diff)

cptms renamed, added cms message polling to update icon color

Line 
1<!DOCTYPE html>
2<html>
3  <head>
4<!-- Launch with  python -m CGIHTTPServer 80  -->
5  <meta http-equiv="Content-Type" content="text/html;charset=utf-8">
6    <title>CPTMS Map v0.6.1</title> 
7    <style>
8        @font-face {
9          font-family: Scoreboard;
10          src: url('scoreboard.ttf');
11        }
12      /* Set the size of the div element that contains the map */
13      #mapdiv {
14        height: 100%;
15        width: 100%; 
16       }
17        /* Makes the page fill the window. */
18      html, body {
19        height: 100%;
20        margin: 0;
21        padding: 0;
22      }
23        textarea {
24           height: 33px;
25           width: 272px;
26           resize: none;
27           font-family: Scoreboard;
28           font-size: xx-large;
29           background-color: #2F4F4F;
30           color: yellow;
31        }
32       input {
33           border: thin solid #333;
34           padding: 2px;
35           font-family: "Lucida Console", Monaco, monospace;
36           font-size: medium;
37        }
38        /*img.resize {
39          max-width:auto;
40          max-height:500px;
41        }*/
42      #search-input {
43        background-color: #17263c;  /* #CD853F;  /*#E6E6FA; /* lavender */
44        color: #E6E6FA;  /* #FFEFD5; */
45        font-family: Roboto;
46        font-size: 18px;
47        font-weight: 400;
48        margin-left: 12px;
49        padding: 0 11px 0 13px;
50        text-overflow: ellipsis;
51        border-color: #746855;   /* #4d90fe; */
52        width: 400px;
53      }
54
55      #search-input:focus {
56        border-color: #E6E6FA; 
57      }
58
59      #ctrButton {
60        font-size: 40px;
61        margin-right: 9px;
62        background-color: #fff;
63        color: #47476b;
64        cursor: pointer;
65      }
66      #cms-info-label {
67           height: 20px;
68           width: 590px;
69           overflow: hidden;
70           background-color: #A8C5FF;  /*#ECECFB; */
71           border: thin solid #BDBDBD;
72           padding: 5px;
73       }
74      #message-display {
75           height: 172px;
76           width: 300px;
77           overflow: hidden;
78           float: left;
79       }
80      #message-input {
81           height: 122px;
82           width: 165px;
83           background-color: #729FFF;
84           float: left;
85       }
86       #buttonPanel {
87           height: 122px;
88           width: 130px;
89           background-color: #729FFF;
90           border-left: none;
91           float: left;
92           padding: 20px;
93        }
94       .wrapper {
95           position: relative;\
96        }
97.gm-style-iw {
98background-color: #729FFF;
99border-color: #729FFF;
100}
101       #dialog {
102          position: absolute;
103          top: 10%;
104          right: 20%;
105          background-color: #729FFF; /* #ECECFB; */
106          margin: auto;
107          padding: 20px;
108          border: 1px solid #888;
109          width: 680px;
110          display: none;           
111        }
112      .unstyled-button {
113        border: 0 none;
114        padding: 0;
115        background: none;
116        cursor: pointer;
117      }   
118    /* The Close Button */
119    .close {
120      color: #2E2E2E;
121      float: right;
122      font-size: 30px;
123      font-weight: bold;
124    }
125    .close:hover,
126    .close:focus {
127      color: black;
128      text-decoration: none;
129      cursor: pointer;
130    }
131
132    </style>
133  </head>
134  <body>
135    <!--
136         Version 6.1 Puts cms messages in json formatted file.  Polls for updates.
137         Version 6.0 Adds speed-dependent images to infowindow for cctv icons
138         Version 5.8 Adds an infowindow with a static image for all cctv icons
139         Version 5.7 integrates CCTV icons and button (but empty click handler)
140         Version 5.6 integrates CMS features
141         Version 5.5 renames title to CPTMS, loads static data on startup and dynamic data
142         every ten seconds. 
143         Version 5.4 adds Search box and Center button
144         Version 5.3 fixed dot color update defect, increased refresh rate to 10 sec.
145         Version 5.2 places red dots overlapping yellow dots.
146         Version 5.1 removes the map and street view buttons and the H3 tag.
147         Version 5 uses precomputed perpendicular vector in dot adjustment function
148         Version 4 Adjust the spacing between dots when the map is zoomed.
149         Version 3 does loadGeoJson only once, and subsequently does an ajax load
150         of the highways file, and selectively updates only those placePins whose
151         color has changed.
152         @author jdalbey  2019.2.17
153    -->
154    <!-- The text area input for the Search Box -->
155    <input id="search-input" class="controls" type="text" placeholder="Search Box">
156    <!--The div element where the map appears -->
157    <div id="mapdiv"></div>
158    <!--The div element where the buttons appears -->
159    <div id="ctrButton" class="unstyled-button">&#x2295;</div>
160    <button id="cctvButton" class="unstyled-button"><img id="cctvBtnImg" src="images/CPTMSImages/btnReady_CCTV.png"></button>
161    <button id="cmsButton" class="unstyled-button"><img id="cmsBtnImg" src="images/CPTMSImages/btnReady_CMS.png"></button>
162    <button id="vdsButton" class="unstyled-button"><img id="vdsBtnImg" src="images/CPTMSImages/btnDepressed_VDS.png"></button>
163    <!-- The div element for the popup dialog -->
164    <div id="dialog" style="display:none;">
165        <span class="close">&#x2612;</span>  <!-- close button symbol -->
166        <br>
167        <div id="cms-info-label" style="font-family:'Courier New'">CMS ID: xxx LOCATION: </div>
168        <br>
169        <input id='cmsID' value="" type='hidden'/>
170        <div id="message-input">Proposed:
171        <input id="msgcontent1"  maxlength="16" type="text"/><br><br>
172        <input id="msgcontent2"  maxlength="16" type="text"/><br><br>
173        <input id="msgcontent3"  maxlength="16" type="text"/>
174        </div>       
175        <div id="buttonPanel"    style="display: block;">
176        <button onclick="handleSubmit();">Send >></button><br>
177        <button onclick="handleClear();">Clear >></button><br>
178        <button onclick="handleClose();">Close </button>
179        </div>
180        <div id="message-display"  style="display: block;">Current:
181         <textarea readonly id="msgdisplay1" maxlength="16" rows="1" cols="16"></textarea>
182         <textarea readonly id="msgdisplay2" maxlength="16" rows="1" cols="16"></textarea>
183         <textarea readonly id="msgdisplay3" maxlength="16" rows="1" cols="16"></textarea>
184        </div>
185    </div>
186
187    <script>
188//TODO:  Can we speed up vds loading?  can we load the data in the background
189// only display dots when the button is clicked?  Does using setStyle slow it down? Instead of setstyle should we simply set the color on each marker (like we do for cms icons?)  What's the difference between a feature created by loadGeoJson and just a marker added individually?
190// would pre-computing coords for each zoom level and have them accessed in a table make a difference?
191// Add interval refresh for cms icon colors. 
192// Add phase 2 to messages.
193// cms set visible gets undefined error after last icon is processed.
194// have we solved the duplicate cms defect?
195
196    // a global variable for the google map
197    var map;
198    // a global dictionary to lookup a station's original coordinates
199    var vds_coords = {};
200    // a global variable to hold locations of marked search places
201    var placePins = [];
202    // Constant name of dynamic json data file created by CADserver
203    var kMapPointsFile = "highways.json";
204    // Constant name of initial (static) highways file used once at startup
205    var kMapStartupFile = "highways_startup.json";
206    // Constant for map center location: The John Wayne Airport
207    //var centerPoint = {lat: 33.687228, lng: -117.872148};
208    // Constant for map center location in District 12
209    var centerPoint = {
210        lat: 33.693385,
211        lng: -117.798937
212    };
213    // Initial map zoom
214    var initZoom = 11;
215    // Dot colors used in traffic model to indicate free-flowing, slowed, and stopped traffic
216    // and their associated zvalues so slower traffic dots are more visible.
217    // white means a disabled spot
218    var colorZvalues = {
219        "white": 5,
220        "lime": 10,
221        "yellow": 20,
222        "red": 30
223    };
224    var kCMSstartupFile = "cmsStatusD12.json";
225    var kCCTVfile = "cctv_locations_D12.json";
226    var blueFlag = "images/CPTMSImages/icon_cmsBlue.png";
227    var yellowFlag = "images/CPTMSImages/icon_cmsYellow.png";
228    var cctvIcon = "images/CPTMSImages/icon_cctvCyan.png";
229    var cctvIconWhite = "images/CPTMSImages/icon_cctvWhite.png";
230
231    var cms_info;
232    var cmsList = [];
233    var messageDict = {};
234    var cctvList = [];
235    var cms_showing = false;
236    var vds_showing = true;
237    var cctv_showing = false;
238    // Build a solid colored icon to use instead of the classic pin
239    // Use a diamond on N and E directions, circle on S and W directions
240    function dotSymbol(color, postmileID) //,direction)
241    {
242        var circle = google.maps.SymbolPath.CIRCLE;
243        var diamond = 'M -1,0 0,-1 1,0 0,1 z';
244        var myShape = circle;
245        // See if postmile name contains N or W letters
246        //if ((postmileID.indexOf('N') != -1) || (postmileID.indexOf('W') != -1))
247        //{
248        //   myShape = diamond
249        //}
250        return {
251            path: myShape,
252            scale: 5,
253            strokeColor: "black", // the border color
254            strokeWeight: 1, // the border thickness
255            fillColor: color,
256            fillOpacity: 1.0
257        };
258    }
259
260    // Load the map data from a json file and style all the points
261    function loadMapData()
262    {
263        // Load the static map data and call saveCoords when done
264        map.data.loadGeoJson(kMapStartupFile, null, saveCoords)
265
266        // Style the map data by applying the desired properties to each feature (marker)
267        // The function will be called every time a feature's properties are updated.
268        map.data.setStyle(function(feature)
269        {
270            // Get the postmile id
271            var name = feature.getId();
272            // Get the desired color value
273            var ptColor = feature.getProperty("color");
274            var street = feature.getProperty("street");
275            // Build the marker
276            var iconSymbol = dotSymbol(ptColor, name);
277            // return the StyleOptions
278            return {
279                icon: iconSymbol,
280                title: name + " @" + street, // set rollover text
281                // set zIndex for slowed traffic to a higher value so they overlap
282                zIndex: colorZvalues[ptColor]
283            };
284        });
285    }
286    // callback when load GeoJson completes
287    // save each feature's Point as the original coordinates for later reference
288    function saveCoords(features)
289    {
290        // Iterate over all the features in the map
291        features.forEach(function(feature)
292        {
293            var pt = feature.getGeometry().get();
294            vds_coords[feature.getId()] = pt; // save the Point in a dictionary
295        });
296        // update the dot colors from the dynamic json data
297        updateMap();
298        // go adjust the marker coordinates so dots don't overlap
299        adjustCoords(calcDistanceFactor());
300    }
301
302    // magic formula controls distance between dots proportionate to zoom factor
303    function calcDistanceFactor()
304    {
305        // 15 is maximum zoom, the point at which no adjustment is needed
306        return (.0005 * (15 - map.getZoom()));
307    }
308
309    // Adjust the coordinates of dots so they appear side-by-side
310    // The perpendicular vector for each dot has been provided,
311    // so we just need to multiply by a scaling factor (adjAmount)
312    // @param adjAmount amount by which to adjust coordinate
313    function adjustCoords(adjAmount)
314    {
315        // Adjust the NB points a slight amount
316        map.data.forEach(function(feature)
317        {
318            // get the name of the current feature
319            var name = feature.getId();
320            // lookup the original coordinates for this feature
321            var coords = vds_coords[name];
322
323            //retrieve the perpendicular vector (precomputed)
324            var perpx = feature.getProperty("perpx")
325            var perpy = feature.getProperty("perpy")
326                // Make adjustment and save it
327            var myLat = coords.lat() + perpy * adjAmount
328            var myLong = coords.lng() + perpx * adjAmount
329            feature.setGeometry(
330            {
331                lat: myLat,
332                lng: myLong
333            });
334        });
335    }
336
337    // update the color (as needed) for a given marker
338    function updateMarker(marker)
339    {
340        target = marker.id;
341        newColor = marker.properties.color;
342        // see if new color is different than current color
343        currentFeature = map.data.getFeatureById(target);
344        currentColor = currentFeature.getProperty("color");
345        // if a new color is desired then assign it to the feature's color property
346        if (currentColor != newColor)
347        {
348            currentFeature.setProperty("color", newColor);
349            // set zIndex for slowed traffic to a higher value so they overlap
350            currentFeature.setProperty("zIndex", colorZvalues[newColor]);
351        }
352    }
353
354    // Load the dynamic json file for highways, etc.
355    // Ref: https://codepen.io/KryptoniteDove/post/load-json-file-locally-using-pure-javascript
356    function loadJSON(inFile, callback)
357    {
358        var xobj = new XMLHttpRequest();
359        xobj.overrideMimeType("application/json");
360        xobj.open('GET', inFile, true);
361        xobj.onreadystatechange = function()
362        {
363            if (xobj.readyState == 4 && xobj.status == "200")
364            {
365                callback(xobj.responseText);
366            }
367        };
368        // We want ajax to ignore any cached responses
369        xobj.setRequestHeader('If-Modified-Since', 'Sat, 01 Jan 2000 01:01:01 GMT')
370        xobj.send(null);
371    }
372    // Load the highways dynamic json file and update the map
373    function updateMap()
374    {
375        var parsed_JSON;
376        loadJSON(kMapPointsFile, function(response)
377        {
378            // Parse JSON string into object
379            parsed_JSON = JSON.parse(response);
380            // Process each new marker - lookup in current map
381            parsed_JSON.features.forEach(updateMarker);
382        });
383    }
384
385    // Initialize the center button (to re-center the map)
386    function initCenter()
387    {
388        var centerBtnDiv = document.getElementById('ctrButton');
389        map.controls[google.maps.ControlPosition.RIGHT_CENTER].push(centerBtnDiv)
390        centerBtnDiv.title = 'Click to recenter the map';
391
392        // Setup the click event listeners: reset center location and zoom factor
393        centerBtnDiv.addEventListener('click', function()
394        {
395            map.setCenter(centerPoint);
396            map.setZoom(initZoom);
397            clearPlacePins();
398        });
399    }
400
401    // Initialize the search box and listener
402    function initSearch()
403    {
404        // Create the search box and link it to the UI element.
405        var input = document.getElementById('search-input');
406        var searchBox = new google.maps.places.SearchBox(input);
407        map.controls[google.maps.ControlPosition.TOP_LEFT].push(input);
408
409        // Bias the SearchBox results towards current map's viewport.
410        map.addListener('bounds_changed', function()
411        {
412            searchBox.setBounds(map.getBounds());
413        });
414
415        // Listen for the event fired when the user selects a prediction and retrieve
416        // more details for that place.
417        searchBox.addListener('places_changed', function()
418        {
419            var places = searchBox.getPlaces();
420
421            if (places.length == 0)
422            {
423                return;
424            }
425
426            clearPlacePins();
427
428            // Create a bounding region to include the search result places
429            var bounds = new google.maps.LatLngBounds();
430            // For each place, get the icon, name and location.
431            // There may be multiple search results
432            places.forEach(function(place)
433            {
434                if (!place.geometry)
435                {
436                    console.log("Returned place contains no geometry");
437                    return;
438                }
439
440                // Create a marker for each place.
441                placeMarker = new google.maps.Marker(
442                {
443                    map: map,
444                    title: place.name,
445                    position: place.geometry.location
446                })
447
448                // Click on the marker to remove it from the display
449                placeMarker.addListener('click', function()
450                {
451                    placeMarker.setMap(null);
452                });
453
454                // Add this marker to the collection of current markers
455                placePins.push(placeMarker);
456
457                // Create a bounding region to include this place
458                if (place.geometry.viewport)
459                {
460                    // Only geocodes have viewport.
461                    bounds.union(place.geometry.viewport);
462                }
463                else
464                {
465                    bounds.extend(place.geometry.location);
466                }
467            });
468            // This will pan and zoom to the area around the marker
469            //map.fitBounds(bounds);
470            // This will center the map on the new marker(s) but not zoom
471            map.setCenter(bounds.getCenter());
472        });
473    }
474
475    // Remove any place pins from a previous search
476    function clearPlacePins()
477    {
478        placePins.forEach(function(marker)
479        {
480            marker.setMap(null);
481        });
482        placePins = [];
483    }
484    // Initialize the view/hide buttons
485    function initButton()
486    {
487        var cctvBtnDiv = document.getElementById('cctvButton');
488        map.controls[google.maps.ControlPosition.LEFT_BOTTOM].push(cctvBtnDiv)
489        cctvBtnDiv.title = 'Click to toggle cctv view';
490        // Setup the click event listeners to toggle icon display
491        cctvBtnDiv.addEventListener('click', function()
492        {
493            cctv_showing = !cctv_showing;
494            // reveal or hide all the icons
495            for (var key in cctvList)
496            {
497                cctvList[key].setVisible(cctv_showing);
498            }
499            // Determine which button image to show
500            if (cctv_showing)
501            {
502                pic = "images/CPTMSImages/btnDepressed_CCTV.png"
503            }
504            else
505            {
506                pic = "images/CPTMSImages/btnReady_CCTV.png"
507            }
508            document.getElementById('cctvBtnImg').src = pic;
509        });
510
511        var cmsBtnDiv = document.getElementById('cmsButton');
512        map.controls[google.maps.ControlPosition.LEFT_BOTTOM].push(cmsBtnDiv)
513        cmsBtnDiv.title = 'Click to toggle cms view';
514        // Setup the click event listeners to toggle icon display
515        cmsBtnDiv.addEventListener('click', function()
516        {
517            cms_showing = !cms_showing;
518            // reveal or hide all the icons
519            for (var key in cmsList)
520            {
521                //key = Object.keys(cmsList)[i];
522                cmsList[key].setVisible(cms_showing);
523            }
524            // Determine which button image to show
525            if (cms_showing)
526            {
527                pic = "images/CPTMSImages/btnDepressed_CMS.png"
528            }
529            else
530            {
531                pic = "images/CPTMSImages/btnReady_CMS.png"
532            }
533            document.getElementById('cmsBtnImg').src = pic;
534        });
535
536        var vdsBtnDiv = document.getElementById('vdsButton');
537        map.controls[google.maps.ControlPosition.LEFT_BOTTOM].push(vdsBtnDiv)
538        vdsBtnDiv.title = 'Click to toggle vds view';
539
540        // Setup the click event listeners to toggle icon display
541        vdsBtnDiv.addEventListener('click', function()
542        {
543            vds_showing = !vds_showing;
544            // reveal or hide all the dots
545            map.data.forEach(function(feature)
546            {
547                map.data.overrideStyle(feature,
548                {
549                    visible: vds_showing
550                });
551            });
552            // Determine which button image to show
553            if (vds_showing)
554            {
555                pic = "images/CPTMSImages/btnDepressed_VDS.png"
556            }
557            else
558            {
559                pic = "images/CPTMSImages/btnReady_VDS.png"
560            }
561            document.getElementById('vdsBtnImg').src = pic;
562        });
563    }
564
565    function setCMSmarkers()
566    {
567        var simpleImage = "";
568        loadJSON(kCMSstartupFile, function(response)
569        {
570            // Parse JSON string into object
571            cms_info = JSON.parse(response);
572            //console.log(cms_info.data[0].cms);
573            // Process each new marker
574            for (var i = 0; i < cms_info.data.length; i++)
575            {
576                var cms = cms_info.data[i].cms;
577                var currLat = Number(cms.location.latitude);
578                var currLong = Number(cms.location.longitude);
579                var cmsID = cms.index;
580                var directionCode = cms.location.direction.charAt(0);
581                var locationInfo = directionCode + " " + cms.location
582                    .route + " " + cms.location.postmile + " " + cms
583                    .location.locationName
584                cmsList[cmsID] = new google.maps.Marker(
585                {
586                    cmsid: cmsID,
587                    position:
588                    {
589                        lat: currLat,
590                        lng: currLong
591                    },
592                    map: map,
593                    icon: yellowFlag,
594                    title: "#" + cmsID + " " + locationInfo,
595                    location: locationInfo
596                });
597                cmsList[cmsID].setVisible(false); // initially hidden
598                google.maps.event.addListener(cmsList[cmsID], 'click',
599                    function()
600                    {
601                        var dialog = document.getElementById('dialog');
602                        dialog.style.display = 'block';
603                        // fetch the sequential msg #
604                        cmsID = this.cmsid;
605                        // Assign to the hidden field
606                        document.getElementById('cmsID').value = cmsID;
607                        showMessage(cmsID); // note: this is async
608                        document.getElementById('cms-info-label').innerHTML = "CMS ID: " +
609                            cmsID + "&nbsp;&nbsp;&nbsp;LOCATION: " + this.location;
610                        // clear input fields
611                        document.getElementById('msgcontent1').value = "";
612                        document.getElementById('msgcontent2').value = "";
613                        document.getElementById('msgcontent3').value = "";
614                        document.getElementById('msgcontent1').focus();
615                        var span = document.getElementsByClassName("close")[0]
616                            // When the user clicks on <span> (x), close the modal
617                        span.onclick = function()
618                        {
619                            handleClose();
620                        }
621                    });
622             }
623        });
624    }
625
626    function setCCTVmarkers()
627    {
628        loadJSON(kCCTVfile, function(response)
629        {
630            var imgTag = '<IMG WIDTH="700" SRC="images/CPTMSImages/';
631            // Parse JSON string into object
632            cctv_info = JSON.parse(response);
633            // Process each new marker
634            for (var i = 0; i < cctv_info.data.length; i++)
635            {
636                var cctv = cctv_info.data[i].cctv;
637                var currLat = Number(cctv.location.latitude);
638                var currLong = Number(cctv.location.longitude);
639                var locationInfo = cctv.location.locationName;
640                var imgIcon = cctvIcon
641                if ((typeof map.data.getFeatureById(cctv.location.nearVDS)) == "undefined")
642                {
643                    imgIcon = cctvIconWhite;
644                }
645
646                var vdsResult = map.data.getFeatureById(cctv.location.nearVDS)
647                    //console.log("building "+locationInfo+" near "+cctv.location.nearVDS + " found "+vdsResult);
648                cctvList[i] = new google.maps.Marker(
649                {
650                    position:
651                    {
652                        lat: currLat,
653                        lng: currLong
654                    },
655                    map: map,
656                    icon: imgIcon,
657                    title: "#" + i + " " + locationInfo,
658                    cctvid: "" + i,
659                    location: locationInfo,
660                    index: cctv.index,
661                    nearVDS: cctv.location.nearVDS
662                });
663                cctvList[i].info = new google.maps.InfoWindow(
664                {
665                    content: locationInfo
666                });
667
668                cctvList[i].setVisible(false); // initially hidden
669                cctvList[i].addListener('click',
670                    function()
671                    {
672                        cctvIndex = this.index;
673                        //console.log(this.title + " is looking for " + this.nearVDS);
674                        currentFeature = map.data.getFeatureById(this.nearVDS);
675                        currentColor = currentFeature.getProperty("color");
676                        var imgDir = "CCTVFast/";
677                        if (currentColor == "red" || currentColor == "yellow")
678                        {
679                            imgDir = "CCTVSlow/"
680                        }
681                        //console.log(currentFeature.getId() +  ' ' + currentColor + " " + cctvIndex);
682                        this.info.setContent('<div style="font-weight:bold;font-family: monospace">' + this.location + "&nbsp;nearVDS:" +
683                            this.nearVDS + "&nbsp;" + currentColor + "<BR>" + imgTag + imgDir + cctvIndex + '.jpg">' + "</div>");
684                        this.info.open(map, this);
685                    });
686            }
687        });
688    }
689
690
691    // Center justify message text in a 16 column field
692    function justifyText(message)
693    {
694        var kBlanks = "                ";
695        var padLen = (16 - message.length) / 2;
696        var padding = kBlanks.substring(0, padLen);
697        return padding + message;
698    }
699
700    function handleSubmit()
701    {
702        // recover the user's response
703        var response1 = justifyText(document.getElementById('msgcontent1').value.trim());
704        var response2 = justifyText(document.getElementById('msgcontent2').value.trim());
705        var response3 = justifyText(document.getElementById('msgcontent3').value.trim());
706        var newMsg = response1 + response2 + response3;
707        if (newMsg.length == 0)
708        {
709            alert("Nothing to Send ... Proposed is empty.");
710        }
711        else
712        {
713            document.getElementById('msgdisplay1').value = response1;
714            document.getElementById('msgdisplay2').value = response2;
715            document.getElementById('msgdisplay3').value = response3;
716            saveMessage(response1 + "|" + response2 + "|" + response3);
717        }
718    }
719
720    function handleClose()
721    {
722        // hide the display
723        document.getElementById('dialog').style.display = 'none'
724    }
725
726    function handleClear()
727    {
728        document.getElementById('msgdisplay1').value = "";
729        document.getElementById('msgdisplay2').value = "";
730        document.getElementById('msgdisplay3').value = "";
731        saveMessage("||");
732    }
733    // retrieve the current cms message file
734    function showMessage(cmsID)
735    {
736        loadAllMessages();  // because someone else may have made a recent update
737        // lookup the message for this cms ID
738        var message = messageDict[cmsID].cms.message;
739        // show the message in the display
740        document.getElementById('msgdisplay1').value = message.phase1.Line1;
741        document.getElementById('msgdisplay2').value = message.phase1.Line2;
742        document.getElementById('msgdisplay3').value = message.phase1.Line3;
743    }
744    function loadAllMessages()
745    {
746        loadJSON("cms_messages.json", function(response)
747        {
748            // Parse JSON string into object
749            messagejson = JSON.parse(response);
750            // Add each message to a lookup dictionary
751            for (var i = 0; i < messagejson.data.length; i++)
752            {
753                var item = messagejson.data[i];
754                messageDict[item.cms.index] = item;
755            }
756        });
757    }
758    function refreshCMSicons()
759    {
760        loadAllMessages();
761        // Examine each CMS's message
762        for (var idx in cmsList)
763        {
764            // load a yellow flag if there's currently no message
765            if (messageDict[idx].cms.message.phase1.Line1 + 
766                messageDict[idx].cms.message.phase1.Line2 +
767                messageDict[idx].cms.message.phase1.Line3 == "")
768                cmsList[idx].setIcon(yellowFlag);
769            else
770                cmsList[idx].setIcon(blueFlag);
771        }
772    }
773    // Save an updated cms message to the file
774    function saveMessage(outMessage, cmsID)
775    {
776        var cmsID = document.getElementById('cmsID').value;
777        console.log("Saving " + outMessage + " for cmsID " + cmsID)
778        msgParts = outMessage.split("|");
779        messageDict[cmsID].cms.message.phase1.Line1 = msgParts[0];
780        messageDict[cmsID].cms.message.phase1.Line2 = msgParts[1];
781        messageDict[cmsID].cms.message.phase1.Line3 = msgParts[2];
782        // Change icon if something was saved
783        if (outMessage == "||")
784            cmsList[cmsID].setIcon(yellowFlag);
785        else
786            cmsList[cmsID].setIcon(blueFlag);
787        outString = "{\n\t\"data\":\n\t\t" + JSON.stringify(Object.values(messageDict)) + "}";
788
789        var xhttp = new XMLHttpRequest();
790        xhttp.open("GET", "cgi-bin/saveMessage.py?msg=" + outString, true);
791        xhttp.send();
792        // Using POST might be a better idea ... haven't tried this yet
793        //      var xhr = new XMLHttpRequest();
794        //      xhr.open("POST", "/cgi-bin/saveMessage.py?", true);
795        //      xhr.setRequestHeader('Content-Type', 'application/json; charset=UTF-8');
796        // send the collected data as JSON
797        //      xhr.send(JSON.stringify(messageList));
798    }
799
800    // Initialize the map and load the points
801    function initMap()
802    {
803        // Declare the map and where it belongs on the page
804        map = new google.maps.Map(document.getElementById('mapdiv'),
805        {
806            zoom: initZoom,
807            center: centerPoint,
808            styles: night_mode,
809            mapTypeControl: false,
810            streetViewControl: false
811        });
812        loadMapData(); // go load the map data
813        // setup the search box and center button
814        initSearch();
815        initCenter();
816        loadAllMessages(); // load the current message file
817        var startTime = setTimeout(setCMSmarkers, 2000);
818        var startTime = setTimeout(setCCTVmarkers, 3000);
819        initButton();
820
821        // Start a timer to refresh the map every 10 seconds
822        var myTimer = setInterval(updateMap, 10000);
823        // start an interval timer to refresh the cms icons every 10 seconds
824        var cmsTimer = setInterval(refreshCMSicons, 10000);
825        // Listen for zoom changes and move the placePins so as to keep a nice
826        // visual distance between them appropriate to the zoom factor
827        map.addListener('zoom_changed', function()
828        {
829            // fetch how much the map is currently zoomed
830            currentZoom = map.getZoom();
831            // only bother adjusting within this range
832            if ((currentZoom < 16) && (currentZoom > 10))
833            {
834                // magic formula controls distance between dots
835                adjustCoords(calcDistanceFactor());
836            }
837        });
838
839    }
840    // Styles array for Night Mode map
841    // Ref: https://developers.google.com/maps/documentation/javascript/styling
842    var night_mode = [
843                {elementType: 'geometry', stylers: [{color: '#242f3e'}]},
844                {elementType: 'labels.text.stroke', stylers: [{color: '#242f3e'}]},
845                {elementType: 'labels.text.fill', stylers: [{color: '#746855'}]},
846                {
847                  featureType: 'administrative.locality',
848                  elementType: 'labels.text.fill',
849                  stylers: [{color: '#d59563'}]
850                },
851                {
852                  featureType: 'poi',
853                  elementType: 'labels.text.fill',
854                  stylers: [{color: '#d59563'}]
855                },
856                {
857                  featureType: 'poi.park',
858                  elementType: 'geometry',
859                  stylers: [{color: '#263c3f'}]
860                },
861                {
862                  featureType: 'poi.park',
863                  elementType: 'labels.text.fill',
864                  stylers: [{color: '#6b9a76'}]
865                },
866                {
867                  featureType: 'road',
868                  elementType: 'geometry',
869                  stylers: [{color: '#38414e'}]
870                },
871                {
872                  featureType: 'road',
873                  elementType: 'geometry.stroke',
874                  stylers: [{color: '#212a37'}]
875                },
876                {
877                  featureType: 'road',
878                  elementType: 'labels.text.fill',
879                  stylers: [{color: '#9ca5b3'}]
880                },
881                {
882                  featureType: 'road.highway',
883                  elementType: 'geometry',
884                  stylers: [{color: '#746855'}]
885                },
886                {
887                  featureType: 'road.highway',
888                  elementType: 'geometry.stroke',
889                  stylers: [{color: '#1f2835'}]
890                },
891                {
892                  featureType: 'road.highway',
893                  elementType: 'labels.text.fill',
894                  stylers: [{color: '#f3d19c'}]
895                },
896                {
897                  featureType: 'transit',
898                  elementType: 'geometry',
899                  stylers: [{color: '#2f3948'}]
900                },
901                {
902                  featureType: 'transit.station',
903                  elementType: 'labels.text.fill',
904                  stylers: [{color: '#d59563'}]
905                },
906                {
907                  featureType: 'water',
908                  elementType: 'geometry',
909                  stylers: [{color: '#17263c'}]
910                },
911                {
912                  featureType: 'water',
913                  elementType: 'labels.text.fill',
914                  stylers: [{color: '#515c6d'}]
915                },
916                {
917                  featureType: 'water',
918                  elementType: 'labels.text.stroke',
919                  stylers: [{color: '#17263c'}]
920                }
921              ]
922
923    // Using John's API Key
924    </script>
925    <script async defer
926    src="https://maps.googleapis.com/maps/api/js?key=AIzaSyD6iTyN0DjP-9OVkAgicyp4tkC10naE_B8&libraries=places&callback=initMap">
927    </script>
928  </body>
929</html>
Note: See TracBrowser for help on using the repository browser.