Django Raster¶
Django-raster provides raster data functionality for Django projects with a PostGIS database backend. It is based on the Django internal raster data type RasterField and GDAL bindings through GDALRaster.
The django-raster repository is hosted on GitHub.
Contents¶
Installation¶
Django-raster requires Django >= 1.9
configured with a PostGIS backend
and the GDAL library. The use of Celery is highly recommended (see
below).
The package is available on PyPI, you can install it with:
pip install django-raster
To integrate the package into Django, add raster
to your
INSTALLED_APPS
setting like this:
INSTALLED_APPS = (
...
'raster',
)
Django-raster has its own url structure (to serve raster data through a
TMS
endpoint for instance). To activate those urls, add the raster
urls to your main urlconf:
urlpatterns = [
...
url(r'^raster/', include('raster.urls')),
]
Finally, migrate your database to create the tables required by django-raster:
python manage.py migrate
Distributed Task Management¶
Django-raster works best with Celery, a distributed task queue manager. Parsing raster files is a process that will time out most of the time if done through regular http requests. If Celery is installed, several long running tasks will be executed asynchronously in django-raster.
If you have Celery configured for your project, add the following to your project’s settings to tell django-raster to use it:
RASTER_USE_CELERY = True
Introduction¶
Django-raster provides high level utilities to work with raster data in Django. It is based on the Django internal GDALRaster object and RasterField datatype.
There are three main components in this package:
- Parser utility to ingest rasters through the admin or the django shell.
- Tile Map Service (TMS) endpoint to render raster data.
- Raster calculator to compute and render raster calculator expressions.
Raster files are stored in a file field attached to RasterLayer
objects. Data can be added by creating raster layers through the admin
interface or the Django shell.
After creating a RasterLayer
object, the raster data will be parsed
automatically. The parsing can be executed asynchronously if Celery is
integrated into the Django project. The raster parser will automatically
extract the data in the raster and store it as PostGIS raster tiles on the
database.
After ingesting the data, raster styles can be defined through the admin interface which are then used to render the data through TMS endpoints. The endpoints can be used in Javascript mapping software such as OpenLayers or Leaflet.
Limitations¶
The main limitation of the django-raster package is that it is focused on single band rasters. For most of the functionality, only the first band in the raster is used. While the tile parser processes and stores all bands of the input rasters, for the TMS endpoints and raster algebra calculations, currently only the first band is used.
Another limitation is that the projection of the raster tiles is fixed to the Web Mercator Projection (EPSG 3857). This is because a large part of online mapping applications use this projection, especially TMS services.
Raster Layers¶
A RasterLayer
is django-raster’s representation of raster files. It
can be used to input raster data into your application. In most cases there is
one RasterLayer
for each raster file.
Storing a Raster File¶
Raster files can be uploaded through the admin interface and are stored in the
RasterLayer
model. Like for any other model, raster layers can also be
created using the Django shell. Each raster file corresponds to one
RasterLayer
object. When adding a new raster file, the following
properties are required:
- Layer name
- Raster file (either as file, http url or s3 url)
- Data type
The datatype tells django-raster how to interpret the pixel values. The choices are “continuous”, “categorical”, “mask”, or “rank ordered”. By default, django-raster extracts all other raster metadata from the input file. The optional input parameters are the following
- Description
- SRID
- Nodata value
- Max zoom value
- Legend
The srid, the nodata value and the maximum zoom value are all determined automatically from the raster properties if left blank. The max zoom value specifies the highest z-x-y zoom level to create tiles for (see below).
The legend attribute is a foreign key to a raster Legend
object. If
the raster legend is specified, it is used as default style when rendering
tiles from that raster. How raster tiles are rendered is described in detail in
the Rendering tiles section.
There are also three boolean flags that allow finer grained control over the raster layer parse process.
- Next higher zoom level
- Build pyramids
- Store reprojected
The raster layer is “snapped” to the next higher zoom level by default. To snap the raster to the next lower zoom level when compared to the true resolution of the data, the “next higher” flag has to be disactivated.
There is a “build pyramids” flag that controls whether the tiles should be created also for the lower zoom levels. This is enabled by default and is recommended in most cases as the tile renderer will expect those tiles to be present.
During parsing, the raster is reprojected to the web mercator projection. This operation is costly and is only done once by default. Django-raster stores a reprojected version in a separate model. To prevent the storage of the reprojected file, the “store reprojected” flag can be disactivated. Note that this will result in less use of storage, but an overhead when parsing, especially for asynchronous parsing where the file will be reprojected by each worker.
Specifying an Url as Source¶
The raster file can be uploaded directly using the raster file field, or passed as a url either to a public http(s) address, or a url like string, pointing directly to an s3 bucket. The http(s) urls are regular web urls.
For the s3 links, the boto3
library is used to directly access an s3 bucket
and download it from there. In this way, private or requester-pays buckets can
be used as source. The credentials for accessing the buckets need to be configured
so that boto3 can see them.
The url should have the following structure
s3://BUCKET_NAME/BUCKET_KEY
for instance,
s3://sentinel-s2-l1c/tiles/12/S/VG/2017/9/15/0/B12.jp2
gets the same file as the following regular http url
http://sentinel-s2-l1c.s3.amazonaws.com/tiles/12/S/VG/2017/9/15/0/B12.jp2
but instead of making a regular web request, it accesses the file using boto3.
Note that for requester pays bucket this might incur charges even if the requester is not the owner of the bucket.
Raster Tile Creation¶
Upon uploading a file, django-raster automatically parses the raster file. The
parser extracts metadata from the raster and its bands, and creates tiles. The
progress or possible errors in parsing is written to a parse log object, which
is exposed on the RasterLayer
admin interface.
The parser automatically creates a tile pyramid in the z-x-y scheme of a TMS.
By default, the highest zoom level for which to create tiles is calculated
automatically from the resolution of the raster. The zoom level is set such
that the resolution of the highest zoom is at least the original resolution.
This behavior can be changed by manually setting the highest zoom level, using
the max_zoom_value
field.
The tiles are stored as RasterTile
objects. The raster data itself is
stored as PostGIS rasters through a RasterField. The tiles are managed
automatically through their parent RasterLayer
object, and do normally
not require any manual user manipulation.
Asynchronous Parsing¶
It is highly recommended to configure the Django application with Celery, to parse the rasters asynchronously. For most raster files, the creation of tiles takes several minutes or even hours to complete. Since the parsing is triggered automatically upon upload, the html requests in the admin will often time out. For more information about how to configure Celery, consult the Installation section.
Rendering tiles¶
After creating and parsing a RasterLayer
, the tiles for that layer can
be accessed through the tiles url. The raster urls have to be added to the
application’s url patterns. Here we assume that the /raster/
base url is used
as proposed in the Installation section.
The tiles url is structured as follows,
/raster/tiles/layer_id/{z}/{x}/{y}.png
where the layer_id
is the primary key of a raster layer. This structure can be
used directly in online mapping software such as OpenLayers or Leaflet. An
example request could look like this: /raster/tiles/23/8/536/143.png
,
returning a tile in png format of the layer with ID pk=23
at zoom level
z=8
and indexes x=536
and y=143
.
By default, the tiles are rendered using simple grayscale. To apply a custom
colormap, a Legend
needs to be assigned to the layer. Raster layers
have an optional foreign key to a Legend object, which can be set through the
admin interface.
Legends¶
Legends are objects that are used to interpret raster data. This includes the cartographic information (colors), but also the semantics of the data (such as names). Legends be created through the admin interface.
A legend is stored as in the Legend
model, which is a collection
of LegendEntry
objects. Each of the entries have an expression for
classifying the data and a semantic meaning of the expression. The semantics
of the expression are stored in the LegendSemantics
model. Here is
an example for a legend representing two temperatures:
>>> from raster.models import Legend, LegendEntry, LegendEntryOrder, LegendSemantics
>>> hot_semantics = LegendSemantics.objects.create(name='Hot')
>>> cold_semantics = LegendSemantics.objects.create(name='Cold')
>>> hot_entry = LegendEntry.objects.create(semantics=cold, expression='0', color='#0000FF')
>>> cold_entry = LegendEntry.objects.create(semantics=hot, expression='1', color='#FF0000')
>>> legend = Legend.objects.create(title='Temperatures')
>>> LegendEntryOrder.objects.create(legend=legend, legendentry=entry, code='1')
>>> legend.json
... '[{"color": "#FFFFFF", "expression": "1", "name": "Earth"}]'
Legend Entries¶
LegendEntry
entries relate semantics and a color value with a range
of pixel values. One entry has a foreign key to a LegendSemantics
object, a color in hex format and an expression.
The expression is a classification of pixels. It describes a range of pixel values in the data. It is either an exact number for discrete rasters, or a formula for continuous rasters:
expression = "3" # Matches all pixels with an exact value of 3
For more complicated expressions, a logical expression can be specified through
a formula. The variable x
represents the pixel value in the formula. Here
are two examples of valid formula expressions:
# Match pixel values bigger than -3 and smaller or equal than 1
expression = "(-3.0 < x) & (x <= 1)"
# Match all pixels with values smaller or equal to one
expression = "x <= 1"
Formula expressions are currently not validated on input. Wrongly specified formulas might lead to errors when rendering raster tiles. Check your formulas if unexpected errors happen on the TMS endpoints.
Continuous Color Schemes¶
The examples above show how to assign discrete pixel value ranges to individual colors. This allows applying discrete color schemes with a limited number of breaks to continuous rasters.
Django-raster also supports applying continuous color scales. Colormaps are
interpreted as continuous color schemes if the keyword continuous
is provided as a key in the colormap dictionary.
The continuous color scheme requires at least two colors, which are
interpolated over the range of pixel values. These colors can be specified
using the from
and to
keywords. A third color can be specified to
force interpolation through another color in the middle of the range. This
intermediate color can be specified using the over
key.
The range over which the colors are interpolated is determined automatically
from the raster layer metadata if possible, and falls back to the range of
the individual tile data. The fallback might result in a visually confusing
color scheme, as the range of pixel values in a single tile may vary
substantially and are not representative of the raster. The range can
therefore also be specified manually using the range
parameter.
An example for a continuous color scheme, which will interpolate all values
from 0
to 100
into colors ranging from red to blue over green is
the following:
{
"continuous": "True",
"from": [255, 0, 0],
"to": [0, 0, 255],
"over": [0, 255, 0],
"range": [0, 100]
}
The keys continuous
, from
and to
are required. The over
key is an optional intermediate color for the interpolation. The range
key specifies the pixel values over which to interpolate. This parameter
is estimated from metadata if not provided in the legend. All other keys
are ignored in the continuous color mode, which is triggered if the
continuous
key is found in the legend.
Overriding the colormap and the legend¶
While a legend and a colormap can be associated with a raster layer objects in the database it is nonetheless possible to overwrite the legend or colormap used to render the tiling. Overriding is done via the following url parameters:
Parameter | Description |
---|---|
legend | Use given legend to render the tiles |
store | One of database , session . Fetch legend from database or session, default is database |
colormap | Overrides the raster layer’s legend colormap. |
Examples¶
If you want to overrides the legend to use MyOtherLegend stored in database you can use the following url for the tiling (assuming 23 is your rasterlayer_id):
/raster/tiles/23/{z}/{x}/{y}.png?legend=MyOtherLegend
If you want to use the legend from the session with the same name as above you can use following one:
/raster/tiles/23/{z}/{x}/{y}.png?legend=MyOtherLegend&store=session
Note
You can set and get a session colormap with the help of shortcuts functions
set_session_colormap()
and get_session_colormap()
.
And finally if you want to provide this custom colormap
{
"1": "#FF0000",
"2": "#00FF00",
"3": "#0000FF"
}
you can do so by using this url:
/raster/tiles/{z}/{x}/{y}.png?colormap=%22%7B1%3A%20'%23FF0000'%2C%202%3A%20'%2300FF00'%2C%203%3A%20'%230000FF'%7D%22
The colormap value is the URIEncoded version of the json stringified colormap object.
Image formats¶
All endpoints (regular tiles, algebra and RGB) support three formats: PNG, JPEG
and TIFF. The different formats can be requested by changing the file extension
in the url. The extensions to use are .png
, .jpg
, and .tif
.
The PNG and JPEG endpoints behave the same way, except that JPEG images do not support an alpha channel. Nodata pixels are rendered in black.
The TIFF endpoint will return the raw data produced from the request in a georeferenced GeoTIFF file. It therefore ignores any of the rendering parameters and simply returns the raw values of the result of the request. This might be useful for analysis purposes, where raster algebra results can be obtained in raw form for further downstream processing.
Raster Algebra¶
Django-raster has raster calculator functionality. The raster calculator allows rendering raster tiles based on algebraic formulas. The use is very similar to a standard z/x/y tile endpoint, but allows the evaluation of a broad range of algebraic expressions applied to existing pixel values. The z/x/y structure can be used directly in online mapping software such as OpenLayers or Leaflet.
Similar to the regular tiles endpoint, the django-raster url patterns need
to be installed for the raster algebra endpoint to work. For the documentation
we assume that the /raster/
base url is used as proposed in the
Installation section.
Raster algebra TMS endpoint¶
The raster algebra url base is used only to specify the z/x/y tile index. All
the rest of the configuration is done through the query parameters. The input
to the raster algebra is a named list of RasterLayer
ids and a formula
for evaluation. These values are passed to the backend through two required
query parameters: layers
and formula
.
The layers query parameter identifies which raster layers to use for evaluation.
It is a comma separated list of variable-name and RasterLayer id pairs. The
variable names are matched with the names in the formula. An example is
layers=a=2,b=4
which will match RasterLayer
with id 2
to variable
name a
and the layer with id 4
with the variable name b
.
The formula query parameter is a string specifying a formula for evaluation.
The formula is an algebraic expression based on the names given to the layers
in the layers
query parameter. The formula has to be an expression that
can be evaluated by the FormulaParser
. It accepts a broad range of
algebraic expressions. The endpoint supports most of the common mathematical
operators (+
, -
, *
, /
, etc), functions (sin
, cos
,
exp
, etc.), and logical operators (&
, !
, >
, =
, etc.).
It also has a set of predefined constants through reserved keywords such
as pi PI
or the Euler number E
.
Putting it all together, an example request to the raster algebra endpoint could look like this:
/raster/algebra/{z}/{x}/{y}.png?layers=a=1,b=3,c=6&formula=log(a+b)*c&legend=5
In addition to the required query parameters: layers
and formula
, a
Legend
id can be specified using the legend
query parameter.
If specified, the legend will be used to interpret the result of the algebra
expression. This is convenient to use predefined colormaps for the endpoint.
Dynamic colormap¶
For a more dynamic rendering scheme, a dynamic colormap can be passed to the
endpoint using the colormap
query parameter. The following request would
color all pixels that result in a value bigger than zero in red, and all other
pixels in green.
/raster/algebra/{z}/{x}/{y}.png?layers=a=1,b=3,c=6&formula=log(a+b)*c&colormap={'x>0':'#FF0000','x<=0':'#00FF00'}
Using specific bands¶
By default, the algebra and rgb endpoints use the first band in each layer
specified. To use a specific band, use a 'variable:band'
syntax, where
variable is the name of the variable, and band is the band index. For example
{'a:3': 23}
would match band 3 of the RasterLayer
with the id
23
to the variable name a
.
Encoding¶
Both the colormap and the formula should be properly url encoded. The examples here are not encoded and should be considered as instructive examples only.
RGB endpoint¶
The algebra endpoint can also be used to render RGB images. For this, only
three query parameters are expected: r
, g
, and b
. If these
three parameters are found in the list of query parameters, and no formula
has been specified, the three input bands are interpreted as RGB channels
of an RGB image. For example to use raster layer with id 1
as red,
id 3
as green and id 6
as blue, the following url can be used:
/raster/algebra/{z}/{x}/{y}.png?layers=r=1,g=3,b=6
If the raw data in the tiles is not already scaled to the range [0, 255], an additional scaling factor can be specified, which will be used to rescale all three bands to the default RGB color range. For instance, the following query would assume that the input bands have values in the range of [5, 10000], and would rescale them to the RGB color space.
/raster/algebra/{z}/{x}/{y}.png?layers=r=1,g=3,b=6&scale=5,10000
An alpha channel can be activated by passing the alpha
query parameter. The
alpha parameter makes all the pixels transparent that have values equal to
0
in all three RGB channels.
For multi band rasters that have the rgb channels as bands and not in separate
files, the band accessor syntax can be used. For instance, if the layer with id
23
is a 3-band RGB raster, the following would render the tiles as RGB
using bands 0
, 1
, and 2
:
/raster/algebra/{z}/{x}/{y}.png?layers=r:0=23,g:1=23,b:2=23
Image Enhancement¶
The algebra and TMS endpoints support image enhancement using the
ImageEnhance
PIL module. The following query parameters arguments
are passed to the corresponding image enhancers. The parameter value
is passed to the enhancer as factor
argument.
Query | Enhancer |
---|---|
enhance_color | ImageEnhance.Color |
enhance_contrast | ImageEnhance.Contrast |
enhance_brightness | ImageEnhance.Brightness |
enhance_sharpness | ImageEnhance.Sharpness |
The following example enhances the contrast of tiles from the RGB endpoint by a factor of 3:
/raster/algebra/{z}/{x}/{y}.png?layers=r=1,g=3,b=6&scale=5,10000&enhance_contrast=3
Pixel Value Lookup¶
Single pixel values for raster algebra expressions can be looked up by coordinates. The endpoint works very similar to the raster algebra TMS endpoint, but instead of Z-X-Y tile indices, coordinates are passed through the url. The query parameters are analogue to the algebra TMS endpoint as described above.
The base url structure is
/raster/pixel/{xcoord}/{ycoord}/
For instance, the following request will return the pixel value of the
requested raster algebra expressino for the coordinates xcoord = -9218229
and ycoord = 3229269
. The coordinates must be provided in the web mercator
projection (EPSG 3857).
/raster/pixel/-9218229/3229269/?layers=a=1,b=3,c=6&formula=log(a+b)*c
Formula parser¶
At the heart of the raster calculator is the FormulaParser
, which
is based on the pyparsing package. The FormulaParser
is a general
purpose formula evaluation class. It is It does not know about rasters and
operates with Numpy arrays directly. To use it, you need a dictionary with
Numpy arrays of equal shape and a formula as string. The keys in the dictionary
are the variable names and are used to match data to variables in the formula.
Here are some examples of how to use the formula parser:
# Import parser and instantiate an instance.
>>> from raster.algebra.parser import FormulaParser
>>> parser = FormulaParser()
# Create a data dictionary and evaluate a simple sum.
>>> data = {'a': range(5), 'b': range(5)}
>>> formula = 'a + b'
>>> parser.evaluate(data, formula)
... array([0, 2, 4, 6, 8])
# Use the sin function and divide by b.
>>> formula = 'sin(a) / b'
>>> parser.evaluate(data, formula)
... array([ nan, 0.84147098, 0.45464871, 0.04704, -0.18920062])
# Use a logical array.
>>> data.update({'a_new_var': [True, False, False, True, False]})
>>> formula = '!a_new_var * a + 3'
>>> parser.evaluate(data, formula)
... array([ 3., 4., 5., 3., 7.])
# Use the PI keyword in a formula.
>>> formula = 'a * PI'
>>> parser.evaluate(data, formula)
>>> array([0. , 3.14159265, 6.28318531, 9.42477796, 12.56637061])
Raster algebra parser¶
The RasterAlgebraParser
class is a wrapper that can be used to apply
the generic formula parser to raster objects directly. The use is identical
to the generic case except that the objects in the data dictionary are expected
to be :class:GDALRaster objects. The data arrays are extracted from the raster
objects automatically and are passed to the formula parser. The result array is
converted into a GDALRaster
before returning.
By default, the first band is used for calculations, to specify a specific band
to be used the syntax is 'variable:band'
, where variable is the name of the
variable, and band is the band index. For example {'a:3': rst}
would match
band 3 of the GDALRaster rst
to the variable name a
.
Here is a complete example for how to use the RasterAlgebraParser
.
>>> from raster.algebra.parser import RasterAlgebraParser
>>> parser = RasterAlgebraParser()
>>> base = {
>>> 'datatype': 1,
>>> 'driver': 'MEM',
>>> 'width': 2,
>>> 'height': 2,
>>> 'srid': 3086,
>>> 'origin': (500000, 400000),
>>> 'scale': (100, -100),
>>> 'skew': (0, 0),
>>> 'bands': [
>>> {'nodata_value': 10},
>>> {'nodata_value': 10},
>>> {'nodata_value': 10},
>>> ],
>>> }
>>> base['bands'][0]['data'] = range(20, 24)
>>> base['bands'][1]['data'] = range(10, 14)
>>> rast1 = GDALRaster(base)
>>> base['bands'][0]['data'] = [1, 1, 1, 1]
>>> rast2 = GDALRaster(base)
>>> base['bands'][0]['data'] = [30, 31, 32, 33]
>>> base['bands'][0]['nodata_value'] = 31
>>> rast3 = GDALRaster(base)
>>> data = dict(zip(['x:1', 'y:0', 'z'], [rast1, rast2, rast3]))
>>> rst = parser.evaluate_raster_algebra('x*(x>11) + 2*y + 3*z*(z==30)')
>>> rst.bands[0].data()
... array([[ 10., 10.],
... [ 14., 15.]])
Keywords, Operators and Functions¶
The following tables list the available operators, functions and reserved
keywords from the FormulaParser
and the corresponding raster
calculator.
Keyword | Symbol |
---|---|
Euler Number | E |
Pi | PI |
True Boolean | TRUE |
False Boolean | FALSE |
Null | NULL |
Infinite | INF |
Operator | Symbol |
---|---|
Add | + |
Substract | - |
Multiply | * |
Divide | / |
Power | ^ |
Equal | == |
Not Equal | != |
Greater | > |
Greater or Equal | >= |
Less | < |
Less or Equal | <= |
Logical Or | | |
Logial And | & |
Logcal Not | ! |
Fill Nodata Values | ~ |
Unary And | + |
Unary Minus | - |
Unary Not | ! |
Function | Symbol |
---|---|
Sinus | sin |
Cosinus | cos |
Tangens | tan |
Natural Logarithm | log |
Exponential Function | exp |
Absolute Value | abs |
Integer | int |
Round | round |
Sign | sign |
Minimum | min |
Maximum | max |
Mean | mean |
Median | median |
Standard Deviation | std |
Sum | sum |
Settings¶
A list of available settings to customize django-raster’s behavior.
Asynchronous raster parsing¶
Determines whether to use celery tasks for parsing raster layers. It is highly recommended to configure celery, as raster parsing can take quite a while and the parsing through normal web requests will often timed out, even for medium sized raster.
RASTER_USE_CELERY = False
Parser working directory¶
Use this to specify a custom working directory used by the django-raster package when parsing raster files. This is where intermediate files are stored. Defaults to the normal temporary directory of the machine.
RASTER_WORKDIR = None
All in one parse task¶
For some applications where the size of the rasters is small, the distributed
raster parsing might have more overhead than gain. The distributed parsing can
be disactivated with the following setting. If it is set to True
, rasters
are parsed in single celery tasks.
RASTER_PARSE_SINGLE_TASK = True
Parse Batch Size¶
During parsing of a raster, tiles are written to the database in batches, using
the bulk_create
method. The size of each batch in the loop can be controlled
by using the setting below. Defaults to 500 tiles.
RASTER_BATCH_STEP_SIZE = 500
S3 Endpoint URL¶
Set the S3 compatible endpoint used when retrieving raster tile sources from S3.
RASTER_S3_ENDPOINT_URL = "http://localhost:4572"
API Reference¶
Shortcuts¶
-
set_session_colormap
(session, key, colormap)¶ Store the colormap in the user session.
-
get_session_colormap
(session, key)¶ Get the colormap form a legend stored in the user session and identified by key.
Raster Utilities¶
Django-raster hosts some utilities that ease the interaction with raster data.
The functions are located in raster.utils
and raster.tiles.utils
.
-
pixel_value_from_point
(raster, point, band=0)¶ Return the pixel value for the coordinate of the input point from selected band.
The input can be a point or tuple, if its a tuple it is assumed to be a pair of coordinates in the reference system of the raster. The band index to be used for extraction can be specified with the
band
keyword.Example:
# Create a raster. >>> raster = GDALRaster({ 'width': 5, 'height': 5, 'srid': 4326, 'bands': [{'data': range(25)}], 'origin': (2, 2), 'scale': (1, 1) }) # Create a point at origin >>> point = OGRGeometry('SRID=4326;POINT(2 2)') # Get pixel value at origin. >>> pixel_value_from_point(raster, point) ... 0 # Get pixel value from within the raster, using coordinate tuple input. >>> pixel_value_from_point(raster, (2, 3.5)) ... 5