Maps are a common feature many websites want to implement, and depending on requirements, often it is enough to simply embed the map. However, what if you want to create map markers using your own site data or use custom overlays to display additional geographic information? Drupal makes this quite easy with modules like Views and Gmap for Drupal 7.
A Drupal 8 port of GMap is in the works, and there is also the Geolocation D8 module which has Views and Google Maps support. For this article we will review a D7 implementation, although the Javascript code should be easy to adapt for D8 and Geolocation.
Getting Started: Location Markers
Maps require GPS or similar type formats to generate location markers. While you could enter GPS coordinates directly it is more user friendly to use a standard postal address and have Drupal geocode the address using a mapping service like Open Street Map, Google Maps, etc. The Drupal 7 modules used to create our map display are:
- Address Field
- Geofield
- Geocoder
- GMap
- Views
This allowed us to create an address field of type "Postal address" and a geofield using the "geocode from another field" widget type. Now a user can enter a regular postal address and the geocode module will query a map service such as Open Street Map or Google Maps to create a map friendly coordinate. With our location data points we can build a View using the GMap format, add our geo field, then update the GMap format settings and select the geo field which should now be available.
Add a few items with addresses and the Geo modules generate the GPS coordinates for Gmap to display as you can see in the screenshot below. The drop down Legislation Category above is a standard Drupal Views filter, while the Location Boundaries checkboxes on the right are some custom HTML added as a “Global: Text area” field in the View’s footer section with layout and styling done via CSS.
Here are some screenshots of the View configuration. The View format is set to Gmap which requires at least two fields, one with the content’s Node ID and one with the location coordinates. The title field was added to give some information for the marker popup, but you can add more data by using the “rewrite results” option for any field in the View’s config. Simply check “Rewrite the output of this field” and use the Replacement Tokens to combine other field data, and usually you will exclude those other fields from the View display so you don’t have redundant content.
There are some settings required for the Gmap format. We must select the field containing the map coordinates (geotag from the geofield module for our setup), the field with the Node ID, and a field with information to display in a popup when a map marker is clicked.
Building Custom Overlays
For our custom layers we created arrays of GPS coordinates that could be used to define map polygons, and then attached event listeners to HTML elements such as buttons and select lists to control the behavior of our custom polygon layers. In our case we set the fillOpacity of the desired layer to 35% so the markers would be visible underneath, but you could toggle only the border or a variety of other actions. Note that the layer configuration and click handlers are added through the bind and init functions. This makes sure your customizations are added before the map is built:
gmap_object.bind('init', function () { ... code inside here ... });
The Javascript code must be added to your map display. You can attach it through a custom module, theme layer, or even through the Javascript Injector module. The JS Injector module works well, but it is generally advised against because it stores code in your site’s database. It is useful for users that do not have access to the site’s codebase, just restrict access to trusted users only.
All of the JS code should be added inside the obj.bind(‘init) function which is wrapped by the Drupal.gmap.addHandler function. This makes sure all of your JS is ready before the map gets loaded. In the init function we set up the coordinates for our layer boundaries, then use those to create a layer with our desired options. Our layers start out hidden with fillOpacity set to zero, then the sidebar controls are used to control options like borders, fill color, and transparency.
The sidebar controls are simple HTML added in the View’s footer section with a global text field with the functionality added through a jQuery ready function. We use jQuery(document).ready to make sure all the elements are rendered before we attach the interactive functionality. With each button you can specify different actions, so our “Districts” grouping checkbox turns on the borders for all the district layers. The same can be done with pure JS, or your framework of choice, but jQuery is readily available in Drupal 7.
Clicking “District 1” zooms in and pans to the level specified in our jQuery button handler while toggling all the other districts to be grayed out by looping through the rest of the items using our custom highlightItem function to set their opacity to 35% then setting our target layer to transparent:
neighborhoods.forEach(highlightItem); neighborhood_01_layer.setOptions({fillOpacity: 0.0});
Here is a basic example of the JS code, make sure it is only added to your map page.
// GMap module customization for neighborhood map
Drupal.gmap.addHandler('gmap', function (elem) {
var obj = this;
obj.bind('init', function () {
/*** Neighborhood Layers ***/
// Neighborhood 1
const neighorhood_01_coords = [
{ lat: 37.76351, lng: -122.51965 },
{ lat: 37.78172, lng: -122.52265 },
{ lat: 37.79393, lng: -122.51210 },
{ lat: 37.79312, lng: -122.50107 },
];
const neighborhood_01_layer = new google.maps.Polygon({
paths: neighorhood_01_coords,
strokeColor: "#000000",
strokeOpacity: 0.0,
strokeWeight: 2,
fillColor: "#333333",
fillOpacity: 0.0,
});
neighborhood_01_layer.setMap(obj.map);
// Array of layer names so we can easily loop through
// and make changes to all layers, such as hiding all
// layers except for the one selected by the user
var neighborhoods = [
neighborhood_01_layer,
neighborhood_02_layer, //not created in this example
]
/*** Map controls ***/
jQuery(document).ready(function($) {
/*** Neighborhood highlight controls ***/
var buttonN1 = document.querySelector('.neighborhood-1');
buttonN1.addEventListener("click", function() {
neighborhoods.forEach(highlightItem);
neighborhood_01_layer.setOptions({fillOpacity: 0.0});
obj.map.setCenter( {lat: 37.77810413996199, lng: -122.48471403048961 } );
obj.map.setZoom(13.4);
}, false);
// Helper functions below that can be used to loop
// through many layers to modify display options
// Only "highlightItem" used in this example
function showBorders(value, index, array) {
value.setOptions({strokeOpacity: 0.5});
}
function hideBorders(value, index, array) {
value.setOptions({strokeOpacity: 0.0});
}
function highlightItem(value, index, array) {
value.setOptions({fillOpacity: 0.35});
}
function highlightClear(value, index, array) {
value.setOptions({fillOpacity: 0.0});
}
});
});
});
Conclusion
Increasingly, customized maps that highlight neighborhoods, featured locations, and other specific, bespoke points of interest are being sought after by governments, nonprofits and businesses. Drupal allows you to respond quickly and effectively with custom map overlays using GMap and Views. Please feel free to contribute some of your examples in the comments.