Simple maps in Wagtail with Google and geopy

,
Author information: Tom Talbot , Python Developer , Post information: , x min read ,
Related post categories: Digital products , Wagtail ,

Wagtail is a Django content management system focused on flexibility and user experience. In this post we add a simple, extensible map feature to a Wagtail site by integrating geopy, a small geocoding library for Python.

Often, our sites contain geographical information that we would like to be able to display on a map. For advanced geographical features we can use a framework like Geodjango, but for a small project this may provide more than we need. Wagtail is flexible enough that adding a map feature to it is easy. To demonstrate this, we'll build an example site that satisfies the following requirements:

  • Administrators can associate pages with locations. These will be referred to as locatable pages.
  • A map can be added to any page.
  • Site vistors can search for locatable pages
  • Administrators can categorise locatable pages.

Our site must be able to do the following:

    • Take the names of locations and convert them into latitude/longitude pairs that can be plotted on a Google map.
    • Associate locations with pages and save that information in the database.
    • Perform the correct calculations to be able to find locations within a specified radius of a particular latitude and longitude so that site visitors can search for locatable pages local to them.

    We will use the Google Geocoding API to convert the names of locations into latitude/longitude pairs. geopy will be used to communicate with the API and to calculate the distances between those pairs. The results will be plotted on a Google map. If you would like to follow along with this post you can find all of the code here. If you try it with your own Google Maps API key, make sure to enable the relevant APIs in the Google APIs Console.

    To begin, we use Wagtail Template to set up a new Wagtail build called wagtail_map. Our only additional dependency is geopy 1.1.2, which goes in requirements.txt.

    The core of the project is the Location model:

    Converting the name of a location into a latitude/longitude pair is called "geocoding". To geocode the name of a location with the Location model, instantiate it, set Location.name, then call Location.geocode(). If the Geocoding API is available and the name is geocodableLocation.latitude and Location.longitude will be set. You may now save this object to the database and create foreign key relationships between it and other models. If you would like to find Location objects that you have saved previously and that are within a specified distance of the current Location object (specified by Location.SEARCH_RADIUS, in km) call Location.find_nearby_locations().

    A couple of implementation details to note here are that Location uses geopy's implementation of Vincenty's formulae for distance calculations and that we use a bounding box to reduce the number of Location objects that we need to retrieve from the database and hence the number of calculations to perform.

    To make it easy to associate Page models with Locations, we have a Locatable mixin*:

    Inheriting from this mixin will give a page a location_name property and a couple of search functions, allowing you to search for pages that are within a specified geographical distance of the page and in a particular part of the page tree. We use the page tree to categorise locatable pages. For example, we could have two event indexes, one for picnics and the other for birthday parties, and use search_children_locations() to ensure that only events beneath those event indexes in the page tree appear on their respective maps.

    Here is the Locatable mixin in use on two Page models:

    As you can see, we need to tweak the behaviour of save() and get_context() a little to make Locatable work correctly, but the overhead is negligible.

    Now let's create a template that can be included in any locatable page template to give it a map:

    Here it is included in the event page template:

    Note that we've included a search form which we can type location names into. We do the same for the event index. Now all we need to do is fire up the server, log in, create an event index in the Wagtail admin, create a couple of event pages, then sit back and admire our work:

    And there you have it. Maps in Wagtail.

    wagtail map 2
    Wagtail Maps

    Exercise for the Reader

    Can you make it work with AJAX?

    Update

    For a different approach to maps in Wagtail, see wagtailgmaps from Springload.

    *Arguably, pages that can be searched for are distinct from pages where a search may be performed. However for the sake of brevity we will keep all functionality in one mixin.

    ,
    Author information: Tom Talbot , Python Developer , Post information: , x min read ,
    Related post categories: Digital products , Wagtail ,