6

I have data on the latitude and longitude of several locations in Europe (blue). So far I have been able to read this data into QGIS but not more. I would like to plot a transect (red) and then categorise the points into intervals along it.

I’d then like to return the points with their categorisation, A, B, C or D, for example. The intervals do not have to be equal and it would be useful if I could customise their widths later.

Image of central Europe. Blue data points are organised along a red transect split into four sections, A, B, C and D.

How can I go about doing this in QGIS?

I'm suspicious of my result:

enter image description here

since some of my points seem to be lying in the wrong interval. I'd like to create perpendicular lines from the points to the transect to clarify whether the intervals are overlapping or not. Following the help from @Babel I have reached this point:

enter image description here

but am getting an eval error (Cannot convert to geometry).

Using the expression commented by @she_weeds I've been able to add lines from the transect to the points. They aren't yet perpendicular though and sometimes overlap with or don't reach the transect:

enter image description here

The coordinate system I am using is ESPG:3857.

Is this causing the problem?

Taras
  • 32,823
  • 4
  • 66
  • 137
qgisquesls
  • 71
  • 4
  • 1
    The easiest it probably to create a new vector layer and manually draw/digitize the zones – BERA Apr 03 '23 at 16:38
  • Regarding your update with the perpendicular line, try this expression in geometry generator: make_line($geometry,closest_point(overlay_nearest('Transect',$geometry)[0],$geometry)) – she_weeds Apr 05 '23 at 10:46
  • Hello! Thank you, I'm getting the lines now. They aren't perpendicular to the transect though, which is why my points seem to be getting put into the wrong intervals. Can I fix this? – qgisquesls Apr 05 '23 at 14:49
  • Could you share a screenshot of your lines and how they aren't perpendicular? Given the scale of your basemap and divider for the transect distances I'm wondering if you are using a Geographic Coordinate System like EPSG:4326 (units in degrees)? All the answers only really work if you are using a Projected Coordinate System (units in metres or feet). – she_weeds Apr 05 '23 at 22:24
  • Yes! I've included a screenshot of my lines into the question now. Like you said, I'm using a geographic coordinate system. In what way does that change things? – qgisquesls Apr 08 '23 at 15:19

2 Answers2

8

I just wanted to add another solution in case

  1. the intervals are not intended to be equal,
  2. the interval 'names' are not necessarily sequential
  3. the intervals are represented by line geometries, and
  4. you intend to manipulate the intervals by shifting line geometries around

which was my first interpretation of your question before I saw Babel had answered it and it was accepted.

If the above apply, a hard-coded fixed distance and/or sequence wouldn't be appropriate. I will leave this answer here in case a future question comes up with similar parameters...


Requirements

Assume the following:

  • interval_lines is the name of your interval lines layer - change as needed in expression
  • "lineid" is the field in that layer containing the relevant ID you want to label your points with - change as needed in expression
  • You want the points to take on the lineid of the closest line to the LEFT of the point.
  • IMPORTANT: All interval lines must be in the same orientation and the orientation must be south heading to north, or west heading to east. Please use an arrow symbology or similar to visualise the direction of your interval lines. — You can fix the direction of errant lines by selecting them and then using 'Reverse line direction' tool under Processing Toolbox with Edit Features In-Place selected

Expression

Use the following very convoluted expression in your label field (automatically updates), or as a Virtual Field using Field Calculator (also automatically updates).

with_variable('closest_line_array',   --generate CLA - lineid array of closest lines within X map units (result e.g. ['A','B','C'])
    overlay_nearest('interval_lines',
        "lineid",
        limit:=-1,
        max_distance:=500),           --adjust X map units here
    array_get(@closest_line_array,
        array_find(
            array_foreach(@closest_line_array,                   --for EACH element in CLA ...
                if(with_variable('closest_line',                 --1. define CL = geometry of closest line
                       geometry(get_feature('interval_lines', 'lineid', @element)),
                   with_variable('closest_point',                --2. define CP = geometry of point on closest line
                       closest_point(@closest_line, $geometry),
                   azimuth(@closest_point, $geometry) -          --3. calculate azimuth of CP to your point, MINUS ...
                   azimuth(line_interpolate_point(@closest_line, --4. calculate azimuth of line segment on CL
                        line_locate_point(@closest_line,
                            @closest_point) * 1.01),
                        @closest_point)))
                   >0, 'Right', 'Left'                           --5. determine if the CLA element is RIGHT or LEFT of your point (e.g. ['Right','Left','Right'])
                )
            ), 'Left'
        )  --use array_find() to find the position of the first LEFT element (e.g. 1 - for the second element as above)
    )      --use array_get() to return the lineid corresponding to the position of the first LEFT element (i.e. 'B')
)

Example results

Static:

enter image description here

Dynamic:

  • labels update instantly when interval lines are moved.
  • note interval lines are all oriented in the same direction and and go from south to north
  • label adopts attribute of line to LEFT even if there is no line to the right initially (although I presume this is not necessarily your desired outcome).
  • lines are single segments only

enter image description here

Dynamic - west to east

  • interval lines all oriented west to east
  • points adopt attribute of closest line to top (which is left, based on arrow pointing orientation of line)

enter image description here


Left/right determination method adapted from SQL answer by dmitry.v.kiselev.

she_weeds
  • 12,488
  • 1
  • 28
  • 58
  • There is probably an even more elegant solution that uses modulus perhaps to work out right/left regardless of orientation of line (I have tested this out and it only works if lines are oriented south to north, or west to east), but I'll leave that to someone else... – she_weeds Apr 05 '23 at 09:18
  • This looks awesome, thank you. I will eventually have to edit the intervals, I think, so I'll be coming back to your answer :) – qgisquesls Apr 05 '23 at 09:40
  • 2
    @she_weeds: Great answer ! – Babel Apr 05 '23 at 10:31
6

Use this expression: it will automatically adapt if you change the interval (2nd last line). See below for explanation:

array_foreach(
    generate_series(65,90),
    char(@element)
)[floor (
    line_locate_point (
        overlay_nearest (
            'transect',  -- change here: name of transect line layer
            $geometry
        )[0], 
        $geometry
    )/1000000  -- change here: interval
)]

The expression, here used to dynamically label the points (black dotted lines just for visualization purpose): enter image description here

Explanation: use the expression on the point layer. It creates categories (expressed with uppercase letters starting from A) depending on the distance along the transect line:

  1. Project the points to the transect line and measure the distance of the projected point from the start point of the transect line, using line_locate_point() and overlay_nearest() to get the line's geometry.
  2. Categorize the distances: divide the distance by a fixed amount (the interval, here: 1.000.000) and round the number downwards with floor(), resulting in just integer numbers: 0 for the section from the start point up to 1.000.000 m (1000 km) on the transect line, 1 from 1000 to 2000 km and so on.
  3. Change these numbers into characters: 0 -> A, 1 -> B etc. To do so:
    • create an array from 65 (ascii code for A) to 90 (ascii for Z) with generate_series() and array_foreach()
    • convert these numbers to characters with char()
    • from this array, get the value with index (position, 0 for the 1st) x, whereas x (added in [] after the array) is the number calculated in step 2: index 0 = 1st position -> A; index 1 = 2nd position -> B etc.
Babel
  • 71,072
  • 14
  • 78
  • 208
  • Thank you Babel. So am I putting that expression into the Value field in the Single Layers menu of the layer containing the locations? Nothing is happening for me yet. But in the legend the icon for your locations layer and my locations layer is different. Could this be causing the problem (mine is three dots in an arc)? – qgisquesls Apr 04 '23 at 16:42
  • Yes, apply the expression on the point layer with your locations. One option is to use it as label (as in my case). Be sure to change the name of your transect line layer in the expression. Also adapt the distance and make sure your layers are in a projected CRS. If it doesn't work, post a screenshot with your settings – Babel Apr 04 '23 at 17:01
  • It's worked! Thank you, especially for the detailed explanation of your code. How did you put in the black dotted visualisation lines? – qgisquesls Apr 05 '23 at 08:01
  • You're welcome. The dotted lines were created with an additional smybol layer on the point layer of type Geometry Generator, using a variant of the expression in my solution: make_line ($geometry, [expression] ) , where you have to replace [expression] with the part of the expression form my solution starting with line_locate_point and ending in )/1000000 ` – Babel Apr 05 '23 at 08:14