Slide 1

Slide 1 text

Maps with GeoDjango PostGIS and Leaflet 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 www.20tab.com 3 ● Rome based with remote workers ● Meetup and conferences ● Agile and Lean methodologies ● Growth marketing approach ● Software development ● Python, Django, React JS, PostgreSQL

Slide 4

Slide 4 text

paulox.net 20tab.com Goal 4 Find a simple way to integrate a web map in a Django project.

Slide 5

Slide 5 text

paulox.net 20tab.com Outline 5 Basic map GeoDjango Leaflet JS PostGIS Use case

Slide 6

Slide 6 text

paulox.net 20tab.com Web map 6 ● Map delivered by GIS ● Static and Dynamic ● Interactive and view only ● Raster or Vector tiles ● Spatial databases ● Javascript library

Slide 7

Slide 7 text

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

Slide 8

Slide 8 text

paulox.net 20tab.com PostGIS 8 ● Best GeoDjango backend ● PostgreSQL extension ● Integrated spatial data ● Spatial data types ● Spatial indexing ● Spatial functions

Slide 9

Slide 9 text

paulox.net 20tab.com Leaflet 9 ● JavaScript library for maps ● Free Software ● Desktop & Mobile friendly ● Light (< 40 KB of gizp JS) ● Well documented ● Simple, performing, usable

Slide 10

Slide 10 text

paulox.net 20tab.com Basic map example 10 Basic map example

Slide 11

Slide 11 text

paulox.net 20tab.com Making queries 11 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 12

Slide 12 text

paulox.net 20tab.com Making queries - SQL 12 BEGIN; -- -- Create model Entry -- CREATE TABLE "blog_entry" ( "id" serial NOT NULL PRIMARY KEY, "headline" varchar(255) NOT NULL, "body_text" text NOT NULL ); COMMIT;

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 Migrations - SQL 15 BEGIN; -- -- Creates extension postgis -- CREATE EXTENSION IF NOT EXISTS "postgis"; COMMIT;

Slide 16

Slide 16 text

paulox.net 20tab.com Point field 16 from django.contrib.gis.db.models import PointField from django.db import models class Entry(models.Model): # … point = PointField() @property def lat_lon(self): return list(getattr(self.point, 'coords', [])[::-1])

Slide 17

Slide 17 text

paulox.net 20tab.com Point field - SQL 17 BEGIN; -- -- Add field point to entry -- ALTER TABLE "blog_entry" ADD COLUMN "point" geometry(POINT,4326) NOT NULL; CREATE INDEX "blog_entry_point_id" ON "blog_entry" USING GIST ("point"); COMMIT;

Slide 18

Slide 18 text

paulox.net 20tab.com Admin 18 from django.contrib import admin from django.contrib.gis.admin import OSMGeoAdmin from .models import Entry @admin.register(Entry) class EntryAdmin(OSMGeoAdmin): default_lon = 1263000 default_lat = 5542000 default_zoom = 12 # …

Slide 19

Slide 19 text

paulox.net 20tab.com Admin page 19

Slide 20

Slide 20 text

paulox.net 20tab.com Views and urls 20 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 21

Slide 21 text

paulox.net 20tab.com Views and urls - SQL 21 SELECT "blog_entry"."id", "blog_entry"."headline", "blog_entry"."body_text", "blog_entry"."point"::bytea FROM "blog_entry" WHERE "blog_entry"."point" IS NOT NULL

Slide 22

Slide 22 text

paulox.net 20tab.com Template 22

PGDay.IT 2019 Venue

Slide 23

Slide 23 text

paulox.net 20tab.com Javascript 23 var m = L.map('m').setView([44.49, 11.34], 12); # Bologna L.tileLayer('//{s}.tile.osm.org/{z}/{x}/{y}.png').addTo(m); {% for e in object_list %} L.marker({{e.lat_lon}}).addTo(m); {% endfor %}

Slide 24

Slide 24 text

paulox.net 20tab.com Basic map page 24

Slide 25

Slide 25 text

paulox.net 20tab.com Use case 25 ● Coastal properties ● Active since 2014 ● 8 Languages ● ~ 100k active advertisements ● ~ 40 Countries ● 6 Continents

Slide 26

Slide 26 text

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

Slide 27

Slide 27 text

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

Slide 28

Slide 28 text

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

Slide 29

Slide 29 text

paulox.net 20tab.com City - SQL 29 BEGIN; -- -- Create model City -- CREATE TABLE "blog_city" ( "id" serial NOT NULL PRIMARY KEY, "borders" geometry(MULTIPOLYGON,4326) NOT NULL ); CREATE INDEX "blog_city_borders_id" ON "blog_city" USING GIST ("borders"); COMMIT;

Slide 30

Slide 30 text

paulox.net 20tab.com Ad - SQL 30 BEGIN; -- -- Create model Ad -- CREATE TABLE "blog_ad" ( "id" serial NOT NULL PRIMARY KEY, "location" geometry(POINT,4326) NOT NULL ); CREATE INDEX "blog_ad_location_id" ON "blog_ad" USING GIST ("location"); COMMIT;

Slide 31

Slide 31 text

paulox.net 20tab.com RESTful API 31 $ 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 32

Slide 32 text

paulox.net 20tab.com Serializer 32 from rest_framework_gis.serializers import ( GeoFeatureModelSerializer ) from .models import Ad class AdSerializer(GeoFeatureModelSerializer): class Meta: model = Ad geo_field = 'location' fields = ('id',)

Slide 33

Slide 33 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 33

Slide 34

Slide 34 text

paulox.net 20tab.com Views - SQL SELECT "blog_ad"."id", "blog_ad"."location"::bytea FROM "blog_ad" WHERE ( "blog_ad"."location" IS NOT NULL AND "blog_ad"."location" @ ST_MakeEnvelope( 5, 35, 20, 45, 4326 ) ) 34

Slide 35

Slide 35 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 35

Slide 36

Slide 36 text

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

Slide 37

Slide 37 text

paulox.net 20tab.com Demo 37

Slide 38

Slide 38 text

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

Slide 39

Slide 39 text

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

Slide 40

Slide 40 text

paulox.net 20tab.com Acknowledgments 40 Mer et Demeures meretdemeures.com Twentytab info@20tab.com

Slide 41

Slide 41 text

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

Slide 42

Slide 42 text

Paolo Melchiorre www.paulox.net @pauloxnet Thank you! paulox.net 20tab.com