7

For example, an azimuth of "225°" to read "S 45° W." I think there's a plugin that can do this, but I want to use this as a label in QField while digitizing.

2 Answers 2

9

I assume you want your output in bearings as per this image below, where the angles shown are relative to north or south (whichever is closer), in either an east or west direction (credit Dr Dave Harbor's Traverse Analysis page)

enter image description here

You can try this expression in Field Calculator, with inspiration from Babel's solution:

with_variable(
    'azim',
    degrees(azimuth(start_point($geometry),end_point($geometry))), --set azimuth variable
    concat(array('N','E','S','W')[if(@azim % 90 = 0,
                                  @azim / 90, --if azimuth is a right angle, use quadrant
                                  floor(((@azim + 90) % 360) / 180)*2)], --otherwise use only N/S
           if((@azim % 90) = 0,
              '', --if azimuth is a right angle, no more text
              ' '||round(abs(90 * (floor(@azim / 90) % 2)-(@azim % 90)) % 90,1) -- otherwise derive bearing angle rounded to 1 deg
                 ||'° '||if(@azim < 180,'E','W')))) --and add bearing direction (E/W)

Result:

enter image description here


Explanation:

  • Modulo: The answer makes heavy use of the modulo operator ( X % Y ), which returns the fractional remainder after X / Y.

    • Think of it as a kind of "counter" where the output resets after a multiple of Y. Simple explanation at CalcWorkshop

    • For example, the modulo operator is used to easily determine if an angle is a right angle (multiple of 90°) or not. A right angle divided by 90 leaves no remainder, i.e. angle % 90 = 0

  • N/E/S/W prefix: Per Babel's answer, first set up an array of cardinal directions in the right order (N, E, S, W). Arrays are zero-indexed (start from 0), so the index of those values is 0, 1, 2, 3. Then do one of the following:

    • If the angle is a right angle (angle % 90 = 0), you want to assign any one of the 4 cardinal directions (N/E/S/W) as a prefix.

      • First divvy the angles up into corresponding quadrants going clockwise from north (numbered 0, 1, 2, 3) by dividing by 90 (/90) (=1/4th of 360 degrees).
      • Use this result as an array index reference to get the corresponding cardinal direction (e.g. 270° -> 270/90 = 3 -> index 3 -> fourth value in cardinal array -> E)
    • In all other cases, you only want to use N/S as a prefix. We want to determine if the angle is in the north half (azimuth = 0°-90° or 270°-360°) or not and grab the correct cardinal direction from the same array. (Could also carry out this section with an if statement)

      • First "rotate" everything to the left (+90); azimuths that were 0°-90° will now be 90°-180°, and azimuths that were from 270°-360° would be 360°-450°

      • Then "wrap around" the excessive values (360°-450°) back to 0°-90° using modulo 360 (% 360).

      • Divide the result by 180 (/180) and apply floor() (round down) to get a value of 0 for the north half and 1 for the south half.

      • Then multiply that by 2 (*2) to get a value of 0 for the north half and 2 for the south half, which can now be used directly as an index reference to grab the correct cardinal values from the N/E/S/W array (i.e. 0 or 2 = N or S).

The rest below is not applied if the angle is a right angle (angle % 90 = 0).

  • Bearing angle (concept): This is where it got a bit complicated and I welcome any offers to tidy up the expression.

    • First let's look at the azimuths if we modulo by 90 degrees (@azim % 90) - i.e. it resets after every 90 degrees, on the left hand side of the image below - and compare it to the desired outcome on the right hand side (bearing values).

    • We can get the right values for the top-right (NE) and bottom-left (SW) corner straight away. But we want the modulo "direction" to be "inverted" for the top-left (NW) and bottom-right (SE) quadrants.

    • Mathematically this operation can be described as 90 degrees minus the modulo result (90 - (@azim % 90)).

enter image description here

  • Bearing angle (execution): We need to apply this "inversion" (90 -) only in the NW and SE quadrants.

    • If we can get the NW and SE quadrants to have a value of 1 and the other quadrants a value of 0, we can use that as a toggle:

    enter image description here

    • How do we identify the angles in the NW and SE quadrants? Use floor(@azim / 90) per Babel's answer to generate 4 distinct categories going clockwise: 0, 1, 2, 3. Apply modulo 2 (% 2) to convert to 0, 1, 0, 1.

    • Apply this 0/1 multiplier to 90 to get 90 - (@azim % 90) for the NW and SE quadrants, and 0 - (@azim % 90) for the NE and SW quarters.

    enter image description here

    • We don't want a negative value for the NE and SW quarters though, so wrap an absolute value function (abs()) around the calculation to convert all results to positive values.

    • You have now calculated the bearing angle.

  • Bearing direction: if the azimuth is less than 180°, it's east, otherwise it's west, so tack on a simple if(@azim < 180,'E','W')

  • Concatenate: add on spaces and ° symbol etc.

2
  • 2
    The only thing I love more than QGIS is the community around it.
    – GISKon
    Commented Jan 9 at 1:57
  • 3
    You're most welcome, I'm surprised this issue hasn't been asked/answered on GIS.SE before. Most other solutions out there use a lot more conditional statements (if azimuth is between 0 - 90, do x, if it's between 90 - 180, do y...) that is much easier to write and understand but can be a bit unwieldly so trying to find a more streamlined/mathematical approach was interesting
    – she_weeds
    Commented Jan 9 at 4:54
4

Create the actual azimuth of the line with azimuth() function. To get the 8 major orientations (azimuths: N, NE, E, SE, S, SW, W, NW), you need to classify all angle values from 0 to 360 to 8 classes (or slightly modify it if you need another number of classes) so that e.g. the wedge from 22.5 to 67.5 (with 45 in the middle) is one group.

Label value created dynamically from the expression: enter image description here

To do so, assign the line's azimuth in wedges of 22.5 degrees width (16th part of 360, see image above) by dividing the azimuth by 22.5, then round this down (so e.g. an input azimuth of 30 results in 1, 60 results in 2). Then divide this by 2 and round again, e.g.:

  • 1/2 = 0.5, rounded -> 1
  • 2/2 = 1, rounded -> 1. Like this, all line azimuths from 22.5 to 67.5 result in 1, the category for the NE orientation. Similarily, 67.5 to 112.5 results in 2 (=E orientation) etc. The same basic idea can be used for other categorizations.

Then create an array with array() with the textstring and number of elements you want to use for the labels and use the classes created before as index operator [] to get the corresponding array element.

The expression to use looks as follows:

with_variable(
    'azim',
    degrees (azimuth (start_point($geometry),end_point($geometry))),
    replace (
        array('N_','N_E','E_','S_E','S_','S_W','W_','N_W','N')[round(floor(@azim/22.5)/2)],
        '_',
        ' '  || round (@azim,1)  || '° ')
)

In the expression above, I use an underscore _ as placeholder that is replaced by the actual azimuth value. If you want to have it rounded as in your question, you may replace the array to array('N 0 °','N 45° E','E 90°','S 45° E','S 180°','S 45° W','W 90°','N 45° W','N 0°') or whatever you like.

enter image description here

6
  • 1
    Great use of arrays but I think there's a misunderstanding of what bearing means - the segments don't work that way (it's quadrants, not 8ths), and the output angles are still the azimuth (degrees from north), not the bearing (degrees from north or south whichever is closer, plus E/W suffix). I was going to put the solution in the comments but it ended up being very long so will put in another answer
    – she_weeds
    Commented Jan 9 at 1:33
  • @Babel I appreciate the reply. But I'm thinking about land surveying bearings. N is 0° and S is 0°. E is 90° and W is 90°. And angles are measure by each quadrant. For example N 76.32° W, or S 1.12° E The number would never be over 90°
    – GISKon
    Commented Jan 9 at 1:51
  • 1
    @Babel I think this may just be a language/subject matter experience issue, "bearings" along with the provided format (S 45° W) was clear to me as a reference to survey bearings, the methods/description of which are very much established and are included on Wikipedia along with top search results for ("azimuth vs bearing") and so on. en.m.wikipedia.org/wiki/Bearing_(angle)
    – she_weeds
    Commented Jan 9 at 7:50
  • 1
    @she_weeds I was not aware of this standard,should have checked it. On the other hand, providing a short link/screenshot (as you did in your answer and comment) to the bearing definition in the question body would not be expected too much, would it? The question asked is of poor quality, the core of it consisting of less then 10 words, containing only an example, not even an actual question, even less an explicit definition of what exactly OP is looking for (the word "bearing" appears only in the title and tags). The rest of the question body, like 2/3 of it, just mentions "label, not plugin".
    – Babel
    Commented Jan 9 at 8:54
  • 1
    In my opinion, this is not best practice for good questions here that are helping the community. In recent time, I got the impression that questions get more and more lazy, OPs revealing about 10% of what they expect, letting the answerers to guess the remaining 90% to be guessed. So most effort for documenting the use case is on the part of those answering, whereas the benefit of solving a problem is on the part of those asking. To get good answers, OPs should invest a minimal effort in asking good, explicit questions as this is a community effort and should be useful for others as well.
    – Babel
    Commented Jan 9 at 8:57

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.