In our previous post about building interactive maps with Python and Javascript we learned how to create a GeoJSON dataset of campgrounds and display information on a Google Map with markers, using the Google Maps JavaScript API. We’re going to build on that foundation in today’s post by showing you how to use some more advanced techniques that are not discussed in Google’s documentation. To create a more customized user experience, we are going to learn how to filter markers and how to display marker GeoJSON outside of the Google Maps object.

We will be building out this Yelp-like interface for finding campgrounds. Similar to Yelp, you can filter your results, and when you click a marker on the map the information for that campground will be displayed below the map. Try it out!

See the Pen Map with Buttons and Display by Sev (@sevleonard) on CodePen.

Filtering Markers

In the previous post we used the loadGeoJson method to populate the markers on our map. For this example, we are going to use the jQuery getJson method.

$.getJSON(geojson_url, function(result) { // get the features array from the FeatureCollection data = result['features'] $.each(data, function(key, val) { // create markers }) })

We’ve made a modification to the GeoJSON file we were previously using to include specific properties for the amenity fields. These properties are what we will use to filter the markers.

To keep track of what amenities the user wants to filter on lets create a list of possible filters, using the same names as we have for the new property fields:

// start out with filter features set to false, so no filtering happens by default var filters = {shower:false, vault:false, flush:false}

Now, whenever we want to set marker visibility based on filter settings, we simply iterate through the array of markers and compare the properties field to the filters that are set to ‘true’:

// get a subset of the filters that are set to true var get_set_options = function() { ret_array = [] for (option in filters) { if (filters[option]) { ret_array.push(option) } } return ret_array; } var filter_markers = function() { set_filters = get_set_options() // for each marker, check to see if all required options are set for (i = 0; i < markers.length; i++) { marker = markers[i]; // start the filter check assuming the marker will be displayed // if any of the required features are missing, set 'keep' to false // to discard this marker keep=true for (opt=0; opt<set_filters.length; opt++) { if (!marker.properties[set_filters[opt]]) { keep = false; } } marker.setVisible(keep) } }

Since we only want to check the properties that are set to ‘true’ in the filters list, we first call ‘get_set_options’ to return that subset of filters. We then iterate through the markers and check that the filtered value is ‘true’ in the GeoJSON, and if not we set ‘keep’ to ‘false’ to make the marker invisible, thus filtering it out of the current map view.

Because we matched the values in the ‘properties’ field to the names in the ‘filters’ list we simply have to check marker.properties using the name from the ‘filters’ list. A single iteration of the above would look like

marker.properties[filters[opt]] // for opt = 0 // set_filters[0] -> 'shower' // marker.properties -> 'flush':true, 'shower':true, 'vault':false // marker.properties['shower'] evaluates to true

Setting the Filters

Now that we’ve seen how to filter markers, we need to connect the filter selection the UI so users can set and unset filter options. First, we need to provide a way for the user to select different features to filter on.

See the Pen Map with Buttons by Sev (@sevleonard) on CodePen.

Instead of using buttons, we will use checkboxes styled as buttons using a ‘label’ entity. This gives the UI a clean look while providing boolean functionality to use for setting the filters. We’ll use CSS as detailed in this StackOverflow post to style the checkboxes as buttons.

<input type="checkbox" name="filter" id="shower" class='chk-btn'> <label for='shower'>Shower</label> <input type="checkbox" name="filter" id="flush" class='chk-btn'> <label for='flush'>Flush Toilet</label> <input type="checkbox" name="filter" id="vault" class='chk-btn'> <label for='vault'>Vault Toilet</label>

Notice that we have set the name to ‘filter’ for the checkbox inputs. By using a uniform ‘name’ attribute for all of our filter checkboxes we can use a single function to handle all of their change events:

$('input[name=filter]').change(function (e) { // update filters list });

On the ‘change’ event, we can get the ‘id’ attribute of the current checkbox to set the filter, noticing that we used the same value in ‘id’ as we did for our ‘filters’ list. Because we matched these values, we can query the ‘filters’ list for the ‘id’ value of the current checkbox (accessed via ‘this’) and toggle the value of the filter.

// filters list: var filters = {shower:false, vault:false, flush:false} var map_filter = function(id_val) { if (filters[id_val]) filters[id_val] = false else filters[id_val] = true } $('input[name=filter]').change(function (e) { map_filter(this.id); })

We now have a way for the user to set the filters via button-styled checkboxes, and for the filters to be applied to the markers using the ‘filter_markers’ function. We just need a connection between the two to perform the filtering.

<button class="btn btn-blue" id="search_campgrounds">Find Campgrounds</button>

When the user clicks ‘Find Campgrounds’ we execute the ‘filter_markers’ function.

$('#search_campgrounds').on('click', function() { filter_markers() })

Try it out!

See the Pen Map with Buttons by Sev (@sevleonard) on CodePen.

Displaying Marker Info

Info windows are helpful for showing details for a point on the map, but they also hide the surrounding area. Since our users probably want to see the area around the campground it would be better to display the marker ‘description’ in a different place that doesn’t obscure the map.

To do this, lets revisit the ‘loadMarkers’ function from the previous codepen, in particular the ‘addListener’ function:

function loadMarkers() { ... var descriptionText = feature.getProperty('description') var titleText = feature.getProperty('title') var markerInfo = "<div><h3>" + titleText + "</h3>Amenities: " + descriptionText + "</div>" ... marker.addListener('click', function() { infoWindow.close() infoWindow.setContent(markerInfo) infoWindow.open(map, marker) }); ... }

Instead of setting an infoWindow when we click a marker, we want to display the markerInfo in another part of the DOM. First lets provide a home for that information:

<div id="campground_info"></div>

Since we’ve already created the HTML formatting for the markerInfo, we can simply set the html attribute of the campground_info div in the ‘click’ listener, instead of creating the infoWindow:

marker.addListener('click', function() { $('#campground_info').html(markerInfo) });

Now, when we click on each campground we will see the ‘description’ information displayed below the map in the campground_info div:

See the Pen Map with Buttons and Display by Sev (@sevleonard) on CodePen.

Summary

We’ve learned how to add filtering to Google Maps markers and display marker information outside of the map. By using GeoJSON properties in the ‘id’ fields of our filter checkboxes and for the values in our ‘filters’ list we were able to quickly put together this prototype. Standardizing values in this way really simplifies the coding process, and results in code that reads easily. We can leverage this because we own the entire stack from GeoJSON generation to the front end – be careful about using these techniques if someone else is providing the source information, as fields in the GeoJSON could change. In that case you would want to provide a translation function to provide a consistent interface to your UI regardless of changes in the underlying GeoJSON.

Some fun extras you could try out with the code we developed in this post: