7

I have two layers as the whole project in EPSG:4326:

  • polygon layer of countries
  • linestring layer of routes

I want to concatenate the country names (those are stored in "name" attribute) based on intersection with linestring vertices ordered by linestring vertices order.

In my example (vertices numbered with vertex number) I'm expecting the result something like this:

  • for red route: Czech Republic, Slovakia, Hungary, Austria
  • for blue route: Slovakia, Poland, Czech Republic (no matter if result will be arrayed of strings or string, I can handle it later)

enter image description here

I made the various expressions that allow me to get the country names for a particular vertex number, but the concatenation is a problem. I made an expression that iterates over the vertices and for each vertex it should concatenate the intersection country name, here is the code:

array_foreach(
    geometries_to_array(nodes_to_points($geometry)),
    with_variable(
        'n_point',
        @element,
        aggregate(layer:='countries',
        aggregate:='concatenate',
        expression:="name", 
        filter:=intersects($geometry, n_point)
        )
    )
)

I'm on Win 11 Pro, version 23H2; QGIS 3.28.13

I found Error with array_foreach when using together with aggregate in QGIS expression that deals with similar problem as it seems to be the problem/bug of aggregate + array_foreach usage

Can provide the project with dataset if necessary.

2
  • In your filter expression, it should be @n_point, however I did not find a way to have it work (I end-up with as many points that the line has but with the name of le last country repeated as many times)
    – Kasper
    Feb 16 at 16:44
  • 2
    Looks like there is already a reported bug for this: github.com/qgis/QGIS/issues/54159 Feb 17 at 7:56

2 Answers 2

4

You can work around the array/aggregate issue by using nested array_foreach loops. It looks pretty ugly but it spits out the right results for your test dataset!

The trick - which I've never tried before, but which does seem to work - is to assign the @element variable in the outer loop to a new variable, before starting the inner loop. In this case I've used @pt_element. QGIS seems to reserve the @element variable for use in the innermost loop.

So we start by building our two arrays - the list of country features (@countries), and the list of vertices, as per the original question (@vertices).

We then loop through the @vertices and the @countries checking whether a vertex is in that country (within). I've used array_max as a crude way of filtering out the non-NULL values and leaving only the country name that matches the point. There might be cleaner ways.

with_variable('countries',
    aggregate('countries',
              aggregate:='array_agg',
              expression:=@feature
              ),
with_variable('vertices',geometries_to_array(nodes_to_points(@geometry)),
array_foreach(@vertices,
    with_variable('pt_element',@element,
    array_max(
        array_foreach(
            @countries,
            if(
                within(@pt_element,geometry(@element))
            ,attribute(@element,'name'),NULL)
        ))))))

Output as a label (when wrapped in array_to_string):

enter image description here

2
  • This is fantastic! I have tried nested loops before with no success. This opens up a world of possibilities. I just tested it successfully for making a grid of points using Geometry Generator.
    – Matt
    Feb 17 at 9:48
  • @Matt - yeah, I got quite excited when I found that it worked! There are always some problems that seem to run up against the limits of expressions. This seems to push the boundaries a bit. Feb 17 at 10:32
2

First i tried this code, which works and get the countries BUT it's not ordered. Because the the intersection order is ordered by the polygons and not by the nodes.

  aggregate(
        layer:= 'countries',
        aggregate:='concatenate',
        expression:="name",
        concatenator:=', ',
        filter:=intersects($geometry,geometry(@parent))
    )

enter image description here

So i think it have to be done in python. Order is the drawing order.

lyrRoutes = QgsProject.instance().mapLayersByName('routes')[0]
lyrCountries = QgsProject.instance().mapLayersByName('countries')[0]

lyrRoutesCountries_idx = lyrRoutes.fields().indexOf('countries')
lyrCountriesName_idx = lyrCountries.fields().indexOf('NAME')

routepoint = QgsFeature()

with edit(lyrRoutes):
    for route in lyrRoutes.getFeatures():
        route_id = route.id()
        countries_list = []
        route_countries = route.attributes()[lyrRoutesCountries_idx]
        geom = route.geometry()
        for part in geom.parts():
            for p in part.vertices():
                print(p)
                routepoint.setGeometry(QgsGeometry.fromQPointF(p.toQPointF()))
                for country in lyrCountries.getFeatures():
                    country_name = country.attributes()[lyrCountriesName_idx]
                    if routepoint.geometry().within(country.geometry()):
                        countries_list.append(country_name)
                    countries_string = ', '.join(countries_list)
        print(countries_string)
        route['countries'] = countries_string
        lyrRoutes.updateFeature(route)
    
print(countries_list)

enter image description here

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.