Essentially I am wanting to produce an atlas based on a categorical field in a point layer.
i.e. I have a point layer of childcare providers with the categorical field "Provision". I've categorized each feature in this field with "After School Club" , "Breakfast Club" etc, and I now want to produce a set of maps that iterate through each category and show only the points for each. One map of after school clubs, one map of breakfast clubs etc. The extents might be subtly different.
I could do it one by one but it seems like there should be a way to produce an atlas based on the extents of each category? (I do feel i'm missing something obvious :) )
Or alternatively is there a way of automating the creation of a polygon layer and using that as a hidden coverage for the atlas?
EDIT: I've made a little progress with this - you can use rule based styling to switch on and off features relevant to the current atlas coverage feature. it actually works fine if all you want to do is show a different set of points. I'm now looking at tying that back to a color scheme and reactive legend.
Answer
I've finally solved this for my purposes so here's the solution I came up with if it helps anyone:
Write a python script (mine at end of this) which essentially does this:
- identify the unique categories in the point layer field of interest
- for each category, select all matching points and establish the extent of this set
- for each extent generate a new polygon in a blank atlas coverage layer with a key attribute "CategoryName"
This gave me the atlas coverage layer with one polygon for each category of interest looking like this:
Configure the atlas and print composer as per normal - leaving only the issue of turning off and on features.
For this it's a little bit of trial and error to work out the exact set of options:
The below expression lets you get the value currently held in the CategoryName field for the current atlas feature
attribute ($atlasfeature, 'CategoryName')
Use this to create rule based styling for the point layer along the lines of
attribute ($atlasfeature, 'CategoryName') = PointCategory AND PointCategory = "RedDots"
I also had a rule to guarantee all others became transparent
attribute ($atlasfeature, 'CategoryName') IS NOT PointCategory
Testing this with the atlas works really well. Finally just use the same approach to manipulate labels shown, make labels dynamic and filter tables appropriately. Ticking the 'filter legend by map content' is also very effective if you don't want all legend items on all maps.
Final atlas set:
Edit - as it was asked for, here's my script:
from PyQt4.QtCore import *
#main script----------------------------------------------
#set up the layer references - you will need to change this
targetlayer=QgsMapLayerRegistry.instance().mapLayer("AtlasExtents20150727154732521")
eylayer = QgsMapLayerRegistry.instance().mapLayer("Early_Years_Providers20150727152919862")
#establish the unique categories
names = getUniqueAttributes(eylayer, 'Mapping_La')
#get a set of boxes
boxset = getBoundings(eylayer, names)
#ensure layer is emptied, then add bounding boxes
deleteBoxes(targetlayer)
createBoxes(targetlayer, boxset)
#end main script----------------------------------------------
#------functions-------#
#gets unique set of attributes - returns a set()
def getUniqueAttributes(layer, fieldname):
values = set()
for feature in layer.getFeatures():
values.add(feature[fieldname])
return values
#quickly selects all points on a layer, given a query
def selectionQuick(layer, queryitem):
layer.removeSelection ()
#hardcoded field name
expr = QgsExpression( "\"Mapping_La\" = '" + queryitem +"'")
it = layer.getFeatures( QgsFeatureRequest( expr ) )
ids = [i.id() for i in it]
layer.setSelectedFeatures( ids )
#for a set of unique items, get bounding boxes
def getBoundings(layer, itemset):
bboxes = {}
for itemname in itemset:
selectionQuick(layer,itemname)
box = layer.boundingBoxOfSelected()
bboxes[itemname] = box
return bboxes
#for a layer create a bunch of boxes
def createBoxes(layer, boxes):
id=0
for boxkey in boxes:
id = id +1
box=boxes[boxkey]
feat = QgsFeature(layer.pendingFields())
geom = QgsGeometry.fromRect(box)
feat.setAttribute('id', id)
#hardcoded field name
feat.setAttribute('CareType', boxkey)
feat.setGeometry(geom)
(res, outFeats) = layer.dataProvider().addFeatures([feat])
def deleteBoxes(layer):
ids = [f.id() for f in layer.getFeatures()]
layer.dataProvider().deleteFeatures( ids )
No comments:
Post a Comment