Slide 1

Slide 1 text

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

Slide 8

Slide 8 text

paulox.net 20tab.com GeoDjango 8 ● django.contrib.gis ● Geographic framework ● Spatial Field Types ● Spatial ORM queries ● Admin geometry fields ● Four database backends

Slide 9

Slide 9 text

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)

Slide 13

Slide 13 text

paulox.net 20tab.com Settings 13 INSTALLED_APPS = [ # … 'django.contrib.gis', ] DATABASES = {'default': { 'ENGINE': 'django.contrib.gis.db.backends.postgis', # … }}

Slide 14

Slide 14 text

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

Slide 23

Slide 23 text

paulox.net 20tab.com Version 1.0 23 ● Django 1.6 ● Python 2.7 ● PostgreSQL 9.3 ● Text Spatial Fields ● Leaflet 1.0 ● Static/View-only map

Slide 24

Slide 24 text

paulox.net 20tab.com Version 2.0 24 ● Django 2.2 / GeoDjango ● Python 3.6 ● PostgreSQL 10 ● PostGIS 2.4 / Spatial data ● Leaflet 1.4 ● Dynamic/Interactive map

Slide 25

Slide 25 text

paulox.net 20tab.com Models 25 from django.db import models from django.contrib.gis.db.models import ( MultiPolygonField, PointField ) class City(models.Model): borders = MultiPolygonField() class Ad(models.Model): city = models.ForeignKey(City, on_delete=models.CASCADE) location = PointField()

Slide 26

Slide 26 text

paulox.net 20tab.com Setup 26 $ pip install djangorestframework # RESTful API $ pip install djangorestframework-gis # Geographic add-on $ pip install django-filter # Filtering support INSTALLED_APPS = [ # … 'django.contrib.gis', 'rest_framework', 'rest_framework_gis', 'django_filters', ]

Slide 27

Slide 27 text

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

Slide 29

Slide 29 text

paulox.net 20tab.com Urls from rest_framework.routers import DefaultRouter from .views import AdViewSet router = DefaultRouter() router.register(r'markers', AdViewSet, basename='marker') urlpatterns = router.urls 29

Slide 30

Slide 30 text

paulox.net 20tab.com GeoJSON {"type": "FeatureCollection", "features": [{ "id": 1, "type": "Feature", "geometry": { "type": "Point", "coordinates": [11.255814, 43.769562] }, "properties": {} }]} 30

Slide 31

Slide 31 text

paulox.net 20tab.com Ajax / Leaflet var m = L.map('m').setView([43.77, 11.26], 15) L.tileLayer('//{s}.tile.osm.org/{z}/{x}/{y}.png').addTo(m) fetch('/markers') .then(function (results) { L.geoJSON(results).addTo(m) }) 31

Slide 32

Slide 32 text

paulox.net 20tab.com React Leaflet import React from 'react' import { Map, TileLayer, GeoJSON } from 'react-leaflet' export default class Map extends Component { state = { geoJson: {} } onMove = () => { fetch('/markers') .then(geoJson => this.setState({ geoJson })) } // render () {...} } 32

Slide 33

Slide 33 text

paulox.net 20tab.com React Render method render () { return ( ) } 33

Slide 34

Slide 34 text

paulox.net 20tab.com Demo 34

Slide 35

Slide 35 text

paulox.net 20tab.com Conclusion 35 ● Out-of-the-box features ● Spatial & Relational queries ● Django/PostgreSQL ● Backend clusterization ● Administrative levels ● Dynamic spatial entity

Slide 36

Slide 36 text

paulox.net 20tab.com Resources 36 ● docs.djangoproject.com/en/ ● github.com/django/django ● postgis.net/docs/ ● github.com/postgis/postgis ● leafletjs.com/reference.html ● github.com/leaflet/leaflet

Slide 37

Slide 37 text

paulox.net 20tab.com Acknowledgments 37 Mer et Demeures meretdemeures.com Twentytab 20tab.com/lavora-con-noi

Slide 38

Slide 38 text

paulox.net 20tab.com Info 38 ● www.paulox.net/talks ● Slides ● Code samples ● Resource URLs ● Questions and comments ● License (CC BY-SA)

Slide 39

Slide 39 text

Paolo Melchiorre paulox.net @pauloxnet Thank you! paulox.net 20tab.com Carmelo Catalfamo carmelocatalfamo.it @mratomicbomb_