10

I have to make a large number (hundreds) of distribution maps of species. I have a shapefile that contain the distributions for each species, and for each one, I would like to get a map as an image (jpg, png or otherwise) which contain the name of the species concerned, the legend (to distinguish areas of annual distributions, breeding, non-breeding, etc. ...).

I'd like to use QGIS to do this.

9
  • 1
    Could you specify a bit more about the specifics of the maps? For example, do you want all of these maps to show the same area, like a specific continent or country, or do you want the map extent to change dynamically? Also, do you want all of the range subtypes on one map or are they on multiple maps? Depending on these answers, your problem could be fairly simple or it could require a bit more of a sophisticated approach. A good place to start looking is the Atlas plugin for GIS, or if you have ArcGIS 10 or greater available, ESRI's mapbooks will also help. Commented Feb 22, 2013 at 14:53
  • 1
    Sorry for the lack of details. Yes for all the maps, it will be the same area (Europe). I have one shapefile with all the species and, in the attributes, the corresponding distribution. This shapefile, I can easily split it in different shapefiles (one for each species). At the end, I would like to have for each species one picture, with, at each time, exactly the same area (Europe), the same colours (for example annual distributions in dark green, breeding in light green, non-breeding in blue, etc. ...), the same legend, and as title the name of the specie.
    – Onesime
    Commented Feb 22, 2013 at 15:09
  • I think what you need to do is plan out each step you need to take to make each map, then code the selections and map exports in Python. I know this can be done easily in ArcGIS Python, but I don't know enough about the QGIS Python interface to give much guidance. However, I'm confident that you can make this work with one shapefile. Commented Feb 22, 2013 at 15:23
  • I've done something similar with QGIS using a Python plugin. In my case, my layers were stored in PostGIS, but I think you could do something similar using a shapefile. I'm happy to share my code. PM me. Commented Feb 22, 2013 at 19:39
  • 1
    Can you upload a sample of your data for us to play with.
    – Nathan W
    Commented Feb 22, 2013 at 23:06

2 Answers 2

4

I had a similar requirement and put together a QGIS plugin to generate the maps, based on a shapefile with point localities for all species (it assumes a unique taxon name in the attribute table as the common identifier). My requirements were not as complex - I did not need seasonal information, titles or legend, but it may be a useful starting point for you. For the more complex aspects, you will need to use the map composer. See the PyQGIS cookbook for more on that.

Plugin

The plugin automates the creation of the maps, and allows you to configure extents, resolution and other aspects. It applies the same style to the output as your grid overlay. Currently it only runs on the development version of QGIS (1.9 or later).

Sextante script

Before I made the plugin I worked out the logic using SEXTANTE. This user script should also work in 1.8 (haven't tested it). The distribution style file (.qml) is the style of the output distributions (it ignores the style of the distribution overlay). Currently it places output maps in the temp directory based on your operating system defaults (/tmp in Linux, and various places in Windows - defined by the TEMP environmental variable). You can pretty easily define that yourself in the code though. You will also need to edit the extent and the output resolution in the code (and the background colour if you want a different colour for the sea).

#Definition of inputs and outputs
#==================================
##[Scratch]=group
##all_localities=vector
##taxon_field=field all_localities
##africa_map=vector
##sa_map=vector
##grid_layer=vector
##distribution_style_file=file

#Algorithm body
#==================================
from qgis.core import *
from PyQt4.QtCore import *
from PyQt4.QtGui import *
from sextante.core.QGisLayers import QGisLayers
from sextante.core.SextanteVectorWriter import SextanteVectorWriter
import tempfile
import os

def print_map(taxon,taxon_shp):
    #load taxon layer (necessary?)
    #QGisLayers.load(taxon_shp,name = "taxon",style = distribution_style_file)
    taxon_layer = QgsVectorLayer(taxon_shp,"taxon","ogr")
    QgsMapLayerRegistry.instance().addMapLayer(taxon_layer)
    taxon_layer.loadNamedStyle(distribution_style_file)

    # create image (dimensions 325x299)
    img = QImage(QSize(325,299), QImage.Format_ARGB32_Premultiplied)

    # set image's background color
    color = QColor(192,192,255)   # blue sea
    img.fill(color.rgb())

    # create painter
    p = QPainter()
    p.begin(img)
    p.setRenderHint(QPainter.Antialiasing)

    render = QgsMapRenderer()

    # create layer set
    africa_layer = QGisLayers.getObjectFromUri(africa_map)
    sa_layer = QGisLayers.getObjectFromUri(sa_map)
    #taxon_layer = QGisLayers.getObjectFromUri(taxon_shp)

    lst = []
    lst.append(taxon_layer.id())    
    lst.append(sa_layer.id())
    lst.append(africa_layer.id())

    render.setLayerSet(lst)

    # set extent (xmin,ymin,xmax,ymax)
    rect = QgsRectangle(14.75,-36.00,34.00,-21.00)
    render.setExtent(rect)

    # set output size
    render.setOutputSize(img.size(), img.logicalDpiX())

    # do the rendering
    render.render(p)
    p.end()

    # save image
    #outdir = os.path.dirname(os.path.abspath(output))
    tempdir = tempfile.gettempdir()
    img.save(os.path.join(tempdir,taxon+".png"),"png")

    # remove taxon layer from project
    QgsMapLayerRegistry.instance().removeMapLayers([taxon_layer.id()])

tempdir = tempfile.gettempdir()   
taxa = sextante.runalg('qgis:listuniquevalues', all_localities, taxon_field, None)['UNIQUE_VALUES'].split(";")
for taxon in taxa:
    sextante.runalg('qgis:selectbyattribute', all_localities, taxon_field, 0, taxon)
    sextante.runalg('qgis:selectbylocation', grid_layer, all_localities, 0)
    filename = os.path.join(tempdir,"taxon.shp")    #memory file better?
    sextante.runalg('qgis:saveselectedfeatures', grid_layer, filename)
    print_map(taxon,filename)
6
  • Hi all, Thanks for all your replies. To give you some more elements, it is data coming from BirdLife (an example for specie: birdlife.org/datazone/speciesfactsheet.php?id=2794). Briefly, there is one shapefile with all polygons for all species (so, for some of them, many line for a single specie), and there is an attribute which correspond to the seasonal distribution (with values from 1 to 5 corresponding to different uses), one other for the origin etc.. The legend and title is not indispensable.
    – Onesime
    Commented Mar 13, 2013 at 17:06
  • - I use a country layer in background, just for a easy location. - For the different colour for the different value from the attribute "seasonal", I think for that, a use .qml file is suitable. - Optionally, for the title and legend, I think I have to use a file from composer, if it is too hard, I can add it with an other software. - The operation has to be repeated for all species, so, this corresponds to a selection by attribute, which will be used to given the name of the final picture.
    – Onesime
    Commented Mar 13, 2013 at 17:06
  • I tried the plugin "Atlas", but it seems to be more appropriate for various locations, in my case, it is all the time for the same area: Europe. I tried the plugin "Distribution map manager" which seems to correspond in this point because it is possible to fix the coverage area, but I don't need the process which intersects points with a grid layer as I already have a polygon layer. I tried in ArcGis, but it is quite the same for the QGis Atlas plugin, the solution seems to be to written a python script...
    – Onesime
    Commented Mar 13, 2013 at 17:07
  • So I think I will used Sextante, based on the "rudivonstaden" script (thank you for it!) and to adapt it to my case. Finally, Sorry for these different comments, but there is a character number limit...
    – Onesime
    Commented Mar 13, 2013 at 17:08
  • @Onesime, except for the title and legend, I think you'll be able to adapt the sextante script above to do what you need. You will need to probably remove the selectbylocation step, and add an additional selectbyattribute and saveselectedfeatures step for each season (change grid_layer to all_localities). Then load more .qml files and add append your seasonal shapefiles (top layer appended first). If you're not sure how, I could probably try editing the script above to more-or-less work. Commented Mar 14, 2013 at 19:15
2

I took few time to work on this today. So I made some changes to your script. I don't need to add an additional selectbyattribute and saveselectedfeatures step as I use a .qml files and the Seasonal field is in the same shapefile. Below, you can see what I have done:

#Definition of inputs and outputs
#==================================
##[Scratch]=group
##all_localities=vector
##taxon_field=field all_localities
##seasonal_field=field all_localities
##countries_map=vector
##distribution_style_file=file
##output_folder=folder

#Algorithm body
#==================================
from qgis.core import *
from PyQt4.QtCore import *
from PyQt4.QtGui import *
from sextante.core.QGisLayers import QGisLayers
from sextante.core.SextanteVectorWriter import SextanteVectorWriter
import tempfile
import os

def print_map(taxon,taxon_shp):
#load taxon layer (necessary?)
#QGisLayers.load(taxon_shp,name = "taxon",style = distribution_style_file)
taxon_layer = QgsVectorLayer(taxon_shp,"taxon","ogr")
QgsMapLayerRegistry.instance().addMapLayer(taxon_layer)
taxon_layer.loadNamedStyle(distribution_style_file)

# create image (dimensions 325x299)
img = QImage(QSize(325,299), QImage.Format_ARGB32_Premultiplied)

# set image's background color
color = QColor(221,249,254)   # blue sea
img.fill(color.rgb())

# create painter
p = QPainter()
p.begin(img)
p.setRenderHint(QPainter.Antialiasing)

render = QgsMapRenderer()

# create layer set
countries_layer = QGisLayers.getObjectFromUri(countries_map)
taxon_layer = QGisLayers.getObjectFromUri(taxon_shp)

lst = []
lst.append(taxon_layer.id())    
lst.append(countries_layer.id())
render.setLayerSet(lst)

# set extent (xmin,ymin,xmax,ymax)
rect = QgsRectangle(-11,32,39,71)
render.setExtent(rect)
# set output size
render.setOutputSize(img.size(), img.logicalDpiX())

# do the rendering
render.render(p)
p.end()

#save image
#outdir = os.path.dirname(os.path.abspath(output))
tempdir = output_folder
img.save(os.path.join(tempdir,taxon+".png"),"png")

# remove taxon layer from project
QgsMapLayerRegistry.instance().removeMapLayers([taxon_layer.id()])

tempdir = tempfile.gettempdir()  

taxa = sextante.runalg('qgis:listuniquevalues', all_localities, taxon_field, None)        ['UNIQUE_VALUES'].split(";")

for taxon in taxa:
sextante.runalg('qgis:selectbyattribute', all_localities, taxon_field, 0, taxon)
filename = os.path.join(tempdir,"taxon.shp")    #memory file better?
sextante.runalg('qgis:saveselectedfeatures', all_localities, filename)
print_map(taxon,filename)

If you have any remark or advices to improve it, don't hesitate.

To improve it, the best would be when we select the extent (for exemple Europe), it use this extent to select only species included inside this extent. This, because I obtain maps for all species, even those which are outside europe for example (so I have many empty maps). Do you think that is possible?

Cheers,

Onesime

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.