Index: trunk/src/cptms/cptms.html
===================================================================
--- trunk/src/cptms/cptms_map.html	(revision 307)
+++ trunk/src/cptms/cptms.html	(revision 311)
@@ -2,7 +2,7 @@
 <html>
   <head>
-<!-- Launch with  python -m CGIHTTPServer 8080  -->
+<!-- Launch with  python -m CGIHTTPServer 80  -->
   <meta http-equiv="Content-Type" content="text/html;charset=utf-8">
-    <title>CPTMS Map v0.6.0</title> 
+    <title>CPTMS Map v0.6.1</title> 
     <style>
         @font-face {
@@ -95,4 +95,8 @@
            position: relative;\
         }
+.gm-style-iw {
+background-color: #729FFF;
+border-color: #729FFF;
+}
        #dialog {
           position: absolute;
@@ -114,12 +118,12 @@
     /* The Close Button */
     .close {
-      color: orange;
+      color: #2E2E2E;
       float: right;
-      font-size: 20px;
+      font-size: 30px;
       font-weight: bold;
     }
     .close:hover,
     .close:focus {
-      color: red;
+      color: black;
       text-decoration: none;
       cursor: pointer;
@@ -130,4 +134,5 @@
   <body>
     <!-- 
+         Version 6.1 Puts cms messages in json formatted file.  Polls for updates.
          Version 6.0 Adds speed-dependent images to infowindow for cctv icons
          Version 5.8 Adds an infowindow with a static image for all cctv icons
@@ -158,5 +163,5 @@
     <!-- The div element for the popup dialog -->
     <div id="dialog" style="display:none;">
-        <span class="close">&times;</span>
+        <span class="close">&#x2612;</span>  <!-- close button symbol -->
         <br>
         <div id="cms-info-label" style="font-family:'Courier New'">CMS ID: xxx LOCATION: </div>
@@ -181,6 +186,14 @@
 
     <script>
+//TODO:  Can we speed up vds loading?  can we load the data in the background
+// 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?
+// would pre-computing coords for each zoom level and have them accessed in a table make a difference?
+// Add interval refresh for cms icon colors.  
+// Add phase 2 to messages.
+// cms set visible gets undefined error after last icon is processed.
+// have we solved the duplicate cms defect?
+
     // a global variable for the google map
-    var map;  
+    var map;
     // a global dictionary to lookup a station's original coordinates
     var vds_coords = {};
@@ -194,5 +207,8 @@
     //var centerPoint = {lat: 33.687228, lng: -117.872148};
     // Constant for map center location in District 12
-    var centerPoint = {lat: 33.693385, lng: -117.798937};
+    var centerPoint = {
+        lat: 33.693385,
+        lng: -117.798937
+    };
     // Initial map zoom
     var initZoom = 11;
@@ -200,21 +216,27 @@
     // and their associated zvalues so slower traffic dots are more visible.
     // white means a disabled spot
-    var colorZvalues = {"white":5,"lime":10,"yellow":20,"red":30};
-var kCMSstartupFile = "cmsStatusD12.json";
-var kCCTVfile = "cctv_locations_D12.json";
-var blueFlag = "images/CPTMSImages/icon_cmsBlue.png";
-var yellowFlag = "images/CPTMSImages/icon_cmsYellow.png";
-var cctvIcon = "images/CPTMSImages/icon_cctvCyan.png";
-var cctvIconWhite = "images/CPTMSImages/icon_cctvWhite.png";
-var messageList;
-var cms_info;
-var cmsList = [];
-var cctvList = [];
-var cms_showing = false;
-var vds_showing = true;
-var cctv_showing = false;
+    var colorZvalues = {
+        "white": 5,
+        "lime": 10,
+        "yellow": 20,
+        "red": 30
+    };
+    var kCMSstartupFile = "cmsStatusD12.json";
+    var kCCTVfile = "cctv_locations_D12.json";
+    var blueFlag = "images/CPTMSImages/icon_cmsBlue.png";
+    var yellowFlag = "images/CPTMSImages/icon_cmsYellow.png";
+    var cctvIcon = "images/CPTMSImages/icon_cctvCyan.png";
+    var cctvIconWhite = "images/CPTMSImages/icon_cctvWhite.png";
+
+    var cms_info;
+    var cmsList = [];
+    var messageDict = {};
+    var cctvList = [];
+    var cms_showing = false;
+    var vds_showing = true;
+    var cctv_showing = false;
     // Build a solid colored icon to use instead of the classic pin
     // Use a diamond on N and E directions, circle on S and W directions
-    function dotSymbol(color,postmileID) //,direction)
+    function dotSymbol(color, postmileID) //,direction)
     {
         var circle = google.maps.SymbolPath.CIRCLE;
@@ -230,5 +252,5 @@
             scale: 5,
             strokeColor: "black", // the border color
-            strokeWeight: 1,      // the border thickness
+            strokeWeight: 1, // the border thickness
             fillColor: color,
             fillOpacity: 1.0
@@ -240,9 +262,9 @@
     {
         // Load the static map data and call saveCoords when done
-        map.data.loadGeoJson(kMapStartupFile,null,saveCoords)
+        map.data.loadGeoJson(kMapStartupFile, null, saveCoords)
 
         // Style the map data by applying the desired properties to each feature (marker)
         // The function will be called every time a feature's properties are updated.
-        map.data.setStyle(function(feature) 
+        map.data.setStyle(function(feature)
         {
             // Get the postmile id 
@@ -252,12 +274,12 @@
             var street = feature.getProperty("street");
             // Build the marker
-            var iconSymbol = dotSymbol(ptColor,name);
+            var iconSymbol = dotSymbol(ptColor, name);
             // return the StyleOptions
             return {
-                    icon: iconSymbol,
-                    title: name + " @" + street,  // set rollover text
-                    // set zIndex for slowed traffic to a higher value so they overlap
-                    zIndex: colorZvalues[ptColor]
-                   };
+                icon: iconSymbol,
+                title: name + " @" + street, // set rollover text
+                // set zIndex for slowed traffic to a higher value so they overlap
+                zIndex: colorZvalues[ptColor]
+            };
         });
     }
@@ -269,6 +291,6 @@
         features.forEach(function(feature)
         {
-            var pt = feature.getGeometry().get(); 
-            vds_coords[feature.getId()] = pt;  // save the Point in a dictionary
+            var pt = feature.getGeometry().get();
+            vds_coords[feature.getId()] = pt; // save the Point in a dictionary
         });
         // update the dot colors from the dynamic json data 
@@ -282,5 +304,5 @@
     {
         // 15 is maximum zoom, the point at which no adjustment is needed
-        return (.0005*(15-map.getZoom()));  
+        return (.0005 * (15 - map.getZoom()));
     }
 
@@ -302,14 +324,18 @@
             var perpx = feature.getProperty("perpx")
             var perpy = feature.getProperty("perpy")
-            // Make adjustment and save it
+                // Make adjustment and save it
             var myLat = coords.lat() + perpy * adjAmount
             var myLong = coords.lng() + perpx * adjAmount
-            feature.setGeometry({lat:myLat, lng:myLong});
-        });
-    }
-
-     // update the color (as needed) for a given marker
-     function updateMarker(marker)
-     {
+            feature.setGeometry(
+            {
+                lat: myLat,
+                lng: myLong
+            });
+        });
+    }
+
+    // update the color (as needed) for a given marker
+    function updateMarker(marker)
+    {
         target = marker.id;
         newColor = marker.properties.color;
@@ -320,33 +346,33 @@
         if (currentColor != newColor)
         {
-            currentFeature.setProperty("color",newColor);
+            currentFeature.setProperty("color", newColor);
             // set zIndex for slowed traffic to a higher value so they overlap
             currentFeature.setProperty("zIndex", colorZvalues[newColor]);
         }
-     }
+    }
 
     // Load the dynamic json file for highways, etc.
     // Ref: https://codepen.io/KryptoniteDove/post/load-json-file-locally-using-pure-javascript
-function loadJSON(inFile, callback)
-{
-    var xobj = new XMLHttpRequest();
-    xobj.overrideMimeType("application/json");
-    xobj.open('GET', inFile, true);
-    xobj.onreadystatechange = function()
-    {
-        if (xobj.readyState == 4 && xobj.status == "200")
-        {
-            callback(xobj.responseText);
-        }
-    };
-    // We want ajax to ignore any cached responses
-    xobj.setRequestHeader('If-Modified-Since', 'Sat, 01 Jan 2000 01:01:01 GMT')
-    xobj.send(null);
-}
-     // Load the highways dynamic json file and update the map
-     function updateMap()
-     {
+    function loadJSON(inFile, callback)
+    {
+        var xobj = new XMLHttpRequest();
+        xobj.overrideMimeType("application/json");
+        xobj.open('GET', inFile, true);
+        xobj.onreadystatechange = function()
+        {
+            if (xobj.readyState == 4 && xobj.status == "200")
+            {
+                callback(xobj.responseText);
+            }
+        };
+        // We want ajax to ignore any cached responses
+        xobj.setRequestHeader('If-Modified-Since', 'Sat, 01 Jan 2000 01:01:01 GMT')
+        xobj.send(null);
+    }
+    // Load the highways dynamic json file and update the map
+    function updateMap()
+    {
         var parsed_JSON;
-        loadJSON(kMapPointsFile,function(response)
+        loadJSON(kMapPointsFile, function(response)
         {
             // Parse JSON string into object
@@ -355,5 +381,5 @@
             parsed_JSON.features.forEach(updateMarker);
         });
-     }
+    }
 
     // Initialize the center button (to re-center the map)
@@ -365,8 +391,9 @@
 
         // Setup the click event listeners: reset center location and zoom factor
-        centerBtnDiv.addEventListener('click', function() {
-          map.setCenter(centerPoint);
-          map.setZoom(initZoom);
-          clearPlacePins();
+        centerBtnDiv.addEventListener('click', function()
+        {
+            map.setCenter(centerPoint);
+            map.setZoom(initZoom);
+            clearPlacePins();
         });
     }
@@ -381,56 +408,66 @@
 
         // Bias the SearchBox results towards current map's viewport.
-        map.addListener('bounds_changed', function() {
-          searchBox.setBounds(map.getBounds());
+        map.addListener('bounds_changed', function()
+        {
+            searchBox.setBounds(map.getBounds());
         });
 
         // Listen for the event fired when the user selects a prediction and retrieve
         // more details for that place.
-        searchBox.addListener('places_changed', function() {
-          var places = searchBox.getPlaces();
-
-          if (places.length == 0) {
-            return;
-          }
-
-          clearPlacePins();
-
-          // Create a bounding region to include the search result places
-          var bounds = new google.maps.LatLngBounds();
-          // For each place, get the icon, name and location.
-          // There may be multiple search results
-          places.forEach(function(place) {
-            if (!place.geometry) {
-              console.log("Returned place contains no geometry");
-              return;
-            }
-
-            // Create a marker for each place.
-            placeMarker = new google.maps.Marker({
-              map: map,
-              title: place.name,
-              position: place.geometry.location
-            })
-
-            // Click on the marker to remove it from the display
-            placeMarker.addListener('click', function() {
-              placeMarker.setMap(null);
+        searchBox.addListener('places_changed', function()
+        {
+            var places = searchBox.getPlaces();
+
+            if (places.length == 0)
+            {
+                return;
+            }
+
+            clearPlacePins();
+
+            // Create a bounding region to include the search result places
+            var bounds = new google.maps.LatLngBounds();
+            // For each place, get the icon, name and location.
+            // There may be multiple search results
+            places.forEach(function(place)
+            {
+                if (!place.geometry)
+                {
+                    console.log("Returned place contains no geometry");
+                    return;
+                }
+
+                // Create a marker for each place.
+                placeMarker = new google.maps.Marker(
+                {
+                    map: map,
+                    title: place.name,
+                    position: place.geometry.location
+                })
+
+                // Click on the marker to remove it from the display
+                placeMarker.addListener('click', function()
+                {
+                    placeMarker.setMap(null);
+                });
+
+                // Add this marker to the collection of current markers 
+                placePins.push(placeMarker);
+
+                // Create a bounding region to include this place
+                if (place.geometry.viewport)
+                {
+                    // Only geocodes have viewport.
+                    bounds.union(place.geometry.viewport);
+                }
+                else
+                {
+                    bounds.extend(place.geometry.location);
+                }
             });
-
-            // Add this marker to the collection of current markers 
-            placePins.push(placeMarker);
-
-            // Create a bounding region to include this place
-            if (place.geometry.viewport) {
-              // Only geocodes have viewport.
-              bounds.union(place.geometry.viewport);
-            } else {
-              bounds.extend(place.geometry.location);
-            }
-          });
-          // This will pan and zoom to the area around the marker
-          //map.fitBounds(bounds);
-          // This will center the map on the new marker(s) but not zoom
-          map.setCenter(bounds.getCenter());
+            // This will pan and zoom to the area around the marker
+            //map.fitBounds(bounds);
+            // This will center the map on the new marker(s) but not zoom
+            map.setCenter(bounds.getCenter());
         });
     }
@@ -439,22 +476,24 @@
     function clearPlacePins()
     {
-          placePins.forEach(function(marker) {
+        placePins.forEach(function(marker)
+        {
             marker.setMap(null);
-          });
-          placePins = [];
-    }
-// Initialize the view/hide buttons 
-function initButton()
-{
-    var cctvBtnDiv = document.getElementById('cctvButton');
-    map.controls[google.maps.ControlPosition.LEFT_BOTTOM].push(cctvBtnDiv)
-    cctvBtnDiv.title = 'Click to toggle cctv view';
-    // Setup the click event listeners to toggle icon display
-    cctvBtnDiv.addEventListener('click', function() {
+        });
+        placePins = [];
+    }
+    // Initialize the view/hide buttons 
+    function initButton()
+    {
+        var cctvBtnDiv = document.getElementById('cctvButton');
+        map.controls[google.maps.ControlPosition.LEFT_BOTTOM].push(cctvBtnDiv)
+        cctvBtnDiv.title = 'Click to toggle cctv view';
+        // Setup the click event listeners to toggle icon display
+        cctvBtnDiv.addEventListener('click', function()
+        {
             cctv_showing = !cctv_showing;
             // reveal or hide all the icons
-            for (var i = 0; i < cctvList.length; i++)
-            {
-                cctvList[i].setVisible(cctv_showing);
+            for (var key in cctvList)
+            {
+                cctvList[key].setVisible(cctv_showing);
             }
             // Determine which button image to show
@@ -467,17 +506,19 @@
                 pic = "images/CPTMSImages/btnReady_CCTV.png"
             }
-            document.getElementById('cctvBtnImg').src=pic;
-    });
-
-    var cmsBtnDiv = document.getElementById('cmsButton');
-    map.controls[google.maps.ControlPosition.LEFT_BOTTOM].push(cmsBtnDiv)
-    cmsBtnDiv.title = 'Click to toggle cms view';
-    // Setup the click event listeners to toggle icon display
-    cmsBtnDiv.addEventListener('click', function() {
+            document.getElementById('cctvBtnImg').src = pic;
+        });
+
+        var cmsBtnDiv = document.getElementById('cmsButton');
+        map.controls[google.maps.ControlPosition.LEFT_BOTTOM].push(cmsBtnDiv)
+        cmsBtnDiv.title = 'Click to toggle cms view';
+        // Setup the click event listeners to toggle icon display
+        cmsBtnDiv.addEventListener('click', function()
+        {
             cms_showing = !cms_showing;
             // reveal or hide all the icons
-            for (var i = 0; i < cmsList.length; i++)
-            {
-                cmsList[i].setVisible(cms_showing);
+            for (var key in cmsList)
+            {
+                //key = Object.keys(cmsList)[i];
+                cmsList[key].setVisible(cms_showing);
             }
             // Determine which button image to show
@@ -490,18 +531,22 @@
                 pic = "images/CPTMSImages/btnReady_CMS.png"
             }
-            document.getElementById('cmsBtnImg').src=pic;
-    });
-
-    var vdsBtnDiv = document.getElementById('vdsButton');
-    map.controls[google.maps.ControlPosition.LEFT_BOTTOM].push(vdsBtnDiv)
-    vdsBtnDiv.title = 'Click to toggle vds view';
-
-    // Setup the click event listeners to toggle icon display
-    vdsBtnDiv.addEventListener('click', function() {
+            document.getElementById('cmsBtnImg').src = pic;
+        });
+
+        var vdsBtnDiv = document.getElementById('vdsButton');
+        map.controls[google.maps.ControlPosition.LEFT_BOTTOM].push(vdsBtnDiv)
+        vdsBtnDiv.title = 'Click to toggle vds view';
+
+        // Setup the click event listeners to toggle icon display
+        vdsBtnDiv.addEventListener('click', function()
+        {
             vds_showing = !vds_showing;
             // reveal or hide all the dots
             map.data.forEach(function(feature)
             {
-                map.data.overrideStyle(feature, {visible:vds_showing});
+                map.data.overrideStyle(feature,
+                {
+                    visible: vds_showing
+                });
             });
             // Determine which button image to show
@@ -514,247 +559,276 @@
                 pic = "images/CPTMSImages/btnReady_VDS.png"
             }
-            document.getElementById('vdsBtnImg').src=pic;
-    });
-}
-function setCMSmarkers()
-{
-    var simpleImage = "";
-    loadJSON(kCMSstartupFile, function(response)
-    {
-        // Parse JSON string into object
-        cms_info = JSON.parse(response);
-        //console.log(cms_info.data[0].cms);
-        // Process each new marker 
-        for (var i = 0; i < cms_info.data.length; i++)
-        {
-            var cms = cms_info.data[i].cms;
-            var currLat = Number(cms.location.latitude);
-            var currLong = Number(cms.location.longitude);
+            document.getElementById('vdsBtnImg').src = pic;
+        });
+    }
+
+    function setCMSmarkers()
+    {
+        var simpleImage = "";
+        loadJSON(kCMSstartupFile, function(response)
+        {
+            // Parse JSON string into object
+            cms_info = JSON.parse(response);
+            //console.log(cms_info.data[0].cms);
+            // Process each new marker 
+            for (var i = 0; i < cms_info.data.length; i++)
+            {
+                var cms = cms_info.data[i].cms;
+                var currLat = Number(cms.location.latitude);
+                var currLong = Number(cms.location.longitude);
+                var cmsID = cms.index;
+                var directionCode = cms.location.direction.charAt(0);
+                var locationInfo = directionCode + " " + cms.location
+                    .route + " " + cms.location.postmile + " " + cms
+                    .location.locationName
+                cmsList[cmsID] = new google.maps.Marker(
+                {
+                    cmsid: cmsID,
+                    position:
+                    {
+                        lat: currLat,
+                        lng: currLong
+                    },
+                    map: map,
+                    icon: yellowFlag,
+                    title: "#" + cmsID + " " + locationInfo,
+                    location: locationInfo
+                });
+                cmsList[cmsID].setVisible(false); // initially hidden
+                google.maps.event.addListener(cmsList[cmsID], 'click',
+                    function()
+                    {
+                        var dialog = document.getElementById('dialog');
+                        dialog.style.display = 'block';
+                        // fetch the sequential msg #
+                        cmsID = this.cmsid;
+                        // Assign to the hidden field
+                        document.getElementById('cmsID').value = cmsID;
+                        showMessage(cmsID); // note: this is async
+                        document.getElementById('cms-info-label').innerHTML = "CMS ID: " +
+                            cmsID + "&nbsp;&nbsp;&nbsp;LOCATION: " + this.location;
+                        // clear input fields
+                        document.getElementById('msgcontent1').value = "";
+                        document.getElementById('msgcontent2').value = "";
+                        document.getElementById('msgcontent3').value = "";
+                        document.getElementById('msgcontent1').focus();
+                        var span = document.getElementsByClassName("close")[0]
+                            // When the user clicks on <span> (x), close the modal
+                        span.onclick = function()
+                        {
+                            handleClose();
+                        }
+                    });
+             }
+        });
+    }
+
+    function setCCTVmarkers()
+    {
+        loadJSON(kCCTVfile, function(response)
+        {
+            var imgTag = '<IMG WIDTH="700" SRC="images/CPTMSImages/';
+            // Parse JSON string into object
+            cctv_info = JSON.parse(response);
+            // Process each new marker 
+            for (var i = 0; i < cctv_info.data.length; i++)
+            {
+                var cctv = cctv_info.data[i].cctv;
+                var currLat = Number(cctv.location.latitude);
+                var currLong = Number(cctv.location.longitude);
+                var locationInfo = cctv.location.locationName;
+                var imgIcon = cctvIcon
+                if ((typeof map.data.getFeatureById(cctv.location.nearVDS)) == "undefined")
+                {
+                    imgIcon = cctvIconWhite;
+                }
+
+                var vdsResult = map.data.getFeatureById(cctv.location.nearVDS)
+                    //console.log("building "+locationInfo+" near "+cctv.location.nearVDS + " found "+vdsResult); 
+                cctvList[i] = new google.maps.Marker(
+                {
+                    position:
+                    {
+                        lat: currLat,
+                        lng: currLong
+                    },
+                    map: map,
+                    icon: imgIcon,
+                    title: "#" + i + " " + locationInfo,
+                    cctvid: "" + i,
+                    location: locationInfo,
+                    index: cctv.index,
+                    nearVDS: cctv.location.nearVDS
+                });
+                cctvList[i].info = new google.maps.InfoWindow(
+                {
+                    content: locationInfo
+                });
+
+                cctvList[i].setVisible(false); // initially hidden
+                cctvList[i].addListener('click',
+                    function()
+                    {
+                        cctvIndex = this.index;
+                        //console.log(this.title + " is looking for " + this.nearVDS);
+                        currentFeature = map.data.getFeatureById(this.nearVDS);
+                        currentColor = currentFeature.getProperty("color");
+                        var imgDir = "CCTVFast/";
+                        if (currentColor == "red" || currentColor == "yellow")
+                        {
+                            imgDir = "CCTVSlow/"
+                        }
+                        //console.log(currentFeature.getId() +  ' ' + currentColor + " " + cctvIndex);
+                        this.info.setContent('<div style="font-weight:bold;font-family: monospace">' + this.location + "&nbsp;nearVDS:" +
+                            this.nearVDS + "&nbsp;" + currentColor + "<BR>" + imgTag + imgDir + cctvIndex + '.jpg">' + "</div>");
+                        this.info.open(map, this);
+                    });
+            }
+        });
+    }
+
+
+    // Center justify message text in a 16 column field
+    function justifyText(message)
+    {
+        var kBlanks = "                ";
+        var padLen = (16 - message.length) / 2;
+        var padding = kBlanks.substring(0, padLen);
+        return padding + message;
+    }
+
+    function handleSubmit()
+    {
+        // recover the user's response
+        var response1 = justifyText(document.getElementById('msgcontent1').value.trim());
+        var response2 = justifyText(document.getElementById('msgcontent2').value.trim());
+        var response3 = justifyText(document.getElementById('msgcontent3').value.trim());
+        var newMsg = response1 + response2 + response3;
+        if (newMsg.length == 0)
+        {
+            alert("Nothing to Send ... Proposed is empty.");
+        }
+        else
+        {
+            document.getElementById('msgdisplay1').value = response1;
+            document.getElementById('msgdisplay2').value = response2;
+            document.getElementById('msgdisplay3').value = response3;
+            saveMessage(response1 + "|" + response2 + "|" + response3);
+        }
+    }
+
+    function handleClose()
+    {
+        // hide the display
+        document.getElementById('dialog').style.display = 'none'
+    }
+
+    function handleClear()
+    {
+        document.getElementById('msgdisplay1').value = "";
+        document.getElementById('msgdisplay2').value = "";
+        document.getElementById('msgdisplay3').value = "";
+        saveMessage("||");
+    }
+    // retrieve the current cms message file
+    function showMessage(cmsID)
+    {
+        loadAllMessages();  // because someone else may have made a recent update
+        // lookup the message for this cms ID
+        var message = messageDict[cmsID].cms.message;
+        // show the message in the display
+        document.getElementById('msgdisplay1').value = message.phase1.Line1;
+        document.getElementById('msgdisplay2').value = message.phase1.Line2;
+        document.getElementById('msgdisplay3').value = message.phase1.Line3;
+    }
+    function loadAllMessages()
+    {
+        loadJSON("cms_messages.json", function(response)
+        {
+            // Parse JSON string into object
+            messagejson = JSON.parse(response);
+            // Add each message to a lookup dictionary 
+            for (var i = 0; i < messagejson.data.length; i++)
+            {
+                var item = messagejson.data[i];
+                messageDict[item.cms.index] = item;
+            }
+        });
+    }
+    function refreshCMSicons()
+    {
+        loadAllMessages();
+        // Examine each CMS's message
+        for (var idx in cmsList)
+        {
             // load a yellow flag if there's currently no message
-            if (messageList[i] == "||")
-                simpleImage = yellowFlag;
+            if (messageDict[idx].cms.message.phase1.Line1 + 
+                messageDict[idx].cms.message.phase1.Line2 +
+                messageDict[idx].cms.message.phase1.Line3 == "")
+                cmsList[idx].setIcon(yellowFlag);
             else
-                simpleImage = blueFlag;
-            var directionCode = cms.location.direction.charAt(0);
-            var locationInfo = directionCode + " " + cms.location
-                .route + " " + cms.location.postmile + " " + cms
-                .location.locationName
-            cmsList[i] = new google.maps.Marker(
-            {
-                position:
-                {
-                    lat: currLat,
-                    lng: currLong
-                },
-                map: map,
-                icon: simpleImage,
-                title: "#"+i+" " +locationInfo,
-                cmsid: "" + i,
-                location: locationInfo
-            });
-            cmsList[i].setVisible(false); // initially hidden
-            google.maps.event.addListener(cmsList[i], 'click',
-                function()
-                {
-                    var dialog = document.getElementById('dialog');
-                    dialog.style.display = 'block';
-                    // fetch the sequential msg #
-                    cmsID = Number(this.cmsid);
-                    // Assign to the hidden field
-                    document.getElementById('cmsID').value = cmsID;
-                    getMessage(cmsID); // note: this is async
-                    document.getElementById('cms-info-label').innerHTML = "CMS ID: " +
-                        cmsID + "&nbsp;&nbsp;&nbsp;LOCATION: " + this.location;
-                    // clear input fields
-                    document.getElementById('msgcontent1').value = "";
-                    document.getElementById('msgcontent2').value = "";
-                    document.getElementById('msgcontent3').value = "";
-                    document.getElementById('msgcontent1').focus();
-                    var span = document.getElementsByClassName("close")[0]
-                    // When the user clicks on <span> (x), close the modal
-                    span.onclick = function() {
-                      handleClose();
-                    }
-                });
+                cmsList[idx].setIcon(blueFlag);
         }
-    });
-}
-function setCCTVmarkers()
-{
-    loadJSON(kCCTVfile, function(response)
-    {
-        var imgTag = '<IMG WIDTH="700" SRC="images/CPTMSImages/';
-        // Parse JSON string into object
-        cctv_info = JSON.parse(response);
-        // Process each new marker 
-        for (var i = 0; i < cctv_info.data.length; i++)
-        {
-            var cctv = cctv_info.data[i].cctv;
-            var currLat = Number(cctv.location.latitude);
-            var currLong = Number(cctv.location.longitude);
-            var locationInfo = cctv.location.locationName;
-            var imgIcon = cctvIcon
-            if ((typeof map.data.getFeatureById(cctv.location.nearVDS)) == "undefined")
-            {
-                imgIcon = cctvIconWhite;
-            }
-
-            var vdsResult = map.data.getFeatureById(cctv.location.nearVDS)
-            //console.log("building "+locationInfo+" near "+cctv.location.nearVDS + " found "+vdsResult); 
-            cctvList[i] = new google.maps.Marker(
-            {
-                position:
-                {
-                    lat: currLat,
-                    lng: currLong
-                },
-                map: map,
-                icon: imgIcon,
-                title: "#"+i+" " +locationInfo,
-                cctvid: "" + i,
-                location: locationInfo,
-                index: cctv.index,
-                nearVDS: cctv.location.nearVDS
-            });
-            cctvList[i].info=new google.maps.InfoWindow({
-                content: locationInfo
-            });
-        
-            cctvList[i].setVisible(false); // initially hidden
-            cctvList[i].addListener('click',
-                function()
-                {
-                    cctvIndex = this.index;
-                    //console.log(this.title + " is looking for " + this.nearVDS);
-                    currentFeature = map.data.getFeatureById(this.nearVDS);
-                    currentColor = currentFeature.getProperty("color");
-                    var imgDir = "CCTVFast/";
-                    if (currentColor == "red" || currentColor == "yellow")
-                    {
-                        imgDir = "CCTVSlow/"
-                    }
-                    //console.log(currentFeature.getId() +  ' ' + currentColor + " " + cctvIndex);
-                    this.info.setContent('<div>' + this.location + "&nbsp;nearVDS:" 
-         + this.nearVDS + "&nbsp;" + currentColor + "<BR>"+ imgTag + imgDir + cctvIndex + '.jpg">' + "</div>");
-                    this.info.open(map, this);
-                });
-        }
-    });
-}
-
-
-// Center justify message text in a 16 column field
-function justifyText(message)
-{
-    var kBlanks = "                ";
-    var padLen = (16 - message.length)/2;
-    var padding = kBlanks.substring(0,padLen);
-    return padding + message;
-}
-
-function handleSubmit()
-{
-    // recover the user's response
-    var response1 = justifyText(document.getElementById('msgcontent1').value.trim());
-    var response2 = justifyText(document.getElementById('msgcontent2').value.trim());
-    var response3 = justifyText(document.getElementById('msgcontent3').value.trim());
-    var newMsg = response1+response2+response3;
-    if (newMsg.length == 0)
-    {
-        alert("Nothing to Send ... Proposed is empty.");
-    }
-    else
-    {
-        document.getElementById('msgdisplay1').value = response1;
-        document.getElementById('msgdisplay2').value = response2;
-        document.getElementById('msgdisplay3').value = response3;
-        saveMessage(response1 + "|" + response2 + "|" + response3);
-    }
-}
-
-function handleClose()
-{
-    // hide the display
-    document.getElementById('dialog').style.display = 'none'
-}
-
-function handleClear()
-{
-    document.getElementById('msgdisplay1').value = "";
-    document.getElementById('msgdisplay2').value = "";
-    document.getElementById('msgdisplay3').value = "";
-    saveMessage("||");
-}
-// retrieve the current cms message file
-function getMessage(cmsID)
-{
-    loadJSON("messagefile.txt", function(response)
-    {
-        // Parse JSON string into object
-        messageList = JSON.parse(response);
-        // select a message from json for the given cmsID
-        console.log("get by cmsID=" + cmsID);
-        var cmsSign = document.getElementById('msgdisplay1');
-        messageparts = messageList[cmsID].split("|");
-        cmsSign.value = messageparts[0];
-        document.getElementById('msgdisplay2').value = messageparts[1];
-        document.getElementById('msgdisplay3').value = messageparts[2];
-    });
-}
-// Save an updated cms message to the file
-// NB: cms id's are one-based, json array is zero-based.
-function saveMessage(outMessage, cmsID)
-{
-    var cmsID = document.getElementById('cmsID').value;
-    console.log("Saving " + outMessage + " for cmsID " + cmsID)
-    messageList[cmsID] = outMessage;
-    // Change icon if something was saved
-    if (outMessage == "||")
-        cmsList[cmsID].setIcon(yellowFlag);
-    else
-        cmsList[cmsID].setIcon(blueFlag);
-
-    var xhttp = new XMLHttpRequest();
-    xhttp.open("GET", "http://localhost:8080/cgi-bin/saveMessage.py?msg=" + JSON
-        .stringify(messageList), true);
-    xhttp.send();
-    // Using POST might be a better idea ... haven't tried this yet
-    //      var xhr = new XMLHttpRequest();
-    //      xhr.open("POST", "/cgi-bin/saveMessage.py?", true);
-    //      xhr.setRequestHeader('Content-Type', 'application/json; charset=UTF-8');
-    // send the collected data as JSON
-    //      xhr.send(JSON.stringify(messageList));
-} 
+    }
+    // Save an updated cms message to the file
+    function saveMessage(outMessage, cmsID)
+    {
+        var cmsID = document.getElementById('cmsID').value;
+        console.log("Saving " + outMessage + " for cmsID " + cmsID)
+        msgParts = outMessage.split("|");
+        messageDict[cmsID].cms.message.phase1.Line1 = msgParts[0];
+        messageDict[cmsID].cms.message.phase1.Line2 = msgParts[1];
+        messageDict[cmsID].cms.message.phase1.Line3 = msgParts[2];
+        // Change icon if something was saved
+        if (outMessage == "||")
+            cmsList[cmsID].setIcon(yellowFlag);
+        else
+            cmsList[cmsID].setIcon(blueFlag);
+        outString = "{\n\t\"data\":\n\t\t" + JSON.stringify(Object.values(messageDict)) + "}";
+
+        var xhttp = new XMLHttpRequest();
+        xhttp.open("GET", "cgi-bin/saveMessage.py?msg=" + outString, true);
+        xhttp.send();
+        // Using POST might be a better idea ... haven't tried this yet
+        //      var xhr = new XMLHttpRequest();
+        //      xhr.open("POST", "/cgi-bin/saveMessage.py?", true);
+        //      xhr.setRequestHeader('Content-Type', 'application/json; charset=UTF-8');
+        // send the collected data as JSON
+        //      xhr.send(JSON.stringify(messageList));
+    }
 
     // Initialize the map and load the points
-    function initMap() 
+    function initMap()
     {
         // Declare the map and where it belongs on the page
-        map = new google.maps.Map( document.getElementById('mapdiv'), 
-        {
-            zoom: initZoom, 
+        map = new google.maps.Map(document.getElementById('mapdiv'),
+        {
+            zoom: initZoom,
             center: centerPoint,
             styles: night_mode,
             mapTypeControl: false,
-            streetViewControl: false  
-        });
-        loadMapData();  // go load the map data
+            streetViewControl: false
+        });
+        loadMapData(); // go load the map data
         // setup the search box and center button
         initSearch();
         initCenter();
+        loadAllMessages(); // load the current message file
+        var startTime = setTimeout(setCMSmarkers, 2000);
+        var startTime = setTimeout(setCCTVmarkers, 3000);
         initButton();
-        getMessage(1); // load the current message file
-        var startTime = setTimeout(setCMSmarkers,2000);
-        var startTime = setTimeout(setCCTVmarkers, 3000);
 
         // Start a timer to refresh the map every 10 seconds
         var myTimer = setInterval(updateMap, 10000);
+        // start an interval timer to refresh the cms icons every 10 seconds
+        var cmsTimer = setInterval(refreshCMSicons, 10000);
         // Listen for zoom changes and move the placePins so as to keep a nice
         // visual distance between them appropriate to the zoom factor
-        map.addListener('zoom_changed', function() {
+        map.addListener('zoom_changed', function()
+        {
             // fetch how much the map is currently zoomed
-            currentZoom = map.getZoom(); 
+            currentZoom = map.getZoom();
             // only bother adjusting within this range
-            if ((currentZoom <16) && (currentZoom>10))
+            if ((currentZoom < 16) && (currentZoom > 10))
             {
                 // magic formula controls distance between dots
@@ -764,5 +838,4 @@
 
     }
-
     // Styles array for Night Mode map
     // Ref: https://developers.google.com/maps/documentation/javascript/styling
