Slide 1

Slide 1 text

MAPS WITH DJANGO PAOLO MELCHIORRE ~ @pauloxnet

Slide 2

Slide 2 text

Paolo Melchiorre ~ @pauloxnet 2

Slide 3

Slide 3 text

Paolo Melchiorre ~ @pauloxnet @pauloxnet • CTO @ 20tab • Software engineer • Python developer • Django contributor • Conference speaker Paolo Melchiorre 3 DjangoCon Europe 2019 - Bartek Pawlik (CC BY-NC-SA)

Slide 4

Slide 4 text

Paolo Melchiorre ~ @pauloxnet 1.883 m 0 km 27 km 0 km 4 Paolo Melchiorre (CC BY-SA) Adriatic Sea from the Italian Apennines

Slide 5

Slide 5 text

Paolo Melchiorre ~ @pauloxnet • Static or Dynamic • Interactive or view-only • Raster or Vector tiles • Spatial databases • Javascript library Web maps features 5

Slide 6

Slide 6 text

Paolo Melchiorre ~ @pauloxnet 6 Web mapping “… process of using the maps delivered by Geographic Information Systems (GIS) on the Internet …” — “Web mapping”, Wikipedia

Slide 7

Slide 7 text

Paolo Melchiorre ~ @pauloxnet django 7 William Gottlieb (Public domain) Django Reinhardt at the Aquarium jazz club in New York, NY 1946

Slide 8

Slide 8 text

Paolo Melchiorre ~ @pauloxnet 8 Requirements $ python3 --version Python 3.10.4 $ python3 -m venv ~/.mymap $ source ~/.mymap/bin/activate $ python3 -m pip install django~=4.0

Slide 9

Slide 9 text

Paolo Melchiorre ~ @pauloxnet 9 Creating the ‘mymap’ project $ cd ~/projects $ python3 -m django startproject mymap mymap ├── manage.py └── mymap ├── asgi.py ├── __init__.py ├── settings.py ├── urls.py └── wsgi.py

Slide 10

Slide 10 text

Paolo Melchiorre ~ @pauloxnet 10 Creating the ‘markers’ app $ cd mymap $ python3 -m django startapp markers markers ├── admin.py ├── apps.py ├── __init__.py ├── migrations │ └── __init__.py ├── models.py ├── tests.py └── views.py

Slide 11

Slide 11 text

Paolo Melchiorre ~ @pauloxnet 11 Activating the ‘markers’ app # mymap/settings.py INSTALLED_APPS = [ "django.contrib.admin", "django.contrib.auth", "django.contrib.contenttypes", "django.contrib.sessions", "django.contrib.messages", "django.contrib.staticfiles", "markers", ]

Slide 12

Slide 12 text

Paolo Melchiorre ~ @pauloxnet 12 Adding a template view # markers/views.py from django.views.generic import TemplateView class MarkersMapView(TemplateView): template_name = "map.html"

Slide 13

Slide 13 text

Paolo Melchiorre ~ @pauloxnet 13 Adding the ‘map’ template Markers Map

Slide 14

Slide 14 text

Paolo Melchiorre ~ @pauloxnet 14 Adding ‘markers’ urls # markers/urls.py from django.urls import path from markers.views import MarkersMapView app_name = "markers" urlpatterns = [ path("map/", MarkersMapView.as_view()), ]

Slide 15

Slide 15 text

Paolo Melchiorre ~ @pauloxnet 15 Updating ‘mymap’ urls # mymap/urls.py from django.contrib import admin from django.urls import include, path urlpatterns = [ path("admin/", admin.site.urls), path("markers/", include("markers.urls")), ]

Slide 16

Slide 16 text

Paolo Melchiorre ~ @pauloxnet 2.068 m 0 km 27 km 2 km 16 Paolo Melchiorre (CC BY-SA) Sunrise panorama on the Blockhaus

Slide 17

Slide 17 text

Paolo Melchiorre ~ @pauloxnet • JavaScript library for maps • Free Software • Desktop & Mobile friendly • Light (~42 KB of gzipped JS) • Well documented Leaflet 17

Slide 18

Slide 18 text

Paolo Melchiorre ~ @pauloxnet 18 Updating the ‘map’ template {% load static %} Markers Map

Slide 19

Slide 19 text

Paolo Melchiorre ~ @pauloxnet 19 Adding the ‘map’ CSS /* markers/static/map.css */ html, body { height: 100%; margin: 0; } #map { height: 100%; width: 100%; }

Slide 20

Slide 20 text

Paolo Melchiorre ~ @pauloxnet 20 Adding the ‘map’ JavaScript // markers/static/map.js const copy = '© OpenStreetMap contributors' const url = 'https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png' const osm = L.tileLayer(url, { attribution: copy }) const map = L.map('map', { layers: [osm] }) map.fitWorld();

Slide 21

Slide 21 text

Paolo Melchiorre ~ @pauloxnet 21 Show the empty web map $ python3 -m manage runserver $ python3 -m webbrowser -t localhost:8000/markers/map

Slide 22

Slide 22 text

Paolo Melchiorre ~ @pauloxnet 22

Slide 23

Slide 23 text

Paolo Melchiorre ~ @pauloxnet 2.117 m 0 km 27 km 6 km 23 Paolo Melchiorre (CC BY-SA) Trail signs near the Sella Acquaviva fountain

Slide 24

Slide 24 text

Paolo Melchiorre ~ @pauloxnet 24 GeoDjango django.contrib.gis (v1.0 ~2008) Fields, backends, queries, admin, … Spatialite backend (v1.1 ~2009) Multiple backends (v1.2 ~2010) OpenLayers-based widgets (v1.6 ~2013) GeoJSON serializer (v1.8 ~2015) GeoIP2 Geolocation (v1.9 ~2016)

Slide 25

Slide 25 text

Paolo Melchiorre ~ @pauloxnet • OSGeo library • Free Software • Read/Write geospatial data • Raster/Vector formats • Command line interface GDAL 25

Slide 26

Slide 26 text

Paolo Melchiorre ~ @pauloxnet 26 Installing GDAL $ sudo apt install gdal-bin

Slide 27

Slide 27 text

Paolo Melchiorre ~ @pauloxnet 27 Activating GeoDjango # 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", ]

Slide 28

Slide 28 text

Paolo Melchiorre ~ @pauloxnet • SQLite spatial extension • Vector geodatabase functions • Free Software • Simple architecture • Single file SpatiaLite 28

Slide 29

Slide 29 text

Paolo Melchiorre ~ @pauloxnet 29 Installing SpatiaLite $ sudo apt install libsqlite3-mod-spatialite

Slide 30

Slide 30 text

Paolo Melchiorre ~ @pauloxnet 30 Activating SpatiaLite # mymap/settings.py DATABASES = { "default": { "ENGINE": "django.contrib.gis.db.backends.spatialite", "NAME": BASE_DIR / "db.sqlite3", } }

Slide 31

Slide 31 text

Paolo Melchiorre ~ @pauloxnet 31 Adding the Marker model # markers/models.py from django.contrib.gis.db import models class Marker(models.Model): name = models.CharField(max_length=255) location = models.PointField() def __str__(self): return self.name

Slide 32

Slide 32 text

Paolo Melchiorre ~ @pauloxnet 32 Adding the Marker admin # markers/admin.py from django.contrib.gis import admin from markers.models import Marker @admin.register(Marker) class MarkerAdmin(admin.GISModelAdmin): list_display = ("name", "location")

Slide 33

Slide 33 text

Paolo Melchiorre ~ @pauloxnet 33 Adding some markers $ python3 -m manage makemigrations $ python3 -m manage migrate $ python3 -m manage createsuperuser $ python3 -m manage runserver $ python3 -m webbrowser -t localhost:8000/admin

Slide 34

Slide 34 text

Paolo Melchiorre ~ @pauloxnet 34

Slide 35

Slide 35 text

Paolo Melchiorre ~ @pauloxnet 35 Updating the view # markers/views.py import json from django.core.serializers import serialize from django.views.generic.base import TemplateView from markers.models import Marker class MarkersMapView(TemplateView): template_name = "map.html" def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) markers = Marker.objects.all() context["markers"] = json.loads(serialize("geojson", markers)) return context

Slide 36

Slide 36 text

Paolo Melchiorre ~ @pauloxnet 36 Inserting markers in the template {% load static %} Markers Map {{ markers|json_script:"markers-data" }}

Slide 37

Slide 37 text

Paolo Melchiorre ~ @pauloxnet 37 Generated GeoJSON { "type": "FeatureCollection", "crs": { "type": "name", "properties": { "name": "EPSG:4326" } }, "features": [ { "type": "Feature", "properties": { "name": "Monte Amaro (2793m)", "pk": "1" }, "geometry": { "type": "Point", "coordinates": [14.08591836494682, 42.08632592463349] } } ] }

Slide 38

Slide 38 text

Paolo Melchiorre ~ @pauloxnet 38 Rendering all markers in the map // markers/static/map.js const copy = '© OpenStreetMap contributors' const url = 'https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png' const osm = L.tileLayer(url, { attribution: copy }) const map = L.map('map', { layers: [osm], minZoom: 5 }) const markers = JSON.parse(document.getElementById('markers-data').textContent) const features = L.geoJSON(markers).bindPopup(layer => layer.feature.properties.name) map.addLayer(features).fitBounds(feature.getBounds())

Slide 39

Slide 39 text

Paolo Melchiorre ~ @pauloxnet 39 Show the populated web map $ python3 -m manage runserver $ python3 -m webbrowser -t localhost:8000/markers/map

Slide 40

Slide 40 text

Paolo Melchiorre ~ @pauloxnet 40

Slide 41

Slide 41 text

Paolo Melchiorre ~ @pauloxnet 2.450 m 0 km 27 km 7 km 41 Paolo Melchiorre (CC BY-SA) Trail signs near the Fusco bivouac

Slide 42

Slide 42 text

Paolo Melchiorre ~ @pauloxnet • PostgreSQL extension • Best GeoDjango backend • Spatial data types • Spatial indexing • Spatial functions PostGIS 42

Slide 43

Slide 43 text

Paolo Melchiorre ~ @pauloxnet 43 Installing PostgreSQL C client library $ sudo apt install libpq5

Slide 44

Slide 44 text

Paolo Melchiorre ~ @pauloxnet 44 Activating PostGIS # mymap/settings.py DATABASES = { "default": { "ENGINE": "django.contrib.gis.db.backends.postgis", "HOST": "database", "NAME": "mymap", "PASSWORD": "password", "PORT": 5432, "USER": "postgres", } }

Slide 45

Slide 45 text

Paolo Melchiorre ~ @pauloxnet 45 Requirements # requirements.txt django~=4.0.0 psycopg2~=2.9.0 djangorestframework~=3.13.0 djangorestframework-gis~=1.0.0 django-filter~=22.1.0

Slide 46

Slide 46 text

Paolo Melchiorre ~ @pauloxnet 46 Installing requirements $ python3 -m pip install -r requirements.txt

Slide 47

Slide 47 text

Paolo Melchiorre ~ @pauloxnet 47 Activating Django REST Framework # 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", "rest_framework", "rest_framework_gis", "markers", ]

Slide 48

Slide 48 text

Paolo Melchiorre ~ @pauloxnet 48 Adding the Marker serializer # markers/serializers.py from rest_framework_gis import serializers from markers.models import Marker class MarkerSerializer(serializers.GeoFeatureModelSerializer): class Meta: fields = ("id", "name") geo_field = "location" model = Marker

Slide 49

Slide 49 text

Paolo Melchiorre ~ @pauloxnet 49 Adding the Marker viewset # markers/api_views.py from rest_framework import viewsets from rest_framework_gis import filters from markers.models import Marker from markers.serializers import MarkerSerializer class MarkerViewSet(viewsets.ReadOnlyModelViewSet): bbox_filter_field = "location" filter_backends = (filters.InBBoxFilter,) queryset = Marker.objects.all() serializer_class = MarkerSerializer

Slide 50

Slide 50 text

Paolo Melchiorre ~ @pauloxnet 50 Adding API ‘markers’ urls # markers/api_urls.py from rest_framework import viewsets from rest_framework import routers from markers.api_views import MarkerViewSet router = routers.DefaultRouter() router.register(r"markers", MarkerViewSet) urlpatterns = router.urls

Slide 51

Slide 51 text

Paolo Melchiorre ~ @pauloxnet 51 Updating ‘mymap’ urls # mymap/urls.py from django.contrib import admin from django.urls import include, path urlpatterns = [ path("admin/", admin.site.urls), path("api/", include("markers.api_urls")), path("markers/", include("markers.urls")), ]

Slide 52

Slide 52 text

Paolo Melchiorre ~ @pauloxnet 52 Trying to locate the user // markers/static/map.js const copy = '© OpenStreetMap contributors' const url = 'https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png' const osm = L.tileLayer(url, { attribution: copy }) const map = L.map('map', { layers: [osm], minZoom: 5 }) map.locate() .on('locationfound', e => map.setView(e.latlng, 8)) .on('locationerror', () => map.setView([0, 0], 5)) // …

Slide 53

Slide 53 text

Paolo Melchiorre ~ @pauloxnet 53 Rendering markers incrementally // markers/static/map.js async function load_markers() { const markers_url = `/api/markers/?in_bbox=${map.getBounds().toBBoxString()}` const response = await fetch(markers_url) const geojson = await response.json() return geojson } async function render_markers() { const markers = await load_markers() L.geoJSON(markers).bindPopup(layer => layer.feature.properties.name).addTo(map) } map.on('moveend', render_markers)

Slide 54

Slide 54 text

Paolo Melchiorre ~ @pauloxnet 54

Slide 55

Slide 55 text

Paolo Melchiorre ~ @pauloxnet 55

Slide 56

Slide 56 text

Paolo Melchiorre ~ @pauloxnet 2.793 m 0 km 27 km 14 km 56 Paolo Melchiorre (CC BY-SA) Summit of Monte Amaro (2.793 meters)

Slide 57

Slide 57 text

Paolo Melchiorre ~ @pauloxnet 57 What’s next • Markers customization • Relational filtering • Clustering frontend/backend • Geocoding services • …

Slide 58

Slide 58 text

Paolo Melchiorre ~ @pauloxnet 58 Tips • docs in djangoproject.com • details in postgis.net • source code in github.com • questions in gis.stackexchange.com

Slide 59

Slide 59 text

Paolo Melchiorre ~ @pauloxnet 59 License CC BY-SA 4.0 This work is licensed under a Creative Commons Attribution-ShareAlike 4.0 International License.

Slide 60

Slide 60 text

Paolo Melchiorre ~ @pauloxnet @20tab 20tab 20tab [email protected] 20tab.com 60

Slide 61

Slide 61 text

Paolo Melchiorre ~ @pauloxnet @pauloxnet paolomelchiorre pauloxnet [email protected] paulox.net 61