Easy Webmapping with django-leaflet & django-geojson

Let's start with an important thing: I'm a real newbie in webmapping. I've never used Postgis, Mapnik, OpenLayers or <place any webmapping tool here>. Projection, SRID, ... are just some wild words for me.

For an estate management application, I wanted to place some markers on a map : a list of properties found after a search, the location of a consulted property, ... classic. As a Django developer, this application is written with my favorite framework!

Mathieu told me about django-geojson and django-leaflet, two django apps he baked at Makina Corpus. Clearly, he didn't lie to me, django-geojson and django-leaflet make webmapping really lightweight and friendly.

The magic thing is that you don't need a spatial database or some complex geographic libraries! Mathieu and I have removed the last dependencies to GEOS a few days ago. I'm glad I could make this tiny contribution!

Django-geojson

django-geojson allows to manipulate GeoJSON (a JSON format for encoding geographic data structures) in a Django project. You can (de)serialialize objects and querysets, serve map layers through django views and store geographic data in JSON fields.

Django-leaflet

django-leaflet provides a way to play with Leaflet very easily in a Django project. It embeds Leaflet assets, provides a form widget to edit geometries and a templatetag to display maps. In addition, it's highly configurable through django settings.

An Example

Everybody loves mushrooms? Follow me, I'll show you some spots!

So I want to reference some mushroom spots, search for them and show them on a map. Here is the simple model which uses a PointField provided by django-geojson:

# models.py
from djgeojson.fields import PointField
from django.db import models

class MushroomSpot(models.Model):

    geom = PointField()

For the simplicity, when I create a mushroom spot instance, I want to point its location directly on a map. The admin widget coming with django-leaflet will help me:

# admin.py
from leaflet.admin import LeafletGeoAdmin
from django.contrib import admin

admin.site.register(MushroomSpot, LeafletGeoAdmin)

The admin creation form looks like this and the widget allows me to place my marker easily:

django-leaflet admin widget

For information - and I think it's crazy you don't even have to know this to make it all work! -, here is the JSON structure stored in database:

{
  "type":"Point",
  "coordinates":[
    -1.4058208465576172,
    47.15301133231325
  ]
}

Edition is easy, now you'll see that display is too. To display a map, I use templatetags and filters provided by the two modules:

  • leaflet_js and leaflet_css loads my Leaflet assets
  • geojsonfeature helps me to serialize my MushroomSpot instances in a GeoJSON structure
  • leaflet_map shows up the map!

My first view aims to show search results, stored in a queryset named qs_results:

# mushroomspot_list.html
{% extends "base.html" %}
{% load leaflet_tags %}
{% load geojson_tags %}

{% block extra_assets %}
  {% leaflet_js %}
  {% leaflet_css %}
{% endblock %}

{% block content %}

    <script type="text/javascript">
      var collection = {{ qs_results|geojsonfeature|safe }};
      function map_init(map, options) {
          L.geoJson(collection).addTo(map);
      }
    </script>

    {% leaflet_map "spots" callback="window.map_init" %}

{% endblock %}

The simple code above gives me something like this:

simple map with django-leaflet and django-geosjson

Django-geojson can serialize a queryset, but it can also serialize a simple model instance. So for the detail view of a mushroom spot, code is almost the same, excepted the variable name of my data:

var collection = {{ mushroom_spot|geojsonfeature|safe }};

Note that this will dump the whole JSON into the DOM. You can also define a layer view, and obtain the data via Ajax.

A little more

A great option of django-geojson allows to automatically serialize instance properties in the standard GeoJSON feature dictionnary properties. Let's use it to add a pop-up on the marker!

I slightly adapt my model to add a description and a photo, which will be parts of my popup content. I also a write a property popupContent whose content will be serialized in the GeoJSON structure:

# models.py
from djgeojson.fields import PointField
from django.db import models

class MushroomSpot(models.Model):

    geom = PointField()
    description = models.TextField()
    picture = models.ImageField()

    @property
    def popupContent(self):
      return '<img src="{}" /><p><{}</p>'.format(
          self.picture.url,
          self.description)

I've just to change the call of geojsonfeature alittle to specify properties I want to serialize and - for this particular use case - to use the Leaflet option onEachFeature:

<script type="text/javascript">
  var collection = {{ mushroom_spot|geojsonfeature:"popupContent"|safe }};

  function onEachFeature(feature, layer) {
    if (feature.properties && feature.properties.popupContent) {
      layer.bindPopup(feature.properties.popupContent);
    }
  }

  function map_init(map, options) {
    L.geoJson(collection, {onEachFeature: onEachFeature}).addTo(map);
  }
</script>
markers with popup thanks to django-geojson and django-leaflet

And that's it!

What's next?

  • My nexts tests will be to play with more complex geometries like lines or polygons. Django-geojson provides more fields than PointField like MultiPointField, PolygonField, ...
  • For my estate management application, filling may be more simple if the marker was automatically positioned after that the user has wrote the address. I think I could use GeoPy for this.
  • An other solution would be to integrate Leaflet GeoSearch plugin to the django-leaflet admin widget.

See you!

If you have some observations or questions about this post, please leave a comment below.

Let's keep in touch on twitter or through this blog feed!

Comments !