Table of Contents¶
Django WMS Framework¶
The Django WMS Framework is a toolkit that makes it easy to integrate a Web Map Service (WMS) or a x-y-z Tile Map Service into a Django project. Rendering of both vector and raster data formats are supported.
Requirements¶
The processing of spatial data in django-wms relies on MapServer and its python bindings MapScript. Raster data integration depends on the django-raster package. The use of PostGIS as the database backend is required as well, for raster integration PostGIS >= 2.0 is required (see also django-raster package).
Installation¶
Install package with
pip install django-wms
Add “wms” to your INSTALLED_APPS setting like this
INSTALLED_APPS = ( ... 'wms', )
Introduction¶
The structure of django-wms is closely tied to how MapServer works.
The spatial data rendering in django-wms relies on MapScript, the python-bindings for MapServer. The basic funcitonality of django-wms is to use mapscript to dynamically produce directives for MapServer. To render spatial data, MapServer is configured through MapFiles in which global WMS parameters, layers and cartograpy styles are defined.
The concept of MAP directives is translated into a WmsMap
class. LAYER definitions and classes for symbology and cartography are represented in the WmsLayer
class within django-wms. A set of SYMBOL directives for drawing point data are preconfigured as well.
MapScript has its own class definitions for those directives, django-wms simply makes them easy to use in a django project.
Web requests in django-wms are handled through a class based view module WmsView
. The view can be hooked into a url and will take care of handling WMS and TMS requests automatically.
Example¶
To create a mapping service, subclass the django-wms layer, map and view classes and connect them to an existing model in django that has a spatial field (such as Point, Polygon, MultiPolygon or Raster). An example wms_config.py
module could be specified as follows
### wms_config.py
# Load django-wms classes
from wms import maps, layers, views
# Load model with spatial field (Point, Polygon, MultiPolygon)
from myapp.models import MySpatialModel
# Subclass the WmsVectorLayer class and point it to a spatial model.
# Use WmsRasterLayer for rasters
class MyWmsLayer(layers.WmsVectorLayer):
model = MySpatialModel
# Subclass the WmsMap class and add the layer to it
class MyWmsMap(maps.WmsMap):
layer_classes = [ MyWmsLayer ]
# Subclass the WmsView to create a view for the map
class MyWmsView(views.WmsView):
map_class = MyWmsMap
With the WmsView subclass in place, the only thing left to do to create a functional map service is to hook the view into a url. An example url configuration urls.py
could be
### urls.py
# Import the wms view
from myproject.wms_config import MyWmsView
# Add url patterns to setup map services from the view
urlpatterns = patterns('',
# This creates a WMS endpoint
url(r'^wms/$', MyWmsView.as_view(), name='wms'),
# This creates a TMS endpoint
url(r'^tile/(?P<layers>[^/]+)/(?P<z>[0-9]+)/(?P<x>[0-9]+)/(?P<y>[0-9]+)(?P<format>\.jpg|\.png)$',
MyWmsView.as_view(), name='tms'),
)
The django-wms package will automatically detect the first spatial field it can find in MySpatialModel
and create a WMS endpoint from the class based view. If the three arguments x
, y
and z
are found in the urlpattern, the view functions as TMS endpoint.
Layers¶
The first step in creating a map service with django-wms is to setup at least one WmsLayer sublcass. The WmsLayer class represents the LAYER directive from a mapserver map file. Each layer that will be offered as a service end point will be a subclass of WmsLayer.
Layer attributes¶
Spatial field¶
The main component of a WmsLayer is a django model that has at least one spatial field, which has to specified when sublclassing. For example
# In myapp.wmslayers
from wms import layers
from myapp.models import MySpatialModel
class MyLayer(layers.WmsVectorLayer):
model = MySpatialModel
The WmsLayer class auto-detects the first spatial field it can find in the model specified, for models with several spatial fields, the name of the spatial field can be specified explicitly using the geo_field_name
attribute of the class.
Layer name¶
By default, the spatial field name will be used as the layer’s name. For a custom layer name, set the name
attribute of the class.
Where clause¶
This attribute can be used to preselect or filter the data from the model table that is shown on the layer. It can be used analogue to the where clause in a SQL select query such as SELECT FROM ... WHERE
. An example is where='quality>50'
.
Class item¶
The class_item
attribute can be used to specify a column of the specified model as a selector for coloring the map (analogue to the CLASSITEM directive in mapserver layers). To adopt coloring of the map according to the specified class_item field, the cartograpy
attribute has to be specified, as described below.
Cartography¶
The cartography of a layer is defined as an array of dictionaries, where each dictionary has a name, a expression and a color. The name will be the name used in the legend when requested, the expression is a class expression for the class_item
attribute that needs to be specified for the WmsLayer subclass (see above). The expression is SQL like, but not the same. The syntax is defined through the MapServer Expressions. Finally, the color used for this category can be specified as RGB with white spaces as separators (255 0 0
) or as hexadecimal color (#FF0000
).
mycartograpy = [
{
'name': 'Category A',
'expression': '1',
'color': '0 0 255'
},
{
'name': 'Category B',
'expression': '2',
'color': '255 0 0'
}
]
class MySpatialModel(models.Model):
quality = models.FloatField()
geom = models.PolygonField()
class MyLayer(WmsLayer):
model = MySpatialModel
class_item = 'quality'
where = 'quality > 0'
cartograpy = mycartography
Vector layers¶
For vector layers, subclass the layers.WmsVectorLayer
class. Supported spatial vector data types are Points, Lines, Polygons and MultiPolygons. Any of those field types are automatically detected in models or can be specifically set as explained above.
For points and polygons a set of predefined symbols can be used to render them (circle, square, triangle, cross and diagonal), for polygon a hatch fill symbology is available as well (hatch). To use those symbols, add the symbol attribute to the cartography array. For example
mycartograpy = [
{
'name': 'Category A',
'expression': '1',
'color': '0 0 255',
'symbol': 'hatch'
}
]
Custom symbols can be added to display data, see this tutorial for guidance on symbol definitions. After creating custom mapscript symbols, the symbols can be added by subclassing the WmsSymbolSet class and setting an array of symbols in the custom_symbols
attribute. When creating the WmsMap subclass, the custom symbol set needs to be specified as well. Below is a simple example
import mapscript
from wms.symbols import WmsSymbolSet
symb = mapscript.symbolObj('v-line')
symb.type = mapscript.MS_SYMBOL_VECTOR
symb.filled = mapscript.MS_FALSE
line = mapscript.lineObj()
for pnt in [(0,0), (5, 10), (10, 0)]:
line.add(mapscript.pointObj(pnt[0], pnt[1]))
symb.setPoints(line)
symb.sizex = 50
symb.sizey = 50
class MyCustomSymbols(WmsSymbolSet):
custom_symbols = [symb]
class MyWmsMap(maps.WmsMap):
symbolset_class = MyCustomSymbols
Raster layers¶
For vector layers, subclass the layers.WmsRasterLayer
class. Raster layers are supported if the django-raster package is installed. The django-raster package allows basic support for raster data in django, which can then be served through map services with this package.
Currently only x-y-z style TMS endpoints are supported by the raster layer class. The raster layer class will use the x-y-z indexed pyramids build by django-raster to access the data to provide a responsive web-map endpoint even for large rasters.
After uploading a rasterfile to the RasterLayer model, that specific raster layer can be filtered for using the rasterlayer_id or the rasterfile name. Below is an example for a layer using raster data from the RasterTile table, which is generated by the django-raster package through the RasterTile model. The cartography can be equal to the definition in the previous example above.
class MyRasterLayer(WmsLayer):
model = RasterTile
where="filename=\\\'myrasterfile.tif\\\'"
nodata = '0'
cartography = mycartograpy
Maps¶
To create a usable wms map, subclass WmsMap and add WmsLayers to it. The WmsMap class in django-raster represents the MAP directive in the MapServer terminology. WmsMap defines global parameters for a map service endpoint. Only the most important MapServer MAP parameters are implemented in django-wms. The missing parameters still need to be added. The parameters that are currently implemented are listed below.
The WmsMap allone does specifiy global setup, but data has to be added through layers. Each WMS layer that can be accessed through the WmsMap is represented by a WmsLayer subclass. Specify a list of layers classes when creating a WmsLayer subclass. For example
# In myapp.wmsmap
from wms.maps import WmsMap
from myapp.wmslayers import MyFirstLayer, MySecondLayer, MyThirdLayer
class MyMap(WmsMap):
layer_classes = [MyFirstLayer, MySecondLayer, MyThirdLayer]
Map parameters¶
A list of map parameters that can be specified as class attributes when subclassing WmsMap.
Title
The title of a WMS service endpoint. Defaults to
title = 'Django-wms service'
SRS
An array of map projections (srid) can be requested through the map. Defaults to
srs = ['4326', '3086', '3857']
Enable-requests
WMS request types thata are allowed for the map. Defaults to
enable_requests = ['GetMap', 'GetLegendGraphic', 'GetCapabilities']
Legend size
Size in pixels of rendered icons on legends returned by the GetLegendGraphic request. Defaults to
legend_size = (20, 20)
Map Views¶
The third step in creating a map service is to create a class based view by extending the WmsView
class. The WmsView class passes the request from django to mapscript and returns the rendered image. By default, the WmsView acts as a proper Web Map Service view, and expects the corresponding parameters in the html request for it to work. It can also act as Tile Map Service if the urlpattern is configured accordingly (see below).
After creating at least one WmsLayer and adding it to a WmsMap, the WmsView can be setup by sublcassing VmsView and pointing the newly created class to the WmsMap subclass.
# In myapp.wmsviews
from wms.views import WmsView
from myapp.wmsmaps import MyWmsMap
class MyWmsView(WmsView):
map_class = MyWmsMap
Web Map Server¶
With a view prepared, the view can be added to a url pattern by calling the as_view()
method, which will return a proper view function. In the case of a WMS url, the url pattern is straightforward.
# urls.py
from django.conf.urls import patterns,url
from myapp.wmsviews import MyWmsView
urlpatterns = patterns('',
# WMS endpoint
url(r'^wms/$', MyWmsView.as_view(), name='wms'),
)
Since MapServer works with query parameters, the WmsView urlpattern is simple, all additional information is passed on through the GET request parameters. The WmsView will automatically handle those request parameters to render a corresponding image.
Tile Map Server (XYZ)¶
The same view can also be used to create a Tile Map Service. The TMS functionality looks for five arguments it obtains from the urlpattern: layers, x, y, z and format. If those three arguments are present in the urlpattern, the view will act as a tile map service and return the corresponding indexed tile for the layer and format requested. An example url configuration could look like this:
# urls.py
from django.conf.urls import patterns,url
from myapp.wmsviews import MyWmsView
urlpatterns = patterns('',
# TMS endpoint
url(r'^tile/(?P<layers>[^/]+)/(?P<z>[0-9]+)/(?P<x>[0-9]+)/(?P<y>[0-9]+)(?P<format>\.jpg|\.png)$',
MyWmsView.as_view(), name='tile'),
)
In the TMS case, the WmsView calculates the lat/lon bounds for the requested tile behind the scenes and passes that on to the “normal” WmsView mode. The TMS functionality is thus simply a routine that is sandwiched between the normal WmsView part and the request, dynamically calculating tile bounds from the x-y-z indices.
Caching¶
For larger data sets, the dynamic rendering of WMS or TMS request can be quite expensive. Also, for mapping applications, the requested map fragments are often repeated. This is specially true for indexed tiles, as those have short and static urls. WMS requests often have coordinate values that are floating point numbers and tend to vary more.
In any case, it is recommended to use the django-wms package in combination with a caching framework. If a caching backend is configured, caching the map service views is trivial. One simple example is the following, where the reuqested tiles would be cached for 24 hours
from django.views.decorators.cache import cache_page
url(r'^tile/(?P<layers>[^/]+)/(?P<z>[0-9]+)/(?P<x>[0-9]+)/(?P<y>[0-9]+)(?P<format>\.jpg|\.png)$',
cache_page(60 * 60 * 24)(MyWmsView.as_view()), name='tile'),
For more information on how to setup caching with django, see the official documentation.