Warning: Can't use blame annotator:
svn blame failed on trunk/src/cptms/cptms_map.html: ("Can't find a temporary directory: Internal error", 20014)

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

Revision 303, 27.2 KB checked in by jdalbey, 7 years ago (diff)

cptms_map.html integrated cms features and get view/hide buttons operating.

RevLine 
1<!DOCTYPE html>
2<html>
3  <head>
4<!-- Launch with  python -m CGIHTTPServer 8080  -->
5  <meta http-equiv="Content-Type" content="text/html;charset=utf-8">
6    <title>CPTMS Map v0.5.6</title> 
7    <style>
8        @font-face {
9          font-family: Scoreboard;
10          src: url('scoreboard.ttf');
11        }
12      /* Set the size of the div element that contains the map */
13      #mapdiv {
14        height: 100%;
15        width: 100%; 
16       }
17        /* Makes the page fill the window. */
18      html, body {
19        height: 100%;
20        margin: 0;
21        padding: 0;
22      }
23        textarea {
24           height: 33px;
25           width: 272px;
26           resize: none;
27           font-family: Scoreboard;
28           font-size: xx-large;
29           background-color: #2F4F4F;
30           color: yellow;
31        }
32       input {
33           border: thin solid #333;
34           padding: 2px;
35           font-family: monospace;
36           font-size: large;
37        }
38      #search-input {
39        background-color: #17263c;  /* #CD853F;  /*#E6E6FA; /* lavender */
40        color: #E6E6FA;  /* #FFEFD5; */
41        font-family: Roboto;
42        font-size: 18px;
43        font-weight: 400;
44        margin-left: 12px;
45        padding: 0 11px 0 13px;
46        text-overflow: ellipsis;
47        border-color: #746855;   /* #4d90fe; */
48        width: 400px;
49      }
50
51      #search-input:focus {
52        border-color: #E6E6FA; 
53      }
54
55      #ctrButton {
56        font-size: 40px;
57        margin-right: 9px;
58        background-color: #fff;
59        color: #47476b;
60        cursor: pointer;
61      }
62      #cms-info-label {
63           height: 20px;
64           width: 540px;
65           overflow: hidden;
66           background-color: #A8C5FF;  /*#ECECFB; */
67           border: thin solid #BDBDBD;
68           padding: 5px;
69       }
70      #message-display {
71           height: 172px;
72           width: 300px;
73           overflow: hidden;
74           float: left;
75       }
76      #message-input {
77           height: 122px;
78           width: 135px;
79           background-color: #729FFF;
80           float: left;
81       }
82       #buttonPanel {
83           height: 122px;
84           width: 100px;
85           background-color: #729FFF;
86           border-left: none;
87           float: left;
88           padding: 20px;
89        }
90       .wrapper {
91           position: relative;\
92        }
93       #dialog {
94          position: absolute;
95          top: 10%;
96          right: 20%;
97          background-color: #729FFF; /* #ECECFB; */
98          margin: auto;
99          padding: 20px;
100          border: 1px solid #888;
101          width: 52%;
102          display: none;           
103        }
104      .unstyled-button {
105        border: 0 none;
106        padding: 0;
107        background: none;
108        cursor: pointer;
109      }   
110    /* The Close Button */
111    .close {
112      color: orange;
113      float: right;
114      font-size: 20px;
115      font-weight: bold;
116    }
117    .close:hover,
118    .close:focus {
119      color: red;
120      text-decoration: none;
121      cursor: pointer;
122    }
123
124    </style>
125  </head>
126  <body>
127    <!--
128         Version 5.6 integrates CMS features
129         Version 5.5 renames title to CPTMS, loads static data on startup and dynamic data
130         every ten seconds. 
131         Version 5.4 adds Search box and Center button
132         Version 5.3 fixed dot color update defect, increased refresh rate to 10 sec.
133         Version 5.2 places red dots overlapping yellow dots.
134         Version 5.1 removes the map and street view buttons and the H3 tag.
135         Version 5 uses precomputed perpendicular vector in dot adjustment function
136         Version 4 Adjust the spacing between dots when the map is zoomed.
137         Version 3 does loadGeoJson only once, and subsequently does an ajax load
138         of the highways file, and selectively updates only those placePins whose
139         color has changed.
140         @author jdalbey  2019.2.17
141    -->
142    <!-- The text area input for the Search Box -->
143    <input id="search-input" class="controls" type="text" placeholder="Search Box">
144    <!--The div element where the map appears -->
145    <div id="mapdiv"></div>
146    <!--The div element where the buttons appears -->
147    <div id="ctrButton" class="unstyled-button">&#x2295;</div>
148    <button id="cmsButton" class="unstyled-button"><img id="cmsBtnImg" src="images/CPTMSImages/btnDepressed_CMS.png"></button>
149    <button id="vdsButton" class="unstyled-button"><img id="vdsBtnImg" src="images/CPTMSImages/btnDepressed_VDS.png"></button>
150    <!-- The div element for the popup dialog -->
151    <div id="dialog" style="display:none;">
152        <span class="close">&times;</span>
153        <br>
154        <div id="cms-info-label" style="font-family:monospace">CMS ID: xxx LOCATION: </div>
155        <br>
156        <div><pre>Proposed:                              Current:</pre></div>
157        <input id='cmsID' value="" type='hidden'/>
158        <div id="message-input">
159        <input id="msgcontent1" size="16" maxlength="16" type="text"/><br><br>
160        <input id="msgcontent2" size="16" maxlength="16"/><br><br>
161        <input id="msgcontent3" size="16" maxlength="16"/>
162        </div>       
163        <div id="buttonPanel"    style="display: block;">
164        <button onclick="handleSubmit();">Send >></button><br>
165        <button onclick="handleClear();">Clear >></button><br>
166        <button onclick="handleClose();">Close </button>
167        </div>
168        <div id="message-display"  style="display: block;">
169         <textarea readonly id="msgdisplay1" maxlength="16" rows="1" cols="16"></textarea>
170         <textarea readonly id="msgdisplay2" maxlength="16" rows="1" cols="16"></textarea>
171         <textarea readonly id="msgdisplay3" maxlength="16" rows="1" cols="16"></textarea>
172        </div>
173    </div>
174
175    <script>
176    // a global variable for the google map
177    var map; 
178    // a global dictionary to lookup a station's original coordinates
179    var vds_coords = {};
180    // a global variable to hold locations of marked search places
181    var placePins = [];
182    // Constant name of dynamic json data file created by CADserver
183    var kMapPointsFile = "highways.json";
184    // Constant name of initial (static) highways file used once at startup
185    var kMapStartupFile = "highways_startup.json";
186    // Constant for map center location: The John Wayne Airport
187    //var centerPoint = {lat: 33.687228, lng: -117.872148};
188    // Constant for map center location in District 12
189    var centerPoint = {lat: 33.693385, lng: -117.798937};
190    // Initial map zoom
191    var initZoom = 11;
192    // Dot colors used in traffic model to indicate free-flowing, slowed, and stopped traffic
193    // and their associated zvalues so slower traffic dots are more visible.
194    // white means a disabled spot
195    var colorZvalues = {"white":5,"lime":10,"yellow":20,"red":30};
196var kCMSstartupFile = "cmsStatusD12.json";
197var blueFlag = "images/CPTMSImages/icon_cmsBlue.png";
198var yellowFlag = "images/CPTMSImages/icon_cmsYellow.png";
199var messageList;
200var cms_info;
201var markerList = [];
202var cms_showing = true;
203var vds_showing = true;
204
205    // Build a solid colored icon to use instead of the classic pin
206    // Use a diamond on N and E directions, circle on S and W directions
207    function dotSymbol(color,postmileID) //,direction)
208    {
209        var circle = google.maps.SymbolPath.CIRCLE;
210        var diamond = 'M -1,0 0,-1 1,0 0,1 z';
211        var myShape = circle;
212        // See if postmile name contains N or W letters
213        //if ((postmileID.indexOf('N') != -1) || (postmileID.indexOf('W') != -1))
214        //{
215        //   myShape = diamond
216        //}
217        return {
218            path: myShape,
219            scale: 5,
220            strokeColor: "black", // the border color
221            strokeWeight: 1,      // the border thickness
222            fillColor: color,
223            fillOpacity: 1.0
224        };
225    }
226
227    // Load the map data from a json file and style all the points
228    function loadMapData()
229    {
230        // Load the static map data and call saveCoords when done
231        map.data.loadGeoJson(kMapStartupFile,null,saveCoords)
232
233        // Style the map data by applying the desired properties to each feature (marker)
234        // The function will be called every time a feature's properties are updated.
235        map.data.setStyle(function(feature) 
236        {
237            // Get the postmile id
238            var name = feature.getId();
239            // Get the desired color value
240            var ptColor = feature.getProperty("color");
241            var street = feature.getProperty("street");
242            // Build the marker
243            var iconSymbol = dotSymbol(ptColor,name);
244            // return the StyleOptions
245            return {
246                    icon: iconSymbol,
247                    title: name + " @" + street,  // set rollover text
248                    // set zIndex for slowed traffic to a higher value so they overlap
249                    zIndex: colorZvalues[ptColor]
250                   };
251        });
252    }
253    // callback when load GeoJson completes
254    // save each feature's Point as the original coordinates for later reference
255    function saveCoords(features)
256    {
257        // Iterate over all the features in the map
258        features.forEach(function(feature)
259        {
260            var pt = feature.getGeometry().get(); 
261            vds_coords[feature.getId()] = pt;  // save the Point in a dictionary
262        });
263        // update the dot colors from the dynamic json data
264        updateMap();
265        // go adjust the marker coordinates so dots don't overlap
266        adjustCoords(calcDistanceFactor());
267    }
268
269    // magic formula controls distance between dots proportionate to zoom factor
270    function calcDistanceFactor()
271    {
272        // 15 is maximum zoom, the point at which no adjustment is needed
273        return (.0005*(15-map.getZoom())); 
274    }
275
276    // Adjust the coordinates of dots so they appear side-by-side
277    // The perpendicular vector for each dot has been provided,
278    // so we just need to multiply by a scaling factor (adjAmount)
279    // @param adjAmount amount by which to adjust coordinate
280    function adjustCoords(adjAmount)
281    {
282        // Adjust the NB points a slight amount
283        map.data.forEach(function(feature)
284        {
285            // get the name of the current feature
286            var name = feature.getId();
287            // lookup the original coordinates for this feature
288            var coords = vds_coords[name];
289
290            //retrieve the perpendicular vector (precomputed)
291            var perpx = feature.getProperty("perpx")
292            var perpy = feature.getProperty("perpy")
293            // Make adjustment and save it
294            var myLat = coords.lat() + perpy * adjAmount
295            var myLong = coords.lng() + perpx * adjAmount
296            feature.setGeometry({lat:myLat, lng:myLong});
297        });
298    }
299
300     // update the color (as needed) for a given marker
301     function updateMarker(marker)
302     {
303        target = marker.id;
304        newColor = marker.properties.color;
305        // see if new color is different than current color
306        currentFeature = map.data.getFeatureById(target);
307        currentColor = currentFeature.getProperty("color");
308        // if a new color is desired then assign it to the feature's color property
309        if (currentColor != newColor)
310        {
311            currentFeature.setProperty("color",newColor);
312            // set zIndex for slowed traffic to a higher value so they overlap
313            currentFeature.setProperty("zIndex", colorZvalues[newColor]);
314        }
315     }
316
317    // Load the dynamic json file for highways, etc.
318    // Ref: https://codepen.io/KryptoniteDove/post/load-json-file-locally-using-pure-javascript
319function loadJSON(inFile, callback)
320{
321    var xobj = new XMLHttpRequest();
322    xobj.overrideMimeType("application/json");
323    xobj.open('GET', inFile, true);
324    xobj.onreadystatechange = function()
325    {
326        if (xobj.readyState == 4 && xobj.status == "200")
327        {
328            callback(xobj.responseText);
329        }
330    };
331    // We want ajax to ignore any cached responses
332    xobj.setRequestHeader('If-Modified-Since', 'Sat, 01 Jan 2000 01:01:01 GMT')
333    xobj.send(null);
334}
335     // Load the highways dynamic json file and update the map
336     function updateMap()
337     {
338        var parsed_JSON;
339        loadJSON(kMapPointsFile,function(response)
340        {
341            // Parse JSON string into object
342            parsed_JSON = JSON.parse(response);
343            // Process each new marker - lookup in current map
344            parsed_JSON.features.forEach(updateMarker);
345        });
346     }
347
348    // Initialize the center button (to re-center the map)
349    function initCenter()
350    {
351        var centerBtnDiv = document.getElementById('ctrButton');
352        map.controls[google.maps.ControlPosition.RIGHT_CENTER].push(centerBtnDiv)
353        centerBtnDiv.title = 'Click to recenter the map';
354
355        // Setup the click event listeners: reset center location and zoom factor
356        centerBtnDiv.addEventListener('click', function() {
357          map.setCenter(centerPoint);
358          map.setZoom(initZoom);
359          clearPlacePins();
360        });
361    }
362
363    // Initialize the search box and listener
364    function initSearch()
365    {
366        // Create the search box and link it to the UI element.
367        var input = document.getElementById('search-input');
368        var searchBox = new google.maps.places.SearchBox(input);
369        map.controls[google.maps.ControlPosition.TOP_LEFT].push(input);
370
371        // Bias the SearchBox results towards current map's viewport.
372        map.addListener('bounds_changed', function() {
373          searchBox.setBounds(map.getBounds());
374        });
375
376        // Listen for the event fired when the user selects a prediction and retrieve
377        // more details for that place.
378        searchBox.addListener('places_changed', function() {
379          var places = searchBox.getPlaces();
380
381          if (places.length == 0) {
382            return;
383          }
384
385          clearPlacePins();
386
387          // Create a bounding region to include the search result places
388          var bounds = new google.maps.LatLngBounds();
389          // For each place, get the icon, name and location.
390          // There may be multiple search results
391          places.forEach(function(place) {
392            if (!place.geometry) {
393              console.log("Returned place contains no geometry");
394              return;
395            }
396
397            // Create a marker for each place.
398            placeMarker = new google.maps.Marker({
399              map: map,
400              title: place.name,
401              position: place.geometry.location
402            })
403
404            // Click on the marker to remove it from the display
405            placeMarker.addListener('click', function() {
406              placeMarker.setMap(null);
407            });
408
409            // Add this marker to the collection of current markers
410            placePins.push(placeMarker);
411
412            // Create a bounding region to include this place
413            if (place.geometry.viewport) {
414              // Only geocodes have viewport.
415              bounds.union(place.geometry.viewport);
416            } else {
417              bounds.extend(place.geometry.location);
418            }
419          });
420          // This will pan and zoom to the area around the marker
421          //map.fitBounds(bounds);
422          // This will center the map on the new marker(s) but not zoom
423          map.setCenter(bounds.getCenter());
424        });
425    }
426
427    // Remove any place pins from a previous search
428    function clearPlacePins()
429    {
430          placePins.forEach(function(marker) {
431            marker.setMap(null);
432          });
433          placePins = [];
434    }
435// Initialize the view/hide buttons
436function initButton()
437{
438    var cmsBtnDiv = document.getElementById('cmsButton');
439    map.controls[google.maps.ControlPosition.LEFT_BOTTOM].push(cmsBtnDiv)
440    cmsBtnDiv.title = 'Click to toggle cms view';
441
442    // Setup the click event listeners to toggle icon display
443    cmsBtnDiv.addEventListener('click', function() {
444            cms_showing = !cms_showing;
445            // reveal or hide all the icons
446            for (var i = 0; i < markerList.length; i++)
447            {
448                markerList[i].setVisible(cms_showing);
449            }
450            // Determine which button image to show
451            if (cms_showing)
452            {
453                pic = "images/CPTMSImages/btnDepressed_CMS.png"
454            }
455            else
456            {
457                pic = "images/CPTMSImages/btnReady_CMS.png"
458            }
459            document.getElementById('cmsBtnImg').src=pic;
460    });
461    var vdsBtnDiv = document.getElementById('vdsButton');
462    map.controls[google.maps.ControlPosition.LEFT_BOTTOM].push(vdsBtnDiv)
463    vdsBtnDiv.title = 'Click to toggle vds view';
464
465    // Setup the click event listeners to toggle icon display
466    vdsBtnDiv.addEventListener('click', function() {
467            vds_showing = !vds_showing;
468            // reveal or hide all the dots
469            map.data.forEach(function(feature)
470            {
471                map.data.overrideStyle(feature, {visible:vds_showing});
472            });
473            // Determine which button image to show
474            if (vds_showing)
475            {
476                pic = "images/CPTMSImages/btnDepressed_VDS.png"
477            }
478            else
479            {
480                pic = "images/CPTMSImages/btnReady_VDS.png"
481            }
482            document.getElementById('vdsBtnImg').src=pic;
483    });
484}
485function setMarkers()
486{
487    var simpleImage = "";
488    loadcmsJSON(function(response)
489    {
490        // Parse JSON string into object
491        cms_info = JSON.parse(response);
492        console.log(cms_info.data[0].cms);
493        // Process each new marker
494        for (var i = 0; i < cms_info.data.length; i++)
495        {
496            var cms = cms_info.data[i].cms;
497            var currLat = Number(cms.location.latitude);
498            var currLong = Number(cms.location.longitude);
499            // load a yellow flag if there's currently no message
500            if (messageList[i] == "||")
501                simpleImage = yellowFlag;
502            else
503                simpleImage = blueFlag;
504            var directionCode = cms.location.direction.charAt(0);
505            var locationInfo = directionCode + " " + cms.location
506                .route + " " + cms.location.postmile + " " + cms
507                .location.locationName
508            markerList[i] = new google.maps.Marker(
509            {
510                position:
511                {
512                    lat: currLat,
513                    lng: currLong
514                },
515                map: map,
516                icon: simpleImage,
517                title: "#"+i+" " +locationInfo,
518                cmsid: "" + i,
519                location: locationInfo
520            });
521            google.maps.event.addListener(markerList[i], 'click',
522                function()
523                {
524                    var dialog = document.getElementById('dialog');
525                    dialog.style.display = 'block';
526                    // fetch the sequential msg #
527                    cmsID = Number(this.cmsid);
528                    // Assign to the hidden field
529                    document.getElementById('cmsID').value = cmsID;
530                    getMessage(cmsID); // note: this is async
531                    document.getElementById('cms-info-label').innerHTML = "CMS ID: " +
532                        cmsID + "&nbsp;&nbsp;&nbsp;LOCATION: " + this.location;
533                    // clear input fields
534                    document.getElementById('msgcontent1').value = "";
535                    document.getElementById('msgcontent2').value = "";
536                    document.getElementById('msgcontent3').value = "";
537                    document.getElementById('msgcontent1').focus();
538                    var span = document.getElementsByClassName("close")[0]
539                    // When the user clicks on <span> (x), close the modal
540                    span.onclick = function() {
541                      handleClose();
542                    }
543                });
544        }
545    });
546}
547
548function loadcmsJSON(callback)
549{
550    var xobj = new XMLHttpRequest();
551    xobj.overrideMimeType("application/json");
552    xobj.open('GET', kCMSstartupFile, true);
553    xobj.onreadystatechange = function()
554    {
555        if (xobj.readyState == 4 && xobj.status == "200")
556        {
557            callback(xobj.responseText);
558        }
559    };
560    // We want ajax to ignore any cached responses
561    xobj.setRequestHeader('If-Modified-Since', 'Sat, 01 Jan 2000 01:01:01 GMT')
562    xobj.send(null);
563}
564
565function handleSubmit()
566{
567    // recover the user's response
568    var response1 = document.getElementById('msgcontent1').value;
569    var response2 = document.getElementById('msgcontent2').value;
570    var response3 = document.getElementById('msgcontent3').value;
571    var newMsg = response1+response2+response3;
572    if (newMsg.length == 0)
573    {
574        alert("Nothing to Send ... Proposed is empty.");
575    }
576    else
577    {
578        document.getElementById('msgdisplay1').value = response1;
579        document.getElementById('msgdisplay2').value = response2;
580        document.getElementById('msgdisplay3').value = response3;
581        saveMessage(response1 + "|" + response2 + "|" + response3);
582    }
583}
584
585function handleClose()
586{
587    // hide the display
588    document.getElementById('dialog').style.display = 'none'
589}
590
591function handleClear()
592{
593    document.getElementById('msgdisplay1').value = "";
594    document.getElementById('msgdisplay2').value = "";
595    document.getElementById('msgdisplay3').value = "";
596    saveMessage("||");
597}
598// retrieve the current cms message file
599function getMessage(cmsID)
600{
601    loadJSON("http://localhost:8080/messagefile.txt", function(response)
602    {
603        // Parse JSON string into object
604        messageList = JSON.parse(response);
605        // select a message from json for the given cmsID
606        console.log("get by cmsID=" + cmsID);
607        var cmsSign = document.getElementById('msgdisplay1');
608        messageparts = messageList[cmsID].split("|");
609        cmsSign.value = messageparts[0];
610        document.getElementById('msgdisplay2').value = messageparts[1];
611        document.getElementById('msgdisplay3').value = messageparts[2];
612    });
613}
614// Save an updated cms message to the file
615// NB: cms id's are one-based, json array is zero-based.
616function saveMessage(outMessage, cmsID)
617{
618    var cmsID = document.getElementById('cmsID').value;
619    console.log("Saving " + outMessage + " for cmsID " + cmsID)
620    messageList[cmsID] = outMessage;
621    // Change icon if something was saved
622    if (outMessage == "||")
623        markerList[cmsID].setIcon(yellowFlag);
624    else
625        markerList[cmsID].setIcon(blueFlag);
626
627    var xhttp = new XMLHttpRequest();
628    xhttp.open("GET", "http://localhost:8080/cgi-bin/saveMessage.py?msg=" + JSON
629        .stringify(messageList), true);
630    xhttp.send();
631    // Using POST might be a better idea ... haven't tried this yet
632    //      var xhr = new XMLHttpRequest();
633    //      xhr.open("POST", "/cgi-bin/saveMessage.py?", true);
634    //      xhr.setRequestHeader('Content-Type', 'application/json; charset=UTF-8');
635    // send the collected data as JSON
636    //      xhr.send(JSON.stringify(messageList));
637} 
638
639    // Initialize the map and load the points
640    function initMap() 
641    {
642        // Declare the map and where it belongs on the page
643        map = new google.maps.Map( document.getElementById('mapdiv'), 
644        {
645            zoom: initZoom, 
646            center: centerPoint,
647            styles: night_mode,
648            mapTypeControl: false,
649            streetViewControl: false 
650        });
651        loadMapData();  // go load the map data
652        // setup the search box and center button
653        initSearch();
654        initCenter();
655        initButton();
656        getMessage(1); // load the current message file
657        setMarkers();
658
659
660        // Start a timer to refresh the map every 10 seconds
661        var myTimer = setInterval(updateMap, 10000);
662        // Listen for zoom changes and move the placePins so as to keep a nice
663        // visual distance between them appropriate to the zoom factor
664        map.addListener('zoom_changed', function() {
665            // fetch how much the map is currently zoomed
666            currentZoom = map.getZoom(); 
667            // only bother adjusting within this range
668            if ((currentZoom <16) && (currentZoom>10))
669            {
670                // magic formula controls distance between dots
671                adjustCoords(calcDistanceFactor());
672            }
673        });
674
675    }
676
677    // Styles array for Night Mode map
678    // Ref: https://developers.google.com/maps/documentation/javascript/styling
679    var night_mode = [
680                {elementType: 'geometry', stylers: [{color: '#242f3e'}]},
681                {elementType: 'labels.text.stroke', stylers: [{color: '#242f3e'}]},
682                {elementType: 'labels.text.fill', stylers: [{color: '#746855'}]},
683                {
684                  featureType: 'administrative.locality',
685                  elementType: 'labels.text.fill',
686                  stylers: [{color: '#d59563'}]
687                },
688                {
689                  featureType: 'poi',
690                  elementType: 'labels.text.fill',
691                  stylers: [{color: '#d59563'}]
692                },
693                {
694                  featureType: 'poi.park',
695                  elementType: 'geometry',
696                  stylers: [{color: '#263c3f'}]
697                },
698                {
699                  featureType: 'poi.park',
700                  elementType: 'labels.text.fill',
701                  stylers: [{color: '#6b9a76'}]
702                },
703                {
704                  featureType: 'road',
705                  elementType: 'geometry',
706                  stylers: [{color: '#38414e'}]
707                },
708                {
709                  featureType: 'road',
710                  elementType: 'geometry.stroke',
711                  stylers: [{color: '#212a37'}]
712                },
713                {
714                  featureType: 'road',
715                  elementType: 'labels.text.fill',
716                  stylers: [{color: '#9ca5b3'}]
717                },
718                {
719                  featureType: 'road.highway',
720                  elementType: 'geometry',
721                  stylers: [{color: '#746855'}]
722                },
723                {
724                  featureType: 'road.highway',
725                  elementType: 'geometry.stroke',
726                  stylers: [{color: '#1f2835'}]
727                },
728                {
729                  featureType: 'road.highway',
730                  elementType: 'labels.text.fill',
731                  stylers: [{color: '#f3d19c'}]
732                },
733                {
734                  featureType: 'transit',
735                  elementType: 'geometry',
736                  stylers: [{color: '#2f3948'}]
737                },
738                {
739                  featureType: 'transit.station',
740                  elementType: 'labels.text.fill',
741                  stylers: [{color: '#d59563'}]
742                },
743                {
744                  featureType: 'water',
745                  elementType: 'geometry',
746                  stylers: [{color: '#17263c'}]
747                },
748                {
749                  featureType: 'water',
750                  elementType: 'labels.text.fill',
751                  stylers: [{color: '#515c6d'}]
752                },
753                {
754                  featureType: 'water',
755                  elementType: 'labels.text.stroke',
756                  stylers: [{color: '#17263c'}]
757                }
758              ]
759
760    // Using John's API Key
761    </script>
762    <script async defer
763    src="https://maps.googleapis.com/maps/api/js?key=AIzaSyD6iTyN0DjP-9OVkAgicyp4tkC10naE_B8&libraries=places&callback=initMap">
764    </script>
765  </body>
766</html>
Note: See TracBrowser for help on using the repository browser.