Maps
with GeoDjango
PostGIS
and Leaflet
Carmelo Catalfamo @mratomicbomb_
Paolo Melchiorre @pauloxnet
paulox.net
20tab.com
Slide 2
Slide 2 text
paulox.net
20tab.com
Paolo Melchiorre @pauloxnet
2
● Computer Science Engineer
● Python Developer since 2006
● PostgreSQL user (not a DBA)
● Django Developer since 2011
● Remote Worker since 2015
● Senior Developer at 20tab
Slide 3
Slide 3 text
paulox.net
20tab.com
Carmelo Catalfamo @MrAtomicBomb_
3
● JavaScript Developer
● NodeJS developer since 2014
● ReactJS developer since 2015
● React Native developer
● Django user
● Frontend developer at 20tab
Slide 4
Slide 4 text
paulox.net
20tab.com
www.20tab.com
4
● Rome based with remote workers
● Meetup and conferences
● Agile and Lean methodologies
● Growth marketing approach
● Software development
● Python, Django, React JS, uWSGI
Slide 5
Slide 5 text
paulox.net
20tab.com
Goal
5
Find a simple way
to integrate a web map
in a Django project.
Slide 6
Slide 6 text
paulox.net
20tab.com
Outline
6
Basic map
GeoDjango
Leaflet JS
PostGIS
Use case
Slide 7
Slide 7 text
paulox.net
20tab.com
Web map
7
● Map delivered by GIS
● Static and Dynamic
● Interactive and view only
● Raster or Vector tiles
● Spatial databases
● Javascript library
paulox.net
20tab.com
PostGIS
9
● Best GeoDjango backend
● PostgreSQL extension
● Integrated spatial data
● Spatial data types
● Spatial indexing
● Spatial functions
Slide 10
Slide 10 text
paulox.net
20tab.com
Leaflet
10
● JavaScript library for maps
● Free Software
● Desktop & Mobile friendly
● Light (< 40 KB of gizp JS)
● Well documented
● Simple, performing, usable
Slide 11
Slide 11 text
paulox.net
20tab.com
Basic map example
11
Basic map example
Slide 12
Slide 12 text
paulox.net
20tab.com
Making queries
12
from django.db import models
class Blog(models.Model):
name = models.CharField(max_length=100)
class Author(models.Model):
name = models.CharField(max_length=200)
class Entry(models.Model):
blog = models.ForeignKey(Blog, on_delete=models.CASCADE)
authors = models.ManyToManyField(Author)
headline = models.CharField(max_length=255)
paulox.net
20tab.com
Migrations
14
from django.contrib.postgres import operations
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [('blog', '0001_initial')]
operations = [
operations.CreateExtension('postgis')
]
Slide 15
Slide 15 text
paulox.net
20tab.com
Models
15
from django.contrib.gis.db.models import PointField
from django.db import models
class Entry(models.Model):
# …
point = PointField()
@property
def lat_lng(self):
return list(getattr(self.point, 'coords', [])[::-1])
Slide 16
Slide 16 text
paulox.net
20tab.com
Admin
16
from django.contrib import admin
from django.contrib.gis.admin import OSMGeoAdmin
from .models import Entry
@admin.register(Entry)
class EntryAdmin(OSMGeoAdmin):
default_lon = 1253000
default_lat = 5430000
default_zoom = 12
# …
Slide 17
Slide 17 text
paulox.net
20tab.com
Admin page
17
Slide 18
Slide 18 text
paulox.net
20tab.com
Views and urls
18
from django.urls import path
from django.views.generic import ListView
from .models import Entry
class EntryList(ListView):
queryset = Entry.objects.filter(point__isnull=False)
urlpatterns = [
path('map/', EntryList.as_view()),
]
Slide 19
Slide 19 text
paulox.net
20tab.com
Template
19
PyConX Venues
Slide 20
Slide 20 text
paulox.net
20tab.com
Javascript
20
var m = L.map('m').setView([43.77, 11.26], 15); # Florence
L.tileLayer('//{s}.tile.osm.org/{z}/{x}/{y}.png').addTo(m);
{% for e in object_list %}
L.marker({{e.lat_lng}}).addTo(m);
{% endfor %}
Slide 21
Slide 21 text
paulox.net
20tab.com
Basic map page
21
Slide 22
Slide 22 text
paulox.net
20tab.com
Use case
22
● Coastal properties
● Active since 2014
● 8 Languages
● ~ 100k active advertisements
● ~ 40 Countries
● 6 Continents
paulox.net
20tab.com
Serializer
27
from rest_framework_gis.serializers import (
GeoFeatureModelSerializer
)
from .models import Ad
class AdSerializer(GeoFeatureModelSerializer):
class Meta:
model = Ad
geo_field = 'location'
fields = ('id',)
Slide 28
Slide 28 text
paulox.net
20tab.com
Views
from rest_framework.viewsets import ReadOnlyModelViewSet
from rest_framework_gis.filters import InBBoxFilter
from .models import Ad
from .serializers import AdSerializer
class AdViewSet(ReadOnlyModelViewSet):
bbox_filter_field = 'location'
filter_backends = (InBBoxFilter,)
queryset = Ad.objects.filter(location__isnull=False)
serializer_class = AdSerializer
28