Invasion! or How To Copy Polygons With Expressions in QGIS

How do you invade using your country's polygon?

I got to this thinking that if it might be possible in JavaScript than the QGIS expression engine should be able to do it too.

And it is.



The basis for this is pretty similar to what I did for the flights dataset in "Using Polygons As Markers" with the polygon of the plane. Except here I use one polygon from the layer and the geometry generator.

In the JavaScript post I took the centroid of geometry I wanted to use and calculated pairs of bearing and distance for each point (node) in the polygon, using these pairs I could recreate the polygon around any point I wanted with Turf.js' destination function that takes and original point (our centroid), a bearing (for direction), and a distance, and uses those to create a new point. Now the main problem here, is QGIS doesn't have a function like that. 




However, you can also express the distance between points (on a 2D plane(no pun intended)) by the pythagorean theorem, or if you don't want the distance but the actual point you can find it using a formula like:

    Point( X(Centroid) - X(pointN), Y(Centroid) - Y(pointN))

And if we can recreate all points this way, we can copy our geometry.

Since we need to do this for all the points, we'll use an array. We can use the generate_series function to make an array of numbers the same length as the number of points in our geometry:

generate_series( 1,
num_points(
     geometry(get_feature( 'countries','name','Israel'))
))

Here I used Israel from the natural earth countries layer. What I do is get the specific feature, extract its geometry, count the points and use generate_series to create and array that looks like this:



Now what we do, is use the array_foreach function to do something for each number (yes, these are still numbers, not points) in the array. What I do is use make_point which takes only an X and Y coordinates, and into that function we add two expressions.
We take the centroid of the geometry we want to replace (or if it's a point we can just take the geometry) and we take the distance between the centroid of our copied geometry and pointN (pointN being the first node for the first number in the array, the second for the second and so on). now the distance here between the X (or Y) coordinate of the centroid and the X  coordinate of pointN. So the calculation of for the X coordinate of each element will look like this:


    x(centroid($geometry))-
               (
                x(centroid(geometry(get_feature( 'countries','name','Israel')))) -
                x(point_n(geometry(get_feature( 'countries','name','Israel')),@element))
                )

Now we should a have a new array, having all the points we need to recreate our original polygon and our (somewhat cumbersome) expression will look like this:


    array_foreach(
          generate_series( 1, num_points(
         geometry(get_feature( 'countries','name','Israel'))
          )),
          make_point(
          x(centroid($geometry))-
               (
                x(centroid(geometry(get_feature( 'countries','name','Israel'))))-
               x(point_n(geometry(get_feature( 'countries','name','Israel')),@element))
            ),
           y(centroid($geometry))-
               (
                y(centroid(geometry(get_feature( 'countries','name','Israel'))))-
               y(point_n(geometry(get_feature( 'countries','name','Israel')),@element))
               )
           )
       )

Now this is a bit annoying but you can't create a polygon straight from an array of points in the QGIS expression engine, but you can create a line and you can create a polygon from a closed line (which our line will be since it's a recreated polygon), so we only have to wrap our expression in make_line and wrap that in make_polygon. So our final expression would look like this:


    make_polygon(
       make_line(
        
array_foreach(
              generate_series( 1, num_points(
             geometry(get_feature( 'countries','name','Israel'))
              )),
          make_point(
              x(centroid($geometry))-
                   (
                    x(centroid(geometry(get_feature( 'countries','name','Israel'))))-
                   x(point_n(geometry(get_feature( 'countries','name','Israel')),@element))
                ),
               y(centroid($geometry))-
                   (
                        y(centroid(geometry(get_feature( 'countries','name','Israel'))))-
                       y(point_n(geometry(get_feature( 'countries','name','Israel')),@element))
                   )
                  )
               )

           )
       )

Now I don't recommend replacing all the geometries with this expression, unless you
a. don't have a lot of geometries and
b. the geometry you are recreating is not very complex.
What I did (and that's why the colors are different) was use a rule based symbology for each geometry I want to replace.




Enjoy using this neat party trick, obviously this would also work if you want to copy a specific polygon to a specific point, but remember that it can crash your QGIS instance, so use it wisely.

Comments