Upgrade to Pro — share decks privately, control downloads, hide ads and more …

GIS with Python, an easy landing

GIS with Python, an easy landing

Working with PostGIS, Django and Leaflet to deliver a simple map service with Geonames and country borders loaded.

Other Decks in Programming

Transcript

  1. What Self-use GIS service! GeoJSON REST API! Simple map visualization

    ! • https://github.com/jleivaizq/freesquare
  2. PostgreSQL & PostGIS PostgreSQL GIS Extension! PostgreSQL is an Open

    Source RDBMS! http://postgis.net/ www.postgresql.org
  3. Django Web Framework written in Python ! https://docs.djangoproject.com/en/dev/intro/tutorial01/! Great GIS

    libraries included (GeoDjango)! https://docs.djangoproject.com/en/dev/ref/contrib/gis/tutorial/ https://www.djangoproject.com/
  4. API Rest Django “plugin”! Really easy to integrate into Django

    apps! Easy to extend for specific scenarios! django_rest_framework_gis! https://github.com/djangonauts/django-rest-framework-gis Django Rest Framework www.django-rest-framework.org
  5. The GeoNames geographical database covers all countries and contains over

    eight million placenames that are available for download free of charge. http://www.geonames.org/
  6. GADM GADM is a spatial database of the location of

    the world's administrative areas (or adminstrative boundaries) for use in GIS and similar software thematicmapping.org Simpler and more affordable (only country) administrative areas
  7. OpenStreetMaps OpenStreetMap is a map of the world, created by

    people like you and free to use under an open license.
  8. CartoDB Create dynamic maps, analyze and build location aware and

    geospatial applications with your data using the power using the power of PostGIS in the cloud.
  9. templates Django & MVC Pattern models views urls myapp ├──

    __init__.py ├── admin.py ├── models.py ├── templates … ├── tests.py ├── urls.py └── views.py Django flow Django app files $ django-admin.py startapp myapp MVC model view controller
  10. Warm up Requirements Django==1.6.2 psycopg2==2.5.2 djangorestframework==2.3.1 djangorestframework-gis==0.1 django-filter==0.7 - python3

    - pip - virtualenv (virtualenvwrapper) - git :) $ git clone https://github.com/jleivaizq/freesquare.git $ mkvirtualenv -p /usr/local/bin/python3.3 freesquare $ cd freesquare $ pip install -r requirements.txt $ cd freesquare $ python manage.py runserver
  11. Warm up Requirements CREATE ROLE freesquare SUPERUSER CREATEDB LOGIN; create

    database freesquare; create extension postgis; create extension postgis_topology; - python3 - pip - virtualenv (virtualenvwrapper) - git :) $ git clone https://github.com/jleivaizq/freesquare.git $ mkvirtualenv -p /usr/local/bin/python3.3 freesquare $ cd freesquare $ pip install -r requirements.txt $ cd freesquare $ python manage.py runserver
  12. Warm up Project files . ├── freesquare │ ├── settings.py

    │ ├── urls.py │ ├── views.py ├── frontend │ ├── static │ │ ├── css - freesquare.css │ │ └── js - freesquare.js │ ├── templates ─ map.html │ ├── urls.py │ └── views.py └── geo ├── admin.py ├── feeds │ ├── geonames.py │ └── thematicmapping.py ├── models.py ├── serializers.py ├── urls.py └── views.py Model Controller Management Views & Frontend
  13. Models geo/models.py # -*- coding: utf-8 -*- ! from django.contrib.gis.db

    import models ! ! class Place(models.Model): ! ref = models.IntegerField(null=True) name = models.CharField(max_length=200, db_index=True, blank=False) description = models.TextField(max_length=2000, blank=True, default="") feature_code = models.CharField(max_length=10, blank=True) ! location = models.PointField(default='POINT(0 0)', null=True) ! parent = models.ForeignKey('Region', null=True, related_name='places') ! objects = models.GeoManager() ! def __str__(self): return '{} - {}'.format(self.name, self.parent)
  14. Models geo/models.py class Region(models.Model): ! ref = models.IntegerField(null=True) name =

    models.CharField(max_length=200, db_index=True, blank=False) description = models.TextField(max_length=2000, blank=True, default="") feature_code = models.CharField(max_length=10, blank=True) ! border = models.MultiPolygonField(null=True, blank=True) ! parent = models.ForeignKey('Region', null=True, blank=True, related_name='subregions') country = models.ForeignKey('Region', null=True, blank=True, related_name='+') ! objects = models.GeoManager() def __str__(self): parent = ', {}'.format(self.parent) if self.parent else '' return '{} ({}) {}'.format(self.name, self.code, parent) !
  15. Admin geo/admin.py # -*- coding: utf-8 -*- ! from django.contrib

    import admin from django.contrib.gis import admin as gis_admin ! from geo.models import Region, Place ! class PlaceAdmin(gis_admin.OSMGeoAdmin): ! def queryset(self, request): return super(PlaceAdmin, self).queryset(request).prefetch_related('parent') ! point_zoom = 11 list_display = ['name', 'feature_code'] search_fields = ['name'] list_filter = ['feature_code'] raw_id_fields = ('parent', ) ! admin.site.register(Place, PlaceAdmin)
  16. Admin geo/admin.py class RegionAdmin(gis_admin.OSMGeoAdmin): point_zoom = 11 list_display = ['name',

    'parent', 'level', 'description', 'code', 'iso3'] fields = ['name', 'level', 'description', 'code', 'iso3'] search_fields = ['name', 'code'] list_filter = ['feature_code', 'level'] ! admin.site.register(Region, RegionAdmin) ! !
  17. Loading locations ! $ python manage.py geonames -h Usage: ./manage.py

    geonames [options] --load|flush [-- include_countries=... --exclude_countries=... --include_continents=... -- exclude_continents=...--airports=[all|iata] —country_borders=[basic| detailed] $ python manage.py geonames --load=all —include_continents=EU ! $./manage.py thematicmapping detailed
  18. API REST freesquare/urls.py # -*- coding: utf-8 -*-! ! from

    django.conf.urls import patterns, include, url! from django.contrib import admin! from django.contrib.staticfiles.urls import staticfiles_urlpatterns! ! admin.autodiscover()! ! urlpatterns = patterns(! '',! ! url(r'^map/', include('frontend.urls')),! ! url(r'^api/v1/geo/', include('geo.urls')),! ! url(r'^admin/', include(admin.site.urls)),! )! ! urlpatterns += staticfiles_urlpatterns()! !
  19. API REST geo/urls.py # -*- coding: utf-8 -*-! ! from

    django.conf.urls import patterns, url! ! from .views import PlaceListView, RegionListView! ! ! urlpatterns = patterns(! '',! url(r'^places/$',! PlaceListView.as_view(),! name=‘place-list'),! ! url(r'^regions/$',! RegionListView.as_view(),! name='region-list'),! )! ! !
  20. API REST geo/views.py # -*- coding: utf-8 -*-! ! from

    rest_framework.generics import ListAPIView! from rest_framework import filters, status! from rest_framework.response import Response! from rest_framework_gis.filters import InBBOXFilter! ! from .models import Place, Region! from .serializers import PlaceGeoSerializer, RegionGeoSerializer! ! class PlaceListView(ListAPIView):! model = Place! serializer_class = PlaceGeoSerializer! filter_fields = ('name', 'feature_code')! bbox_filter_field = 'location'! filter_backends = (InBBOXFilter, filters.DjangoFilterBackend)
  21. API REST geo/views.py ! ! class RegionListView(ListAPIView):! model = Region!

    serializer_class = RegionGeoSerializer! filter_fields = ('name', 'level', 'feature_code')! bbox_filter_field = 'border'! bbox_filter_include_overlapping = True! filter_backends = (InBBOXFilter, )!
  22. API REST geo/serializers.py from rest_framework_gis import serializers as gis_serializers! from

    rest_framework import serializers! from .models import Place, Region! ! ! class RegionGeoSerializer(gis_serializers.GeoFeatureModelSerializer):! ! class Meta:! model = Region! geo_field = 'border'! fields = ('name', ‘level’, 'feature_code')! ! class PlaceGeoSerializer(gis_serializers.GeoFeatureModelSerializer):! ! class Meta:! model = Place! geo_field = 'location'! fields = (‘name’, ‘feature_code', 'description')!
  23. Demo REST working $ curl http://localhost:8000/api/v1/geo/places/\?feature_code\=AMUS …! {! "features": [!

    {! "geometry": {! "coordinates": [! -1.89241,! 52.99121! ],! "type": "Point"! },! "id": "",! "properties": {! "description": "",! "feature_code": "AMUS",! "name": "Alton Towers"! },! "type": "Feature"! },!
  24. Showing frontend/templates/map.html …! <div id="map"></div>! ! <!-- JQuery 2.1.0 minified

    -->! <script src="http://code.jquery.com/jquery-2.1.0.min.js"></script>! ! <!-- Leaflet 0.7.2 -->! <script src="http://cdn.leafletjs.com/leaflet-0.7.2/leaflet.js"></script>! ! <!-- Bootstrap - Latest compiled and minified JavaScript -->! <script src="//netdna.bootstrapcdn.com/bootstrap/3.1.1/js/ bootstrap.min.js"></script>! ! <!-- Freesquare javascript -->! <script src="/static/js/freesquare.js"></script>! ! </body>!
  25. Showing frontend/static/js/freesquare.js $(document).ready(function() {! map = L.map('map').setView([40.405, -3.60], 11);! !

    L.tileLayer(‘http://{s}.tile.cloudmade.com/.../997/256/{z}/{x}/{y}.png’,{! attribution: 'Map data &copy; <a href="http://! ! ! ! ! ! ! ! ! ! ! openstreetmap.org">OpenStreetMap</a> contributors, <a !! ! ! ! ! ! ! href="http://creativecommons.org/licenses/by-sa/!! ! ! ! ! ! ! ! 2.0/">CC-BY-SA</a>, Imagery © <a href=“http://! ! ! ! ! ! ! cloudmade.com">CloudMade</a>',! maxZoom: 18! }).addTo(map);! ! myLayer = L.geoJson([],{onEachFeature: addPropertiesPopup}).addTo(map);! ! map.on('moveend', function(e) { getMarkers(); })! ! getMarkers();! ! });
  26. Showing frontend/static/js/freesquare.js function getMarkers() {! showing = $('#search select#feature').val();! feature_code

    = $('#search select#feature_code').val();! url = “/api/v1/geo/"+showing+"/?in_bbox="+map.getBounds().toBBoxString();! if (feature_code != "") {! url = url+"&feature_code="+ feature_code;! }! $.get(url, function(data) {! myLayer.addData(data);! });! }! ! function addPropertiesPopup(feature, layer) {! if (feature.properties) {! layer.bindPopup(feature.properties.name+"<br/>"+! feature.properties.feature_code);! }! }
  27. Testing from rest_framework.test import APITestCase from django.core.urlresolvers import reverse from

    django.contrib.gis.geos import Point from .models import Place ! expected = {"type": "FeatureCollection","features": [{"id": "","type": "Feature","geometry": {"coordinates": [ 3.8007, 40.0014 ],”type": "Point"},"properties": { "name": "Madrid","feature_code": "HTL","description": ""}}]} ! ! class SampleTest(APITestCase): ! def test_created_place_should_be_properly_retrieved(self): Place.objects.create(name='Madrid', feature_code='HTL', location=Point(3.8007, 40.0014)) response = self.client.get(reverse('place-list'), {'name': 'Madrid', 'feature_code':'HTL'},format='json') assert(response.data == expected)