Category: Google Maps

Google Maps Marker Toggle Code Example

markertoggle
As a followup to my strangely popular KML Toggle example on Google Maps, someone has prompted me to do a Marker Toggle code example.  I’m pretty late to the game to notice that Google updated some of the Maps library syntax, you can now for example pass an object literal for latitude longitude.  As a warning, the code is a bit clumped together to briefly give an example into working with markers.  I wouldn’t recommend master functions that do everything under the sun which is why Angular is a great library to separate your concerns and dependencies.

This example organizes code as follows:

  • Marker information is formatted as a json object, the standard format used today for data sharing.
  • A single global map variable.
  • The Google Map initializeMap() procedural function
    • In addition this function creates markers and your DOM controls.
  • The createMarkers() function that instantiates markers with infowindows and stores it back in the json.
  • createControls() dynamically generates the controls based on information in the json. This would be akin to using Angular’s ng-repeat.
  • toggleControl() is a state changing function that sets your checkbox state, class style, and marker visibility. It uses inverted logic, if the checkbox is checked, then do the opposite.

Click here to the Google Maps Marker Toggle code example.

// Our data source object in json format 
var markerJson = {
    "coffee1": {
        "name": "Urban Bean Coffee",
        "coordinates": {
            "lat": 44.958813,
            "lng": -93.287918
        }
    },
    "coffee2": {
        "name": "Spyhouse Coffee",
        "coordinates": {
            "lat": 44.998846,
            "lng": -93.246241
        }
    },
    "coffee3": {
        "name": "Blue Moon",
        "coordinates": {
            "lat": 44.948480,
            "lng": -93.216707
        }
    }
};

// Set a global variable for map
var map;

// Setup a listener to load the map via Google
google.maps.event.addDomListener(window, 'load', initializeMap);

/* Google Maps Related Functions */

// Initialize our goo
function initializeMap() {
    var options = {
        center: {
            lat: 44.9812,
            lng: -93.2687
        },
        zoom: 13,
        mapTypeId: google.maps.MapTypeId.ROADMAP
    }
    map = new google.maps.Map(document.getElementById("map_canvas"), options);

    // Create markers into DOM
    createMarkers(markerJson);

    // Create controls dynamically after parsing json
    createControls(markerJson);
};

// Instantiate markers in the background and pass it back to the json object
function createMarkers(markerJson) {
    for (var id in markerJson) {
        var shop = markerJson[id];
        var marker = new google.maps.Marker({
            map: map,
            position: shop.coordinates,
            title: shop.name,
            animation: google.maps.Animation.DROP
        });

        // This attaches unique infowindows to each marker
        // You could otherwise do a global infowindow var and have it overwrite itself
        marker.infowindow = new google.maps.InfoWindow({
            content: "This coffeeshop is called " + shop.name
        });

        marker.addListener('click', function() {
            this.infowindow.open(map, this);
        });

        shop.marker = marker;

    }
};

// In this example create the controls dynamically with all checked, obj is each "coffee" listing
function createControls(markerJson) {
    var html = "";
    for (var id in markerJson) {
        var shop = markerJson[id];
        html += '<li><a class="selected" href="#" id="' + id + '" onclick="toggleControl(this); return false"><input onclick="inputClick(this)" type="checkbox" checked id="' + id + '" />' + shop.name + '</a></li>';
    }
    document.getElementById("controls").innerHTML = html;
};

// Toggle class, checkbox state, and marker visibility
function toggleControl(control) {
    var checkbox = control.getElementsByTagName("input")[0];
    var shop = markerJson[control.id];
    if (checkbox.checked == true) { 
        checkbox.checked = false;
        control.className = "normal";
        shop.marker.setVisible(false); // If you have hundreds of markers use setMap(map)
    } else { 
        checkbox.checked = true;
        control.className = "selected";
        shop.marker.setVisible(true); // Similarly use setMap(null)
    }
}; 

// Cleanup function, resets controls, hides all markers, does not destroy
function removeAll() {
    for (var id in markerJson) {
        var shop = markerJson[id];
        shop.marker.setVisible(false);
        document.getElementById(id).className = "normal";
        document.getElementById(id).getElementsByTagName("input")[0].checked = false;
    }
};

// In this case we are keeping the input box for accessibility purposes, so we bubble up the click event to the parent control
function inputClick(input) {
    input.parentElement.click();
};

Toggle Map Style and the Polygons Shown

In my previous Google Maps examples, I store all objects such as polygons and markers in an overlayArray.  Having a “global” theme change that doesn’t just alter the mapStyle, but also polygons is as simple as looping through your Array and setting options.   Here I’m using variable t1 to control the true/false variable, on a live site you should probably do something local such as adding a Class name to the button.

 

  var t1 = true;
    $("#toggle-theme").click(function(e) {
        e.preventDefault();
        if (t1) {
            t1 = false;
            map.setOptions({
                styles: mapstyleDefault
            });
            for (var i in overlayArray) {
                overlayArray[i].setOptions({
                    strokeColor: '#ff0000',
                    fillColor: '#ff0000',
                })
            }
        } else {
            t1 = true;
            map.setOptions({
                styles: mapstyleAlternate
            });
            for (var i in overlayArray) {
                overlayArray[i].setOptions({
                    strokeColor: '#29800b',
                    fillColor: '#29800b',
                })
            }
        }
    
    });

Remove Overlays

I find the most elegant way to deal with Google Maps objects from markers to polygons is to push them to a global array.  The simplest popular method is merely to use this array for clearing all objects.  The example below does that and references a separate InfoWindow array.  Simply setMap null, close the windows, and if needed reset all the arrays to 0.

function clearOverlays() {
    for (var i in overlayArray ) {
      overlayArray[i].setMap(null);
    }
    for (var i in infowinArray ) {
        infowinArray[i].close();
    }
    overlayArray.length = 0;
    infowinArray.length = 0;
};

Though this will suffice for simple loop operations where you basically call back and forth a few items, I found that for more complex objects and maps, a fancier robust function is needed to manage. In my case, I am storing a “complex” polyline, that is many polyline path segments which must be individually rendered.  For example if your path loops around a city, it’s clear you won’t be able to accomplish rendering that in one path.  KML notation already recognizes this and so groups many LineSegments into a MultiGeometry structure.  Manipulation of a KML layer’s stroke, color, etc is easy.  Unfortunately Google Maps does not have a multigeo, and using a Polygon object will not suffice as it automatically draws in the end points.  As well, your polylines may not necessarily connect but are associated with each other.

Here is my revised kitchen sink clear overlay function that offers a target and exclusion option.

  • clearOverlays(“all”) will simply result in everything being removed.
  • clearOverlays(20) or clearOverlays([20, 22, 25]) results in clearing only the targeted numbers being removed.
  • clearOverlays(“all”, 20) or clearOverlays(“all”, [20, 22, 25]) results in everything except the number or array being targeted.
  • Note you can’t do both such as clearOverlays(22, [80]), it will simply ignore the exclusion and go with the targeted item.  Of course this is also illogical.
  • The function takes into account, as in my case, whether you have an array within an array (“embedded”) as is required for grouping polylines or markers.
function clearOverlays(target, exclusion) { 

    // this function to go a level down into the array of objects (polylines, etc)
    function removeObj(a) { 
        if (typeof a[i].setMap == "function") { 
            for (var i in a) { 
                a[i].setMap(null);
            }
        }
    }

    if (target !== "all") { // just targeting something for removal

        if (typeof target == "number" || typeof target == "string") { 
            for (var i in overlayArray) { 
                if (overlayArray[i][0] == target) { removeObj(overlayArray[i][1]); }
            }
        } else { // if array

            for (var i in overlayArray) { 
                if (inArray(overlayArray[i][0], target)) { 

                    if (typeof overlayArray[i][1].setMap == "function") { 
                        overlayArray[i][1].setMap(null); 
                    } else { 
                        removeObj(overlayArray[i][1]);
                        }
                };
            };

        }

    } else { 

        if (exclusion == "") {  // target all, remove everything
            for (var i in overlayArray) { 
                if (typeof overlayArray[i][1].setMap == "function") { 
                    overlayArray[i][1].setMap(null); 
                    } 
                removeObj(overlayArray[i][1]);
                }
            } else { // target all but exclusions
            for (var i in overlayArray) { 
                if (inArray(overlayArray[i][0], exclusion)) { //ignore 
                } else {
                    removeObj(overlayArray[i][1]);
                    }
                }
            }
    };    

    function inArray(needle, haystack) { // needle being a string
        for (var i = 0; i < haystack.length; i++) {
            if (haystack[i] == needle) return true;
        }
        return false;
    }
};

 

Google Maps KML Toggle

My new KML toggle method is as follows which more logically lays out the variables and functions involved. The true improvement is that in order to add new KML files to the map, you simply add a new row to array kml. The code generates the rest. This code is literal easily adaptable to more asynchronous applications.

Code below or view it on Github.

<html>
<head>
<title>KML Toggle Example</title>

 <script type="text/javascript" src="https://maps.googleapis.com/maps/api/js?sensor=false"></script>

 <script type="text/javascript">

    // define some variables to be used later
    var map;
    var overlays = [];
    var kml = {
        a: {
            name: "MPLS/STPL",
            url: "https://maps.google.com/maps/ms?authuser=0&vps=5&ie=UTF8&msa=0&output=kml&msid=212971981154994583939.0004b06640255267e038c"
        },
        b: {
            name: "Bicycling Tour Routes",
            url: "https://maps.google.com/maps/ms?authuser=0&vps=4&ie=UTF8&msa=0&output=kml&msid=212971981154994583939.0004902a1824bbc8c0fe9"
        }, 
    // keep adding more, the url can be any kml file
    };

    // initialize our goo
    function initializeMap() {
        var options = {
            center: new google.maps.LatLng(44.9812, -93.2687),
            zoom: 13,
            mapTypeId: google.maps.MapTypeId.ROADMAP
        }
        map = new google.maps.Map(document.getElementById("map_canvas"), options);

        createTogglers(); // in this example I dynamically create togglers, you can otherwise use jQuery
    };

    google.maps.event.addDomListener(window, 'load', initializeMap);

    // this does all the toggling work, id references the a b names I gave the kml array items

    function toggleKML(checked, id) {

        if (checked) {

            var layer = new google.maps.KmlLayer(kml[id].url, {
                preserveViewport: true,
                suppressInfoWindows: true 
            });

            kml[id].obj = layer; // turns the layer into an object for reference later
            kml[id].obj.setMap(map); // alternative to simply layer.setMap(map)
        }
        else {
            kml[id].obj.setMap(null);
            delete kml[id].obj;
        }

    };

    // in this example create the controls dynamically, prop is the id name 
    function createTogglers() {

        var html = "<form><ul>";
        for (var prop in kml) {
            html += "<li id="selector-" + prop + ""><input type='checkbox' id='" + prop + "'" +
            " onclick='highlight(this, "selector-" + prop + ""); toggleKML(this.checked, this.id)' />" +
            kml[prop].name + "</li>";
        }
        html += "<li class='control'><a href='#' onclick='removeAll();return false;'>" +
        "Remove all layers</a></li>" + 
        "</ul></form>";

        document.getElementById("toggle_box").innerHTML = html;
    };

    // easy way to remove all objects, cycle through the kml array and delete items that exist
    function removeAll() {
        for (var prop in kml) {
            if (kml[prop].obj) {
                document.getElementById("selector-" + prop).className = 'normal'; // in normal js, this replaces any existing classname
                   document.getElementById(prop).checked = false;
                kml[prop].obj.setMap(null);
                delete kml[prop].obj;
            }
        }
    };

    // append class on select, again old school way 
    function highlight(box, listitem) {
        var selected = 'selected';
        var unselected = 'normal';
        document.getElementById(listitem).className = (box.checked ? selected : unselected);
    };

 </script>

<style type="text/css">
#toggle_box { position: absolute; top: 100px; right: 30px; padding: 10px; background: #fff; z-index: 5; box-shadow: 0 5px 10px #777 }
ul { margin: 0; padding: 0; font: 100 1em/1em Helvetica; }
ul li { display: block; padding: 10px; margin: 2px 0 0 0; transition: all 100ms ease-in-out 600ms; }
ul li a:link { border: 1px solid #ccc; border-radius: 4px; box-shadow: inset 0 5px 20px #ddd; padding: 10px; font-size: 0.8em; display: block; text-align: center; }
.selected { font-weight: bold; background: #ddd; }
</style>

</head>
<body>
<div id="map_canvas" style="width: 100%; height: 600px;"></div>
<div id="toggle_box"></div>
</body>
</html>

Convert KML to PolyLine

To convert a KML file to a native Google Maps rendered polyline is essentially the process of parsing the KML, which is XML syntax, into its constituent parts, that being coordinates within a object.

I found the conversion necessary in the process of pulling stored KML data from a Google Fusion Table. If the KML data is stored as Location type, then it will return a MVCArray which can easily be picked apart with for loops to extract the coordinates. However, I have discovered that Fusion Tables has some issues or limitations with very complex KML objects that are not yielding all coordinate arrays, resulting in a half-rendered line. As such I’ve “manually” parsed the KML as a string. The two methods are presented.

success: function(data) {

var rows = data['rows'][0];

for (var i in rows) { 

var geo = rows[i]['geometries'];

	for (var j in geo) { 
	var linesegment = geo[j]['coordinates'];
	drawLine(linesegment);
	};

}


function drawLine(linesegment) { 
	var coordArray = [];
	console.log("rows");
	for (var i in linesegment) {
		var lat = linesegment[i][1];
		var lng = linesegment[i][0];
		var lineCoord = new google.maps.LatLng(lat, lng);
		coordArray.push(lineCoord);
	}

	var randomnumber = Math.floor(Math.random()*7);

	var colors = ["#FF0000", "#00ffff", "#FF00ff", "#Ffff00", "#555555", "#222222"];

  var routeLine = new google.maps.Polyline({
    path: coordArray,
    strokeColor: colors[randomnumber],
    strokeOpacity: 1.0,
    strokeWeight: 4,
  });

  routeLine.setMap(map);
  
}

Google Maps Toggle Mashup (outdated)

Update 12/30/12: This method is outdated and I have revised it.

Even I sometimes panic at the thought of acronyms but once you get past the technobabble, they’re just fancy names for simple concepts. “Keyhole Mashup Language” is the open source standard of mapping coordinates that Google adopted after it acquired the former Keyhole geo-mapping company. Like HTML, it involves semantic tags wrapping information. I was recently asked to fix a Google “mash-up” that involved multiple maps (KML layers) that would turn on and off a Google Map via Javascript. The fix solution was, as usual, to rebuild the whole thing using Google’s latest API standards (see samples here).

The basic application of this mash-up is to toggle multiple maps, hence the name. For my fellow urban planners, this allows the public to contrast and compare different layers like bike lanes, parks, and farmer markets without them needing to have any special ArcGIS viewer. Google also allows you to pull a KML layer directly off the Maps website, meaning you can pull live maps that anyone can update. For larger and more complex layers though you might want to have the KML layer on your server to reduce loading time.

The operating map is called the Big Box Tool Kit and features layers of points in the country where communities have opposed various big box corporations. Immediately one can toggle on maps to see how active certain parts of the country are compared to others.

Call the API.

<script type="text/javascript"
    src="https://maps.google.com/maps/api/js?sensor=false">
</script>

Now in global.js or the header…

function initialize() {
 displayMaps();
}

Define attributes of the map (Google’s API controls and style guide is handy) and tell it where to show the Google map, in this case map_canvas.

  function displayMaps() {
	var myLatlng = new google.maps.LatLng(40,-93.8);
	var myOptions = {
	  zoom: 4,
	  center: (myLatlng),
	  mapTypeId: google.maps.MapTypeId.ROADMAP
	}

  	var map = new google.maps.Map(document.getElementById("map_canvas"), myOptions);

In general the above should just generate the desired Google maps without anything on it. Now we finagle Javascript to output a layer if a form’s checkbox gets checked. There is an if statement for each layer you want. For example the first statement below: if checkbox fruit in formMapControl gets checked, layer1 will show a new Google map using apples.kml. Using form may not be terribly best practices, but the client wanted to use the browser’s native display of checkboxes.

  	if (document.formMapControl.fruit.checked) {

	        var layer1 = new google.maps.KmlLayer('apples.kml');
  		layer1.setMap(map);

  		/* Ensure the new layer does not change the map center or zoom */
  		layer1.set('preserveViewport', true);
	}

  	if (document.formMapControl.dairy.checked) {

  		var layer2 = new google.maps.KmlLayer('cheese.kml');
  		layer2.setMap(map);

  		layer2.set('preserveViewport', true);
	}

}/*this concludes function displayMaps*/
</script>

In the body the map “toggle control box” is the form and the Google map appears in a div called #map_canvas. Use CSS to style as appropriate. If you add checked to one or multiple inputs, it will default display that map layer when the page loads. In the client example link, the form is arranged in a list and position absolute next to the map_canvas, which is styled to be a box with display block. Options of course are endless, you could get fancier by giving the toggle box a transparent background and box-shadow, positioning it over the map. The bare basics follow.

<form name="formMapControl">
   
   <input type="checkbox" name="fruit" value="fruit" onclick="displayMaps();" checked />
      <label class="mapoption">Show Me Fruit!<label></li>
      <input type="checkbox" name="dairy" value="dairy" onclick="displayMaps();" />
      <label class="mapoption">Show Me Dairy!<label></li>

</form>

<div id="map_canvas"></div>