Qgis functions for labels

One of the great things about Qgis is being able to use expressions in a range of things. This is just a little bit about using a function to combine various fields together to make a caption on a label of a feature.
In NZ Rail Maps I have features called Locations which are a range of things – stations and mileposts are the main ones.  These can have a name, and up to two distances attached to them. There are varying requirements depending on the type of location and the label caption is made up of seven fields combined over three lines. This is the function that is used to create that label.
if(coalesce(“caption” ,”) = ”, ”, “caption”) ||
if (coalesce(“Distance”,-1) = -1,”, if(coalesce(“caption” ,”) = ”,”,’n’ )|| trim(concat(“Distance”,’ ‘, “Unit”, ‘ ‘, “Label”))) ||
if(coalesce(“Distance 2″,-1) = -1,”,’n’ || trim(concat(“Distance 2”,’ ‘, “Unit 2″, ‘ ‘,”Label 2”)))
Basically it consists of the output of three conditional evaluations (if() statements) concatenated together. Each of these if statements is responsible for one line of the label.
I’ll break it down line by line
if(coalesce(“caption” ,”) = ”, ”, “caption”) || 
So that first line is pretty simple. It checks to see if the caption has anything in it. The coalesce() function is used to overcome null propagation, in which if the caption was a null, that null would propagate through the entire function and result in a only null being returned regardless of the rest of the function. So the “caption” field is output if it is non null. The || on the end simply adds the result of the next if statement to the label caption.
if (coalesce(“Distance”,-1) = -1,”, if(coalesce(“caption” ,”) = ”,”,’n’ )|| trim(concat(“Distance”,’ ‘, “Unit”, ‘ ‘, “Label”))) || 
So the first thing we do for the second line is to see if its first field, Distance, is empty (null), again using coalesce(). This time, because it is a numeric field, we use a number for the comparison, instead of a string. If there is a number in there, we use a second if() statement which looks like the first line evaluation of the “caption” field and is intended to make a decision about whether to put a newline character into the label. This ensures that the first line of the label is not a blank line if there is nothing in “caption”. Because while a station will have a caption (its name), a milepost will not be named, instead it will start with a distance. The rest of the statement just adds on the “Unit” and “Label” fields to the second line, and the last thing is the double pipe to concatenate the third line on.
if(coalesce(“Distance 2″,-1) = -1,”,’n’ || trim(concat(“Distance 2”,’ ‘, “Unit 2″, ‘ ‘,”Label 2”)))
The last line is essentially a repeat of the second line, using different fields which are duplications of the ones used to make up the second line. It is simpler because it assumes that there is always a line above it that has already been filled by the previous code and therefore it doesn’t need to check before adding the newline character to move its output down onto the third line of the label.
It took me a lot of work to get this function and after years of using a simpler function that would put out extra blank lines unnecessarily I just wrote the new function last night. Here is the old function:
if(coalesce(“caption” ,”) = ”, ”, “caption” || ‘n’) || trim(concat(“Distance”,’ ‘, “Unit”, ‘ ‘, “Label”, ‘ ‘,”Distance 2″,’ ‘, “Unit 2″, ‘ ‘,”Label 2”, ‘ ‘))
That one only uses two lines and the second line will always be present, but will be blank if the fields are empty. You can have stations for which you don’t know a distance, so that second line is optional. Hence in the new function we have code that checks field values to see if the second and third lines are needed, and if they aren’t, nothing is output, rather than a blank line.
The reason why I haven’t had this function up to now is because I hadn’t realised it would be possible to concatenate multiple if() statements together. If everything had to fit in one if() statement that statement would be extremely complex. I know from writing functions in spreadsheets that you can do a lot of complex stuff with if() statements already but having multiple nested statements makes for a very complex, hard to understand and debug piece of code, and because of that, since I formally studied programming at CPIT more than 20 years ago (which built on my long experience from high school), I discovered how to avoid complex nested conditional statements and simply break all those conditional evaluations up into multiple sequential statements.
So the new label function took a couple of hours to test and debug and get all the bugs out. There are a couple of things that make it easier. One is to code the function in a separate text editor, rather than directly in the Qgis evaluation editor. The other is to test one piece at a time in the editor because its syntax evaluator isn’t very helpful and doesn’t highlight exactly where a problem is. So for this function, each of those three statements was coded in a LibreOffice Writer document, and then pasted one at a time into the Qgis editor to test it.