Index: trunk/src/cptms/cms_messages.json
===================================================================
--- trunk/src/cptms/cms_messages.json	(revision 311)
+++ trunk/src/cptms/cms_messages.json	(revision 311)
@@ -0,0 +1,1 @@
+{"data":[{"cms":{"index":"1200022","message":{"displayTime":"","phase1":{"Line1":"","Line2":"","Line3":""},"phase2":{"Line1":"","Line2":"","Line3":""}}}},{"cms":{"index":"1200023","message":{"displayTime":"","phase1":{"Line1":"","Line2":"","Line3":""},"phase2":{"Line1":"","Line2":"","Line3":""}}}},{"cms":{"index":"1200024","message":{"displayTime":"","phase1":{"Line1":"bottom of fifth","Line2":"dodgers up by 2","Line3":"        "},"phase2":{"Line1":"","Line2":"","Line3":""}}}},{"cms":{"index":"1200025","message":{"displayTime":"","phase1":{"Line1":"","Line2":"","Line3":""},"phase2":{"Line1":"","Line2":"","Line3":""}}}},{"cms":{"index":"1200026","message":{"displayTime":"","phase1":{"Line1":"","Line2":"","Line3":""},"phase2":{"Line1":"","Line2":"","Line3":""}}}},{"cms":{"index":"1200027","message":{"displayTime":"","phase1":{"Line1":"","Line2":"","Line3":""},"phase2":{"Line1":"","Line2":"","Line3":""}}}},{"cms":{"index":"1200028","message":{"displayTime":"","phase1":{"Line1":"","Line2":"","Line3":""},"phase2":{"Line1":"","Line2":"","Line3":""}}}},{"cms":{"index":"1200029","message":{"displayTime":"","phase1":{"Line1":"","Line2":"","Line3":""},"phase2":{"Line1":"","Line2":"","Line3":""}}}},{"cms":{"index":"1200030","message":{"displayTime":"","phase1":{"Line1":"","Line2":"","Line3":""},"phase2":{"Line1":"","Line2":"","Line3":""}}}},{"cms":{"index":"1200031","message":{"displayTime":"","phase1":{"Line1":"","Line2":"","Line3":""},"phase2":{"Line1":"","Line2":"","Line3":""}}}},{"cms":{"index":"1200032","message":{"displayTime":"","phase1":{"Line1":"","Line2":"","Line3":""},"phase2":{"Line1":"","Line2":"","Line3":""}}}},{"cms":{"index":"1200033","message":{"displayTime":"","phase1":{"Line1":"","Line2":"","Line3":""},"phase2":{"Line1":"","Line2":"","Line3":""}}}},{"cms":{"index":"1200034","message":{"displayTime":"","phase1":{"Line1":"","Line2":"","Line3":""},"phase2":{"Line1":"","Line2":"","Line3":""}}}},{"cms":{"index":"1200035","message":{"displayTime":"","phase1":{"Line1":"","Line2":"","Line3":""},"phase2":{"Line1":"","Line2":"","Line3":""}}}},{"cms":{"index":"1200036","message":{"displayTime":"","phase1":{"Line1":"","Line2":"","Line3":""},"phase2":{"Line1":"","Line2":"","Line3":""}}}},{"cms":{"index":"1200037","message":{"displayTime":"","phase1":{"Line1":"","Line2":"","Line3":""},"phase2":{"Line1":"","Line2":"","Line3":""}}}},{"cms":{"index":"1200038","message":{"displayTime":"","phase1":{"Line1":"","Line2":"","Line3":""},"phase2":{"Line1":"","Line2":"","Line3":""}}}},{"cms":{"index":"1200039","message":{"displayTime":"","phase1":{"Line1":"","Line2":"","Line3":""},"phase2":{"Line1":"","Line2":"","Line3":""}}}},{"cms":{"index":"1200040","message":{"displayTime":"","phase1":{"Line1":"","Line2":"","Line3":""},"phase2":{"Line1":"","Line2":"","Line3":""}}}},{"cms":{"index":"1200041","message":{"displayTime":"","phase1":{"Line1":"","Line2":"","Line3":""},"phase2":{"Line1":"","Line2":"","Line3":""}}}},{"cms":{"index":"1200042","message":{"displayTime":"","phase1":{"Line1":"","Line2":"","Line3":""},"phase2":{"Line1":"","Line2":"","Line3":""}}}},{"cms":{"index":"1200043","message":{"displayTime":"","phase1":{"Line1":"","Line2":"","Line3":""},"phase2":{"Line1":"","Line2":"","Line3":""}}}},{"cms":{"index":"1200044","message":{"displayTime":"","phase1":{"Line1":"","Line2":"","Line3":""},"phase2":{"Line1":"","Line2":"","Line3":""}}}},{"cms":{"index":"1200045","message":{"displayTime":"","phase1":{"Line1":"","Line2":"","Line3":""},"phase2":{"Line1":"","Line2":"","Line3":""}}}},{"cms":{"index":"1200046","message":{"displayTime":"","phase1":{"Line1":"","Line2":"","Line3":""},"phase2":{"Line1":"","Line2":"","Line3":""}}}},{"cms":{"index":"1200047","message":{"displayTime":"","phase1":{"Line1":"","Line2":"","Line3":""},"phase2":{"Line1":"","Line2":"","Line3":""}}}},{"cms":{"index":"1200048","message":{"displayTime":"","phase1":{"Line1":"","Line2":"","Line3":""},"phase2":{"Line1":"","Line2":"","Line3":""}}}},{"cms":{"index":"1200049","message":{"displayTime":"","phase1":{"Line1":"","Line2":"","Line3":""},"phase2":{"Line1":"","Line2":"","Line3":""}}}},{"cms":{"index":"1200050","message":{"displayTime":"","phase1":{"Line1":"","Line2":"","Line3":""},"phase2":{"Line1":"","Line2":"","Line3":""}}}},{"cms":{"index":"1200051","message":{"displayTime":"","phase1":{"Line1":"","Line2":"","Line3":""},"phase2":{"Line1":"","Line2":"","Line3":""}}}},{"cms":{"index":"1200052","message":{"displayTime":"","phase1":{"Line1":"","Line2":"","Line3":""},"phase2":{"Line1":"","Line2":"","Line3":""}}}},{"cms":{"index":"1200053","message":{"displayTime":"","phase1":{"Line1":"  slow for the","Line2":"   cone zone","Line3":"        "},"phase2":{"Line1":"","Line2":"","Line3":""}}}},{"cms":{"index":"1200055","message":{"displayTime":"","phase1":{"Line1":"","Line2":"","Line3":""},"phase2":{"Line1":"","Line2":"","Line3":""}}}},{"cms":{"index":"1200056","message":{"displayTime":"","phase1":{"Line1":"    caution","Line2":"  slow traffic","Line3":"     ahead"},"phase2":{"Line1":"","Line2":"","Line3":""}}}},{"cms":{"index":"1200057","message":{"displayTime":"","phase1":{"Line1":"","Line2":"","Line3":""},"phase2":{"Line1":"","Line2":"","Line3":""}}}},{"cms":{"index":"1200058","message":{"displayTime":"","phase1":{"Line1":"","Line2":"","Line3":""},"phase2":{"Line1":"","Line2":"","Line3":""}}}},{"cms":{"index":"1208488","message":{"displayTime":"","phase1":{"Line1":"","Line2":"","Line3":""},"phase2":{"Line1":"","Line2":"","Line3":""}}}},{"cms":{"index":"1211184","message":{"displayTime":"","phase1":{"Line1":"","Line2":"","Line3":""},"phase2":{"Line1":"","Line2":"","Line3":""}}}},{"cms":{"index":"1211185","message":{"displayTime":"","phase1":{"Line1":"","Line2":"","Line3":""},"phase2":{"Line1":"","Line2":"","Line3":""}}}},{"cms":{"index":"1211967","message":{"displayTime":"","phase1":{"Line1":"","Line2":"","Line3":""},"phase2":{"Line1":"","Line2":"","Line3":""}}}},{"cms":{"index":"1211978","message":{"displayTime":"","phase1":{"Line1":"","Line2":"","Line3":""},"phase2":{"Line1":"","Line2":"","Line3":""}}}},{"cms":{"index":"1212138","message":{"displayTime":"","phase1":{"Line1":"","Line2":"","Line3":""},"phase2":{"Line1":"","Line2":"","Line3":""}}}},{"cms":{"index":"1212822","message":{"displayTime":"","phase1":{"Line1":"","Line2":"","Line3":""},"phase2":{"Line1":"","Line2":"","Line3":""}}}},{"cms":{"index":"1212823","message":{"displayTime":"","phase1":{"Line1":"","Line2":"","Line3":""},"phase2":{"Line1":"","Line2":"","Line3":""}}}},{"cms":{"index":"1214503","message":{"displayTime":"","phase1":{"Line1":"","Line2":"","Line3":""},"phase2":{"Line1":"","Line2":"","Line3":""}}}},{"cms":{"index":"1214504","message":{"displayTime":"","phase1":{"Line1":"","Line2":"","Line3":""},"phase2":{"Line1":"","Line2":"","Line3":""}}}},{"cms":{"index":"1214505","message":{"displayTime":"","phase1":{"Line1":"","Line2":"","Line3":""},"phase2":{"Line1":"","Line2":"","Line3":""}}}},{"cms":{"index":"1214506","message":{"displayTime":"","phase1":{"Line1":"","Line2":"","Line3":""},"phase2":{"Line1":"","Line2":"","Line3":""}}}},{"cms":{"index":"1214507","message":{"displayTime":"","phase1":{"Line1":"","Line2":"","Line3":""},"phase2":{"Line1":"","Line2":"","Line3":""}}}},{"cms":{"index":"1214508","message":{"displayTime":"","phase1":{"Line1":"","Line2":"","Line3":""},"phase2":{"Line1":"","Line2":"","Line3":""}}}},{"cms":{"index":"1214509","message":{"displayTime":"","phase1":{"Line1":"","Line2":"","Line3":""},"phase2":{"Line1":"","Line2":"","Line3":""}}}},{"cms":{"index":"1214510","message":{"displayTime":"","phase1":{"Line1":"","Line2":"","Line3":""},"phase2":{"Line1":"","Line2":"","Line3":""}}}},{"cms":{"index":"1214511","message":{"displayTime":"","phase1":{"Line1":"","Line2":"","Line3":""},"phase2":{"Line1":"","Line2":"","Line3":""}}}},{"cms":{"index":"1214512","message":{"displayTime":"","phase1":{"Line1":"","Line2":"","Line3":""},"phase2":{"Line1":"","Line2":"","Line3":""}}}},{"cms":{"index":"1214513","message":{"displayTime":"","phase1":{"Line1":"","Line2":"","Line3":""},"phase2":{"Line1":"","Line2":"","Line3":""}}}},{"cms":{"index":"1214514","message":{"displayTime":"","phase1":{"Line1":"","Line2":"","Line3":""},"phase2":{"Line1":"","Line2":"","Line3":""}}}},{"cms":{"index":"1214956","message":{"displayTime":"","phase1":{"Line1":"","Line2":"","Line3":""},"phase2":{"Line1":"","Line2":"","Line3":""}}}},{"cms":{"index":"1217542","message":{"displayTime":"","phase1":{"Line1":"","Line2":"","Line3":""},"phase2":{"Line1":"","Line2":"","Line3":""}}}},{"cms":{"index":"1218442","message":{"displayTime":"","phase1":{"Line1":"   every dog","Line2":"    has its","Line3":"      day"},"phase2":{"Line1":"","Line2":"","Line3":""}}}},{"cms":{"index":"1218482","message":{"displayTime":"","phase1":{"Line1":"","Line2":"","Line3":""},"phase2":{"Line1":"","Line2":"","Line3":""}}}},{"cms":{"index":"1218483","message":{"displayTime":"","phase1":{"Line1":"","Line2":"","Line3":""},"phase2":{"Line1":"","Line2":"","Line3":""}}}},{"cms":{"index":"1218484","message":{"displayTime":"","phase1":{"Line1":"","Line2":"","Line3":""},"phase2":{"Line1":"","Line2":"","Line3":""}}}},{"cms":{"index":"1218485","message":{"displayTime":"","phase1":{"Line1":"","Line2":"","Line3":""},"phase2":{"Line1":"","Line2":"","Line3":""}}}},{"cms":{"index":"1218486","message":{"displayTime":"","phase1":{"Line1":"","Line2":"","Line3":""},"phase2":{"Line1":"","Line2":"","Line3":""}}}}]}
Index: trunk/src/cptms/cptms.html
===================================================================
--- trunk/src/cptms/cptms.html	(revision 311)
+++ trunk/src/cptms/cptms.html	(revision 311)
@@ -0,0 +1,929 @@
+<!DOCTYPE html>
+<html>
+  <head>
+<!-- Launch with  python -m CGIHTTPServer 80  -->
+  <meta http-equiv="Content-Type" content="text/html;charset=utf-8">
+    <title>CPTMS Map v0.6.1</title> 
+    <style>
+        @font-face {
+          font-family: Scoreboard;
+          src: url('scoreboard.ttf');
+        }
+      /* Set the size of the div element that contains the map */
+      #mapdiv {
+        height: 100%;
+        width: 100%;  
+       }
+        /* Makes the page fill the window. */
+      html, body {
+        height: 100%;
+        margin: 0;
+        padding: 0;
+      }
+        textarea {
+           height: 33px;
+           width: 272px;
+           resize: none;
+           font-family: Scoreboard;
+           font-size: xx-large;
+           background-color: #2F4F4F;
+           color: yellow;
+        }
+       input {
+           border: thin solid #333;
+           padding: 2px;
+           font-family: "Lucida Console", Monaco, monospace;
+           font-size: medium;
+        }
+        /*img.resize {
+          max-width:auto;
+          max-height:500px;
+        }*/
+      #search-input {
+        background-color: #17263c;  /* #CD853F;  /*#E6E6FA; /* lavender */
+        color: #E6E6FA;  /* #FFEFD5; */
+        font-family: Roboto;
+        font-size: 18px;
+        font-weight: 400;
+        margin-left: 12px;
+        padding: 0 11px 0 13px;
+        text-overflow: ellipsis;
+        border-color: #746855;   /* #4d90fe; */
+        width: 400px;
+      }
+
+      #search-input:focus {
+        border-color: #E6E6FA; 
+      }
+
+      #ctrButton {
+        font-size: 40px;
+        margin-right: 9px;
+        background-color: #fff;
+        color: #47476b;
+        cursor: pointer;
+      }
+      #cms-info-label {
+           height: 20px;
+           width: 590px;
+           overflow: hidden;
+           background-color: #A8C5FF;  /*#ECECFB; */
+           border: thin solid #BDBDBD;
+           padding: 5px;
+       }
+      #message-display {
+           height: 172px;
+           width: 300px;
+           overflow: hidden;
+           float: left;
+       }
+      #message-input {
+           height: 122px;
+           width: 165px;
+           background-color: #729FFF;
+           float: left;
+       }
+       #buttonPanel {
+           height: 122px;
+           width: 130px;
+           background-color: #729FFF;
+           border-left: none;
+           float: left;
+           padding: 20px;
+        }
+       .wrapper {
+           position: relative;\
+        }
+.gm-style-iw {
+background-color: #729FFF;
+border-color: #729FFF;
+}
+       #dialog {
+          position: absolute;
+          top: 10%;
+          right: 20%;
+          background-color: #729FFF; /* #ECECFB; */
+          margin: auto;
+          padding: 20px;
+          border: 1px solid #888;
+          width: 680px;
+          display: none;           
+        }
+      .unstyled-button {
+        border: 0 none;
+        padding: 0;
+        background: none;
+        cursor: pointer;
+      }    
+    /* The Close Button */
+    .close {
+      color: #2E2E2E;
+      float: right;
+      font-size: 30px;
+      font-weight: bold;
+    }
+    .close:hover,
+    .close:focus {
+      color: black;
+      text-decoration: none;
+      cursor: pointer;
+    }
+
+    </style>
+  </head>
+  <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
+         Version 5.7 integrates CCTV icons and button (but empty click handler)
+         Version 5.6 integrates CMS features
+         Version 5.5 renames title to CPTMS, loads static data on startup and dynamic data
+         every ten seconds.  
+         Version 5.4 adds Search box and Center button
+         Version 5.3 fixed dot color update defect, increased refresh rate to 10 sec.
+         Version 5.2 places red dots overlapping yellow dots.
+         Version 5.1 removes the map and street view buttons and the H3 tag.
+         Version 5 uses precomputed perpendicular vector in dot adjustment function
+         Version 4 Adjust the spacing between dots when the map is zoomed.
+         Version 3 does loadGeoJson only once, and subsequently does an ajax load
+         of the highways file, and selectively updates only those placePins whose
+         color has changed. 
+         @author jdalbey  2019.2.17
+    -->
+    <!-- The text area input for the Search Box -->
+    <input id="search-input" class="controls" type="text" placeholder="Search Box">
+    <!--The div element where the map appears -->
+    <div id="mapdiv"></div>
+    <!--The div element where the buttons appears -->
+    <div id="ctrButton" class="unstyled-button">&#x2295;</div>
+    <button id="cctvButton" class="unstyled-button"><img id="cctvBtnImg" src="images/CPTMSImages/btnReady_CCTV.png"></button>
+    <button id="cmsButton" class="unstyled-button"><img id="cmsBtnImg" src="images/CPTMSImages/btnReady_CMS.png"></button>
+    <button id="vdsButton" class="unstyled-button"><img id="vdsBtnImg" src="images/CPTMSImages/btnDepressed_VDS.png"></button>
+    <!-- The div element for the popup dialog -->
+    <div id="dialog" style="display:none;">
+        <span class="close">&#x2612;</span>  <!-- close button symbol -->
+        <br>
+        <div id="cms-info-label" style="font-family:'Courier New'">CMS ID: xxx LOCATION: </div>
+        <br>
+        <input id='cmsID' value="" type='hidden'/>
+        <div id="message-input">Proposed:
+        <input id="msgcontent1"  maxlength="16" type="text"/><br><br>
+        <input id="msgcontent2"  maxlength="16" type="text"/><br><br>
+        <input id="msgcontent3"  maxlength="16" type="text"/>
+        </div>        
+        <div id="buttonPanel"    style="display: block;">
+        <button onclick="handleSubmit();">Send >></button><br>
+        <button onclick="handleClear();">Clear >></button><br>
+        <button onclick="handleClose();">Close </button>
+        </div>
+        <div id="message-display"  style="display: block;">Current:
+         <textarea readonly id="msgdisplay1" maxlength="16" rows="1" cols="16"></textarea>
+         <textarea readonly id="msgdisplay2" maxlength="16" rows="1" cols="16"></textarea>
+         <textarea readonly id="msgdisplay3" maxlength="16" rows="1" cols="16"></textarea>
+        </div>
+    </div>
+
+    <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;
+    // a global dictionary to lookup a station's original coordinates
+    var vds_coords = {};
+    // a global variable to hold locations of marked search places
+    var placePins = [];
+    // Constant name of dynamic json data file created by CADserver
+    var kMapPointsFile = "highways.json";
+    // Constant name of initial (static) highways file used once at startup
+    var kMapStartupFile = "highways_startup.json";
+    // Constant for map center location: The John Wayne Airport
+    //var centerPoint = {lat: 33.687228, lng: -117.872148};
+    // Constant for map center location in District 12
+    var centerPoint = {
+        lat: 33.693385,
+        lng: -117.798937
+    };
+    // Initial map zoom
+    var initZoom = 11;
+    // Dot colors used in traffic model to indicate free-flowing, slowed, and stopped traffic
+    // 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 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)
+    {
+        var circle = google.maps.SymbolPath.CIRCLE;
+        var diamond = 'M -1,0 0,-1 1,0 0,1 z';
+        var myShape = circle;
+        // See if postmile name contains N or W letters
+        //if ((postmileID.indexOf('N') != -1) || (postmileID.indexOf('W') != -1))
+        //{
+        //   myShape = diamond
+        //}
+        return {
+            path: myShape,
+            scale: 5,
+            strokeColor: "black", // the border color
+            strokeWeight: 1, // the border thickness
+            fillColor: color,
+            fillOpacity: 1.0
+        };
+    }
+
+    // Load the map data from a json file and style all the points
+    function loadMapData()
+    {
+        // Load the static map data and call saveCoords when done
+        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)
+        {
+            // Get the postmile id 
+            var name = feature.getId();
+            // Get the desired color value
+            var ptColor = feature.getProperty("color");
+            var street = feature.getProperty("street");
+            // Build the marker
+            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]
+            };
+        });
+    }
+    // callback when load GeoJson completes
+    // save each feature's Point as the original coordinates for later reference
+    function saveCoords(features)
+    {
+        // Iterate over all the features in the map
+        features.forEach(function(feature)
+        {
+            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 
+        updateMap();
+        // go adjust the marker coordinates so dots don't overlap
+        adjustCoords(calcDistanceFactor());
+    }
+
+    // magic formula controls distance between dots proportionate to zoom factor
+    function calcDistanceFactor()
+    {
+        // 15 is maximum zoom, the point at which no adjustment is needed
+        return (.0005 * (15 - map.getZoom()));
+    }
+
+    // Adjust the coordinates of dots so they appear side-by-side
+    // The perpendicular vector for each dot has been provided,
+    // so we just need to multiply by a scaling factor (adjAmount) 
+    // @param adjAmount amount by which to adjust coordinate 
+    function adjustCoords(adjAmount)
+    {
+        // Adjust the NB points a slight amount
+        map.data.forEach(function(feature)
+        {
+            // get the name of the current feature
+            var name = feature.getId();
+            // lookup the original coordinates for this feature
+            var coords = vds_coords[name];
+
+            //retrieve the perpendicular vector (precomputed)
+            var perpx = feature.getProperty("perpx")
+            var perpy = feature.getProperty("perpy")
+                // 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)
+    {
+        target = marker.id;
+        newColor = marker.properties.color;
+        // see if new color is different than current color
+        currentFeature = map.data.getFeatureById(target);
+        currentColor = currentFeature.getProperty("color");
+        // if a new color is desired then assign it to the feature's color property
+        if (currentColor != 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()
+    {
+        var parsed_JSON;
+        loadJSON(kMapPointsFile, function(response)
+        {
+            // Parse JSON string into object
+            parsed_JSON = JSON.parse(response);
+            // Process each new marker - lookup in current map
+            parsed_JSON.features.forEach(updateMarker);
+        });
+    }
+
+    // Initialize the center button (to re-center the map)
+    function initCenter()
+    {
+        var centerBtnDiv = document.getElementById('ctrButton');
+        map.controls[google.maps.ControlPosition.RIGHT_CENTER].push(centerBtnDiv)
+        centerBtnDiv.title = 'Click to recenter the map';
+
+        // Setup the click event listeners: reset center location and zoom factor
+        centerBtnDiv.addEventListener('click', function()
+        {
+            map.setCenter(centerPoint);
+            map.setZoom(initZoom);
+            clearPlacePins();
+        });
+    }
+
+    // Initialize the search box and listener 
+    function initSearch()
+    {
+        // Create the search box and link it to the UI element.
+        var input = document.getElementById('search-input');
+        var searchBox = new google.maps.places.SearchBox(input);
+        map.controls[google.maps.ControlPosition.TOP_LEFT].push(input);
+
+        // Bias the SearchBox results towards current map's viewport.
+        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);
+                });
+
+                // 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());
+        });
+    }
+
+    // Remove any place pins from a previous search
+    function clearPlacePins()
+    {
+        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()
+        {
+            cctv_showing = !cctv_showing;
+            // reveal or hide all the icons
+            for (var key in cctvList)
+            {
+                cctvList[key].setVisible(cctv_showing);
+            }
+            // Determine which button image to show
+            if (cctv_showing)
+            {
+                pic = "images/CPTMSImages/btnDepressed_CCTV.png"
+            }
+            else
+            {
+                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()
+        {
+            cms_showing = !cms_showing;
+            // reveal or hide all the icons
+            for (var key in cmsList)
+            {
+                //key = Object.keys(cmsList)[i];
+                cmsList[key].setVisible(cms_showing);
+            }
+            // Determine which button image to show
+            if (cms_showing)
+            {
+                pic = "images/CPTMSImages/btnDepressed_CMS.png"
+            }
+            else
+            {
+                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()
+        {
+            vds_showing = !vds_showing;
+            // reveal or hide all the dots
+            map.data.forEach(function(feature)
+            {
+                map.data.overrideStyle(feature,
+                {
+                    visible: vds_showing
+                });
+            });
+            // Determine which button image to show
+            if (vds_showing)
+            {
+                pic = "images/CPTMSImages/btnDepressed_VDS.png"
+            }
+            else
+            {
+                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);
+                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 (messageDict[idx].cms.message.phase1.Line1 + 
+                messageDict[idx].cms.message.phase1.Line2 +
+                messageDict[idx].cms.message.phase1.Line3 == "")
+                cmsList[idx].setIcon(yellowFlag);
+            else
+                cmsList[idx].setIcon(blueFlag);
+        }
+    }
+    // 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()
+    {
+        // Declare the map and where it belongs on the page
+        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
+        // 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();
+
+        // 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()
+        {
+            // fetch how much the map is currently zoomed
+            currentZoom = map.getZoom();
+            // only bother adjusting within this range
+            if ((currentZoom < 16) && (currentZoom > 10))
+            {
+                // magic formula controls distance between dots
+                adjustCoords(calcDistanceFactor());
+            }
+        });
+
+    }
+    // Styles array for Night Mode map
+    // Ref: https://developers.google.com/maps/documentation/javascript/styling
+    var night_mode = [
+                {elementType: 'geometry', stylers: [{color: '#242f3e'}]},
+                {elementType: 'labels.text.stroke', stylers: [{color: '#242f3e'}]},
+                {elementType: 'labels.text.fill', stylers: [{color: '#746855'}]},
+                {
+                  featureType: 'administrative.locality',
+                  elementType: 'labels.text.fill',
+                  stylers: [{color: '#d59563'}]
+                },
+                {
+                  featureType: 'poi',
+                  elementType: 'labels.text.fill',
+                  stylers: [{color: '#d59563'}]
+                },
+                {
+                  featureType: 'poi.park',
+                  elementType: 'geometry',
+                  stylers: [{color: '#263c3f'}]
+                },
+                {
+                  featureType: 'poi.park',
+                  elementType: 'labels.text.fill',
+                  stylers: [{color: '#6b9a76'}]
+                },
+                {
+                  featureType: 'road',
+                  elementType: 'geometry',
+                  stylers: [{color: '#38414e'}]
+                },
+                {
+                  featureType: 'road',
+                  elementType: 'geometry.stroke',
+                  stylers: [{color: '#212a37'}]
+                },
+                {
+                  featureType: 'road',
+                  elementType: 'labels.text.fill',
+                  stylers: [{color: '#9ca5b3'}]
+                },
+                {
+                  featureType: 'road.highway',
+                  elementType: 'geometry',
+                  stylers: [{color: '#746855'}]
+                },
+                {
+                  featureType: 'road.highway',
+                  elementType: 'geometry.stroke',
+                  stylers: [{color: '#1f2835'}]
+                },
+                {
+                  featureType: 'road.highway',
+                  elementType: 'labels.text.fill',
+                  stylers: [{color: '#f3d19c'}]
+                },
+                {
+                  featureType: 'transit',
+                  elementType: 'geometry',
+                  stylers: [{color: '#2f3948'}]
+                },
+                {
+                  featureType: 'transit.station',
+                  elementType: 'labels.text.fill',
+                  stylers: [{color: '#d59563'}]
+                },
+                {
+                  featureType: 'water',
+                  elementType: 'geometry',
+                  stylers: [{color: '#17263c'}]
+                },
+                {
+                  featureType: 'water',
+                  elementType: 'labels.text.fill',
+                  stylers: [{color: '#515c6d'}]
+                },
+                {
+                  featureType: 'water',
+                  elementType: 'labels.text.stroke',
+                  stylers: [{color: '#17263c'}]
+                }
+              ]
+
+    // Using John's API Key
+    </script>
+    <script async defer
+    src="https://maps.googleapis.com/maps/api/js?key=AIzaSyD6iTyN0DjP-9OVkAgicyp4tkC10naE_B8&libraries=places&callback=initMap">
+    </script>
+  </body>
+</html>
Index: trunk/src/cptms/cgi-bin/saveMessage.py
===================================================================
--- trunk/src/cptms/cgi-bin/saveMessage.py	(revision 295)
+++ trunk/src/cptms/cgi-bin/saveMessage.py	(revision 311)
@@ -11,5 +11,5 @@
    
 # write message to file
-text_file = open("messagefile.txt", "w")
+text_file = open("cms_messages.json", "w")
 text_file.write(outMessage)
 text_file.close()
Index: trunk/src/cptms/cptms_map.html
===================================================================
--- trunk/src/cptms/cptms_map.html	(revision 307)
+++ 	(revision )
@@ -1,856 +1,0 @@
-<!DOCTYPE html>
-<html>
-  <head>
-<!-- Launch with  python -m CGIHTTPServer 8080  -->
-  <meta http-equiv="Content-Type" content="text/html;charset=utf-8">
-    <title>CPTMS Map v0.6.0</title> 
-    <style>
-        @font-face {
-          font-family: Scoreboard;
-          src: url('scoreboard.ttf');
-        }
-      /* Set the size of the div element that contains the map */
-      #mapdiv {
-        height: 100%;
-        width: 100%;  
-       }
-        /* Makes the page fill the window. */
-      html, body {
-        height: 100%;
-        margin: 0;
-        padding: 0;
-      }
-        textarea {
-           height: 33px;
-           width: 272px;
-           resize: none;
-           font-family: Scoreboard;
-           font-size: xx-large;
-           background-color: #2F4F4F;
-           color: yellow;
-        }
-       input {
-           border: thin solid #333;
-           padding: 2px;
-           font-family: "Lucida Console", Monaco, monospace;
-           font-size: medium;
-        }
-        /*img.resize {
-          max-width:auto;
-          max-height:500px;
-        }*/
-      #search-input {
-        background-color: #17263c;  /* #CD853F;  /*#E6E6FA; /* lavender */
-        color: #E6E6FA;  /* #FFEFD5; */
-        font-family: Roboto;
-        font-size: 18px;
-        font-weight: 400;
-        margin-left: 12px;
-        padding: 0 11px 0 13px;
-        text-overflow: ellipsis;
-        border-color: #746855;   /* #4d90fe; */
-        width: 400px;
-      }
-
-      #search-input:focus {
-        border-color: #E6E6FA; 
-      }
-
-      #ctrButton {
-        font-size: 40px;
-        margin-right: 9px;
-        background-color: #fff;
-        color: #47476b;
-        cursor: pointer;
-      }
-      #cms-info-label {
-           height: 20px;
-           width: 590px;
-           overflow: hidden;
-           background-color: #A8C5FF;  /*#ECECFB; */
-           border: thin solid #BDBDBD;
-           padding: 5px;
-       }
-      #message-display {
-           height: 172px;
-           width: 300px;
-           overflow: hidden;
-           float: left;
-       }
-      #message-input {
-           height: 122px;
-           width: 165px;
-           background-color: #729FFF;
-           float: left;
-       }
-       #buttonPanel {
-           height: 122px;
-           width: 130px;
-           background-color: #729FFF;
-           border-left: none;
-           float: left;
-           padding: 20px;
-        }
-       .wrapper {
-           position: relative;\
-        }
-       #dialog {
-          position: absolute;
-          top: 10%;
-          right: 20%;
-          background-color: #729FFF; /* #ECECFB; */
-          margin: auto;
-          padding: 20px;
-          border: 1px solid #888;
-          width: 680px;
-          display: none;           
-        }
-      .unstyled-button {
-        border: 0 none;
-        padding: 0;
-        background: none;
-        cursor: pointer;
-      }    
-    /* The Close Button */
-    .close {
-      color: orange;
-      float: right;
-      font-size: 20px;
-      font-weight: bold;
-    }
-    .close:hover,
-    .close:focus {
-      color: red;
-      text-decoration: none;
-      cursor: pointer;
-    }
-
-    </style>
-  </head>
-  <body>
-    <!-- 
-         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
-         Version 5.7 integrates CCTV icons and button (but empty click handler)
-         Version 5.6 integrates CMS features
-         Version 5.5 renames title to CPTMS, loads static data on startup and dynamic data
-         every ten seconds.  
-         Version 5.4 adds Search box and Center button
-         Version 5.3 fixed dot color update defect, increased refresh rate to 10 sec.
-         Version 5.2 places red dots overlapping yellow dots.
-         Version 5.1 removes the map and street view buttons and the H3 tag.
-         Version 5 uses precomputed perpendicular vector in dot adjustment function
-         Version 4 Adjust the spacing between dots when the map is zoomed.
-         Version 3 does loadGeoJson only once, and subsequently does an ajax load
-         of the highways file, and selectively updates only those placePins whose
-         color has changed. 
-         @author jdalbey  2019.2.17
-    -->
-    <!-- The text area input for the Search Box -->
-    <input id="search-input" class="controls" type="text" placeholder="Search Box">
-    <!--The div element where the map appears -->
-    <div id="mapdiv"></div>
-    <!--The div element where the buttons appears -->
-    <div id="ctrButton" class="unstyled-button">&#x2295;</div>
-    <button id="cctvButton" class="unstyled-button"><img id="cctvBtnImg" src="images/CPTMSImages/btnReady_CCTV.png"></button>
-    <button id="cmsButton" class="unstyled-button"><img id="cmsBtnImg" src="images/CPTMSImages/btnReady_CMS.png"></button>
-    <button id="vdsButton" class="unstyled-button"><img id="vdsBtnImg" src="images/CPTMSImages/btnDepressed_VDS.png"></button>
-    <!-- The div element for the popup dialog -->
-    <div id="dialog" style="display:none;">
-        <span class="close">&times;</span>
-        <br>
-        <div id="cms-info-label" style="font-family:'Courier New'">CMS ID: xxx LOCATION: </div>
-        <br>
-        <input id='cmsID' value="" type='hidden'/>
-        <div id="message-input">Proposed:
-        <input id="msgcontent1"  maxlength="16" type="text"/><br><br>
-        <input id="msgcontent2"  maxlength="16" type="text"/><br><br>
-        <input id="msgcontent3"  maxlength="16" type="text"/>
-        </div>        
-        <div id="buttonPanel"    style="display: block;">
-        <button onclick="handleSubmit();">Send >></button><br>
-        <button onclick="handleClear();">Clear >></button><br>
-        <button onclick="handleClose();">Close </button>
-        </div>
-        <div id="message-display"  style="display: block;">Current:
-         <textarea readonly id="msgdisplay1" maxlength="16" rows="1" cols="16"></textarea>
-         <textarea readonly id="msgdisplay2" maxlength="16" rows="1" cols="16"></textarea>
-         <textarea readonly id="msgdisplay3" maxlength="16" rows="1" cols="16"></textarea>
-        </div>
-    </div>
-
-    <script>
-    // a global variable for the google map
-    var map;  
-    // a global dictionary to lookup a station's original coordinates
-    var vds_coords = {};
-    // a global variable to hold locations of marked search places
-    var placePins = [];
-    // Constant name of dynamic json data file created by CADserver
-    var kMapPointsFile = "highways.json";
-    // Constant name of initial (static) highways file used once at startup
-    var kMapStartupFile = "highways_startup.json";
-    // Constant for map center location: The John Wayne Airport
-    //var centerPoint = {lat: 33.687228, lng: -117.872148};
-    // Constant for map center location in District 12
-    var centerPoint = {lat: 33.693385, lng: -117.798937};
-    // Initial map zoom
-    var initZoom = 11;
-    // Dot colors used in traffic model to indicate free-flowing, slowed, and stopped traffic
-    // 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;
-    // 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)
-    {
-        var circle = google.maps.SymbolPath.CIRCLE;
-        var diamond = 'M -1,0 0,-1 1,0 0,1 z';
-        var myShape = circle;
-        // See if postmile name contains N or W letters
-        //if ((postmileID.indexOf('N') != -1) || (postmileID.indexOf('W') != -1))
-        //{
-        //   myShape = diamond
-        //}
-        return {
-            path: myShape,
-            scale: 5,
-            strokeColor: "black", // the border color
-            strokeWeight: 1,      // the border thickness
-            fillColor: color,
-            fillOpacity: 1.0
-        };
-    }
-
-    // Load the map data from a json file and style all the points
-    function loadMapData()
-    {
-        // Load the static map data and call saveCoords when done
-        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) 
-        {
-            // Get the postmile id 
-            var name = feature.getId();
-            // Get the desired color value
-            var ptColor = feature.getProperty("color");
-            var street = feature.getProperty("street");
-            // Build the marker
-            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]
-                   };
-        });
-    }
-    // callback when load GeoJson completes
-    // save each feature's Point as the original coordinates for later reference
-    function saveCoords(features)
-    {
-        // Iterate over all the features in the map
-        features.forEach(function(feature)
-        {
-            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 
-        updateMap();
-        // go adjust the marker coordinates so dots don't overlap
-        adjustCoords(calcDistanceFactor());
-    }
-
-    // magic formula controls distance between dots proportionate to zoom factor
-    function calcDistanceFactor()
-    {
-        // 15 is maximum zoom, the point at which no adjustment is needed
-        return (.0005*(15-map.getZoom()));  
-    }
-
-    // Adjust the coordinates of dots so they appear side-by-side
-    // The perpendicular vector for each dot has been provided,
-    // so we just need to multiply by a scaling factor (adjAmount) 
-    // @param adjAmount amount by which to adjust coordinate 
-    function adjustCoords(adjAmount)
-    {
-        // Adjust the NB points a slight amount
-        map.data.forEach(function(feature)
-        {
-            // get the name of the current feature
-            var name = feature.getId();
-            // lookup the original coordinates for this feature
-            var coords = vds_coords[name];
-
-            //retrieve the perpendicular vector (precomputed)
-            var perpx = feature.getProperty("perpx")
-            var perpy = feature.getProperty("perpy")
-            // 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)
-     {
-        target = marker.id;
-        newColor = marker.properties.color;
-        // see if new color is different than current color
-        currentFeature = map.data.getFeatureById(target);
-        currentColor = currentFeature.getProperty("color");
-        // if a new color is desired then assign it to the feature's color property
-        if (currentColor != 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()
-     {
-        var parsed_JSON;
-        loadJSON(kMapPointsFile,function(response)
-        {
-            // Parse JSON string into object
-            parsed_JSON = JSON.parse(response);
-            // Process each new marker - lookup in current map
-            parsed_JSON.features.forEach(updateMarker);
-        });
-     }
-
-    // Initialize the center button (to re-center the map)
-    function initCenter()
-    {
-        var centerBtnDiv = document.getElementById('ctrButton');
-        map.controls[google.maps.ControlPosition.RIGHT_CENTER].push(centerBtnDiv)
-        centerBtnDiv.title = 'Click to recenter the map';
-
-        // Setup the click event listeners: reset center location and zoom factor
-        centerBtnDiv.addEventListener('click', function() {
-          map.setCenter(centerPoint);
-          map.setZoom(initZoom);
-          clearPlacePins();
-        });
-    }
-
-    // Initialize the search box and listener 
-    function initSearch()
-    {
-        // Create the search box and link it to the UI element.
-        var input = document.getElementById('search-input');
-        var searchBox = new google.maps.places.SearchBox(input);
-        map.controls[google.maps.ControlPosition.TOP_LEFT].push(input);
-
-        // Bias the SearchBox results towards current map's viewport.
-        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);
-            });
-
-            // 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());
-        });
-    }
-
-    // Remove any place pins from a previous search
-    function clearPlacePins()
-    {
-          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() {
-            cctv_showing = !cctv_showing;
-            // reveal or hide all the icons
-            for (var i = 0; i < cctvList.length; i++)
-            {
-                cctvList[i].setVisible(cctv_showing);
-            }
-            // Determine which button image to show
-            if (cctv_showing)
-            {
-                pic = "images/CPTMSImages/btnDepressed_CCTV.png"
-            }
-            else
-            {
-                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() {
-            cms_showing = !cms_showing;
-            // reveal or hide all the icons
-            for (var i = 0; i < cmsList.length; i++)
-            {
-                cmsList[i].setVisible(cms_showing);
-            }
-            // Determine which button image to show
-            if (cms_showing)
-            {
-                pic = "images/CPTMSImages/btnDepressed_CMS.png"
-            }
-            else
-            {
-                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() {
-            vds_showing = !vds_showing;
-            // reveal or hide all the dots
-            map.data.forEach(function(feature)
-            {
-                map.data.overrideStyle(feature, {visible:vds_showing});
-            });
-            // Determine which button image to show
-            if (vds_showing)
-            {
-                pic = "images/CPTMSImages/btnDepressed_VDS.png"
-            }
-            else
-            {
-                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);
-            // load a yellow flag if there's currently no message
-            if (messageList[i] == "||")
-                simpleImage = 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();
-                    }
-                });
-        }
-    });
-}
-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));
-} 
-
-    // Initialize the map and load the points
-    function initMap() 
-    {
-        // Declare the map and where it belongs on the page
-        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
-        // setup the search box and center button
-        initSearch();
-        initCenter();
-        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);
-        // 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() {
-            // fetch how much the map is currently zoomed
-            currentZoom = map.getZoom(); 
-            // only bother adjusting within this range
-            if ((currentZoom <16) && (currentZoom>10))
-            {
-                // magic formula controls distance between dots
-                adjustCoords(calcDistanceFactor());
-            }
-        });
-
-    }
-
-    // Styles array for Night Mode map
-    // Ref: https://developers.google.com/maps/documentation/javascript/styling
-    var night_mode = [
-                {elementType: 'geometry', stylers: [{color: '#242f3e'}]},
-                {elementType: 'labels.text.stroke', stylers: [{color: '#242f3e'}]},
-                {elementType: 'labels.text.fill', stylers: [{color: '#746855'}]},
-                {
-                  featureType: 'administrative.locality',
-                  elementType: 'labels.text.fill',
-                  stylers: [{color: '#d59563'}]
-                },
-                {
-                  featureType: 'poi',
-                  elementType: 'labels.text.fill',
-                  stylers: [{color: '#d59563'}]
-                },
-                {
-                  featureType: 'poi.park',
-                  elementType: 'geometry',
-                  stylers: [{color: '#263c3f'}]
-                },
-                {
-                  featureType: 'poi.park',
-                  elementType: 'labels.text.fill',
-                  stylers: [{color: '#6b9a76'}]
-                },
-                {
-                  featureType: 'road',
-                  elementType: 'geometry',
-                  stylers: [{color: '#38414e'}]
-                },
-                {
-                  featureType: 'road',
-                  elementType: 'geometry.stroke',
-                  stylers: [{color: '#212a37'}]
-                },
-                {
-                  featureType: 'road',
-                  elementType: 'labels.text.fill',
-                  stylers: [{color: '#9ca5b3'}]
-                },
-                {
-                  featureType: 'road.highway',
-                  elementType: 'geometry',
-                  stylers: [{color: '#746855'}]
-                },
-                {
-                  featureType: 'road.highway',
-                  elementType: 'geometry.stroke',
-                  stylers: [{color: '#1f2835'}]
-                },
-                {
-                  featureType: 'road.highway',
-                  elementType: 'labels.text.fill',
-                  stylers: [{color: '#f3d19c'}]
-                },
-                {
-                  featureType: 'transit',
-                  elementType: 'geometry',
-                  stylers: [{color: '#2f3948'}]
-                },
-                {
-                  featureType: 'transit.station',
-                  elementType: 'labels.text.fill',
-                  stylers: [{color: '#d59563'}]
-                },
-                {
-                  featureType: 'water',
-                  elementType: 'geometry',
-                  stylers: [{color: '#17263c'}]
-                },
-                {
-                  featureType: 'water',
-                  elementType: 'labels.text.fill',
-                  stylers: [{color: '#515c6d'}]
-                },
-                {
-                  featureType: 'water',
-                  elementType: 'labels.text.stroke',
-                  stylers: [{color: '#17263c'}]
-                }
-              ]
-
-    // Using John's API Key
-    </script>
-    <script async defer
-    src="https://maps.googleapis.com/maps/api/js?key=AIzaSyD6iTyN0DjP-9OVkAgicyp4tkC10naE_B8&libraries=places&callback=initMap">
-    </script>
-  </body>
-</html>
Index: trunk/src/cptms/messagefile.txt
===================================================================
--- trunk/src/cptms/messagefile.txt	(revision 305)
+++ 	(revision )
@@ -1,1 +1,0 @@
-["zero||","||","||","||","||","||","||","||","||","||","slow for|work zone |","||","||","||","||","||","||","||","||","||","||","||","||","||","||","||","||","||","||","||","     every|    dog has|    its day","    slow|  traffic|   ahead","||","   slow down|   debris on|    roadway","    caution|  slow traffic|     ahead","  use caution|uneven pavement|","||","free tomatoes!|U-Pick from |roadway","||","||","||","||","||","||","||","||","||","bottom of 5th|Dodgers up by 2|","||","||","watch out for|falling rocks|","||","||","||","||","||","||","||","||","||","||","||","||","||","||","||","||","||","||","||","||","||","||","||","||","||"]
