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

Revision 325, 32.5 KB checked in by jdalbey, 7 years ago (diff)

more file cleanup

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