Google's latest version of their Mapping API is really great. Mainly, it's nice that I don't have to worry about API keys and can make very simple calls to get maps formatted exactly how I want.
I recently started putting events on my website and I wanted to include a simple map on the page. You could also change the code pretty easily to show many locations on a single map if you wanted. Once you start playing around with it, the sky is the limit.
To start, I created a new layout called event
. The template will be able to make use of certain YAML attributes if it finds them. I'll get back to the template in a minute.
Once I know I have a particular type of post that uses an event
template, I created some YAML front matter attributes on the post itself. Here's an example:
layout: event
location:
address: 218 W 15th St, NY, NY
latitude: 40.7396183
longitude: -74.00017439999999
If you have the address, you can just use that value and skip the latitude and longitude. But if you don't have the address, but you have a particular set of coordinates, you can skip the address.
Now that your content is aware of a location, we need to send that to the event
template that you created in your _layouts
directory. I've created a javascript file that scans the page for elements that need to be mapped and then converts HTML to a map. So the first step is to create that HTML in my template. Here's the snippet of code I have in my event
template.
{% if page.location %}
<div class="mapping">
<div class="map-canvas" id="map-event"></div>
<div class="map-point" address="{{ page.location.address }}" latitude="{{ page.location.latitude }}" longitude="{{ page.location.longitude }}">
<div><b>{{ page.title }}</b></div>
<div>{{ page.location.address }}</div>
</div>
</div>{% endif %}
There are a few things going on here. First of all, everything should be wrapped in a DIV with a class of mapping
. This allows me to have multiple maps on a page and denote that the points listed in the DIV correspond with a map. Next, I declare a DIV with a class of map-canvas
. This will be the actual map that gets displayed. Be sure in your CSS to always specify a height for your map or it won't be visible. And then thirdly, I have one or more DIVs with a class of map-point
. These will correspond to the markers on the map. The content within is what will be rendered in the pop-up info window when a user clicks on the map.
At this point, if you rendered the site, you would get an empty paragraph where the map should be. The information about the location should be showing up as attributes of that paragraph, but nothing is displayed to the end user yet. Now it's time to work Google Maps into the equation.
First of all, make sure that Google Maps is being loaded by putting this in your main layout file:
<script type="text/javascript" src="http://maps.googleapis.com/maps/api/js?sensor=false"></script>
Next, it's time to create your custom JQuery code to translate that HTML you created into an actual map. I created a file called (creatively enough) maps.js
and put a reference to it in my main layout file.
The logic for displaying the map is roughly the following:
- Find all occurrences of elements with a class of
mapping
- Loop through them and initialize elements with
.map-canvas
as a Google map - Find all the
.map-point
elements, add a marker to the map using the attributes of the HTML
Here's the entirety of that JavaScript file:
var geocoder; // currently not used
$(document).ready ( function() {
//loop through all the mapping elements
$(".mapping").each(function() {
var map;
var bounds;
// initialize the map canvas
$(this).find('.map-canvas').each(function() {
map = initMap(this);
bounds = new google.maps.LatLngBounds();
});
// loop through map points and put them on the map
var points = $(this).find(".map-point");
points.each(function() {
// create a latlong and marker for each point
var latlng = new google.maps.LatLng($(this).attr('latitude'), $(this).attr('longitude'));
var marker = new google.maps.Marker({
map: map,
icon: $(this).attr('icon'),
position: latlng,
title: $(this).find(".description").text(),
url: $(this).find(".description a").attr('href')
});
// if the map point has HTML, turn it into an info window
var infowindow = new google.maps.InfoWindow({
content: $(this).html()
});
// handle clicks to the marker
google.maps.event.addListener(marker, 'click', function() {
if (this.url) {
window.open(this.url);
} else {
infowindow.open(map, marker);
}
});
// if there are multiple markers, fit the map to them; otherwise, center the map on the only marker
if (points.length == 1) {
map.setCenter(latlng);
} else {
bounds.extend(latlng);
map.fitBounds(bounds);
}
});
});
});
function initMap(canvas) {
// set a default zoom unless it's specified
zoom = parseInt($(canvas).attr('zoom')) || 15;
// center the map and create options
var latlng = new google.maps.LatLng(0, 0);
var myOptions = {
zoom: zoom,
center: latlng,
mapTypeControl: false,
streetViewControl: false,
mapTypeId: google.maps.MapTypeId.ROADMAP
}
// create the map
map = new google.maps.Map(canvas, myOptions);
map.setCenter(latlng);
// set some custom styles
map.set('styles', [
{
featureType: 'road',
elementType: 'geometry',
stylers: [
{ weight: 0.5 }
]
}, {
featureType: 'landscape',
elementType: 'geometry',
stylers: [
{ hue: '#ffff00' }
]
}
]);
return map;
}
Please note that there's a little extra functionality included here than I originally discussed. I created a second map that loads my latest Foursquare checkins. For those items, I am simply linking the marker to the URL of the venue of Foursquare. There's no info window for those items. Also, because I'm showing multiple items on a map, I have to go through a couple of lines of code to move the map to see all the items on it.