source: tmcsimulator/trunk/webapps/cptms.html @ 320

Revision 320, 32.7 KB checked in by jdalbey, 7 years ago (diff)

Move .html files to webapps folder

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