QGIS Slope Symbology – Drawing Slope Signature in QGIS with Geometry Generator

geometry-generatorqgisslopesymbology

I would like to have a slope signature (in German "Böschungsschraffen") like this :

There are long and short lines going from a upper level to a lower level representing the slope.

I just want to draw the signature outgoing from an upper-level line to the lower-level line. I tried it with simple Marker-Line-Symbology but this is not working on edges as marked with red arrows. Maybe the Geometry Generator could do the job, but it seems very advanced.

This is a gravel pit where I tried "Böschungsschraffen" but the red arrows shows the problems I got:
enter image description here

Best Answer

Automatic solution

I created a model that automatically creates the lines as you want as a layer style. Run the model, make the inputs and you get the style you want. Details about how I did it: see below.

When running the model, you need to define 5 inputs: an inner (1) and an outer (2) vector layer (polygons or lines), the interval between the lines you want to create (3) and the files used for styling the output inner layer (4: these are the lines you want) and outer (5, no style at all) layers. The two style files (4 and 5) are included in the zip file together with the model itself, you can download everything here: https://drive.switch.ch/index.php/s/i6AkSCXxLFSNXxX

You can avoid having to define the two style files if you define them in the model: hardcode the path to the two style files. For this, change the two Set layer style entries and instead of Model input change to Value and enter your path/filename. Then delete the inner style and outer style inputs.

enter image description here

Solution: the prinicple

To get correct lines at corners you must combine two styles: one for "outward/convex" corners, one for "inward/concave" corners. Create points at regular intervals on inner and outer polygon boundary, then first create the line from each point of the inner layer to the closest point of the outer layer. Second, identify those points of the outer layer that have not yet been connected and create additional lines from these points to the closest point of the inner layer.

Solution, connecting red (inner) and blue (outer) points - combination of two symbol layer of type Geometry generator:

enter image description here

Step by step

Use QGIS expressions with geometry generator.

  1. Create points at regular intervals along the boundary of the inner and outer polygons, thus creating two point layers inner and outer. Use e.g. Points along geometry tool or this expression with Geometry by expression (and run Multipart to single parts), where 2 is the interval (change this to fit your needs):

    collect_geometries (
        array_foreach (
            generate_series (0, length(boundary ($geometry)), 2),
            line_interpolate_point (boundary($geometry), @element)
        )
    )
    

    Result: points layers inner (red) and outer (blue): enter image description here

  2. Create slope lines from each point of the inner layer to the closest point of the outer layer with this expression (e.g. with Geometry Generator) and change outer to the layer name you use:

     with_variable (
         'line',
         make_line (
             $geometry,
             overlay_nearest ('outer',$geometry)[0]
         ),
         line_substring (@line,0, length(@line)*($id%2+1 )/2)
     )
    

    If you want the half lines to start from the outer layer, change the second last line to: line_substring (@line, length(@line)*($id%2+0)/2, length(@line))

    Result: lines from all red points to the closest blue point:

    enter image description here

  3. After the first part, you now need to create lines from the points on the outer layer that have not been used yet. Add another symbol layer of type Geometry generator with this expression (again replacing outer with the layer name you use) - see below for explanation:

     case when $id= minimum ($id) then 
     collect_geometries(
         with_variable(
             'array',
             array_foreach (
                 array_foreach ( 
                     array_filter(
                         array_foreach (
                             aggregate ('outer', 'array_agg', $id),
                             if (
                                 array_contains( 
                                     array_agg (overlay_nearest ('outer',$id)[0]),
                                     @element
                                 ),
                                 '',
                                 @element
                             )
                         ), 
                         @element<>''
                     ),
                     geometry (
                         get_feature_by_id(
                             'outer',
                             @element
                         )
                     )
                 ),
                 make_line (
                     @element,
                     closest_point (
                         collect( $geometry),
                         @element
                     )
                 )
             ),
             array_foreach (
                 generate_series (0, array_length( @array)-1),
                 line_substring(
                     @array[@element],
                     length (@array[@element])*(@element%2+0)/2, 
                     length(@array[@element])
                 )
             )
         )
     )
     end
    

    Result: lines in red (color for visualization only to see the difference from lines created before): they connect blue points not yet connected in step 2 above with the closest red point:

    enter image description here

Variant:

To change the half-size lines of step 3 to be outgoing from the outer line (not the inner), change the line_substring() part of the above expression (9th last to 5th last line) like this:

line_substring(
    @array[@element],
    0,
    length (@array[@element])*(@element%2+1)/2
)

enter image description here

Explanation of step 3 from above

  1. For performance reasons, just run the expression once and not repeatedly for each feature as it would return the same output for each feature. Use a case ... when condition (line 1) with $id= minimum ($id) - so just for the first feature you have.
  2. Get all features form the outer (blue) layer: create an array of all id's of each point from the outer layer using aggregate ('outer', 'array_agg', $id) (line 9)
  3. Get all features of the outer (blue) layer that are connected in the first round (step 2 above) with this expression: array_agg (overlay_nearest ('outer',$id)[0]) (line 12)
  4. Use array_contains() (line 11) with array_foreach() (line 8) to check for each point from step 1 if it is contained in the points from step 2
  5. Use the ids returned in step 3 (ids of the outer/blue points not that have not been connected in the first round) with geometry() and get_feature_by_id() (lines 21-22) to convert the id to the features's point geometry.
  6. Use this point to make a line connecting it to the closest point on the inner (red) layer using make_line () (line 28)
  7. Use a line_substring() function (line 38) similar to the one above to create alternating full and half lines. Add another array_foreach () (line 36) to create alternating values of 1 and 0.5 (end of line 40)
Related Question