source: tmcsimulator/trunk/src/cptms/cptms_map_v55.html @ 286

Revision 286, 16.2 KB checked in by jdalbey, 7 years ago (diff)

cptms_map_v55.html added

Line 
1<!DOCTYPE html>
2<html>
3  <head>
4  <meta http-equiv="Content-Type" content="text/html;charset=utf-8">
5    <title>CPTMS Map v0.5.5</title> 
6    <style>
7      /* Set the size of the div element that contains the map */
8      #mapdiv {
9        height: 100%;
10        width: 100%; 
11       }
12        /* Makes the page fill the window. */
13      html, body {
14        height: 100%;
15        margin: 0;
16        padding: 0;
17      }
18      #search-input {
19        background-color: #17263c;  /* #CD853F;  /*#E6E6FA; /* lavender */
20        color: #E6E6FA;  /* #FFEFD5; */
21        font-family: Roboto;
22        font-size: 18px;
23        font-weight: 400;
24        margin-left: 12px;
25        padding: 0 11px 0 13px;
26        text-overflow: ellipsis;
27        border-color: #746855;   /* #4d90fe; */
28        width: 400px;
29      }
30
31      #search-input:focus {
32        border-color: #E6E6FA; 
33      }
34
35      #ctrButton {
36        font-size: 40px;
37        margin-right: 9px;
38        background-color: #fff;
39        color: #47476b;
40        cursor: pointer;
41      }
42
43    </style>
44  </head>
45  <body>
46    <!-- Version 5.5 renames title to CPTMS, loads static data on startup and dynamic data
47         every ten seconds. 
48         Version 5.4 adds Search box and Center button
49         Version 5.3 fixed dot color update defect, increased refresh rate to 10 sec.
50         Version 5.2 places red dots overlapping yellow dots.
51         Version 5.1 removes the map and street view buttons and the H3 tag.
52         Version 5 uses precomputed perpendicular vector in dot adjustment function
53         Version 4 Adjust the spacing between dots when the map is zoomed.
54         Version 3 does loadGeoJson only once, and subsequently does an ajax load
55         of the highways file, and selectively updates only those placePins whose
56         color has changed.
57         @author jdalbey  2019.2.17
58    -->
59    <!-- The text area input for the Search Box -->
60    <input id="search-input" class="controls" type="text" placeholder="Search Box">
61    <!--The div element where the map appears -->
62    <div id="mapdiv"></div>
63    <!--The div element where the center button appears -->
64    <div id="ctrButton">&#x2295;</div>
65    <script>
66    // a global variable for the google map
67    var map; 
68    // a global dictionary to lookup a station's original coordinates
69    var vds_coords = {};
70    // a global variable to hold locations of marked search places
71    var placePins = [];
72    // Constant name of dynamic json data file created by CADserver
73    var kMapPointsFile = "highways.json";
74    // Constant name of initial (static) highways file used once at startup
75    var kMapStartupFile = "highways_startup.json";
76    // Constant for map center location: The John Wayne Airport
77    //var centerPoint = {lat: 33.687228, lng: -117.872148};
78    // Constant for map center location in District 12
79    var centerPoint = {lat: 33.693385, lng: -117.798937};
80    // Initial map zoom
81    var initZoom = 11;
82    // Dot colors used in traffic model to indicate free-flowing, slowed, and stopped traffic
83    // and their associated zvalues so slower traffic dots are more visible.
84    var colorZvalues = {"lime":10,"yellow":20,"red":30};
85
86    // Build a solid colored icon to use instead of the classic pin
87    // Use a diamond on N and E directions, circle on S and W directions
88    function dotSymbol(color,postmileID) //,direction)
89    {
90        var circle = google.maps.SymbolPath.CIRCLE;
91        var diamond = 'M -1,0 0,-1 1,0 0,1 z';
92        var myShape = circle;
93        // See if postmile name contains N or W letters
94        //if ((postmileID.indexOf('N') != -1) || (postmileID.indexOf('W') != -1))
95        //{
96        //   myShape = diamond
97        //}
98        return {
99            path: myShape,
100            scale: 5,
101            strokeColor: "black", // the border color
102            strokeWeight: 1,      // the border thickness
103            fillColor: color,
104            fillOpacity: 1.0
105        };
106    }
107
108    // Load the map data from a json file and style all the points
109    function loadMapData()
110    {
111        // Load the static map data and call saveCoords when done
112        map.data.loadGeoJson(kMapStartupFile,null,saveCoords)
113
114        // Style the map data by applying the desired properties to each feature (marker)
115        // The function will be called every time a feature's properties are updated.
116        map.data.setStyle(function(feature) 
117        {
118            //console.log(feature.getId() + " " + feature.getGeometry().get());
119            // Get the postmile id
120            var name = feature.getId();
121            // Get the desired color value
122            var ptColor = feature.getProperty("color");
123            var street = feature.getProperty("street");
124            // Build the marker
125            var iconSymbol = dotSymbol(ptColor,name);
126            // return the StyleOptions
127            return {
128                    icon: iconSymbol,
129                    title: name + " @" + street,  // set rollover text
130                    // set zIndex for slowed traffic to a higher value so they overlap
131                    zIndex: colorZvalues[ptColor]
132                   };
133        });
134    }
135    // callback when load GeoJson completes
136    // save each feature's Point as the original coordinates for later reference
137    function saveCoords(features)
138    {
139        // Iterate over all the features in the map
140        features.forEach(function(feature)
141        {
142            var pt = feature.getGeometry().get(); 
143            vds_coords[feature.getId()] = pt;  // save the Point in a dictionary
144        });
145        // update the dot colors from the dynamic json data
146        updateMap();
147        // go adjust the marker coordinates so dots don't overlap
148        adjustCoords(calcDistanceFactor());
149    }
150
151    // magic formula controls distance between dots proportionate to zoom factor
152    function calcDistanceFactor()
153    {
154        // 15 is maximum zoom, the point at which no adjusment is needed
155        return (.0005*(15-map.getZoom())); 
156    }
157
158    // Adjust the coordinates of dots so they appear side-by-side
159    // The perpendicular vector for each dot has been provided,
160    // so we just need to multiply by a scaling factor (adjAmount)
161    // @param adjAmount amount by which to adjust coordinate
162    function adjustCoords(adjAmount)
163    {
164        //console.log("adjusting coordinates");
165        // Adjust the NB points a slight amount
166        map.data.forEach(function(feature)
167        {
168            // get the name of the current feature
169            var name = feature.getId();
170            // lookup the original coordinates for this feature
171            var coords = vds_coords[name];
172
173            //retrieve the perpendicular vector (precomputed)
174            var perpx = feature.getProperty("perpx")
175            var perpy = feature.getProperty("perpy")
176            // Make adjustment and save it
177            var myLat = coords.lat() + perpy * adjAmount
178            var myLong = coords.lng() + perpx * adjAmount
179            feature.setGeometry({lat:myLat, lng:myLong});
180        });
181    }
182
183     // update the color (as needed) for a given marker
184     function updateMarker(marker)
185     {
186        target = marker.id;
187        newColor = marker.properties.color;
188        // see if new color is different than current color
189        currentFeature = map.data.getFeatureById(target);
190        currentColor = currentFeature.getProperty("color");
191        // if a new color is desired then assign it to the feature's color property
192        if (currentColor != newColor)
193        {
194            currentFeature.setProperty("color",newColor);
195            // set zIndex for slowed traffic to a higher value so they overlap
196            currentFeature.setProperty("zIndex", colorZvalues[newColor]);
197            //console.log(target+" updated to "+newColor);
198        }
199     }
200
201    // Load the dynamic highways file via ajax
202    // Ref: https://codepen.io/KryptoniteDove/post/load-json-file-locally-using-pure-javascript
203     function loadJSON(callback) {   
204
205        var xobj = new XMLHttpRequest();
206            xobj.overrideMimeType("application/json");
207        xobj.open('GET', kMapPointsFile, true); 
208        xobj.onreadystatechange = function () {
209              if (xobj.readyState == 4 && xobj.status == "200") {
210                // Required use of an anonymous callback as .open will NOT return a value but simply returns undefined in asynchronous mode
211                callback(xobj.responseText);
212              }
213        };
214        xobj.send(null); 
215     }
216
217     // Load the highways dynamic json file and update the map
218     function updateMap()
219     {
220        var parsed_JSON;
221        loadJSON(function(response)
222        {
223            // Parse JSON string into object
224            parsed_JSON = JSON.parse(response);
225            // Process each new marker - lookup in current map
226            parsed_JSON.features.forEach(updateMarker);
227        });
228     }
229
230    // Initialize the center button (to re-center the map)
231    function initCenter()
232    {
233        var centerBtnDiv = document.getElementById('ctrButton');
234        map.controls[google.maps.ControlPosition.RIGHT_CENTER].push(centerBtnDiv)
235        centerBtnDiv.title = 'Click to recenter the map';
236
237        // Setup the click event listeners: reset center location and zoom factor
238        centerBtnDiv.addEventListener('click', function() {
239          map.setCenter(centerPoint);
240          map.setZoom(initZoom);
241          clearPlacePins();
242        });
243    }
244
245    // Initialize the search box and listener
246    function initSearch()
247    {
248        // Create the search box and link it to the UI element.
249        var input = document.getElementById('search-input');
250        var searchBox = new google.maps.places.SearchBox(input);
251        map.controls[google.maps.ControlPosition.TOP_LEFT].push(input);
252
253        // Bias the SearchBox results towards current map's viewport.
254        map.addListener('bounds_changed', function() {
255          searchBox.setBounds(map.getBounds());
256        });
257
258        // Listen for the event fired when the user selects a prediction and retrieve
259        // more details for that place.
260        searchBox.addListener('places_changed', function() {
261          var places = searchBox.getPlaces();
262
263          if (places.length == 0) {
264            return;
265          }
266
267          clearPlacePins();
268
269          // Create a bounding region to include the search result places
270          var bounds = new google.maps.LatLngBounds();
271          // For each place, get the icon, name and location.
272          // There may be multiple search results
273          places.forEach(function(place) {
274            if (!place.geometry) {
275              console.log("Returned place contains no geometry");
276              return;
277            }
278
279            // Create a marker for each place.
280            placeMarker = new google.maps.Marker({
281              map: map,
282              title: place.name,
283              position: place.geometry.location
284            })
285
286            // Click on the marker to remove it from the display
287            placeMarker.addListener('click', function() {
288              placeMarker.setMap(null);
289            });
290
291            // Add this marker to the collection of current markers
292            placePins.push(placeMarker);
293
294            // Create a bounding region to include this place
295            if (place.geometry.viewport) {
296              // Only geocodes have viewport.
297              bounds.union(place.geometry.viewport);
298            } else {
299              bounds.extend(place.geometry.location);
300            }
301          });
302          // This will pan and zoom to the area around the marker
303          //map.fitBounds(bounds);
304          // This will center the map on the new marker(s) but not zoom
305          map.setCenter(bounds.getCenter());
306        });
307    }
308
309    // Remove any place pins from a previous search
310    function clearPlacePins()
311    {
312          placePins.forEach(function(marker) {
313            marker.setMap(null);
314          });
315          placePins = [];
316    }
317
318    // Initialize the map and load the points
319    function initMap() 
320    {
321        // Declare the map and where it belongs on the page
322        map = new google.maps.Map( document.getElementById('mapdiv'), 
323        {
324            zoom: initZoom, 
325            center: centerPoint,
326            styles: night_mode,
327            mapTypeControl: false,
328            streetViewControl: false 
329        });
330        // setup the search box and center button
331        initSearch();
332        initCenter();
333
334        loadMapData();  // go load the map data
335
336        // Start a timer to refresh the map every 10 seconds
337        var myTimer = setInterval(updateMap, 10000);
338        // Listen for zoom changes and move the placePins so as to keep a nice
339        // visual distance between them appropriate to the zoom factor
340        map.addListener('zoom_changed', function() {
341            // fetch how much the map is currently zoomed
342            currentZoom = map.getZoom(); 
343            //console.log("Zoom changed to ",currentZoom);
344            // only bother adjusting within this range
345            if ((currentZoom <16) && (currentZoom>10))
346            {
347                // magic formula controls distance between dots
348                adjustCoords(calcDistanceFactor());
349            }
350        });
351
352    }
353
354    // Styles array for Night Mode map
355    // Ref: https://developers.google.com/maps/documentation/javascript/styling
356    var night_mode = [
357                {elementType: 'geometry', stylers: [{color: '#242f3e'}]},
358                {elementType: 'labels.text.stroke', stylers: [{color: '#242f3e'}]},
359                {elementType: 'labels.text.fill', stylers: [{color: '#746855'}]},
360                {
361                  featureType: 'administrative.locality',
362                  elementType: 'labels.text.fill',
363                  stylers: [{color: '#d59563'}]
364                },
365                {
366                  featureType: 'poi',
367                  elementType: 'labels.text.fill',
368                  stylers: [{color: '#d59563'}]
369                },
370                {
371                  featureType: 'poi.park',
372                  elementType: 'geometry',
373                  stylers: [{color: '#263c3f'}]
374                },
375                {
376                  featureType: 'poi.park',
377                  elementType: 'labels.text.fill',
378                  stylers: [{color: '#6b9a76'}]
379                },
380                {
381                  featureType: 'road',
382                  elementType: 'geometry',
383                  stylers: [{color: '#38414e'}]
384                },
385                {
386                  featureType: 'road',
387                  elementType: 'geometry.stroke',
388                  stylers: [{color: '#212a37'}]
389                },
390                {
391                  featureType: 'road',
392                  elementType: 'labels.text.fill',
393                  stylers: [{color: '#9ca5b3'}]
394                },
395                {
396                  featureType: 'road.highway',
397                  elementType: 'geometry',
398                  stylers: [{color: '#746855'}]
399                },
400                {
401                  featureType: 'road.highway',
402                  elementType: 'geometry.stroke',
403                  stylers: [{color: '#1f2835'}]
404                },
405                {
406                  featureType: 'road.highway',
407                  elementType: 'labels.text.fill',
408                  stylers: [{color: '#f3d19c'}]
409                },
410                {
411                  featureType: 'transit',
412                  elementType: 'geometry',
413                  stylers: [{color: '#2f3948'}]
414                },
415                {
416                  featureType: 'transit.station',
417                  elementType: 'labels.text.fill',
418                  stylers: [{color: '#d59563'}]
419                },
420                {
421                  featureType: 'water',
422                  elementType: 'geometry',
423                  stylers: [{color: '#17263c'}]
424                },
425                {
426                  featureType: 'water',
427                  elementType: 'labels.text.fill',
428                  stylers: [{color: '#515c6d'}]
429                },
430                {
431                  featureType: 'water',
432                  elementType: 'labels.text.stroke',
433                  stylers: [{color: '#17263c'}]
434                }
435              ]
436
437    // Using John's API Key
438    </script>
439    <script async defer
440    src="https://maps.googleapis.com/maps/api/js?key=AIzaSyD6iTyN0DjP-9OVkAgicyp4tkC10naE_B8&libraries=places&callback=initMap">
441    </script>
442  </body>
443</html>
Note: See TracBrowser for help on using the repository browser.