3

In QGIS I`m trying to find an expression (or anything really) that could could do the following: given a layer of polygon and a layer of line that intersects it, I wish to be able to populate or create a field in polygon with the length of the the line where it intersects with the polygon, the reference for the length being always the start point. But this value can only be integer and only store even values.

This image illustrate what I'm trying to achieve. enter image description here

Felippe M.
  • 1,082
  • 5
  • 10

2 Answers2

4

You can find this distance using a virtual layer. It would compute the line intersection, compute the distance from the line start to the intersection start, and from the line start to the intersection end, then it would keep the maximum distance.

Go to the menu Layer > Add Layer > Add/Edit Virtual Layer... and enter the following query. Feel free to add more fields from your layers, and to adjust the names!

select p.*, l.id, 
      max(
       st_distance(
          st_startpoint(
            st_intersection(p.geometry, l.geometry)),  
          st_startpoint(l.geometry)),
       st_distance(
          st_endpoint(
            st_intersection(p.geometry, l.geometry)), 
         st_startpoint(l.geometry))) d
from p
join l 
  on st_intersects(p.geometry, l.geometry)

For the 2nd part of your question, you can play with floor minus the modulo (%) between the floored value and 2.

Taras
  • 32,823
  • 4
  • 66
  • 137
JGH
  • 41,794
  • 3
  • 43
  • 89
4

Edit: meanwhile, there is a slightly easier way to do that, see: https://gis.stackexchange.com/a/410818/88814

There is a single if somehow complex (at least at the first glance) expression you can use to create the value you look for. See at the bottom for a detailed step by step explanation how to build the expression. You could also use this expression to dynamically label you polygons, so it gets updated every time you change the line or polygon layer and that even without creating any new layer or field.

How to apply it: On your polygon layer, create a new field with field calculator and paste the following expression. The only thing you have to adapt is the name of your line layer - in my example it's called line and appears three times (line 8, 18 and 26). Change this to the name of your line layer.

By the way: the expression rounds the value up or down to the next even number (be it smaller or bigger). If you always want to round down to the next smaller even number, it's even simpler - you have to change the end of the expression, see step 12 at the very bottom of this answer for details.

So this is the full expression to apply:

with_variable (
    'measure',
    length (
        make_line (
            start_point (
                collect_geometries (
                    overlay_nearest (
                        'line', 
                        $geometry
                    )
                )
            ),
            closest_point (
                intersection(
                    $geometry, 
                    collect_geometries (
                        overlay_nearest (
                            'line', 
                            $geometry
                        )
                    )
                ),
                end_point (
                    collect_geometries (
                        overlay_nearest (
                            'line', 
                            $geometry
                        )
                    )
                )
            )
        )
    ),
    if (
        round ( @measure) %2 =0,
        round ( @measure),
        if ( 
            @measure -  floor( @measure) < 0.5,
             ceil( @measure),
             floor( @measure) 
        )
    )
)

Screenshot: as you can see, the label on the polygon layer is created dynamically with the same expression as above. The line measurement in the upper polygons from the start_point of the line to the last point of the last polygon is 175.397 m, which is rounded not to 175, but to 176:

enter image description here

How does the expression work? Let's get through it step by step.

  1. As the expression is applied on the polygon layer, you first have to access the line layer and get the nearest feature to the current polygon feature - thus: the line that crosses the polygon. This is done with this expression (the function overlay_nearest is available since QGIS 3.16):
collect_geometries ( 
   overlay_nearest (
      'line', 
         $geometry
   )
)
  1. Now, get the start point of the line. Here and from now on, I will refer to the expression of each step with this syntax: (square brackets combined with @ and a number, e.g. [@1]) - thus you should replace [@1] with the expression from step 1. Thus, to get the start point, use start_point ([@1])

  2. Get the end point of the line: end_point ([@1])

  3. Get the intersection of the line with the polygons - thus for each polygon, get only the segment of the line inside the polygon: intersection($geometry, [@1] )

  4. From this line (from step 4), get the point closest to the end point. This is the point where the line intersects with the polygon (those of the two intersection points farther away from the start point, as can be seen on your screenshot): closest_point ( [@4] , [@3] )

  5. Make a line from the start point to the intersection point created in 5: make_line ( [@2], [@5] )

  6. Measure the length of the line from 6: this is the value you're looking for: length ( [@6])

  7. Now, before going to round the value, we first create a variable to avoid repeatingly inserting all over again the same expression. The variable is the length of the line we created in step 7: with_variable ( 'measure', [@7], [@9] )

  8. Create an if condition to see if the (simply) rounded value is already an even value: in this case, we apply round ( @measure) - with @measure we refer to the variable we stored in step 8 (=the length of the line) : if ( round ( @measure) % 2 =0, round ( @measure), [@10] )

  9. If rounding results in a uneven number, we use another if clause. For values with decimals smaller than 0.5 (line 2 in the expression), we want to bring it up to a round number, thus ceil (@measure) (line 3): 175.397 will result in 176, NOT in 175 as we would expect using round (). In all other cases, we can simply round: 175.6 will result in 176, thus what we want :-)

if ( 
   @measure - floor( @measure) < 0.5,
   ceil( @measure),
   floor( @measure) 
)
  1. Now put all together using the expression in step 8 and fill all the values with syntax like (square brackets combined with @ and a number, e.g. [@11]) with the expression from the belonging step. Repeat as long until there are no expressions in the style (square brackets combined with @ and a number) left and you're done with the complex epression from the beginning of this solution.

  2. If you don't want to round up or down to the next even number, but always get the next smaller even number, replace the last 10 lines of the full expression (see above) with this even simpler expression:

    if (
        floor ( @measure) %2 =0,
        floor ( @measure),
        floor ( @measure)-1
    )
)
Babel
  • 71,072
  • 14
  • 78
  • 208
  • This is absolutely incredible! I don't know if I should ask this in another question, since it's not really a problem about the software, but where can I learn this complex codes in QGIS? I can't find much in youtube, udemy, linkedinlearning or any other. Do you know where should I look for? Thank you! – Felippe M. Mar 16 '21 at 22:15
  • 1
    I learned this expressions simply be reading carefully the explanations in the QGIS expression editor: for every function, you have a help that shows you the syntax etc. to use. I also asked questions here at GIS SE where unsure. A good video tutorial is Ujaval Gandhi's Advanced QGIS Expressions: https://www.youtube.com/watch?v=IXPCec8vgLA – Babel Mar 16 '21 at 22:19
  • 2
    But as you see from my step-by-step guide, you can start with a simple part and than get more complex by combining each step. QGIS geometry generator is great as you can see the result in realtime and quickly adapt the expression. See here with the first 5 screenshots how that works: https://gis.stackexchange.com/a/390011/88814 – Babel Mar 16 '21 at 22:22