source: tmcsimulator/trunk/src/cptms/cptms_map.html @ 293

Revision 293, 16.0 KB checked in by jdalbey, 7 years ago (diff)

Rename cptms map file to remove version number since it is now under SVN. We'll keep the version number in the title string.

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    // white means a disabled spot
85    var colorZvalues = {"white":5,"lime":10,"yellow":20,"red":30};
86
87    // Build a solid colored icon to use instead of the classic pin
88    // Use a diamond on N and E directions, circle on S and W directions
89    function dotSymbol(color,postmileID) //,direction)
90    {
91        var circle = google.maps.SymbolPath.CIRCLE;
92        var diamond = 'M -1,0 0,-1 1,0 0,1 z';
93        var myShape = circle;
94        // See if postmile name contains N or W letters
95        //if ((postmileID.indexOf('N') != -1) || (postmileID.indexOf('W') != -1))
96        //{
97        //   myShape = diamond
98        //}
99        return {
100            path: myShape,
101            scale: 5,
102            strokeColor: "black", // the border color
103            strokeWeight: 1,      // the border thickness
104            fillColor: color,
105            fillOpacity: 1.0
106        };
107    }
108
109    // Load the map data from a json file and style all the points
110    function loadMapData()
111    {
112        // Load the static map data and call saveCoords when done
113        map.data.loadGeoJson(kMapStartupFile,null,saveCoords)
114
115        // Style the map data by applying the desired properties to each feature (marker)
116        // The function will be called every time a feature's properties are updated.
117        map.data.setStyle(function(feature) 
118        {
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 adjustment 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        // Adjust the NB points a slight amount
165        map.data.forEach(function(feature)
166        {
167            // get the name of the current feature
168            var name = feature.getId();
169            // lookup the original coordinates for this feature
170            var coords = vds_coords[name];
171
172            //retrieve the perpendicular vector (precomputed)
173            var perpx = feature.getProperty("perpx")
174            var perpy = feature.getProperty("perpy")
175            // Make adjustment and save it
176            var myLat = coords.lat() + perpy * adjAmount
177            var myLong = coords.lng() + perpx * adjAmount
178            feature.setGeometry({lat:myLat, lng:myLong});
179        });
180    }
181
182     // update the color (as needed) for a given marker
183     function updateMarker(marker)
184     {
185        target = marker.id;
186        newColor = marker.properties.color;
187        // see if new color is different than current color
188        currentFeature = map.data.getFeatureById(target);
189        currentColor = currentFeature.getProperty("color");
190        // if a new color is desired then assign it to the feature's color property
191        if (currentColor != newColor)
192        {
193            currentFeature.setProperty("color",newColor);
194            // set zIndex for slowed traffic to a higher value so they overlap
195            currentFeature.setProperty("zIndex", colorZvalues[newColor]);
196        }
197     }
198
199    // Load the dynamic highways file via ajax
200    // Ref: https://codepen.io/KryptoniteDove/post/load-json-file-locally-using-pure-javascript
201     function loadJSON(callback) {   
202
203        var xobj = new XMLHttpRequest();
204            xobj.overrideMimeType("application/json");
205        xobj.open('GET', kMapPointsFile, true); 
206        xobj.onreadystatechange = function () {
207              if (xobj.readyState == 4 && xobj.status == "200") {
208                // Required use of an anonymous callback as .open will NOT return a value but simply returns undefined in asynchronous mode
209                callback(xobj.responseText);
210              }
211        };
212        xobj.send(null); 
213     }
214
215     // Load the highways dynamic json file and update the map
216     function updateMap()
217     {
218        var parsed_JSON;
219        loadJSON(function(response)
220        {
221            // Parse JSON string into object
222            parsed_JSON = JSON.parse(response);
223            // Process each new marker - lookup in current map
224            parsed_JSON.features.forEach(updateMarker);
225        });
226     }
227
228    // Initialize the center button (to re-center the map)
229    function initCenter()
230    {
231        var centerBtnDiv = document.getElementById('ctrButton');
232        map.controls[google.maps.ControlPosition.RIGHT_CENTER].push(centerBtnDiv)
233        centerBtnDiv.title = 'Click to recenter the map';
234
235        // Setup the click event listeners: reset center location and zoom factor
236        centerBtnDiv.addEventListener('click', function() {
237          map.setCenter(centerPoint);
238          map.setZoom(initZoom);
239          clearPlacePins();
240        });
241    }
242
243    // Initialize the search box and listener
244    function initSearch()
245    {
246        // Create the search box and link it to the UI element.
247        var input = document.getElementById('search-input');
248        var searchBox = new google.maps.places.SearchBox(input);
249        map.controls[google.maps.ControlPosition.TOP_LEFT].push(input);
250
251        // Bias the SearchBox results towards current map's viewport.
252        map.addListener('bounds_changed', function() {
253          searchBox.setBounds(map.getBounds());
254        });
255
256        // Listen for the event fired when the user selects a prediction and retrieve
257        // more details for that place.
258        searchBox.addListener('places_changed', function() {
259          var places = searchBox.getPlaces();
260
261          if (places.length == 0) {
262            return;
263          }
264
265          clearPlacePins();
266
267          // Create a bounding region to include the search result places
268          var bounds = new google.maps.LatLngBounds();
269          // For each place, get the icon, name and location.
270          // There may be multiple search results
271          places.forEach(function(place) {
272            if (!place.geometry) {
273              console.log("Returned place contains no geometry");
274              return;
275            }
276
277            // Create a marker for each place.
278            placeMarker = new google.maps.Marker({
279              map: map,
280              title: place.name,
281              position: place.geometry.location
282            })
283
284            // Click on the marker to remove it from the display
285            placeMarker.addListener('click', function() {
286              placeMarker.setMap(null);
287            });
288
289            // Add this marker to the collection of current markers
290            placePins.push(placeMarker);
291
292            // Create a bounding region to include this place
293            if (place.geometry.viewport) {
294              // Only geocodes have viewport.
295              bounds.union(place.geometry.viewport);
296            } else {
297              bounds.extend(place.geometry.location);
298            }
299          });
300          // This will pan and zoom to the area around the marker
301          //map.fitBounds(bounds);
302          // This will center the map on the new marker(s) but not zoom
303          map.setCenter(bounds.getCenter());
304        });
305    }
306
307    // Remove any place pins from a previous search
308    function clearPlacePins()
309    {
310          placePins.forEach(function(marker) {
311            marker.setMap(null);
312          });
313          placePins = [];
314    }
315
316    // Initialize the map and load the points
317    function initMap() 
318    {
319        // Declare the map and where it belongs on the page
320        map = new google.maps.Map( document.getElementById('mapdiv'), 
321        {
322            zoom: initZoom, 
323            center: centerPoint,
324            styles: night_mode,
325            mapTypeControl: false,
326            streetViewControl: false 
327        });
328        // setup the search box and center button
329        initSearch();
330        initCenter();
331
332        loadMapData();  // go load the map data
333
334        // Start a timer to refresh the map every 10 seconds
335        var myTimer = setInterval(updateMap, 10000);
336        // Listen for zoom changes and move the placePins so as to keep a nice
337        // visual distance between them appropriate to the zoom factor
338        map.addListener('zoom_changed', function() {
339            // fetch how much the map is currently zoomed
340            currentZoom = map.getZoom(); 
341            // only bother adjusting within this range
342            if ((currentZoom <16) && (currentZoom>10))
343            {
344                // magic formula controls distance between dots
345                adjustCoords(calcDistanceFactor());
346            }
347        });
348
349    }
350
351    // Styles array for Night Mode map
352    // Ref: https://developers.google.com/maps/documentation/javascript/styling
353    var night_mode = [
354                {elementType: 'geometry', stylers: [{color: '#242f3e'}]},
355                {elementType: 'labels.text.stroke', stylers: [{color: '#242f3e'}]},
356                {elementType: 'labels.text.fill', stylers: [{color: '#746855'}]},
357                {
358                  featureType: 'administrative.locality',
359                  elementType: 'labels.text.fill',
360                  stylers: [{color: '#d59563'}]
361                },
362                {
363                  featureType: 'poi',
364                  elementType: 'labels.text.fill',
365                  stylers: [{color: '#d59563'}]
366                },
367                {
368                  featureType: 'poi.park',
369                  elementType: 'geometry',
370                  stylers: [{color: '#263c3f'}]
371                },
372                {
373                  featureType: 'poi.park',
374                  elementType: 'labels.text.fill',
375                  stylers: [{color: '#6b9a76'}]
376                },
377                {
378                  featureType: 'road',
379                  elementType: 'geometry',
380                  stylers: [{color: '#38414e'}]
381                },
382                {
383                  featureType: 'road',
384                  elementType: 'geometry.stroke',
385                  stylers: [{color: '#212a37'}]
386                },
387                {
388                  featureType: 'road',
389                  elementType: 'labels.text.fill',
390                  stylers: [{color: '#9ca5b3'}]
391                },
392                {
393                  featureType: 'road.highway',
394                  elementType: 'geometry',
395                  stylers: [{color: '#746855'}]
396                },
397                {
398                  featureType: 'road.highway',
399                  elementType: 'geometry.stroke',
400                  stylers: [{color: '#1f2835'}]
401                },
402                {
403                  featureType: 'road.highway',
404                  elementType: 'labels.text.fill',
405                  stylers: [{color: '#f3d19c'}]
406                },
407                {
408                  featureType: 'transit',
409                  elementType: 'geometry',
410                  stylers: [{color: '#2f3948'}]
411                },
412                {
413                  featureType: 'transit.station',
414                  elementType: 'labels.text.fill',
415                  stylers: [{color: '#d59563'}]
416                },
417                {
418                  featureType: 'water',
419                  elementType: 'geometry',
420                  stylers: [{color: '#17263c'}]
421                },
422                {
423                  featureType: 'water',
424                  elementType: 'labels.text.fill',
425                  stylers: [{color: '#515c6d'}]
426                },
427                {
428                  featureType: 'water',
429                  elementType: 'labels.text.stroke',
430                  stylers: [{color: '#17263c'}]
431                }
432              ]
433
434    // Using John's API Key
435    </script>
436    <script async defer
437    src="https://maps.googleapis.com/maps/api/js?key=AIzaSyD6iTyN0DjP-9OVkAgicyp4tkC10naE_B8&libraries=places&callback=initMap">
438    </script>
439  </body>
440</html>
Note: See TracBrowser for help on using the repository browser.