Page 2 of 64 (1280 posts)

  • talks about »

Tags

Last update:
Mon Nov 24 02:50:07 2014

A Django site.

QGIS Planet

QGIS atlas on non geometry tables

This is proof that no matter how close you are to a project you can still miss some really cool stuff that you never knew or considered was possible.

The problem to solve:

You have a CSV with a row of colours. Each row should be a new map and each column is the colour for that feature.

This is example of that kind of input

A       B
#93b2f3 #FF0000 
#dfbdbb #FF0000
#f9d230 #FF0000

This questhion was asked on GIS.SE this morning. When I first saw it I had no idea it was even possible, I was thinking along the same lines as the person asking, that it would have to be done with Python. Not hard, but a lot harder then something built in and I put it in the too hard basket. I thought the atlas can almost do that, almost but not really.

Well almost was wrong. It can.

Note: You will need QGIS 2.5 (2.6 when released) for this to work

Lets make some cool maps! (and go to GIS.SE and upvote Nyalls answer)

First open your vector layer and the CSV. Don't worry about style just yet, we will do it later.

Create a composer and add your map.

Here comes the first part of the trick.

Enable Atlas and set the coverage layer to the CSV layer. Wait? What? That doesn't make any sense. If you think about it for a while it does. We need a map for each row (or "feature") in the CSV and atlas does just that.

atlas_colours.png

How do we style the features? Well here is the other part of the trick. In 2.6 there is a magic expression function that returns a field value from another feature. And it's as simple as attribute( $atlasfeature , 'A' ) - give me the attribute from the current atlas feature for field 'A'. Simple.

First we categorize our features so we have a symbol for each feature. I'm using a sample layer I have but you can understand how this works. The first feature is A and the other is B, etc, etc

render.png

Now to use another awesome feature of QGIS. The data defined symbol properties (and labels too). Change each symbol and define the colour data defined property. Using attribute( $atlasfeature , 'A' ) for the first one and attribute( $atlasfeature , 'B' ) for the second.

atlas_feature.png

That is it. Now jump back over to your composer and enable Atlas preview.

atlas1.png

atlas2.png

Bam! Magic! How awesome is that!

Now my other thought was. "Ok cool, but the legend won't update". I should learn by now not to assume anything. The legend will also update based on the colours from the feature.

atlas1_legend.png

atlas2_legend.png

How far can we take this. What if you need the label to match the colour. Simple just make the label text look like this:

<h1 style='color:[% "A" %]'>This is the colour of A</h1>

atlas1_label.png

Heaps of credit to Nyall and the others who have added all this great stuff to the composer, atlas, and the data defined properties. It's not something that you will do every day but it's great to see the flexibility of QGIS in these situations.

You can even make the background colour of the page match the atlas feature

atlas1_back.png

but don't do that because people might think you are mad ;)

Labels as text in SVG exports

Today’s post is inspired by a recent thread on the QGIS user mailing list titled “exporting text to Illustrator?”. The issue was that with the introduction of the new labeling system, all labels were exported as paths when creating an SVG. Unnoticed by almost everyone (and huge thanks to Alex Mandel for pointing out!) an option has been added to 2.4 by Larry Shaffer which allows exporting labels as texts again.

To export labels as text, open the Automatic Placement Settings (button in the upper right corner of the label dialog) and uncheck the Draw text as outlines option.

Screenshot 2014-09-20 21.03.26

Note that we are also cautioned that

For now the developers recommend you only toggle this option right
before exporting
and that you recheck it after.

Alex even recorded a video showcasing the functionality:


Registration is open

Registration is open now for the Scottish QGIS user group.  You can register here:

https://www.eventbrite.co.uk/e/qgis-uk-scottish-user-group-tickets-13092023595

The planned agenda for the day is as follows:

  • arrive for coffee 9.00
  • welcome and intro 9.30
  • workshops 10.00 – 12.00
  • lightning talks 12.00 – 12.30
  • lunch 12.30 – 13.30
  • afternoon presentations 13.30 – 16.00
  • close
  • geobeers

There is a great line up of talks and after feedback from the last user group there are now two workshops in the morning for you to choose from.

  • Workshop 1: QGIS plugin development with Python
  • Workshop 2: Using the QGIS Processing module
  • Michael Spencer – Raster time series analysis using FOSS4G
  • Heikki Versanto – Visualising population demographics in 3D
  • Simon Willcocks – Using PgRouting with OS ITN in QGIS
  • Ross McDonald – Cartographic experiments in QGIS
  • Mag Low – Rendering OS MasterMap buildings using QGIS2threeJS
  • Paul Weedon – PostGIS, QGIS and Associated Street Data
  • Lightning talks – a few minutes to tell us about, well, anything really.

Thanks to EDINA, thinkWhere and Ordnance Survey for helping to make the day happen!


Exporting QGIS symbols as images

Ever wanted to export your QGIS symbols as images? Yes. Well here is some Python code that will let you do just that:

from PyQt4.QtCore import QSize
from PyQt4.QtGui import QImage, QPainter

style = QgsStyleV2.defaultStyle()
names = style.symbolNames()
size = QSize(64, 64)

for name in names:
    symbol = style.symbol(name)
    if not symbol.type() == QgsSymbolV2.Marker:
        continue
    
    image = QImage(size, QImage.Format_ARGB32_Premultiplied)
    image.fill(0) 
    painter = QPainter(image)
    symbol.drawPreviewIcon(painter, size)
    painter.end()
    image.save(r"C:\temp\{}.png".format(name), "PNG")

Or in 2.6 it's even easier:

from PyQt4.QtCore import QSize
from PyQt4.QtGui import QImage, QPainter

style = QgsStyleV2.defaultStyle()
names = style.symbolNames()
size = QSize(64, 64)

for name in names:
    symbol = style.symbol(name)
    if not symbol.type() == QgsSymbolV2.Marker:
        continue
        
    image = symbol.asImage(size)
    image.save(r"C:\temp\{}.png".format(name), "PNG")

Bam!

symbols.png

Why? Because we can.

Exporting QGIS symbols as images

Ever wanted to export your QGIS symbols as images? Yes. Well here is some Python code that will let you do just that:

from PyQt4.QtCore import QSize
from PyQt4.QtGui import QImage, QPainter

style = QgsStyleV2.defaultStyle()
names = style.symbolNames()
size = QSize(64, 64)

for name in names:
    symbol = style.symbol(name)
    if not symbol.type() == QgsSymbolV2.Marker:
        continue

    image = QImage(size, QImage.Format_ARGB32_Premultiplied)
    image.fill(0) 
    painter = QPainter(image)
    symbol.drawPreviewIcon(painter, size)
    painter.end()
    image.save(r"C:\temp\{}.png".format(name), "PNG")

Or in 2.6 it's even easier:

from PyQt4.QtCore import QSize
from PyQt4.QtGui import QImage, QPainter

style = QgsStyleV2.defaultStyle()
names = style.symbolNames()
size = QSize(64, 64)

for name in names:
    symbol = style.symbol(name)
    if not symbol.type() == QgsSymbolV2.Marker:
        continue

    image = symbol.asImage(size)
    image.save(r"C:\temp\{}.png".format(name), "PNG")

Bam!

Alt Text

Why? Because we can.

QGIS OpenLayers alternative: ArcGIS online basemap

The QGIS Ireland user group posted a method of adding the ArcGIS Online global raster basemap to QGIS to use as an alternative to the openLayers plugin:

http://ieqgis.wordpress.com/2014/08/09/adding-esris-online-world-imagery-dataset-to-qgis/


Gary Sherman wins the Sol Katz award

This year Gary Sherman won the annual Sol Katz award. To quote the OSGEO page about the award:

The Sol Katz Award for Geospatial Free and Open Source Software (GFOSS) is awarded annually by OSGeo to individuals who have demonstrated leadership in the GFOSS community. Recipients of the award will have contributed significantly through their activities to advance open source ideals in the geospatial realm. The hope is that the award will both acknowledge the work of community members, and pay tribute to one of its founders, for years to come.

A couple of years ago I did an interview with Gary about the beginning of QGIS:

and part2:

I am so pleased that the Sol Katz award committee has seen fit to present this award to Gary. The fact that Gary created QGIS and open sourced its code has had a profound effect on many people’s lives – especially my own. Participating in QGIS for the last decade+ has immeasurably improved my own life and enabled me to eke out a comfortable living doing something I love. Hardly a day goes without me receiving an email in my inbox from someone in a far flung place telling me how they are using QGIS and how it is making their world a better place.

Presenting this award to Gary is a long overdue recognition and ‘thank you’ to the person who started a project that has become the rally point for an incredible, diverse and  friendly bunch of people, and a daily use tool for hundreds of thousands of users how there who may not even know who Gary is. QGIS rocks Gary, and so do you!

Here is Gary’s acceptance speech:

 

 

Slides FOSS4G 2014

Slides from our presentations at FOSS4G 2014 in Portland/Oregon:

@PirminKalberer

Selective data removal in an elevation map by means of floodfilling

Do you also sometimes get maps which contain zero (0) rather than NULL (no data) in some parts of the map? This can be easily solved with “floodfilling”, even in a GIS.

My original map looks like this (here, Trentino elevation model):

The light blue parts should be no data (NULL) rather than zero (0)...

Now what? In a paint software we would simply use bucket fill but what about GIS data? Well, we can do something similar using “clumping”. It requires a bit of computational time but works perfectly, even for large DEMs, e.g., all Italy at 20m resolution. Using the open source software GRASS GIS 7, we can compute all “clumps” (that are many for a floating point DEM!):

# first we set the computational region to the raster map:
g.region rast=pat_DTM_2008_derived_2m -p
r.clump pat_DTM_2008_derived_2m out=pat_DTM_2008_derived_2m_clump

The resulting clump map produced by r.clump is nicely colorized:

Clumped map derived from DEM (generated with r.clump)

As we can see, the area of interest (province) is now surrounded by three clumps. With a simple map algebra statement (r.mapcalc or GUI calculator) we can create a MASK by assigning these outer boundary clumps to NULL and the other “good” clumps to 1:

r.mapcalc "no_data_mask = if(pat_DTM_2008_derived_2m_clump == 264485050 || \
   pat_DTM_2008_derived_2m_clump == 197926480 || \
   pat_DTM_2008_derived_2m_clump == 3, null(), 1)"

This mask map looks like this:

Mask map from all clumps except for the large outer clumps

We now activate this MASK and generate a copy of the original map into a new map name by using map algebra again (this just keeps the data matched by the MASK). Eventually we remove the MASK and verify the result:

# apply the mask
r.mask no_data_mask
# generate a copy of the DEM, filter on the fly
r.mapcalc "pat_DTM_2008_derived_2m_fixed = pat_DTM_2008_derived_2m"
# assign a nice color table
r.colors pat_DTM_2008_derived_2m_fixed color=srtmplus
# remove the MASK
r.mask -r

And the final DEM is now properly cleaned up in terms of NULL values (no data):

DEM cleaned up for no data

Enjoy.

The post Selective data removal in an elevation map by means of floodfilling appeared first on GFOSS Blog | GRASS GIS Courses.

Share and manage your Data with QGIS Cloud and WFS-T

A lot of people are using QGIS Cloud as a service with ready to use QGIS webclient. It’s very easy to publish data and share maps in this way. But QGIS Cloud has more power under the hood. A not so obvious feature of QGIS Cloud is the option to share your data via Web Feature Service (WFS) and manage them via Web Feature Service Transactional (WFS-T). “The basic Web Feature Service allows querying and retrieval of features. A transactional Web Feature Service (WFS-T) allows creation, deletion, and updating of features” (Wikipedia). With WFS-T you have full access to your vector data for editing over the web. Since QGIS Server includes WFS-T functionality, you can manage and edit your data served by QGIS Cloud from every client supporting WFS-T. In addition, with QGIS Cloud Pro you have the option to control access to your published WFS.

How to setup a QGIS Cloud WFS-T in few steps:

  1. Setup a QGIS Project containing the data you like to pubish as WFS-T

  2. Load local vector data of your choice to your project.

  3. Define vector layers you wish to publish and set the appropriate settings for them in the following way:
    • open the Project Properties -> OWS Server tab.
    • scroll to the WFS-Capabilities section and setup the appropriate settings. Tick Published, Update, Insert and Delete for every layer you want to publish.

  • additionally you can set the published fields of every layer in the Layer Properties -> Fields tab.

  • Publish the project on QGIS Cloud.
    • save the project. (If you don’t have installed the QGIS Cloud plugin, than install it from the official QGIS Plugin Repository)
    • open the QGIS Cloud plugin and log in your QGIS Cloud account. (If you don’t have a QGIS Cloud account, sign up a new account).
    • upload the local data to your QGIS Cloud database (if you don’t have a QGIS Cloud database, create one from the QGIS Cloud plugin).
    • publish the project via QGIS Cloud plugin.
    • that’s it!

Have a look at the Services tab of the QGIS Cloud Plugin. There you will find the URL for Public WMS. Your just created WFS has the same URL. Now you can start working with WFS and WFS-T.

Working with WFS-T in QGIS Desktop

You can access your WFS-T with QGIS or any other client which supports WFS and WFS-T. As an example here we show how to access WFS with QGIS Desktop:

  1. Open the QGIS WFS Server connections dialog (Layer -> Add WFS Layer … ).
  2. Add a new connection
  3. Give the connection a name of your choice and add the above created URL
  4. Click connect and you will see the just published WFS layers
  5. Add one or more of them to your project

Thus you have set the Update, Insert and Delete options for the WFS, these layers can be edited in QGIS like any other editable layer.

All the services published under QGIS Cloud Free are public and accessible by everyone. If you need resctricted access , you can order the QGIS Cloud Pro plan.

Follow @QGISCloud on Twitter for QGISCloud related news and infos.

Sourcepole at FOSS4G 2014 in Portland

In one week, the 2014 FOSS4G Conference will start in Portland/Oregon. Sourcepole supports this major event as a bronze sponsor.

Our conference contributions:

Workshop presented by Horst Düster (@moazagotl)

  • Tuesday afternoon: QGIS Plugin Development with PyQt4 and PyQGIS

Presentations by Pirmin Kalberer (@PirminKalberer)

  • Thursday, Session 2, Track 7, 13:00 - 13:25: State of QGIS Server
  • Thursday, Session 2, Track 7, 13:30 - 13:55: From Nottingham to PDX: QGIS 2014 roundup
  • Thursday, Session 3, Track 6, 16:25 - 13:25: Easy ETL with OGR

Meet Pirmin and Horst at Sourcepole’s exhibition booth and have a look at our latest products.

We’re looking forward to meet you in Portland next week!

Follow @Sourcepole for selected QGIS news and other Open Source Geospatial related infos.

Scottish QGIS User Group – 21 October, Edinburgh

The next QGIS user group meeting in Scotland is happening on 21st October 2014.
It is being held in the School of Informatics at Edinburgh University.  For more info about the venue: http://www.ed.ac.uk/schools-departments/informatics/about/location

This is your chance to offer a short talk or presentation or workshop so we can build an exciting programme for the day.  The final programme and agenda will be released closer to the date.  Please let me know through the contact form or comments or twitter (@mixedbredie) if you have a presentation or talk you would like to share.

Ross


WMS Legend Plugin for Leaflet

This weekend I was updating our map gallery at http://maps.kartoza.com and I wanted to have WMS legends in my maps. The maps are mostly generated using QGIS server which also produces a nice looking graphic for its getLegendGraphic requests. Since Leaflet does not seem have a legend control out of the box, I wrote a small leaflet plugin to do it.

Leaflet WMS Legend Plugin

In the future I may extend the control to automatically fetch getLegendGraphics from all loaded WMS layers, but for now it simply takes a complete legend graphic URI as parameter.

Leaflet is a great web mapping client and extending it with little plugins is very easy to do. If you want to use the plugin I wrote, head over to the plugin repository and give it a try!

Sourcepole Kursprogramm Herbst 2014

Im November 2014 bietet Sourcepole wieder sein kompetentes Kursprogramm rund um alle GDI Komponenten an. Zu allen Kursen gehört umfangreiches Kursmaterial, Mittagessen und Kaffepausen. Bei Buchung eines Grundkurses und dem darauf folgenden Aufbaukurs erhalten die Teilnehmer Rabatt auf den Kurspreis.

Geo-Datenbank:

  • PostgreSQL / PostGIS Einführung (3. - 4. November 2014)
  • PostgreSQL / PostGIS für Fortgeschrittene (5. November 2014)

Desktop GIS

  • QGIS 2.4 / Enterprise Desktop Grundkurs (10. - 11. November 2014)
  • QGIS 2.4 / Enterprise Desktop für Power User (12. November 2014)

GDI

  • Verteilte GDI mit der QGIS Suite und PostgreSQL (20. November 2014)

QGIS Programmierung

  • QGIS 2.4 / Enterprise Plugin Entwicklung mit PyQt4 und PyQGIS (17. - 18. November 2014)

Informationen zu den Kursen und die Online Anmeldung finden Sie im Kursprogramm

Wir freuen uns darauf Sie in Zürich begrüssen zu können.

Fundraising for Eloise and Heartfelt

The death of our daughter was one of the hardest things my wife and I have ever had to deal with. It's not something that I wish anyone ever have to experience and feel really sorry for those who have had to do it many times. There is something really raw about losing your own flesh and blood. It cuts deep, really really deep. There are really no words to describe the emptiness that you feel, or the feelings that follow after the event. Even with all the pain of loosing a child there is a great Australian service that helps to capture some of the final monents. The service is called Heartfelt and we used them for Ellie.

Heartfelt is a great free service that provides a photo session, including editing and prints (hard and digital) after, in the last and final days. This is the quote from their site:

Heartfelt is a volunteer organisation of professional photographers from all over Australia dedicated to giving the gift of photographic memories to families that have experienced stillbirths, premature births, or have children with serious and terminal illnesses.

Heartfelt is dedicated to providing this gift to families in a caring, compassionate manner.

All services are provided free of charge

Pretty impressive stuff. The last thing you want to have to do in a time like that is shell out for photos when you have other pressing issues.

As Ellie's 1st Birthday is coming in up October Stace and I would love to raise enough money to donate a camera pack to a hospital though Heartfelt in Elly's name. We have created a fundraiser page in her name at: http://www.mycause.com.au/page/79669/eloises1stbirthdayheartfelt in order collect dontations for anyone who would like to help.

Camera packs can be donated to a hospital to allow staff at the hospital to capture photos if Heartfelt can't make it. The bonus is that Heartfelt will still edit and print the photos. How bloody awesome is that! More info on the camera packs is at: http://www.mycause.com.au/page/79669/eloises1stbirthdayheartfelt

We would be greatful for any donations, big or small, so we can donate a camera pack in Elly's name.

We love and miss you a lot Eloise.

Fundraising for Eloise and Heartfelt

The death of our daughter was one of the hardest things my wife and I have ever had to deal with. It's not something that I wish anyone ever have to experience and feel really sorry for those who have had to do it many times. There is something really raw about lossing your own flesh and blood. It cuts deep, really really deep. There are really no words to describe the emptiness that you feel, or the feelings that follow after the event. Even with all the pain of lossing a child there is a great Australian service that helps to capture some of the final monents. The service is called Heartfelt and we used them for Elly.

Heartfelt is a great free service that provides a photo session, including editing and prints (hard and digital) after, in the last and final days. This is the quote from their site:

Heartfelt is a volunteer organisation of professional photographers from all over Australia dedicated to giving the gift of photographic memories to families that have experienced stillbirths, premature births, or have children with serious and terminal illnesses.

Heartfelt is dedicated to providing this gift to families in a caring, compassionate manner.

All services are provided free of charge

Pretty impressive stuff. The last thing you want to have to do in a time like that is shell out for photos when you have other pressing issues.

As Elly's 1st Birthday is coming in up October Stace and I would love to raise enough money to donate a camera pack to a hospital though Heartfelt in Elly's name. We have created a fundraiser page in her name at: http://www.mycause.com.au/page/79669/eloises1stbirthdayheartfelt in order collect dontations for anyone who would like to help.

Camera packs can be donated to a hospital to allow staff at the hospital to capture photos if Heartfelt can't make it. The bonus is that Heartfelt will still edit and print the photos. How bloody awesome is that! More info on the camera packs is at: http://www.mycause.com.au/page/79669/eloises1stbirthdayheartfelt

We would be greatful for any donations, big or small, so we can donate a camera pack in Elly's name.

We love and miss you a lot Eloise.

5 meter elevation model of Vienna published

A while ago I wrote about the 5 meter elevation model of the city of Vienna. In the meantime the 5 meter model has been replaced by a 10 meter version.

For future reference, I’ve therefore published the 5 meter version on opendataportal.at.

details from the Viennese elevation model

details of the Viennese elevation model

I’ve been using the dataset to compare it to EU-DEM and NASA SRTM for energy estimation:
A. Graser, J. Asamer, M. Dragaschnig: “How to Reduce Range Anxiety? The Impact of Digital Elevation Model Quality on Energy Estimates for Electric Vehicles” (2014).

I hope someone else will find it useful as well because assembling the whole elevation model was quite a challenge.

mosaicking the rasterized WFS responses

mosaicking the rasterized WFS responses


Rendering a brain CT scan in 3D with GRASS GIS 7

brainscan1Last year (2013) I “enjoyed” a brain CT scan in order to identify a post-surgery issue – luckily nothing found. Being in Italy, like all patients I received a CD-ROM with the scan data on it: so, something to play with! In this article I’ll show how to easily turn 2D scan data into a volumetric (voxel) visualization.

The CT scan data come in a DICOM format which ImageMagick is able to read and convert. Knowing that, we furthermore need the open source software packages GRASS GIS 7 and Paraview to get the job done.

First of all, we create a new XY (unprojected) GRASS location to import the data into:

# create a new, empty location (or use the Location wizard):
grass70 -c ~/grassdata/brain_ct

We now start GRASS GIS 7 with that location. After mounting the CD-ROM we navigate into the image directory therein. The directory name depends on the type of CT scanner which was used in the hospital. The file name suffix may be .IMA.

Now we count the number of images, convert and import them into GRASS GIS:

# list and count
LIST=`ls -1 *.IMA`
MAX=`echo $LIST | wc -w`

# import into XY location:
curr=1
for i in $LIST ; do

# pretty print the numbers to 000X for easier looping:
curr=`echo $curr | awk ‘{printf “%04d\n”, $1}’`
convert “$i” brain.$curr.png
r.in.gdal in=brain.$curr.png out=brain.$curr
r.null brain.$curr setnull=0
rm -f brain.$curr.png
curr=`expr $curr + 1`

done

At this point all CT slices are imported in an ordered way. For extra fun, we can animate the 2D slices in g.gui.animation:

Animation of brain scan slices
(click to enlarge)

# enter in one line:
g.gui.animation rast=`g.mlist -e rast separator=comma pattern=”brain*”`

The tool allows to export as animated GIF or AVI:

Animation of brain scan slices (click to enlarge)

Now it is time to generate a volume:

# first count number of available layers
g.mlist rast pat=”brain*” | wc -l

# now set 3D region to number of available layers (as number of depths)
g.region rast=brain.0003 b=1 t=$MAX -p3

At this point the computational region is properly defined to our 3D raster space. Time to convert the 2D slices into voxels by stacking them on top of each other:

# convert 2D slices to 3D slices:
r.to.rast3 `g.mlist rast pat=”brain*” sep=,` out=brain_vol

We can now look at the volume with GRASS GIS’ wxNVIZ or preferably the extremely powerful Paraview. The latter requires an export of the volume to VTK format:

# fetch some environment variables
eval `g.gisenv -s`
# export GRASS voxels to VTK 3D as 3D points, with scaled z values:
SCALE=2
g.message “Exporting to VTK format, scale factor: $SCALE”
r3.out.vtk brain_vol dp=2 elevscale=$SCALE \
output=${PREFIX}_${MAPSET}_brain_vol_scaled${SCALE}.vtk -p

Eventually we can open this new VTK file in Paraview for visual exploration:

# show as volume
# In Paraview: Properties: Apply; Display Repres: volume; etc.
paraview –data=brain_s1_vol_scaled2.vtk

markus_brain_ct_scan3 markus_brain_ct_scan4 markus_brain_ct_scan2

 

 

 

 

 

 

 

 

 

 

 

 

Fairly easy!

BTW: I have a scan of my non-smoker lungs as well :-)

The post Rendering a brain CT scan in 3D with GRASS GIS 7 appeared first on GFOSS Blog | GRASS GIS Courses.

QGIS Layer Tree API (Part 2)

In part 1 we covered how to access the project’s layer tree and read its data. Now let’s focus on building and manipulating layer trees. We’ll also look at how to receive updates about changes within a layer tree.

Starting with an empty project, first we will get access to the layer tree and create some memory layers for testing:

root = QgsProject.instance().layerTreeRoot()

layer1 = QgsVectorLayer("Point", "Layer 1", "memory")
layer2 = QgsVectorLayer("Polygon", "Layer 2", "memory")

Adding Nodes

Now let’s add some layers to the project’s layer tree. There are two ways of doing that:

  1. Explicit addition. This is done with the addLayer() or insertLayer() call of the QgsLayerTreeGroup class. The former appends to the group node, while the latter allows you to specify the index at which the layer should be added.

    # step 1: add the layer to the registry, False indicates not to add to the layer tree
    QgsMapLayerRegistry.instance().addMapLayer(layer1, False)
    # step 2: append layer to the root group node
    node_layer1 = root.addLayer(layer1)
    
  2. Implicit addition. The project’s layer tree is connected to the layer registry and listens for the addition and removal of layers. When a layer is added to the registry, it will be automatically added to the layer tree. It is therefore enough to simply add a layer to the map layer registry (leaving the second argument of addMapLayer() with its default value of True):

    QgsMapLayerRegistry.instance().addMapLayer(layer1)
    

    This behaviour is facilitated by the QgsLayerTreeRegistryBridge class. By default it inserts layers at the first position of the root node. The insertion point for new layers can be changed – within the QGIS application the insertion point is updated whenever the current selection in the layer tree view changes.

Groups can be added using the addGroup() or insertGroup() calls of the QgsLayerTreeGroup class:

node_group1 = root.insertGroup(0, "Group 1")
# add another sub-group to group1
node_subgroup1 = node_group1.addGroup("Sub-group 1")

There are also the general addChildNode(), insertChildNode() and insertChildNodes() calls that allow the addition of existing nodes:

QgsMapLayerRegistry.instance().addMapLayer(layer2, False)

node_layer2 = QgsLayerTreeLayer(layer2)
root.insertChildNode(0, node_layer2)

node_group2 = QgsLayerTreeGroup("Group 2")
root.addChildNode(node_group2)

Nodes that are being added must not have any parent yet (i.e. being part of some layer tree). On the other hand, the nodes that get inserted may already have children, so it is possible to create a whole sub-tree and then add it in one operation to the project’s layer tree.

Removing Nodes

The removal of nodes from a layer tree is always done from the parent group node. For example, nodes displayed as top-level items need to be removed from the root node. There are several ways of removing them. The most general form is to use the removeChildren() method that takes two arguments: the index of the first child node to be removed and how many child nodes to remove. Removal of a group node will also remove all of its children.

There are several convenience methods for removal:

root.removeChildNode(node_group2)

root.removeLayer(layer1)

There is one more way to remove layers from the project’s layer tree:

QgsMapLayerRegistry.instance().addMapLayer(layer1)

The project’s layer tree is notified when any map layers are being removed from the map layer registry and the layer nodes representing affected layers will be automatically removed from the layer tree. This is handled by the QgsLayerTreeRegistryBridge class mentioned earlier.

Moving Nodes

When managing the layer tree, it is often necessary to move some nodes to a different position – within the same parent node or to a different parent node (group). Moving a node is done in three steps: 1. clone the existing node, 2. add the cloned node to the desired place in layer tree, 3. remove the original node. The following code assumes that the existing node we move is a child of the root node:

cloned_group1 = node_group1.clone()
root.insertChildNode(0, cloned_group1)
root.removeChildNode(node_group1)

Modifying Nodes

There are a number of operations one can do with nodes:

  1. Rename. Both group and layer nodes can be renamed. For layer nodes this will modify the name directly inside the map layers.

    node_group1.setName("Group X")
    node_layer2.setLayerName("Layer X")
    
  2. Change visibility. This is actually a check state (checked or unchecked, for group nodes also partially checked) that is associated with the node and normally related to the visibility of layers and groups in the map canvas. In the GUI, the layer tree view is capable of showing a check box for changing the state.

    print node_group1.isVisible()
    node_group1.setVisible(Qt.Checked)
    node_layer2.setVisible(Qt.Unchecked)
    
  3. Change expanded state. The boolean expanded/collapsed state refers to how the node should be shown in layer tree view in the GUI – whether its children should be shown or hidden.

    print node_group1.isExpanded()
    node_group1.setExpanded(False)
    
  4. Change custom properties. Each node may have some associated custom properties. The properties are key-value pairs, keys being strings, values being of variant type (QVariant). They can be used by other components of QGIS or plugins to store additional data. Custom properties are preserved when a layer tree is saved and loaded.

    Use the customProperties() call to get a list of keys of custom properties, then the customProperty() method for getting the value of a particular key. To modify properties, there is a setCustomProperty() method which sets a key-value pair and a removeCustomProperty() method to remove a pair.

    node_group1.setCustomProperty("test_key", "test_value")
    print node_group1.customProperties()
    print node_group1.customProperty("test_key")
    node_group1.removeCustomProperty("test_key")
    print node_group1.customProperties()
    

Signals from Nodes

There are various signals emitted by nodes which may be used by client code to follow changes to the layer tree. Signals from children are automatically propagated to their parent node, so it is enough to connect to the root node to listen for changes from any level of the tree.

Modification of the Layer Tree Structure

The addition of new nodes always emits a pair of signals – before and after the actual addition. Signals pass information about which node is the parent node and the range of child indices:

  • willAddChildren(node, indexFrom, indexTo)
  • addedChildren(node, indexFrom, indexTo)

In order to access the newly added nodes, it is necessary to use the addedChildren signal.

The following code sample illustrates how to connect to signals emitted from the layer tree. When the last line is executed, two lines from the newly defined methods should be printed to the console:

def onWillAddChildren(node, indexFrom, indexTo):
  print "WILL ADD", node, indexFrom, indexTo
def onAddedChildren(node, indexFrom, indexTo):
  print "ADDED", node, indexFrom, indexTo

root.willAddChildren.connect(onWillAddChildren)
root.addedChildren.connect(onAddedChildren)

g = root.addGroup("My Group")

Removal of nodes is handled in a very similar manner to the addition – there is also a pair of signals:

  • willRemoveChildren(node, indexFrom, indexTo)
  • removedChildren(node, indexFrom, indexTo)

This time in order to access nodes being removed it is necessary to connect to the willRemoveChildren signal.

Modification of Tree Nodes

There are a few more signals that allow monitoring of internal changes to nodes:

  • a node is checked or unchecked: visibilityChanged(node, state)
  • a node’s custom property is defined or removed: customPropertyChanged(node, key)
  • a node gets expanded or collapsed: expandedChanged(node, expanded)

Summary

We have covered how to make changes to a layer tree structure and how to listen for changes possibly made by other pieces of code. In a future post we look at GUI components for displaying and modifying the layer tree and the connection between map canvas and layer tree.

QGIS Layer Tree API (Part 2)

In the first part we have covered how to access the project’s layer tree and read the data stored in the tree. Now let’s focus on building layer trees and manipulating with them. We will also have a look at how to receive updates about changes within a layer tree.

In an empty project, first we will get access to the layer tree and create some memory layers for testing:

root = QgsProject.instance().layerTreeRoot()

layer1 = QgsVectorLayer("Point", "Layer 1", "memory")
layer2 = QgsVectorLayer("Polygon", "Layer 2", "memory")

Adding Nodes

Now let’s add some layers to the project’s layer tree. There are two ways of doing that:

  1. Explicit addition. This is done with addLayer() or insertLayer() call of QgsLayerTreeGroup class. The former one appends to the group node, while the latter allows you to specify index at which the layer should be added.

    # step 1: add the layer to registry, False means do not add to layer tree
    QgsMapLayerRegistry.instance().addMapLayer(layer1, False)
    # step 2: append layer to the root group node
    node_layer1 = root.addLayer(layer1)
    
  2. Implicit addition. Project’s layer tree is connected to the layer registry and listens to the additions and removals of layers. When a layer is added to the registry, it will automatically be added to the layer tree. It is therefore enough to add a layer to the map layer registry (leaving the second argument with default value True):

    QgsMapLayerRegistry.instance().addMapLayer(layer1)
    

    This behaviour is facilitated by QgsLayerTreeRegistryBridge class. By default it inserts layers at the first position of the root node. The insertion point for new layers can be changed – within QGIS application the insertion point is updated whenever the current selection in layer tree view changes.

Adding groups is done with addGroup() or insertGroup() calls of QgsLayerTreeGroup class:

node_group1 = root.insertGroup(0, "Group 1")
# add another sub-group to group1
node_subgroup1 = node_group1.addGroup("Sub-group 1")

There are also general addChildNode(), insertChildNode() and insertChildNodes() calls that allow addition of existing nodes:

QgsMapLayerRegistry.instance().addMapLayer(layer2, False)

node_layer2 = QgsLayerTreeLayer(layer2)
root.insertChildNode(0, node_layer2)

node_group2 = QgsLayerTreeGroup("Group 2")
root.addChildNode(node_group2)

Nodes that are being added must not have any parent yet (i.e. being part of some layer tree). On the other hand, the nodes that get inserted may already have children, so it is possible to create a whole sub-tree and then add it at once to the project’s layer tree.

Removing Nodes

Removal of nodes from a layer tree is always done from parent group node. For example, nodes displayed as top-level items need to be removed from root node. There are several ways how to remove them. The most general form is to use removeChildren() method that takes two arguments: index of the first child node to be removed and how many child nodes to remove. Removal of a group node will cause all its children to be removed, too.

There are several convenience methods for removal:

root.removeChildNode(node_group2)

root.removeLayer(layer1)

If a layer tree is used with QgsLayerTreeRegistryBridge (which is true for project’s layer tree), there is also implicit removal of layer nodes of map layers from layer tree that get removed from the map layer registry.

Moving Nodes

When managing layer tree, it is often necessary to move some nodes to a different position – within the same parent node or to a different parent node (group). Moving a node is done in three steps: 1. clone the existing node, 2. add cloned node to desired place in layer tree, 3. remove the original node. The following code assumes that the existing node we move is child of the root node:

cloned_group1 = node_group1.clone()
root.insertChildNode(0, cloned_group1)
root.removeChildNode(node_group1)

Modifying Nodes

There are several operations one can do with nodes:

  1. Rename. Both group and layer nodes can be renamed. For layer nodes this will modify the name directly inside the map layers.

    node_group1.setName("Group X")
    node_layer2.setLayerName("Layer X")
    
  2. Change visibility. This is actually a check state (checked or unchecked, for group nodes also partially checked) that is associated with the node and normally it is related to visibility of layers and groups in map canvas. In GUI, the layer tree view is capable of showing a check box for changing the state.

    print node_group1.isVisible()
    node_group1.setVisible(Qt.Checked)
    node_layer2.setVisible(Qt.Unchecked)
    
  3. Change expanded state. The boolean expanded/collapsed state refers to how the node should be shown in layer tree view in GUI, whether its children should be shown or hidden.

    print node_group1.isExpanded()
    node_group1.setExpanded(False)
    
  4. Change custom properties. Each node may have some custom properties associated with it. The properties are key-value pairs, keys being strings, values being variant type (QVariant). They can be used by other components of QGIS or plugins to store some additional data. Custom properties are kept when a layer tree is saved and loaded.

    Use customProperties() call to get a list of keys of custom properties, then customProperty() method for getting value of particular key. For modification of the properties, there is setCustomProperty() method to set a key-value pair and removeCustomProperty() to remove a pair.

    node_group1.setCustomProperty("test_key", "test_value")
    print node_group1.customProperties()
    print node_group1.customProperty("test_key")
    node_group1.removeCustomProperty("test_key")
    print node_group1.customProperties()
    

Signals from Nodes

There are various signals emitted by nodes which may be used by client code to follow changes to the layer tree. Signals from children are automatically propagated to their parent node, so it is enough to connect to the root node to listen changes from any level of the tree.

Modification of Layer Tree Structure

Addition of new nodes always emits a pair of signals – before and after the actual addition. Signals pass information about which node is the parent node and the range of child indices:

  • willAddChildren(node, indexFrom, indexTo)
  • addedChildren(node, indexFrom, indexTo)

In order to be able to access the newly added nodes, it is necessary to use addedChildren signal.

The following code sample illustrates how to connect to signals emitted from layer tree. when the last line is executed, two lines from the newly defined methods should be printed to the console:

def onWillAddChildren(node, indexFrom, indexTo):
  print "WILL ADD", node, indexFrom, indexTo
def onAddedChildren(node, indexFrom, indexTo):
  print "ADDED", node, indexFrom, indexTo

root.willAddChildren.connect(onWillAddChildren)
root.addedChildren.connect(onAddedChildren)

g = root.addGroup("My Group")

Removal of nodes is handled in a very similar manner to the addition – there is also a pair of signals:

  • willRemoveChildren(node, indexFrom, indexTo)
  • removedChildren(node, indexFrom, indexTo)

This time in order to access nodes being removed it is necessary to connect to willRemoveChildren signals.

Modification of Tree Nodes

There are few more signals that allow monitoring of internal changes of nodes:

  • node gets checked or unchecked: visibilityChanged(node, state)
  • node’s custom property is defined or removed: customPropertyChanged(node, key)
  • node gets expanded or collapsed: expandedChanged(node, expanded)

Summary

We have covered how to make changes to a layer tree structure and how to listen to the changes possibly done by other pieces of code. In a post that will follow we look at how GUI components for display and modification of a layer tree and connection between map canvas and layer tree.

  • <<
  • Page 2 of 64 ( 1280 posts )
  • >>

Back to Top

Sponsors