Maps with Django (part 1): GeoDjango, SpatiaLite and Leaflet
Paolo Melchiorre
Posted on December 10, 2020
A quickstart guide to create a web map with the Python based web framework Django using its module GeoDjango , the SQLite database with its spatial extension SpaliaLite and Leaflet , a JavaScript library for interactive maps.
Introduction
In this guide we will see how to create a minimal web map using Django (the Python-based web framework), starting from its default project, writing a few lines of code and with the minimum addition of other software:
GeoDjango, the Django geographic module
SpatiaLite, the SQLite spatial extension
Leaflet, a JavaScript library for interactive maps.
Requirements
The only python package required is Django.
We’ll assume you have Django installed already.
This guide is tested with Django 3.1 and Python 3.8.
Creating a project
You need to create the basic project in your workspace directory with this command:
$ django-admin startproject mymap
That’ll create a directory mymap
, which is laid out like this:
mymap/
├── manage.py
└── mymap
├── __init__.py
├── asgi.py
├── settings.py
├── urls.py
└── wsgi.py
Creating the Markers app
To create your app, make sure you’re in the same directory as manage.py
and type this command:
$ python manage.py startapp markers
That’ll create a directory markers
, which is laid out like this:
markers/
├── __init__.py
├── admin.py
├── apps.py
├── migrations
│ └── __init__.py
├── models.py
├── tests.py
└── views.py
Activating the Markers app
Modify the INSTALLED_APPS setting
Append markers
to the INSTALLED_APPS
in mymap/settings.py
INSTALLED_APPS = [
"django.contrib.admin",
"django.contrib.auth",
"django.contrib.contenttypes",
"django.contrib.sessions",
"django.contrib.messages",
"django.contrib.staticfiles",
"markers",
]
Adding an empty web map
We're going to add an empty web to the app:
Adding a template view
We have to add a TemplateView
in views.py
:
"""Markers view."""
from django.views.generic.base import TemplateView
class MarkersMapView(TemplateView):
"""Markers map view."""
template_name = "map.html"
Adding a map template
We have to add a templates/
directory in markers/
:
$ mkdir templates
And a map.html
template in markers/templates/
:
{% load static %}
<!doctype html>
<html lang="en">
<head>
<title>Markers Map</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" type="text/css" href="{% static 'map.css' %}">
<link rel="stylesheet" type="text/css" href="https://unpkg.com/leaflet@1.7.1/dist/leaflet.css">
<script src="https://unpkg.com/leaflet@1.7.1/dist/leaflet.js"></script>
</head>
<body>
<div id="map"></div>
<script src="{% static 'map.js' %}"></script>
</body>
</html>
Adding javascript and css files
We have to add a static/
directory in markers/
:
$ mkdir static
Add a map.css
stylesheet in markers/static/
:
html, body {
height: 100%;
margin: 0;
}
#map {
width: 100%;
height: 100%;
}
Add a map.js
stylesheet in markers/static/
:
const attribution = '© <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'
const map = L.map('map')
L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', { attribution: attribution }).addTo(map);
map.fitWorld();
Adding a new URL
Add an urls.py
files in markers/
:
"""Markers urls."""
from django.urls import path
from .views import MarkersMapView
app_name = "markers"
urlpatterns = [
path("map/", MarkersMapView.as_view()),
]
Modify urls.py
in mymap/
:
"""mymap URL Configuration."""
from django.contrib import admin
from django.urls import include, path
urlpatterns = [
path("admin/", admin.site.urls),
path("markers/", include("markers.urls")),
]
Testing the web map
Now you can test the empty web map running this command:
$ python manage.py runserver
Now that the server’s running, visit http://127.0.0.1:8000/markers/map/ with your Web browser. You’ll see a “Markers map” page, with a full page map. It worked!
Adding geographic features
Installing SpatiaLite
We need to install the SQLite
spatial extension SpatiaLite
:
- on Debian-based GNU/Linux distributions (es: Debian, Ubuntu, ...):
$ apt install libsqlite3-mod-spatialite
- on macOS using Homebrew:
$ brew install spatialite-tools
Changing the database engine
Modify the DATABASES
default engine in mymap/settings.py
DATABASES = {
"default": {
"ENGINE": "django.contrib.gis.db.backends.spatialite",
"NAME": BASE_DIR / "db.sqlite3",
}
}
Activating GeoDjango
Modify the INSTALLED_APPS setting
Append GeoDjango
to the INSTALLED_APPS
in mymap/settings.py
INSTALLED_APPS = [
"django.contrib.admin",
"django.contrib.auth",
"django.contrib.contenttypes",
"django.contrib.sessions",
"django.contrib.messages",
"django.contrib.staticfiles",
"django.contrib.gis",
"markers",
]
Adding some markers
Now we can add some markers in the map.
Adding the Marker model
We're going to add a Marker
model in markes/models.py
:
"""Markers models."""
from django.contrib.gis.db.models import PointField
from django.db import models
class Marker(models.Model):
"""A marker with name and location."""
name = models.CharField(max_length=255)
location = PointField()
Now we have to create migrations for the new model:
$ python manage.py makemigrations
And then we'll apply this migration to the SQLite
database:
$ python manage.py migrate
Activating the Marker admin
To insert new marker we have to add a Marker admin in markes/admin.py
"""Markers admin."""
from django.contrib.gis import admin
from .models import Marker
@admin.register(Marker)
class MarkerAdmin(admin.OSMGeoAdmin):
"""Marker admin."""
list_display = ("name", "location")
Testing the admin
We have to create an admin user to login and test it:
$ python manage.py createsuperuser
Now you can test the admin running this command:
$ python manage.py runserver
Now that the server’s running, visit http://127.0.0.1:8000/admin/markers/marker/add/ with your Web browser. You’ll see a “Markers” admin page, to add a new markers with a map widget. I added a marker to the latest peak I climbed: "Monte Amaro 2793m 🇮🇹"
Showing all markers in the web map
Adding all markers in the view
We can add with a serializer
all markers as a GeoJSON
in the context of the MarkersMapView
in markes/views.py
:
"""Markers view."""
import json
from django.core.serializers import serialize
from django.views.generic.base import TemplateView
from .models import Marker
class MarkersMapView(TemplateView):
"""Markers map view."""
template_name = "map.html"
def get_context_data(self, **kwargs):
"""Return the view context data."""
context = super().get_context_data(**kwargs)
context["markers"] = json.loads(serialize("geojson", Marker.objects.all()))
return context
The value of the markes
key in the context
dictionary we'll something like that:
{
"type": "FeatureCollection",
"crs": {
"type": "name",
"properties": {
"name": "EPSG:4326"
}
},
"features": [
{
"type": "Feature",
"properties": {
"name": "Monte Amaro 2793m \ud83c\uddee\ud83c\uddf9",
"pk": "1"
},
"geometry": {
"type": "Point",
"coordinates": [
14.08591836494682,
42.08632592463349
]
}
}
]
}
Inserting the GeoJSON in the template
Using json_script
built-in filter we can safely outputs the Python dict with all markers as GeoJSON
in markers/templates/map.html
:
{% load static %}
<!doctype html>
<html lang="en">
<head>
<title>Markers Map</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" type="text/css" href="{% static 'map.css' %}">
<link rel="stylesheet" type="text/css" href="https://unpkg.com/leaflet@1.7.1/dist/leaflet.css">
<script src="https://unpkg.com/leaflet@1.7.1/dist/leaflet.js"></script>
</head>
<body>
{{ markers|json_script:"markers-data" }}
<div id="map"></div>
<script src="{% static 'map.js' %}"></script>
</body>
</html>
Rendering all markers in the map
We can render the GeoJSON
with all markers in the web map using Leaflet
in markers/static/map.js
:
const attribution = '© <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'
const map = L.map('map')
L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', { attribution: attribution }).addTo(map);
const markers = JSON.parse(document.getElementById('markers-data').textContent);
let feature = L.geoJSON(markers).bindPopup(function (layer) { return layer.feature.properties.name; }).addTo(map);
map.fitBounds(feature.getBounds(), { padding: [100, 100] });
Testing the populated map
I populated the map with other markers of the highest or lowest points I've visited in the world to show them on my map.
Now you can test the populated web map running this command:
$ python manage.py runserver
Now that the server’s running, visit http://127.0.0.1:8000/markers/map/ with your Web browser. You’ll see the “Markers map” page, with a full page map and all the markers. It worked!
A map with some markers of the highest or lowest points I've visited in the world with the opened popup of the latest peak I climbed.
Curiosity
If you want to know more about my latest hike to the Monte Amaro peak you can see it on my Wikiloc account: Round trip hike from Rifugio Pomilio to Monte Amaro 🔗.
Conclusion
We have shown an example of a fully functional map, trying to use the least amount of software, without using external services.
This map is enough to show a few points in a simple project using SQLite and Django templates.
In future articles we will see how to make this map even more advanced using Django Rest Framework, PostGIS, etc ... to render very large numbers of markers in an even more dynamic way.
Stay tuned.
-- Paolo
Resources
License
This article and related presentation is released with Creative Commons Attribution ShareAlike license (CC BY-SA)
Original
Originally posted on my blog:
https://www.paulox.net/2020/12/08/maps-with-django-part-1-geodjango-spatialite-and-leaflet/
Posted on December 10, 2020
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.