Index: /trunk/stms_map_v52.html
===================================================================
--- /trunk/stms_map_v52.html	(revision 275)
+++ /trunk/stms_map_v52.html	(revision 275)
@@ -0,0 +1,302 @@
+<!DOCTYPE html>
+<html>
+  <head>
+  <meta http-equiv="Content-Type" content="text/html;charset=utf-8">
+    <title>STMS Map v0.52</title> 
+    <style>
+      /* 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;
+      }
+    </style>
+  </head>
+  <body>
+    <!--The div element where the map appears -->
+    <div id="mapdiv"></div>
+    <!-- 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 markers whose
+         color has changed. 
+         @author jdalbey  2019.2.17
+    -->
+    <script>
+    // a global variable for the google map
+    var map;  
+    // Constant name of json data file
+    var kMapPointsFile = "highways.json";
+    // Constant for map center location: The John Wayne Airport
+    var centerPoint = {lat: 33.687228, lng: -117.872148};
+    // Initial map zoom
+    var initZoom = 12;
+    // 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.
+    var colorZvalues = {"lime":10,"yellow":20,"red":30};
+
+    // 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 map data
+        map.data.loadGeoJson(kMapPointsFile);
+        // Style the map data by applying the desired properties to each feature (marker)
+        map.data.setStyle(function(feature) 
+        {
+            // Get the desired color value
+            var ptColor = feature.getProperty("color");
+            // Get the postmile id 
+            var name = feature.getId();
+            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]
+                   };
+        });
+    }
+
+    // magic formula controls distance between dots proportionate to zoom factor
+    function calcDistanceFactor()
+    {
+        // 15 is maximum zoom, the point at which no adjusment 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)
+    {
+        //console.log("adjusting coordinates");
+        // 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 name in the list of markers
+            var marker = parsed_JSON.features.find(function(element)
+                {
+                    return element.id == name;
+                });
+            // extract the original coordinates
+            origLat = marker.geometry.coordinates[1];
+            origLong = marker.geometry.coordinates[0];
+
+            //retrieve the perpendicular vector (precomputed)
+            var perpx = feature.getProperty("perpx")
+            var perpy = feature.getProperty("perpy")
+            // Make adjustment and save it
+            var myLat = origLat + perpy * adjAmount
+            var myLong = origLong + 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");
+        //console.log("current color:",currentColor," newColor: ",newColor);
+        // 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[ptColor]);
+        }
+     }
+
+    // Load the highways file via ajax
+    // Ref: https://codepen.io/KryptoniteDove/post/load-json-file-locally-using-pure-javascript
+     function loadJSON(callback) {   
+
+        var xobj = new XMLHttpRequest();
+            xobj.overrideMimeType("application/json");
+        xobj.open('GET', kMapPointsFile, true); 
+        xobj.onreadystatechange = function () {
+              if (xobj.readyState == 4 && xobj.status == "200") {
+                // Required use of an anonymous callback as .open will NOT return a value but simply returns undefined in asynchronous mode
+                callback(xobj.responseText);
+              }
+        };
+        xobj.send(null);  
+     }
+
+     var parsed_JSON;
+     // Load the highways json file and update the map
+     function updateMap()
+     {
+        loadJSON(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 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
+        // After a brief pause to let map finish loading ...
+        quiktimer = setTimeout("updateMap()",3000);
+        // go adjust the marker coordinates so dots don't overlap
+        quiktimer = setTimeout("adjustCoords(calcDistanceFactor())",3500);  
+        // Start a time to refresh the map every 30 seconds
+        var myTimer = setInterval(updateMap, 30000);
+        // Listen for zoom changes and move the markers 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(); 
+            //console.log("Zoom changed to ",currentZoom);
+            // only bother adjusting within this range
+            if ((currentZoom <16) && (currentZoom>10))
+            {
+                // magic formula controls distance between dots
+                factor = (.0005*(15-currentZoom));  
+                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&callback=initMap">
+    </script>
+  </body>
+</html>
