10

I have GPS coordinates which identify aid projects in Africa. Now, I want to re-locate these projects, such that each project is re-located by 100 km in a random direction from its original location.

(Extra: I have country layers; can I force QGIS to re-locate only within the original country?)

I tried the Virtual Layer & Geometry Generator, both didn't work so far. Any leads?

I use QGIS 3.14.15.

0

1 Answer 1

22

This answer has been updated several times to get a solution that gradually adds complexity to the expression. For the final solution, you should go directely to the very bottom (step 5). The history of updating the orignal answer however adds up to a step by step guide of how this solution works, starting with a very easy way. It gives some insight how more complex QGIS expressions work and what can be achieved with it. Each step of complexity is separated by a horizontal rule and named Step 1, Step 2 ... to Step 5 so that you can jump easily to these steps. Step 4 is a detailed explanation about how all the different parts of the expressions come together.


Step 1

You can do this by using the following expression (see below for the extra to re-locate inside of countries):

project ( $geometry , 100000 , radians ( rand ( 0,360) ) ) 

If you need it for visualization purpose only, add a symbol layer, define it as geoemtry generator and paste the expression above. Change 100000 if the units of your CRS is not in meters. Be aware that every time you pan, zoom or perform another activity, the layer is updated and the random points will be on a new location. If you also want a random distance of say between 50 and 100 km, replace 100000 with rand ( 50000, 100000).

If you want the actual geometry, use Menu processing / toolbox / geoemtry by expression to generate a new layer, use the same expression.

Result: red dots: original; blue dots: re-located points.

enter image description here

There is a possibility to create x and y coordinates for the random points in your original points layer without creating a new geometry. To do that, use the field calculator and create a new field, named let's say x_coordinate and y_coordinate. Use the expression x(geometry) and y(geometry) to create a separate field for each that contains the coordinates of the randomly re-placed points whereas geometry is the expression to create the re-placed points (in the screenshot below I used the more sophisticated expression from below). You will get two new fields with values of re-placed x-/y- coordinates. They will be stored parmanently and will not change/update any more (you could use virtual fields to have changing content).

qgis expression editor: create x and y coordinates

With that, you could use this fields to visualize your points in the re-placed position, using a symbol layer with the expression:

make_point(
   "x_coordinate" ,  
   "y_coordiante" 
)

This expression generates the white, re-placed, points in the following sreenshot. If you want, you can remove the original representation of the original, red, points to keep only the re-placed points:

qgis expression editor: geometry generator make_point


Step 2

A refined version to re-locate the points only inside the country. Same solution, but instead of the above expression, use this one here. Be aware to change 'countries' to the name of the layer that contains your country polygons. Both layers as well as the project should be in the same CRS, best use a projected CRS (with a geographic CRS, distance measurements do not make sense).

Again, this layer should have a field 'fid' that contains a unique integer (an not, as stated in an earlier version, any unique field value like country name). You can replace 'fid' in the following expression with any fieldname of your countries layer that contains an integer that is unique:

with_variable ( 
   'boundary',
    boundary ( 
      intersection (
         buffer ( $geometry, 100000),
         geometry(
            get_feature( 
               'countries',
                'fid',
                to_int ( array_to_string (
                   overlay_within (
                      'countries', 
                      fid)
                 ))
            )
          )
       )
   ), 
   line_interpolate_point (
      @boundary,
      rand (
         0,
         length(@boundary)
      )
   )
)

Step 3

And a further perfection of the expression: in the version above, if the original points are near the border (< 100 km), the re-placed points are sometimes located on the border itself and thus less than 100 km away from the orginal point. The above expression creates a circle around the original point and does an intersection with the current country. It than takes the outer line of this intersection and places a random point on this outer boundary - and this boundary includes sometimes segments of the country-borders if the original point is within a distance of 100 km.

To prevent that, I changed the expression accordingly, adding a difference operation: from the boundary just described, it clips (extracts) all country borders. On the remaining lines (only those parts of the original buffer-circle inside the current country of the point remain), the new points are positioned randomly. (see at the bottom of this answer for a revised version of the expression, as not all border segments are clipped completely):

with_variable ( 
   'boundary',
    
    difference(
        boundary ( 
          intersection (
             buffer ( $geometry, 100000),
             geometry(
                get_feature( 
                   'countries',
                    'fid',
                    to_int ( array_to_string (
                       overlay_within (
                          'countries', 
                          fid)
                     ))
                )
              )
           )
      ), 
      boundary ( 
         aggregate ( 
            'countries', 
            'collect',
            $geometry)
      )),
   line_interpolate_point (
      @boundary,
      rand (
         0,
         length(@boundary)
      )
   )
)

Step 4

Following a description with screenshots to better understand how the expression works:

  1. create a buffer of 100 km around the points: buffer ( $geometry, 100000) (polygon)

qgis expression editor: create buffer from points

  1. get the polygon shapes of the countries from the other layer:

expression:

geometry(
   get_feature(
         'countries',
      'fid',
      to_int ( 
         array_to_string (
            overlay_within (
           'countries', 
                   fid)
         )
      )
   )
)

(polygon)

qgis expression editor: feature overlay polygon geometry

  1. Intersection of 1 and 2: only those parts of the buffers (1) that lay within the current country (2)

intersection ( a, b), whereas a is the expression from 1, b the expression from 2

qgis expression editor: intersect

  1. create the outer boundary of 3 (part of the buffer that is inside the country): boundary (c), whereas c is the expression from 3 (line)

qgis expression editor: boundary intersection to create line

  1. get the country borders as line from the polygon boundaries of the countries layer:

expression:

boundary ( 
       aggregate ( 
          'countries', 
          'collect',
          $geometry)
    )

(line) qgis expression editor: boundary aggregate

  1. clip (extract) the country borders (5) from 4 (outer boundary of buffer inside country): difference(d, e), whereas d is the expression from 4 and e is the expression from 5

qgis expression editor: difference, clip country border

  1. Now, set a random point somewhere on the line from no. 6 with the expression line_interpolate_point(geometry,distance) whereas
  • geometry is the expression from 6 (thus: the 100-km circle except the parts laying outside of the country and without the country borders)
  • distance on this line is create randomly with the expression rand(min,max) with min=0 and max= whole length of this geometry line (from no. 6) - so the point will be allocated ranomly somewhere between the start and the end of the line.
  1. The last (or very first part) of the expression is with_variable(name,value,expression): this sets a variable and within this function we have all the elements we created from 1 to 7 together.
  • A: The variable has the name (1st argument) 'boundary' (to be referenced in C, see below) - the name is arbitrary and could be anyting else.

  • B: it's value (2nd argument) is the whole expression created in no. 6 (with the element from 1 to 5), thus the cirle line without country borders and

  • C: the expression (3rd argument) is the the line_interpolate_point expression from no. 7 that takes the input-geometry (the value from B) as variable @boundary, the name defined above (see A) (the @ character in front of the name of the variable is the QGIS convention to address a variable).

The result looks like this - blue: original point with id-label, white: re-located point with id-label:

enter image description here


Step 5

And a (hopfully very last) improvement: as I realized only now, in some cases points still may lay on the border, less than 100 km away from the original point. The border-segments that are crossed by the circle are not clipped, see: qgis expression editor line segments

This is the background why I asked this question:Getting the segment of a line, crossed by another with QGIS expression

But there is an easier solution that I include here for completeness, to have a "final" answer: in step 4, part 5 (get the country borders), instead of using the country borders, I create a buffer of let's say 1 meter around the border, so this part of the expression look slightly different:

buffer (
   boundary ( 
      aggregate ( 
         'countries', 
         'collect',
          $geometry)
       ) ,
    1 
)

Be aware that now, it takes quite some time to render the map image because QGIS has to calculate the buffer around the borders. This last solution is better to use with the geometry by expression tool to create once and for all new geometries, not for symbol layers that start to render each time you pan or zoom.

Increasing the number 1 in the expression, you have the control of how far away the random points should be from the border - setting it to 20000 [meters, in case you use a projected CRS with meters as units] means that re-located points should have at least a distance of 20 km away from the border, while still being 100 km away from the original point.

qgis expression editor: buffer boundary segments

So here is the final expression the generate these random points:

with_variable ( 
   'boundary',

    difference(
        boundary ( 
          intersection (
             buffer ( $geometry, 100000),
             geometry(
                get_feature( 
                   'countries',
                    'fid',
                    to_int ( array_to_string (
                       overlay_within (
                          'countries', 
                          fid)
                     ))
                )
              )
           )
      ), 
      buffer (boundary ( 
         aggregate ( 
            'countries', 
            'collect',
            $geometry)
      ),1)),
   line_interpolate_point (
      @boundary,
      rand (
         0,
         length(@boundary)
      )
   )
)

That's all Folks!


15
  • Thanks! The first version worked - is there any way I can export the "new" coordinates as a csv assigned to the data of the original point? I couldn't make it work with the second option - it won't allow the geometry expression. I pasted project_location_id ( $geometry , 100000 , radians ( rand ( 0,360) ) ) - got it wrong?
    – Franziska
    Commented Nov 27, 2020 at 16:19
  • 1
    project in the expression is a function that projects (re-places) a point to another location - so don't change this part of the expression. Just replace the numbers (the distance, in principle) if it does not suit. You only have to select the right layer above, in the drop-down menu - the same layer you used for the first version. See my screenshot: everything except the input layer (africa2) and the distance (100000) should look exactely the same: i.sstatic.net/mR4bo.png
    – Babel
    Commented Nov 27, 2020 at 16:53
  • 1
    Great! Than it's time to try the other solution I just added - it displaces the points only within your country-layer
    – Babel
    Commented Nov 27, 2020 at 17:52
  • 1
    Amazing explanation! I managed and now want to export the file with the new coordinates. I right-click on Layer and then Export > Save Features as and select Geometry > As XY, however, it only exports the old coordinates (old data file, two columns added with the same latitude/longitude data as original point).
    – Franziska
    Commented Nov 28, 2020 at 13:05
  • 1
    Thanks, @Taras - I'm learning from the best. The honor for not less great answers belongs to you ! There are so many very substantial answers out here by you and I learned a lot from them (and will continue to do so, hopefully).
    – Babel
    Commented Nov 30, 2020 at 10:05

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.